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
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 ChatRoom : ScreenViewEvent("ChatRoomFragment")
object ChatRooms : ScreenViewEvent("ChatRoomsFragment")
object CreateChannel : ScreenViewEvent("CreateChannelFragment")
object FavoriteMessages : ScreenViewEvent("FavoriteMessagesFragment")
object Files : ScreenViewEvent("FilesFragment")
object Login : ScreenViewEvent("LoginFragment")
object MemberBottomSheet : ScreenViewEvent("MemberBottomSheetFragment")
object Members : ScreenViewEvent("MembersFragment")
object Mentions : ScreenViewEvent("MentionsFragment")
......@@ -17,11 +26,5 @@ sealed class ScreenViewEvent(val screenName: String) {
object PinnedMessages : ScreenViewEvent("PinnedMessagesFragment")
object Preferences : ScreenViewEvent("PreferencesFragment")
object Profile : ScreenViewEvent("ProfileFragment")
object RegisterUsername : ScreenViewEvent("RegisterUsernameFragment")
object ResetPassword : ScreenViewEvent("ResetPasswordFragment")
object Server : ScreenViewEvent("ServerFragment")
object Settings : ScreenViewEvent("SettingsFragment")
object SignUp : ScreenViewEvent("SignupFragment")
object TwoFa : ScreenViewEvent("TwoFAFragment")
object OnBoarding : ScreenViewEvent("OnBoardingFragment")
}
package chat.rocket.android.authentication.di
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
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 dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
class AuthenticationModule {
@Provides
@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
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.login.presentation.LoginView
import chat.rocket.android.authentication.login.ui.LoginFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
class LoginFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
fun loginView(frag: LoginFragment): LoginView = frag
@Provides
@PerFragment
fun loginView(frag: LoginFragment): LoginView {
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: LoginFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
fun provideLifecycleOwner(frag: LoginFragment): LifecycleOwner = frag
}
\ No newline at end of file
......@@ -5,113 +5,24 @@ import chat.rocket.android.core.behaviours.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.
*
* REMARK: We must set up the forgot password view listener [setupForgotPasswordView].
*/
fun showForgotPasswordView()
/**
* Setups the forgot password view when tapped.
*/
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.
* Saves Google Smart Lock credentials.
*/
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
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.ui.LoginOptionsFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
class LoginOptionsFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
fun loginOptionsView(frag: LoginOptionsFragment): LoginOptionsView = frag
@Provides
@PerFragment
fun loginOptionsView(frag: LoginOptionsFragment): LoginOptionsView{
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: LoginOptionsFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
fun provideLifecycleOwner(frag: LoginOptionsFragment): LifecycleOwner = frag
}
\ No newline at end of file
......@@ -4,76 +4,194 @@ import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.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()
/**
* 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()
/**
* Setups the Github button when tapped.
* Setups the Github button.
*
* @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)
/**
* 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()
/**
* Setups the Google button when tapped.
* Setups the Google button.
*
* @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)
/**
* 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()
/**
* Setups the Linkedin button when tapped.
* Setups the Linkedin button.
*
* @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)
/**
* 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.
* @param state A random string generated by the app, which you'll verify later (to protect against forgery attacks).
* REMARK: We must set up the Gitlab button listener before enabling it
* [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).
*/
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
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.onboarding.presentation.OnBoardingView
import chat.rocket.android.authentication.onboarding.ui.OnBoardingFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
class OnBoardingFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun onBoardingView(frag: OnBoardingFragment): OnBoardingView = frag
......@@ -23,9 +17,4 @@ class OnBoardingFragmentModule {
@Provides
@PerFragment
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
import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.ui
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 javax.inject.Inject
......@@ -48,16 +48,14 @@ class OnBoardingFragment : Fragment(), OnBoardingView {
private fun setupToobar() {
with(activity as AuthenticationActivity) {
view?.let { setLightStatusBar(it) }
view?.let { this.setLightStatusBar(it) }
toolbar.isVisible = false
}
}
private fun setupOnClickListener() {
connect_with_a_server_container.setOnClickListener { connectWithAServer() }
join_community_container.setOnClickListener { joinInTheCommunity() }
create_server_container.setOnClickListener { createANewServer() }
}
......
......@@ -2,21 +2,8 @@ package chat.rocket.android.authentication.presentation
import android.content.Intent
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.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.newServerIntent
import chat.rocket.android.main.ui.MainActivity
......@@ -28,48 +15,57 @@ import chat.rocket.android.webview.ui.webViewIntent
class AuthenticationNavigator(internal val activity: AuthenticationActivity) {
fun toConnectWithAServer(deepLinkInfo: LoginDeepLinkInfo?) {
activity.addFragmentBackStack(TAG_SERVER_FRAGMENT, R.id.fragment_container) {
ServerFragment.newInstance(deepLinkInfo)
activity.addFragmentBackStack(ScreenViewEvent.Server.screenName, R.id.fragment_container) {
chat.rocket.android.authentication.server.ui.newInstance(deepLinkInfo)
}
}
fun toLoginOptions(server: String) {
activity.addFragmentBackStack(TAG_LOGIN_OPTIONS, R.id.fragment_container) {
LoginOptionsFragment.newInstance(server)
fun toLoginOptions(server: String, deepLinkInfo: LoginDeepLinkInfo? = null) {
activity.addFragmentBackStack(
ScreenViewEvent.LoginOptions.screenName,
R.id.fragment_container
) {
chat.rocket.android.authentication.loginoptions.ui.newInstance(server, deepLinkInfo)
}
}
fun toLogin() {
activity.addFragmentBackStack(TAG_LOGIN_FRAGMENT, R.id.fragment_container) {
LoginFragment.newInstance()
fun toTwoFA(username: String, password: String) {
activity.addFragmentBackStack(ScreenViewEvent.TwoFa.screenName, R.id.fragment_container) {
chat.rocket.android.authentication.twofactor.ui.newInstance(username, password)
}
}
fun toLogin(deepLinkInfo: LoginDeepLinkInfo) {
activity.addFragmentBackStack(TAG_LOGIN_FRAGMENT, R.id.fragment_container) {
LoginFragment.newInstance(deepLinkInfo)
fun toCreateAccount() {
activity.addFragmentBackStack(ScreenViewEvent.SignUp.screenName, R.id.fragment_container) {
chat.rocket.android.authentication.signup.ui.newInstance()
}
}
fun toPreviousView() {
activity.toPreviousView()
fun toLogin() {
activity.addFragmentBackStack(ScreenViewEvent.Login.screenName, R.id.fragment_container) {
chat.rocket.android.authentication.login.ui.newInstance()
}
}
fun toTwoFA(username: String, password: String) {
activity.addFragmentBackStack(TAG_TWO_FA_FRAGMENT, R.id.fragment_container) {
TwoFAFragment.newInstance(username, password)
fun toForgotPassword() {
activity.addFragmentBackStack(
ScreenViewEvent.ResetPassword.screenName,
R.id.fragment_container
) {
chat.rocket.android.authentication.resetpassword.ui.newInstance()
}
}
fun toSignUp() {
activity.addFragmentBackStack(TAG_SIGNUP_FRAGMENT, R.id.fragment_container) {
SignupFragment.newInstance()
}
fun toPreviousView() {
activity.toPreviousView()
}
fun toForgotPassword() {
activity.addFragmentBackStack(TAG_RESET_PASSWORD_FRAGMENT, R.id.fragment_container) {
ResetPasswordFragment.newInstance()
fun toRegisterUsername(userId: String, authToken: String) {
activity.addFragmentBackStack(
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) {
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() {
activity.startActivity(Intent(activity, MainActivity::class.java))
activity.finish()
......@@ -98,10 +88,4 @@ class AuthenticationNavigator(internal val activity: AuthenticationActivity) {
activity.startActivity(activity.newServerIntent())
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
import chat.rocket.android.core.lifecycle.CancelStrategy
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.termsOfServiceUrl
import javax.inject.Inject
class AuthenticationPresenter @Inject constructor(
private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator,
private val getCurrentServerInteractor: GetCurrentServerInteractor,
private val getAccountInteractor: GetAccountInteractor,
......@@ -15,33 +22,34 @@ class AuthenticationPresenter @Inject constructor(
private val tokenRepository: TokenRepository,
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 {
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, account.userName)
}
fun loadCredentials(newServer: Boolean, callback: (isAuthenticated: Boolean) -> Unit) {
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) {
callback(false)
} else {
callback(true)
navigator.toChatList()
}
}
account?.let {
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, account.userName)
}
fun termsOfService(toolbarTitle: String) {
serverInteractor.get()?.let {
navigator.toWebPage(it.termsOfServiceUrl(), toolbarTitle)
if (newServer || currentServer == null ||
serverToken == null ||
settings == null ||
account?.userName == null
) {
callback(false)
} else {
callback(true)
navigator.toChatList()
}
}
}
fun privacyPolicy(toolbarTitle: String) {
serverInteractor.get()?.let {
navigator.toWebPage(it.privacyPolicyUrl(), toolbarTitle)
}
}
fun termsOfService(toolbarTitle: String) =
serverInteractor.get()?.let { navigator.toWebPage(it.termsOfServiceUrl(), 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
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.registerusername.presentation.RegisterUsernameView
import chat.rocket.android.authentication.registerusername.ui.RegisterUsernameFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
class RegisterUsernameFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
fun registerUsernameView(frag: RegisterUsernameFragment): RegisterUsernameView = frag
@Provides
@PerFragment
fun registerUsernameView(frag: RegisterUsernameFragment): RegisterUsernameView {
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: RegisterUsernameFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
fun provideLifecycleOwner(frag: RegisterUsernameFragment): LifecycleOwner = frag
}
\ No newline at end of file
......@@ -30,48 +30,44 @@ class RegisterUsernamePresenter @Inject constructor(
private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator,
private val tokenRepository: TokenRepository,
factory: RocketChatClientFactory,
private val saveAccountInteractor: SaveAccountInteractor,
private val analyticsManager: AnalyticsManager,
serverInteractor: GetConnectingServerInteractor,
private val saveCurrentServer: SaveCurrentServerInteractor,
settingsInteractor: GetSettingsInteractor
val serverInteractor: GetConnectingServerInteractor,
val factory: RocketChatClientFactory,
val settingsInteractor: GetSettingsInteractor
) {
private val currentServer = serverInteractor.get()!!
private val client: RocketChatClient = factory.create(currentServer)
private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!)
fun registerUsername(username: String, userId: String, authToken: String) {
if (username.isBlank()) {
view.alertBlankUsername()
} else {
launchUI(strategy) {
view.showLoading()
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()
launchUI(strategy) {
view.showLoading()
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()
}
}
}
......
......@@ -6,7 +6,12 @@ import chat.rocket.android.core.behaviours.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
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.fragment.app.Fragment
import chat.rocket.android.R
import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.authentication.registerusername.presentation.RegisterUsernamePresenter
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.setVisible
import chat.rocket.android.util.extensions.shake
import chat.rocket.android.util.extensions.showKeyboard
import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.textContent
import chat.rocket.android.util.extensions.ui
import chat.rocket.android.util.extensions.vibrateSmartPhone
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 java.util.concurrent.TimeUnit
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 {
@Inject
......@@ -33,26 +47,19 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView {
lateinit var analyticsManager: AnalyticsManager
private lateinit var userId: String
private lateinit var authToken: String
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)
}
}
}
private lateinit var usernameDisposable: Disposable
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
// TODO - research a better way to initialize parameters on fragments.
userId = arguments?.getString(USER_ID) ?: ""
authToken = arguments?.getString(AUTH_TOKEN) ?: ""
val bundle = arguments
if (bundle != null) {
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(
......@@ -74,14 +81,32 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView {
}
setupOnClickListener()
subscribeEditText()
analyticsManager.logScreenView(ScreenViewEvent.RegisterUsername)
}
override fun alertBlankUsername() {
ui {
vibrateSmartPhone()
text_username.shake()
override fun onDestroyView() {
super.onDestroyView()
unsubscribeEditText()
}
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 {
}
private fun enableUserInput() {
button_use_this_username.isEnabled = true
enableButtonUseThisUsername()
text_username.isEnabled = true
}
private fun disableUserInput() {
button_use_this_username.isEnabled = false
disableButtonUseThisUsername()
text_username.isEnabled = true
}
......@@ -139,4 +164,18 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView {
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
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.resetpassword.presentation.ResetPasswordView
import chat.rocket.android.authentication.resetpassword.ui.ResetPasswordFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
class ResetPasswordFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
fun resetPasswordView(frag: ResetPasswordFragment): ResetPasswordView = frag
@Provides
@PerFragment
fun resetPasswordView(frag: ResetPasswordFragment): ResetPasswordView {
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: ResetPasswordFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
fun provideLifecycleOwner(frag: ResetPasswordFragment): LifecycleOwner = frag
}
\ No newline at end of file
......@@ -4,7 +4,6 @@ import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.GetConnectingServerInteractor
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.retryIO
import chat.rocket.common.RocketChatException
......@@ -25,30 +24,26 @@ class ResetPasswordPresenter @Inject constructor(
private val client: RocketChatClient = factory.create(currentServer)
fun resetPassword(email: String) {
when {
email.isBlank() -> view.alertBlankEmail()
!email.isEmail() -> view.alertInvalidEmail()
else -> launchUI(strategy) {
view.showLoading()
try {
retryIO("forgotPassword(email = $email)") {
client.forgotPassword(email)
}
navigator.toPreviousView()
view.emailSent()
} catch (exception: RocketChatException) {
if (exception is RocketChatInvalidResponseException) {
view.updateYourServerVersion()
} else {
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
launchUI(strategy) {
view.showLoading()
try {
retryIO("forgotPassword(email = $email)") {
client.forgotPassword(email)
}
navigator.toPreviousView()
view.emailSent()
} catch (exception: RocketChatException) {
if (exception is RocketChatInvalidResponseException) {
view.updateYourServerVersion()
} else {
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
}
} finally {
view.hideLoading()
}
}
}
......
......@@ -6,22 +6,22 @@ import chat.rocket.android.core.behaviours.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
import DrawableHelper
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.fragment.app.Fragment
import chat.rocket.android.R
import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.authentication.resetpassword.presentation.ResetPasswordPresenter
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.isEmail
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.showToast
import chat.rocket.android.util.extensions.textContent
import chat.rocket.android.util.extensions.ui
import chat.rocket.android.util.extensions.vibrateSmartPhone
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 java.util.concurrent.TimeUnit
import javax.inject.Inject
internal const val TAG_RESET_PASSWORD_FRAGMENT = "ResetPasswordFragment"
fun newInstance(): Fragment = ResetPasswordFragment()
class ResetPasswordFragment : Fragment(), ResetPasswordView {
@Inject
lateinit var presenter: ResetPasswordPresenter
@Inject
lateinit var analyticsManager: AnalyticsManager
private lateinit var emailAddressDisposable: Disposable
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
......@@ -52,34 +55,39 @@ class ResetPasswordFragment : Fragment(), ResetPasswordView {
showKeyboard(text_email)
}
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
tintEditTextDrawableStart()
}
setupOnClickListener()
subscribeEditText()
analyticsManager.logScreenView(ScreenViewEvent.ResetPassword)
}
override fun alertBlankEmail() {
ui {
vibrateShakeAndRequestFocusForTextEmail()
}
override fun onDestroyView() {
super.onDestroyView()
unsubscribeEditText()
}
override fun alertInvalidEmail() {
ui {
vibrateShakeAndRequestFocusForTextEmail()
showMessage(R.string.msg_invalid_email)
}
}
override fun emailSent() = showMessage(R.string.msg_check_your_email_to_reset_your_password)
override fun updateYourServerVersion() =
showMessage(R.string.msg_update_app_version_in_order_to_continue)
override fun emailSent() {
showToast(R.string.msg_check_your_email_to_reset_your_password, Toast.LENGTH_LONG)
override fun enableButtonConnect() {
context?.let {
ViewCompat.setBackgroundTintList(
button_reset_password, ContextCompat.getColorStateList(it, R.color.colorAccent)
)
button_reset_password.isEnabled = true
}
}
override fun updateYourServerVersion() {
showMessage(R.string.msg_update_app_version_in_order_to_continue)
override fun disableButtonConnect() {
context?.let {
ViewCompat.setBackgroundTintList(
button_reset_password,
ContextCompat.getColorStateList(it, R.color.colorAuthenticationButtonDisabled)
)
button_reset_password.isEnabled = false
}
}
override fun showLoading() {
......@@ -108,42 +116,35 @@ class ResetPasswordFragment : Fragment(), ResetPasswordView {
}
}
override fun showGenericErrorMessage() {
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)
}
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
private fun enableUserInput() {
button_reset_password.isEnabled = true
enableButtonConnect()
text_email.isEnabled = true
}
private fun disableUserInput() {
button_reset_password.isEnabled = false
disableButtonConnect()
text_email.isEnabled = true
}
private fun vibrateShakeAndRequestFocusForTextEmail() {
vibrateSmartPhone()
text_email.shake()
text_email.requestFocus()
}
private fun setupOnClickListener() {
private fun setupOnClickListener() =
button_reset_password.setOnClickListener {
presenter.resetPassword(text_email.textContent)
}
}
companion object {
fun newInstance() = ResetPasswordFragment()
private fun subscribeEditText() {
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
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.server.presentation.ServerView
import chat.rocket.android.authentication.server.ui.ServerFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
class ServerFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
fun serverView(frag: ServerFragment): ServerView = frag
@Provides
@PerFragment
fun serverView(frag: ServerFragment): ServerView {
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: ServerFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
fun provideLifecycleOwner(frag: ServerFragment): LifecycleOwner = frag
}
\ No newline at end of file
......@@ -63,6 +63,10 @@ class ServerPresenter @Inject constructor(
}
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
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
......@@ -11,6 +10,7 @@ import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import chat.rocket.android.BuildConfig
......@@ -41,7 +41,15 @@ import okhttp3.HttpUrl
import java.util.concurrent.TimeUnit
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 {
@Inject
......@@ -50,7 +58,7 @@ class ServerFragment : Fragment(), ServerView {
lateinit var analyticsManager: AnalyticsManager
private var deepLinkInfo: LoginDeepLinkInfo? = null
private var protocol = "https://"
private lateinit var serverUrlEditTextDisposable: Disposable
private lateinit var serverUrlDisposable: Disposable
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
text_server_url.isCursorVisible =
KeyboardHelper.isSoftKeyboardShown(constraint_layout.rootView)
......@@ -137,23 +145,20 @@ class ServerFragment : Fragment(), ServerView {
override fun showInvalidServerUrlMessage() =
showMessage(getString(R.string.msg_invalid_server_url))
@SuppressLint("RestrictedApi")
override fun enableButtonConnect() {
context?.let {
button_connect.supportBackgroundTintList = ContextCompat.getColorStateList(
it,
R.color.colorAccent
ViewCompat.setBackgroundTintList(
button_connect, ContextCompat.getColorStateList(it, R.color.colorAccent)
)
button_connect.isEnabled = true
}
}
@SuppressLint("RestrictedApi")
override fun disableButtonConnect() {
context?.let {
button_connect.supportBackgroundTintList = ContextCompat.getColorStateList(
it,
R.color.colorAuthenticationOnBoardingButtonDisabled
ViewCompat.setBackgroundTintList(
button_connect,
ContextCompat.getColorStateList(it, R.color.colorAuthenticationButtonDisabled)
)
button_connect.isEnabled = false
}
......@@ -242,9 +247,9 @@ class ServerFragment : Fragment(), ServerView {
}
private fun subscribeEditText() {
serverUrlEditTextDisposable = text_server_url.asObservable()
serverUrlDisposable = text_server_url.asObservable()
.debounce(300, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.filter { t -> t.isNotBlank() }
.filter { it.isNotBlank() }
.subscribe {
if (it.toString().isValidUrl()) {
enableButtonConnect()
......@@ -254,7 +259,7 @@ class ServerFragment : Fragment(), ServerView {
}
}
private fun unsubscribeEditText() = serverUrlEditTextDisposable.dispose()
private fun unsubscribeEditText() = serverUrlDisposable.dispose()
private fun enableUserInput() {
enableButtonConnect()
......@@ -265,12 +270,4 @@ class ServerFragment : Fragment(), ServerView {
disableButtonConnect()
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
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.signup.presentation.SignupView
import chat.rocket.android.authentication.signup.ui.SignupFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
class SignupFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
fun signupView(frag: SignupFragment): SignupView = frag
@Provides
@PerFragment
fun signupView(frag: SignupFragment): SignupView {
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: SignupFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
fun provideLifecycleOwner(frag: SignupFragment): LifecycleOwner = frag
}
\ No newline at end of file
......@@ -44,57 +44,37 @@ class SignupPresenter @Inject constructor(
private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!)
fun signup(name: String, username: String, password: String, email: String) {
val server = serverInteractor.get()
when {
server == null -> {
navigator.toServerScreen()
}
name.isBlank() -> {
view.alertBlankName()
}
username.isBlank() -> {
view.alertBlankUsername()
}
password.isEmpty() -> {
view.alertEmptyPassword()
}
email.isBlank() -> {
view.alertBlankEmail()
}
else -> {
val client = factory.create(server)
launchUI(strategy) {
view.showLoading()
try {
// TODO This function returns a user so should we save it?
retryIO("signup") { client.signup(email, name, username, password) }
// TODO This function returns a user token so should we save it?
retryIO("login") { client.login(username, password) }
val me = retryIO("me") { client.me() }
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()
}
val client = factory.create(currentServer)
launchUI(strategy) {
view.showLoading()
try {
// TODO This function returns a user so should we save it?
retryIO("signup") { client.signup(email, name, username, password) }
// TODO This function returns a user token so should we save it?
retryIO("login") { client.login(username, password) }
val me = retryIO("me") { client.me() }
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()
}
}
}
......
......@@ -6,24 +6,15 @@ import chat.rocket.android.core.behaviours.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.
......
......@@ -3,34 +3,18 @@ package chat.rocket.android.authentication.twofactor.di
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.twofactor.presentation.TwoFAView
import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
class TwoFAFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
fun loginView(frag: TwoFAFragment): TwoFAView = frag
@Provides
@PerFragment
fun loginView(frag: TwoFAFragment): TwoFAView {
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: TwoFAFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
fun provideLifecycleOwner(frag: TwoFAFragment): LifecycleOwner = frag
}
......@@ -33,73 +33,59 @@ class TwoFAPresenter @Inject constructor(
private val navigator: AuthenticationNavigator,
private val tokenRepository: TokenRepository,
private val localRepository: LocalRepository,
private val serverInteractor: GetConnectingServerInteractor,
private val saveCurrentServerInteractor: SaveCurrentServerInteractor,
private val analyticsManager: AnalyticsManager,
private val factory: RocketChatClientFactory,
private val saveAccountInteractor: SaveAccountInteractor,
settingsInteractor: GetSettingsInteractor
val serverInteractor: GetConnectingServerInteractor,
val settingsInteractor: GetSettingsInteractor
) {
private val currentServer = 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(
usernameOrEmail: String,
password: String,
twoFactorAuthenticationCode: String
) {
val server = serverInteractor.get()
when {
server == null -> {
navigator.toServerScreen()
}
twoFactorAuthenticationCode.isBlank() -> {
view.alertBlankTwoFactorAuthenticationCode()
}
else -> {
launchUI(strategy) {
val client = factory.create(server)
view.showLoading()
try {
// The token is saved via the client TokenProvider
val token = retryIO("login") {
client.login(usernameOrEmail, password, twoFactorAuthenticationCode)
}
val me = retryIO("me") { client.me() }
saveAccount(me)
saveCurrentServerInteractor.save(currentServer)
tokenRepository.save(server, token)
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username)
analyticsManager.logLogin(
AuthenticationEvent.AuthenticationWithUserAndPassword,
true
)
navigator.toChatList()
} catch (exception: RocketChatException) {
if (exception is RocketChatAuthException) {
view.alertInvalidTwoFactorAuthenticationCode()
} else {
analyticsManager.logLogin(
AuthenticationEvent.AuthenticationWithUserAndPassword,
false
)
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
}
} finally {
view.hideLoading()
launchUI(strategy) {
val client = factory.create(currentServer)
view.showLoading()
try {
// The token is saved via the client TokenProvider
val token = retryIO("login") {
client.login(usernameOrEmail, password, twoFactorAuthenticationCode)
}
val me = retryIO("me") { client.me() }
saveAccount(me)
saveCurrentServerInteractor.save(currentServer)
tokenRepository.save(currentServer, token)
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username)
analyticsManager.logLogin(
AuthenticationEvent.AuthenticationWithUserAndPassword,
true
)
navigator.toChatList()
} catch (exception: RocketChatException) {
if (exception is RocketChatAuthException) {
view.alertInvalidTwoFactorAuthenticationCode()
} else {
analyticsManager.logLogin(
AuthenticationEvent.AuthenticationWithUserAndPassword,
false
)
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
}
} finally {
view.hideLoading()
}
}
}
fun signup() = navigator.toSignUp()
private suspend fun saveAccount(me: Myself) {
val icon = settings.favicon()?.let {
currentServer.serverLogoUrl(it)
......
......@@ -6,9 +6,14 @@ import chat.rocket.android.core.behaviours.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.
......
package chat.rocket.android.authentication.twofactor.ui
import DrawableHelper
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
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 chat.rocket.android.R
import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.authentication.twofactor.presentation.TwoFAPresenter
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.setVisible
import chat.rocket.android.util.extensions.shake
import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.textContent
import chat.rocket.android.util.extensions.ui
import chat.rocket.android.util.extensions.vibrateSmartPhone
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 java.util.concurrent.TimeUnit
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 {
@Inject
lateinit var presenter: TwoFAPresenter
@Inject
lateinit var analyticsManager: AnalyticsManager
lateinit var username: String
lateinit var password: String
private lateinit var username: String
private lateinit var password: String
private lateinit var twoFaCodeDisposable: Disposable
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
// TODO - research a better way to initialize parameters on fragments.
username = arguments?.getString(USERNAME) ?: ""
password = arguments?.getString(PASSWORD) ?: ""
val bundle = arguments
if (bundle != null) {
username = bundle.getString(BUNDLE_USERNAME)
password = bundle.getString(BUNDLE_PASSWORD)
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
}
override fun onCreateView(
......@@ -54,42 +71,58 @@ class TwoFAFragment : Fragment(), TwoFAView {
super.onViewCreated(view, savedInstanceState)
activity?.apply {
text_two_factor_auth.requestFocus()
text_two_factor_authentication_code.requestFocus()
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(text_two_factor_auth, InputMethodManager.RESULT_UNCHANGED_SHOWN)
}
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
tintEditTextDrawableStart()
imm.showSoftInput(
text_two_factor_authentication_code,
InputMethodManager.RESULT_UNCHANGED_SHOWN
)
}
setupOnClickListener()
subscribeEditText()
analyticsManager.logScreenView(ScreenViewEvent.TwoFa)
}
override fun alertBlankTwoFactorAuthenticationCode() {
ui {
vibrateSmartPhone()
text_two_factor_auth.shake()
override fun onDestroyView() {
super.onDestroyView()
unsubscribeEditText()
}
override fun enableButtonConfirm() {
context?.let {
ViewCompat.setBackgroundTintList(
button_confirm, ContextCompat.getColorStateList(it, R.color.colorAccent)
)
button_confirm.isEnabled = true
}
}
override fun alertInvalidTwoFactorAuthenticationCode() {
showMessage(getString(R.string.msg_invalid_2fa_code))
override fun disableButtonConfirm() {
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() {
ui {
enableUserInput(false)
view_loading.setVisible(true)
disableUserInput()
view_loading.isVisible = true
}
}
override fun hideLoading() {
ui {
view_loading.setVisible(false)
enableUserInput(true)
view_loading.isVisible = false
enableUserInput()
}
}
......@@ -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() {
ui {
val lockDrawable =
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() {
enableButtonConfirm()
text_two_factor_authentication_code.isEnabled = true
}
private fun enableUserInput(value: Boolean) {
button_log_in.isEnabled = value
text_two_factor_auth.isEnabled = value
private fun disableUserInput() {
disableButtonConfirm()
text_two_factor_authentication_code.isEnabled = false
}
private fun setupOnClickListener() {
button_log_in.setOnClickListener {
presenter.authenticate(username, password, text_two_factor_auth.textContent)
button_confirm.setOnClickListener {
presenter.authenticate(
username,
password,
text_two_factor_authentication_code.textContent
)
}
}
// TODO - we could create an in memory repository to save username and password.
companion object {
private const val USERNAME = "username"
private const val PASSWORD = "password"
fun newInstance(username: String, password: String) = TwoFAFragment().apply {
arguments = Bundle(2).apply {
putString(USERNAME, username)
putString(PASSWORD, password)
private fun subscribeEditText() {
twoFaCodeDisposable = text_two_factor_authentication_code.asObservable()
.debounce(300, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribe {
if (it.isNotBlank()) {
enableButtonConfirm()
} else {
disableButtonConfirm()
}
}
}
}
private fun unsubscribeEditText() = twoFaCodeDisposable.dispose()
}
......@@ -5,23 +5,18 @@ import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.fragment.app.Fragment
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import chat.rocket.android.R
import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo
import chat.rocket.android.authentication.domain.model.getLoginDeepLinkInfo
import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.authentication.onboarding.ui.OnBoardingFragment
import chat.rocket.android.authentication.presentation.AuthenticationPresenter
import chat.rocket.android.authentication.server.ui.ServerFragment
import chat.rocket.android.util.extensions.addFragment
import dagger.android.AndroidInjection
import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
import dagger.android.support.HasSupportFragmentInjector
import kotlinx.android.synthetic.main.app_bar_chat_room.*
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch
import kotlinx.android.synthetic.main.app_bar.*
import javax.inject.Inject
class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector {
......@@ -29,39 +24,26 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector {
lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
@Inject
lateinit var presenter: AuthenticationPresenter
val job = Job()
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
setContentView(R.layout.activity_authentication)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_authentication)
setupToolbar()
}
private fun setupToolbar() {
setSupportActionBar(toolbar)
supportActionBar?.setDisplayShowTitleEnabled(false)
toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white_24dp)
toolbar.setNavigationOnClickListener {
onBackPressed()
with(toolbar) {
setSupportActionBar(this)
setNavigationIcon(R.drawable.ic_arrow_back_white_24dp)
setNavigationOnClickListener { onBackPressed() }
}
supportActionBar?.setDisplayShowTitleEnabled(false)
}
override fun onStart() {
super.onStart()
launch(UI + job) {
val newServer = intent.getBooleanExtra(INTENT_ADD_NEW_SERVER, false)
presenter.loadCredentials(newServer) { authenticated ->
if (!authenticated) {
showOnBoarding()
}
}
}
}
override fun onStop() {
job.cancel()
super.onStop()
loadCredentials()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
......@@ -70,15 +52,8 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector {
currentFragment?.onActivityResult(requestCode, resultCode, data)
}
override fun supportFragmentInjector(): AndroidInjector<Fragment> {
return fragmentDispatchingAndroidInjector
}
private fun showOnBoarding() {
addFragment("OnBoardingFragment", R.id.fragment_container, allowStateLoss = true) {
OnBoardingFragment.newInstance()
}
}
override fun supportFragmentInjector(): AndroidInjector<Fragment> =
fragmentDispatchingAndroidInjector
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.legal, menu)
......@@ -86,12 +61,31 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector {
}
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_privacy_policy -> presenter.privacyPolicy(getString(R.string.action_privacy_policy))
}
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"
......
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
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.
*
......
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.ViewAnimationUtils
import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator
import androidx.fragment.app.Fragment
import androidx.core.view.isVisible
fun View.rotateBy(value: Float, duration: Long = 100) {
animate()
......@@ -23,7 +15,7 @@ fun View.rotateBy(value: Float, duration: Long = 100) {
fun View.fadeIn(startValue: Float = 0f, finishValue: Float = 1f, duration: Long = 200) {
if (alpha == finishValue) {
setVisible(true)
isVisible = true
return
}
......@@ -38,12 +30,12 @@ fun View.fadeIn(startValue: Float = 0f, finishValue: Float = 1f, duration: Long
.setInterpolator(AccelerateInterpolator()).start()
}.start()
setVisible(true)
isVisible = true
}
fun View.fadeOut(startValue: Float = 1f, finishValue: Float = 0f, duration: Long = 200) {
if (alpha == finishValue) {
setVisible(false)
isVisible = false
return
}
......@@ -58,7 +50,7 @@ fun View.fadeOut(startValue: Float = 1f, finishValue: Float = 0f, duration: Long
.setInterpolator(AccelerateInterpolator()).start()
}.start()
setVisible(false)
isVisible = false
}
fun View.circularRevealOrUnreveal(
......@@ -72,43 +64,7 @@ fun View.circularRevealOrUnreveal(
ViewAnimationUtils.createCircularReveal(this, centerX, centerY, startRadius, endRadius)
anim.duration = duration
if (startRadius < endRadius) {
setVisible(true)
} else {
setVisible(false)
}
isVisible = startRadius < endRadius
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
import androidx.appcompat.view.menu.MenuBuilder
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import chat.rocket.android.R
fun AppCompatActivity.setLightStatusBar(view: View) {
fun FragmentActivity.setLightStatusBar(view: View) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
var flags = view.systemUiVisibility
flags = flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
......@@ -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) {
window.statusBarColor = ContextCompat.getColor(this, R.color.colorPrimary)
}
......
......@@ -2,5 +2,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#cbced1" />
<solid android:color="@color/colorAuthenticationChevronAndExpandIcon" />
</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 @@
<stroke
android:width="1dp"
android:color="@color/colorAuthenticationOnBoardingButtonBorder" />
android:color="@color/colorAuthenticationButtonBorderAndDivider" />
<corners android:radius="2dp" />
</shape>
\ No newline at end of file
......@@ -3,13 +3,13 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/AppTheme"
android:orientation="vertical"
android:theme="@style/AppTheme"
tools:context=".authentication.ui.AuthenticationActivity">
<include
android:id="@+id/layout_app_bar"
layout="@layout/app_bar_chat_room" />
layout="@layout/app_bar" />
<FrameLayout
android:id="@+id/fragment_container"
......
......@@ -2,19 +2,18 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/constraint_layout"
android:layout_width="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">
<TextView
android:id="@+id/text_sign_in_to_your_server"
android:id="@+id/text_login"
style="@style/Authentication.Headline.TextView"
android:text="@string/title_log_in"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="21dp"
android:layout_marginTop="30dp" />
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/text_username_or_email"
......@@ -26,7 +25,7 @@
android:inputType="textEmailAddress|text"
app:layout_constraintLeft_toLeftOf="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
android:id="@+id/image_key"
......@@ -48,56 +47,45 @@
android:hint="@string/msg_password"
android:imeOptions="actionDone"
android:inputType="textPassword"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_username_or_email" />
<Button
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:visibility="visible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_password"
android:layout_marginTop="20dp" />
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_password" />
<TextView
android:id="@+id/text_forgot_your_password"
android:layout_width="wrap_content"
<Button
android:id="@+id/button_forgot_your_password"
style="?borderlessButtonStyle"
android:layout_width="wrap_content"
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:textStyle="bold"
android:textColor="@color/colorAccent"
android:textAllCaps="false"
app:layout_constraintStart_toStartOf="parent"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_log_in"
android:layout_marginTop="10dp"/>
tools:visibility="visible" />
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
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"
tools:visibility="visible" />
<Button
android:id="@+id/button_cas"
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" />
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
......@@ -11,7 +11,6 @@
android:id="@+id/image_on_boarding"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:src="@drawable/ic_onboarding"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
......@@ -74,7 +73,7 @@
android:layout_centerVertical="true"
android:layout_marginEnd="8dp"
android:src="@drawable/ic_chevron_right_black_24dp"
android:tint="@color/colorAuthenticationOnBoardingChevron"
android:tint="@color/colorAuthenticationChevronAndExpandIcon"
tools:ignore="ContentDescription" />
</RelativeLayout>
......@@ -126,7 +125,7 @@
android:layout_centerVertical="true"
android:layout_marginEnd="8dp"
android:src="@drawable/ic_chevron_right_black_24dp"
android:tint="@color/colorAuthenticationOnBoardingChevron"
android:tint="@color/colorAuthenticationChevronAndExpandIcon"
tools:ignore="ContentDescription" />
</RelativeLayout>
......
......@@ -4,6 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/screen_edge_left_and_right_margins"
tools:context=".authentication.registerusername.ui.RegisterUsernameFragment">
<TextView
......@@ -11,39 +12,38 @@
style="@style/Authentication.Headline.TextView"
android:text="@string/title_register_username"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="30dp"
android:layout_marginStart="21dp" />
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/text_username"
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:drawableStart="@drawable/ic_at_black_20dp"
android:hint="@string/msg_username"
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
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"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_username"
android:layout_marginTop="20dp" />
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_username" />
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
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"
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>
\ No newline at end of file
......@@ -4,46 +4,46 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/screen_edge_left_and_right_margins"
tools:context=".authentication.resetpassword.ui.ResetPasswordFragment">
<TextView
android:id="@+id/text_sign_in_to_your_server"
android:id="@+id/text_reset_password"
style="@style/Authentication.Headline.TextView"
android:text="@string/title_reset_password"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="21dp"
android:layout_marginTop="30dp" />
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/text_email"
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="30dp"
android:layout_marginTop="16dp"
android:drawableStart="@drawable/ic_email_black_20dp"
android:hint="@string/msg_email"
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
android:id="@+id/button_reset_password"
style="@style/Authentication.Button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
style="@style/Authentication.Button.Flat"
android:layout_marginTop="20dp"
app:layout_constraintTop_toBottomOf="@id/text_email"
android:text="@string/title_reset_password" />
android:backgroundTint="@color/colorAuthenticationButtonDisabled"
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
android:id="@+id/view_loading"
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"
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>
\ No newline at end of file
......@@ -14,7 +14,6 @@
android:id="@+id/image_server"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:src="@drawable/ic_server"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
......@@ -40,17 +39,11 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_sign_in_to_your_server">
<FrameLayout
android:id="@+id/protocol_container"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<Spinner
android:id="@+id/spinner_server_protocol"
android:layout_width="100dp"
android:layout_height="match_parent"
android:popupBackground="@color/colorWhite" />
</FrameLayout>
<Spinner
android:id="@+id/spinner_server_protocol"
android:layout_width="100dp"
android:layout_height="match_parent"
android:popupBackground="@color/colorWhite" />
<EditText
android:id="@+id/text_server_url"
......@@ -60,7 +53,7 @@
android:layout_centerVertical="true"
android:layout_marginStart="6dp"
android:layout_marginEnd="16dp"
android:layout_toEndOf="@+id/protocol_container"
android:layout_toEndOf="@+id/spinner_server_protocol"
android:background="@color/colorWhite"
android:cursorVisible="false"
android:hint="@string/server_hint_url"
......@@ -68,11 +61,11 @@
android:inputType="text|textUri" />
</RelativeLayout>
<androidx.appcompat.widget.AppCompatButton
<Button
android:id="@+id/button_connect"
style="@style/Authentication.Button"
style="@style/Authentication.Button.Flat"
android:layout_marginTop="20dp"
android:backgroundTint="@color/colorAuthenticationOnBoardingButtonDisabled"
android:backgroundTint="@color/colorAuthenticationButtonDisabled"
android:enabled="false"
android:text="@string/action_connect"
app:layout_constraintEnd_toEndOf="parent"
......
......@@ -4,55 +4,44 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/screen_edge_left_and_right_margins"
tools:context=".authentication.twofactor.ui.TwoFAFragment">
<TextView
android:id="@+id/text_sign_in_to_your_server"
android:id="@+id/text_two_factor_authentication"
style="@style/Authentication.Headline.TextView"
android:text="Two-factor Authentication"
app:layout_constraintStart_toStartOf="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" />
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/text_two_factor_auth"
android:id="@+id/text_two_factor_authentication_code"
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:drawableStart="@drawable/ic_vpn_key_black_24dp"
android:hint="@string/msg_2fa_code"
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
android:id="@+id/button_log_in"
style="@style/Authentication.Button"
android:id="@+id/button_confirm"
style="@style/Authentication.Button.Flat"
android:layout_marginTop="20dp"
android:backgroundTint="@color/colorAuthenticationButtonDisabled"
android:enabled="false"
android:text="@string/action_confirm"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_two_factor_auth"
android:layout_marginTop="20dp" />
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_two_factor_authentication_code" />
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
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"
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>
\ 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 @@
<resources>
<!-- Main colors -->
<color name="colorPrimary">#2f343d</color>
<color name="colorPrimaryDark">#2f343d</color>
<color name="colorPrimary">#FF2F343D</color>
<color name="colorPrimaryDark">#FF2F343D</color>
<color name="colorAccent">#FF1D74F5</color>
<!-- Text colors -->
......@@ -23,10 +23,10 @@
<color name="colorRed">#FFFF0000</color>
<!-- Authentication colors -->
<color name="colorAuthenticationOnBoardingButtonBorder">#FFE1E5E8</color>
<color name="colorAuthenticationOnBoardingButtonDisabled">#FFE1E5E8</color>
<color name="colorAuthenticationOnBoardingChevron">#FFCBCED1</color>
<color name="colorAuthenticationOnBoardingSecondaryText">#FF9EA2A8</color>
<color name="colorAuthenticationButtonBorderAndDivider">#FFE1E5E8</color>
<color name="colorAuthenticationButtonDisabled">#FFE1E5E8</color>
<color name="colorAuthenticationChevronAndExpandIcon">#FFCBCED1</color>
<color name="colorAuthenticationSecondaryText">#FF9EA2A8</color>
<color name="darkGray">#FFa0a0a0</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