Unverified Commit ea6c8083 authored by Rafael Kellermann Streit's avatar Rafael Kellermann Streit Committed by GitHub

Merge pull request #1742 from RocketChat/beta

[RELEASE] Merge Beta to Master - Don't merge yet
parents 20ce8107 65de72ff
...@@ -16,8 +16,8 @@ android { ...@@ -16,8 +16,8 @@ android {
applicationId "chat.rocket.android" applicationId "chat.rocket.android"
minSdkVersion versions.minSdk minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk targetSdkVersion versions.targetSdk
versionCode 2043 versionCode 2048
versionName "2.6.1" versionName "3.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true
...@@ -93,6 +93,7 @@ dependencies { ...@@ -93,6 +93,7 @@ dependencies {
implementation project(':draw') implementation project(':draw')
implementation project(':util') implementation project(':util')
implementation project(':core') implementation project(':core')
implementation project(':suggestions')
implementation libraries.kotlin implementation libraries.kotlin
implementation libraries.coroutines implementation libraries.coroutines
...@@ -151,6 +152,8 @@ dependencies { ...@@ -151,6 +152,8 @@ dependencies {
implementation libraries.livedataKtx implementation libraries.livedataKtx
implementation 'com.google.code.findbugs:jsr305:3.0.2'
// Proprietary libraries // Proprietary libraries
playImplementation libraries.fcm playImplementation libraries.fcm
playImplementation libraries.firebaseAnalytics playImplementation libraries.firebaseAnalytics
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
673693445664
673693445664
package chat.rocket.android.analytics package chat.rocket.android.analytics
import chat.rocket.android.analytics.event.AuthenticationEvent class AnswersAnalytics : Analytics
import chat.rocket.android.analytics.event.ScreenViewEvent \ No newline at end of file
import chat.rocket.android.analytics.event.SubscriptionTypeEvent
class AnswersAnalytics : Analytics {
override fun logLogin(event: AuthenticationEvent, loginSucceeded: Boolean) {
// Do absolutely nothing
}
override fun logSignUp(event: AuthenticationEvent, signUpSucceeded: Boolean) {
// Do absolutely nothing
}
override fun logScreenView(event: ScreenViewEvent) {
// Do absolutely nothing
}
override fun logMessageSent(event: SubscriptionTypeEvent, serverUrl: String) {
// Do absolutely nothing
}
override fun logMediaUploaded(event: SubscriptionTypeEvent, mimeType: String) {
// Do absolutely nothing
}
override fun logReaction(event: SubscriptionTypeEvent) {
// Do absolutely nothing
}
override fun logServerSwitch(serverUrl: String, serverCount: Int) {
// Do absolutely nothing
}
}
package chat.rocket.android.analytics package chat.rocket.android.analytics
import android.content.Context import android.content.Context
import chat.rocket.android.analytics.event.AuthenticationEvent
import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.analytics.event.SubscriptionTypeEvent
import javax.inject.Inject import javax.inject.Inject
class GoogleAnalyticsForFirebase @Inject constructor(val context: Context) : class GoogleAnalyticsForFirebase @Inject constructor(val context: Context) : Analytics
Analytics { \ No newline at end of file
override fun logLogin(event: AuthenticationEvent, loginSucceeded: Boolean) {
// Do absolutely nothing
}
override fun logSignUp(event: AuthenticationEvent, signUpSucceeded: Boolean) {
// Do absolutely nothing
}
override fun logScreenView(event: ScreenViewEvent) {
// Do absolutely nothing
}
override fun logMessageSent(event: SubscriptionTypeEvent, serverUrl: String) {
// Do absolutely nothing
}
override fun logMediaUploaded(event: SubscriptionTypeEvent, mimeType: String) {
// Do absolutely nothing
}
override fun logReaction(event: SubscriptionTypeEvent) {
// Do absolutely nothing
}
override fun logServerSwitch(serverUrl: String, serverCount: Int) {
// Do absolutely nothing
}
}
...@@ -9,24 +9,25 @@ ...@@ -9,24 +9,25 @@
<application <application
android:name=".app.RocketChatApplication" android:name=".app.RocketChatApplication"
tools:replace="android:name"
android:allowBackup="true" android:allowBackup="true"
android:fullBackupContent="@xml/backup_config" android:fullBackupContent="@xml/backup_config"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"> android:supportsRtl="true"
tools:replace="android:name">
<activity <activity
android:name=".authentication.ui.AuthenticationActivity" android:name=".authentication.ui.AuthenticationActivity"
android:configChanges="orientation" android:configChanges="orientation"
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:theme="@style/AuthenticationTheme" android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
...@@ -40,6 +41,7 @@ ...@@ -40,6 +41,7 @@
<data <data
android:host="auth" android:host="auth"
android:scheme="rocketchat" /> android:scheme="rocketchat" />
<data <data
android:host="go.rocket.chat" android:host="go.rocket.chat"
android:path="/auth" android:path="/auth"
...@@ -49,7 +51,7 @@ ...@@ -49,7 +51,7 @@
<activity <activity
android:name=".server.ui.ChangeServerActivity" android:name=".server.ui.ChangeServerActivity"
android:theme="@style/AuthenticationTheme" /> android:theme="@style/AppTheme" />
<activity <activity
android:name=".main.ui.MainActivity" android:name=".main.ui.MainActivity"
......
...@@ -12,7 +12,7 @@ interface Analytics { ...@@ -12,7 +12,7 @@ interface Analytics {
* @param event The [AuthenticationEvent] used to log in. * @param event The [AuthenticationEvent] used to log in.
* @param loginSucceeded True if successful logged in, false otherwise. * @param loginSucceeded True if successful logged in, false otherwise.
*/ */
fun logLogin(event: AuthenticationEvent, loginSucceeded: Boolean) fun logLogin(event: AuthenticationEvent, loginSucceeded: Boolean) {}
/** /**
* Logs the sign up event. * Logs the sign up event.
...@@ -20,14 +20,14 @@ interface Analytics { ...@@ -20,14 +20,14 @@ interface Analytics {
* @param event The [AuthenticationEvent] used to sign up. * @param event The [AuthenticationEvent] used to sign up.
* @param signUpSucceeded True if successful signed up, false otherwise. * @param signUpSucceeded True if successful signed up, false otherwise.
*/ */
fun logSignUp(event: AuthenticationEvent, signUpSucceeded: Boolean) fun logSignUp(event: AuthenticationEvent, signUpSucceeded: Boolean) {}
/** /**
* Logs the screen view event. * Logs the screen view event.
* *
* @param event The [ScreenViewEvent] to log. * @param event The [ScreenViewEvent] to log.
*/ */
fun logScreenView(event: ScreenViewEvent) fun logScreenView(event: ScreenViewEvent) {}
/** /**
* Logs the message sent event. * Logs the message sent event.
...@@ -35,7 +35,7 @@ interface Analytics { ...@@ -35,7 +35,7 @@ interface Analytics {
* @param event The [SubscriptionTypeEvent] to log. * @param event The [SubscriptionTypeEvent] to log.
* @param serverUrl The server URL to log. * @param serverUrl The server URL to log.
*/ */
fun logMessageSent(event: SubscriptionTypeEvent, serverUrl: String) fun logMessageSent(event: SubscriptionTypeEvent, serverUrl: String) {}
/** /**
* Logs the media upload event. * Logs the media upload event.
...@@ -43,14 +43,14 @@ interface Analytics { ...@@ -43,14 +43,14 @@ interface Analytics {
* @param event The [SubscriptionTypeEvent] to log. * @param event The [SubscriptionTypeEvent] to log.
* @param mimeType The mime type of the media uploaded to log. * @param mimeType The mime type of the media uploaded to log.
*/ */
fun logMediaUploaded(event: SubscriptionTypeEvent, mimeType: String) fun logMediaUploaded(event: SubscriptionTypeEvent, mimeType: String) {}
/** /**
* Logs the reaction event. * Logs the reaction event.
* *
* @param event The [SubscriptionTypeEvent] to log. * @param event The [SubscriptionTypeEvent] to log.
*/ */
fun logReaction(event: SubscriptionTypeEvent) fun logReaction(event: SubscriptionTypeEvent) {}
/** /**
* Logs the server switch event. * Logs the server switch event.
...@@ -58,5 +58,10 @@ interface Analytics { ...@@ -58,5 +58,10 @@ interface Analytics {
* @param serverUrl The server URL to log. * @param serverUrl The server URL to log.
* @param serverCount The number of server(s) the use own. * @param serverCount The number of server(s) the use own.
*/ */
fun logServerSwitch(serverUrl: String, serverCount: Int) fun logServerSwitch(serverUrl: String, serverCount: Int) {}
/**
* Logs the admin opening.
*/
fun logOpenAdmin() {}
} }
...@@ -64,4 +64,10 @@ class AnalyticsManager @Inject constructor( ...@@ -64,4 +64,10 @@ class AnalyticsManager @Inject constructor(
analytics.forEach { it.logServerSwitch(serverUrl, accounts.size) } analytics.forEach { it.logServerSwitch(serverUrl, accounts.size) }
} }
} }
fun logOpenAdmin() {
if (analyticsTrackingInteractor.get()) {
analytics.forEach { it.logOpenAdmin() }
}
}
} }
...@@ -2,13 +2,22 @@ package chat.rocket.android.analytics.event ...@@ -2,13 +2,22 @@ package chat.rocket.android.analytics.event
sealed class ScreenViewEvent(val screenName: String) { sealed class ScreenViewEvent(val screenName: String) {
// Authentication
object OnBoarding : ScreenViewEvent("OnBoardingFragment")
object Server : ScreenViewEvent("ServerFragment")
object LoginOptions : ScreenViewEvent("LoginOptionsFragment")
object Login : ScreenViewEvent("LoginFragment")
object TwoFa : ScreenViewEvent("TwoFAFragment")
object SignUp : ScreenViewEvent("SignupFragment")
object RegisterUsername : ScreenViewEvent("RegisterUsernameFragment")
object ResetPassword : ScreenViewEvent("ResetPasswordFragment")
object About : ScreenViewEvent("AboutFragment") object About : ScreenViewEvent("AboutFragment")
object ChatRoom : ScreenViewEvent("ChatRoomFragment") object ChatRoom : ScreenViewEvent("ChatRoomFragment")
object ChatRooms : ScreenViewEvent("ChatRoomsFragment") object ChatRooms : ScreenViewEvent("ChatRoomsFragment")
object CreateChannel : ScreenViewEvent("CreateChannelFragment") object CreateChannel : ScreenViewEvent("CreateChannelFragment")
object FavoriteMessages : ScreenViewEvent("FavoriteMessagesFragment") object FavoriteMessages : ScreenViewEvent("FavoriteMessagesFragment")
object Files : ScreenViewEvent("FilesFragment") object Files : ScreenViewEvent("FilesFragment")
object Login : ScreenViewEvent("LoginFragment")
object MemberBottomSheet : ScreenViewEvent("MemberBottomSheetFragment") object MemberBottomSheet : ScreenViewEvent("MemberBottomSheetFragment")
object Members : ScreenViewEvent("MembersFragment") object Members : ScreenViewEvent("MembersFragment")
object Mentions : ScreenViewEvent("MentionsFragment") object Mentions : ScreenViewEvent("MentionsFragment")
...@@ -17,10 +26,5 @@ sealed class ScreenViewEvent(val screenName: String) { ...@@ -17,10 +26,5 @@ sealed class ScreenViewEvent(val screenName: String) {
object PinnedMessages : ScreenViewEvent("PinnedMessagesFragment") object PinnedMessages : ScreenViewEvent("PinnedMessagesFragment")
object Preferences : ScreenViewEvent("PreferencesFragment") object Preferences : ScreenViewEvent("PreferencesFragment")
object Profile : ScreenViewEvent("ProfileFragment") object Profile : ScreenViewEvent("ProfileFragment")
object RegisterUsername : ScreenViewEvent("RegisterUsernameFragment")
object ResetPassword : ScreenViewEvent("ResetPasswordFragment")
object Server : ScreenViewEvent("ServerFragment")
object Settings : ScreenViewEvent("SettingsFragment") object Settings : ScreenViewEvent("SettingsFragment")
object SignUp : ScreenViewEvent("SignupFragment")
object TwoFa : ScreenViewEvent("TwoFAFragment")
} }
package chat.rocket.android.authentication.di package chat.rocket.android.authentication.di
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.presentation.AuthenticationNavigator import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.authentication.ui.AuthenticationActivity import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.dagger.qualifier.ForAuthentication import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerActivity import chat.rocket.android.dagger.scope.PerActivity
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module @Module
class AuthenticationModule { class AuthenticationModule {
@Provides @Provides
@PerActivity @PerActivity
fun provideAuthenticationNavigator(activity: AuthenticationActivity) = AuthenticationNavigator(activity) fun provideAuthenticationNavigator(activity: AuthenticationActivity) =
AuthenticationNavigator(activity)
@Provides
@PerActivity
fun provideJob() = Job()
@Provides
@PerActivity
fun provideLifecycleOwner(activity: AuthenticationActivity): LifecycleOwner = activity
@Provides
@PerActivity
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy =
CancelStrategy(owner, jobs)
} }
\ No newline at end of file
...@@ -3,34 +3,18 @@ package chat.rocket.android.authentication.login.di ...@@ -3,34 +3,18 @@ package chat.rocket.android.authentication.login.di
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.login.presentation.LoginView import chat.rocket.android.authentication.login.presentation.LoginView
import chat.rocket.android.authentication.login.ui.LoginFragment import chat.rocket.android.authentication.login.ui.LoginFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module @Module
class LoginFragmentModule { class LoginFragmentModule {
@Provides @Provides
@PerFragment @PerFragment
fun provideJob() = Job() fun loginView(frag: LoginFragment): LoginView = frag
@Provides @Provides
@PerFragment @PerFragment
fun loginView(frag: LoginFragment): LoginView { fun provideLifecycleOwner(frag: LoginFragment): LifecycleOwner = frag
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: LoginFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
} }
\ No newline at end of file
...@@ -5,259 +5,34 @@ import chat.rocket.android.core.behaviours.MessageView ...@@ -5,259 +5,34 @@ import chat.rocket.android.core.behaviours.MessageView
interface LoginView : LoadingView, MessageView { interface LoginView : LoadingView, MessageView {
/**
* Shows the form view (i.e the username/email and password fields) if it is enabled by the server settings.
*
* REMARK: We must set up the login button listener [setupLoginButtonListener].
* Remember to enable [enableUserInput] or disable [disableUserInput] the view interaction for the user when submitting the form.
*/
fun showFormView()
/**
* Hides the form view.
*/
fun hideFormView()
/**
* Setups the login button when tapped.
*/
fun setupLoginButtonListener()
/**
* Enables the view interactions for the user.
*/
fun enableUserInput()
/**
* Disables the view interactions for the user.
*/
fun disableUserInput()
/**
* Shows the CAS button if the sign in/sign out via CAS protocol is enabled by the server settings.
*
* REMARK: We must set up the CAS button listener before showing it [setupCasButtonListener].
*/
fun showCasButton()
/**
* Hides the CAS button.
*/
fun hideCasButton()
/**
* Setups the CAS button when tapped.
*
* @param casUrl The CAS URL to authenticate with.
* @param casToken The requested token to be sent to the CAS server.
*/
fun setupCasButtonListener(casUrl: String, casToken: String)
/**
* Shows the sign up view if the new users registration is enabled by the server settings.
*
* REMARK: We must set up the sign up view listener [setupSignUpView].
*/
fun showSignUpView()
/**
* Setups the sign up view when tapped.
*/
fun setupSignUpView()
/** /**
* Shows the forgot password view if enabled by the server settings. * Shows the forgot password view if enabled by the server settings.
*
* REMARK: We must set up the forgot password view listener [setupForgotPasswordView].
*/ */
fun showForgotPasswordView() fun showForgotPasswordView()
/** /**
* Setups the forgot password view when tapped. * Saves Google Smart Lock credentials.
*/
fun setupForgotPasswordView()
/**
* Hides the sign up view.
*/
fun hideSignUpView()
/**
* Enables and shows the oauth view if there is login via social accounts enabled by the server settings.
*
* REMARK: We must show at maximum *three* social accounts views ([enableLoginByFacebook], [enableLoginByGithub], [enableLoginByGoogle],
* [enableLoginByLinkedin], [enableLoginByMeteor], [enableLoginByTwitter], [enableLoginByGitlab], [addCustomOauthServiceButton] or [addSamlServiceButton]) for the oauth view.
* If the possibility of login via social accounts exceeds 3 different ways we should set up the FAB ([setupFabListener]) to show the remaining view(s).
*/
fun enableOauthView()
/**
* Disables and hides the Oauth view if there is not login via social accounts enabled by the server settings.
*/
fun disableOauthView()
/**
* Shows the login button.
*/
fun showLoginButton()
/**
* Hides the login button.
*/
fun hideLoginButton()
/**
* Shows the "login by Facebook view if it is enable by the server settings.
*/
fun enableLoginByFacebook()
/**
* Shows the "login by Github" view if it is enable by the server settings.
*
* REMARK: We must set up the Github button listener before enabling it [setupGithubButtonListener].
*/
fun enableLoginByGithub()
/**
* Setups the Github button when tapped.
*
* @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).
*/
fun setupGithubButtonListener(githubUrl: String, state: String)
/**
* Shows the "login by Google" view if it is enable by the server settings.
*
* REMARK: We must set up the Google button listener before enabling it [setupGoogleButtonListener].
*/
fun enableLoginByGoogle()
/**
* Setups the Google button when tapped.
*
* @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).
*/
fun setupGoogleButtonListener(googleUrl: String, state: String)
/**
* Shows the "login by Linkedin" view if it is enable by the server settings.
*
* REMARK: We must set up the Linkedin button listener before enabling it [setupLinkedinButtonListener].
*/
fun enableLoginByLinkedin()
/**
* Setups the Linkedin button when tapped.
*
* @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).
*/
fun setupLinkedinButtonListener(linkedinUrl: String, state: String)
/**
* Setups the Facebook button when tapped.
*
* @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 Meteor" view if it is enable by the server settings.
*/
fun enableLoginByMeteor()
/**
* Shows the "login by Twitter" view if it is enable by the server settings.
*/
fun enableLoginByTwitter()
/**
* Shows the "login by Gitlab" view if it is enable by the server settings.
*
* REMARK: We must set up the Gitlab button listener before enabling it [setupGitlabButtonListener].
*/
fun enableLoginByGitlab()
/**
* Setups the Gitlab button when tapped.
*
* @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 setupGitlabButtonListener(gitlabUrl: String, state: String)
/**
* Shows the "login by WordPress" view if it is enable by the server settings.
*
* 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 setupWordpressButtonListener(wordpressUrl: String, state: String)
/**
* 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( fun saveSmartLockCredentials(id: String, password: String)
samlUrl: String,
samlToken: String,
serviceName: String,
serviceNameColor: Int,
buttonColor: Int
)
/** /**
* Setups the FloatingActionButton to show more social accounts views (expanding the oauth view interface to show the remaining view(s)). * Enables the button to login when the user inputs an username and a password.
*/ */
fun setupFabListener() fun enableButtonLogin()
fun setupGlobalListener()
/** /**
* Alerts the user about a wrong inputted username or email. * 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 alertWrongUsernameOrEmail() fun disableButtonLogin()
/** /**
* Alerts the user about a wrong inputted password. * Enables the forget password button after requesting a processing to log in.
*/ */
fun alertWrongPassword() fun enableButtonForgetPassword()
/** /**
* Saves Google Smart Lock credentials. * Disables the forget password button when requesting a processing to log in.
*/ */
fun saveSmartLockCredentials(id: String, password: String) fun disableButtonForgetPassword()
} }
package chat.rocket.android.authentication.loginoptions.di
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.loginoptions.presentation.LoginOptionsView
import chat.rocket.android.authentication.loginoptions.ui.LoginOptionsFragment
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.Provides
@Module
class LoginOptionsFragmentModule {
@Provides
@PerFragment
fun loginOptionsView(frag: LoginOptionsFragment): LoginOptionsView = frag
@Provides
@PerFragment
fun provideLifecycleOwner(frag: LoginOptionsFragment): LifecycleOwner = frag
}
\ No newline at end of file
package chat.rocket.android.authentication.loginoptions.di
import chat.rocket.android.authentication.loginoptions.ui.LoginOptionsFragment
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module abstract class LoginOptionsFragmentProvider {
@ContributesAndroidInjector(modules = [LoginOptionsFragmentModule::class])
@PerFragment
abstract fun providesLoginOptionFragment(): LoginOptionsFragment
}
\ No newline at end of file
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.domain.model.LoginDeepLinkInfo
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.infrastructure.LocalRepository
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.favicon
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.domain.wideTile
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.serverLogoUrl
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatAuthException
import chat.rocket.common.RocketChatException
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.loginWithCas
import chat.rocket.core.internal.rest.loginWithOauth
import chat.rocket.core.internal.rest.loginWithSaml
import chat.rocket.core.internal.rest.me
import kotlinx.coroutines.experimental.delay
import java.util.concurrent.TimeUnit
import javax.inject.Inject
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
class LoginOptionsPresenter @Inject constructor(
private val view: LoginOptionsView,
private val strategy: CancelStrategy,
private val factory: RocketChatClientFactory,
private val navigator: AuthenticationNavigator,
private val settingsInteractor: GetSettingsInteractor,
private val localRepository: LocalRepository,
private val saveCurrentServer: SaveCurrentServerInteractor,
private val saveAccountInteractor: SaveAccountInteractor,
private val analyticsManager: AnalyticsManager,
private val tokenRepository: TokenRepository,
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 credentialToken: String
private lateinit var credentialSecret: String
private lateinit var deepLinkUserId: String
private lateinit var deepLinkToken: String
private lateinit var loginMethod: AuthenticationEvent
fun toCreateAccount() = navigator.toCreateAccount()
fun toLoginWithEmail() = navigator.toLogin(currentServer)
fun authenticateWithOauth(oauthToken: String, oauthSecret: String) {
setupConnectionInfo(currentServer)
credentialToken = oauthToken
credentialSecret = oauthSecret
loginMethod = AuthenticationEvent.AuthenticationWithOauth
doAuthentication(TYPE_LOGIN_OAUTH)
}
fun authenticateWithCas(casToken: String) {
setupConnectionInfo(currentServer)
credentialToken = casToken
loginMethod = AuthenticationEvent.AuthenticationWithCas
doAuthentication(TYPE_LOGIN_CAS)
}
fun authenticateWithSaml(samlToken: String) {
setupConnectionInfo(currentServer)
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)
}
}
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_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"
)
}
}
}
val myself = retryIO("me()") { client.me() }
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
)
localRepository.saveCurrentUser(url = currentServer, user = user)
saveCurrentServer.save(currentServer)
saveAccount(username)
saveToken(token)
analyticsManager.logLogin(loginMethod, true)
navigator.toChatList()
}.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 {
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
}
}
}
private fun setupConnectionInfo(serverUrl: String) {
currentServer = serverUrl
client = factory.create(currentServer)
settings = settingsInteractor.get(currentServer)
}
private suspend fun saveAccount(username: String) {
val icon = settings.favicon()?.let {
currentServer.serverLogoUrl(it)
}
val logo = settings.wideTile()?.let {
currentServer.serverLogoUrl(it)
}
val thumb = currentServer.avatarUrl(username)
val account = Account(currentServer, icon, logo, username, thumb)
saveAccountInteractor.save(account)
}
private fun saveToken(token: Token) = tokenRepository.save(currentServer, token)
}
\ No newline at end of file
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.dagger.scope.PerFragment
import dagger.Module
import dagger.Provides
@Module
class OnBoardingFragmentModule {
@Provides
@PerFragment
fun onBoardingView(frag: OnBoardingFragment): OnBoardingView = frag
@Provides
@PerFragment
fun provideLifecycleOwner(frag: OnBoardingFragment): LifecycleOwner = frag
}
\ No newline at end of file
package chat.rocket.android.authentication.onboarding.di
import chat.rocket.android.authentication.onboarding.ui.OnBoardingFragment
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module
abstract class OnBoardingFragmentProvider {
@ContributesAndroidInjector(modules = [OnBoardingFragmentModule::class])
@PerFragment
abstract fun provideOnBoardingFragment(): OnBoardingFragment
}
\ No newline at end of file
package chat.rocket.android.authentication.onboarding.presentation
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
interface OnBoardingView : LoadingView, MessageView
\ No newline at end of file
package chat.rocket.android.authentication.presentation package chat.rocket.android.authentication.presentation
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetAccountInteractor 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.GetCurrentServerInteractor
import chat.rocket.android.server.domain.SettingsRepository import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.TokenRepository 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 import javax.inject.Inject
class AuthenticationPresenter @Inject constructor( class AuthenticationPresenter @Inject constructor(
private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator, private val navigator: AuthenticationNavigator,
private val getCurrentServerInteractor: GetCurrentServerInteractor, private val getCurrentServerInteractor: GetCurrentServerInteractor,
private val getAccountInteractor: GetAccountInteractor, private val getAccountInteractor: GetAccountInteractor,
private val settingsRepository: SettingsRepository, private val settingsRepository: SettingsRepository,
private val localRepository: LocalRepository, private val localRepository: LocalRepository,
private val tokenRepository: TokenRepository private val tokenRepository: TokenRepository,
private val serverInteractor: GetConnectingServerInteractor
) { ) {
suspend fun loadCredentials(newServer: Boolean, callback: (authenticated: Boolean) -> Unit) {
fun loadCredentials(newServer: Boolean, callback: (isAuthenticated: Boolean) -> Unit) {
launchUI(strategy) {
val currentServer = getCurrentServerInteractor.get() val currentServer = getCurrentServerInteractor.get()
val serverToken = currentServer?.let { tokenRepository.get(currentServer) } val serverToken = currentServer?.let { tokenRepository.get(currentServer) }
val settings = currentServer?.let { settingsRepository.get(currentServer) } val settings = currentServer?.let { settingsRepository.get(currentServer) }
...@@ -25,11 +34,23 @@ class AuthenticationPresenter @Inject constructor( ...@@ -25,11 +34,23 @@ class AuthenticationPresenter @Inject constructor(
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, account.userName) localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, account.userName)
} }
if (newServer || currentServer == null || serverToken == null || settings == null || account?.userName == null) { if (newServer || currentServer == null ||
serverToken == null ||
settings == null ||
account?.userName == null
) {
callback(false) callback(false)
} else { } else {
callback(true) callback(true)
navigator.toChatList()
} }
} }
}
fun termsOfService(toolbarTitle: String) =
serverInteractor.get()?.let { navigator.toWebPage(it.termsOfServiceUrl(), toolbarTitle) }
fun privacyPolicy(toolbarTitle: String) =
serverInteractor.get()?.let { navigator.toWebPage(it.privacyPolicyUrl(), toolbarTitle) }
fun toChatList() = navigator.toChatList()
} }
\ No newline at end of file
...@@ -6,7 +6,12 @@ import chat.rocket.android.core.behaviours.MessageView ...@@ -6,7 +6,12 @@ import chat.rocket.android.core.behaviours.MessageView
interface RegisterUsernameView : LoadingView, MessageView { interface RegisterUsernameView : LoadingView, MessageView {
/** /**
* Alerts the user about a blank username. * Enables the button to set the username if the user entered at least one character.
*/ */
fun alertBlankUsername() fun enableButtonUseThisUsername()
/**
* Disables the button to set the username when there is no character entered by the user.
*/
fun disableButtonUseThisUsername()
} }
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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