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
......@@ -2,10 +2,8 @@ package chat.rocket.android.authentication.login.presentation
import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.AuthenticationEvent
import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.OauthHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetConnectingServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
......@@ -13,35 +11,17 @@ import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.SaveAccountInteractor
import chat.rocket.android.server.domain.SaveCurrentServerInteractor
import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.domain.casLoginUrl
import chat.rocket.android.server.domain.favicon
import chat.rocket.android.server.domain.gitlabUrl
import chat.rocket.android.server.domain.isCasAuthenticationEnabled
import chat.rocket.android.server.domain.isFacebookAuthenticationEnabled
import chat.rocket.android.server.domain.isGithubAuthenticationEnabled
import chat.rocket.android.server.domain.isGitlabAuthenticationEnabled
import chat.rocket.android.server.domain.isGoogleAuthenticationEnabled
import chat.rocket.android.server.domain.isLdapAuthenticationEnabled
import chat.rocket.android.server.domain.isLinkedinAuthenticationEnabled
import chat.rocket.android.server.domain.isLoginFormEnabled
import chat.rocket.android.server.domain.isPasswordResetEnabled
import chat.rocket.android.server.domain.isRegistrationEnabledForNewUsers
import chat.rocket.android.server.domain.isWordpressAuthenticationEnabled
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.domain.wideTile
import chat.rocket.android.server.domain.wordpressUrl
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.casUrl
import chat.rocket.android.util.extensions.encodeToBase64
import chat.rocket.android.util.extensions.generateRandomString
import chat.rocket.android.util.extensions.isEmail
import chat.rocket.android.util.extensions.parseColor
import chat.rocket.android.util.extensions.samlUrl
import chat.rocket.android.util.extensions.serverLogoUrl
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatAuthException
import chat.rocket.common.RocketChatException
import chat.rocket.common.RocketChatTwoFactorException
import chat.rocket.common.model.Email
......@@ -50,30 +30,11 @@ import chat.rocket.common.model.User
import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login
import chat.rocket.core.internal.rest.loginWithCas
import chat.rocket.core.internal.rest.loginWithEmail
import chat.rocket.core.internal.rest.loginWithLdap
import chat.rocket.core.internal.rest.loginWithOauth
import chat.rocket.core.internal.rest.loginWithSaml
import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.settingsOauth
import kotlinx.coroutines.experimental.delay
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
private const val TYPE_LOGIN_USER_EMAIL = 0
private const val TYPE_LOGIN_CAS = 1
private const val TYPE_LOGIN_SAML = 2
private const val TYPE_LOGIN_OAUTH = 3
private const val TYPE_LOGIN_DEEP_LINK = 4
private const val SERVICE_NAME_FACEBOOK = "facebook"
private const val SERVICE_NAME_GITHUB = "github"
private const val SERVICE_NAME_GOOGLE = "google"
private const val SERVICE_NAME_LINKEDIN = "linkedin"
private const val SERVICE_NAME_GILAB = "gitlab"
private const val SERVICE_NAME_WORDPRESS = "wordpress"
class LoginPresenter @Inject constructor(
private val view: LoginView,
private val strategy: CancelStrategy,
......@@ -82,240 +43,45 @@ class LoginPresenter @Inject constructor(
private val localRepository: LocalRepository,
private val settingsInteractor: GetSettingsInteractor,
private val analyticsManager: AnalyticsManager,
serverInteractor: GetConnectingServerInteractor,
private val saveCurrentServer: SaveCurrentServerInteractor,
private val saveAccountInteractor: SaveAccountInteractor,
private val factory: RocketChatClientFactory
private val factory: RocketChatClientFactory,
val serverInteractor: GetConnectingServerInteractor
) {
// TODO - we should validate the current server when opening the app, and have a nonnull get()
private var currentServer = serverInteractor.get()!!
private lateinit var client: RocketChatClient
private lateinit var settings: PublicSettings
private lateinit var usernameOrEmail: String
private lateinit var password: String
private lateinit var credentialToken: String
private lateinit var credentialSecret: String
private lateinit var deepLinkUserId: String
private lateinit var deepLinkToken: String
private lateinit var loginMethod: AuthenticationEvent
fun setupView() {
setupConnectionInfo(currentServer)
setupLoginView()
setupForgotPasswordView()
setupCasView()
setupOauthServicesView()
}
fun authenticateWithUserAndPassword(usernameOrEmail: String, password: String) {
when {
usernameOrEmail.isBlank() -> {
view.alertWrongUsernameOrEmail()
}
password.isEmpty() -> {
view.alertWrongPassword()
}
else -> {
this.usernameOrEmail = usernameOrEmail
this.password = password
loginMethod = AuthenticationEvent.AuthenticationWithUserAndPassword
doAuthentication(TYPE_LOGIN_USER_EMAIL)
}
}
}
fun authenticateWithCas(casToken: String) {
credentialToken = casToken
loginMethod = AuthenticationEvent.AuthenticationWithCas
doAuthentication(TYPE_LOGIN_CAS)
}
fun authenticateWithSaml(samlToken: String) {
credentialToken = samlToken
loginMethod = AuthenticationEvent.AuthenticationWithSaml
doAuthentication(TYPE_LOGIN_SAML)
}
fun authenticateWithOauth(oauthToken: String, oauthSecret: String) {
credentialToken = oauthToken
credentialSecret = oauthSecret
loginMethod = AuthenticationEvent.AuthenticationWithOauth
doAuthentication(TYPE_LOGIN_OAUTH)
}
fun authenticateWithDeepLink(deepLinkInfo: LoginDeepLinkInfo) {
val serverUrl = deepLinkInfo.url
setupConnectionInfo(serverUrl)
if (deepLinkInfo.userId != null && deepLinkInfo.token != null) {
deepLinkUserId = deepLinkInfo.userId
deepLinkToken = deepLinkInfo.token
tokenRepository.save(serverUrl, Token(deepLinkUserId, deepLinkToken))
loginMethod = AuthenticationEvent.AuthenticationWithDeeplink
doAuthentication(TYPE_LOGIN_DEEP_LINK)
} else {
// If we don't have the login credentials, just go through normal setup and user input.
setupView()
}
}
private fun setupConnectionInfo(serverUrl: String) {
currentServer = serverUrl
client = factory.create(serverUrl)
settings = settingsInteractor.get(serverUrl)
}
fun signup() = navigator.toSignUp()
fun forgotPassword() = navigator.toForgotPassword()
private fun setupLoginView() {
if (settings.isLoginFormEnabled()) {
view.showFormView()
view.setupLoginButtonListener()
} else {
view.hideFormView()
}
}
private fun setupCasView() {
if (settings.isCasAuthenticationEnabled()) {
val casToken = generateRandomString(17)
view.setupCasButtonListener(
settings.casLoginUrl().casUrl(currentServer, casToken),
casToken
)
view.showCasButton()
}
client = factory.create(currentServer)
settings = settingsInteractor.get(currentServer)
}
private fun setupForgotPasswordView() {
if (settings.isPasswordResetEnabled()) {
view.setupForgotPasswordView()
view.showForgotPasswordView()
}
}
private fun setupOauthServicesView() {
launchUI(strategy) {
try {
val services = retryIO("settingsOauth()") {
client.settingsOauth().services
}
if (services.isNotEmpty()) {
val state =
"{\"loginStyle\":\"popup\",\"credentialToken\":\"${generateRandomString(40)}\",\"isCordova\":true}".encodeToBase64()
var totalSocialAccountsEnabled = 0
getCustomOauthServices(services).let {
for (serviceMap in it) {
val serviceName = getCustomOauthServiceName(serviceMap)
val host = getCustomOauthHost(serviceMap)
val authorizePath = getCustomOauthAuthorizePath(serviceMap)
val clientId = getOauthClientId(serviceMap)
val scope = getCustomOauthScope(serviceMap)
val textColor = getServiceNameColorForCustomOauthOrSaml(serviceMap)
val buttonColor = getServiceButtonColor(serviceMap)
if (serviceName != null &&
host != null &&
authorizePath != null &&
clientId != null &&
scope != null &&
textColor != null &&
buttonColor != null
) {
val customOauthUrl = OauthHelper.getCustomOauthUrl(
host,
authorizePath,
clientId,
currentServer,
serviceName,
state,
scope
)
view.addCustomOauthServiceButton(
customOauthUrl,
state,
serviceName,
textColor,
buttonColor
)
totalSocialAccountsEnabled++
}
}
}
getSamlServices(services).let {
val samlToken = generateRandomString(17)
for (serviceMap in it) {
val provider = getSamlProvider(serviceMap)
val serviceName = getSamlServiceName(serviceMap)
val textColor = getServiceNameColorForCustomOauthOrSaml(serviceMap)
val buttonColor = getServiceButtonColor(serviceMap)
if (provider != null &&
serviceName != null &&
textColor != null &&
buttonColor != null
) {
view.addSamlServiceButton(
currentServer.samlUrl(provider, samlToken),
samlToken,
serviceName,
textColor,
buttonColor
)
totalSocialAccountsEnabled++
}
}
}
}
} catch (exception: RocketChatException) {
Timber.e(exception)
}
}
}
private fun doAuthentication(loginType: Int) {
fun authenticateWithUserAndPassword(usernameOrEmail: String, password: String) {
launchUI(strategy) {
view.disableUserInput()
view.showLoading()
try {
val token = retryIO("login") {
when (loginType) {
TYPE_LOGIN_USER_EMAIL -> {
when {
settings.isLdapAuthenticationEnabled() ->
client.loginWithLdap(usernameOrEmail, password)
usernameOrEmail.isEmail() ->
client.loginWithEmail(usernameOrEmail, password)
else ->
client.login(usernameOrEmail, password)
}
}
TYPE_LOGIN_CAS -> {
delay(3, TimeUnit.SECONDS)
client.loginWithCas(credentialToken)
}
TYPE_LOGIN_SAML -> {
delay(3, TimeUnit.SECONDS)
client.loginWithSaml(credentialToken)
}
TYPE_LOGIN_OAUTH -> {
client.loginWithOauth(credentialToken, credentialSecret)
}
TYPE_LOGIN_DEEP_LINK -> {
val myself = client.me() // Just checking if the credentials worked.
if (myself.id == deepLinkUserId) {
Token(deepLinkUserId, deepLinkToken)
} else {
throw RocketChatAuthException("Invalid AuthenticationEvent Deep Link Credentials...")
}
}
else -> {
throw IllegalStateException("Expected TYPE_LOGIN_USER_EMAIL, TYPE_LOGIN_CAS,TYPE_LOGIN_SAML, TYPE_LOGIN_OAUTH or TYPE_LOGIN_DEEP_LINK")
}
when {
settings.isLdapAuthenticationEnabled() ->
client.loginWithLdap(usernameOrEmail, password)
usernameOrEmail.isEmail() ->
client.loginWithEmail(usernameOrEmail, password)
else ->
client.login(usernameOrEmail, password)
}
}
val myself = retryIO("me()") { client.me() }
......@@ -334,13 +100,12 @@ class LoginPresenter @Inject constructor(
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, myself.username)
saveAccount(myself.username!!)
saveToken(token)
analyticsManager.logLogin(loginMethod, true)
if (loginType == TYPE_LOGIN_USER_EMAIL) {
view.saveSmartLockCredentials(usernameOrEmail, password)
}
analyticsManager.logLogin(
AuthenticationEvent.AuthenticationWithUserAndPassword,
true
)
view.saveSmartLockCredentials(usernameOrEmail, password)
navigator.toChatList()
} else if (loginType == TYPE_LOGIN_OAUTH) {
navigator.toRegisterUsername(token.userId, token.authToken)
}
} catch (exception: RocketChatException) {
when (exception) {
......@@ -348,7 +113,10 @@ class LoginPresenter @Inject constructor(
navigator.toTwoFA(usernameOrEmail, password)
}
else -> {
analyticsManager.logLogin(loginMethod, false)
analyticsManager.logLogin(
AuthenticationEvent.AuthenticationWithUserAndPassword,
false
)
exception.message?.let {
view.showMessage(it)
}.ifNull {
......@@ -358,119 +126,12 @@ class LoginPresenter @Inject constructor(
}
} finally {
view.hideLoading()
view.enableUserInput()
}
}
}
/**
* Returns an OAuth service map given a [serviceName].
*
* @param listMap The list of [Map] to get the service from.
* @param serviceName The service name to get in the [listMap]
* @return The OAuth service map or null otherwise.
*/
private fun getServiceMap(
listMap: List<Map<String, Any>>,
serviceName: String
): Map<String, Any>? = listMap.find { map -> map.containsValue(serviceName) }
/**
* Returns the OAuth client ID of a [serviceMap].
* REMARK: This function works for common OAuth providers (Google, Facebook, Github and so on)
* as well as custom OAuth.
*
* @param serviceMap The service map to get the OAuth client ID.
* @return The OAuth client ID or null otherwise.
*/
private fun getOauthClientId(serviceMap: Map<String, Any>): String? =
serviceMap["clientId"] as? String ?: serviceMap["appId"] as? String
/**
* Returns a custom OAuth service list.
*
* @return A custom OAuth service list, otherwise an empty list if there is no custom OAuth service.
*/
private fun getCustomOauthServices(listMap: List<Map<String, Any>>): List<Map<String, Any>> =
listMap.filter { map -> map["custom"] == true }
/** Returns the custom OAuth service host.
*
* @param serviceMap The service map to get the custom OAuth service host.
* @return The custom OAuth service host, otherwise null.
*/
private fun getCustomOauthHost(serviceMap: Map<String, Any>): String? =
serviceMap["serverURL"] as? String
/** Returns the custom OAuth service authorize path.
*
* @param serviceMap The service map to get the custom OAuth service authorize path.
* @return The custom OAuth service authorize path, otherwise null.
*/
private fun getCustomOauthAuthorizePath(serviceMap: Map<String, Any>): String? =
serviceMap["authorizePath"] as? String
/** Returns the custom OAuth service scope.
*
* @param serviceMap The service map to get the custom OAuth service scope.
* @return The custom OAuth service scope, otherwise null.
*/
private fun getCustomOauthScope(serviceMap: Map<String, Any>): String? =
serviceMap["scope"] as? String
/** Returns the text of the custom OAuth service.
*
* @param serviceMap The service map to get the text of the custom OAuth service.
* @return The text of the custom OAuth service, otherwise null.
*/
private fun getCustomOauthServiceName(serviceMap: Map<String, Any>): String? =
serviceMap["service"] as? String
/**
* Returns a SAML OAuth service list.
*
* @return A SAML service list, otherwise an empty list if there is no SAML OAuth service.
*/
private fun getSamlServices(listMap: List<Map<String, Any>>): List<Map<String, Any>> =
listMap.filter { map -> map["service"] == "saml" }
/**
* Returns the SAML provider.
*
* @param serviceMap The service map to provider from.
* @return The SAML provider, otherwise null.
*/
private fun getSamlProvider(serviceMap: Map<String, Any>): String? =
(serviceMap["clientConfig"] as Map<*, *>)["provider"] as? String
/**
* Returns the text of the SAML service.
*
* @param serviceMap The service map to get the text of the SAML service.
* @return The text of the SAML service, otherwise null.
*/
private fun getSamlServiceName(serviceMap: Map<String, Any>): String? =
serviceMap["buttonLabelText"] as? String
/**
* Returns the text color of the service name.
* REMARK: This can be used for custom OAuth or SAML.
*
* @param serviceMap The service map to get the text color from.
* @return The text color of the service (custom OAuth or SAML), otherwise null.
*/
private fun getServiceNameColorForCustomOauthOrSaml(serviceMap: Map<String, Any>): Int? =
(serviceMap["buttonLabelColor"] as? String)?.parseColor()
}
/**
* Returns the button color of the service name.
* REMARK: This can be used for custom OAuth or SAML.
*
* @param serviceMap The service map to get the button color from.
* @return The button color of the service (custom OAuth or SAML), otherwise null.
*/
private fun getServiceButtonColor(serviceMap: Map<String, Any>): Int? =
(serviceMap["buttonColor"] as? String)?.parseColor()
fun forgotPassword() = navigator.toForgotPassword()
private suspend fun saveAccount(username: String) {
val icon = settings.favicon()?.let {
......@@ -484,7 +145,5 @@ class LoginPresenter @Inject constructor(
saveAccountInteractor.save(account)
}
private fun saveToken(token: Token) {
tokenRepository.save(currentServer, token)
}
private fun saveToken(token: Token) = tokenRepository.save(currentServer, token)
}
\ 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.login.ui
import DrawableHelper
import android.app.Activity
import android.content.Intent
import android.graphics.PorterDuff
import android.os.Build
import android.os.Bundle
import android.text.style.ClickableSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.widget.Button
import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.ScrollView
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import androidx.core.view.postDelayed
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.domain.model.LoginDeepLinkInfo
import chat.rocket.android.authentication.login.presentation.LoginPresenter
import chat.rocket.android.authentication.login.presentation.LoginView
import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.TextHelper
import chat.rocket.android.helper.getCredentials
import chat.rocket.android.helper.hasCredentialsSupport
import chat.rocket.android.helper.requestStoredCredentials
import chat.rocket.android.helper.saveCredentials
import chat.rocket.android.util.extension.asObservable
import chat.rocket.android.util.extensions.inflate
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 chat.rocket.android.webview.oauth.ui.INTENT_OAUTH_CREDENTIAL_SECRET
import chat.rocket.android.webview.oauth.ui.INTENT_OAUTH_CREDENTIAL_TOKEN
import chat.rocket.android.webview.oauth.ui.oauthWebViewIntent
import chat.rocket.android.webview.sso.ui.INTENT_SSO_TOKEN
import chat.rocket.android.webview.sso.ui.ssoWebViewIntent
import chat.rocket.common.util.ifNull
import dagger.android.support.AndroidSupportInjection
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.Observables
import kotlinx.android.synthetic.main.fragment_authentication_log_in.*
import javax.inject.Inject
internal const val TAG_LOGIN_FRAGMENT = "LoginFragment"
internal const val REQUEST_CODE_FOR_SIGN_IN_REQUIRED = 1
internal const val REQUEST_CODE_FOR_MULTIPLE_ACCOUNTS_RESOLUTION = 2
internal const val REQUEST_CODE_FOR_SAVE_RESOLUTION = 3
internal const val REQUEST_CODE_FOR_CAS = 4
internal const val REQUEST_CODE_FOR_SAML = 5
internal const val REQUEST_CODE_FOR_OAUTH = 6
fun newInstance() = LoginFragment()
class LoginFragment : Fragment(), LoginView {
@Inject
lateinit var presenter: LoginPresenter
@Inject
lateinit var analyticsManager: AnalyticsManager
private var isOauthViewEnable = false
private var deepLinkInfo: LoginDeepLinkInfo? = null
companion object {
private const val DEEP_LINK_INFO = "DeepLinkInfo"
fun newInstance(deepLinkInfo: LoginDeepLinkInfo? = null) = LoginFragment().apply {
arguments = Bundle().apply {
putParcelable(DEEP_LINK_INFO, deepLinkInfo)
}
}
}
private val editTextsDisposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
deepLinkInfo = arguments?.getParcelable(DEEP_LINK_INFO)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? =
container?.inflate(R.layout.fragment_authentication_log_in)
): View? = container?.inflate(R.layout.fragment_authentication_log_in)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter.setupView()
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
tintEditTextDrawableStart()
}
deepLinkInfo?.let {
presenter.authenticateWithDeepLink(it)
}.ifNull {
presenter.setupView()
}
if (!hasCredentialsSupport()) {
image_key.isVisible = false
}
subscribeEditTexts()
setupOnClickListener()
image_key.isVisible = hasCredentialsSupport()
analyticsManager.logScreenView(ScreenViewEvent.Login)
}
......@@ -109,32 +69,16 @@ class LoginFragment : Fragment(), LoginView {
if (resultCode == Activity.RESULT_OK) {
if (data != null) {
when (requestCode) {
REQUEST_CODE_FOR_MULTIPLE_ACCOUNTS_RESOLUTION -> {
REQUEST_CODE_FOR_MULTIPLE_ACCOUNTS_RESOLUTION ->
getCredentials(data)?.let {
onCredentialRetrieved(it.first, it.second)
}
}
REQUEST_CODE_FOR_SIGN_IN_REQUIRED -> {
REQUEST_CODE_FOR_SIGN_IN_REQUIRED ->
getCredentials(data)?.let { credential ->
text_username_or_email.setText(credential.first)
text_password.setText(credential.second)
}
}
REQUEST_CODE_FOR_SAVE_RESOLUTION -> {
showMessage(getString(R.string.message_credentials_saved_successfully))
}
REQUEST_CODE_FOR_CAS -> {
presenter.authenticateWithCas(data.getStringExtra(INTENT_SSO_TOKEN))
}
REQUEST_CODE_FOR_SAML -> data.apply {
presenter.authenticateWithSaml(getStringExtra(INTENT_SSO_TOKEN))
}
REQUEST_CODE_FOR_OAUTH -> {
presenter.authenticateWithOauth(
data.getStringExtra(INTENT_OAUTH_CREDENTIAL_TOKEN),
data.getStringExtra(INTENT_OAUTH_CREDENTIAL_SECRET)
)
}
REQUEST_CODE_FOR_SAVE_RESOLUTION -> showMessage(getString(R.string.message_credentials_saved_successfully))
}
}
}
......@@ -148,42 +92,14 @@ class LoginFragment : Fragment(), LoginView {
}
}
private fun tintEditTextDrawableStart() {
ui {
val personDrawable =
DrawableHelper.getDrawableFromId(R.drawable.ic_assignment_ind_black_24dp, it)
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_lock_black_24dp, it)
val drawables = arrayOf(personDrawable, lockDrawable)
DrawableHelper.wrapDrawables(drawables)
DrawableHelper.tintDrawables(drawables, it, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawables(
arrayOf(text_username_or_email, text_password),
drawables
)
}
}
private fun requestStoredCredentials() {
activity?.let {
it.requestStoredCredentials()?.let { credentials ->
onCredentialRetrieved(credentials.first, credentials.second)
}
}
}
private fun onCredentialRetrieved(id: String, password: String) {
presenter.authenticateWithUserAndPassword(id, password)
}
override fun saveSmartLockCredentials(id: String, password: String) {
activity?.let {
it.saveCredentials(id, password)
}
override fun onDestroyView() {
super.onDestroyView()
unsubscribeEditTexts()
}
override fun showLoading() {
ui {
disableUserInput()
view_loading.isVisible = true
}
}
......@@ -191,6 +107,7 @@ class LoginFragment : Fragment(), LoginView {
override fun hideLoading() {
ui {
view_loading.isVisible = false
enableUserInput()
}
}
......@@ -206,27 +123,10 @@ class LoginFragment : Fragment(), LoginView {
}
}
override fun showGenericErrorMessage() {
showMessage(R.string.msg_generic_error)
}
override fun showFormView() {
ui {
text_username_or_email.isVisible = true
text_password.isVisible = true
image_key.isVisible = true
}
}
override fun showGenericErrorMessage() = showMessage(R.string.msg_generic_error)
override fun hideFormView() {
ui {
text_username_or_email.isVisible = false
text_password.isVisible = false
}
}
override fun setupLoginButtonListener() {
ui {
private fun setupOnClickListener() =
ui { _ ->
button_log_in.setOnClickListener {
presenter.authenticateWithUserAndPassword(
text_username_or_email.textContent,
......@@ -234,141 +134,82 @@ class LoginFragment : Fragment(), LoginView {
)
}
}
}
override fun enableUserInput() {
ui {
button_log_in.isEnabled = true
text_username_or_email.isEnabled = true
text_password.isEnabled = true
}
}
override fun showForgotPasswordView() {
ui { _ ->
button_forgot_your_password.isVisible = true
button_forgot_your_password.setOnClickListener { presenter.forgotPassword() }
override fun disableUserInput() {
ui {
button_log_in.isEnabled = false
text_username_or_email.isEnabled = false
text_password.isEnabled = false
}
}
override fun showCasButton() {
ui {
button_cas.isVisible = true
override fun enableButtonLogin() {
context?.let {
ViewCompat.setBackgroundTintList(
button_log_in, ContextCompat.getColorStateList(it, R.color.colorAccent)
)
button_log_in.isEnabled = true
}
}
override fun hideCasButton() {
ui {
button_cas.isVisible = false
}
}
override fun setupCasButtonListener(casUrl: String, casToken: String) {
ui { activity ->
button_cas.setOnClickListener {
startActivityForResult(
activity.ssoWebViewIntent(casUrl, casToken),
REQUEST_CODE_FOR_CAS
)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
override fun disableButtonLogin() {
context?.let {
ViewCompat.setBackgroundTintList(
button_log_in,
ContextCompat.getColorStateList(it, R.color.colorAuthenticationButtonDisabled)
)
button_log_in.isEnabled = false
}
}
override fun showForgotPasswordView() {
ui {
text_forgot_your_password.isVisible = true
private fun requestStoredCredentials() {
activity?.requestStoredCredentials()?.let { credentials ->
onCredentialRetrieved(credentials.first, credentials.second)
}
}
override fun setupForgotPasswordView() {
ui {
text_forgot_your_password.text = String.format(getString(R.string.msg_forgot_password))
text_forgot_your_password.setOnClickListener {
presenter.forgotPassword()
}
}
private fun onCredentialRetrieved(id: String, password: String) {
presenter.authenticateWithUserAndPassword(id, password)
}
override fun addCustomOauthServiceButton(
customOauthUrl: String,
state: String,
serviceName: String,
serviceNameColor: Int,
buttonColor: Int
) {
ui { activity ->
val button = getCustomServiceButton(serviceName, serviceNameColor, buttonColor)
button.setOnClickListener {
startActivityForResult(
activity.oauthWebViewIntent(customOauthUrl, state),
REQUEST_CODE_FOR_OAUTH
)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
}
override fun saveSmartLockCredentials(id: String, password: String) {
activity?.saveCredentials(id, password)
}
override fun addSamlServiceButton(
samlUrl: String,
samlToken: String,
serviceName: String,
serviceNameColor: Int,
buttonColor: Int
) {
ui { activity ->
val button = getCustomServiceButton(serviceName, serviceNameColor, buttonColor)
button.setOnClickListener {
startActivityForResult(
activity.ssoWebViewIntent(samlUrl, samlToken),
REQUEST_CODE_FOR_SAML
)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
}
private fun subscribeEditTexts() {
editTextsDisposable.add(
Observables.combineLatest(
text_username_or_email.asObservable(),
text_password.asObservable()
) { text_username_or_email, text_password ->
return@combineLatest (
text_username_or_email.isNotBlank() && text_password.isNotBlank()
)
}.subscribe { isValid ->
if (isValid) {
enableButtonLogin()
} else {
disableButtonLogin()
}
})
}
override fun alertWrongUsernameOrEmail() {
private fun unsubscribeEditTexts() = editTextsDisposable.clear()
private fun enableUserInput() {
ui {
vibrateSmartPhone()
text_username_or_email.shake()
text_username_or_email.requestFocus()
enableButtonLogin()
text_username_or_email.isEnabled = true
text_password.isEnabled = true
}
}
override fun alertWrongPassword() {
private fun disableUserInput() {
ui {
vibrateSmartPhone()
text_password.shake()
text_password.requestFocus()
disableButtonLogin()
text_username_or_email.isEnabled = false
text_password.isEnabled = false
}
}
/**
* Gets a stylized custom service button.
*/
private fun getCustomServiceButton(
buttonText: String,
buttonTextColor: Int,
buttonBgColor: Int
): Button {
val params: LinearLayout.LayoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
val margin = resources.getDimensionPixelSize(R.dimen.screen_edge_left_and_right_margins)
params.setMargins(margin, margin, margin, 0)
val button = Button(context)
button.layoutParams = params
button.text = buttonText
button.setTextColor(buttonTextColor)
button.background.setColorFilter(buttonBgColor, PorterDuff.Mode.MULTIPLY)
return button
}
}
\ 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
......@@ -2,37 +2,68 @@ package chat.rocket.android.authentication.loginoptions.presentation
import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.AuthenticationEvent
import chat.rocket.android.authentication.login.presentation.*
import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.OauthHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.*
import chat.rocket.android.server.domain.GetConnectingServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.SaveAccountInteractor
import chat.rocket.android.server.domain.SaveCurrentServerInteractor
import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.domain.casLoginUrl
import chat.rocket.android.server.domain.favicon
import chat.rocket.android.server.domain.gitlabUrl
import chat.rocket.android.server.domain.isCasAuthenticationEnabled
import chat.rocket.android.server.domain.isFacebookAuthenticationEnabled
import chat.rocket.android.server.domain.isGithubAuthenticationEnabled
import chat.rocket.android.server.domain.isGitlabAuthenticationEnabled
import chat.rocket.android.server.domain.isGoogleAuthenticationEnabled
import chat.rocket.android.server.domain.isLinkedinAuthenticationEnabled
import chat.rocket.android.server.domain.isLoginFormEnabled
import chat.rocket.android.server.domain.isRegistrationEnabledForNewUsers
import chat.rocket.android.server.domain.isWordpressAuthenticationEnabled
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.domain.wideTile
import chat.rocket.android.server.domain.wordpressUrl
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.*
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.casUrl
import chat.rocket.android.util.extensions.generateRandomString
import chat.rocket.android.util.extensions.parseColor
import chat.rocket.android.util.extensions.samlUrl
import chat.rocket.android.util.extensions.serverLogoUrl
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatAuthException
import chat.rocket.common.RocketChatException
import chat.rocket.common.RocketChatTwoFactorException
import chat.rocket.common.model.Email
import chat.rocket.common.model.Token
import chat.rocket.common.model.User
import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.*
import chat.rocket.core.internal.rest.loginWithCas
import chat.rocket.core.internal.rest.loginWithOauth
import chat.rocket.core.internal.rest.loginWithSaml
import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.settingsOauth
import kotlinx.coroutines.experimental.delay
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
private const val TYPE_LOGIN_OAUTH = 3
private const val TYPE_LOGIN_OAUTH = 1
private const val TYPE_LOGIN_CAS = 2
private const val TYPE_LOGIN_SAML = 3
private const val TYPE_LOGIN_DEEP_LINK = 4
private const val SERVICE_NAME_FACEBOOK = "facebook"
private const val SERVICE_NAME_GITHUB = "github"
private const val SERVICE_NAME_GOOGLE = "google"
private const val SERVICE_NAME_LINKEDIN = "linkedin"
private const val SERVICE_NAME_GILAB = "gitlab"
private const val SERVICE_NAME_WORDPRESS = "wordpress"
class LoginOptionsPresenter @Inject constructor(
private val view: LoginOptionsView,
......@@ -49,161 +80,240 @@ class LoginOptionsPresenter @Inject constructor(
) {
// TODO - we should validate the current server when opening the app, and have a nonnull get()
private var currentServer = serverInteractor.get()!!
private lateinit var settings: PublicSettings
private lateinit var client: RocketChatClient
private lateinit var settings: PublicSettings
private lateinit var credentialToken: String
private lateinit var credentialSecret: String
private lateinit var deepLinkUserId: String
private lateinit var deepLinkToken: String
private lateinit var loginMethod: AuthenticationEvent
fun setupView(){
fun setupView() {
setupConnectionInfo(currentServer)
setupOauthServicesView()
setupAccountContainerView()
setupLoginWithEmailView()
setupCreateNewAccountView()
}
private fun setupConnectionInfo(serverUrl: String) {
currentServer = serverUrl
client = factory.create(serverUrl)
settings = settingsInteractor.get(serverUrl)
client = factory.create(currentServer)
settings = settingsInteractor.get(currentServer)
}
private fun setupOauthServicesView() {
private fun setupAccountContainerView() {
launchUI(strategy) {
try {
val services = retryIO("settingsOauth()") {
client.settingsOauth().services
}
if (services.isNotEmpty()) {
val state =
"{\"loginStyle\":\"popup\",\"credentialToken\":\"${generateRandomString(40)}\",\"isCordova\":true}".encodeToBase64()
val state = OauthHelper.getState()
var totalSocialAccountsEnabled = 0
// OAuth accounts.
if (settings.isFacebookAuthenticationEnabled()) {
val clientId = getOauthClientId(services, SERVICE_NAME_FACEBOOK)
if (clientId != null) {
view.setupFacebookButtonListener(
getServiceMap(services, SERVICE_NAME_FACEBOOK)?.let { serviceMap ->
getOauthClientId(serviceMap)?.let { clientId ->
view.setupFacebookButtonListener(
OauthHelper.getFacebookOauthUrl(
clientId,
currentServer,
state
clientId,
currentServer,
state
), state
)
view.enableLoginByFacebook()
totalSocialAccountsEnabled++
)
view.enableLoginByFacebook()
totalSocialAccountsEnabled++
}
}
}
if (settings.isGithubAuthenticationEnabled()) {
val clientId = getOauthClientId(services, SERVICE_NAME_GITHUB)
if (clientId != null) {
view.setupGithubButtonListener(
getServiceMap(services, SERVICE_NAME_GITHUB)?.let { serviceMap ->
getOauthClientId(serviceMap)?.let { clientId ->
view.setupGithubButtonListener(
OauthHelper.getGithubOauthUrl(
clientId,
state
clientId,
state
), state
)
view.enableLoginByGithub()
totalSocialAccountsEnabled++
)
view.enableLoginByGithub()
totalSocialAccountsEnabled++
}
}
}
if (settings.isGoogleAuthenticationEnabled()) {
val clientId = getOauthClientId(services, SERVICE_NAME_GOOGLE)
if (clientId != null) {
view.setupGoogleButtonListener(
getServiceMap(services, SERVICE_NAME_GOOGLE)?.let { serviceMap ->
getOauthClientId(serviceMap)?.let { clientId ->
view.setupGoogleButtonListener(
OauthHelper.getGoogleOauthUrl(
clientId,
currentServer,
state
clientId,
currentServer,
state
), state
)
view.enableLoginByGoogle()
totalSocialAccountsEnabled++
)
view.enableLoginByGoogle()
totalSocialAccountsEnabled++
}
}
}
if (settings.isLinkedinAuthenticationEnabled()) {
val clientId = getOauthClientId(services, SERVICE_NAME_LINKEDIN)
if (clientId != null) {
view.setupLinkedinButtonListener(
getServiceMap(services, SERVICE_NAME_LINKEDIN)?.let { serviceMap ->
getOauthClientId(serviceMap)?.let { clientId ->
view.setupLinkedinButtonListener(
OauthHelper.getLinkedinOauthUrl(
clientId,
currentServer,
state
clientId,
currentServer,
state
), state
)
view.enableLoginByLinkedin()
totalSocialAccountsEnabled++
)
view.enableLoginByLinkedin()
totalSocialAccountsEnabled++
}
}
}
if (settings.isGitlabAuthenticationEnabled()) {
val clientId = getOauthClientId(services, SERVICE_NAME_GILAB)
if (clientId != null) {
val gitlabOauthUrl = if (settings.gitlabUrl() != null) {
OauthHelper.getGitlabOauthUrl(
getServiceMap(services, SERVICE_NAME_GILAB)?.let { serviceMap ->
getOauthClientId(serviceMap)?.let { clientId ->
val gitlabOauthUrl = if (settings.gitlabUrl() != null) {
OauthHelper.getGitlabOauthUrl(
host = settings.gitlabUrl(),
clientId = clientId,
serverUrl = currentServer,
state = state
)
} else {
OauthHelper.getGitlabOauthUrl(
)
} else {
OauthHelper.getGitlabOauthUrl(
clientId = clientId,
serverUrl = currentServer,
state = state
)
}
view.setupGitlabButtonListener(gitlabOauthUrl, state)
view.enableLoginByGitlab()
totalSocialAccountsEnabled++
}
}
}
if (settings.isWordpressAuthenticationEnabled()) {
getServiceMap(services, SERVICE_NAME_WORDPRESS)?.let { serviceMap ->
getOauthClientId(serviceMap)?.let { clientId ->
val wordpressOauthUrl =
if (settings.wordpressUrl().isNullOrEmpty()) {
OauthHelper.getWordpressComOauthUrl(
clientId,
currentServer,
state
)
} else {
OauthHelper.getWordpressCustomOauthUrl(
getCustomOauthHost(serviceMap)
?: "https://public-api.wordpress.com",
getCustomOauthAuthorizePath(serviceMap)
?: "/oauth/authorize",
clientId,
currentServer,
SERVICE_NAME_WORDPRESS,
state,
getCustomOauthScope(serviceMap) ?: "openid"
)
}
view.setupWordpressButtonListener(wordpressOauthUrl, state)
view.enableLoginByWordpress()
totalSocialAccountsEnabled++
}
}
}
// CAS account.
if (settings.isCasAuthenticationEnabled()) {
val casToken = generateRandomString(17)
view.setupCasButtonListener(
settings.casLoginUrl().casUrl(currentServer, casToken), casToken
)
view.enableLoginByCas()
totalSocialAccountsEnabled++
}
// Custom OAuth account.
getCustomOauthServices(services).let {
for (serviceMap in it) {
val serviceName = getCustomOauthServiceName(serviceMap)
val host = getCustomOauthHost(serviceMap)
val authorizePath = getCustomOauthAuthorizePath(serviceMap)
val clientId = getOauthClientId(serviceMap)
val scope = getCustomOauthScope(serviceMap)
val textColor = getServiceNameColorForCustomOauthOrSaml(serviceMap)
val buttonColor = getServiceButtonColor(serviceMap)
if (serviceName != null &&
host != null &&
authorizePath != null &&
clientId != null &&
scope != null &&
textColor != null &&
buttonColor != null
) {
val customOauthUrl = OauthHelper.getCustomOauthUrl(
host,
authorizePath,
clientId,
currentServer,
serviceName,
state,
scope
)
view.addCustomOauthButton(
customOauthUrl,
state,
serviceName,
textColor,
buttonColor
)
totalSocialAccountsEnabled++
}
}
}
// SAML account.
getSamlServices(services).let {
val samlToken = generateRandomString(17)
for (serviceMap in it) {
val provider = getSamlProvider(serviceMap)
val serviceName = getSamlServiceName(serviceMap)
val textColor = getServiceNameColorForCustomOauthOrSaml(serviceMap)
val buttonColor = getServiceButtonColor(serviceMap)
if (provider != null &&
serviceName != null &&
textColor != null &&
buttonColor != null
) {
view.addSamlButton(
currentServer.samlUrl(provider, samlToken),
samlToken,
serviceName,
textColor,
buttonColor
)
totalSocialAccountsEnabled++
}
view.setupGitlabButtonListener(gitlabOauthUrl, state)
view.enableLoginByGitlab()
totalSocialAccountsEnabled++
}
}
// getCustomOauthServices(services).let {
// for (service in it) {
// val serviceName = getCustomOauthServiceName(service)
//
// val customOauthUrl = OauthHelper.getCustomOauthUrl(
// getCustomOauthHost(service),
// getCustomOauthAuthorizePath(service),
// getCustomOauthClientId(service),
// currentServer,
// serviceName,
// state,
// getCustomOauthScope(service)
// )
//
// view.addCustomOauthServiceButton(
// customOauthUrl,
// state,
// serviceName,
// getServiceNameColor(service),
// getServiceButtonColor(service)
// )
// totalSocialAccountsEnabled++
// }
// }
// getSamlServices(services).let {
// val samlToken = generateRandomString(17)
//
// for (service in it) {
// view.addSamlServiceButton(
// currentServer.samlUrl(getSamlProvider(service), samlToken),
// samlToken,
// getSamlServiceName(service),
// getServiceNameColor(service),
// getServiceButtonColor(service)
// )
// totalSocialAccountsEnabled++
// }
// }
// if (totalSocialAccountsEnabled > 0) {
// view.enableOauthView()
// if (totalSocialAccountsEnabled > 3) {
// view.setupFabListener()
// }
// } else {
// view.disableOauthView()
// }
} else {
// view.disableOauthView()
if (totalSocialAccountsEnabled > 0) {
view.showAccountsView()
if (totalSocialAccountsEnabled > 3) {
view.setupExpandAccountsView()
}
}
}
} catch (exception: RocketChatException) {
Timber.e(exception)
......@@ -211,52 +321,112 @@ class LoginOptionsPresenter @Inject constructor(
}
}
private fun setupLoginWithEmailView() {
if (settings.isLoginFormEnabled()) {
view.showLoginWithEmailButton()
}
}
private fun setupCreateNewAccountView() {
if (settings.isRegistrationEnabledForNewUsers() && settings.isLoginFormEnabled()) {
view.showCreateNewAccountButton()
}
}
fun toCreateAccount() = navigator.toCreateAccount()
fun toLoginWithEmail() = navigator.toLogin()
fun authenticateWithOauth(oauthToken: String, oauthSecret: String) {
credentialToken = oauthToken
credentialSecret = oauthSecret
loginMethod = AuthenticationEvent.AuthenticationWithOauth
doAuthentication(TYPE_LOGIN_OAUTH)
}
fun authenticateWithCas(casToken: String) {
credentialToken = casToken
loginMethod = AuthenticationEvent.AuthenticationWithCas
doAuthentication(TYPE_LOGIN_CAS)
}
fun authenticateWithSaml(samlToken: String) {
credentialToken = samlToken
loginMethod = AuthenticationEvent.AuthenticationWithSaml
doAuthentication(TYPE_LOGIN_SAML)
}
fun authenticateWithDeepLink(deepLinkInfo: LoginDeepLinkInfo) {
val serverUrl = deepLinkInfo.url
setupConnectionInfo(serverUrl)
if (deepLinkInfo.userId != null && deepLinkInfo.token != null) {
deepLinkUserId = deepLinkInfo.userId
deepLinkToken = deepLinkInfo.token
tokenRepository.save(serverUrl, Token(deepLinkUserId, deepLinkToken))
loginMethod = AuthenticationEvent.AuthenticationWithDeeplink
doAuthentication(TYPE_LOGIN_DEEP_LINK)
} else {
// If we don't have the login credentials, just go through normal setup and user input.
setupView()
}
}
private fun doAuthentication(loginType: Int) {
launchUI(strategy) {
view.showLoading()
try {
val token = retryIO("login") {
when (loginType) {
TYPE_LOGIN_OAUTH -> {
client.loginWithOauth(credentialToken, credentialSecret)
TYPE_LOGIN_OAUTH -> client.loginWithOauth(credentialToken, credentialSecret)
TYPE_LOGIN_CAS -> {
delay(3, TimeUnit.SECONDS)
client.loginWithCas(credentialToken)
}
TYPE_LOGIN_SAML -> {
delay(3, TimeUnit.SECONDS)
client.loginWithSaml(credentialToken)
}
TYPE_LOGIN_DEEP_LINK -> {
val myself = client.me() // Just checking if the credentials worked.
if (myself.id == deepLinkUserId) {
Token(deepLinkUserId, deepLinkToken)
} else {
throw RocketChatAuthException("Invalid AuthenticationEvent Deep Link Credentials...")
}
}
else -> {
throw IllegalStateException("Expected TYPE_LOGIN_USER_EMAIL, TYPE_LOGIN_CAS,TYPE_LOGIN_SAML, TYPE_LOGIN_OAUTH or TYPE_LOGIN_DEEP_LINK")
throw IllegalStateException(
"Expected TYPE_LOGIN_USER_EMAIL, " +
"TYPE_LOGIN_CAS,TYPE_LOGIN_SAML, TYPE_LOGIN_OAUTH or " +
"TYPE_LOGIN_DEEP_LINK"
)
}
}
}
val myself = retryIO("me()") { client.me() }
if (myself.username != null) {
myself.username?.let { username ->
val user = User(
id = myself.id,
roles = myself.roles,
status = myself.status,
name = myself.name,
emails = myself.emails?.map { Email(it.address ?: "", it.verified) },
username = myself.username,
utcOffset = myself.utcOffset
id = myself.id,
roles = myself.roles,
status = myself.status,
name = myself.name,
emails = myself.emails?.map { Email(it.address ?: "", it.verified) },
username = myself.username,
utcOffset = myself.utcOffset
)
localRepository.saveCurrentUser(url = currentServer, user = user)
saveCurrentServer.save(currentServer)
saveAccount(myself.username!!)
saveAccount(username)
saveToken(token)
// TODO
// analyticsManager.logSignUp(
// AuthenticationEvent.AuthenticationWithOauth,
// true
// )
analyticsManager.logLogin(loginMethod, true)
navigator.toChatList()
} else if (loginType == TYPE_LOGIN_OAUTH) {
navigator.toRegisterUsername(token.userId, token.authToken)
}.ifNull {
if (loginType == TYPE_LOGIN_OAUTH) {
navigator.toRegisterUsername(token.userId, token.authToken)
}
}
} catch (exception: RocketChatException) {
analyticsManager.logLogin(loginMethod, false)
exception.message?.let {
view.showMessage(it)
}.ifNull {
......@@ -268,12 +438,6 @@ class LoginOptionsPresenter @Inject constructor(
}
}
private fun getOauthClientId(listMap: List<Map<String, Any>>, serviceName: String): String? {
return listMap.find { map -> map.containsValue(serviceName) }?.let {
it["clientId"] ?: it["appId"]
}.toString()
}
private suspend fun saveAccount(username: String) {
val icon = settings.favicon()?.let {
currentServer.serverLogoUrl(it)
......@@ -286,15 +450,115 @@ class LoginOptionsPresenter @Inject constructor(
saveAccountInteractor.save(account)
}
private fun saveToken(token: Token) {
tokenRepository.save(currentServer, token)
}
private fun saveToken(token: Token) = tokenRepository.save(currentServer, token)
fun toCreateAccount() {
navigator.toCreateAccount()
}
fun toLogin() {
navigator.toLogin()
}
/** Returns an OAuth service map given a [serviceName].
*
* @param listMap The list of [Map] to get the service from.
* @param serviceName The service name to get in the [listMap]
* @return The OAuth service map or null otherwise.
*/
private fun getServiceMap(
listMap: List<Map<String, Any>>,
serviceName: String
): Map<String, Any>? = listMap.find { map -> map.containsValue(serviceName) }
/**
* Returns the OAuth client ID of a [serviceMap].
* REMARK: This function works for common OAuth providers (Google, Facebook, Github and so on)
* as well as custom OAuth.
*
* @param serviceMap The service map to get the OAuth client ID.
* @return The OAuth client ID or null otherwise.
*/
private fun getOauthClientId(serviceMap: Map<String, Any>): String? =
serviceMap["clientId"] as? String ?: serviceMap["appId"] as? String
/**
* Returns a custom OAuth service list.
*
* @return A custom OAuth service list, otherwise an empty list if there is no custom OAuth service.
*/
private fun getCustomOauthServices(listMap: List<Map<String, Any>>): List<Map<String, Any>> =
listMap.filter { map -> map["custom"] == true }
/** Returns the custom OAuth service host.
*
* @param serviceMap The service map to get the custom OAuth service host.
* @return The custom OAuth service host, otherwise null.
*/
private fun getCustomOauthHost(serviceMap: Map<String, Any>): String? =
serviceMap["serverURL"] as? String
/** Returns the custom OAuth service authorize path.
*
* @param serviceMap The service map to get the custom OAuth service authorize path.
* @return The custom OAuth service authorize path, otherwise null.
*/
private fun getCustomOauthAuthorizePath(serviceMap: Map<String, Any>): String? =
serviceMap["authorizePath"] as? String
/** Returns the custom OAuth service scope.
*
* @param serviceMap The service map to get the custom OAuth service scope.
* @return The custom OAuth service scope, otherwise null.
*/
private fun getCustomOauthScope(serviceMap: Map<String, Any>): String? =
serviceMap["scope"] as? String
/** Returns the text of the custom OAuth service.
*
* @param serviceMap The service map to get the text of the custom OAuth service.
* @return The text of the custom OAuth service, otherwise null.
*/
private fun getCustomOauthServiceName(serviceMap: Map<String, Any>): String? =
serviceMap["service"] as? String
/**
* Returns a SAML OAuth service list.
*
* @return A SAML service list, otherwise an empty list if there is no SAML OAuth service.
*/
private fun getSamlServices(listMap: List<Map<String, Any>>): List<Map<String, Any>> =
listMap.filter { map -> map["service"] == "saml" }
/**
* Returns the SAML provider.
*
* @param serviceMap The service map to provider from.
* @return The SAML provider, otherwise null.
*/
private fun getSamlProvider(serviceMap: Map<String, Any>): String? =
(serviceMap["clientConfig"] as Map<*, *>)["provider"] as? String
/**
* Returns the text of the SAML service.
*
* @param serviceMap The service map to get the text of the SAML service.
* @return The text of the SAML service, otherwise null.
*/
private fun getSamlServiceName(serviceMap: Map<String, Any>): String? =
serviceMap["buttonLabelText"] as? String
/**
* Returns the text color of the service name.
* REMARK: This can be used for custom OAuth or SAML.
*
* @param serviceMap The service map to get the text color from.
* @return The text color of the service (custom OAuth or SAML), otherwise null.
*/
private fun getServiceNameColorForCustomOauthOrSaml(serviceMap: Map<String, Any>): Int? =
(serviceMap["buttonLabelColor"] as? String)?.parseColor()
/**
* Returns the button color of the service name.
* REMARK: This can be used for custom OAuth or SAML.
*
* @param serviceMap The service map to get the button color from.
* @return The button color of the service (custom OAuth or SAML), otherwise null.
*/
private fun getServiceButtonColor(serviceMap: Map<String, Any>): Int? =
(serviceMap["buttonColor"] as? String)?.parseColor()
}
\ 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()
}
......@@ -2,55 +2,73 @@ package chat.rocket.android.authentication.loginoptions.ui
import android.app.Activity
import android.content.Intent
import android.graphics.PorterDuff
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.LinearLayout
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import chat.rocket.android.R
import chat.rocket.android.authentication.login.ui.REQUEST_CODE_FOR_OAUTH
import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo
import chat.rocket.android.authentication.loginoptions.presentation.LoginOptionsPresenter
import chat.rocket.android.authentication.loginoptions.presentation.LoginOptionsView
import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.util.extensions.clearLightStatusBar
import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.rotateBy
import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.ui
import chat.rocket.android.webview.oauth.ui.INTENT_OAUTH_CREDENTIAL_SECRET
import chat.rocket.android.webview.oauth.ui.INTENT_OAUTH_CREDENTIAL_TOKEN
import chat.rocket.android.webview.oauth.ui.oauthWebViewIntent
import chat.rocket.android.webview.sso.ui.INTENT_SSO_TOKEN
import chat.rocket.android.webview.sso.ui.ssoWebViewIntent
import chat.rocket.common.util.ifNull
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_login_options.*
import javax.inject.Inject
internal const val TAG_LOGIN_OPTIONS = "LoginOptionsFragment"
private const val BUNDLE_SERVER_NAME = "BUNDLE_SERVER_NAME"
private const val DEEP_LINK_INFO = "DeepLinkInfo"
class LoginOptionsFragment : Fragment(), LoginOptionsView {
@Inject
lateinit var presenter: LoginOptionsPresenter
private var server: String? = null
internal const val REQUEST_CODE_FOR_OAUTH = 1
internal const val REQUEST_CODE_FOR_CAS = 2
internal const val REQUEST_CODE_FOR_SAML = 3
companion object {
fun newInstance(server: String): LoginOptionsFragment {
return LoginOptionsFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_SERVER_NAME, server)
}
}
fun newInstance(
serverName: String,
deepLinkInfo: LoginDeepLinkInfo? = null
): Fragment {
return LoginOptionsFragment().apply {
arguments = Bundle(2).apply {
putString(BUNDLE_SERVER_NAME, serverName)
putParcelable(DEEP_LINK_INFO, deepLinkInfo)
}
}
}
class LoginOptionsFragment : Fragment(), LoginOptionsView {
@Inject
lateinit var presenter: LoginOptionsPresenter
@Inject
lateinit var analyticsManager: AnalyticsManager
private var deepLinkInfo: LoginDeepLinkInfo? = null
private var serverName: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
setHasOptionsMenu(true)
val bundle = arguments
if (bundle != null) {
server = bundle.getString(BUNDLE_SERVER_NAME)
serverName = bundle.getString(BUNDLE_SERVER_NAME)
deepLinkInfo = bundle.getParcelable(DEEP_LINK_INFO)
}
}
......@@ -58,130 +76,125 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_authentication_login_options, container, false)
): View? = container?.inflate(R.layout.fragment_authentication_login_options)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter.setupView()
setupToolbar()
setupClickListener()
}
presenter.setupView()
analyticsManager.logScreenView(ScreenViewEvent.LoginOptions)
private fun setupClickListener() {
button_create_account.setOnClickListener {
presenter.toCreateAccount()
}
button_login.setOnClickListener {
presenter.toLogin()
}
image_more_login_option.setOnClickListener {
if (it.rotation == 0f) {
image_more_login_option.rotateBy(180f)
button_linkedin.isVisible = true
button_gitlab.isVisible = true
} else {
image_more_login_option.rotateBy(-180f)
button_linkedin.isVisible = false
button_gitlab.isVisible = false
}
deepLinkInfo?.let {
presenter.authenticateWithDeepLink(it)
}.ifNull {
presenter.setupView()
}
}
private fun setupToolbar() {
with((activity as AuthenticationActivity)) {
view?.let { clearLightStatusBar(it) }
with(activity as AuthenticationActivity) {
this.clearLightStatusBar()
toolbar.isVisible = true
text_room_name.text = server?.replace(getString(R.string.default_protocol), "")
toolbar.title = serverName?.replace(getString(R.string.default_protocol), "")
}
}
override fun enableLoginByFacebook() {
ui {
button_facebook.isClickable = true
}
}
// OAuth Accounts.
override fun enableLoginByFacebook() = enableAccountButton(button_facebook)
override fun setupFacebookButtonListener(facebookOauthUrl: String, state: String) {
ui { activity ->
button_facebook.setOnClickListener {
startActivityForResult(
activity.oauthWebViewIntent(facebookOauthUrl, state),
REQUEST_CODE_FOR_OAUTH
)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
}
}
override fun setupFacebookButtonListener(facebookOauthUrl: String, state: String) =
setupButtonListener(button_facebook, facebookOauthUrl, state, REQUEST_CODE_FOR_OAUTH)
override fun enableLoginByGithub() {
ui {
button_github.isClickable = true
}
}
override fun enableLoginByGithub() = enableAccountButton(button_github)
override fun setupGithubButtonListener(githubUrl: String, state: String) {
ui { activity ->
button_github.setOnClickListener {
startActivityForResult(
activity.oauthWebViewIntent(githubUrl, state),
REQUEST_CODE_FOR_OAUTH
)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
}
}
override fun setupGithubButtonListener(githubUrl: String, state: String) =
setupButtonListener(button_github, githubUrl, state, REQUEST_CODE_FOR_OAUTH)
override fun enableLoginByGoogle() {
ui {
button_google.isClickable = true
}
override fun enableLoginByGoogle() = enableAccountButton(button_google)
override fun setupGoogleButtonListener(googleUrl: String, state: String) =
setupButtonListener(button_google, googleUrl, state, REQUEST_CODE_FOR_OAUTH)
override fun enableLoginByLinkedin() = enableAccountButton(button_linkedin)
override fun setupLinkedinButtonListener(linkedinUrl: String, state: String) =
setupButtonListener(button_linkedin, linkedinUrl, state, REQUEST_CODE_FOR_OAUTH)
override fun enableLoginByGitlab() = enableAccountButton(button_gitlab)
override fun setupGitlabButtonListener(gitlabUrl: String, state: String) =
setupButtonListener(button_gitlab, gitlabUrl, state, REQUEST_CODE_FOR_OAUTH)
override fun enableLoginByWordpress() = enableAccountButton(button_wordpress)
override fun setupWordpressButtonListener(wordpressUrl: String, state: String) =
setupButtonListener(button_wordpress, wordpressUrl, state, REQUEST_CODE_FOR_OAUTH)
// CAS service account.
override fun enableLoginByCas() = enableAccountButton(button_cas)
override fun setupCasButtonListener(casUrl: String, casToken: String) =
setupButtonListener(button_cas, casUrl, casToken, REQUEST_CODE_FOR_CAS)
// Custom OAuth account.
override fun addCustomOauthButton(
customOauthUrl: String,
state: String,
serviceName: String,
serviceNameColor: Int,
buttonColor: Int
) {
val button = getCustomServiceButton(serviceName, serviceNameColor, buttonColor)
accounts_container.addView(button)
setupButtonListener(button, customOauthUrl, state, REQUEST_CODE_FOR_OAUTH)
}
override fun setupGoogleButtonListener(googleUrl: String, state: String) {
ui { activity ->
button_google.setOnClickListener {
startActivityForResult(
activity.oauthWebViewIntent(googleUrl, state),
REQUEST_CODE_FOR_OAUTH
)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
}
// SAML account.
override fun addSamlButton(
samlUrl: String,
samlToken: String,
serviceName: String,
serviceNameColor: Int,
buttonColor: Int
) {
val button = getCustomServiceButton(serviceName, serviceNameColor, buttonColor)
accounts_container.addView(button)
setupButtonListener(button, samlUrl, samlToken, REQUEST_CODE_FOR_SAML)
}
override fun enableLoginByLinkedin() {
override fun showAccountsView() {
ui {
button_linkedin.isClickable = true
showThreeAccountsMethods()
accounts_container.isVisible = true
}
}
override fun setupLinkedinButtonListener(linkedinUrl: String, state: String) {
ui { activity ->
button_linkedin.setOnClickListener {
startActivityForResult(
activity.oauthWebViewIntent(linkedinUrl, state),
REQUEST_CODE_FOR_OAUTH
)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
override fun setupExpandAccountsView() {
ui {
expand_more_accounts_container.isVisible = true
button_expand_collapse_accounts.setOnClickListener { view ->
if (view.rotation == 0F) {
button_expand_collapse_accounts.rotateBy(180F, 400)
expandAccountsView()
} else {
button_expand_collapse_accounts.rotateBy(180F, 400)
collapseAccountsView()
}
}
}
}
override fun enableLoginByGitlab() {
ui {
button_gitlab.isClickable = true
override fun showLoginWithEmailButton() {
ui { _ ->
button_login_with_email.setOnClickListener { presenter.toLoginWithEmail() }
button_login_with_email.isVisible = true
}
}
override fun setupGitlabButtonListener(gitlabUrl: String, state: String) {
ui { activity ->
button_gitlab.setOnClickListener {
startActivityForResult(
activity.oauthWebViewIntent(gitlabUrl, state),
REQUEST_CODE_FOR_OAUTH
)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
override fun showCreateNewAccountButton() {
ui { _ ->
button_create_an_account.setOnClickListener { presenter.toCreateAccount() }
button_create_an_account.isVisible = true
}
}
......@@ -194,6 +207,12 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView {
data.getStringExtra(INTENT_OAUTH_CREDENTIAL_SECRET)
)
}
REQUEST_CODE_FOR_CAS -> presenter.authenticateWithCas(
data.getStringExtra(INTENT_SSO_TOKEN)
)
REQUEST_CODE_FOR_SAML -> data.apply {
presenter.authenticateWithSaml(getStringExtra(INTENT_SSO_TOKEN))
}
}
}
}
......@@ -225,4 +244,84 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView {
override fun showGenericErrorMessage() {
showMessage(R.string.msg_generic_error)
}
private fun enableAccountButton(button: Button) {
ui {
button.isClickable = true
}
}
private fun setupButtonListener(
button: Button,
accountUrl: String,
argument: String,
requestCode: Int
) {
ui { activity ->
button.setOnClickListener {
when (requestCode) {
REQUEST_CODE_FOR_OAUTH -> startActivityForResult(
activity.oauthWebViewIntent(accountUrl, argument), REQUEST_CODE_FOR_OAUTH
)
REQUEST_CODE_FOR_CAS -> startActivityForResult(
activity.ssoWebViewIntent(accountUrl, argument), REQUEST_CODE_FOR_CAS
)
REQUEST_CODE_FOR_SAML -> startActivityForResult(
activity.ssoWebViewIntent(accountUrl, argument), REQUEST_CODE_FOR_SAML
)
}
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
}
}
/**
* Gets a stylized custom service button.
*/
private fun getCustomServiceButton(
buttonText: String,
buttonTextColor: Int,
buttonBgColor: Int
): Button {
val params: LinearLayout.LayoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
val marginTop = resources.getDimensionPixelSize(R.dimen.button_account_margin_top)
params.setMargins(0, marginTop, 0, 0)
val button = Button(context)
button.layoutParams = params
button.text = buttonText
button.setTextColor(buttonTextColor)
button.background.setColorFilter(buttonBgColor, PorterDuff.Mode.MULTIPLY)
return button
}
private fun showThreeAccountsMethods() {
(0..accounts_container.childCount)
.mapNotNull { accounts_container.getChildAt(it) as? Button }
.filter { it.isClickable }
.take(3)
.forEach { it.isVisible = true }
}
private fun expandAccountsView() {
(0..accounts_container.childCount)
.mapNotNull { accounts_container.getChildAt(it) as? Button }
.filter { it.isClickable }
.forEach { it.isVisible = true }
}
private fun collapseAccountsView() {
(0..accounts_container.childCount)
.mapNotNull { accounts_container.getChildAt(it) as? Button }
.filter { it.isVisible }
.drop(3)
.forEach { it.isVisible = false }
}
}
......@@ -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.
......
package chat.rocket.android.authentication.signup.ui
import DrawableHelper
import android.app.Activity
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.text.style.ClickableSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
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.R.string.message_credentials_saved_successfully
......@@ -17,20 +16,21 @@ import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.authentication.signup.presentation.SignupPresenter
import chat.rocket.android.authentication.signup.presentation.SignupView
import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.TextHelper
import chat.rocket.android.helper.saveCredentials
import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.util.extensions.shake
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.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.disposables.CompositeDisposable
import io.reactivex.rxkotlin.Observables
import kotlinx.android.synthetic.main.fragment_authentication_sign_up.*
import javax.inject.Inject
internal const val TAG_SIGNUP_FRAGMENT = "SignupFragment"
fun newInstance() = SignupFragment()
internal const val SAVE_CREDENTIALS = 1
class SignupFragment : Fragment(), SignupView {
......@@ -38,17 +38,7 @@ class SignupFragment : Fragment(), SignupView {
lateinit var presenter: SignupPresenter
@Inject
lateinit var analyticsManager: AnalyticsManager
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
if (KeyboardHelper.isSoftKeyboardShown(constraint_layout.rootView)) {
text_new_user_agreement.setVisible(false)
} else {
text_new_user_agreement.apply {
postDelayed({
ui { setVisible(false) }
}, 3)
}
}
}
private val editTextsDisposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
......@@ -59,89 +49,76 @@ class SignupFragment : Fragment(), SignupView {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_authentication_sign_up, container, false)
): View? = container?.inflate(R.layout.fragment_authentication_sign_up)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
tintEditTextDrawableStart()
}
constraint_layout.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
subscribeEditTexts()
setupOnClickListener()
setUpNewUserAgreementListener()
analyticsManager.logScreenView(ScreenViewEvent.SignUp)
}
button_sign_up.setOnClickListener {
presenter.signup(
text_username.textContent,
text_username.textContent,
text_password.textContent,
text_email.textContent
)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
if (data != null) {
if (requestCode == SAVE_CREDENTIALS) {
showMessage(getString(message_credentials_saved_successfully))
}
}
}
analyticsManager.logScreenView(ScreenViewEvent.SignUp)
}
override fun onDestroyView() {
constraint_layout.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
super.onDestroyView()
}
override fun alertBlankName() {
ui {
vibrateSmartPhone()
text_name.shake()
text_name.requestFocus()
unsubscribeEditTexts()
}
private fun setupOnClickListener() =
ui { _ ->
button_register.setOnClickListener {
presenter.signup(
text_username.textContent,
text_username.textContent,
text_password.textContent,
text_email.textContent
)
}
}
}
override fun alertBlankUsername() {
ui {
vibrateSmartPhone()
text_username.shake()
text_username.requestFocus()
override fun enableButtonRegister() {
context?.let {
ViewCompat.setBackgroundTintList(
button_register, ContextCompat.getColorStateList(it, R.color.colorAccent)
)
button_register.isEnabled = true
}
}
override fun alertEmptyPassword() {
ui {
vibrateSmartPhone()
text_password.shake()
text_password.requestFocus()
}
}
override fun alertBlankEmail() {
ui {
vibrateSmartPhone()
text_email.shake()
text_email.requestFocus()
override fun disableButtonRegister() {
context?.let {
ViewCompat.setBackgroundTintList(
button_register,
ContextCompat.getColorStateList(it, R.color.colorAuthenticationButtonDisabled)
)
button_register.isEnabled = false
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
if (data != null) {
if (requestCode == SAVE_CREDENTIALS) {
showMessage(getString(message_credentials_saved_successfully))
}
}
}
}
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()
}
}
......@@ -157,72 +134,51 @@ class SignupFragment : Fragment(), SignupView {
}
}
override fun showGenericErrorMessage() {
showMessage(getString(R.string.msg_generic_error))
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
override fun saveSmartLockCredentials(id: String, password: String) {
activity?.saveCredentials(id, password)
}
private fun tintEditTextDrawableStart() {
ui {
val personDrawable =
DrawableHelper.getDrawableFromId(R.drawable.ic_person_black_20dp, it)
val atDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_at_black_20dp, it)
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_lock_black_24dp, it)
val emailDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_email_black_20dp, it)
val drawables = arrayOf(personDrawable, atDrawable, lockDrawable, emailDrawable)
DrawableHelper.wrapDrawables(drawables)
DrawableHelper.tintDrawables(drawables, it, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawables(
arrayOf(
text_name,
text_username,
text_password,
text_email
), drawables
)
}
private fun subscribeEditTexts() {
editTextsDisposable.add(
Observables.combineLatest(
text_name.asObservable(),
text_username.asObservable(),
text_password.asObservable(),
text_email.asObservable()
) { text_name, text_username, text_password, text_email ->
return@combineLatest (
(text_name.isNotBlank() &&
text_username.isNotBlank() &&
text_password.isNotBlank() &&
text_email.isNotBlank()) ||
!text_email.toString().isEmail()
)
}.subscribe { isValid ->
if (isValid) {
enableButtonRegister()
} else {
disableButtonRegister()
}
})
}
private fun setUpNewUserAgreementListener() {
val termsOfService = getString(R.string.action_terms_of_service)
val privacyPolicy = getString(R.string.action_privacy_policy)
val newUserAgreement =
String.format(getString(R.string.msg_new_user_agreement), termsOfService, privacyPolicy)
text_new_user_agreement.text = newUserAgreement
val termsOfServiceListener = object : ClickableSpan() {
override fun onClick(view: View) {
presenter.termsOfService()
}
}
val privacyPolicyListener = object : ClickableSpan() {
override fun onClick(view: View) {
presenter.privacyPolicy()
}
}
TextHelper.addLink(
text_new_user_agreement,
arrayOf(termsOfService, privacyPolicy),
arrayOf(termsOfServiceListener, privacyPolicyListener)
)
}
private fun unsubscribeEditTexts() = editTextsDisposable.clear()
private fun enableUserInput(value: Boolean) {
button_sign_up.isEnabled = value
text_username.isEnabled = value
text_username.isEnabled = value
text_password.isEnabled = value
text_email.isEnabled = value
private fun enableUserInput() {
text_name.isEnabled = true
text_username.isEnabled = true
text_password.isEnabled = true
text_email.isEnabled = true
enableButtonRegister()
}
companion object {
fun newInstance() = SignupFragment()
private fun disableUserInput() {
disableButtonRegister()
text_name.isEnabled = false
text_username.isEnabled = false
text_password.isEnabled = false
text_email.isEnabled = false
}
}
......@@ -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
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView 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:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_white"
tools:context="chat.rocket.android.authentication.loginoptions.ui.LoginOptionsFragment">
<Button
android:id="@+id/button_facebook"
style="?borderlessButtonStyle"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="16dp"
android:background="@drawable/rounded_border"
android:drawableStart="@drawable/ic_facebook_24dp"
android:foreground="?selectableItemBackground"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/msg_continue_with_facebook"
android:textAllCaps="false"
android:textColor="#2f343d"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button_github"
style="?borderlessButtonStyle"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="16dp"
android:background="@drawable/rounded_border"
android:drawableStart="@drawable/ic_github_24dp"
android:foreground="?selectableItemBackground"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/msg_continue_with_github"
android:textAllCaps="false"
android:textColor="#2f343d"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_facebook" />
<Button
android:id="@+id/button_google"
style="?borderlessButtonStyle"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="16dp"
android:background="@drawable/rounded_border"
android:drawableStart="@drawable/ic_google_24dp"
android:foreground="?selectableItemBackground"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/msg_continue_with_google"
android:textAllCaps="false"
android:textColor="#2f343d"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_github" />
<Button
android:id="@+id/button_linkedin"
style="?borderlessButtonStyle"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="16dp"
android:background="@drawable/rounded_border"
android:drawableStart="@drawable/ic_linkedin_24dp"
android:foreground="?selectableItemBackground"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/msg_continue_with_linkedin"
android:textAllCaps="false"
android:textColor="#2f343d"
android:textSize="16sp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_google"
tools:visibility="visible" />
<Button
android:id="@+id/button_gitlab"
style="?borderlessButtonStyle"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="16dp"
android:background="@drawable/rounded_border"
android:drawableStart="@drawable/ic_gitlab_24dp"
android:foreground="?selectableItemBackground"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/msg_continue_with_gitlab"
android:textAllCaps="false"
android:textColor="#2f343d"
android:textSize="16sp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_linkedin"
tools:visibility="visible" />
<ImageView
android:id="@+id/image_more_login_option"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginTop="16dp"
android:background="@drawable/circle_background_grey"
android:src="@drawable/ic_expand_more_black_24dp"
android:tint="#fff"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_gitlab" />
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
android:background="#e1e5e8"
app:layout_constraintBottom_toBottomOf="@id/image_more_login_option"
app:layout_constraintEnd_toStartOf="@id/image_more_login_option"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/image_more_login_option" />
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="16dp"
android:background="#e1e5e8"
app:layout_constraintBottom_toBottomOf="@id/image_more_login_option"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/image_more_login_option"
app:layout_constraintTop_toTopOf="@id/image_more_login_option" />
<Button
android:id="@+id/button_login"
style="@style/Authentication.Button"
android:layout_marginTop="32dp"
android:text="@string/msg_login_with_email"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/image_more_login_option" />
<Button
android:id="@+id/button_create_account"
style="?borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginTop="10dp"
android:text="@string/msg_create_account"
android:textAllCaps="false"
android:textColor="@color/colorAccent"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_login" />
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
style="@style/Authentication.AVLoadingIndicatorView"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
android:background="@color/colorWhite"
android:padding="@dimen/screen_edge_left_and_right_margins"
tools:context="authentication.loginoptions.ui.LoginOptionsFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/accounts_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible">
<Button
android:id="@+id/button_facebook"
style="?borderlessButtonStyle"
android:layout_width="0dp"
android:layout_height="48dp"
android:background="@drawable/rounded_border"
android:clickable="false"
android:drawableStart="@drawable/ic_facebook_24dp"
android:foreground="?selectableItemBackground"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/msg_continue_with_facebook"
android:textAllCaps="false"
android:textColor="@color/colorPrimary"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button_github"
style="?borderlessButtonStyle"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginTop="10dp"
android:background="@drawable/rounded_border"
android:clickable="false"
android:drawableStart="@drawable/ic_github_24dp"
android:foreground="?selectableItemBackground"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/msg_continue_with_github"
android:textAllCaps="false"
android:textColor="@color/colorPrimary"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button_facebook" />
<Button
android:id="@+id/button_google"
style="?borderlessButtonStyle"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginTop="10dp"
android:background="@drawable/rounded_border"
android:clickable="false"
android:drawableStart="@drawable/ic_google_24dp"
android:foreground="?selectableItemBackground"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/msg_continue_with_google"
android:textAllCaps="false"
android:textColor="@color/colorPrimary"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button_github" />
<Button
android:id="@+id/button_linkedin"
style="?borderlessButtonStyle"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginTop="10dp"
android:background="@drawable/rounded_border"
android:clickable="false"
android:drawableStart="@drawable/ic_linkedin_24dp"
android:foreground="?selectableItemBackground"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/msg_continue_with_linkedin"
android:textAllCaps="false"
android:textColor="@color/colorPrimary"
android:textSize="16sp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button_google"
tools:visibility="visible" />
<Button
android:id="@+id/button_gitlab"
style="?borderlessButtonStyle"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginTop="10dp"
android:background="@drawable/rounded_border"
android:clickable="false"
android:drawableStart="@drawable/ic_gitlab_24dp"
android:foreground="?selectableItemBackground"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/msg_continue_with_gitlab"
android:textAllCaps="false"
android:textColor="@color/colorPrimary"
android:textSize="16sp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button_linkedin"
tools:visibility="visible" />
<Button
android:id="@+id/button_wordpress"
style="?borderlessButtonStyle"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginTop="10dp"
android:background="@drawable/rounded_border"
android:clickable="false"
android:drawableStart="@drawable/ic_gitlab_24dp"
android:foreground="?selectableItemBackground"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/msg_continue_with_wordpress"
android:textAllCaps="false"
android:textColor="@color/colorPrimary"
android:textSize="16sp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button_gitlab"
tools:visibility="visible" />
<Button
android:id="@+id/button_cas"
style="@style/Authentication.Button.Flat"
android:layout_marginTop="10dp"
android:clickable="false"
android:text="@string/action_login_or_sign_up"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button_wordpress"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/expand_more_accounts_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/accounts_container"
tools:visibility="visible">
<ImageButton
android:id="@+id/button_expand_collapse_accounts"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@drawable/circle_background_grey"
android:contentDescription="@string/msg_content_description_show_more_login_options"
android:src="@drawable/ic_expand_more_black_24dp"
android:tint="@color/colorWhite"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginEnd="8dp"
android:background="@color/colorAuthenticationButtonBorderAndDivider"
app:layout_constraintBottom_toBottomOf="@+id/button_expand_collapse_accounts"
app:layout_constraintEnd_toStartOf="@+id/button_expand_collapse_accounts"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/button_expand_collapse_accounts" />
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="8dp"
android:background="@color/colorAuthenticationButtonBorderAndDivider"
app:layout_constraintBottom_toBottomOf="@+id/button_expand_collapse_accounts"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/button_expand_collapse_accounts"
app:layout_constraintTop_toTopOf="@+id/button_expand_collapse_accounts" />
</androidx.constraintlayout.widget.ConstraintLayout>
<Button
android:id="@+id/button_login_with_email"
style="@style/Authentication.Button.Flat"
android:layout_marginTop="32dp"
android:text="@string/msg_login_with_email"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/expand_more_accounts_container"
tools:visibility="visible" />
<Button
android:id="@+id/button_create_an_account"
style="?borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginTop="10dp"
android:text="@string/msg_create_account"
android:textAllCaps="false"
android:textColor="@color/colorAccent"
android:textSize="18sp"
android:textStyle="bold"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button_login_with_email"
tools:visibility="visible" />
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
style="@style/Authentication.AVLoadingIndicatorView"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
\ 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"
......
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView 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:focusableInTouchMode="true"
android:background="@color/colorWhite"
android:padding="@dimen/screen_edge_left_and_right_margins"
tools:context=".authentication.signup.ui.SignupFragment">
<TextView
android:id="@+id/text_sign_in_to_your_server"
style="@style/Authentication.Headline.TextView"
android:text="@string/title_sign_up"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="21dp"
android:layout_marginTop="30dp" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/text_name"
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_person_black_20dp"
android:hint="@string/msg_name"
android:imeOptions="actionNext"
android:inputType="textCapWords" />
<TextView
android:id="@+id/text_sign_up"
style="@style/Authentication.Headline.TextView"
android:text="@string/title_sign_up"
app:layout_constraintStart_toStartOf="parent"
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_name"
android:layout_marginTop="10dp"
android:drawableStart="@drawable/ic_at_black_20dp"
android:hint="@string/msg_username"
android:imeOptions="actionNext"
android:inputType="text" />
<EditText
android:id="@+id/text_name"
style="@style/Authentication.EditText"
android:layout_marginTop="16dp"
android:drawableStart="@drawable/ic_person_black_20dp"
android:hint="@string/msg_name"
android:imeOptions="actionNext"
android:inputType="textCapWords"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_sign_up" />
<EditText
android:id="@+id/text_password"
style="@style/Authentication.EditText"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_username"
android:layout_marginTop="10dp"
android:drawableStart="@drawable/ic_key_black_20dp"
android:hint="@string/msg_password"
android:imeOptions="actionNext"
android:inputType="textPassword" />
<EditText
android:id="@+id/text_username"
style="@style/Authentication.EditText"
android:layout_marginTop="10dp"
android:drawableStart="@drawable/ic_at_black_20dp"
android:hint="@string/msg_username"
android:imeOptions="actionNext"
android:inputType="text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_name" />
<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_password"
android:layout_marginTop="10dp"
android:drawableStart="@drawable/ic_email_black_20dp"
android:hint="@string/msg_email"
android:imeOptions="actionDone"
android:inputType="textEmailAddress" />
<EditText
android:id="@+id/text_password"
style="@style/Authentication.EditText"
android:layout_marginTop="10dp"
android:drawableStart="@drawable/ic_key_black_20dp"
android:hint="@string/msg_password"
android:imeOptions="actionNext"
android:inputType="textPassword"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_username" />
<Button
android:id="@+id/button_sign_up"
style="@style/Authentication.Button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_email"
android:layout_marginTop="20dp"
android:text="@string/action_register" />
<EditText
android:id="@+id/text_email"
style="@style/Authentication.EditText"
android:layout_marginTop="10dp"
android:drawableStart="@drawable/ic_email_black_20dp"
android:hint="@string/msg_email"
android:imeOptions="actionDone"
android:inputType="textEmailAddress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_password" />
<TextView
android:id="@+id/text_new_user_agreement"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_margin="@dimen/screen_edge_left_and_right_margins"
android:fitsSystemWindows="true"
android:textColor="@color/colorPrimaryText"
android:textColorLink="@color/colorAccent" />
<Button
android:id="@+id/button_register"
style="@style/Authentication.Button.Flat"
android:layout_marginTop="20dp"
android:backgroundTint="@color/colorAuthenticationButtonDisabled"
android:enabled="false"
android:text="@string/action_register"
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" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
style="@style/Authentication.AVLoadingIndicatorView"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
\ No newline at end of file
......@@ -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
......@@ -65,17 +65,16 @@
<string name="msg_username">Benutzername</string>
<string name="msg_username_or_email">Benutzername oder E-Mail</string>
<string name="msg_password">Passwort</string>
<string name="msg_name">Name</string>
<string name="msg_email">E-Mail</string>
<string name="msg_name">Name</string> <!-- TODO Add translation -->
<string name="msg_email">EMail</string> <!-- TODO Add translation -->
<string name="msg_avatar_url">Avatar URL</string>
<string name="msg_or_continue_using_social_accounts">Oder weiter mit einem Social Account</string>
<string name="msg_new_user">Neuer Benutzer? %1$s</string>
<string name="msg_forgot_password">Passwort vergessen?</string>
<string name="msg_forgot__your_password">Passwort vergessen?</string>
<string name="msg_reset">Zurücksetzen</string>
<string name="msg_check_your_email_to_reset_your_password">E-Mail gesendet! Prüfe dein E-Mail Posteingang um dein Passwort zurückzusetzen.</string>
<string name="msg_invalid_email">Bitte eine korrekte E-Mail Adresse eingeben</string>
<string name="msg_new_user_agreement">Beim weitergehen akzeptieren Sie usere\n%1$s und %2$s</string>
<string name="msg_2fa_code">2FA Code</string>
<string name="msg_yesterday">Gestern</string>
<string name="msg_today">Heute</string>
<string name="msg_message">Nachricht</string>
......@@ -92,6 +91,7 @@
<string name="msg_content_description_log_in_using_gitlab">Login mit Gitlab</string>
<string name="msg_content_description_log_in_using_wordpress">Login mit WordPress</string>
<string name="msg_content_description_send_message">Sende Nachricht</string>
<string name="msg_content_description_show_more_login_options">Show more login options</string> <!-- TODO Translate-->
<string name="msg_content_description_show_attachment_options">Zeige Anhang Optionen</string>
<string name="msg_you">Du</string>
<string name="msg_unknown">Unbekannt</string>
......@@ -132,13 +132,14 @@
<string name="msg_sent_attachment">Sende Anhang</string>
<string name="msg_welcome_to_rocket_chat">Welcome to Rocket.Chat</string> <!-- TODO Add translation -->
<string name="msg_team_communication">Open Source Communication</string> <!-- TODO Add translation -->
<string name="msg_login_with_email">Login with e-mail</string> <!-- TODO Add translation -->
<string name="msg_login_with_email">Login with <b>e-mail</b></string> <!-- TODO Add translation -->
<string name="msg_create_account">Create an account</string> <!-- TODO Add translation -->
<string name="msg_continue_with_facebook">Continue with Facebook</string> <!-- TODO Add translation -->
<string name="msg_continue_with_github">Continue with Github</string> <!-- TODO Add translation -->
<string name="msg_continue_with_google">Continue with Google</string> <!-- TODO Add translation -->
<string name="msg_continue_with_linkedin">Continue with Linkedin</string> <!-- TODO Add translation -->
<string name="msg_continue_with_gitlab">Continue with GitLab</string> <!-- TODO Add translation -->
<string name="msg_continue_with_facebook">Continue with <b>Facebook</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_github">Continue with <b>Github</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_google">Continue with <b>Google</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_linkedin">Continue with <b>Linkedin</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_gitlab">Continue with <b>GitLab</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_wordpress">Continue with <b>WordPress</b></string> <!-- TODO Add translation -->
<string name="msg_two_factor_authentication">Two-factor Authentication</string> <!-- TODO Add translation -->
<string name="msg__your_2fa_code">What’s your 2FA code?</string> <!-- TODO Add translation -->
......
......@@ -62,19 +62,18 @@
<string name="msg_no_data_to_display">No hay información para mostrar</string>
<string name="msg_profile_update_successfully">Actualización de perfil con éxito</string>
<string name="msg_username">usuario</string>
<string name="msg_username_or_email">nombre de usuario o correo electrónico</string>
<string name="msg_password">contraseña</string>
<string name="msg_name">nombre</string>
<string name="msg_email">correo electrónico</string>
<string name="msg_username_or_email">Nombre de usuario o correo electrónico</string>
<string name="msg_password">Contraseña</string>
<string name="msg_name">Nombre</string>
<string name="msg_email">Correo electrónico</string>
<string name="msg_avatar_url">URL del avatar</string>
<string name="msg_or_continue_using_social_accounts">O continuar usando cuentas sociales</string>
<string name="msg_new_user">Nuevo usuario? %1$s</string>
<string name="msg_forgot_password">Se te olvidó tu contraseña?</string>
<string name="msg_forgot__your_password">Se te olvidó tu contraseña?</string>
<string name="msg_reset">reiniciar</string>
<string name="msg_check_your_email_to_reset_your_password">¡Email enviado! Verifique su bandeja de entrada para restablecer su contraseña.</string>
<string name="msg_invalid_email">Por favor escriba un correo electrónico válido</string>
<string name="msg_new_user_agreement">Al continuar estás aceptando nuestra\n%1$s y %2$s</string>
<string name="msg_2fa_code">Código 2FA</string>
<string name="msg_yesterday">Ayer</string>
<string name="msg_today">Hoy</string>
<string name="msg_message">Mensaje</string>
......@@ -91,6 +90,7 @@
<string name="msg_content_description_log_in_using_gitlab">Inicia sesión usando Gitlab</string>
<string name="msg_content_description_log_in_using_wordpress">Login using WordPress</string> <!-- TODO Translate-->
<string name="msg_content_description_send_message">Enviar mensaje</string>
<string name="msg_content_description_show_more_login_options">Show more login options</string> <!-- TODO Translate-->
<string name="msg_content_description_show_attachment_options">Mostrar opciones de archivo adjunto</string>
<string name="msg_you"></string>
<string name="msg_unknown">Desconocido</string>
......@@ -132,13 +132,14 @@
<string name="msg_no_search_found">No se han encontrado resultados</string>
<string name="msg_welcome_to_rocket_chat">Welcome to Rocket.Chat</string> <!-- TODO Add translation -->
<string name="msg_team_communication">Open Source Communication</string> <!-- TODO Add translation -->
<string name="msg_login_with_email">Login with e-mail</string> <!-- TODO Add translation -->
<string name="msg_login_with_email">Login with <b>e-mail</b></string> <!-- TODO Add translation -->
<string name="msg_create_account">Create an account</string> <!-- TODO Add translation -->
<string name="msg_continue_with_facebook">Continue with Facebook</string> <!-- TODO Add translation -->
<string name="msg_continue_with_github">Continue with Github</string> <!-- TODO Add translation -->
<string name="msg_continue_with_google">Continue with Google</string> <!-- TODO Add translation -->
<string name="msg_continue_with_linkedin">Continue with Linkedin</string> <!-- TODO Add translation -->
<string name="msg_continue_with_gitlab">Continue with GitLab</string> <!-- TODO Add translation -->
<string name="msg_continue_with_facebook">Continue with <b>Facebook</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_github">Continue with <b>Github</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_google">Continue with <b>Google</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_linkedin">Continue with <b>Linkedin</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_gitlab">Continue with <b>GitLab</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_wordpress">Continue with <b>WordPress</b></string> <!-- TODO Add translation -->
<string name="msg_two_factor_authentication">Two-factor Authentication</string> <!-- TODO Add translation -->
<string name="msg__your_2fa_code">What’s your 2FA code?</string> <!-- TODO Add translation -->
......
......@@ -64,19 +64,18 @@
<string name="msg_no_data_to_display">Aucune donnée à afficher</string>
<string name="msg_profile_update_successfully">Mise à jour du profil avec succès</string>
<string name="msg_username">nom d\'utilisateur</string>
<string name="msg_username_or_email">nom d\'utilisateur ou email</string>
<string name="msg_password">mot de passe</string>
<string name="msg_name">prénom</string>
<string name="msg_email">email</string>
<string name="msg_username_or_email">Nom d\'utilisateur ou email</string>
<string name="msg_password">Mot de passe</string>
<string name="msg_name">Prénom</string>
<string name="msg_email">Email</string>
<string name="msg_avatar_url">URL de l\'avatar</string>
<string name="msg_or_continue_using_social_accounts">Ou continuer en utilisant les comptes sociaux</string>
<string name="msg_new_user">Nouvel utilisateur? %1$s</string>
<string name="msg_forgot_password">Mot de passe oublié ? %1$s</string>
<string name="msg_forgot__your_password">Mot de passe oublié ? %1$s</string>
<string name="msg_reset">Réinitialiser</string>
<string name="msg_check_your_email_to_reset_your_password">Email envoyé ! Consultez votre boîte mail pour réinitialiser votre mot de passe.</string>
<string name="msg_invalid_email">Veuillez entrer une adresse valide</string>
<string name="msg_new_user_agreement">En procédant, vous acceptez notre\n%1$s et %2$s</string>
<string name="msg_2fa_code">Code 2FA</string>
<string name="msg_yesterday">Hier</string>
<string name="msg_today">Aujourd\'hui</string>
<string name="msg_message">Message</string>
......@@ -93,6 +92,7 @@
<string name="msg_content_description_log_in_using_gitlab">Connectez-vous en utilisant Gitlab</string>
<string name="msg_content_description_log_in_using_wordpress">Connectez-vous en utilisant WordPress</string>
<string name="msg_content_description_send_message">Envoyer message</string>
<string name="msg_content_description_show_more_login_options">Show more login options</string> <!-- TODO Translate-->
<string name="msg_content_description_show_attachment_options">Afficher les options de fichiers</string>
<string name="msg_you">Vous</string>
<string name="msg_unknown">Inconnu</string>
......@@ -137,13 +137,14 @@
<string name="msg_delete_description">Êtes-vous sûr de vouloir supprimer ce message</string>
<string name="msg_welcome_to_rocket_chat">Welcome to Rocket.Chat</string> <!-- TODO Add translation -->
<string name="msg_team_communication">Open Source Communication</string> <!-- TODO Add translation -->
<string name="msg_login_with_email">Login with e-mail</string> <!-- TODO Add translation -->
<string name="msg_login_with_email">Login with <b>e-mail</b></string> <!-- TODO Add translation -->
<string name="msg_create_account">Create an account</string> <!-- TODO Add translation -->
<string name="msg_continue_with_facebook">Continue with Facebook</string> <!-- TODO Add translation -->
<string name="msg_continue_with_github">Continue with Github</string> <!-- TODO Add translation -->
<string name="msg_continue_with_google">Continue with Google</string> <!-- TODO Add translation -->
<string name="msg_continue_with_linkedin">Continue with Linkedin</string> <!-- TODO Add translation -->
<string name="msg_continue_with_gitlab">Continue with GitLab</string> <!-- TODO Add translation -->
<string name="msg_continue_with_facebook">Continue with <b>Facebook</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_github">Continue with <b>Github</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_google">Continue with <b>Google</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_linkedin">Continue with <b>Linkedin</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_gitlab">Continue with <b>GitLab</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_wordpress">Continue with <b>WordPress</b></string> <!-- TODO Add translation -->
<string name="msg_two_factor_authentication">Two-factor Authentication</string> <!-- TODO Add translation -->
<string name="msg__your_2fa_code">What’s your 2FA code?</string> <!-- TODO Add translation -->
......
......@@ -70,12 +70,11 @@
<string name="msg_avatar_url">अवतार यूआरएल</string>
<string name="msg_or_continue_using_social_accounts">या सामाजिक खाते का उपयोग करना जारी रखें</string>
<string name="msg_new_user">नया उपयोगकर्ता? %1$s</string>
<string name="msg_forgot_password">पासवर्ड भूल गए?</string>
<string name="msg_forgot__your_password">पासवर्ड भूल गए?</string>
<string name="msg_reset">रीसेट करें</string>
<string name="msg_check_your_email_to_reset_your_password">ईमेल गया गया है! अपना पासवर्ड रीसेट करने के लिए अपने इनबॉक्स की जांच करें।</string>
<string name="msg_invalid_email">कृपया एक वैध ई-मेल टाइप करें</string>
<string name="msg_new_user_agreement">आगे बढ़कर आप हमारे %1$s और %2$s से सहमत हो रहे हैं</string>
<string name="msg_2fa_code">कोड 2FA</string>
<string name="msg_yesterday">कल</string>
<string name="msg_today">आज</string>
<string name="msg_message">संदेश</string>
......@@ -92,6 +91,7 @@
<string name="msg_content_description_log_in_using_gitlab">Gitlab द्वारा लॉगिन करें</string>
<string name="msg_content_description_log_in_using_wordpress">WordPress द्वारा लॉगिन करें</string>
<string name="msg_content_description_send_message">मेसेज भेजें</string>
<string name="msg_content_description_show_more_login_options">Show more login options</string> <!-- TODO Translate-->
<string name="msg_content_description_show_attachment_options">अटैचमेंट विकल्प दिखाएं</string>
<string name="msg_you">आप</string>
<string name="msg_unknown">अनजान</string>
......@@ -140,11 +140,12 @@
<string name="msg_team_communication">ओपन सोर्स कम्युनिकेशन</string>
<string name="msg_login_with_email">ई-मेल के साथ लॉगिन करें</string>
<string name="msg_create_account">खाता बनाएं</string>
<string name="msg_continue_with_facebook">Facebook के साथ जारी रखें</string>
<string name="msg_continue_with_github">Github के साथ जारी रखें</string>
<string name="msg_continue_with_google">Google के साथ जारी रखें</string>
<string name="msg_continue_with_linkedin">Linkedin के साथ जारी रखें</string>
<string name="msg_continue_with_gitlab">GitLab के साथ जारी रखें</string>
<string name="msg_continue_with_facebook"><b>Facebook</b> के साथ जारी रखें</string>
<string name="msg_continue_with_github"><b>Github</b> के साथ जारी रखें</string>
<string name="msg_continue_with_google"><b>Google</b> के साथ जारी रखें</string>
<string name="msg_continue_with_linkedin"><b>Linkedin</b> के साथ जारी रखें</string>
<string name="msg_continue_with_gitlab"><b>GitLab</b> के साथ जारी रखें</string>
<string name="msg_continue_with_wordpress"><b>WordPress</b> के साथ जारी रखें</string>
<string name="msg_two_factor_authentication">दो तरीकों से प्रमाणीकरण</string>
<string name="msg__your_2fa_code">आपका 2FA कोड क्या है?</string>
......
......@@ -72,12 +72,11 @@
<string name="msg_avatar_url">アバター URL</string>
<string name="msg_or_continue_using_social_accounts">またはソーシャルアカウントを使用する</string>
<string name="msg_new_user">新規ユーザー? %1$s</string>
<string name="msg_forgot_password">パスワードをお忘れですか? %1$s</string>
<string name="msg_forgot__your_password">パスワードをお忘れですか? %1$s</string>
<string name="msg_reset">リセット</string>
<string name="msg_check_your_email_to_reset_your_password">メールが送信されました! パスワードを変更するためには、受信ボックスを確認してください。</string>
<string name="msg_invalid_email">有効なメールアドレスを入力してください</string>
<string name="msg_new_user_agreement">サインアップすることで、\n%1$s と %2$s に同意したとみなします。</string>
<string name="msg_2fa_code">2FA コード</string>
<string name="msg_today">Today</string> <!-- TODO Add translation -->
<string name="msg_yesterday">昨日</string>
<string name="msg_message">メッセージ</string>
......@@ -85,15 +84,16 @@
<string name="msg_invalid_2fa_code">無効な 2FA コード</string>
<string name="msg_invalid_file">無効なファイル</string>
<string name="msg_invalid_server_url">無効なサーバー URL</string>
<string name="msg_content_description_log_in_using_facebook">Login using Facebook</string>
<string name="msg_content_description_log_in_using_github">Login using Github</string>
<string name="msg_content_description_log_in_using_google">Login using Google</string>
<string name="msg_content_description_log_in_using_linkedin">Login using Linkedin</string>
<string name="msg_content_description_log_in_using_meteor">Login using Meteor</string>
<string name="msg_content_description_log_in_using_twitter">Login using Twitter</string>
<string name="msg_content_description_log_in_using_gitlab">Login using Gitlab</string>
<string name="msg_content_description_log_in_using_wordpress">Login using WordPress</string>
<string name="msg_content_description_log_in_using_facebook">Login using Facebook</string> <!-- TODO Add translation -->
<string name="msg_content_description_log_in_using_github">Login using Github</string> <!-- TODO Add translation -->
<string name="msg_content_description_log_in_using_google">Login using Google</string> <!-- TODO Add translation -->
<string name="msg_content_description_log_in_using_linkedin">Login using Linkedin</string> <!-- TODO Add translation -->
<string name="msg_content_description_log_in_using_meteor">Login using Meteor</string> <!-- TODO Add translation -->
<string name="msg_content_description_log_in_using_twitter">Login using Twitter</string> <!-- TODO Add translation -->
<string name="msg_content_description_log_in_using_gitlab">Login using Gitlab</string> <!-- TODO Add translation -->
<string name="msg_content_description_log_in_using_wordpress">Login using WordPress</string> <!-- TODO Add translation -->
<string name="msg_content_description_send_message">メッセージを送信</string> <!-- TODO Add translation -->
<string name="msg_content_description_show_more_login_options">Show more login options</string> <!-- TODO Translate-->
<string name="msg_content_description_show_attachment_options">添付ファイルオプションを表示する</string>
<string name="msg_you">あなた</string>
<string name="msg_unknown">不明</string>
......@@ -137,13 +137,14 @@
<string name="msg_sent_attachment">添付ファイルを送信しました</string>
<string name="msg_welcome_to_rocket_chat">Welcome to Rocket.Chat</string> <!-- TODO Add translation -->
<string name="msg_team_communication">Team Communication</string> <!-- TODO Add translation -->
<string name="msg_login_with_email">Login with e-mail</string> <!-- TODO Add translation -->
<string name="msg_login_with_email">Login with <b>e-mail</b></string> <!-- TODO Add translation -->
<string name="msg_create_account">Create an account</string> <!-- TODO Add translation -->
<string name="msg_continue_with_facebook">Continue with Facebook</string> <!-- TODO Add translation -->
<string name="msg_continue_with_github">Continue with Github</string> <!-- TODO Add translation -->
<string name="msg_continue_with_google">Continue with Google</string> <!-- TODO Add translation -->
<string name="msg_continue_with_linkedin">Continue with Linkedin</string> <!-- TODO Add translation -->
<string name="msg_continue_with_gitlab">Continue with GitLab</string> <!-- TODO Add translation -->
<string name="msg_continue_with_facebook">Continue with <b>Facebook</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_github">Continue with <b>Github</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_google">Continue with <b>Google</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_linkedin">Continue with <b>Linkedin</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_gitlab">Continue with <b>GitLab</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_wordpress">Continue with <b>WordPress</b></string> <!-- TODO Add translation -->
<string name="msg_two_factor_authentication">Two-factor Authentication</string> <!-- TODO Add translation -->
<string name="msg__your_2fa_code">What’s your 2FA code?</string> <!-- TODO Add translation -->
......
......@@ -63,19 +63,18 @@
<string name="msg_no_data_to_display">Nenhum dado para exibir</string>
<string name="msg_profile_update_successfully">Perfil atualizado com sucesso</string>
<string name="msg_username">nome de usuário</string>
<string name="msg_username_or_email">nome de usuário ou email</string>
<string name="msg_password">senha</string>
<string name="msg_username_or_email">Nome de usuário ou email</string>
<string name="msg_password">Senha</string>
<string name="msg_name">nome</string>
<string name="msg_email">email</string>
<string name="msg_avatar_url">URL do avatar</string>
<string name="msg_or_continue_using_social_accounts">Ou continue através de contas sociais</string>
<string name="msg_new_user">Novo usuário? %1$s</string>
<string name="msg_forgot_password">Esqueceu a senha?</string>
<string name="msg_forgot__your_password">Esqueceu sua senha?</string>
<string name="msg_reset">Redefinir</string>
<string name="msg_check_your_email_to_reset_your_password">Email enviado! Verifique sua caixa de entrada para redefinir sua senha.</string>
<string name="msg_invalid_email">Por favor informe um e-mail válido</string>
<string name="msg_new_user_agreement">Ao proceder você concorda com nossos %1$s e %2$s</string>
<string name="msg_2fa_code">Código 2FA</string>
<string name="msg_yesterday">Ontem</string>
<string name="msg_today">Hoje</string>
<string name="msg_message">Mensagem</string>
......@@ -92,6 +91,7 @@
<string name="msg_content_description_log_in_using_gitlab">Fazer login através do Gitlab</string>
<string name="msg_content_description_log_in_using_wordpress">Fazer login através do WordPress</string>
<string name="msg_content_description_send_message">Enviar mensagem</string>
<string name="msg_content_description_show_more_login_options">Mostrar mais opções de login</string>
<string name="msg_content_description_show_attachment_options">Mostrar opções de anexo</string>
<string name="msg_you">Você</string>
<string name="msg_unknown">Desconhecido</string>
......@@ -133,18 +133,18 @@
<string name="msg_upload_file">Subir arquivo</string>
<string name="msg_file_description">Descrição do arquivo</string>
<string name="msg_send">Enviar</string>
// TODO: Add proper translation.
<string name="msg_delete_message">Delete Message</string>
<string name="msg_delete_description">Are you sure you want to delete this message</string>
<string name="msg_delete_message">Delete Message</string> <!-- TODO Add translation -->
<string name="msg_delete_description">Are you sure you want to delete this message</string> <!-- TODO Add translation -->
<string name="msg_welcome_to_rocket_chat">Welcome to Rocket.Chat</string> <!-- TODO Add translation -->
<string name="msg_team_communication">Open Source Communication</string> <!-- TODO Add translation -->
<string name="msg_login_with_email">Login with e-mail</string> <!-- TODO Add translation -->
<string name="msg_login_with_email">Login with <b>e-mail</b></string> <!-- TODO Add translation -->
<string name="msg_create_account">Create an account</string> <!-- TODO Add translation -->
<string name="msg_continue_with_facebook">Continue with Facebook</string> <!-- TODO Add translation -->
<string name="msg_continue_with_github">Continue with Github</string> <!-- TODO Add translation -->
<string name="msg_continue_with_google">Continue with Google</string> <!-- TODO Add translation -->
<string name="msg_continue_with_linkedin">Continue with Linkedin</string> <!-- TODO Add translation -->
<string name="msg_continue_with_gitlab">Continue with GitLab</string> <!-- TODO Add translation -->
<string name="msg_continue_with_facebook">Continue with <b>Facebook</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_github">Continue with <b>Github</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_google">Continue with <b>Google</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_linkedin">Continue with <b>Linkedin</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_gitlab">Continue with <b>GitLab</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_wordpress">Continue with <b>WordPress</b></string> <!-- TODO Add translation -->
<string name="msg_two_factor_authentication">Two-factor Authentication</string> <!-- TODO Add translation -->
<string name="msg__your_2fa_code">What’s your 2FA code?</string> <!-- TODO Add translation -->
......
......@@ -70,12 +70,11 @@
<string name="msg_avatar_url">URL аватара</string>
<string name="msg_or_continue_using_social_accounts">Или продолжить, используя социальные учетные записи</string>
<string name="msg_new_user">Новый пользователь? %1$s</string>
<string name="msg_forgot_password">Забыли пароль?</string>
<string name="msg_forgot__your_password">Забыли пароль?</string>
<string name="msg_reset">Сброс</string>
<string name="msg_check_your_email_to_reset_your_password">Письмо отправлено! Проверьте свой почтовый ящик, чтобы сбросить пароль.</string>
<string name="msg_invalid_email">Введите действующий e-mail</string>
<string name="msg_new_user_agreement">Продолжая, вы принимаете\n%1$s и %2$s</string>
<string name="msg_2fa_code">Код 2FA</string>
<string name="msg_yesterday">Вчера</string>
<string name="msg_today">Сегодня</string>
<string name="msg_message">Сообщение</string>
......@@ -92,6 +91,7 @@
<string name="msg_content_description_log_in_using_gitlab">Войти с помощью Gitlab</string>
<string name="msg_content_description_log_in_using_wordpress">Войти с помощью WordPress</string>
<string name="msg_content_description_send_message">Отправить сообщение</string>
<string name="msg_content_description_show_more_login_options">Show more login options</string> <!-- TODO Translate-->
<string name="msg_content_description_show_attachment_options">Показать параметры вложения</string>
<string name="msg_you">Вы</string>
<string name="msg_unknown">Неизвестный</string>
......@@ -135,13 +135,14 @@
<string name="msg_search">Поиск</string>
<string name="msg_welcome_to_rocket_chat">Welcome to Rocket.Chat</string> <!-- TODO Add translation -->
<string name="msg_team_communication">Open Source Communication</string> <!-- TODO Add translation -->
<string name="msg_login_with_email">Login with e-mail</string> <!-- TODO Add translation -->
<string name="msg_login_with_email">Login with <b>e-mail</b></string> <!-- TODO Add translation -->
<string name="msg_create_account">Create an account</string> <!-- TODO Add translation -->
<string name="msg_continue_with_facebook">Continue with Facebook</string> <!-- TODO Add translation -->
<string name="msg_continue_with_github">Continue with Github</string> <!-- TODO Add translation -->
<string name="msg_continue_with_google">Continue with Google</string> <!-- TODO Add translation -->
<string name="msg_continue_with_linkedin">Continue with Linkedin</string> <!-- TODO Add translation -->
<string name="msg_continue_with_gitlab">Continue with GitLab</string> <!-- TODO Add translation -->
<string name="msg_continue_with_facebook">Continue with <b>Facebook</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_github">Continue with <b>Github</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_google">Continue with <b>Google</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_linkedin">Continue with <b>Linkedin</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_gitlab">Continue with <b>GitLab</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_wordpress">Continue with <b>WordPress</b></string> <!-- TODO Add translation -->
<string name="msg_two_factor_authentication">Two-factor Authentication</string> <!-- TODO Add translation -->
<string name="msg__your_2fa_code">What’s your 2FA code?</string> <!-- TODO Add translation -->
......
......@@ -70,12 +70,11 @@
<string name="msg_avatar_url">URL аватара</string>
<string name="msg_or_continue_using_social_accounts">Або продовжити, за допомогою акаунта у соціальних мережах</string>
<string name="msg_new_user">Новий користувач? %1$s</string>
<string name="msg_forgot_password">Забули пароль? %1$s</string>
<string name="msg_forgot__your_password">Забули пароль? %1$s</string>
<string name="msg_reset">Скинути</string>
<string name="msg_check_your_email_to_reset_your_password">Лист було відправлено! Перевірте свою поштову скриньку, щоб скинути пароль.</string>
<string name="msg_invalid_email">Введіть діючий e-mail</string>
<string name="msg_new_user_agreement">Продовжуючи, ви погоджуєтеся з %1$s і %2$s</string>
<string name="msg_2fa_code">Код 2FA</string>
<string name="msg_yesterday">Вчора</string>
<string name="msg_today">Сьогодні</string>
<string name="msg_message">"Повідомлення "</string>
......@@ -92,6 +91,7 @@
<string name="msg_content_description_log_in_using_gitlab">Увійти за допомогою Gitlab</string>
<string name="msg_content_description_log_in_using_wordpress">Увійти за допомогою Wordpress</string>
<string name="msg_content_description_send_message">Надіслати повідомлення</string>
<string name="msg_content_description_show_more_login_options">Show more login options</string> <!-- TODO Translate-->
<string name="msg_content_description_show_attachment_options">Показати параметри долучення</string>
<string name="msg_you">Ви</string>
<string name="msg_unknown">Невідомий</string>
......@@ -134,13 +134,14 @@
<string name="msg_delete_description">Ви впевнені, що хочете видалити це повідомлення?</string>
<string name="msg_welcome_to_rocket_chat">Welcome to Rocket.Chat</string> <!-- TODO Add translation -->
<string name="msg_team_communication">Team Communication</string> <!-- TODO Add translation -->
<string name="msg_login_with_email">Login with e-mail</string> <!-- TODO Add translation -->
<string name="msg_login_with_email">Login with <b>e-mail</b></string> <!-- TODO Add translation -->
<string name="msg_create_account">Create an account</string> <!-- TODO Add translation -->
<string name="msg_continue_with_facebook">Continue with Facebook</string> <!-- TODO Add translation -->
<string name="msg_continue_with_github">Continue with Github</string> <!-- TODO Add translation -->
<string name="msg_continue_with_google">Continue with Google</string> <!-- TODO Add translation -->
<string name="msg_continue_with_linkedin">Continue with Linkedin</string> <!-- TODO Add translation -->
<string name="msg_continue_with_gitlab">Continue with GitLab</string> <!-- TODO Add translation -->
<string name="msg_continue_with_facebook">Continue with <b>Facebook</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_github">Continue with <b>Github</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_google">Continue with <b>Google</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_linkedin">Continue with <b>Linkedin</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_gitlab">Continue with <b>GitLab</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_wordpress">Continue with <b>WordPress</b></string> <!-- TODO Add translation -->
<string name="msg_two_factor_authentication">Two-factor Authentication</string> <!-- TODO Add translation -->
<string name="msg__your_2fa_code">What’s your 2FA code?</string> <!-- TODO Add translation -->
......
......@@ -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>
......
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<!-- Default screen margins, per the Android Design guidelines -->
<dimen name="screen_edge_left_and_right_margins">16dp</dimen>
<dimen name="screen_edge_left_and_right_padding">16dp</dimen>
<!-- Authentication -->
<dimen name="button_account_margin_top">10dp</dimen>
<dimen name="message_item_top_and_bottom_padding">6dp</dimen>
<dimen name="member_item_top_and_bottom_padding">6dp</dimen>
......@@ -25,7 +28,6 @@
<!-- ChatRoom -->
<dimen name="chat_item_top_and_bottom_padding">12dp</dimen>
<!-- Emoji -->
<dimen name="picker_padding_bottom">16dp</dimen>
<dimen name="supposed_keyboard_height">252dp</dimen>
......
......@@ -14,7 +14,7 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<!-- Titles -->
<string name="title_sign_in_your_server">Sign in to your server</string>
<string name="title_log_in">Log in</string>
<string name="title_log_in">Login</string>
<string name="title_register_username">Register username</string>
<string name="title_reset_password">Reset password</string>
<string name="title_sign_up">Sign up</string>
......@@ -75,19 +75,18 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<string name="msg_no_data_to_display">No data to display</string>
<string name="msg_profile_update_successfully">Profile update successfully</string>
<string name="msg_username">username</string>
<string name="msg_username_or_email">username or email</string>
<string name="msg_password">password</string>
<string name="msg_name">name</string>
<string name="msg_email">email</string>
<string name="msg_username_or_email">Username or email</string>
<string name="msg_password">Password</string>
<string name="msg_name">Name</string>
<string name="msg_email">Email</string>
<string name="msg_avatar_url">avatar URL</string>
<string name="msg_or_continue_using_social_accounts">Or continue using social accounts</string>
<string name="msg_new_user">New user? %1$s</string>
<string name="msg_forgot_password">Forgot password?</string>
<string name="msg_forgot__your_password">Forgot your password?</string>
<string name="msg_reset">Reset</string>
<string name="msg_check_your_email_to_reset_your_password">Email sent! Check your inbox to reset your password.</string>
<string name="msg_invalid_email">Please type a valid e-mail</string>
<string name="msg_new_user_agreement">By proceeding you are agreeing to our\n%1$s and %2$s</string>
<string name="msg_2fa_code">2FA Code</string>
<string name="msg_more_than_ninety_nine_unread_messages" translatable="false">99+</string>
<string name="msg_yesterday">Yesterday</string>
<string name="msg_today">Today</string>
......@@ -105,6 +104,7 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<string name="msg_content_description_log_in_using_gitlab">Login using Gitlab</string>
<string name="msg_content_description_log_in_using_wordpress">Login using WordPress</string>
<string name="msg_content_description_send_message">Send message</string>
<string name="msg_content_description_show_more_login_options">Show more login options</string>
<string name="msg_content_description_show_attachment_options">Show attachment options</string>
<string name="msg_you">You</string>
<string name="msg_unknown">Unknown</string>
......@@ -149,13 +149,14 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<string name="msg_sent_attachment">Sent an attachment</string>
<string name="msg_welcome_to_rocket_chat">Welcome to Rocket.Chat</string>
<string name="msg_team_communication">Team Communication</string>
<string name="msg_login_with_email">Login with e-mail</string>
<string name="msg_login_with_email">Login with <b>e-mail</b></string>
<string name="msg_create_account">Create an account</string>
<string name="msg_continue_with_facebook">Continue with Facebook</string>
<string name="msg_continue_with_github">Continue with Github</string>
<string name="msg_continue_with_google">Continue with Google</string>
<string name="msg_continue_with_linkedin">Continue with Linkedin</string>
<string name="msg_continue_with_gitlab">Continue with GitLab</string>
<string name="msg_continue_with_facebook">Continue with <b>Facebook</b></string>
<string name="msg_continue_with_github">Continue with <b>Github</b></string>
<string name="msg_continue_with_google">Continue with <b>Google</b></string>
<string name="msg_continue_with_linkedin">Continue with <b>Linkedin</b></string>
<string name="msg_continue_with_gitlab">Continue with <b>GitLab</b></string>
<string name="msg_continue_with_wordpress">Continue with <b>WordPress</b></string>
<string name="msg_two_factor_authentication">Two-factor Authentication</string>
<string name="msg__your_2fa_code">What’s your 2FA code?</string>
......
......@@ -11,17 +11,7 @@
</style>
<!-- Widget styles. -->
<style name="Authentication.TextView" parent="TextAppearance.AppCompat.Medium">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">50dp</item>
<item name="android:layout_marginStart">@dimen/screen_edge_left_and_right_margins</item>
<item name="android:paddingStart">@dimen/edit_text_margin</item>
<item name="android:maxLines">1</item>
<item name="android:drawablePadding">@dimen/edit_text_drawable_padding</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:background">@drawable/style_edit_text_authentication</item>
</style>
<!-- Authentication -->
<style name="Authentication.Headline.TextView" parent="TextAppearance.AppCompat">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
......@@ -42,7 +32,6 @@
<item name="android:textStyle">normal</item>
</style>
<!-- Authentication -->
<style name="Authentication.Button.Title" parent="TextAppearance.AppCompat">
<item name="android:textSize">17sp</item>
<item name="android:fontFamily">sans-serif</item>
......@@ -59,7 +48,7 @@
<item name="android:textSize">15sp</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:textStyle">normal</item>
<item name="android:textColor">@color/colorAuthenticationOnBoardingSecondaryText</item>
<item name="android:textColor">@color/colorAuthenticationSecondaryText</item>
<item name="android:letterSpacing">0.04</item>
</style>
......@@ -67,15 +56,13 @@
<item name="android:textSize">17sp</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:textStyle">normal</item>
<item name="android:textColorHint">@color/colorAuthenticationOnBoardingSecondaryText</item>
<item name="android:textColorHint">@color/colorAuthenticationSecondaryText</item>
<item name="android:lineSpacingExtra">3sp</item>
</style>
<style name="Authentication.EditText" parent="Widget.AppCompat.EditText">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">48dp</item>
<item name="android:layout_marginStart">@dimen/screen_edge_left_and_right_margins</item>
<item name="android:layout_marginEnd">@dimen/screen_edge_left_and_right_margins</item>
<item name="android:paddingStart">@dimen/edit_text_margin</item>
<item name="android:paddingEnd">@dimen/edit_text_margin</item>
<item name="android:maxLines">1</item>
......@@ -86,11 +73,10 @@
<item name="android:background">@drawable/rounded_border</item>
</style>
<style name="Authentication.Button" parent="Widget.AppCompat.Button.Borderless">
<style name="Authentication.Button.Flat" parent="Widget.AppCompat.Button.Borderless">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">48dp</item>
<item name="android:foreground">?selectableItemBackground</item>
<item name="android:radius">60dp</item>
<item name="android:textSize">18sp</item>
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textStyle">normal</item>
......
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