Commit 1a427032 authored by Filipe de Lima Brito's avatar Filipe de Lima Brito

Redesign authentication views.

parent a6877b4e
...@@ -2,13 +2,22 @@ package chat.rocket.android.analytics.event ...@@ -2,13 +2,22 @@ package chat.rocket.android.analytics.event
sealed class ScreenViewEvent(val screenName: String) { sealed class ScreenViewEvent(val screenName: String) {
// Authentication
object OnBoarding : ScreenViewEvent("OnBoardingFragment")
object Server : ScreenViewEvent("ServerFragment")
object LoginOptions : ScreenViewEvent("LoginOptionsFragment")
object Login : ScreenViewEvent("LoginFragment")
object TwoFa : ScreenViewEvent("TwoFAFragment")
object SignUp : ScreenViewEvent("SignupFragment")
object RegisterUsername : ScreenViewEvent("RegisterUsernameFragment")
object ResetPassword : ScreenViewEvent("ResetPasswordFragment")
object About : ScreenViewEvent("AboutFragment") object About : ScreenViewEvent("AboutFragment")
object ChatRoom : ScreenViewEvent("ChatRoomFragment") object ChatRoom : ScreenViewEvent("ChatRoomFragment")
object ChatRooms : ScreenViewEvent("ChatRoomsFragment") object ChatRooms : ScreenViewEvent("ChatRoomsFragment")
object CreateChannel : ScreenViewEvent("CreateChannelFragment") object CreateChannel : ScreenViewEvent("CreateChannelFragment")
object FavoriteMessages : ScreenViewEvent("FavoriteMessagesFragment") object FavoriteMessages : ScreenViewEvent("FavoriteMessagesFragment")
object Files : ScreenViewEvent("FilesFragment") object Files : ScreenViewEvent("FilesFragment")
object Login : ScreenViewEvent("LoginFragment")
object MemberBottomSheet : ScreenViewEvent("MemberBottomSheetFragment") object MemberBottomSheet : ScreenViewEvent("MemberBottomSheetFragment")
object Members : ScreenViewEvent("MembersFragment") object Members : ScreenViewEvent("MembersFragment")
object Mentions : ScreenViewEvent("MentionsFragment") object Mentions : ScreenViewEvent("MentionsFragment")
...@@ -17,11 +26,5 @@ sealed class ScreenViewEvent(val screenName: String) { ...@@ -17,11 +26,5 @@ sealed class ScreenViewEvent(val screenName: String) {
object PinnedMessages : ScreenViewEvent("PinnedMessagesFragment") object PinnedMessages : ScreenViewEvent("PinnedMessagesFragment")
object Preferences : ScreenViewEvent("PreferencesFragment") object Preferences : ScreenViewEvent("PreferencesFragment")
object Profile : ScreenViewEvent("ProfileFragment") object Profile : ScreenViewEvent("ProfileFragment")
object RegisterUsername : ScreenViewEvent("RegisterUsernameFragment")
object ResetPassword : ScreenViewEvent("ResetPasswordFragment")
object Server : ScreenViewEvent("ServerFragment")
object Settings : ScreenViewEvent("SettingsFragment") object Settings : ScreenViewEvent("SettingsFragment")
object SignUp : ScreenViewEvent("SignupFragment")
object TwoFa : ScreenViewEvent("TwoFAFragment")
object OnBoarding : ScreenViewEvent("OnBoardingFragment")
} }
package chat.rocket.android.authentication.di package chat.rocket.android.authentication.di
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.presentation.AuthenticationNavigator import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.authentication.ui.AuthenticationActivity import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.dagger.qualifier.ForAuthentication import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerActivity import chat.rocket.android.dagger.scope.PerActivity
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module @Module
class AuthenticationModule { class AuthenticationModule {
@Provides @Provides
@PerActivity @PerActivity
fun provideAuthenticationNavigator(activity: AuthenticationActivity) = AuthenticationNavigator(activity) fun provideAuthenticationNavigator(activity: AuthenticationActivity) =
AuthenticationNavigator(activity)
@Provides
@PerActivity
fun provideJob() = Job()
@Provides
@PerActivity
fun provideLifecycleOwner(activity: AuthenticationActivity): LifecycleOwner = activity
@Provides
@PerActivity
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy =
CancelStrategy(owner, jobs)
} }
\ No newline at end of file
...@@ -3,34 +3,18 @@ package chat.rocket.android.authentication.login.di ...@@ -3,34 +3,18 @@ package chat.rocket.android.authentication.login.di
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.login.presentation.LoginView import chat.rocket.android.authentication.login.presentation.LoginView
import chat.rocket.android.authentication.login.ui.LoginFragment import chat.rocket.android.authentication.login.ui.LoginFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module @Module
class LoginFragmentModule { class LoginFragmentModule {
@Provides @Provides
@PerFragment @PerFragment
fun provideJob() = Job() fun loginView(frag: LoginFragment): LoginView = frag
@Provides @Provides
@PerFragment @PerFragment
fun loginView(frag: LoginFragment): LoginView { fun provideLifecycleOwner(frag: LoginFragment): LifecycleOwner = frag
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: LoginFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
} }
\ No newline at end of file
...@@ -5,113 +5,24 @@ import chat.rocket.android.core.behaviours.MessageView ...@@ -5,113 +5,24 @@ import chat.rocket.android.core.behaviours.MessageView
interface LoginView : LoadingView, MessageView { interface LoginView : LoadingView, MessageView {
/**
* Shows the form view (i.e the username/email and password fields) if it is enabled by the server settings.
*
* REMARK: We must set up the login button listener [setupLoginButtonListener].
* Remember to enable [enableUserInput] or disable [disableUserInput] the view interaction for the user when submitting the form.
*/
fun showFormView()
/**
* Hides the form view.
*/
fun hideFormView()
/**
* Setups the login button when tapped.
*/
fun setupLoginButtonListener()
/**
* Enables the view interactions for the user.
*/
fun enableUserInput()
/**
* Disables the view interactions for the user.
*/
fun disableUserInput()
/**
* Shows the CAS button if the sign in/sign out via CAS protocol is enabled by the server settings.
*
* REMARK: We must set up the CAS button listener before showing it [setupCasButtonListener].
*/
fun showCasButton()
/**
* Hides the CAS button.
*/
fun hideCasButton()
/**
* Setups the CAS button when tapped.
*
* @param casUrl The CAS URL to authenticate with.
* @param casToken The requested token to be sent to the CAS server.
*/
fun setupCasButtonListener(casUrl: String, casToken: String)
/** /**
* Shows the forgot password view if enabled by the server settings. * Shows the forgot password view if enabled by the server settings.
*
* REMARK: We must set up the forgot password view listener [setupForgotPasswordView].
*/ */
fun showForgotPasswordView() fun showForgotPasswordView()
/** /**
* Setups the forgot password view when tapped. * Saves Google Smart Lock credentials.
*/
fun setupForgotPasswordView()
/**
* Adds a custom OAuth button in the oauth view.
*
* @customOauthUrl The custom OAuth url to sets up the button (the listener).
* @state A random string generated by the app, which you'll verify later (to protect against forgery attacks).
* @serviceName The custom OAuth service name.
* @serviceNameColor The custom OAuth service name color (just stylizing).
* @buttonColor The custom OAuth button color (just stylizing).
* @see [enableOauthView]
*/
fun addCustomOauthServiceButton(
customOauthUrl: String,
state: String,
serviceName: String,
serviceNameColor: Int,
buttonColor: Int
)
/**
* Adds a SAML button in the oauth view.
*
* @samlUrl The SAML url to sets up the button (the listener).
* @serviceName The SAML service name.
* @serviceNameColor The SAML service name color (just stylizing).
* @buttonColor The SAML button color (just stylizing).
* @see [enableOauthView]
*/
fun addSamlServiceButton(
samlUrl: String,
samlToken: String,
serviceName: String,
serviceNameColor: Int,
buttonColor: Int
)
/**
* Alerts the user about a wrong inputted username or email.
*/ */
fun alertWrongUsernameOrEmail() fun saveSmartLockCredentials(id: String, password: String)
/** /**
* Alerts the user about a wrong inputted password. * Enables the button to login when the user inputs an username and a password.
*/ */
fun alertWrongPassword() fun enableButtonLogin()
/** /**
* Saves Google Smart Lock credentials. * Disables the button to login when there is not an entered/not blank username or password by
* the user (i.e. The fields are empty/blank)
*/ */
fun saveSmartLockCredentials(id: String, password: String) fun disableButtonLogin()
} }
\ No newline at end of file
package chat.rocket.android.authentication.loginoptions.di package chat.rocket.android.authentication.loginoptions.di
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.login.ui.LoginFragment
import chat.rocket.android.authentication.loginoptions.presentation.LoginOptionsView import chat.rocket.android.authentication.loginoptions.presentation.LoginOptionsView
import chat.rocket.android.authentication.loginoptions.ui.LoginOptionsFragment import chat.rocket.android.authentication.loginoptions.ui.LoginOptionsFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module @Module
class LoginOptionsFragmentModule { class LoginOptionsFragmentModule {
@Provides @Provides
@PerFragment @PerFragment
fun provideJob() = Job() fun loginOptionsView(frag: LoginOptionsFragment): LoginOptionsView = frag
@Provides @Provides
@PerFragment @PerFragment
fun loginOptionsView(frag: LoginOptionsFragment): LoginOptionsView{ fun provideLifecycleOwner(frag: LoginOptionsFragment): LifecycleOwner = frag
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: LoginOptionsFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
} }
\ No newline at end of file
...@@ -4,76 +4,194 @@ import chat.rocket.android.core.behaviours.LoadingView ...@@ -4,76 +4,194 @@ import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView import chat.rocket.android.core.behaviours.MessageView
interface LoginOptionsView : LoadingView, MessageView { interface LoginOptionsView : LoadingView, MessageView {
// OAuth accounts.
/** /**
* Shows the "login by Facebook view if it is enable by the server settings. * Shows the "login by Facebook" view if it is enabled by the server settings.
*
* REMARK: We must set up the Facebook button listener before enabling it
* [setupFacebookButtonListener].
* @see [showAccountsView]
*/ */
fun enableLoginByFacebook() fun enableLoginByFacebook()
/** /**
* Shows the "login by Github" view if it is enable by the server settings. * Setups the Facebook button.
*
* @param facebookOauthUrl The Facebook OAuth URL to authenticate with.
* @param state A random string generated by the app, which you'll verify later
* (to protect against forgery attacks).
*/
fun setupFacebookButtonListener(facebookOauthUrl: String, state: String)
/**
* Shows the "login by Github" view if it is enabled by the server settings.
* *
* REMARK: We must set up the Github button listener before enabling it [setupGithubButtonListener]. * REMARK: We must set up the Github button listener before enabling it
* [setupGithubButtonListener].
* @see [showAccountsView]
*/ */
fun enableLoginByGithub() fun enableLoginByGithub()
/** /**
* Setups the Github button when tapped. * Setups the Github button.
* *
* @param githubUrl The Github OAuth URL to authenticate with. * @param githubUrl The Github OAuth URL to authenticate with.
* @param state A random string generated by the app, which you'll verify later (to protect against forgery attacks). * @param state A random string generated by the app, which you'll verify later
* (to protect against forgery attacks).
*/ */
fun setupGithubButtonListener(githubUrl: String, state: String) fun setupGithubButtonListener(githubUrl: String, state: String)
/** /**
* Shows the "login by Google" view if it is enable by the server settings. * Shows the "login by Google" view if it is enabled by the server settings.
* *
* REMARK: We must set up the Google button listener before enabling it [setupGoogleButtonListener]. * REMARK: We must set up the Google button listener before enabling it
* [setupGoogleButtonListener].
* @see [showAccountsView]
*/ */
fun enableLoginByGoogle() fun enableLoginByGoogle()
/** /**
* Setups the Google button when tapped. * Setups the Google button.
* *
* @param googleUrl The Google OAuth URL to authenticate with. * @param googleUrl The Google OAuth URL to authenticate with.
* @param state A random string generated by the app, which you'll verify later (to protect against forgery attacks). * @param state A random string generated by the app, which you'll verify later
* (to protect against forgery attacks).
*/ */
fun setupGoogleButtonListener(googleUrl: String, state: String) fun setupGoogleButtonListener(googleUrl: String, state: String)
/** /**
* Shows the "login by Linkedin" view if it is enable by the server settings. * Shows the "login by Linkedin" view if it is enabled by the server settings.
* *
* REMARK: We must set up the Linkedin button listener before enabling it [setupLinkedinButtonListener]. * REMARK: We must set up the Linkedin button listener before enabling it
* [setupLinkedinButtonListener].
* @see [showAccountsView]
*/ */
fun enableLoginByLinkedin() fun enableLoginByLinkedin()
/** /**
* Setups the Linkedin button when tapped. * Setups the Linkedin button.
* *
* @param linkedinUrl The Linkedin OAuth URL to authenticate with. * @param linkedinUrl The Linkedin OAuth URL to authenticate with.
* @param state A random string generated by the app, which you'll verify later (to protect against forgery attacks). * @param state A random string generated by the app, which you'll verify later
* (to protect against forgery attacks).
*/ */
fun setupLinkedinButtonListener(linkedinUrl: String, state: String) fun setupLinkedinButtonListener(linkedinUrl: String, state: String)
/** /**
* Setups the Facebook button when tapped. * Shows the "login by Gitlab" view if it is enabled by the server settings.
* *
* @param facebookOauthUrl The Facebook OAuth URL to authenticate with. * REMARK: We must set up the Gitlab button listener before enabling it
* @param state A random string generated by the app, which you'll verify later (to protect against forgery attacks). * [setupGitlabButtonListener].
* @see [showAccountsView]
*/ */
fun setupFacebookButtonListener(facebookOauthUrl: String, state: String) fun enableLoginByGitlab()
/** /**
* Shows the "login by Gitlab" view if it is enable by the server settings. * Setups the Gitlab button.
* *
* REMARK: We must set up the Gitlab button listener before enabling it [setupGitlabButtonListener]. * @param gitlabUrl The Gitlab OAuth URL to authenticate with.
* @param state A random string generated by the app, which you'll verify later
* (to protect against forgery attacks).
*/ */
fun enableLoginByGitlab() fun setupGitlabButtonListener(gitlabUrl: String, state: String)
/** /**
* Setups the Gitlab button when tapped. * Shows the "login by WordPress" view if it is enabled by the server settings.
* *
* @param gitlabUrl The Gitlab OAuth URL to authenticate with. * REMARK: We must set up the Gitlab button listener before enabling it [setupWordpressButtonListener].
*/
fun enableLoginByWordpress()
/**
* Setups the WordPress button when tapped.
*
* @param wordpressUrl The WordPress OAuth URL to authenticate with.
* @param state A random string generated by the app, which you'll verify later (to protect against forgery attacks). * @param state A random string generated by the app, which you'll verify later (to protect against forgery attacks).
*/ */
fun setupGitlabButtonListener(gitlabUrl: String, state: String) fun setupWordpressButtonListener(wordpressUrl: String, state: String)
// CAS account.
/**
* Shows the CAS button if the sign in/sign out via CAS protocol is enabled by the server
* settings.
*
* REMARK: We must set up the CAS button listener before showing it [setupCasButtonListener].
* @see [showAccountsView]
*/
fun enableLoginByCas()
/**
* Setups the CAS button.
*
* @param casUrl The CAS URL to authenticate with.
* @param casToken The requested token to be sent to the CAS server.
*/
fun setupCasButtonListener(casUrl: String, casToken: String)
// Custom OAuth account.
/**
* Adds a custom OAuth button in the accounts container.
*
* @customOauthUrl The custom OAuth url.
* @state A random string generated by the app, which you'll verify later
* (to protect against forgery attacks).
* @serviceName The custom OAuth service name.
* @serviceNameColor The custom OAuth service name color (just stylizing).
* @buttonColor The custom OAuth button color (just stylizing).
* @see [showAccountsView]
*/
fun addCustomOauthButton(
customOauthUrl: String,
state: String,
serviceName: String,
serviceNameColor: Int,
buttonColor: Int
)
// SAML account.
/**
* Adds a SAML button in the accounts container.
*
* @samlUrl The SAML url.
* @serviceName The SAML service name.
* @serviceNameColor The SAML service name color (just stylizing).
* @buttonColor The SAML button color (just stylizing).
* @see [showAccountsView]
*/
fun addSamlButton(
samlUrl: String,
samlToken: String,
serviceName: String,
serviceNameColor: Int,
buttonColor: Int
)
/**
* Shows the accounts container view if there is at least a login via
* OAuth/Custom OAuth/CAS/SAML account enabled by the server settings.
*
* REMARK: We must show at maximum *three* accounts views ([enableLoginByFacebook],
* [enableLoginByGithub], [enableLoginByGoogle], [enableLoginByLinkedin], [enableLoginByGitlab],
* [enableLoginByCas], [addCustomOauthButton] or [addSamlButton]) for the accounts container view.
* If the enabled accounts exceeds 3 we must set up the [setupExpandAccountsView] to show the
* remaining view(s).
*/
fun showAccountsView()
/**
* Setups the expand accounts view to show more accounts views (expanding the accounts view
* interface to show the remaining view(s)).
*/
fun setupExpandAccountsView()
/**
* Shows the "login with e-mail" view if it is enabled by the server settings.
*/
fun showLoginWithEmailButton()
/**
* Shows the "Create new account" view if it is enabled by the server settings.
*/
fun showCreateNewAccountButton()
} }
...@@ -3,19 +3,13 @@ package chat.rocket.android.authentication.onboarding.di ...@@ -3,19 +3,13 @@ package chat.rocket.android.authentication.onboarding.di
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.onboarding.presentation.OnBoardingView import chat.rocket.android.authentication.onboarding.presentation.OnBoardingView
import chat.rocket.android.authentication.onboarding.ui.OnBoardingFragment import chat.rocket.android.authentication.onboarding.ui.OnBoardingFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module @Module
class OnBoardingFragmentModule { class OnBoardingFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
@Provides @Provides
@PerFragment @PerFragment
fun onBoardingView(frag: OnBoardingFragment): OnBoardingView = frag fun onBoardingView(frag: OnBoardingFragment): OnBoardingView = frag
...@@ -23,9 +17,4 @@ class OnBoardingFragmentModule { ...@@ -23,9 +17,4 @@ class OnBoardingFragmentModule {
@Provides @Provides
@PerFragment @PerFragment
fun provideLifecycleOwner(frag: OnBoardingFragment): LifecycleOwner = frag fun provideLifecycleOwner(frag: OnBoardingFragment): LifecycleOwner = frag
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy =
CancelStrategy(owner, jobs)
} }
\ No newline at end of file
...@@ -18,7 +18,7 @@ import chat.rocket.android.util.extensions.setLightStatusBar ...@@ -18,7 +18,7 @@ import chat.rocket.android.util.extensions.setLightStatusBar
import chat.rocket.android.util.extensions.showToast import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.ui import chat.rocket.android.util.extensions.ui
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.app_bar_chat_room.* import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.fragment_authentication_on_boarding.* import kotlinx.android.synthetic.main.fragment_authentication_on_boarding.*
import javax.inject.Inject import javax.inject.Inject
...@@ -48,16 +48,14 @@ class OnBoardingFragment : Fragment(), OnBoardingView { ...@@ -48,16 +48,14 @@ class OnBoardingFragment : Fragment(), OnBoardingView {
private fun setupToobar() { private fun setupToobar() {
with(activity as AuthenticationActivity) { with(activity as AuthenticationActivity) {
view?.let { setLightStatusBar(it) } view?.let { this.setLightStatusBar(it) }
toolbar.isVisible = false toolbar.isVisible = false
} }
} }
private fun setupOnClickListener() { private fun setupOnClickListener() {
connect_with_a_server_container.setOnClickListener { connectWithAServer() } connect_with_a_server_container.setOnClickListener { connectWithAServer() }
join_community_container.setOnClickListener { joinInTheCommunity() } join_community_container.setOnClickListener { joinInTheCommunity() }
create_server_container.setOnClickListener { createANewServer() } create_server_container.setOnClickListener { createANewServer() }
} }
......
...@@ -2,21 +2,8 @@ package chat.rocket.android.authentication.presentation ...@@ -2,21 +2,8 @@ package chat.rocket.android.authentication.presentation
import android.content.Intent import android.content.Intent
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo
import chat.rocket.android.authentication.login.ui.LoginFragment
import chat.rocket.android.authentication.login.ui.TAG_LOGIN_FRAGMENT
import chat.rocket.android.authentication.loginoptions.ui.LoginOptionsFragment
import chat.rocket.android.authentication.loginoptions.ui.TAG_LOGIN_OPTIONS
import chat.rocket.android.authentication.registerusername.ui.RegisterUsernameFragment
import chat.rocket.android.authentication.registerusername.ui.TAG_REGISTER_USERNAME_FRAGMENT
import chat.rocket.android.authentication.resetpassword.ui.ResetPasswordFragment
import chat.rocket.android.authentication.resetpassword.ui.TAG_RESET_PASSWORD_FRAGMENT
import chat.rocket.android.authentication.server.ui.ServerFragment
import chat.rocket.android.authentication.server.ui.TAG_SERVER_FRAGMENT
import chat.rocket.android.authentication.signup.ui.SignupFragment
import chat.rocket.android.authentication.signup.ui.TAG_SIGNUP_FRAGMENT
import chat.rocket.android.authentication.twofactor.ui.TAG_TWO_FA_FRAGMENT
import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment
import chat.rocket.android.authentication.ui.AuthenticationActivity import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.authentication.ui.newServerIntent import chat.rocket.android.authentication.ui.newServerIntent
import chat.rocket.android.main.ui.MainActivity import chat.rocket.android.main.ui.MainActivity
...@@ -28,48 +15,57 @@ import chat.rocket.android.webview.ui.webViewIntent ...@@ -28,48 +15,57 @@ import chat.rocket.android.webview.ui.webViewIntent
class AuthenticationNavigator(internal val activity: AuthenticationActivity) { class AuthenticationNavigator(internal val activity: AuthenticationActivity) {
fun toConnectWithAServer(deepLinkInfo: LoginDeepLinkInfo?) { fun toConnectWithAServer(deepLinkInfo: LoginDeepLinkInfo?) {
activity.addFragmentBackStack(TAG_SERVER_FRAGMENT, R.id.fragment_container) { activity.addFragmentBackStack(ScreenViewEvent.Server.screenName, R.id.fragment_container) {
ServerFragment.newInstance(deepLinkInfo) chat.rocket.android.authentication.server.ui.newInstance(deepLinkInfo)
} }
} }
fun toLoginOptions(server: String) { fun toLoginOptions(server: String, deepLinkInfo: LoginDeepLinkInfo? = null) {
activity.addFragmentBackStack(TAG_LOGIN_OPTIONS, R.id.fragment_container) { activity.addFragmentBackStack(
LoginOptionsFragment.newInstance(server) ScreenViewEvent.LoginOptions.screenName,
R.id.fragment_container
) {
chat.rocket.android.authentication.loginoptions.ui.newInstance(server, deepLinkInfo)
} }
} }
fun toLogin() { fun toTwoFA(username: String, password: String) {
activity.addFragmentBackStack(TAG_LOGIN_FRAGMENT, R.id.fragment_container) { activity.addFragmentBackStack(ScreenViewEvent.TwoFa.screenName, R.id.fragment_container) {
LoginFragment.newInstance() chat.rocket.android.authentication.twofactor.ui.newInstance(username, password)
} }
} }
fun toLogin(deepLinkInfo: LoginDeepLinkInfo) { fun toCreateAccount() {
activity.addFragmentBackStack(TAG_LOGIN_FRAGMENT, R.id.fragment_container) { activity.addFragmentBackStack(ScreenViewEvent.SignUp.screenName, R.id.fragment_container) {
LoginFragment.newInstance(deepLinkInfo) chat.rocket.android.authentication.signup.ui.newInstance()
} }
} }
fun toPreviousView() { fun toLogin() {
activity.toPreviousView() activity.addFragmentBackStack(ScreenViewEvent.Login.screenName, R.id.fragment_container) {
chat.rocket.android.authentication.login.ui.newInstance()
}
} }
fun toTwoFA(username: String, password: String) { fun toForgotPassword() {
activity.addFragmentBackStack(TAG_TWO_FA_FRAGMENT, R.id.fragment_container) { activity.addFragmentBackStack(
TwoFAFragment.newInstance(username, password) ScreenViewEvent.ResetPassword.screenName,
R.id.fragment_container
) {
chat.rocket.android.authentication.resetpassword.ui.newInstance()
} }
} }
fun toSignUp() { fun toPreviousView() {
activity.addFragmentBackStack(TAG_SIGNUP_FRAGMENT, R.id.fragment_container) { activity.toPreviousView()
SignupFragment.newInstance()
}
} }
fun toForgotPassword() { fun toRegisterUsername(userId: String, authToken: String) {
activity.addFragmentBackStack(TAG_RESET_PASSWORD_FRAGMENT, R.id.fragment_container) { activity.addFragmentBackStack(
ResetPasswordFragment.newInstance() ScreenViewEvent.RegisterUsername.screenName,
R.id.fragment_container
) {
chat.rocket.android.authentication.registerusername.ui.newInstance(userId, authToken)
} }
} }
...@@ -78,12 +74,6 @@ class AuthenticationNavigator(internal val activity: AuthenticationActivity) { ...@@ -78,12 +74,6 @@ class AuthenticationNavigator(internal val activity: AuthenticationActivity) {
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold) activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
} }
fun toRegisterUsername(userId: String, authToken: String) {
activity.addFragmentBackStack(TAG_REGISTER_USERNAME_FRAGMENT, R.id.fragment_container) {
RegisterUsernameFragment.newInstance(userId, authToken)
}
}
fun toChatList() { fun toChatList() {
activity.startActivity(Intent(activity, MainActivity::class.java)) activity.startActivity(Intent(activity, MainActivity::class.java))
activity.finish() activity.finish()
...@@ -98,10 +88,4 @@ class AuthenticationNavigator(internal val activity: AuthenticationActivity) { ...@@ -98,10 +88,4 @@ class AuthenticationNavigator(internal val activity: AuthenticationActivity) {
activity.startActivity(activity.newServerIntent()) activity.startActivity(activity.newServerIntent())
activity.finish() activity.finish()
} }
}
fun toCreateAccount() {
activity.addFragmentBackStack("SignUpFragment", R.id.fragment_container) {
SignupFragment.newInstance()
}
}
}
\ No newline at end of file
package chat.rocket.android.authentication.presentation package chat.rocket.android.authentication.presentation
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.* import chat.rocket.android.server.domain.GetAccountInteractor
import chat.rocket.android.server.domain.GetConnectingServerInteractor
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.privacyPolicyUrl import chat.rocket.android.util.extensions.privacyPolicyUrl
import chat.rocket.android.util.extensions.termsOfServiceUrl import chat.rocket.android.util.extensions.termsOfServiceUrl
import javax.inject.Inject import javax.inject.Inject
class AuthenticationPresenter @Inject constructor( class AuthenticationPresenter @Inject constructor(
private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator, private val navigator: AuthenticationNavigator,
private val getCurrentServerInteractor: GetCurrentServerInteractor, private val getCurrentServerInteractor: GetCurrentServerInteractor,
private val getAccountInteractor: GetAccountInteractor, private val getAccountInteractor: GetAccountInteractor,
...@@ -15,33 +22,34 @@ class AuthenticationPresenter @Inject constructor( ...@@ -15,33 +22,34 @@ class AuthenticationPresenter @Inject constructor(
private val tokenRepository: TokenRepository, private val tokenRepository: TokenRepository,
private val serverInteractor: GetConnectingServerInteractor private val serverInteractor: GetConnectingServerInteractor
) { ) {
suspend fun loadCredentials(newServer: Boolean, callback: (authenticated: Boolean) -> Unit) {
val currentServer = getCurrentServerInteractor.get()
val serverToken = currentServer?.let { tokenRepository.get(currentServer) }
val settings = currentServer?.let { settingsRepository.get(currentServer) }
val account = currentServer?.let { getAccountInteractor.get(currentServer) }
account?.let { fun loadCredentials(newServer: Boolean, callback: (isAuthenticated: Boolean) -> Unit) {
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, account.userName) launchUI(strategy) {
} val currentServer = getCurrentServerInteractor.get()
val serverToken = currentServer?.let { tokenRepository.get(currentServer) }
val settings = currentServer?.let { settingsRepository.get(currentServer) }
val account = currentServer?.let { getAccountInteractor.get(currentServer) }
if (newServer || currentServer == null || serverToken == null || settings == null || account?.userName == null) { account?.let {
callback(false) localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, account.userName)
} else { }
callback(true)
navigator.toChatList()
}
}
fun termsOfService(toolbarTitle: String) { if (newServer || currentServer == null ||
serverInteractor.get()?.let { serverToken == null ||
navigator.toWebPage(it.termsOfServiceUrl(), toolbarTitle) settings == null ||
account?.userName == null
) {
callback(false)
} else {
callback(true)
navigator.toChatList()
}
} }
} }
fun privacyPolicy(toolbarTitle: String) { fun termsOfService(toolbarTitle: String) =
serverInteractor.get()?.let { serverInteractor.get()?.let { navigator.toWebPage(it.termsOfServiceUrl(), toolbarTitle) }
navigator.toWebPage(it.privacyPolicyUrl(), toolbarTitle)
} fun privacyPolicy(toolbarTitle: String) =
} serverInteractor.get()?.let { navigator.toWebPage(it.privacyPolicyUrl(), toolbarTitle) }
} }
\ No newline at end of file
...@@ -3,34 +3,18 @@ package chat.rocket.android.authentication.registerusername.di ...@@ -3,34 +3,18 @@ package chat.rocket.android.authentication.registerusername.di
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.registerusername.presentation.RegisterUsernameView import chat.rocket.android.authentication.registerusername.presentation.RegisterUsernameView
import chat.rocket.android.authentication.registerusername.ui.RegisterUsernameFragment import chat.rocket.android.authentication.registerusername.ui.RegisterUsernameFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module @Module
class RegisterUsernameFragmentModule { class RegisterUsernameFragmentModule {
@Provides @Provides
@PerFragment @PerFragment
fun provideJob() = Job() fun registerUsernameView(frag: RegisterUsernameFragment): RegisterUsernameView = frag
@Provides @Provides
@PerFragment @PerFragment
fun registerUsernameView(frag: RegisterUsernameFragment): RegisterUsernameView { fun provideLifecycleOwner(frag: RegisterUsernameFragment): LifecycleOwner = frag
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: RegisterUsernameFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
} }
\ No newline at end of file
...@@ -30,48 +30,44 @@ class RegisterUsernamePresenter @Inject constructor( ...@@ -30,48 +30,44 @@ class RegisterUsernamePresenter @Inject constructor(
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator, private val navigator: AuthenticationNavigator,
private val tokenRepository: TokenRepository, private val tokenRepository: TokenRepository,
factory: RocketChatClientFactory,
private val saveAccountInteractor: SaveAccountInteractor, private val saveAccountInteractor: SaveAccountInteractor,
private val analyticsManager: AnalyticsManager, private val analyticsManager: AnalyticsManager,
serverInteractor: GetConnectingServerInteractor,
private val saveCurrentServer: SaveCurrentServerInteractor, private val saveCurrentServer: SaveCurrentServerInteractor,
settingsInteractor: GetSettingsInteractor val serverInteractor: GetConnectingServerInteractor,
val factory: RocketChatClientFactory,
val settingsInteractor: GetSettingsInteractor
) { ) {
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
private val client: RocketChatClient = factory.create(currentServer) private val client: RocketChatClient = factory.create(currentServer)
private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!) private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!)
fun registerUsername(username: String, userId: String, authToken: String) { fun registerUsername(username: String, userId: String, authToken: String) {
if (username.isBlank()) { launchUI(strategy) {
view.alertBlankUsername() view.showLoading()
} else { try {
launchUI(strategy) { val me = retryIO("updateOwnBasicInformation(username = $username)") {
view.showLoading() client.updateOwnBasicInformation(username = username)
try {
val me = retryIO("updateOwnBasicInformation(username = $username)") {
client.updateOwnBasicInformation(username = username)
}
val registeredUsername = me.username
if (registeredUsername != null) {
saveAccount(registeredUsername)
saveCurrentServer.save(currentServer)
tokenRepository.save(currentServer, Token(userId, authToken))
analyticsManager.logSignUp(
AuthenticationEvent.AuthenticationWithOauth,
true
)
navigator.toChatList()
}
} catch (exception: RocketChatException) {
analyticsManager.logSignUp(AuthenticationEvent.AuthenticationWithOauth, false)
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
} }
val registeredUsername = me.username
if (registeredUsername != null) {
saveAccount(registeredUsername)
saveCurrentServer.save(currentServer)
tokenRepository.save(currentServer, Token(userId, authToken))
analyticsManager.logSignUp(
AuthenticationEvent.AuthenticationWithOauth,
true
)
navigator.toChatList()
}
} catch (exception: RocketChatException) {
analyticsManager.logSignUp(AuthenticationEvent.AuthenticationWithOauth, false)
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
} }
} }
} }
......
...@@ -6,7 +6,12 @@ import chat.rocket.android.core.behaviours.MessageView ...@@ -6,7 +6,12 @@ import chat.rocket.android.core.behaviours.MessageView
interface RegisterUsernameView : LoadingView, MessageView { interface RegisterUsernameView : LoadingView, MessageView {
/** /**
* Alerts the user about a blank username. * Enables the button to set the username if the user entered at least one character.
*/ */
fun alertBlankUsername() fun enableButtonUseThisUsername()
/**
* Disables the button to set the username when there is no character entered by the user.
*/
fun disableButtonUseThisUsername()
} }
\ No newline at end of file
...@@ -6,25 +6,39 @@ import android.os.Bundle ...@@ -6,25 +6,39 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.analytics.AnalyticsManager import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.ScreenViewEvent import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.authentication.registerusername.presentation.RegisterUsernamePresenter import chat.rocket.android.authentication.registerusername.presentation.RegisterUsernamePresenter
import chat.rocket.android.authentication.registerusername.presentation.RegisterUsernameView import chat.rocket.android.authentication.registerusername.presentation.RegisterUsernameView
import chat.rocket.android.util.extension.asObservable
import chat.rocket.android.util.extensions.inflate import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.setVisible import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.util.extensions.shake
import chat.rocket.android.util.extensions.showKeyboard import chat.rocket.android.util.extensions.showKeyboard
import chat.rocket.android.util.extensions.showToast import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.textContent import chat.rocket.android.util.extensions.textContent
import chat.rocket.android.util.extensions.ui import chat.rocket.android.util.extensions.ui
import chat.rocket.android.util.extensions.vibrateSmartPhone
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import kotlinx.android.synthetic.main.fragment_authentication_register_username.* import kotlinx.android.synthetic.main.fragment_authentication_register_username.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
internal const val TAG_REGISTER_USERNAME_FRAGMENT = "RegisterUsernameFragment" private const val BUNDLE_USER_ID = "user_id"
private const val BUNDLE_AUTH_TOKEN = "auth_token"
fun newInstance(userId: String, authToken: String): Fragment {
return RegisterUsernameFragment().apply {
arguments = Bundle(2).apply {
putString(BUNDLE_USER_ID, userId)
putString(BUNDLE_AUTH_TOKEN, authToken)
}
}
}
class RegisterUsernameFragment : Fragment(), RegisterUsernameView { class RegisterUsernameFragment : Fragment(), RegisterUsernameView {
@Inject @Inject
...@@ -33,26 +47,19 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView { ...@@ -33,26 +47,19 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView {
lateinit var analyticsManager: AnalyticsManager lateinit var analyticsManager: AnalyticsManager
private lateinit var userId: String private lateinit var userId: String
private lateinit var authToken: String private lateinit var authToken: String
private lateinit var usernameDisposable: Disposable
companion object {
private const val USER_ID = "user_id"
private const val AUTH_TOKEN = "auth_token"
fun newInstance(userId: String, authToken: String) = RegisterUsernameFragment().apply {
arguments = Bundle(1).apply {
putString(USER_ID, userId)
putString(AUTH_TOKEN, authToken)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
// TODO - research a better way to initialize parameters on fragments. val bundle = arguments
userId = arguments?.getString(USER_ID) ?: "" if (bundle != null) {
authToken = arguments?.getString(AUTH_TOKEN) ?: "" userId = bundle.getString(BUNDLE_USER_ID)
authToken = bundle.getString(BUNDLE_AUTH_TOKEN)
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
} }
override fun onCreateView( override fun onCreateView(
...@@ -74,14 +81,32 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView { ...@@ -74,14 +81,32 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView {
} }
setupOnClickListener() setupOnClickListener()
subscribeEditText()
analyticsManager.logScreenView(ScreenViewEvent.RegisterUsername) analyticsManager.logScreenView(ScreenViewEvent.RegisterUsername)
} }
override fun alertBlankUsername() { override fun onDestroyView() {
ui { super.onDestroyView()
vibrateSmartPhone() unsubscribeEditText()
text_username.shake() }
override fun enableButtonUseThisUsername() {
context?.let {
ViewCompat.setBackgroundTintList(
button_use_this_username, ContextCompat.getColorStateList(it, R.color.colorAccent)
)
button_use_this_username.isEnabled = true
}
}
override fun disableButtonUseThisUsername() {
context?.let {
ViewCompat.setBackgroundTintList(
button_use_this_username,
ContextCompat.getColorStateList(it, R.color.colorAuthenticationButtonDisabled)
)
button_use_this_username.isEnabled = false
} }
} }
...@@ -125,12 +150,12 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView { ...@@ -125,12 +150,12 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView {
} }
private fun enableUserInput() { private fun enableUserInput() {
button_use_this_username.isEnabled = true enableButtonUseThisUsername()
text_username.isEnabled = true text_username.isEnabled = true
} }
private fun disableUserInput() { private fun disableUserInput() {
button_use_this_username.isEnabled = false disableButtonUseThisUsername()
text_username.isEnabled = true text_username.isEnabled = true
} }
...@@ -139,4 +164,18 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView { ...@@ -139,4 +164,18 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView {
presenter.registerUsername(text_username.textContent, userId, authToken) presenter.registerUsername(text_username.textContent, userId, authToken)
} }
} }
private fun subscribeEditText() {
usernameDisposable = text_username.asObservable()
.debounce(300, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribe {
if (it.isNotBlank()) {
enableButtonUseThisUsername()
} else {
disableButtonUseThisUsername()
}
}
}
private fun unsubscribeEditText() = usernameDisposable.dispose()
} }
\ No newline at end of file
...@@ -3,34 +3,18 @@ package chat.rocket.android.authentication.resetpassword.di ...@@ -3,34 +3,18 @@ package chat.rocket.android.authentication.resetpassword.di
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.resetpassword.presentation.ResetPasswordView import chat.rocket.android.authentication.resetpassword.presentation.ResetPasswordView
import chat.rocket.android.authentication.resetpassword.ui.ResetPasswordFragment import chat.rocket.android.authentication.resetpassword.ui.ResetPasswordFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module @Module
class ResetPasswordFragmentModule { class ResetPasswordFragmentModule {
@Provides @Provides
@PerFragment @PerFragment
fun provideJob() = Job() fun resetPasswordView(frag: ResetPasswordFragment): ResetPasswordView = frag
@Provides @Provides
@PerFragment @PerFragment
fun resetPasswordView(frag: ResetPasswordFragment): ResetPasswordView { fun provideLifecycleOwner(frag: ResetPasswordFragment): LifecycleOwner = frag
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: ResetPasswordFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
} }
\ No newline at end of file
...@@ -4,7 +4,6 @@ import chat.rocket.android.authentication.presentation.AuthenticationNavigator ...@@ -4,7 +4,6 @@ import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.GetConnectingServerInteractor import chat.rocket.android.server.domain.GetConnectingServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.isEmail
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
...@@ -25,30 +24,26 @@ class ResetPasswordPresenter @Inject constructor( ...@@ -25,30 +24,26 @@ class ResetPasswordPresenter @Inject constructor(
private val client: RocketChatClient = factory.create(currentServer) private val client: RocketChatClient = factory.create(currentServer)
fun resetPassword(email: String) { fun resetPassword(email: String) {
when { launchUI(strategy) {
email.isBlank() -> view.alertBlankEmail() view.showLoading()
!email.isEmail() -> view.alertInvalidEmail() try {
else -> launchUI(strategy) { retryIO("forgotPassword(email = $email)") {
view.showLoading() client.forgotPassword(email)
try { }
retryIO("forgotPassword(email = $email)") { navigator.toPreviousView()
client.forgotPassword(email) view.emailSent()
} } catch (exception: RocketChatException) {
navigator.toPreviousView() if (exception is RocketChatInvalidResponseException) {
view.emailSent() view.updateYourServerVersion()
} catch (exception: RocketChatException) { } else {
if (exception is RocketChatInvalidResponseException) { exception.message?.let {
view.updateYourServerVersion() view.showMessage(it)
} else { }.ifNull {
exception.message?.let { view.showGenericErrorMessage()
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
} }
} finally {
view.hideLoading()
} }
} finally {
view.hideLoading()
} }
} }
} }
......
...@@ -6,22 +6,22 @@ import chat.rocket.android.core.behaviours.MessageView ...@@ -6,22 +6,22 @@ import chat.rocket.android.core.behaviours.MessageView
interface ResetPasswordView : LoadingView, MessageView { interface ResetPasswordView : LoadingView, MessageView {
/** /**
* Alerts the user about a blank email. * Shows a successful email sent message.
*/ */
fun alertBlankEmail() fun emailSent()
/** /**
* Alerts the user about a invalid email. * Shows a message to update the server version in order to use an app feature.
*/ */
fun alertInvalidEmail() fun updateYourServerVersion()
/** /**
* Shows a successful email sent message. * Enables the button to reset the password when the user inputs a valid email address.
*/ */
fun emailSent() fun enableButtonConnect()
/** /**
* Shows a message to update the server version in order to use an app feature. * Disables the button to reset the password when the user entered an invalid email address
*/ */
fun updateYourServerVersion() fun disableButtonConnect()
} }
\ No newline at end of file
package chat.rocket.android.authentication.resetpassword.ui package chat.rocket.android.authentication.resetpassword.ui
import DrawableHelper
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.analytics.AnalyticsManager import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.ScreenViewEvent import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.authentication.resetpassword.presentation.ResetPasswordPresenter import chat.rocket.android.authentication.resetpassword.presentation.ResetPasswordPresenter
import chat.rocket.android.authentication.resetpassword.presentation.ResetPasswordView import chat.rocket.android.authentication.resetpassword.presentation.ResetPasswordView
import chat.rocket.android.util.extension.asObservable
import chat.rocket.android.util.extensions.inflate import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.isEmail
import chat.rocket.android.util.extensions.setVisible import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.util.extensions.shake
import chat.rocket.android.util.extensions.showKeyboard import chat.rocket.android.util.extensions.showKeyboard
import chat.rocket.android.util.extensions.showToast import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.textContent import chat.rocket.android.util.extensions.textContent
import chat.rocket.android.util.extensions.ui import chat.rocket.android.util.extensions.ui
import chat.rocket.android.util.extensions.vibrateSmartPhone
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import kotlinx.android.synthetic.main.fragment_authentication_reset_password.* import kotlinx.android.synthetic.main.fragment_authentication_reset_password.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
internal const val TAG_RESET_PASSWORD_FRAGMENT = "ResetPasswordFragment" fun newInstance(): Fragment = ResetPasswordFragment()
class ResetPasswordFragment : Fragment(), ResetPasswordView { class ResetPasswordFragment : Fragment(), ResetPasswordView {
@Inject @Inject
lateinit var presenter: ResetPasswordPresenter lateinit var presenter: ResetPasswordPresenter
@Inject @Inject
lateinit var analyticsManager: AnalyticsManager lateinit var analyticsManager: AnalyticsManager
private lateinit var emailAddressDisposable: Disposable
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
...@@ -52,34 +55,39 @@ class ResetPasswordFragment : Fragment(), ResetPasswordView { ...@@ -52,34 +55,39 @@ class ResetPasswordFragment : Fragment(), ResetPasswordView {
showKeyboard(text_email) showKeyboard(text_email)
} }
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
tintEditTextDrawableStart()
}
setupOnClickListener() setupOnClickListener()
subscribeEditText()
analyticsManager.logScreenView(ScreenViewEvent.ResetPassword) analyticsManager.logScreenView(ScreenViewEvent.ResetPassword)
} }
override fun alertBlankEmail() { override fun onDestroyView() {
ui { super.onDestroyView()
vibrateShakeAndRequestFocusForTextEmail() unsubscribeEditText()
}
} }
override fun alertInvalidEmail() { override fun emailSent() = showMessage(R.string.msg_check_your_email_to_reset_your_password)
ui {
vibrateShakeAndRequestFocusForTextEmail() override fun updateYourServerVersion() =
showMessage(R.string.msg_invalid_email) showMessage(R.string.msg_update_app_version_in_order_to_continue)
}
}
override fun emailSent() { override fun enableButtonConnect() {
showToast(R.string.msg_check_your_email_to_reset_your_password, Toast.LENGTH_LONG) context?.let {
ViewCompat.setBackgroundTintList(
button_reset_password, ContextCompat.getColorStateList(it, R.color.colorAccent)
)
button_reset_password.isEnabled = true
}
} }
override fun updateYourServerVersion() { override fun disableButtonConnect() {
showMessage(R.string.msg_update_app_version_in_order_to_continue) context?.let {
ViewCompat.setBackgroundTintList(
button_reset_password,
ContextCompat.getColorStateList(it, R.color.colorAuthenticationButtonDisabled)
)
button_reset_password.isEnabled = false
}
} }
override fun showLoading() { override fun showLoading() {
...@@ -108,42 +116,35 @@ class ResetPasswordFragment : Fragment(), ResetPasswordView { ...@@ -108,42 +116,35 @@ class ResetPasswordFragment : Fragment(), ResetPasswordView {
} }
} }
override fun showGenericErrorMessage() { override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
showMessage(getString(R.string.msg_generic_error))
}
private fun tintEditTextDrawableStart() {
ui {
val emailDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_email_black_20dp, it)
DrawableHelper.wrapDrawable(emailDrawable)
DrawableHelper.tintDrawable(emailDrawable, it, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawable(text_email, emailDrawable)
}
}
private fun enableUserInput() { private fun enableUserInput() {
button_reset_password.isEnabled = true enableButtonConnect()
text_email.isEnabled = true text_email.isEnabled = true
} }
private fun disableUserInput() { private fun disableUserInput() {
button_reset_password.isEnabled = false disableButtonConnect()
text_email.isEnabled = true text_email.isEnabled = true
} }
private fun vibrateShakeAndRequestFocusForTextEmail() { private fun setupOnClickListener() =
vibrateSmartPhone()
text_email.shake()
text_email.requestFocus()
}
private fun setupOnClickListener() {
button_reset_password.setOnClickListener { button_reset_password.setOnClickListener {
presenter.resetPassword(text_email.textContent) presenter.resetPassword(text_email.textContent)
} }
}
companion object { private fun subscribeEditText() {
fun newInstance() = ResetPasswordFragment() emailAddressDisposable = text_email.asObservable()
.debounce(300, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.filter { it.isNotBlank() }
.subscribe {
if (it.toString().isEmail()) {
enableButtonConnect()
} else {
disableButtonConnect()
}
}
} }
private fun unsubscribeEditText() = emailAddressDisposable.dispose()
} }
\ No newline at end of file
...@@ -3,34 +3,18 @@ package chat.rocket.android.authentication.server.di ...@@ -3,34 +3,18 @@ package chat.rocket.android.authentication.server.di
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.server.presentation.ServerView import chat.rocket.android.authentication.server.presentation.ServerView
import chat.rocket.android.authentication.server.ui.ServerFragment import chat.rocket.android.authentication.server.ui.ServerFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module @Module
class ServerFragmentModule { class ServerFragmentModule {
@Provides @Provides
@PerFragment @PerFragment
fun provideJob() = Job() fun serverView(frag: ServerFragment): ServerView = frag
@Provides @Provides
@PerFragment @PerFragment
fun serverView(frag: ServerFragment): ServerView { fun provideLifecycleOwner(frag: ServerFragment): LifecycleOwner = frag
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: ServerFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
} }
\ No newline at end of file
...@@ -63,6 +63,10 @@ class ServerPresenter @Inject constructor( ...@@ -63,6 +63,10 @@ class ServerPresenter @Inject constructor(
} }
fun deepLink(deepLinkInfo: LoginDeepLinkInfo) { fun deepLink(deepLinkInfo: LoginDeepLinkInfo) {
connectToServer(deepLinkInfo.url) { navigator.toLogin(deepLinkInfo) } connectToServer(deepLinkInfo.url) {
navigator.toLoginOptions(
deepLinkInfo.url, deepLinkInfo
)
}
} }
} }
\ No newline at end of file
package chat.rocket.android.authentication.server.ui package chat.rocket.android.authentication.server.ui
import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
...@@ -11,6 +10,7 @@ import android.widget.ArrayAdapter ...@@ -11,6 +10,7 @@ import android.widget.ArrayAdapter
import android.widget.Toast import android.widget.Toast
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import chat.rocket.android.BuildConfig import chat.rocket.android.BuildConfig
...@@ -41,7 +41,15 @@ import okhttp3.HttpUrl ...@@ -41,7 +41,15 @@ import okhttp3.HttpUrl
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
internal const val TAG_SERVER_FRAGMENT = "ServerFragment" fun newInstance(deepLinkInfo: LoginDeepLinkInfo?): Fragment {
return ServerFragment().apply {
arguments = Bundle(1).apply {
putParcelable(DEEP_LINK_INFO, deepLinkInfo)
}
}
}
private const val DEEP_LINK_INFO = "DeepLinkInfo"
class ServerFragment : Fragment(), ServerView { class ServerFragment : Fragment(), ServerView {
@Inject @Inject
...@@ -50,7 +58,7 @@ class ServerFragment : Fragment(), ServerView { ...@@ -50,7 +58,7 @@ class ServerFragment : Fragment(), ServerView {
lateinit var analyticsManager: AnalyticsManager lateinit var analyticsManager: AnalyticsManager
private var deepLinkInfo: LoginDeepLinkInfo? = null private var deepLinkInfo: LoginDeepLinkInfo? = null
private var protocol = "https://" private var protocol = "https://"
private lateinit var serverUrlEditTextDisposable: Disposable private lateinit var serverUrlDisposable: Disposable
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener { private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
text_server_url.isCursorVisible = text_server_url.isCursorVisible =
KeyboardHelper.isSoftKeyboardShown(constraint_layout.rootView) KeyboardHelper.isSoftKeyboardShown(constraint_layout.rootView)
...@@ -137,23 +145,20 @@ class ServerFragment : Fragment(), ServerView { ...@@ -137,23 +145,20 @@ class ServerFragment : Fragment(), ServerView {
override fun showInvalidServerUrlMessage() = override fun showInvalidServerUrlMessage() =
showMessage(getString(R.string.msg_invalid_server_url)) showMessage(getString(R.string.msg_invalid_server_url))
@SuppressLint("RestrictedApi")
override fun enableButtonConnect() { override fun enableButtonConnect() {
context?.let { context?.let {
button_connect.supportBackgroundTintList = ContextCompat.getColorStateList( ViewCompat.setBackgroundTintList(
it, button_connect, ContextCompat.getColorStateList(it, R.color.colorAccent)
R.color.colorAccent
) )
button_connect.isEnabled = true button_connect.isEnabled = true
} }
} }
@SuppressLint("RestrictedApi")
override fun disableButtonConnect() { override fun disableButtonConnect() {
context?.let { context?.let {
button_connect.supportBackgroundTintList = ContextCompat.getColorStateList( ViewCompat.setBackgroundTintList(
it, button_connect,
R.color.colorAuthenticationOnBoardingButtonDisabled ContextCompat.getColorStateList(it, R.color.colorAuthenticationButtonDisabled)
) )
button_connect.isEnabled = false button_connect.isEnabled = false
} }
...@@ -242,9 +247,9 @@ class ServerFragment : Fragment(), ServerView { ...@@ -242,9 +247,9 @@ class ServerFragment : Fragment(), ServerView {
} }
private fun subscribeEditText() { private fun subscribeEditText() {
serverUrlEditTextDisposable = text_server_url.asObservable() serverUrlDisposable = text_server_url.asObservable()
.debounce(300, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) .debounce(300, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.filter { t -> t.isNotBlank() } .filter { it.isNotBlank() }
.subscribe { .subscribe {
if (it.toString().isValidUrl()) { if (it.toString().isValidUrl()) {
enableButtonConnect() enableButtonConnect()
...@@ -254,7 +259,7 @@ class ServerFragment : Fragment(), ServerView { ...@@ -254,7 +259,7 @@ class ServerFragment : Fragment(), ServerView {
} }
} }
private fun unsubscribeEditText() = serverUrlEditTextDisposable.dispose() private fun unsubscribeEditText() = serverUrlDisposable.dispose()
private fun enableUserInput() { private fun enableUserInput() {
enableButtonConnect() enableButtonConnect()
...@@ -265,12 +270,4 @@ class ServerFragment : Fragment(), ServerView { ...@@ -265,12 +270,4 @@ class ServerFragment : Fragment(), ServerView {
disableButtonConnect() disableButtonConnect()
text_server_url.isEnabled = false text_server_url.isEnabled = false
} }
companion object {
private const val DEEP_LINK_INFO = "DeepLinkInfo"
fun newInstance(deepLinkInfo: LoginDeepLinkInfo?) = ServerFragment().apply {
arguments = Bundle().apply { putParcelable(DEEP_LINK_INFO, deepLinkInfo) }
}
}
} }
...@@ -3,34 +3,18 @@ package chat.rocket.android.authentication.signup.di ...@@ -3,34 +3,18 @@ package chat.rocket.android.authentication.signup.di
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.signup.presentation.SignupView import chat.rocket.android.authentication.signup.presentation.SignupView
import chat.rocket.android.authentication.signup.ui.SignupFragment import chat.rocket.android.authentication.signup.ui.SignupFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module @Module
class SignupFragmentModule { class SignupFragmentModule {
@Provides @Provides
@PerFragment @PerFragment
fun provideJob() = Job() fun signupView(frag: SignupFragment): SignupView = frag
@Provides @Provides
@PerFragment @PerFragment
fun signupView(frag: SignupFragment): SignupView { fun provideLifecycleOwner(frag: SignupFragment): LifecycleOwner = frag
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: SignupFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
} }
\ No newline at end of file
...@@ -44,57 +44,37 @@ class SignupPresenter @Inject constructor( ...@@ -44,57 +44,37 @@ class SignupPresenter @Inject constructor(
private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!) private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!)
fun signup(name: String, username: String, password: String, email: String) { fun signup(name: String, username: String, password: String, email: String) {
val server = serverInteractor.get() val client = factory.create(currentServer)
when { launchUI(strategy) {
server == null -> { view.showLoading()
navigator.toServerScreen() try {
} // TODO This function returns a user so should we save it?
name.isBlank() -> { retryIO("signup") { client.signup(email, name, username, password) }
view.alertBlankName() // TODO This function returns a user token so should we save it?
} retryIO("login") { client.login(username, password) }
username.isBlank() -> { val me = retryIO("me") { client.me() }
view.alertBlankUsername() saveCurrentServerInteractor.save(currentServer)
} localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username)
password.isEmpty() -> { saveAccount(me)
view.alertEmptyPassword() analyticsManager.logSignUp(
} AuthenticationEvent.AuthenticationWithUserAndPassword,
email.isBlank() -> { true
view.alertBlankEmail() )
} view.saveSmartLockCredentials(username, password)
else -> { navigator.toChatList()
val client = factory.create(server) } catch (exception: RocketChatException) {
launchUI(strategy) { analyticsManager.logSignUp(
view.showLoading() AuthenticationEvent.AuthenticationWithUserAndPassword,
try { false
// TODO This function returns a user so should we save it? )
retryIO("signup") { client.signup(email, name, username, password) } exception.message?.let {
// TODO This function returns a user token so should we save it? view.showMessage(it)
retryIO("login") { client.login(username, password) } }.ifNull {
val me = retryIO("me") { client.me() } view.showGenericErrorMessage()
saveCurrentServerInteractor.save(currentServer)
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username)
saveAccount(me)
analyticsManager.logSignUp(
AuthenticationEvent.AuthenticationWithUserAndPassword,
true
)
view.saveSmartLockCredentials(username, password)
navigator.toChatList()
} catch (exception: RocketChatException) {
analyticsManager.logSignUp(
AuthenticationEvent.AuthenticationWithUserAndPassword,
false
)
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
}
} }
} finally {
view.hideLoading()
} }
} }
} }
......
...@@ -6,24 +6,15 @@ import chat.rocket.android.core.behaviours.MessageView ...@@ -6,24 +6,15 @@ import chat.rocket.android.core.behaviours.MessageView
interface SignupView : LoadingView, MessageView { interface SignupView : LoadingView, MessageView {
/** /**
* Alerts the user about a blank name. * Enables the button to register when the user enters all the required fields.
*/ */
fun alertBlankName() fun enableButtonRegister()
/** /**
* Alerts the user about a blank username. * Disables the button to register when the user doesn't enter all the required fields.
*/ */
fun alertBlankUsername() fun disableButtonRegister()
/**
* Alerts the user about a empty password.
*/
fun alertEmptyPassword()
/**
* Alerts the user about a blank email.
*/
fun alertBlankEmail()
/** /**
* Saves Google Smart Lock credentials. * Saves Google Smart Lock credentials.
......
...@@ -3,34 +3,18 @@ package chat.rocket.android.authentication.twofactor.di ...@@ -3,34 +3,18 @@ package chat.rocket.android.authentication.twofactor.di
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.twofactor.presentation.TwoFAView import chat.rocket.android.authentication.twofactor.presentation.TwoFAView
import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module @Module
class TwoFAFragmentModule { class TwoFAFragmentModule {
@Provides @Provides
@PerFragment @PerFragment
fun provideJob() = Job() fun loginView(frag: TwoFAFragment): TwoFAView = frag
@Provides @Provides
@PerFragment @PerFragment
fun loginView(frag: TwoFAFragment): TwoFAView { fun provideLifecycleOwner(frag: TwoFAFragment): LifecycleOwner = frag
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: TwoFAFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
} }
...@@ -33,73 +33,59 @@ class TwoFAPresenter @Inject constructor( ...@@ -33,73 +33,59 @@ class TwoFAPresenter @Inject constructor(
private val navigator: AuthenticationNavigator, private val navigator: AuthenticationNavigator,
private val tokenRepository: TokenRepository, private val tokenRepository: TokenRepository,
private val localRepository: LocalRepository, private val localRepository: LocalRepository,
private val serverInteractor: GetConnectingServerInteractor,
private val saveCurrentServerInteractor: SaveCurrentServerInteractor, private val saveCurrentServerInteractor: SaveCurrentServerInteractor,
private val analyticsManager: AnalyticsManager, private val analyticsManager: AnalyticsManager,
private val factory: RocketChatClientFactory, private val factory: RocketChatClientFactory,
private val saveAccountInteractor: SaveAccountInteractor, private val saveAccountInteractor: SaveAccountInteractor,
settingsInteractor: GetSettingsInteractor val serverInteractor: GetConnectingServerInteractor,
val settingsInteractor: GetSettingsInteractor
) { ) {
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!) private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!)
// TODO: If the usernameOrEmail and password was informed by the user on the previous screen, then we should pass only the pin, like this: fun authenticate(pin: EditText)
fun authenticate( fun authenticate(
usernameOrEmail: String, usernameOrEmail: String,
password: String, password: String,
twoFactorAuthenticationCode: String twoFactorAuthenticationCode: String
) { ) {
val server = serverInteractor.get() launchUI(strategy) {
when { val client = factory.create(currentServer)
server == null -> { view.showLoading()
navigator.toServerScreen() try {
} // The token is saved via the client TokenProvider
twoFactorAuthenticationCode.isBlank() -> { val token = retryIO("login") {
view.alertBlankTwoFactorAuthenticationCode() client.login(usernameOrEmail, password, twoFactorAuthenticationCode)
} }
else -> { val me = retryIO("me") { client.me() }
launchUI(strategy) { saveAccount(me)
val client = factory.create(server) saveCurrentServerInteractor.save(currentServer)
view.showLoading() tokenRepository.save(currentServer, token)
try { localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username)
// The token is saved via the client TokenProvider analyticsManager.logLogin(
val token = retryIO("login") { AuthenticationEvent.AuthenticationWithUserAndPassword,
client.login(usernameOrEmail, password, twoFactorAuthenticationCode) true
} )
val me = retryIO("me") { client.me() } navigator.toChatList()
saveAccount(me) } catch (exception: RocketChatException) {
saveCurrentServerInteractor.save(currentServer) if (exception is RocketChatAuthException) {
tokenRepository.save(server, token) view.alertInvalidTwoFactorAuthenticationCode()
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username) } else {
analyticsManager.logLogin( analyticsManager.logLogin(
AuthenticationEvent.AuthenticationWithUserAndPassword, AuthenticationEvent.AuthenticationWithUserAndPassword,
true false
) )
navigator.toChatList() exception.message?.let {
} catch (exception: RocketChatException) { view.showMessage(it)
if (exception is RocketChatAuthException) { }.ifNull {
view.alertInvalidTwoFactorAuthenticationCode() view.showGenericErrorMessage()
} else {
analyticsManager.logLogin(
AuthenticationEvent.AuthenticationWithUserAndPassword,
false
)
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
}
} finally {
view.hideLoading()
} }
} }
} finally {
view.hideLoading()
} }
} }
} }
fun signup() = navigator.toSignUp()
private suspend fun saveAccount(me: Myself) { private suspend fun saveAccount(me: Myself) {
val icon = settings.favicon()?.let { val icon = settings.favicon()?.let {
currentServer.serverLogoUrl(it) currentServer.serverLogoUrl(it)
......
...@@ -6,9 +6,14 @@ import chat.rocket.android.core.behaviours.MessageView ...@@ -6,9 +6,14 @@ import chat.rocket.android.core.behaviours.MessageView
interface TwoFAView : LoadingView, MessageView { interface TwoFAView : LoadingView, MessageView {
/** /**
* Alerts the user about a blank Two Factor Authentication code. * Enables the button to set the username if the user entered at least one character.
*/ */
fun alertBlankTwoFactorAuthenticationCode() fun enableButtonConfirm()
/**
* Disables the button to set the username when there is no character entered by the user.
*/
fun disableButtonConfirm()
/** /**
* Alerts the user about an invalid inputted Two Factor Authentication code. * Alerts the user about an invalid inputted Two Factor Authentication code.
......
package chat.rocket.android.authentication.twofactor.ui package chat.rocket.android.authentication.twofactor.ui
import DrawableHelper
import android.content.Context import android.content.Context
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.analytics.AnalyticsManager import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.ScreenViewEvent import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.authentication.twofactor.presentation.TwoFAPresenter import chat.rocket.android.authentication.twofactor.presentation.TwoFAPresenter
import chat.rocket.android.authentication.twofactor.presentation.TwoFAView import chat.rocket.android.authentication.twofactor.presentation.TwoFAView
import chat.rocket.android.util.extension.asObservable
import chat.rocket.android.util.extensions.inflate import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.util.extensions.shake
import chat.rocket.android.util.extensions.showToast import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.textContent import chat.rocket.android.util.extensions.textContent
import chat.rocket.android.util.extensions.ui import chat.rocket.android.util.extensions.ui
import chat.rocket.android.util.extensions.vibrateSmartPhone
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import kotlinx.android.synthetic.main.fragment_authentication_two_fa.* import kotlinx.android.synthetic.main.fragment_authentication_two_fa.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
internal const val TAG_TWO_FA_FRAGMENT = "TwoFAFragment" fun newInstance(username: String, password: String): Fragment {
return TwoFAFragment().apply {
arguments = Bundle(2).apply {
putString(BUNDLE_USERNAME, username)
putString(BUNDLE_PASSWORD, password)
}
}
}
private const val BUNDLE_USERNAME = "username"
private const val BUNDLE_PASSWORD = "password"
class TwoFAFragment : Fragment(), TwoFAView { class TwoFAFragment : Fragment(), TwoFAView {
@Inject @Inject
lateinit var presenter: TwoFAPresenter lateinit var presenter: TwoFAPresenter
@Inject @Inject
lateinit var analyticsManager: AnalyticsManager lateinit var analyticsManager: AnalyticsManager
lateinit var username: String private lateinit var username: String
lateinit var password: String private lateinit var password: String
private lateinit var twoFaCodeDisposable: Disposable
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
// TODO - research a better way to initialize parameters on fragments. val bundle = arguments
username = arguments?.getString(USERNAME) ?: "" if (bundle != null) {
password = arguments?.getString(PASSWORD) ?: "" username = bundle.getString(BUNDLE_USERNAME)
password = bundle.getString(BUNDLE_PASSWORD)
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
} }
override fun onCreateView( override fun onCreateView(
...@@ -54,42 +71,58 @@ class TwoFAFragment : Fragment(), TwoFAView { ...@@ -54,42 +71,58 @@ class TwoFAFragment : Fragment(), TwoFAView {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
activity?.apply { activity?.apply {
text_two_factor_auth.requestFocus() text_two_factor_authentication_code.requestFocus()
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(text_two_factor_auth, InputMethodManager.RESULT_UNCHANGED_SHOWN) imm.showSoftInput(
} text_two_factor_authentication_code,
InputMethodManager.RESULT_UNCHANGED_SHOWN
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { )
tintEditTextDrawableStart()
} }
setupOnClickListener() setupOnClickListener()
subscribeEditText()
analyticsManager.logScreenView(ScreenViewEvent.TwoFa) analyticsManager.logScreenView(ScreenViewEvent.TwoFa)
} }
override fun alertBlankTwoFactorAuthenticationCode() { override fun onDestroyView() {
ui { super.onDestroyView()
vibrateSmartPhone() unsubscribeEditText()
text_two_factor_auth.shake() }
override fun enableButtonConfirm() {
context?.let {
ViewCompat.setBackgroundTintList(
button_confirm, ContextCompat.getColorStateList(it, R.color.colorAccent)
)
button_confirm.isEnabled = true
} }
} }
override fun alertInvalidTwoFactorAuthenticationCode() { override fun disableButtonConfirm() {
showMessage(getString(R.string.msg_invalid_2fa_code)) context?.let {
ViewCompat.setBackgroundTintList(
button_confirm,
ContextCompat.getColorStateList(it, R.color.colorAuthenticationButtonDisabled)
)
button_confirm.isEnabled = false
}
} }
override fun alertInvalidTwoFactorAuthenticationCode() =
showMessage(R.string.msg_invalid_2fa_code)
override fun showLoading() { override fun showLoading() {
ui { ui {
enableUserInput(false) disableUserInput()
view_loading.setVisible(true) view_loading.isVisible = true
} }
} }
override fun hideLoading() { override fun hideLoading() {
ui { ui {
view_loading.setVisible(false) view_loading.isVisible = false
enableUserInput(true) enableUserInput()
} }
} }
...@@ -105,39 +138,39 @@ class TwoFAFragment : Fragment(), TwoFAView { ...@@ -105,39 +138,39 @@ class TwoFAFragment : Fragment(), TwoFAView {
} }
} }
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error)) override fun showGenericErrorMessage() = showMessage(R.string.msg_generic_error)
private fun tintEditTextDrawableStart() { private fun enableUserInput() {
ui { enableButtonConfirm()
val lockDrawable = text_two_factor_authentication_code.isEnabled = true
DrawableHelper.getDrawableFromId(R.drawable.ic_vpn_key_black_24dp, it)
DrawableHelper.wrapDrawable(lockDrawable)
DrawableHelper.tintDrawable(lockDrawable, it, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawable(text_two_factor_auth, lockDrawable)
}
} }
private fun enableUserInput(value: Boolean) { private fun disableUserInput() {
button_log_in.isEnabled = value disableButtonConfirm()
text_two_factor_auth.isEnabled = value text_two_factor_authentication_code.isEnabled = false
} }
private fun setupOnClickListener() { private fun setupOnClickListener() {
button_log_in.setOnClickListener { button_confirm.setOnClickListener {
presenter.authenticate(username, password, text_two_factor_auth.textContent) presenter.authenticate(
username,
password,
text_two_factor_authentication_code.textContent
)
} }
} }
// TODO - we could create an in memory repository to save username and password. private fun subscribeEditText() {
companion object { twoFaCodeDisposable = text_two_factor_authentication_code.asObservable()
private const val USERNAME = "username" .debounce(300, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
private const val PASSWORD = "password" .subscribe {
if (it.isNotBlank()) {
fun newInstance(username: String, password: String) = TwoFAFragment().apply { enableButtonConfirm()
arguments = Bundle(2).apply { } else {
putString(USERNAME, username) disableButtonConfirm()
putString(PASSWORD, password) }
} }
}
} }
private fun unsubscribeEditText() = twoFaCodeDisposable.dispose()
} }
...@@ -5,23 +5,18 @@ import android.content.Intent ...@@ -5,23 +5,18 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.fragment.app.Fragment
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.authentication.domain.model.getLoginDeepLinkInfo
import chat.rocket.android.authentication.onboarding.ui.OnBoardingFragment import chat.rocket.android.authentication.onboarding.ui.OnBoardingFragment
import chat.rocket.android.authentication.presentation.AuthenticationPresenter import chat.rocket.android.authentication.presentation.AuthenticationPresenter
import chat.rocket.android.authentication.server.ui.ServerFragment
import chat.rocket.android.util.extensions.addFragment import chat.rocket.android.util.extensions.addFragment
import dagger.android.AndroidInjection import dagger.android.AndroidInjection
import dagger.android.AndroidInjector import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector import dagger.android.DispatchingAndroidInjector
import dagger.android.support.HasSupportFragmentInjector import dagger.android.support.HasSupportFragmentInjector
import kotlinx.android.synthetic.main.app_bar_chat_room.* import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch
import javax.inject.Inject import javax.inject.Inject
class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector { class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector {
...@@ -29,39 +24,26 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector { ...@@ -29,39 +24,26 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector {
lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment> lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
@Inject @Inject
lateinit var presenter: AuthenticationPresenter lateinit var presenter: AuthenticationPresenter
val job = Job()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this) AndroidInjection.inject(this)
setContentView(R.layout.activity_authentication)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_authentication)
setupToolbar() setupToolbar()
} }
private fun setupToolbar() { private fun setupToolbar() {
setSupportActionBar(toolbar) with(toolbar) {
supportActionBar?.setDisplayShowTitleEnabled(false) setSupportActionBar(this)
toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white_24dp) setNavigationIcon(R.drawable.ic_arrow_back_white_24dp)
toolbar.setNavigationOnClickListener { setNavigationOnClickListener { onBackPressed() }
onBackPressed()
} }
supportActionBar?.setDisplayShowTitleEnabled(false)
} }
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
launch(UI + job) { loadCredentials()
val newServer = intent.getBooleanExtra(INTENT_ADD_NEW_SERVER, false)
presenter.loadCredentials(newServer) { authenticated ->
if (!authenticated) {
showOnBoarding()
}
}
}
}
override fun onStop() {
job.cancel()
super.onStop()
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
...@@ -70,15 +52,8 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector { ...@@ -70,15 +52,8 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector {
currentFragment?.onActivityResult(requestCode, resultCode, data) currentFragment?.onActivityResult(requestCode, resultCode, data)
} }
override fun supportFragmentInjector(): AndroidInjector<Fragment> { override fun supportFragmentInjector(): AndroidInjector<Fragment> =
return fragmentDispatchingAndroidInjector fragmentDispatchingAndroidInjector
}
private fun showOnBoarding() {
addFragment("OnBoardingFragment", R.id.fragment_container, allowStateLoss = true) {
OnBoardingFragment.newInstance()
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean { override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.legal, menu) menuInflater.inflate(R.menu.legal, menu)
...@@ -86,12 +61,31 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector { ...@@ -86,12 +61,31 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector {
} }
override fun onOptionsItemSelected(item: MenuItem?): Boolean { override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when(item?.itemId){ when (item?.itemId) {
R.id.action_terms_of_Service -> presenter.termsOfService(getString(R.string.action_terms_of_service)) R.id.action_terms_of_Service -> presenter.termsOfService(getString(R.string.action_terms_of_service))
R.id.action_privacy_policy -> presenter.privacyPolicy(getString(R.string.action_privacy_policy)) R.id.action_privacy_policy -> presenter.privacyPolicy(getString(R.string.action_privacy_policy))
} }
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
private fun loadCredentials() {
val newServer = intent.getBooleanExtra(INTENT_ADD_NEW_SERVER, false)
presenter.loadCredentials(newServer) { isAuthenticated ->
if (!isAuthenticated) {
showOnBoarding()
}
}
}
private fun showOnBoarding() {
addFragment(
ScreenViewEvent.OnBoarding.screenName,
R.id.fragment_container,
allowStateLoss = true
) {
OnBoardingFragment.newInstance()
}
}
} }
const val INTENT_ADD_NEW_SERVER = "INTENT_ADD_NEW_SERVER" const val INTENT_ADD_NEW_SERVER = "INTENT_ADD_NEW_SERVER"
......
package chat.rocket.android.helper package chat.rocket.android.helper
import chat.rocket.android.util.extensions.encodeToBase64
import chat.rocket.android.util.extensions.generateRandomString
import chat.rocket.android.util.extensions.removeTrailingSlash import chat.rocket.android.util.extensions.removeTrailingSlash
object OauthHelper { object OauthHelper {
/**
* Returns an unguessable random string used to protect against forgery attacks.
*/
fun getState() =
"{\"loginStyle\":\"popup\"," +
"\"credentialToken\":\"${generateRandomString(40)}\"," +
"\"isCordova\":true}".encodeToBase64()
/** /**
* Returns the Github Oauth URL. * Returns the Github Oauth URL.
* *
......
package chat.rocket.android.util.extensions package chat.rocket.android.util.extensions
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.content.Context
import android.os.Build
import android.os.VibrationEffect
import android.os.Vibrator
import android.view.View import android.view.View
import android.view.ViewAnimationUtils import android.view.ViewAnimationUtils
import android.view.animation.AccelerateInterpolator import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import androidx.fragment.app.Fragment import androidx.core.view.isVisible
fun View.rotateBy(value: Float, duration: Long = 100) { fun View.rotateBy(value: Float, duration: Long = 100) {
animate() animate()
...@@ -23,7 +15,7 @@ fun View.rotateBy(value: Float, duration: Long = 100) { ...@@ -23,7 +15,7 @@ fun View.rotateBy(value: Float, duration: Long = 100) {
fun View.fadeIn(startValue: Float = 0f, finishValue: Float = 1f, duration: Long = 200) { fun View.fadeIn(startValue: Float = 0f, finishValue: Float = 1f, duration: Long = 200) {
if (alpha == finishValue) { if (alpha == finishValue) {
setVisible(true) isVisible = true
return return
} }
...@@ -38,12 +30,12 @@ fun View.fadeIn(startValue: Float = 0f, finishValue: Float = 1f, duration: Long ...@@ -38,12 +30,12 @@ fun View.fadeIn(startValue: Float = 0f, finishValue: Float = 1f, duration: Long
.setInterpolator(AccelerateInterpolator()).start() .setInterpolator(AccelerateInterpolator()).start()
}.start() }.start()
setVisible(true) isVisible = true
} }
fun View.fadeOut(startValue: Float = 1f, finishValue: Float = 0f, duration: Long = 200) { fun View.fadeOut(startValue: Float = 1f, finishValue: Float = 0f, duration: Long = 200) {
if (alpha == finishValue) { if (alpha == finishValue) {
setVisible(false) isVisible = false
return return
} }
...@@ -58,7 +50,7 @@ fun View.fadeOut(startValue: Float = 1f, finishValue: Float = 0f, duration: Long ...@@ -58,7 +50,7 @@ fun View.fadeOut(startValue: Float = 1f, finishValue: Float = 0f, duration: Long
.setInterpolator(AccelerateInterpolator()).start() .setInterpolator(AccelerateInterpolator()).start()
}.start() }.start()
setVisible(false) isVisible = false
} }
fun View.circularRevealOrUnreveal( fun View.circularRevealOrUnreveal(
...@@ -72,43 +64,7 @@ fun View.circularRevealOrUnreveal( ...@@ -72,43 +64,7 @@ fun View.circularRevealOrUnreveal(
ViewAnimationUtils.createCircularReveal(this, centerX, centerY, startRadius, endRadius) ViewAnimationUtils.createCircularReveal(this, centerX, centerY, startRadius, endRadius)
anim.duration = duration anim.duration = duration
if (startRadius < endRadius) { isVisible = startRadius < endRadius
setVisible(true)
} else {
setVisible(false)
}
anim.start() anim.start()
} }
fun View.shake(x: Float = 2F, num: Int = 0) {
if (num == 6) {
this.translationX = 0.toFloat()
return
}
val animatorSet = AnimatorSet()
animatorSet.playTogether(ObjectAnimator.ofFloat(this, "translationX", this.context.dp(x)))
animatorSet.duration = 50
animatorSet.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
shake(if (num == 5) 0.toFloat() else -x, num + 1)
}
})
animatorSet.start()
}
fun Context.dp(value: Float): Float {
val density = this.resources.displayMetrics.density
val result = Math.ceil(density.times(value.toDouble()))
return result.toFloat()
}
fun Fragment.vibrateSmartPhone() {
val vibrator = context?.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
if (Build.VERSION.SDK_INT >= 26) {
vibrator.vibrate(VibrationEffect.createOneShot(200, VibrationEffect.DEFAULT_AMPLITUDE))
} else {
vibrator.vibrate(200)
}
}
\ No newline at end of file
...@@ -19,9 +19,10 @@ import androidx.appcompat.view.SupportMenuInflater ...@@ -19,9 +19,10 @@ import androidx.appcompat.view.SupportMenuInflater
import androidx.appcompat.view.menu.MenuBuilder import androidx.appcompat.view.menu.MenuBuilder
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import chat.rocket.android.R import chat.rocket.android.R
fun AppCompatActivity.setLightStatusBar(view: View) { fun FragmentActivity.setLightStatusBar(view: View) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
var flags = view.systemUiVisibility var flags = view.systemUiVisibility
flags = flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR flags = flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
...@@ -30,7 +31,7 @@ fun AppCompatActivity.setLightStatusBar(view: View) { ...@@ -30,7 +31,7 @@ fun AppCompatActivity.setLightStatusBar(view: View) {
} }
} }
fun AppCompatActivity.clearLightStatusBar(view: View) { fun FragmentActivity.clearLightStatusBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
window.statusBarColor = ContextCompat.getColor(this, R.color.colorPrimary) window.statusBarColor = ContextCompat.getColor(this, R.color.colorPrimary)
} }
......
...@@ -2,5 +2,5 @@ ...@@ -2,5 +2,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval"> android:shape="oval">
<solid android:color="#cbced1" /> <solid android:color="@color/colorAuthenticationChevronAndExpandIcon" />
</shape> </shape>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19,3h-4.18C14.4,1.84 13.3,1 12,1c-1.3,0 -2.4,0.84 -2.82,2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM12,3c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM12,7c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM18,19L6,19v-1.4c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1L18,19z" />
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z" />
</vector>
\ No newline at end of file
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFF" android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<stroke <stroke
android:width="1dp" android:width="1dp"
android:color="@color/colorAuthenticationOnBoardingButtonBorder" /> android:color="@color/colorAuthenticationButtonBorderAndDivider" />
<corners android:radius="2dp" /> <corners android:radius="2dp" />
</shape> </shape>
\ No newline at end of file
...@@ -3,13 +3,13 @@ ...@@ -3,13 +3,13 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:theme="@style/AppTheme"
android:orientation="vertical" android:orientation="vertical"
android:theme="@style/AppTheme"
tools:context=".authentication.ui.AuthenticationActivity"> tools:context=".authentication.ui.AuthenticationActivity">
<include <include
android:id="@+id/layout_app_bar" android:id="@+id/layout_app_bar"
layout="@layout/app_bar_chat_room" /> layout="@layout/app_bar" />
<FrameLayout <FrameLayout
android:id="@+id/fragment_container" android:id="@+id/fragment_container"
......
...@@ -2,19 +2,18 @@ ...@@ -2,19 +2,18 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/constraint_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/constraint_layout" android:padding="@dimen/screen_edge_left_and_right_margins"
tools:context=".authentication.login.ui.LoginFragment"> tools:context=".authentication.login.ui.LoginFragment">
<TextView <TextView
android:id="@+id/text_sign_in_to_your_server" android:id="@+id/text_login"
style="@style/Authentication.Headline.TextView" style="@style/Authentication.Headline.TextView"
android:text="@string/title_log_in" android:text="@string/title_log_in"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent" />
android:layout_marginStart="21dp"
android:layout_marginTop="30dp" />
<EditText <EditText
android:id="@+id/text_username_or_email" android:id="@+id/text_username_or_email"
...@@ -26,7 +25,7 @@ ...@@ -26,7 +25,7 @@
android:inputType="textEmailAddress|text" android:inputType="textEmailAddress|text"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_sign_in_to_your_server" /> app:layout_constraintTop_toBottomOf="@+id/text_login" />
<ImageView <ImageView
android:id="@+id/image_key" android:id="@+id/image_key"
...@@ -48,56 +47,45 @@ ...@@ -48,56 +47,45 @@
android:hint="@string/msg_password" android:hint="@string/msg_password"
android:imeOptions="actionDone" android:imeOptions="actionDone"
android:inputType="textPassword" android:inputType="textPassword"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_username_or_email" /> app:layout_constraintTop_toBottomOf="@+id/text_username_or_email" />
<Button <Button
android:id="@+id/button_log_in" android:id="@+id/button_log_in"
style="@style/Authentication.Button" style="@style/Authentication.Button.Flat"
android:layout_marginTop="20dp"
android:backgroundTint="@color/colorAuthenticationButtonDisabled"
android:enabled="false"
android:text="@string/title_log_in" android:text="@string/title_log_in"
android:visibility="visible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_password" app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="20dp" /> app:layout_constraintTop_toBottomOf="@id/text_password" />
<TextView <Button
android:id="@+id/text_forgot_your_password" android:id="@+id/button_forgot_your_password"
android:layout_width="wrap_content"
style="?borderlessButtonStyle" style="?borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="48dp" android:layout_height="48dp"
android:text="@string/msg_forgot_password" android:layout_marginTop="10dp"
android:text="@string/msg_forgot__your_password"
android:textAllCaps="false"
android:textColor="@color/colorAccent"
android:textSize="18sp" android:textSize="18sp"
android:textStyle="bold" android:textStyle="bold"
android:textColor="@color/colorAccent" android:visibility="gone"
android:textAllCaps="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_log_in" app:layout_constraintTop_toBottomOf="@id/button_log_in"
android:layout_marginTop="10dp"/> tools:visibility="visible" />
<com.wang.avi.AVLoadingIndicatorView <com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading" android:id="@+id/view_loading"
style="@style/Authentication.AVLoadingIndicatorView" style="@style/Authentication.AVLoadingIndicatorView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible" /> app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
<Button app:layout_constraintStart_toStartOf="parent"
android:id="@+id/button_cas" app:layout_constraintTop_toTopOf="parent" />
style="@style/Authentication.Button"
android:layout_marginStart="@dimen/screen_edge_left_and_right_margins"
android:layout_marginTop="16dp"
android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins"
android:text="@string/action_login_or_sign_up"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_forgot_your_password"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
...@@ -11,7 +11,6 @@ ...@@ -11,7 +11,6 @@
android:id="@+id/image_on_boarding" android:id="@+id/image_on_boarding"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:src="@drawable/ic_onboarding" android:src="@drawable/ic_onboarding"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
...@@ -74,7 +73,7 @@ ...@@ -74,7 +73,7 @@
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:src="@drawable/ic_chevron_right_black_24dp" android:src="@drawable/ic_chevron_right_black_24dp"
android:tint="@color/colorAuthenticationOnBoardingChevron" android:tint="@color/colorAuthenticationChevronAndExpandIcon"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
</RelativeLayout> </RelativeLayout>
...@@ -126,7 +125,7 @@ ...@@ -126,7 +125,7 @@
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:src="@drawable/ic_chevron_right_black_24dp" android:src="@drawable/ic_chevron_right_black_24dp"
android:tint="@color/colorAuthenticationOnBoardingChevron" android:tint="@color/colorAuthenticationChevronAndExpandIcon"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
</RelativeLayout> </RelativeLayout>
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="@dimen/screen_edge_left_and_right_margins"
tools:context=".authentication.registerusername.ui.RegisterUsernameFragment"> tools:context=".authentication.registerusername.ui.RegisterUsernameFragment">
<TextView <TextView
...@@ -11,39 +12,38 @@ ...@@ -11,39 +12,38 @@
style="@style/Authentication.Headline.TextView" style="@style/Authentication.Headline.TextView"
android:text="@string/title_register_username" android:text="@string/title_register_username"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent" />
android:layout_marginTop="30dp"
android:layout_marginStart="21dp" />
<EditText <EditText
android:id="@+id/text_username" android:id="@+id/text_username"
style="@style/Authentication.EditText" style="@style/Authentication.EditText"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_sign_in_to_your_server"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:drawableStart="@drawable/ic_at_black_20dp" android:drawableStart="@drawable/ic_at_black_20dp"
android:hint="@string/msg_username" android:hint="@string/msg_username"
android:imeOptions="actionDone" android:imeOptions="actionDone"
android:inputType="text" /> android:inputType="text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_sign_in_to_your_server" />
<Button <Button
android:id="@+id/button_use_this_username" android:id="@+id/button_use_this_username"
style="@style/Authentication.Button" style="@style/Authentication.Button.Flat"
android:layout_marginTop="20dp"
android:backgroundTint="@color/colorAuthenticationButtonDisabled"
android:enabled="false"
android:text="@string/action_use_this_username" android:text="@string/action_use_this_username"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_username" app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="20dp" /> app:layout_constraintTop_toBottomOf="@id/text_username" />
<com.wang.avi.AVLoadingIndicatorView <com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading" android:id="@+id/view_loading"
style="@style/Authentication.AVLoadingIndicatorView" style="@style/Authentication.AVLoadingIndicatorView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible" /> app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
...@@ -4,46 +4,46 @@ ...@@ -4,46 +4,46 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="@dimen/screen_edge_left_and_right_margins"
tools:context=".authentication.resetpassword.ui.ResetPasswordFragment"> tools:context=".authentication.resetpassword.ui.ResetPasswordFragment">
<TextView <TextView
android:id="@+id/text_sign_in_to_your_server" android:id="@+id/text_reset_password"
style="@style/Authentication.Headline.TextView" style="@style/Authentication.Headline.TextView"
android:text="@string/title_reset_password" android:text="@string/title_reset_password"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="21dp" app:layout_constraintTop_toTopOf="parent" />
android:layout_marginTop="30dp" />
<EditText <EditText
android:id="@+id/text_email" android:id="@+id/text_email"
style="@style/Authentication.EditText" style="@style/Authentication.EditText"
app:layout_constraintStart_toStartOf="parent" android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_sign_in_to_your_server"
android:layout_marginTop="30dp"
android:drawableStart="@drawable/ic_email_black_20dp" android:drawableStart="@drawable/ic_email_black_20dp"
android:hint="@string/msg_email" android:hint="@string/msg_email"
android:imeOptions="actionDone" android:imeOptions="actionDone"
android:inputType="textEmailAddress" /> android:inputType="textEmailAddress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_reset_password" />
<Button <Button
android:id="@+id/button_reset_password" android:id="@+id/button_reset_password"
style="@style/Authentication.Button" style="@style/Authentication.Button.Flat"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="20dp" android:layout_marginTop="20dp"
app:layout_constraintTop_toBottomOf="@id/text_email" android:backgroundTint="@color/colorAuthenticationButtonDisabled"
android:text="@string/title_reset_password" /> android:enabled="false"
android:text="@string/title_reset_password"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_email" />
<com.wang.avi.AVLoadingIndicatorView <com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading" android:id="@+id/view_loading"
style="@style/Authentication.AVLoadingIndicatorView" style="@style/Authentication.AVLoadingIndicatorView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible" /> app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
...@@ -14,7 +14,6 @@ ...@@ -14,7 +14,6 @@
android:id="@+id/image_server" android:id="@+id/image_server"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:src="@drawable/ic_server" android:src="@drawable/ic_server"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
...@@ -40,17 +39,11 @@ ...@@ -40,17 +39,11 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_sign_in_to_your_server"> app:layout_constraintTop_toBottomOf="@+id/text_sign_in_to_your_server">
<FrameLayout <Spinner
android:id="@+id/protocol_container" android:id="@+id/spinner_server_protocol"
android:layout_width="wrap_content" android:layout_width="100dp"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:popupBackground="@color/colorWhite" />
<Spinner
android:id="@+id/spinner_server_protocol"
android:layout_width="100dp"
android:layout_height="match_parent"
android:popupBackground="@color/colorWhite" />
</FrameLayout>
<EditText <EditText
android:id="@+id/text_server_url" android:id="@+id/text_server_url"
...@@ -60,7 +53,7 @@ ...@@ -60,7 +53,7 @@
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginStart="6dp" android:layout_marginStart="6dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_toEndOf="@+id/protocol_container" android:layout_toEndOf="@+id/spinner_server_protocol"
android:background="@color/colorWhite" android:background="@color/colorWhite"
android:cursorVisible="false" android:cursorVisible="false"
android:hint="@string/server_hint_url" android:hint="@string/server_hint_url"
...@@ -68,11 +61,11 @@ ...@@ -68,11 +61,11 @@
android:inputType="text|textUri" /> android:inputType="text|textUri" />
</RelativeLayout> </RelativeLayout>
<androidx.appcompat.widget.AppCompatButton <Button
android:id="@+id/button_connect" android:id="@+id/button_connect"
style="@style/Authentication.Button" style="@style/Authentication.Button.Flat"
android:layout_marginTop="20dp" android:layout_marginTop="20dp"
android:backgroundTint="@color/colorAuthenticationOnBoardingButtonDisabled" android:backgroundTint="@color/colorAuthenticationButtonDisabled"
android:enabled="false" android:enabled="false"
android:text="@string/action_connect" android:text="@string/action_connect"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
......
...@@ -4,55 +4,44 @@ ...@@ -4,55 +4,44 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="@dimen/screen_edge_left_and_right_margins"
tools:context=".authentication.twofactor.ui.TwoFAFragment"> tools:context=".authentication.twofactor.ui.TwoFAFragment">
<TextView <TextView
android:id="@+id/text_sign_in_to_your_server" android:id="@+id/text_two_factor_authentication"
style="@style/Authentication.Headline.TextView" style="@style/Authentication.Headline.TextView"
android:text="Two-factor Authentication"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent" />
android:layout_marginStart="21dp"
android:layout_marginTop="30dp"
android:text="Two-factor Authentication" />
<TextView
android:id="@+id/text_description"
style="@style/Authentication.Description.TextView"
android:text="What’s your 2FA code?"
app:layout_constraintTop_toBottomOf="@id/text_sign_in_to_your_server"
app:layout_constraintStart_toStartOf="@id/text_sign_in_to_your_server"
android:layout_marginTop="4dp" />
<EditText <EditText
android:id="@+id/text_two_factor_auth" android:id="@+id/text_two_factor_authentication_code"
style="@style/Authentication.EditText" style="@style/Authentication.EditText"
android:layout_below="@id/text_sign_in_to_your_server"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_description"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:drawableStart="@drawable/ic_vpn_key_black_24dp"
android:hint="@string/msg_2fa_code"
android:imeOptions="actionDone" android:imeOptions="actionDone"
android:inputType="number" /> android:inputType="text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_two_factor_authentication" />
<Button <Button
android:id="@+id/button_log_in" android:id="@+id/button_confirm"
style="@style/Authentication.Button" style="@style/Authentication.Button.Flat"
android:layout_marginTop="20dp"
android:backgroundTint="@color/colorAuthenticationButtonDisabled"
android:enabled="false"
android:text="@string/action_confirm" android:text="@string/action_confirm"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_two_factor_auth" app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="20dp" /> app:layout_constraintTop_toBottomOf="@+id/text_two_factor_authentication_code" />
<com.wang.avi.AVLoadingIndicatorView <com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading" android:id="@+id/view_loading"
style="@style/Authentication.AVLoadingIndicatorView" style="@style/Authentication.AVLoadingIndicatorView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible" /> app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
<resources> <resources>
<!-- Main colors --> <!-- Main colors -->
<color name="colorPrimary">#2f343d</color> <color name="colorPrimary">#FF2F343D</color>
<color name="colorPrimaryDark">#2f343d</color> <color name="colorPrimaryDark">#FF2F343D</color>
<color name="colorAccent">#FF1D74F5</color> <color name="colorAccent">#FF1D74F5</color>
<!-- Text colors --> <!-- Text colors -->
...@@ -23,10 +23,10 @@ ...@@ -23,10 +23,10 @@
<color name="colorRed">#FFFF0000</color> <color name="colorRed">#FFFF0000</color>
<!-- Authentication colors --> <!-- Authentication colors -->
<color name="colorAuthenticationOnBoardingButtonBorder">#FFE1E5E8</color> <color name="colorAuthenticationButtonBorderAndDivider">#FFE1E5E8</color>
<color name="colorAuthenticationOnBoardingButtonDisabled">#FFE1E5E8</color> <color name="colorAuthenticationButtonDisabled">#FFE1E5E8</color>
<color name="colorAuthenticationOnBoardingChevron">#FFCBCED1</color> <color name="colorAuthenticationChevronAndExpandIcon">#FFCBCED1</color>
<color name="colorAuthenticationOnBoardingSecondaryText">#FF9EA2A8</color> <color name="colorAuthenticationSecondaryText">#FF9EA2A8</color>
<color name="darkGray">#FFa0a0a0</color> <color name="darkGray">#FFa0a0a0</color>
<color name="actionMenuColor">#FF727272</color> <color name="actionMenuColor">#FF727272</color>
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment