diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 07ebfa45e3130f33ab5921bbc3678c811855216f..85c804c4587f45fddc8bc08560ec1313af0a6687 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -70,8 +70,6 @@ native <methods>; -keep class org.futo.circles.core.model.CircleRoomTypeArg -keep class org.futo.circles.core.model.InviteTypeArg --keep class org.futo.circles.auth.model.PasswordModeArg --keep class org.futo.circles.auth.model.TermsModeArg -keep class org.futo.circles.core.model.ShareUrlTypeArg -keep class org.futo.circles.model.PeopleCategoryTypeArg diff --git a/app/src/main/java/org/futo/circles/feature/settings/SettingsDataSource.kt b/app/src/main/java/org/futo/circles/feature/settings/SettingsDataSource.kt index c7e6b4c62839f74565392ca611443415c9c51bbc..b60abdd6c0994b32d729f35a07892e6917b64e5c 100644 --- a/app/src/main/java/org/futo/circles/feature/settings/SettingsDataSource.kt +++ b/app/src/main/java/org/futo/circles/feature/settings/SettingsDataSource.kt @@ -1,7 +1,7 @@ package org.futo.circles.feature.settings import org.futo.circles.auth.feature.change_password.ChangePasswordDataSource -import org.futo.circles.auth.feature.reauth.AuthConfirmationProvider +import org.futo.circles.auth.feature.uia.flow.reauth.AuthConfirmationProvider import org.futo.circles.core.extensions.Response import org.futo.circles.core.extensions.createResult import org.futo.circles.core.provider.MatrixSessionProvider diff --git a/app/src/main/java/org/futo/circles/feature/settings/SettingsFragment.kt b/app/src/main/java/org/futo/circles/feature/settings/SettingsFragment.kt index 11e4fa65c451fc76072c3bb3f490a6d8ad24d0c8..4af55af0b7303c9db62140652598328b99cf92f5 100644 --- a/app/src/main/java/org/futo/circles/feature/settings/SettingsFragment.kt +++ b/app/src/main/java/org/futo/circles/feature/settings/SettingsFragment.kt @@ -11,7 +11,7 @@ import by.kirich1409.viewbindingdelegate.viewBinding import dagger.hilt.android.AndroidEntryPoint import org.futo.circles.MainActivity import org.futo.circles.R -import org.futo.circles.auth.feature.reauth.ReAuthCancellationListener +import org.futo.circles.auth.feature.uia.flow.reauth.ReAuthCancellationListener import org.futo.circles.auth.model.LogOut import org.futo.circles.auth.model.SwitchUser import org.futo.circles.core.base.CirclesAppConfig diff --git a/app/src/main/java/org/futo/circles/feature/settings/SettingsNavigator.kt b/app/src/main/java/org/futo/circles/feature/settings/SettingsNavigator.kt index 6c34556ccb2d072db69f66f11bef40307357f610..b855c5b75200670a795cbfdc99b6899b07be99b1 100644 --- a/app/src/main/java/org/futo/circles/feature/settings/SettingsNavigator.kt +++ b/app/src/main/java/org/futo/circles/feature/settings/SettingsNavigator.kt @@ -1,10 +1,7 @@ package org.futo.circles.feature.settings import androidx.navigation.fragment.findNavController -import org.futo.circles.R import org.futo.circles.core.extensions.navigateSafe -import org.futo.circles.core.extensions.showError -import org.futo.circles.core.model.ShareUrlTypeArg class SettingsNavigator(private val fragment: SettingsFragment) { @@ -25,7 +22,7 @@ class SettingsNavigator(private val fragment: SettingsFragment) { fun navigateToReAuthStages() { fragment.findNavController() - .navigateSafe(SettingsFragmentDirections.toReAuthStagesDialogFragment()) + .navigateSafe(SettingsFragmentDirections.toUiaDialogFragment()) } diff --git a/app/src/main/java/org/futo/circles/feature/settings/SettingsViewModel.kt b/app/src/main/java/org/futo/circles/feature/settings/SettingsViewModel.kt index bacd54ec9cd8d8ce4f1ac193703d0a9ac46950f3..b7d11b53094d02cdf78a36ab5c67d05e0bc35c49 100644 --- a/app/src/main/java/org/futo/circles/feature/settings/SettingsViewModel.kt +++ b/app/src/main/java/org/futo/circles/feature/settings/SettingsViewModel.kt @@ -2,13 +2,11 @@ package org.futo.circles.feature.settings import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel -import org.futo.circles.auth.feature.log_in.log_out.LogoutDataSource import org.futo.circles.auth.feature.token.RefreshTokenManager import org.futo.circles.core.base.SingleEventLiveData import org.futo.circles.core.extensions.Response import org.futo.circles.core.extensions.createResult import org.futo.circles.core.extensions.launchBg -import org.futo.circles.core.feature.workspace.SharedCircleDataSource import org.futo.circles.core.provider.MatrixSessionProvider import org.matrix.android.sdk.internal.session.media.MediaUsageInfo import javax.inject.Inject @@ -16,7 +14,6 @@ import javax.inject.Inject @HiltViewModel class SettingsViewModel @Inject constructor( private val settingsDataSource: SettingsDataSource, - private val logoutDataSource: LogoutDataSource, private val refreshTokenManager: RefreshTokenManager ) : ViewModel() { @@ -31,7 +28,9 @@ class SettingsViewModel @Inject constructor( fun logOut() { launchBg { MatrixSessionProvider.currentSession?.let { refreshTokenManager.cancelTokenRefreshing(it) } - val result = logoutDataSource.logOut() + val result = createResult { + MatrixSessionProvider.getSessionOrThrow().signOutService().signOut(true) + } logOutLiveData.postValue(result) } } diff --git a/app/src/main/res/navigation/settings_nav_graph.xml b/app/src/main/res/navigation/settings_nav_graph.xml index dde2cbff7b7fbd4dbee57aaeac4942e4d20aa039..abe59b04798150055e186d0422d1561cf02f5b59 100644 --- a/app/src/main/res/navigation/settings_nav_graph.xml +++ b/app/src/main/res/navigation/settings_nav_graph.xml @@ -18,8 +18,8 @@ android:id="@+id/to_activeSessionsDialogFragment" app:destination="@id/log_in_sessions_nav_graph" /> <action - android:id="@+id/to_reAuthStagesDialogFragment" - app:destination="@id/reAuthStagesDialogFragment" /> + android:id="@+id/to_uiaDialogFragment" + app:destination="@id/UIADialogFragment" /> <action android:id="@+id/to_pushNotificationsSettingsDialogFragment" app:destination="@id/pushNotificationsSettingsDialogFragment" /> @@ -50,14 +50,13 @@ <include app:graph="@navigation/log_in_sessions_nav_graph" /> - <dialog - android:id="@+id/reAuthStagesDialogFragment" - android:name="org.futo.circles.auth.feature.reauth.ReAuthStagesDialogFragment" - tools:layout="@layout/fragment_login_stages" /> - <dialog android:id="@+id/manageSubscriptionDialogFragment" android:name="org.futo.circles.auth.feature.manage_subscription.ManageSubscriptionDialogFragment" tools:layout="@layout/dialog_fragment_manage_subscription" /> + <dialog + android:id="@+id/UIADialogFragment" + android:name="org.futo.circles.auth.feature.uia.UIADialogFragment" + tools:layout="@layout/dialog_fragment_uia" /> </navigation> \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/base/BaseAcceptTermsDataSource.kt b/auth/src/main/java/org/futo/circles/auth/base/BaseAcceptTermsDataSource.kt deleted file mode 100644 index 950a3e2f67feaa7a574a766621718b304a87e5b7..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/base/BaseAcceptTermsDataSource.kt +++ /dev/null @@ -1,39 +0,0 @@ -package org.futo.circles.auth.base - -import androidx.lifecycle.MutableLiveData -import org.futo.circles.auth.feature.log_in.stages.terms.LoginAcceptTermsDataSource -import org.futo.circles.auth.feature.sign_up.terms.SignupAcceptTermsDataSource -import org.futo.circles.auth.model.TermsListItem -import org.futo.circles.auth.model.TermsModeArg -import org.futo.circles.core.extensions.Response -import javax.inject.Inject - -abstract class BaseAcceptTermsDataSource { - - class Factory @Inject constructor( - private val loginStagesDataSourceFactory: BaseLoginStagesDataSource.Factory, - private val signupAcceptTermsDataSource: SignupAcceptTermsDataSource - ) { - fun create(mode: TermsModeArg): BaseAcceptTermsDataSource = when (mode) { - TermsModeArg.Login -> LoginAcceptTermsDataSource( - loginStagesDataSourceFactory.create(false) - ) - - TermsModeArg.Signup -> signupAcceptTermsDataSource - TermsModeArg.ReAuth -> LoginAcceptTermsDataSource( - loginStagesDataSourceFactory.create(true) - ) - } - } - - protected abstract fun getTermsList(): List<TermsListItem> - abstract suspend fun acceptTerms(): Response<Unit> - - val termsListLiveData by lazy { MutableLiveData(getTermsList()) } - - fun changeTermCheck(item: TermsListItem) { - termsListLiveData.value = - termsListLiveData.value?.map { if (it.id == item.id) it.copy(isChecked = !it.isChecked) else it } - } - -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/base/BaseLoginStagesDataSource.kt b/auth/src/main/java/org/futo/circles/auth/base/BaseLoginStagesDataSource.kt deleted file mode 100644 index 8fe2c916e7acd852570bd2072a0f8b7ce8e5ddd5..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/base/BaseLoginStagesDataSource.kt +++ /dev/null @@ -1,122 +0,0 @@ -package org.futo.circles.auth.base - -import android.content.Context -import androidx.lifecycle.MutableLiveData -import org.futo.circles.auth.R -import org.futo.circles.auth.feature.log_in.stages.LoginStagesDataSource -import org.futo.circles.auth.feature.reauth.ReAuthStagesDataSource -import org.futo.circles.auth.feature.sign_up.SignUpDataSource.Companion.REGISTRATION_BSSPEKE_OPRF_TYPE -import org.futo.circles.auth.feature.sign_up.SignUpDataSource.Companion.REGISTRATION_BSSPEKE_SAVE_TYPE -import org.futo.circles.core.base.SingleEventLiveData -import org.futo.circles.core.extensions.Response -import org.matrix.android.sdk.api.auth.registration.RegistrationResult -import org.matrix.android.sdk.api.auth.registration.Stage -import org.matrix.android.sdk.api.util.JsonDict -import javax.inject.Inject - -abstract class BaseLoginStagesDataSource(private val context: Context) { - - class Factory @Inject constructor( - private val loginStagesDataSource: LoginStagesDataSource, - private val reAuthStagesDataSource: ReAuthStagesDataSource - ) { - fun create(isReAuth: Boolean): BaseLoginStagesDataSource = if (isReAuth) - reAuthStagesDataSource else loginStagesDataSource - } - - val subtitleLiveData = MutableLiveData<String>() - val loginStageNavigationLiveData = SingleEventLiveData<LoginStageNavigationEvent>() - - val stagesToComplete = mutableListOf<Stage>() - var currentStage: Stage? = null - private set - - var userName: String = "" - private set - var domain: String = "" - private set - - protected var userPassword: String = "" - - fun startLoginStages( - loginStages: List<Stage>, - name: String, - serverDomain: String - ) { - userName = name - domain = serverDomain - userPassword = "" - currentStage = null - stagesToComplete.clear() - stagesToComplete.addAll(loginStages) - navigateToNextStage() - } - - protected fun getIdentifier() = mapOf( - USER_PARAM_KEY to "@$userName:$domain", - TYPE_PARAM_KEY to LOGIN_PASSWORD_USER_ID_TYPE - ) - - abstract suspend fun performLoginStage( - authParams: JsonDict, - password: String? = null - ): Response<RegistrationResult> - - private fun getCurrentStageIndex() = - stagesToComplete.indexOf(currentStage).takeIf { it != -1 } ?: 0 - - private fun setNextStage() { - currentStage = currentStage?.let { - stagesToComplete.getOrNull(getCurrentStageIndex() + 1) - } ?: stagesToComplete.firstOrNull() - } - - protected fun isStageRetry(result: RegistrationResult?): Boolean { - val nextStageType = - ((result as? RegistrationResult.FlowResponse)?.flowResult?.missingStages?.lastOrNull() as? Stage.Other)?.type - return nextStageType == (currentStage as? Stage.Other)?.type && nextStageType != null - } - - protected fun navigateToNextStage() { - setNextStage() - val event = when (val stage = currentStage) { - is Stage.Terms -> LoginStageNavigationEvent.Terms - is Stage.Other -> handleStageOther(stage.type) - else -> throw IllegalArgumentException( - context.getString(R.string.not_supported_stage_format, stage.toString()) - ) - } - event?.let { loginStageNavigationLiveData.postValue(it) } - updatePageSubtitle() - } - - private fun handleStageOther(type: String): LoginStageNavigationEvent? = when (type) { - LOGIN_PASSWORD_TYPE -> LoginStageNavigationEvent.Password - DIRECT_LOGIN_PASSWORD_TYPE -> LoginStageNavigationEvent.DirectPassword - LOGIN_BSSPEKE_OPRF_TYPE -> LoginStageNavigationEvent.BSspekeLogin - LOGIN_BSSPEKE_VERIFY_TYPE -> null - REGISTRATION_BSSPEKE_OPRF_TYPE -> LoginStageNavigationEvent.BSspekeSignup - REGISTRATION_BSSPEKE_SAVE_TYPE -> null - else -> throw IllegalArgumentException( - context.getString(R.string.not_supported_stage_format, type) - ) - } - - private fun updatePageSubtitle() { - val size = stagesToComplete.size - val number = getCurrentStageIndex() + 1 - val subtitle = context.getString(R.string.sign_up_stage_subtitle_format, number, size) - subtitleLiveData.postValue(subtitle) - } - - companion object { - const val USER_PARAM_KEY = "user" - - const val LOGIN_PASSWORD_TYPE = "m.login.password" - const val DIRECT_LOGIN_PASSWORD_TYPE = "m.login.password.direct" - const val LOGIN_BSSPEKE_OPRF_TYPE = "m.login.bsspeke-ecc.oprf" - const val LOGIN_BSSPEKE_VERIFY_TYPE = "m.login.bsspeke-ecc.verify" - const val TYPE_PARAM_KEY = "type" - const val LOGIN_PASSWORD_USER_ID_TYPE = "m.id.user" - } -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/base/LoginStageNavigationEvent.kt b/auth/src/main/java/org/futo/circles/auth/base/LoginStageNavigationEvent.kt deleted file mode 100644 index 6824fab6b8bc40991dc0bddbcc433b6a438be29b..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/base/LoginStageNavigationEvent.kt +++ /dev/null @@ -1,3 +0,0 @@ -package org.futo.circles.auth.base - -enum class LoginStageNavigationEvent { Password, Terms, DirectPassword, BSspekeLogin, BSspekeSignup } \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/base/PasswordDataSource.kt b/auth/src/main/java/org/futo/circles/auth/base/PasswordDataSource.kt deleted file mode 100644 index c4bcf8c20c3baf6d100d8fa35e7e3aa58cdfb751..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/base/PasswordDataSource.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.futo.circles.auth.base - -import org.futo.circles.core.extensions.Response - -interface PasswordDataSource { - suspend fun processPasswordStage(password: String): Response<Unit> -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/bsspeke/BSSpekeClientProvider.kt b/auth/src/main/java/org/futo/circles/auth/bsspeke/BSSpekeClientProvider.kt index ccdf8710b1306131b524279f2bf27b80aaef0b0a..c43391f0d6658f81e0cbb26bc6385d0bddf75e5e 100644 --- a/auth/src/main/java/org/futo/circles/auth/bsspeke/BSSpekeClientProvider.kt +++ b/auth/src/main/java/org/futo/circles/auth/bsspeke/BSSpekeClientProvider.kt @@ -11,11 +11,6 @@ object BSSpekeClientProvider { clientInstance = BSSpekeClient("@$userPart:$domain", domain, password) } - fun initClient(userId: String, password: String) { - val serverId = userId.substringAfter(":") - clientInstance = BSSpekeClient(userId, serverId, password) - } - fun clear() { clientInstance = null } diff --git a/auth/src/main/java/org/futo/circles/auth/di/AuthModule.kt b/auth/src/main/java/org/futo/circles/auth/di/AuthModule.kt deleted file mode 100644 index 455e6dc91ccde82d40f717fc8810da0fe7a3c969..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/di/AuthModule.kt +++ /dev/null @@ -1,61 +0,0 @@ -package org.futo.circles.auth.di - -import androidx.lifecycle.SavedStateHandle -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.components.ViewModelComponent -import dagger.hilt.android.scopes.ViewModelScoped -import org.futo.circles.auth.base.BaseLoginStagesDataSource -import org.futo.circles.auth.base.PasswordDataSource -import org.futo.circles.auth.feature.log_in.stages.password.DirectLoginPasswordDataSource -import org.futo.circles.auth.feature.log_in.stages.password.LoginBsSpekeDataSource -import org.futo.circles.auth.feature.log_in.stages.password.LoginPasswordDataSource -import org.futo.circles.auth.feature.sign_up.password.SignupBsSpekeDataSource -import org.futo.circles.auth.feature.sign_up.password.SignupPasswordDataSource -import org.futo.circles.auth.model.PasswordModeArg -import org.futo.circles.core.extensions.getOrThrow - -@Module -@InstallIn(ViewModelComponent::class) -object AuthModule { - - @Provides - @ViewModelScoped - fun providePasswordDataSource( - savedStateHandle: SavedStateHandle, - loginStagesDataSourceFactory: BaseLoginStagesDataSource.Factory, - loginBsSpekeStageDataSourceFactory: LoginBsSpekeDataSource.Factory, - directLoginPasswordDataSource: DirectLoginPasswordDataSource, - signupPasswordDataSource: SignupPasswordDataSource, - signupBsSpekeDataSource: SignupBsSpekeDataSource - ): PasswordDataSource = when (savedStateHandle.getOrThrow<PasswordModeArg>("mode")) { - PasswordModeArg.LoginPasswordStage -> LoginPasswordDataSource( - loginStagesDataSourceFactory.create(false) - ) - - PasswordModeArg.ReAuthPassword -> LoginPasswordDataSource( - loginStagesDataSourceFactory.create(true) - ) - - PasswordModeArg.LoginBsSpekeStage -> loginBsSpekeStageDataSourceFactory.create( - isReauth = false, - isChangePasswordEnroll = false - ) - - PasswordModeArg.ReAuthBsSpekeLogin -> loginBsSpekeStageDataSourceFactory.create( - isReauth = true, - isChangePasswordEnroll = false - ) - - PasswordModeArg.ReAuthBsSpekeSignup -> loginBsSpekeStageDataSourceFactory.create( - isReauth = true, - isChangePasswordEnroll = true - ) - - PasswordModeArg.LoginDirect -> directLoginPasswordDataSource - PasswordModeArg.SignupPasswordStage -> signupPasswordDataSource - PasswordModeArg.SignupBsSpekeStage -> signupBsSpekeDataSource - } - -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/active_sessions/ActiveSessionsDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/active_sessions/ActiveSessionsDataSource.kt index f2ab8ff523d7677734107364ac8b91b53995b9c9..74d62d6b1c5f08caa85608d4ade017639bf6edf6 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/active_sessions/ActiveSessionsDataSource.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/active_sessions/ActiveSessionsDataSource.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import org.futo.circles.auth.R -import org.futo.circles.auth.feature.reauth.AuthConfirmationProvider +import org.futo.circles.auth.feature.uia.flow.reauth.AuthConfirmationProvider import org.futo.circles.auth.model.ActiveSession import org.futo.circles.auth.model.ActiveSessionListItem import org.futo.circles.auth.model.SessionHeader diff --git a/auth/src/main/java/org/futo/circles/auth/feature/active_sessions/ActiveSessionsDialogFragment.kt b/auth/src/main/java/org/futo/circles/auth/feature/active_sessions/ActiveSessionsDialogFragment.kt index ffa3dd11c25bec100bb0ccb144f3446e137a713b..555bf16b8fe8464dd4abc0616a365ec421bd67fd 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/active_sessions/ActiveSessionsDialogFragment.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/active_sessions/ActiveSessionsDialogFragment.kt @@ -79,7 +79,7 @@ class ActiveSessionsDialogFragment : error = { showError(getString(R.string.invalid_auth)) } ) viewModel.startReAuthEventLiveData.observeData(this) { - findNavController().navigateSafe(ActiveSessionsDialogFragmentDirections.toReAuthStagesDialogFragment()) + findNavController().navigateSafe(ActiveSessionsDialogFragmentDirections.toUiaDialogFragment()) } } } \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/change_password/ChangePasswordDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/change_password/ChangePasswordDataSource.kt index 54c5f06323ca014024e79152404125429b825cf6..1c1d71ca9d6645336531a8d5f2f957ffd7be0421 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/change_password/ChangePasswordDataSource.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/change_password/ChangePasswordDataSource.kt @@ -3,7 +3,7 @@ package org.futo.circles.auth.feature.change_password import org.futo.circles.auth.bsspeke.BSSpekeClientProvider import org.futo.circles.auth.feature.pass_phrase.EncryptionAlgorithmHelper import org.futo.circles.auth.feature.pass_phrase.create.CreatePassPhraseDataSource -import org.futo.circles.auth.feature.reauth.AuthConfirmationProvider +import org.futo.circles.auth.feature.uia.flow.reauth.AuthConfirmationProvider import org.futo.circles.core.extensions.Response import org.futo.circles.core.extensions.createResult import org.futo.circles.core.provider.MatrixSessionProvider diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/LogInFragment.kt b/auth/src/main/java/org/futo/circles/auth/feature/log_in/LogInFragment.kt index de612add29a36b41e068288aa7b4dc946aca1d86..b0be875dfcee95d12dd05cb03b6eabfea703d734 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/log_in/LogInFragment.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/log_in/LogInFragment.kt @@ -15,6 +15,7 @@ import org.futo.circles.auth.R import org.futo.circles.auth.databinding.FragmentLogInBinding import org.futo.circles.auth.feature.log_in.switch_user.list.SwitchUsersAdapter import org.futo.circles.auth.feature.log_in.switch_user.list.SwitchUsersViewHolder +import org.futo.circles.auth.model.ForgotPassword import org.futo.circles.auth.model.RemoveUser import org.futo.circles.core.base.CirclesAppConfig import org.futo.circles.core.base.NetworkObserver @@ -98,7 +99,7 @@ class LogInFragment : Fragment(R.layout.fragment_log_in), HasLoadingState { NetworkObserver.observe(this) { setEnabledViews(it) } viewModel.loginResultLiveData.observeResponse(this, success = { - findNavController().navigateSafe(LogInFragmentDirections.toLoginStagesFragment()) + findNavController().navigateSafe(LogInFragmentDirections.toUiaFragment()) }, error = { showError(getString(R.string.username_not_found)) @@ -118,18 +119,23 @@ class LogInFragment : Fragment(R.layout.fragment_log_in), HasLoadingState { btnSignUp.setOnClickListener { findNavController().navigateSafe(LogInFragmentDirections.toSignUpFragment()) } - btnLogin.setOnClickListener { - val userName = binding.tilUserName.getText() - if (userName.isEmpty()) { - showError(getString(R.string.username_can_not_be_empty)) - return@setOnClickListener - } - startLoading(btnLogin) - viewModel.startLogInFlow(userName, getDomain()) + btnLogin.setOnClickListener { startLogin(false) } + btnForgotPassword.setOnClickListener { + withConfirmation(ForgotPassword()) { startLogin(true) } } } } + private fun startLogin(isForgotPassword: Boolean) { + val userName = binding.tilUserName.getText() + if (userName.isEmpty()) { + showError(getString(R.string.username_can_not_be_empty)) + return + } + startLoading(binding.btnLogin) + viewModel.startLogInFlow(userName, getDomain(), isForgotPassword) + } + private fun getDomain() = binding.tvDomain.text.toString().takeIf { it.isNotEmpty() } ?: CirclesAppConfig.serverDomains.first() } \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/LogInViewModel.kt b/auth/src/main/java/org/futo/circles/auth/feature/log_in/LogInViewModel.kt index 9c50802a2f9e2a727eadc3ab686d9dd0b91a9371..006376514579c6b6155eccb07c8b6d68bd1d57cb 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/log_in/LogInViewModel.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/log_in/LogInViewModel.kt @@ -21,10 +21,10 @@ class LogInViewModel @Inject constructor( val switchUsersLiveData = MutableLiveData(switchUserDataSource.getSwitchUsersList()) val navigateToBottomMenuScreenLiveData = SingleEventLiveData<Unit>() - fun startLogInFlow(userName: String, domain: String) { + fun startLogInFlow(userName: String, domain: String, isForgotPassword: Boolean) { switchUserDataSource.getSessionCredentialsIdByUserInfo(userName, domain) ?.let { resumeSwitchUserSession(it) } - ?: login(userName, domain) + ?: login(userName, domain, isForgotPassword) } fun removeSwitchUser(id: String) { @@ -43,9 +43,9 @@ class LogInViewModel @Inject constructor( } } - private fun login(userName: String, domain: String) { + private fun login(userName: String, domain: String, isForgotPassword: Boolean) { launchBg { - val loginResult = loginDataSource.startLogin(userName, domain) + val loginResult = loginDataSource.startLogin(userName, domain, isForgotPassword) loginResultLiveData.postValue(loginResult) } } diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/LoginDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/log_in/LoginDataSource.kt index decc0120ddd640a542156b46c10cd5c6ce953282..6e3a96ef5480e4e5f60b4fa7350841ab56884b7c 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/log_in/LoginDataSource.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/log_in/LoginDataSource.kt @@ -3,12 +3,18 @@ package org.futo.circles.auth.feature.log_in import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import org.futo.circles.auth.R -import org.futo.circles.auth.base.BaseLoginStagesDataSource.Companion.DIRECT_LOGIN_PASSWORD_TYPE -import org.futo.circles.auth.base.BaseLoginStagesDataSource.Companion.LOGIN_PASSWORD_TYPE -import org.futo.circles.auth.base.BaseLoginStagesDataSource.Companion.LOGIN_PASSWORD_USER_ID_TYPE -import org.futo.circles.auth.base.BaseLoginStagesDataSource.Companion.TYPE_PARAM_KEY -import org.futo.circles.auth.base.BaseLoginStagesDataSource.Companion.USER_PARAM_KEY -import org.futo.circles.auth.feature.log_in.stages.LoginStagesDataSource +import org.futo.circles.auth.feature.uia.UIADataSource +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.DIRECT_LOGIN_PASSWORD_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.ENROLL_BSSPEKE_SAVE_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.ENROLL_EMAIL_SUBMIT_TOKEN_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.LOGIN_BSSPEKE_VERIFY_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.LOGIN_EMAIL_SUBMIT_TOKEN_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.LOGIN_PASSWORD_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.LOGIN_PASSWORD_USER_ID_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.TYPE_PARAM_KEY +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.USER_PARAM_KEY +import org.futo.circles.auth.feature.uia.UIADataSourceProvider +import org.futo.circles.auth.model.UIAFlowType import org.futo.circles.core.extensions.createResult import org.futo.circles.core.provider.MatrixInstanceProvider import org.futo.circles.core.utils.HomeServerUtils.buildHomeServerConfigFromDomain @@ -17,42 +23,76 @@ import javax.inject.Inject class LoginDataSource @Inject constructor( @ApplicationContext private val context: Context, - private val loginStagesDataSource: LoginStagesDataSource + private val uiaFactory: UIADataSource.Factory ) { + private val authService by lazy { MatrixInstanceProvider.matrix.authenticationService() } suspend fun startLogin( userName: String, - domain: String + domain: String, + isForgotPassword: Boolean ) = createResult { authService.cancelPendingLoginOrRegistration() - val stages = prepareLoginStages(userName, domain) - loginStagesDataSource.startLoginStages(stages, userName, domain) + val stages = prepareLoginStages(userName, domain, isForgotPassword) + val uiaDataSource = UIADataSourceProvider.create( + if (isForgotPassword) UIAFlowType.ForgotPassword else UIAFlowType.Login, + uiaFactory + ) + uiaDataSource.startUIAStages(stages, domain, userName) } private suspend fun prepareLoginStages( userName: String, - domain: String + domain: String, + isForgotPassword: Boolean ): List<Stage> { val homeServerConfig = buildHomeServerConfigFromDomain(domain) val supportedLoginMethods = authService.getLoginFlow(homeServerConfig).supportedLoginTypes - return if (isPasswordLogin(supportedLoginMethods)) - listOf(Stage.Other(true, DIRECT_LOGIN_PASSWORD_TYPE, null)) - else getCircleLoginStages(userName, domain) + return if (isPasswordLogin(supportedLoginMethods)) { + if (isForgotPassword) throw IllegalArgumentException("Forgot password is only available for Circles domains") + else listOf(Stage.Other(true, DIRECT_LOGIN_PASSWORD_TYPE, null)) + } else getCircleStages(userName, domain, isForgotPassword) } private fun isPasswordLogin(methods: List<String>) = methods.contains(LOGIN_PASSWORD_TYPE) - private suspend fun getCircleLoginStages(userName: String, domain: String): List<Stage> { + private suspend fun getCircleStages( + userName: String, + domain: String, + isForgotPassword: Boolean + ): List<Stage> { val identifierParams = mapOf( USER_PARAM_KEY to "@$userName:$domain", TYPE_PARAM_KEY to LOGIN_PASSWORD_USER_ID_TYPE ) val flows = authService.getLoginWizard() .getAllLoginFlows(identifierParams, context.getString(R.string.initial_device_name)) - return flows.firstOrNull() + + val stages = if (isForgotPassword) getCircleStagesForForgotPassword(flows) + else getCircleStagesForLogin(flows) + + return stages ?: throw IllegalArgumentException(context.getString(R.string.unsupported_login_method)) } + + private fun getCircleStagesForLogin(flows: List<List<Stage>>): List<Stage>? = + flows.firstOrNull { stages -> + stages.firstOrNull { stage -> + (stage as? Stage.Other)?.type == LOGIN_BSSPEKE_VERIFY_TYPE + } != null + } + + private fun getCircleStagesForForgotPassword(flows: List<List<Stage>>): List<Stage>? = + flows.firstOrNull { stages -> + val containsEmailStage = stages.firstOrNull { stage -> + (stage as? Stage.Other)?.type == LOGIN_EMAIL_SUBMIT_TOKEN_TYPE + } != null + val containsSetPassword = stages.firstOrNull { stage -> + (stage as? Stage.Other)?.type == ENROLL_BSSPEKE_SAVE_TYPE + } != null + containsEmailStage && containsSetPassword + } } \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/log_out/LogoutDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/log_in/log_out/LogoutDataSource.kt deleted file mode 100644 index 95ce658c1636f8e22e635f765ca84d1b08645504..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/feature/log_in/log_out/LogoutDataSource.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.futo.circles.auth.feature.log_in.log_out - -import org.futo.circles.core.extensions.createResult -import org.futo.circles.core.provider.MatrixSessionProvider -import javax.inject.Inject - -class LogoutDataSource @Inject constructor() { - - suspend fun logOut() = createResult { - MatrixSessionProvider.getSessionOrThrow().signOutService().signOut(true) - } - -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/LogInStagesFragment.kt b/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/LogInStagesFragment.kt deleted file mode 100644 index 610ea348f09646a916b739aa2de0805cef7b74e8..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/LogInStagesFragment.kt +++ /dev/null @@ -1,148 +0,0 @@ -package org.futo.circles.auth.feature.log_in.stages - -import android.net.Uri -import android.os.Bundle -import android.view.View -import androidx.activity.OnBackPressedCallback -import androidx.activity.result.contract.ActivityResultContracts -import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels -import androidx.navigation.findNavController -import androidx.navigation.fragment.NavHostFragment -import androidx.navigation.fragment.findNavController -import by.kirich1409.viewbindingdelegate.viewBinding -import dagger.hilt.android.AndroidEntryPoint -import org.futo.circles.auth.R -import org.futo.circles.auth.base.LoginStageNavigationEvent -import org.futo.circles.auth.databinding.FragmentLoginStagesBinding -import org.futo.circles.auth.feature.log_in.recovery.EnterPassPhraseDialog -import org.futo.circles.auth.feature.log_in.recovery.EnterPassPhraseDialogListener -import org.futo.circles.core.base.NetworkObserver -import org.futo.circles.core.extensions.navigateSafe -import org.futo.circles.core.extensions.observeData -import org.futo.circles.core.extensions.observeResponse -import org.futo.circles.core.extensions.onBackPressed -import org.futo.circles.core.extensions.setEnabledViews -import org.futo.circles.core.extensions.showDialog -import org.futo.circles.core.extensions.showError -import org.futo.circles.core.base.fragment.BackPressOwner -import org.futo.circles.core.view.LoadingDialog - -@AndroidEntryPoint -class LogInStagesFragment : Fragment(R.layout.fragment_login_stages), - BackPressOwner { - - private val viewModel by viewModels<LoginStagesViewModel>() - private val binding by viewBinding(FragmentLoginStagesBinding::bind) - - private val loadingDialog by lazy { LoadingDialog(requireContext()) } - private var enterPassPhraseDialog: EnterPassPhraseDialog? = null - - private val deviceIntentLauncher = registerForActivityResult( - ActivityResultContracts.GetContent() - ) { uri -> - uri ?: return@registerForActivityResult - enterPassPhraseDialog?.selectFile(uri) - } - - private val childNavHostFragment by lazy { - childFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding.toolbar.setNavigationOnClickListener { onBackPressed() } - binding.toolbar.title = getString(R.string.log_in) - setupObservers() - } - - private fun setupObservers() { - NetworkObserver.observe(this) { setEnabledViews(it) } - viewModel.loginStageNavigationLiveData.observeData(this) { event -> - val id = when (event) { - LoginStageNavigationEvent.DirectPassword -> R.id.to_direct_login - LoginStageNavigationEvent.Password -> R.id.to_password - LoginStageNavigationEvent.Terms -> R.id.to_acceptTerms - LoginStageNavigationEvent.BSspekeLogin -> R.id.to_bsspeke - else -> throw IllegalArgumentException(getString(R.string.not_supported_navigation_event)) - } - binding.navHostFragment.findNavController().navigateSafe(id) - } - viewModel.subtitleLiveData.observeData(this) { - binding.toolbar.subtitle = it - } - viewModel.restoreKeysLiveData.observeResponse( - this, - error = { - showError(it) - loadingDialog.dismiss() - } - ) - viewModel.passPhraseLoadingLiveData.observeData(this) { - loadingDialog.handleLoading(it) - } - viewModel.loginNavigationLiveData.observeData(this) { event -> - when (event) { - LoginNavigationEvent.Main -> navigateToHome() - LoginNavigationEvent.PassPhrase -> showPassPhraseDialog() - else -> navigateToHome() - } - } - viewModel.messageEventLiveData.observeData(this) { messageId -> - showError(requireContext().getString(messageId)) - } - } - - private fun showPassPhraseDialog() { - enterPassPhraseDialog = - EnterPassPhraseDialog(requireContext(), object : EnterPassPhraseDialogListener { - override fun onRestoreBackupWithPassphrase(passphrase: String) { - viewModel.restoreBackupWithPassPhrase(passphrase) - } - - override fun onRestoreBackupWithRawKey(key: String) { - viewModel.restoreBackupWithRawKey(key) - } - - override fun onRestoreBackup(uri: Uri) { - viewModel.restoreBackup(uri) - } - - override fun onDoNotRestore() { - viewModel.onDoNotRestoreBackup() - } - - override fun onSelectFileClicked() { - deviceIntentLauncher.launch(recoveryKeyMimeType) - } - }).apply { - setOnDismissListener { enterPassPhraseDialog = null } - show() - } - } - - private fun navigateToHome() { - findNavController().navigateSafe(LogInStagesFragmentDirections.toHomeFragment()) - } - - private fun showDiscardDialog() { - showDialog( - titleResIdRes = R.string.discard_current_login_progress, - negativeButtonVisible = true, - positiveAction = { findNavController().popBackStack() }) - } - - override fun onChildBackPress(callback: OnBackPressedCallback) { - val includedFragmentsManager = childNavHostFragment.childFragmentManager - if (includedFragmentsManager.backStackEntryCount == 0) { - callback.remove() - onBackPressed() - } else { - showDiscardDialog() - } - } - - companion object { - private const val recoveryKeyMimeType = "text/plain" - } -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/LoginStagesDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/LoginStagesDataSource.kt deleted file mode 100644 index c163b972f8c28f80bc11ce80015db0e1c5ffe464..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/LoginStagesDataSource.kt +++ /dev/null @@ -1,130 +0,0 @@ -package org.futo.circles.auth.feature.log_in.stages - -import android.content.Context -import android.net.Uri -import dagger.hilt.android.qualifiers.ApplicationContext -import org.futo.circles.auth.R -import org.futo.circles.auth.base.BaseLoginStagesDataSource -import org.futo.circles.auth.bsspeke.BSSpekeClientProvider -import org.futo.circles.auth.feature.pass_phrase.EncryptionAlgorithmHelper -import org.futo.circles.auth.feature.pass_phrase.create.CreatePassPhraseDataSource -import org.futo.circles.auth.feature.pass_phrase.restore.RestoreBackupDataSource -import org.futo.circles.auth.feature.token.RefreshTokenManager -import org.futo.circles.core.base.SingleEventLiveData -import org.futo.circles.core.extensions.Response -import org.futo.circles.core.extensions.createResult -import org.futo.circles.core.model.LoadingData -import org.futo.circles.core.provider.MatrixInstanceProvider -import org.futo.circles.core.provider.MatrixSessionProvider -import org.matrix.android.sdk.api.auth.registration.RegistrationResult -import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.util.JsonDict -import javax.inject.Inject -import javax.inject.Singleton - -enum class LoginNavigationEvent { Main, PassPhrase } - -@Singleton -class LoginStagesDataSource @Inject constructor( - @ApplicationContext private val context: Context, - private val restoreBackupDataSource: RestoreBackupDataSource, - private val encryptionAlgorithmHelper: EncryptionAlgorithmHelper, - private val createPassPhraseDataSource: CreatePassPhraseDataSource, - private val refreshTokenManager: RefreshTokenManager -) : BaseLoginStagesDataSource(context) { - - val loginNavigationLiveData = SingleEventLiveData<LoginNavigationEvent>() - val passPhraseLoadingLiveData = restoreBackupDataSource.loadingLiveData - val messageEventLiveData = SingleEventLiveData<Int>() - - override suspend fun performLoginStage( - authParams: JsonDict, - password: String? - ): Response<RegistrationResult> { - val wizard = MatrixInstanceProvider.matrix.authenticationService().getLoginWizard() - val result = createResult { - wizard.loginStageCustom( - authParams, - getIdentifier(), - context.getString(R.string.initial_device_name), - true - ) - } - (result as? Response.Success)?.let { stageCompleted(result.data, password) } - return result - } - - suspend fun stageCompleted(result: RegistrationResult, password: String?) { - if (isStageRetry(result)) return - password?.let { userPassword = it } - (result as? RegistrationResult.Success)?.let { - finishLogin(it.session) - } ?: navigateToNextStage() - } - - private suspend fun finishLogin(session: Session) { - passPhraseLoadingLiveData.postValue( - LoadingData(messageId = R.string.initial_sync, isLoading = true) - ) - MatrixSessionProvider.awaitForSessionSync(session) - passPhraseLoadingLiveData.postValue(LoadingData(isLoading = false)) - refreshTokenManager.scheduleTokenRefreshIfNeeded(session) - handleKeysBackup() - BSSpekeClientProvider.clear() - } - - private suspend fun handleKeysBackup() { - if (encryptionAlgorithmHelper.isBcryptAlgorithm()) restoreAndMigrateBCrypt(userPassword) - else { - if (encryptionAlgorithmHelper.isBsSpekePassPhrase()) restoreBsSpekeBackup() - else loginNavigationLiveData.postValue(LoginNavigationEvent.PassPhrase) - } - } - - private suspend fun restoreBsSpekeBackup(): Response<Unit> { - val restoreResult = createResult { restoreBackupDataSource.restoreWithBsSpekeKey() } - return handleRestoreResult(restoreResult) - } - - private suspend fun restoreAndMigrateBCrypt(passphrase: String): Response<Unit> { - val restoreResult = createResult { - restoreBackupDataSource.restoreBcryptWithPassPhase(passphrase) - createPassPhraseDataSource.replaceToNewKeyBackup() - } - return handleRestoreResult(restoreResult) - } - - - suspend fun restoreBackupWithPassphrase(password: String): Response<Unit> { - val restoreResult = createResult { - restoreBackupDataSource.restoreKeysWithPassPhase(password) - } - return handleRestoreResult(restoreResult) - } - - suspend fun restoreBackupWithRawKey(rawKey: String): Response<Unit> { - val restoreResult = createResult { - restoreBackupDataSource.restoreKeysWithRawKey(rawKey) - } - return handleRestoreResult(restoreResult) - } - - suspend fun restoreBackup(uri: Uri): Response<Unit> { - val restoreResult = createResult { - restoreBackupDataSource.restoreKeysWithRecoveryKey(uri) - } - return handleRestoreResult(restoreResult) - } - - private fun handleRestoreResult(restoreResult: Response<Unit>): Response<Unit> { - when (restoreResult) { - is Response.Error -> loginNavigationLiveData.postValue(LoginNavigationEvent.PassPhrase) - is Response.Success -> navigateToMain() - } - return restoreResult - } - - fun navigateToMain() { - loginNavigationLiveData.postValue(LoginNavigationEvent.Main) - } -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/LoginStagesViewModel.kt b/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/LoginStagesViewModel.kt deleted file mode 100644 index c8790b38ba33ffb4d8625ebe3a7bf704b4cdf855..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/LoginStagesViewModel.kt +++ /dev/null @@ -1,47 +0,0 @@ -package org.futo.circles.auth.feature.log_in.stages - -import android.net.Uri -import androidx.lifecycle.ViewModel -import dagger.hilt.android.lifecycle.HiltViewModel -import org.futo.circles.core.base.SingleEventLiveData -import org.futo.circles.core.extensions.Response -import org.futo.circles.core.extensions.launchBg -import javax.inject.Inject - -@HiltViewModel -class LoginStagesViewModel @Inject constructor( - private val loginStagesDataSource: LoginStagesDataSource -) : ViewModel() { - - val subtitleLiveData = loginStagesDataSource.subtitleLiveData - val loginStageNavigationLiveData = loginStagesDataSource.loginStageNavigationLiveData - val restoreKeysLiveData = SingleEventLiveData<Response<Unit>>() - val loginNavigationLiveData = loginStagesDataSource.loginNavigationLiveData - val passPhraseLoadingLiveData = loginStagesDataSource.passPhraseLoadingLiveData - val messageEventLiveData = loginStagesDataSource.messageEventLiveData - - fun restoreBackupWithPassPhrase(passphrase: String) { - launchBg { - val result = loginStagesDataSource.restoreBackupWithPassphrase(passphrase) - restoreKeysLiveData.postValue(result) - } - } - - fun restoreBackupWithRawKey(rawKey: String) { - launchBg { - val result = loginStagesDataSource.restoreBackupWithRawKey(rawKey) - restoreKeysLiveData.postValue(result) - } - } - - fun restoreBackup(uri: Uri) { - launchBg { - restoreKeysLiveData.postValue(loginStagesDataSource.restoreBackup(uri)) - } - } - - fun onDoNotRestoreBackup() { - loginStagesDataSource.navigateToMain() - } - -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/password/DirectLoginPasswordDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/password/DirectLoginPasswordDataSource.kt deleted file mode 100644 index 84d76d1427de0ccd36c8f93c23ec3479755da647..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/password/DirectLoginPasswordDataSource.kt +++ /dev/null @@ -1,38 +0,0 @@ -package org.futo.circles.auth.feature.log_in.stages.password - -import android.content.Context -import dagger.hilt.android.qualifiers.ApplicationContext -import org.futo.circles.auth.R -import org.futo.circles.auth.base.PasswordDataSource -import org.futo.circles.core.extensions.Response -import org.futo.circles.core.extensions.createResult -import org.futo.circles.auth.feature.log_in.stages.LoginStagesDataSource -import org.futo.circles.core.provider.MatrixInstanceProvider -import org.matrix.android.sdk.api.auth.registration.RegistrationResult -import javax.inject.Inject - -class DirectLoginPasswordDataSource @Inject constructor( - @ApplicationContext private val context: Context, - private val loginStagesDataSource: LoginStagesDataSource -) : PasswordDataSource { - - override suspend fun processPasswordStage(password: String): Response<Unit> { - val result = createResult { - MatrixInstanceProvider.matrix.authenticationService().getLoginWizard().login( - login = loginStagesDataSource.userName, - password = password, - initialDeviceName = context.getString(R.string.initial_device_name) - ) - } - return when (result) { - is Response.Success -> { - loginStagesDataSource.stageCompleted( - RegistrationResult.Success(result.data), password - ) - Response.Success(Unit) - } - - is Response.Error -> result - } - } -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/password/LoginBsSpekeDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/password/LoginBsSpekeDataSource.kt deleted file mode 100644 index 3977d00b3523960ddedc8053145d477dbfd4d6a9..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/password/LoginBsSpekeDataSource.kt +++ /dev/null @@ -1,41 +0,0 @@ -package org.futo.circles.auth.feature.log_in.stages.password - -import android.content.Context -import dagger.hilt.android.qualifiers.ApplicationContext -import org.futo.circles.auth.base.BaseBsSpekeStageDataSource -import org.futo.circles.auth.base.BaseLoginStagesDataSource -import org.futo.circles.core.extensions.Response -import org.matrix.android.sdk.api.auth.registration.RegistrationResult -import org.matrix.android.sdk.api.auth.registration.Stage -import org.matrix.android.sdk.api.util.JsonDict -import javax.inject.Inject - -class LoginBsSpekeDataSource( - context: Context, - private val isChangePasswordEnroll: Boolean, - private val loginStagesDataSource: BaseLoginStagesDataSource -) : BaseBsSpekeStageDataSource(context) { - - class Factory @Inject constructor( - @ApplicationContext private val context: Context, - private val loginStagesDataSourceFactory: BaseLoginStagesDataSource.Factory - ) { - fun create(isReauth: Boolean, isChangePasswordEnroll: Boolean): LoginBsSpekeDataSource = - LoginBsSpekeDataSource( - context, - isChangePasswordEnroll, - loginStagesDataSourceFactory.create(isReauth) - ) - } - - override val userName: String get() = loginStagesDataSource.userName - override val domain: String get() = loginStagesDataSource.domain - override val isLoginMode: Boolean get() = !isChangePasswordEnroll - override fun getStages(): List<Stage> = loginStagesDataSource.stagesToComplete - - override suspend fun performAuthStage( - authParams: JsonDict, - password: String? - ): Response<RegistrationResult> = loginStagesDataSource.performLoginStage(authParams, password) - -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/password/LoginPasswordDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/password/LoginPasswordDataSource.kt deleted file mode 100644 index 6b194816fe362622374dc87e448a26a4ef130673..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/password/LoginPasswordDataSource.kt +++ /dev/null @@ -1,29 +0,0 @@ -package org.futo.circles.auth.feature.log_in.stages.password - -import org.futo.circles.auth.base.BaseLoginStagesDataSource -import org.futo.circles.auth.base.BaseLoginStagesDataSource.Companion.LOGIN_PASSWORD_TYPE -import org.futo.circles.auth.base.BaseLoginStagesDataSource.Companion.TYPE_PARAM_KEY -import org.futo.circles.auth.base.PasswordDataSource -import org.futo.circles.core.extensions.Response - -class LoginPasswordDataSource( - private val loginStagesDataSource: BaseLoginStagesDataSource -) : PasswordDataSource { - - override suspend fun processPasswordStage(password: String): Response<Unit> { - val result = loginStagesDataSource.performLoginStage( - mapOf( - TYPE_PARAM_KEY to LOGIN_PASSWORD_TYPE, - PASSWORD_PARAM_KEY to password - ), password - ) - return when (result) { - is Response.Success -> Response.Success(Unit) - is Response.Error -> result - } - } - - companion object { - const val PASSWORD_PARAM_KEY = "password" - } -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/terms/LoginAcceptTermsDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/terms/LoginAcceptTermsDataSource.kt deleted file mode 100644 index f3949b9fc441de476ca67a89b95531e8f6706fe6..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/terms/LoginAcceptTermsDataSource.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.futo.circles.auth.feature.log_in.stages.terms - -import org.futo.circles.auth.base.BaseAcceptTermsDataSource -import org.futo.circles.auth.base.BaseLoginStagesDataSource -import org.futo.circles.auth.base.BaseLoginStagesDataSource.Companion.TYPE_PARAM_KEY -import org.futo.circles.auth.extensions.toTermsListItems -import org.futo.circles.core.extensions.Response -import org.matrix.android.sdk.api.auth.data.LoginFlowTypes -import org.matrix.android.sdk.api.auth.registration.Stage -import javax.inject.Inject - -class LoginAcceptTermsDataSource @Inject constructor( - private val loginStagesDataSource: BaseLoginStagesDataSource -) : BaseAcceptTermsDataSource() { - - override suspend fun acceptTerms(): Response<Unit> { - val result = loginStagesDataSource.performLoginStage( - mapOf(TYPE_PARAM_KEY to LoginFlowTypes.TERMS) - ) - return when (result) { - is Response.Success -> Response.Success(Unit) - is Response.Error -> result - } - } - - override fun getTermsList() = - (loginStagesDataSource.currentStage as? Stage.Terms)?.policies?.toTermsListItems() - ?: emptyList() - -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialog.kt b/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/recovery/EnterPassPhraseDialog.kt similarity index 98% rename from auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialog.kt rename to auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/recovery/EnterPassPhraseDialog.kt index 25f0035fe5be9bd6fd5cb3bd76fc11cd0d443acd..de00621cff4dc6f8493242b9408d763d5efddc75 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialog.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/recovery/EnterPassPhraseDialog.kt @@ -1,4 +1,4 @@ -package org.futo.circles.auth.feature.log_in.recovery +package org.futo.circles.auth.feature.pass_phrase.recovery import android.app.ActionBar import android.content.Context diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialogListener.kt b/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/recovery/EnterPassPhraseDialogListener.kt similarity index 81% rename from auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialogListener.kt rename to auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/recovery/EnterPassPhraseDialogListener.kt index 9c750f9129879994768ece2c415816522a3d4f13..f8407df944e16138403cfcbcd874f396df64a22b 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialogListener.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/recovery/EnterPassPhraseDialogListener.kt @@ -1,4 +1,4 @@ -package org.futo.circles.auth.feature.log_in.recovery +package org.futo.circles.auth.feature.pass_phrase.recovery import android.net.Uri diff --git a/auth/src/main/java/org/futo/circles/auth/feature/reauth/ReAuthStageViewModel.kt b/auth/src/main/java/org/futo/circles/auth/feature/reauth/ReAuthStageViewModel.kt deleted file mode 100644 index 5761913b75eccb53ee233439f6080d600a8e723d..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/feature/reauth/ReAuthStageViewModel.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.futo.circles.auth.feature.reauth - -import androidx.lifecycle.ViewModel -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject - -@HiltViewModel -class ReAuthStageViewModel @Inject constructor(reAuthStagesDataSource: ReAuthStagesDataSource) : ViewModel() { - - val subtitleLiveData = reAuthStagesDataSource.subtitleLiveData - val loginStageNavigationLiveData = reAuthStagesDataSource.loginStageNavigationLiveData - val finishReAuthEventLiveData = reAuthStagesDataSource.finishReAuthEventLiveData -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/reauth/ReAuthStagesDialogFragment.kt b/auth/src/main/java/org/futo/circles/auth/feature/reauth/ReAuthStagesDialogFragment.kt deleted file mode 100644 index 2c6508f37530dd9b88aadbd468d8e2c84e7b2384..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/feature/reauth/ReAuthStagesDialogFragment.kt +++ /dev/null @@ -1,106 +0,0 @@ -package org.futo.circles.auth.feature.reauth - -import android.app.Dialog -import android.os.Bundle -import android.view.View -import androidx.activity.OnBackPressedCallback -import androidx.fragment.app.viewModels -import androidx.navigation.findNavController -import androidx.navigation.fragment.NavHostFragment -import androidx.navigation.fragment.findNavController -import dagger.hilt.android.AndroidEntryPoint -import org.futo.circles.auth.R -import org.futo.circles.auth.base.LoginStageNavigationEvent -import org.futo.circles.auth.databinding.FragmentLoginStagesBinding -import org.futo.circles.core.base.fragment.BackPressOwner -import org.futo.circles.core.base.fragment.BaseFullscreenDialogFragment -import org.futo.circles.core.extensions.navigateSafe -import org.futo.circles.core.extensions.observeData -import org.futo.circles.core.extensions.onBackPressed -import org.futo.circles.core.extensions.showDialog - -@AndroidEntryPoint -class ReAuthStagesDialogFragment : - BaseFullscreenDialogFragment(FragmentLoginStagesBinding::inflate), BackPressOwner { - - private val viewModel by viewModels<ReAuthStageViewModel>() - - private val binding by lazy { - getBinding() as FragmentLoginStagesBinding - } - - private val childNavHostFragment by lazy { - childFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment - } - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = - object : Dialog(requireContext(), theme) { - @Suppress("OVERRIDE_DEPRECATION") - override fun onBackPressed() { - activity?.onBackPressedDispatcher?.onBackPressed() - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding.toolbar.apply { - title = getString(R.string.confirm_auth) - setNavigationOnClickListener { handleBackAction() } - } - setupObservers() - } - - private fun setupObservers() { - viewModel.loginStageNavigationLiveData.observeData(this) { event -> - val id = when (event) { - LoginStageNavigationEvent.DirectPassword -> R.id.to_direct_login - LoginStageNavigationEvent.Password -> R.id.to_reAuthPassword - LoginStageNavigationEvent.Terms -> R.id.to_ReAuthAcceptTerms - LoginStageNavigationEvent.BSspekeLogin -> R.id.to_reAuthBsSpekeLogin - LoginStageNavigationEvent.BSspekeSignup -> R.id.to_reAuthBsSpekeSignup - else -> throw IllegalArgumentException(getString(R.string.not_supported_navigation_event)) - } - binding.navHostFragment.findNavController().navigateSafe(id) - } - viewModel.subtitleLiveData.observeData(this) { - binding.toolbar.subtitle = it - } - viewModel.finishReAuthEventLiveData.observeData(this) { - dismiss() - } - } - - private fun showDiscardDialog() { - showDialog( - titleResIdRes = R.string.discard_current_login_progress, - negativeButtonVisible = true, - positiveAction = { - cancelReAuth() - findNavController().popBackStack() - } - ) - } - - override fun onChildBackPress(callback: OnBackPressedCallback) { - handleBackAction(callback) - } - - private fun handleBackAction(callback: OnBackPressedCallback? = null) { - val includedFragmentsManager = childNavHostFragment.childFragmentManager - if (includedFragmentsManager.backStackEntryCount == 1) { - cancelReAuth() - callback?.remove() - onBackPressed() - } else { - showDiscardDialog() - } - } - - private fun cancelReAuth() { - parentFragment?.childFragmentManager?.fragments?.forEach { - (it as? ReAuthCancellationListener)?.onReAuthCanceled() - } - - } - -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/SignUpDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/sign_up/SignUpDataSource.kt index b7319fdbbd1f5719ebaeeb5d9a0fc549ce0ae33c..a123c8f8d91c845efd14c62cf40e2e539142b0f5 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/SignUpDataSource.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/sign_up/SignUpDataSource.kt @@ -1,162 +1,66 @@ package org.futo.circles.auth.feature.sign_up import android.content.Context -import androidx.lifecycle.MutableLiveData import dagger.hilt.android.qualifiers.ApplicationContext import org.futo.circles.auth.R -import org.futo.circles.auth.base.BaseLoginStagesDataSource -import org.futo.circles.auth.bsspeke.BSSpekeClientProvider -import org.futo.circles.auth.feature.pass_phrase.create.CreatePassPhraseDataSource -import org.futo.circles.core.base.SingleEventLiveData -import org.futo.circles.core.extensions.Response +import org.futo.circles.auth.feature.uia.UIADataSource +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.SUBSCRIPTION_FREE_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.SUBSCRPTION_GOOGLE_TYPE +import org.futo.circles.auth.feature.uia.UIADataSourceProvider +import org.futo.circles.auth.model.UIAFlowType +import org.futo.circles.core.base.CirclesAppConfig import org.futo.circles.core.extensions.createResult import org.futo.circles.core.provider.MatrixInstanceProvider -import org.futo.circles.core.provider.MatrixSessionProvider -import org.futo.circles.core.provider.PreferencesProvider -import org.matrix.android.sdk.api.auth.registration.RegistrationResult +import org.futo.circles.core.utils.HomeServerUtils.buildHomeServerConfigFromDomain import org.matrix.android.sdk.api.auth.registration.Stage -import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.util.JsonDict import javax.inject.Inject -import javax.inject.Singleton -enum class SignUpNavigationEvents { TokenValidation, Subscription, AcceptTerm, ValidateEmail, Password, BSspeke, Username } - -@Singleton class SignUpDataSource @Inject constructor( @ApplicationContext private val context: Context, - private val createPassPhraseDataSource: CreatePassPhraseDataSource, - private val preferencesProvider: PreferencesProvider + private val uiaFactory: UIADataSource.Factory ) { - val subtitleLiveData = MutableLiveData<String>() - val navigationLiveData = SingleEventLiveData<SignUpNavigationEvents>() - val finishRegistrationLiveData = SingleEventLiveData<Response<Unit>>() - val passPhraseLoadingLiveData = createPassPhraseDataSource.loadingLiveData - - val stagesToComplete = mutableListOf<Stage>() - - var currentStage: Stage? = null - private set - - var userName: String = "" - private set - var domain: String = "" - private set - - suspend fun startSignUpStages( - stages: List<Stage>, - serverDomain: String - ) { - currentStage = null - stagesToComplete.clear() - domain = serverDomain - stagesToComplete.addAll(stages) - navigateToNextStage() - } + private var registrationFlowsForDomain: Pair<String, List<List<Stage>>>? = null - suspend fun performRegistrationStage( - authParams: JsonDict, - name: String? = null - ): Response<RegistrationResult> { - val wizard = MatrixInstanceProvider.matrix.authenticationService().getRegistrationWizard() - val result = createResult { - wizard.registrationCustom( - authParams, - context.getString(R.string.initial_device_name), - true - ) + suspend fun getAuthFlowsFor(domain: String) = createResult { + registrationFlowsForDomain = null + val authService = MatrixInstanceProvider.matrix.authenticationService().apply { + cancelPendingLoginOrRegistration() + initiateAuth(buildHomeServerConfigFromDomain(domain)) } - - (result as? Response.Success)?.let { - name?.let { userName = it } - stageCompleted(result.data) + authService.getRegistrationWizard().getAllRegistrationFlows().also { + registrationFlowsForDomain = domain to it } - return result - } - - private suspend fun stageCompleted(result: RegistrationResult?) { - if (isStageRetry(result)) return - (result as? RegistrationResult.Success)?.let { - finishRegistrationLiveData.postValue(finishRegistration(it.session)) - } ?: navigateToNextStage() } - fun clearSubtitle() { - subtitleLiveData.postValue("") - } - - private fun isStageRetry(result: RegistrationResult?): Boolean { - val nextStageType = - ((result as? RegistrationResult.FlowResponse)?.flowResult?.missingStages?.firstOrNull() as? Stage.Other)?.type - return nextStageType == (currentStage as? Stage.Other)?.type && nextStageType != null - } - - private suspend fun finishRegistration(session: Session) = createResult { - MatrixInstanceProvider.matrix.authenticationService().reset() - MatrixSessionProvider.awaitForSessionStart(session) - preferencesProvider.setShouldShowAllExplanations() - createPassPhraseDataSource.createPassPhraseBackup() - BSSpekeClientProvider.clear() - } + suspend fun startNewRegistration(isSubscription: Boolean) = createResult { + val (domain, flows) = registrationFlowsForDomain ?: throw IllegalArgumentException( + context.getString(R.string.wrong_signup_config) + ) + val stages = if (isSubscription) getSubscriptionSignupStages(flows) + else getFreeSignupStages(flows) - private fun getCurrentStageIndex() = - stagesToComplete.indexOf(currentStage).takeIf { it != -1 } ?: 0 + stages ?: throw IllegalArgumentException(context.getString(R.string.wrong_signup_config)) - private fun setNextStage() { - currentStage = currentStage?.let { - stagesToComplete.getOrNull(getCurrentStageIndex() + 1) - } ?: stagesToComplete.firstOrNull() + val uiaDataSource = UIADataSourceProvider.create(UIAFlowType.Signup, uiaFactory) + uiaDataSource.startUIAStages(stages, domain) } - private suspend fun navigateToNextStage() { - setNextStage() - val event = when (val stage = currentStage) { - is Stage.Terms -> SignUpNavigationEvents.AcceptTerm - is Stage.Other -> handleStageOther(stage.type) - else -> throw IllegalArgumentException( - context.getString(R.string.not_supported_stage_format, stage.toString()) - ) - } - event?.let { navigationLiveData.postValue(it) } - updatePageSubtitle() + // Must contain org.futo.subscriptions.free_forever + fun getFreeSignupStages(flows: List<List<Stage>>): List<Stage>? = flows.firstOrNull { stages -> + stages.firstOrNull { stage -> + (stage as? Stage.Other)?.type == SUBSCRIPTION_FREE_TYPE + } != null } - private suspend fun handleStageOther(type: String): SignUpNavigationEvents? = when (type) { - REGISTRATION_FREE_TYPE -> { - performRegistrationStage(mapOf(BaseLoginStagesDataSource.TYPE_PARAM_KEY to REGISTRATION_FREE_TYPE)) - null - } + // Must contain org.futo.subscription.google_play, available only for gPlay flavor + fun getSubscriptionSignupStages(flows: List<List<Stage>>): List<Stage>? = + if (CirclesAppConfig.isGplayFlavor()) { + flows.firstOrNull { stages -> + stages.firstOrNull { stage -> + (stage as? Stage.Other)?.type == SUBSCRPTION_GOOGLE_TYPE + } != null + } + } else null - REGISTRATION_TOKEN_TYPE -> SignUpNavigationEvents.TokenValidation - REGISTRATION_SUBSCRIPTION_TYPE -> SignUpNavigationEvents.Subscription - REGISTRATION_EMAIL_REQUEST_TOKEN_TYPE -> SignUpNavigationEvents.ValidateEmail - REGISTRATION_EMAIL_SUBMIT_TOKEN_TYPE -> null - REGISTRATION_USERNAME_TYPE -> SignUpNavigationEvents.Username - REGISTRATION_PASSWORD_TYPE -> SignUpNavigationEvents.Password - REGISTRATION_BSSPEKE_OPRF_TYPE -> SignUpNavigationEvents.BSspeke - REGISTRATION_BSSPEKE_SAVE_TYPE -> null - else -> throw IllegalArgumentException( - context.getString(R.string.not_supported_stage_format, type) - ) - } - - private fun updatePageSubtitle() { - val size = stagesToComplete.size - val number = getCurrentStageIndex() + 1 - val subtitle = context.getString(R.string.sign_up_stage_subtitle_format, number, size) - subtitleLiveData.postValue(subtitle) - } - - companion object { - const val REGISTRATION_FREE_TYPE = "org.futo.subscriptions.free_forever" - const val REGISTRATION_TOKEN_TYPE = "m.login.registration_token" - const val REGISTRATION_SUBSCRIPTION_TYPE = "org.futo.subscriptions.google_play" - const val REGISTRATION_EMAIL_REQUEST_TOKEN_TYPE = "m.enroll.email.request_token" - const val REGISTRATION_EMAIL_SUBMIT_TOKEN_TYPE = "m.enroll.email.submit_token" - const val REGISTRATION_USERNAME_TYPE = "m.enroll.username" - const val REGISTRATION_PASSWORD_TYPE = "m.enroll.password" - const val REGISTRATION_BSSPEKE_OPRF_TYPE = "m.enroll.bsspeke-ecc.oprf" - const val REGISTRATION_BSSPEKE_SAVE_TYPE = "m.enroll.bsspeke-ecc.save" - } } \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/SignUpFragment.kt b/auth/src/main/java/org/futo/circles/auth/feature/sign_up/SignUpFragment.kt index 109d41325e342f245b40afc1654fb5ab8e645702..94e737616cfce75af621df96dbed4ce056fdb4b2 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/SignUpFragment.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/sign_up/SignUpFragment.kt @@ -2,102 +2,98 @@ package org.futo.circles.auth.feature.sign_up import android.os.Bundle import android.view.View -import androidx.activity.OnBackPressedCallback +import android.widget.RadioButton +import androidx.core.view.children import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels -import androidx.navigation.findNavController -import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.findNavController import by.kirich1409.viewbindingdelegate.viewBinding +import com.google.android.material.radiobutton.MaterialRadioButton import dagger.hilt.android.AndroidEntryPoint import org.futo.circles.auth.R import org.futo.circles.auth.databinding.FragmentSignUpBinding -import org.futo.circles.core.base.NetworkObserver +import org.futo.circles.core.base.CirclesAppConfig +import org.futo.circles.core.base.fragment.HasLoadingState +import org.futo.circles.core.extensions.gone import org.futo.circles.core.extensions.navigateSafe -import org.futo.circles.core.extensions.observeData import org.futo.circles.core.extensions.observeResponse -import org.futo.circles.core.extensions.onBackPressed -import org.futo.circles.core.extensions.setEnabledViews -import org.futo.circles.core.extensions.showDialog +import org.futo.circles.core.extensions.setIsVisible import org.futo.circles.core.extensions.showError -import org.futo.circles.core.base.fragment.BackPressOwner -import org.futo.circles.core.view.LoadingDialog @AndroidEntryPoint class SignUpFragment : Fragment(R.layout.fragment_sign_up), - BackPressOwner { + HasLoadingState { - private val viewModel by viewModels<SignUpViewModel>() - private val binding by viewBinding(FragmentSignUpBinding::bind) - private val loadingDialog by lazy { LoadingDialog(requireContext()) } + override val fragment: Fragment = this - private val childNavHostFragment by lazy { - childFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment - } + private val binding by viewBinding(FragmentSignUpBinding::bind) + private val viewModel by viewModels<SignUpViewModel>() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - binding.toolbar.setNavigationOnClickListener { onBackPressed() } + setupViews() setupObservers() } - private fun setupObservers() { - NetworkObserver.observe(this){ setEnabledViews(it) } - viewModel.subtitleLiveData.observeData(this) { - binding.toolbar.subtitle = it - } - viewModel.navigationLiveData.observeData(this) { - handleNavigation(it) - } - viewModel.finishRegistrationLiveData.observeResponse(this, - success = { navigateToConfigureWorkspace() }, - error = { message -> - showError(message) - loadingDialog.dismiss() + private fun setupViews() { + with(binding) { + serverDomainGroup.setOnCheckedChangeListener { _, _ -> + setFlowsLoading(true) + viewModel.loadSignupFlowsForDomain(getDomain()) + } + CirclesAppConfig.serverDomains.forEach { domain -> + serverDomainGroup.addView( + MaterialRadioButton(requireContext()).apply { + text = domain + textSize = 20f + } + ) + } + (serverDomainGroup.children.first() as? RadioButton)?.toggle() + btnSubscription.setOnClickListener { + startLoading(btnSubscription) + viewModel.startSignUp(true) + } + btnFree.setOnClickListener { + startLoading(btnFree) + viewModel.startSignUp(false) } - ) - viewModel.passPhraseLoadingLiveData.observeData(this) { - loadingDialog.handleLoading(it) } } + private fun setupObservers() { + viewModel.startSignUpEventLiveData.observeResponse( + this, + success = { findNavController().navigateSafe(SignUpFragmentDirections.toUiaFragment()) } + ) - private fun handleNavigation(event: SignUpNavigationEvents) { - val directionId = when (event) { - SignUpNavigationEvents.TokenValidation -> R.id.to_validateToken - SignUpNavigationEvents.Subscription -> R.id.to_subscriptions - SignUpNavigationEvents.AcceptTerm -> R.id.to_acceptTerms - SignUpNavigationEvents.ValidateEmail -> R.id.to_validateEmail - SignUpNavigationEvents.Password -> R.id.to_password - SignUpNavigationEvents.BSspeke -> R.id.to_bsspeke - SignUpNavigationEvents.Username -> R.id.to_username - } - binding.navHostFragment.findNavController().navigateSafe(directionId) - } - - private fun showDiscardDialog() { - showDialog( - titleResIdRes = R.string.discard_current_registration_progress, - negativeButtonVisible = true, - positiveAction = { - childNavHostFragment.navController.popBackStack( - R.id.selectSignUpTypeFragment, false - ) - }) + viewModel.signupFlowsLiveData.observeResponse(this, + success = { + val hasSubscriptionFlow = viewModel.hasSubscriptionFlow(it) + val hasFreeFlow = viewModel.hasFreeFlow(it) + with(binding) { + btnSubscription.setIsVisible(hasSubscriptionFlow) + btnFree.setIsVisible(hasFreeFlow) + tvOr.setIsVisible(hasFreeFlow && hasSubscriptionFlow) + } + }, + error = { message -> + showError(message) + binding.lButtonsContainer.gone() + }, + onRequestInvoked = { setFlowsLoading(false) } + ) } - override fun onChildBackPress(callback: OnBackPressedCallback) { - val includedFragmentsManager = childNavHostFragment.childFragmentManager - if (includedFragmentsManager.backStackEntryCount == 1) { - callback.remove() - onBackPressed() - } else { - showDiscardDialog() + private fun setFlowsLoading(isLoading: Boolean) { + with(binding) { + lButtonsContainer.setIsVisible(!isLoading) + flowProgress.setIsVisible(isLoading) } } - private fun navigateToConfigureWorkspace() { - findNavController().navigateSafe(SignUpFragmentDirections.toConfigureWorkspace()) - } + private fun getDomain() = + binding.serverDomainGroup + .findViewById<MaterialRadioButton>(binding.serverDomainGroup.checkedRadioButtonId).text.toString() } \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/SignUpViewModel.kt b/auth/src/main/java/org/futo/circles/auth/feature/sign_up/SignUpViewModel.kt index 039bc2140d3cb7fd01c5e9e704fb3e3cc87efc02..0c23ed8a0a043b2830e32e36e1c10f877bb52b15 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/SignUpViewModel.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/sign_up/SignUpViewModel.kt @@ -2,16 +2,38 @@ package org.futo.circles.auth.feature.sign_up import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel +import org.futo.circles.core.base.SingleEventLiveData +import org.futo.circles.core.extensions.Response +import org.futo.circles.core.extensions.launchBg +import org.matrix.android.sdk.api.auth.registration.Stage import javax.inject.Inject @HiltViewModel class SignUpViewModel @Inject constructor( - datasource: SignUpDataSource + private val dataSource: SignUpDataSource ) : ViewModel() { - val subtitleLiveData = datasource.subtitleLiveData - val passPhraseLoadingLiveData = datasource.passPhraseLoadingLiveData - val finishRegistrationLiveData = datasource.finishRegistrationLiveData - val navigationLiveData = datasource.navigationLiveData + val startSignUpEventLiveData = SingleEventLiveData<Response<Unit?>>() + val signupFlowsLiveData = SingleEventLiveData<Response<List<List<Stage>>>>() + + fun startSignUp(isSubscription: Boolean) { + launchBg { + val result = dataSource.startNewRegistration(isSubscription) + startSignUpEventLiveData.postValue(result) + } + } + + fun loadSignupFlowsForDomain(domain: String) { + launchBg { + val result = dataSource.getAuthFlowsFor(domain) + signupFlowsLiveData.postValue(result) + } + } + + fun hasSubscriptionFlow(flows: List<List<Stage>>): Boolean = + dataSource.getSubscriptionSignupStages(flows) != null + + fun hasFreeFlow(flows: List<List<Stage>>): Boolean = + dataSource.getFreeSignupStages(flows) != null } \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/password/SignupBsSpekeDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/sign_up/password/SignupBsSpekeDataSource.kt deleted file mode 100644 index 808b059e9089ef4cd381e6869e395353e6f5fa23..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/password/SignupBsSpekeDataSource.kt +++ /dev/null @@ -1,27 +0,0 @@ -package org.futo.circles.auth.feature.sign_up.password - -import android.content.Context -import dagger.hilt.android.qualifiers.ApplicationContext -import org.futo.circles.auth.base.BaseBsSpekeStageDataSource -import org.futo.circles.auth.feature.sign_up.SignUpDataSource -import org.futo.circles.core.extensions.Response -import org.matrix.android.sdk.api.auth.registration.RegistrationResult -import org.matrix.android.sdk.api.auth.registration.Stage -import org.matrix.android.sdk.api.util.JsonDict -import javax.inject.Inject - -class SignupBsSpekeDataSource @Inject constructor( - @ApplicationContext context: Context, - private val signUpDataSource: SignUpDataSource -) : BaseBsSpekeStageDataSource(context) { - - override val userName: String get() = signUpDataSource.userName - override val domain: String get() = signUpDataSource.domain - override val isLoginMode: Boolean get() = false - override fun getStages(): List<Stage> = signUpDataSource.stagesToComplete - - override suspend fun performAuthStage( - authParams: JsonDict, - password: String? - ): Response<RegistrationResult> = signUpDataSource.performRegistrationStage(authParams = authParams) -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/password/SignupPasswordDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/sign_up/password/SignupPasswordDataSource.kt deleted file mode 100644 index 8f94a793ca309c3ce6e007538b03a8d7eecde36d..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/password/SignupPasswordDataSource.kt +++ /dev/null @@ -1,28 +0,0 @@ -package org.futo.circles.auth.feature.sign_up.password - -import org.futo.circles.auth.base.BaseLoginStagesDataSource.Companion.TYPE_PARAM_KEY -import org.futo.circles.auth.base.PasswordDataSource -import org.futo.circles.auth.feature.sign_up.SignUpDataSource -import org.futo.circles.auth.feature.sign_up.SignUpDataSource.Companion.REGISTRATION_PASSWORD_TYPE -import org.futo.circles.core.extensions.Response -import javax.inject.Inject - -class SignupPasswordDataSource @Inject constructor( - private val signUpDataSource: SignUpDataSource -) : PasswordDataSource { - - override suspend fun processPasswordStage(password: String): Response<Unit> = - when (val result = signUpDataSource.performRegistrationStage( - mapOf( - TYPE_PARAM_KEY to REGISTRATION_PASSWORD_TYPE, - PASSWORD_PARAM_KEY to password - ) - )) { - is Response.Success -> Response.Success(Unit) - is Response.Error -> result - } - - companion object { - private const val PASSWORD_PARAM_KEY = "new_password" - } -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/sign_up_type/SelectSignUpTypeDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/sign_up/sign_up_type/SelectSignUpTypeDataSource.kt deleted file mode 100644 index 938a45db2087e51a2de9cf1f3cf01cdc0fb53c5e..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/sign_up_type/SelectSignUpTypeDataSource.kt +++ /dev/null @@ -1,67 +0,0 @@ -package org.futo.circles.auth.feature.sign_up.sign_up_type - -import android.content.Context -import dagger.hilt.android.qualifiers.ApplicationContext -import org.futo.circles.auth.R -import org.futo.circles.auth.feature.sign_up.SignUpDataSource -import org.futo.circles.auth.feature.sign_up.SignUpDataSource.Companion.REGISTRATION_FREE_TYPE -import org.futo.circles.auth.feature.sign_up.SignUpDataSource.Companion.REGISTRATION_SUBSCRIPTION_TYPE -import org.futo.circles.core.base.CirclesAppConfig -import org.futo.circles.core.extensions.createResult -import org.futo.circles.core.provider.MatrixInstanceProvider -import org.futo.circles.core.utils.HomeServerUtils.buildHomeServerConfigFromDomain -import org.matrix.android.sdk.api.auth.registration.Stage -import javax.inject.Inject - -class SelectSignUpTypeDataSource @Inject constructor( - @ApplicationContext private val context: Context, - private val signUpDataSource: SignUpDataSource -) { - - private var registrationFlowsForDomain: Pair<String, List<List<Stage>>>? = null - - fun clearSubtitle() { - signUpDataSource.clearSubtitle() - } - - suspend fun getAuthFlowsFor(domain: String) = createResult { - registrationFlowsForDomain = null - val authService = MatrixInstanceProvider.matrix.authenticationService().apply { - cancelPendingLoginOrRegistration() - initiateAuth(buildHomeServerConfigFromDomain(domain)) - } - authService.getRegistrationWizard().getAllRegistrationFlows().also { - registrationFlowsForDomain = domain to it - } - } - - suspend fun startNewRegistration(isSubscription: Boolean) = createResult { - val (domain, flows) = registrationFlowsForDomain ?: throw IllegalArgumentException( - context.getString(R.string.wrong_signup_config) - ) - val stages = if (isSubscription) getSubscriptionSignupStages(flows) - else getFreeSignupStages(flows) - - stages ?: throw IllegalArgumentException(context.getString(R.string.wrong_signup_config)) - - signUpDataSource.startSignUpStages(stages, domain) - } - - // Must contain org.futo.subscriptions.free_forever - fun getFreeSignupStages(flows: List<List<Stage>>): List<Stage>? = flows.firstOrNull { stages -> - stages.firstOrNull { stage -> - (stage as? Stage.Other)?.type == REGISTRATION_FREE_TYPE - } != null - } - - // Must contain org.futo.subscription.google_play, available only for gPlay flavor - fun getSubscriptionSignupStages(flows: List<List<Stage>>): List<Stage>? = - if (CirclesAppConfig.isGplayFlavor()) { - flows.firstOrNull { stages -> - stages.firstOrNull { stage -> - (stage as? Stage.Other)?.type == REGISTRATION_SUBSCRIPTION_TYPE - } != null - } - } else null - -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/sign_up_type/SelectSignUpTypeFragment.kt b/auth/src/main/java/org/futo/circles/auth/feature/sign_up/sign_up_type/SelectSignUpTypeFragment.kt deleted file mode 100644 index 7c1ca617f958d823a1d579ec0285233bd8257585..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/sign_up_type/SelectSignUpTypeFragment.kt +++ /dev/null @@ -1,93 +0,0 @@ -package org.futo.circles.auth.feature.sign_up.sign_up_type - -import android.os.Bundle -import android.view.View -import androidx.core.view.children -import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels -import by.kirich1409.viewbindingdelegate.viewBinding -import com.google.android.material.radiobutton.MaterialRadioButton -import dagger.hilt.android.AndroidEntryPoint -import org.futo.circles.auth.R -import org.futo.circles.auth.databinding.FragmentSelectSignUpTypeBinding -import org.futo.circles.core.base.CirclesAppConfig -import org.futo.circles.core.base.fragment.HasLoadingState -import org.futo.circles.core.extensions.gone -import org.futo.circles.core.extensions.observeResponse -import org.futo.circles.core.extensions.setIsVisible -import org.futo.circles.core.extensions.showError - -@AndroidEntryPoint -class SelectSignUpTypeFragment : Fragment(R.layout.fragment_select_sign_up_type), - HasLoadingState { - - override val fragment: Fragment = this - - private val binding by viewBinding(FragmentSelectSignUpTypeBinding::bind) - private val viewModel by viewModels<SelectSignUpTypeViewModel>() - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - viewModel.clearSubtitle() - setupViews() - setupObservers() - } - - private fun setupViews() { - with(binding) { - serverDomainGroup.setOnCheckedChangeListener { _, _ -> - setFlowsLoading(true) - viewModel.loadSignupFlowsForDomain(getDomain()) - } - CirclesAppConfig.serverDomains.forEach { domain -> - serverDomainGroup.addView( - MaterialRadioButton(requireContext()).apply { - text = domain - textSize = 20f - } - ) - } - serverDomainGroup.check(serverDomainGroup.children.first().id) - btnSubscription.setOnClickListener { - startLoading(btnSubscription) - viewModel.startSignUp(true) - } - btnFree.setOnClickListener { - startLoading(btnFree) - viewModel.startSignUp(false) - } - } - } - - private fun setupObservers() { - viewModel.startSignUpEventLiveData.observeResponse(this) - viewModel.signupFlowsLiveData.observeResponse(this, - success = { - val hasSubscriptionFlow = viewModel.hasSubscriptionFlow(it) - val hasFreeFlow = viewModel.hasFreeFlow(it) - with(binding) { - btnSubscription.setIsVisible(hasSubscriptionFlow) - btnFree.setIsVisible(hasFreeFlow) - tvOr.setIsVisible(hasFreeFlow && hasSubscriptionFlow) - } - }, - error = { message -> - showError(message) - binding.lButtonsContainer.gone() - }, - onRequestInvoked = { setFlowsLoading(false) } - ) - } - - private fun setFlowsLoading(isLoading: Boolean) { - with(binding) { - lButtonsContainer.setIsVisible(!isLoading) - flowProgress.setIsVisible(isLoading) - } - } - - private fun getDomain() = - binding.serverDomainGroup - .findViewById<MaterialRadioButton>(binding.serverDomainGroup.checkedRadioButtonId).text.toString() - -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/sign_up_type/SelectSignUpTypeViewModel.kt b/auth/src/main/java/org/futo/circles/auth/feature/sign_up/sign_up_type/SelectSignUpTypeViewModel.kt deleted file mode 100644 index 677f8727cb2fc6f241b355066bfe02e445b74472..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/sign_up_type/SelectSignUpTypeViewModel.kt +++ /dev/null @@ -1,43 +0,0 @@ -package org.futo.circles.auth.feature.sign_up.sign_up_type - -import androidx.lifecycle.ViewModel -import dagger.hilt.android.lifecycle.HiltViewModel -import org.futo.circles.core.base.SingleEventLiveData -import org.futo.circles.core.extensions.Response -import org.futo.circles.core.extensions.launchBg -import org.matrix.android.sdk.api.auth.registration.Stage -import javax.inject.Inject - -@HiltViewModel -class SelectSignUpTypeViewModel @Inject constructor( - private val dataSource: SelectSignUpTypeDataSource -) : ViewModel() { - - val startSignUpEventLiveData = SingleEventLiveData<Response<Unit?>>() - val signupFlowsLiveData = SingleEventLiveData<Response<List<List<Stage>>>>() - - fun startSignUp(isSubscription: Boolean) { - launchBg { - val result = dataSource.startNewRegistration(isSubscription) - startSignUpEventLiveData.postValue(result) - } - } - - fun clearSubtitle() { - dataSource.clearSubtitle() - } - - fun loadSignupFlowsForDomain(domain: String) { - launchBg { - val result = dataSource.getAuthFlowsFor(domain) - signupFlowsLiveData.postValue(result) - } - } - - fun hasSubscriptionFlow(flows: List<List<Stage>>): Boolean = - dataSource.getSubscriptionSignupStages(flows) != null - - fun hasFreeFlow(flows: List<List<Stage>>): Boolean = - dataSource.getFreeSignupStages(flows) != null - -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/terms/SignupAcceptTermsDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/sign_up/terms/SignupAcceptTermsDataSource.kt deleted file mode 100644 index f4206731de46fef77712f1d6c84779bae1513571..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/terms/SignupAcceptTermsDataSource.kt +++ /dev/null @@ -1,28 +0,0 @@ -package org.futo.circles.auth.feature.sign_up.terms - - -import org.futo.circles.auth.base.BaseAcceptTermsDataSource -import org.futo.circles.auth.base.BaseLoginStagesDataSource.Companion.TYPE_PARAM_KEY -import org.futo.circles.auth.extensions.toTermsListItems -import org.futo.circles.auth.feature.sign_up.SignUpDataSource -import org.futo.circles.core.extensions.Response -import org.matrix.android.sdk.api.auth.data.LoginFlowTypes -import org.matrix.android.sdk.api.auth.registration.Stage -import javax.inject.Inject - -class SignupAcceptTermsDataSource @Inject constructor( - private val signUpDataSource: SignUpDataSource -) : BaseAcceptTermsDataSource() { - - override suspend fun acceptTerms(): Response<Unit> = - when (val result = signUpDataSource.performRegistrationStage( - mapOf(TYPE_PARAM_KEY to LoginFlowTypes.TERMS) - )) { - is Response.Success -> Response.Success(Unit) - is Response.Error -> result - } - - override fun getTermsList() = - (signUpDataSource.currentStage as? Stage.Terms)?.policies?.toTermsListItems() ?: emptyList() - -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/username/UsernameDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/sign_up/username/UsernameDataSource.kt deleted file mode 100644 index bf64130222077532aa3ced8140c307748663cd4f..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/username/UsernameDataSource.kt +++ /dev/null @@ -1,28 +0,0 @@ -package org.futo.circles.auth.feature.sign_up.username - -import androidx.lifecycle.MutableLiveData -import org.futo.circles.auth.base.BaseLoginStagesDataSource.Companion.TYPE_PARAM_KEY -import org.futo.circles.auth.feature.sign_up.SignUpDataSource -import org.futo.circles.auth.feature.sign_up.SignUpDataSource.Companion.REGISTRATION_USERNAME_TYPE -import org.futo.circles.core.extensions.Response -import org.matrix.android.sdk.api.auth.registration.RegistrationResult -import javax.inject.Inject - -class UsernameDataSource @Inject constructor( - private val signUpDataSource: SignUpDataSource -) { - - val domainLiveData = MutableLiveData(signUpDataSource.domain) - - suspend fun processUsernameStage(username: String): Response<RegistrationResult> = - signUpDataSource.performRegistrationStage( - mapOf( - TYPE_PARAM_KEY to REGISTRATION_USERNAME_TYPE, - USERNAME_PARAM_KEY to username - ), name = username - ) - - companion object { - private const val USERNAME_PARAM_KEY = "username" - } -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/validate_email/ValidateEmailDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/sign_up/validate_email/ValidateEmailDataSource.kt deleted file mode 100644 index 0ae5902e3a558d69cc1f9b39de75ce6b13951aac..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/validate_email/ValidateEmailDataSource.kt +++ /dev/null @@ -1,45 +0,0 @@ -package org.futo.circles.auth.feature.sign_up.validate_email - -import org.futo.circles.auth.base.BaseLoginStagesDataSource.Companion.TYPE_PARAM_KEY -import org.futo.circles.auth.feature.sign_up.SignUpDataSource -import org.futo.circles.auth.feature.sign_up.SignUpDataSource.Companion.REGISTRATION_EMAIL_REQUEST_TOKEN_TYPE -import org.futo.circles.auth.feature.sign_up.SignUpDataSource.Companion.REGISTRATION_EMAIL_SUBMIT_TOKEN_TYPE -import org.futo.circles.core.extensions.Response -import org.matrix.android.sdk.api.auth.registration.RegistrationResult -import org.matrix.android.sdk.api.auth.registration.Stage -import javax.inject.Inject - -class ValidateEmailDataSource @Inject constructor( - private val signUpDataSource: SignUpDataSource -) { - - suspend fun sendValidationCode( - email: String, - subscribeToUpdates: Boolean - ): Response<RegistrationResult> = signUpDataSource.performRegistrationStage( - mapOf( - TYPE_PARAM_KEY to REGISTRATION_EMAIL_REQUEST_TOKEN_TYPE, - EMAIL_PARAM_KEY to email, - SUBSCRIBE_TO_LIST to subscribeToUpdates - ) - ) - - suspend fun validateEmail(code: String): Response<RegistrationResult> = - signUpDataSource.performRegistrationStage( - mapOf( - TYPE_PARAM_KEY to REGISTRATION_EMAIL_SUBMIT_TOKEN_TYPE, - TOKEN_PARAM_KEY to code - ) - ) - - fun shouldShowSubscribeToEmail(): Boolean = - (signUpDataSource.currentStage as? Stage.Other)?.params?.get(OFFER_LIST_SUBSCRIPTION_KEY) as? Boolean - ?: false - - companion object { - private const val EMAIL_PARAM_KEY = "email" - private const val TOKEN_PARAM_KEY = "token" - private const val SUBSCRIBE_TO_LIST = "subscribe_to_list" - private const val OFFER_LIST_SUBSCRIPTION_KEY = "offer_list_subscription" - } -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/validate_token/ValidateTokenDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/sign_up/validate_token/ValidateTokenDataSource.kt deleted file mode 100644 index d77b10fd4a72868fe0f67176d8ef96183b8f749f..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/validate_token/ValidateTokenDataSource.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.futo.circles.auth.feature.sign_up.validate_token - -import org.futo.circles.auth.base.BaseLoginStagesDataSource.Companion.TYPE_PARAM_KEY -import org.futo.circles.auth.feature.sign_up.SignUpDataSource -import org.futo.circles.auth.feature.sign_up.SignUpDataSource.Companion.REGISTRATION_TOKEN_TYPE -import org.futo.circles.core.extensions.Response -import org.matrix.android.sdk.api.auth.registration.RegistrationResult -import javax.inject.Inject - -class ValidateTokenDataSource @Inject constructor( - private val signUpDataSource: SignUpDataSource -) { - - suspend fun validateToken(token: String): Response<RegistrationResult> = - signUpDataSource.performRegistrationStage( - mapOf( - TYPE_PARAM_KEY to REGISTRATION_TOKEN_TYPE, - TOKEN_PARAM_KEY to token - ) - ) - - companion object { - private const val TOKEN_PARAM_KEY = "token" - } -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/uia/UIADataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/UIADataSource.kt new file mode 100644 index 0000000000000000000000000000000000000000..01a620e786cc656a7e39611d8fac2e55bf0cef0a --- /dev/null +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/UIADataSource.kt @@ -0,0 +1,185 @@ +package org.futo.circles.auth.feature.uia + +import androidx.lifecycle.MutableLiveData +import org.futo.circles.auth.feature.uia.flow.LoginStagesDataSource +import org.futo.circles.auth.feature.uia.flow.SignUpStagesDataSource +import org.futo.circles.auth.feature.uia.flow.reauth.ReAuthStagesDataSource +import org.futo.circles.auth.model.UIAFlowType +import org.futo.circles.auth.model.UIANavigationEvent +import org.futo.circles.core.base.SingleEventLiveData +import org.futo.circles.core.extensions.Response +import org.matrix.android.sdk.api.auth.UIABaseAuth +import org.matrix.android.sdk.api.auth.data.LoginFlowTypes +import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse +import org.matrix.android.sdk.api.auth.registration.RegistrationResult +import org.matrix.android.sdk.api.auth.registration.Stage +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.util.JsonDict +import javax.inject.Inject +import kotlin.coroutines.Continuation + +abstract class UIADataSource { + + class Factory @Inject constructor( + private val loginStagesDataSource: LoginStagesDataSource, + private val reAuthStagesDataSource: ReAuthStagesDataSource, + private val signUpStagesDataSource: SignUpStagesDataSource + ) { + fun create(flowType: UIAFlowType): UIADataSource = when (flowType) { + UIAFlowType.Login -> loginStagesDataSource + UIAFlowType.Signup -> signUpStagesDataSource + UIAFlowType.ReAuth -> reAuthStagesDataSource + UIAFlowType.ForgotPassword -> loginStagesDataSource + } + } + + + val subtitleLiveData = MutableLiveData<Pair<Int, Int>>() + val stagesNavigationLiveData = SingleEventLiveData<UIANavigationEvent>() + val finishUIAEventLiveData = SingleEventLiveData<Session>() + + val stagesToComplete = mutableListOf<Stage>() + var currentStage: Stage? = null + private set + var userName: String = "" + protected set + var domain: String = "" + private set + + + open suspend fun startUIAStages( + stages: List<Stage>, + serverDomain: String, + name: String? = null + ) { + currentStage = null + this.userName = name ?: "" + stagesToComplete.clear() + domain = serverDomain + stagesToComplete.addAll(stages) + navigateToNextStage() + } + + open suspend fun startUIAStages( + stages: List<Stage>, + session: String, + promise: Continuation<UIABaseAuth> + ) { + throw IllegalArgumentException("Override only for AuthConfirmation provider usage") + } + + abstract suspend fun performUIAStage( + authParams: JsonDict, + name: String? = null, + password: String? = null + ): Response<RegistrationResult> + + open fun onStageResult( + promise: Continuation<UIABaseAuth>, + flowResponse: RegistrationFlowResponse, + errCode: String? + ) { + throw IllegalArgumentException("Override only for AuthConfirmation provider usage") + } + + + suspend fun stageCompleted(result: RegistrationResult) { + if (isStageRetry(result)) return + (result as? RegistrationResult.Success)?.let { + finishUIAEventLiveData.postValue(it.session) + } ?: navigateToNextStage() + } + + fun getCurrentStageKey() = when (currentStage) { + is Stage.Other -> (currentStage as Stage.Other).type + else -> LoginFlowTypes.TERMS + } + + private fun isStageRetry(result: RegistrationResult?): Boolean { + val nextStageType = + ((result as? RegistrationResult.FlowResponse)?.flowResult?.missingStages?.firstOrNull() as? Stage.Other)?.type + return nextStageType == (currentStage as? Stage.Other)?.type && nextStageType != null + } + + private fun setNextStage() { + currentStage = currentStage?.let { + stagesToComplete.getOrNull(getCurrentStageIndex() + 1) + } ?: stagesToComplete.firstOrNull() + } + + private suspend fun navigateToNextStage() { + setNextStage() + val event = when (val stage = currentStage) { + is Stage.Terms -> UIANavigationEvent.AcceptTerm + is Stage.Other -> handleStageOther(stage.type) + else -> throw IllegalArgumentException("Not supported stage $stage") + } + event?.let { stagesNavigationLiveData.postValue(it) } + updatePageSubtitle() + } + + + private suspend fun handleStageOther(type: String): UIANavigationEvent? = when (type) { + SUBSCRIPTION_FREE_TYPE -> { + performUIAStage(mapOf(TYPE_PARAM_KEY to type)) + null + } + + LOGIN_REGISTRATION_TOKEN_TYPE -> UIANavigationEvent.TokenValidation + SUBSCRPTION_GOOGLE_TYPE -> UIANavigationEvent.Subscription + LOGIN_EMAIL_REQUEST_TOKEN_TYPE, + ENROLL_EMAIL_REQUEST_TOKEN_TYPE -> UIANavigationEvent.ValidateEmail + LOGIN_EMAIL_SUBMIT_TOKEN_TYPE, + ENROLL_EMAIL_SUBMIT_TOKEN_TYPE -> null //stay on same screen + ENROLL_USERNAME_TYPE -> UIANavigationEvent.Username + ENROLL_PASSWORD_TYPE, + LOGIN_PASSWORD_TYPE, + LOGIN_BSSPEKE_OPRF_TYPE, + DIRECT_LOGIN_PASSWORD_TYPE, + ENROLL_BSSPEKE_OPRF_TYPE -> UIANavigationEvent.Password + + ENROLL_BSSPEKE_SAVE_TYPE -> null + LOGIN_BSSPEKE_VERIFY_TYPE -> null + else -> throw IllegalArgumentException("Not supported stage $type") + + } + + private fun getCurrentStageIndex() = + stagesToComplete.indexOf(currentStage).takeIf { it != -1 } ?: 0 + + private fun updatePageSubtitle() { + val size = stagesToComplete.size + val number = getCurrentStageIndex() + 1 + subtitleLiveData.postValue(number to size) + } + + companion object { + //params + const val TYPE_PARAM_KEY = "type" + const val USER_PARAM_KEY = "user" + const val LOGIN_PASSWORD_USER_ID_TYPE = "m.id.user" + + //stages password + const val LOGIN_PASSWORD_TYPE = "m.login.password" + const val DIRECT_LOGIN_PASSWORD_TYPE = "m.login.password.direct" + const val LOGIN_BSSPEKE_OPRF_TYPE = "m.login.bsspeke-ecc.oprf" + const val LOGIN_BSSPEKE_VERIFY_TYPE = "m.login.bsspeke-ecc.verify" + const val ENROLL_BSSPEKE_OPRF_TYPE = "m.enroll.bsspeke-ecc.oprf" + const val ENROLL_BSSPEKE_SAVE_TYPE = "m.enroll.bsspeke-ecc.save" + + //stages subscription + const val SUBSCRIPTION_FREE_TYPE = "org.futo.subscriptions.free_forever" + const val SUBSCRPTION_GOOGLE_TYPE = "org.futo.subscriptions.google_play" + + //stages email + const val LOGIN_EMAIL_REQUEST_TOKEN_TYPE = "m.login.email.request_token" + const val LOGIN_EMAIL_SUBMIT_TOKEN_TYPE = "m.login.email.submit_token" + const val ENROLL_EMAIL_REQUEST_TOKEN_TYPE = "m.enroll.email.request_token" + const val ENROLL_EMAIL_SUBMIT_TOKEN_TYPE = "m.enroll.email.submit_token" + + const val LOGIN_REGISTRATION_TOKEN_TYPE = "m.login.registration_token" + const val ENROLL_USERNAME_TYPE = "m.enroll.username" + const val ENROLL_PASSWORD_TYPE = "m.enroll.password" + + } +} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/uia/UIADataSourceProvider.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/UIADataSourceProvider.kt new file mode 100644 index 0000000000000000000000000000000000000000..2f8aeebf322290131e9951710da25ffca56e8ce5 --- /dev/null +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/UIADataSourceProvider.kt @@ -0,0 +1,26 @@ +package org.futo.circles.auth.feature.uia + +import org.futo.circles.auth.model.UIAFlowType + + +object UIADataSourceProvider { + + private var activeFlowDataSource: UIADataSource? = null + + var activeFlowType: UIAFlowType? = null + private set + + fun getDataSourceOrThrow() = + activeFlowDataSource ?: throw IllegalArgumentException("Flow is not active") + + + fun create(flowType: UIAFlowType, factory: UIADataSource.Factory): UIADataSource { + activeFlowType = flowType + return factory.create(flowType).also { activeFlowDataSource = it } + } + + fun clear() { + activeFlowType = null + activeFlowDataSource = null + } +} diff --git a/auth/src/main/java/org/futo/circles/auth/feature/uia/UIADialogFragment.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/UIADialogFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..046a9860c977b263ada3858e2c18f79e69590149 --- /dev/null +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/UIADialogFragment.kt @@ -0,0 +1,220 @@ +package org.futo.circles.auth.feature.uia + +import android.app.Dialog +import android.content.DialogInterface +import android.net.Uri +import android.os.Bundle +import android.view.View +import androidx.activity.OnBackPressedCallback +import androidx.activity.result.contract.ActivityResultContracts +import androidx.fragment.app.viewModels +import androidx.navigation.findNavController +import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.fragment.findNavController +import dagger.hilt.android.AndroidEntryPoint +import org.futo.circles.auth.R +import org.futo.circles.auth.databinding.DialogFragmentUiaBinding +import org.futo.circles.auth.feature.pass_phrase.recovery.EnterPassPhraseDialog +import org.futo.circles.auth.feature.pass_phrase.recovery.EnterPassPhraseDialogListener +import org.futo.circles.auth.feature.uia.flow.reauth.ReAuthCancellationListener +import org.futo.circles.auth.model.AuthUIAScreenNavigationEvent +import org.futo.circles.auth.model.UIAFlowType +import org.futo.circles.auth.model.UIANavigationEvent +import org.futo.circles.core.base.NetworkObserver +import org.futo.circles.core.base.fragment.BackPressOwner +import org.futo.circles.core.base.fragment.BaseFullscreenDialogFragment +import org.futo.circles.core.extensions.navigateSafe +import org.futo.circles.core.extensions.observeData +import org.futo.circles.core.extensions.observeResponse +import org.futo.circles.core.extensions.onBackPressed +import org.futo.circles.core.extensions.setEnabledViews +import org.futo.circles.core.extensions.showDialog +import org.futo.circles.core.extensions.showError +import org.futo.circles.core.view.LoadingDialog + +@AndroidEntryPoint +class UIADialogFragment : + BaseFullscreenDialogFragment(DialogFragmentUiaBinding::inflate), BackPressOwner { + + private val viewModel by viewModels<UIAViewModel>() + + private val binding by lazy { + getBinding() as DialogFragmentUiaBinding + } + + private val loadingDialog by lazy { LoadingDialog(requireContext()) } + private var enterPassPhraseDialog: EnterPassPhraseDialog? = null + + private val deviceIntentLauncher = registerForActivityResult( + ActivityResultContracts.GetContent() + ) { uri -> + uri ?: return@registerForActivityResult + enterPassPhraseDialog?.selectFile(uri) + } + + private val childNavHostFragment by lazy { + childFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = + object : Dialog(requireContext(), theme) { + @Suppress("OVERRIDE_DEPRECATION") + override fun onBackPressed() { + activity?.onBackPressedDispatcher?.onBackPressed() + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupViews() + setupObservers() + } + + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + UIADataSourceProvider.clear() + } + + private fun setupViews() { + binding.toolbar.apply { + title = getString( + when (UIADataSourceProvider.activeFlowType) { + UIAFlowType.Login -> R.string.log_in + UIAFlowType.Signup -> R.string.sign_up + UIAFlowType.ReAuth -> R.string.confirm_auth + UIAFlowType.ForgotPassword -> R.string.forgot_password + else -> R.string.log_in + } + ) + setNavigationOnClickListener { handleBackAction() } + } + } + + private fun setupObservers() { + NetworkObserver.observe(this) { setEnabledViews(it) } + viewModel.stagesNavigationLiveData.observeData(this) { event -> + handleStagesNavigation(event) + } + viewModel.navigationLiveData.observeData(this) { event -> + handleScreenNavigation(event) + } + viewModel.subtitleLiveData.observeData(this) { (number, size) -> + binding.toolbar.subtitle = + getString(R.string.sign_up_stage_subtitle_format, number, size) + } + viewModel.restoreKeysLiveData.observeResponse( + this, + error = { + showError(it) + loadingDialog.dismiss() + } + ) + viewModel.passPhraseLoadingLiveData.observeData(this) { + loadingDialog.handleLoading(it) + } + viewModel.finishUIAEventLiveData.observeData(this) { + when (UIADataSourceProvider.activeFlowType) { + UIAFlowType.Login -> viewModel.finishLogin(it) + UIAFlowType.Signup -> viewModel.finishSignup(it) + UIAFlowType.ForgotPassword -> viewModel.finishForgotPassword(it) + else -> dismiss() + } + } + viewModel.createBackupResultLiveData.observeResponse(this, + error = { message -> + showError(message) + loadingDialog.dismiss() + } + ) + } + + private fun handleScreenNavigation(event: AuthUIAScreenNavigationEvent) { + when (event) { + AuthUIAScreenNavigationEvent.Home -> findNavController().navigateSafe( + UIADialogFragmentDirections.toHomeFragment() + ) + + AuthUIAScreenNavigationEvent.ConfigureWorkspace -> findNavController().navigateSafe( + UIADialogFragmentDirections.toConfigureWorkspace() + ) + + AuthUIAScreenNavigationEvent.PassPhrase -> showPassPhraseDialog() + } + } + + private fun handleStagesNavigation(event: UIANavigationEvent) { + val id = when (event) { + UIANavigationEvent.TokenValidation -> R.id.to_validateToken + UIANavigationEvent.Subscription -> R.id.to_subscriptions + UIANavigationEvent.AcceptTerm -> R.id.to_acceptTerms + UIANavigationEvent.ValidateEmail -> R.id.to_validateEmail + UIANavigationEvent.Password -> R.id.to_password + UIANavigationEvent.Username -> R.id.to_username + } + binding.navHostFragment.findNavController().navigateSafe(id) + } + + private fun showPassPhraseDialog() { + enterPassPhraseDialog = + EnterPassPhraseDialog(requireContext(), object : EnterPassPhraseDialogListener { + override fun onRestoreBackupWithPassphrase(passphrase: String) { + viewModel.restoreBackupWithPassPhrase(passphrase) + } + + override fun onRestoreBackupWithRawKey(key: String) { + viewModel.restoreBackupWithRawKey(key) + } + + override fun onRestoreBackup(uri: Uri) { + viewModel.restoreBackup(uri) + } + + override fun onDoNotRestore() { + viewModel.cancelRestore() + } + + override fun onSelectFileClicked() { + deviceIntentLauncher.launch(recoveryKeyMimeType) + } + }).apply { + setOnDismissListener { enterPassPhraseDialog = null } + show() + } + } + + private fun showDiscardDialog() { + showDialog( + titleResIdRes = R.string.discard_current_auth_progress, + negativeButtonVisible = true, + positiveAction = { + cancelReAuth() + findNavController().popBackStack() + } + ) + } + + override fun onChildBackPress(callback: OnBackPressedCallback) { + handleBackAction(callback) + } + + private fun handleBackAction(callback: OnBackPressedCallback? = null) { + val includedFragmentsManager = childNavHostFragment.childFragmentManager + if (includedFragmentsManager.backStackEntryCount == 1) { + cancelReAuth() + callback?.remove() + onBackPressed() + } else { + showDiscardDialog() + } + } + + private fun cancelReAuth() { + parentFragment?.childFragmentManager?.fragments?.forEach { + (it as? ReAuthCancellationListener)?.onReAuthCanceled() + } + } + + companion object { + private const val recoveryKeyMimeType = "text/plain" + } +} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/uia/UIAViewModel.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/UIAViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..a4bb317ce31055db6b3f5235ac5eaec83fccb152 --- /dev/null +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/UIAViewModel.kt @@ -0,0 +1,170 @@ +package org.futo.circles.auth.feature.uia + +import android.net.Uri +import androidx.lifecycle.LiveData +import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import org.futo.circles.auth.R +import org.futo.circles.auth.bsspeke.BSSpekeClientProvider +import org.futo.circles.auth.feature.pass_phrase.EncryptionAlgorithmHelper +import org.futo.circles.auth.feature.pass_phrase.create.CreatePassPhraseDataSource +import org.futo.circles.auth.feature.pass_phrase.restore.RestoreBackupDataSource +import org.futo.circles.auth.feature.token.RefreshTokenManager +import org.futo.circles.auth.feature.uia.flow.LoginStagesDataSource +import org.futo.circles.auth.model.AuthUIAScreenNavigationEvent +import org.futo.circles.core.base.SingleEventLiveData +import org.futo.circles.core.extensions.Response +import org.futo.circles.core.extensions.createResult +import org.futo.circles.core.extensions.launchBg +import org.futo.circles.core.model.LoadingData +import org.futo.circles.core.provider.MatrixInstanceProvider +import org.futo.circles.core.provider.MatrixSessionProvider +import org.futo.circles.core.provider.PreferencesProvider +import org.matrix.android.sdk.api.auth.data.sessionId +import org.matrix.android.sdk.api.session.Session +import javax.inject.Inject + +@HiltViewModel +class UIAViewModel @Inject constructor( + private val encryptionAlgorithmHelper: EncryptionAlgorithmHelper, + private val createPassPhraseDataSource: CreatePassPhraseDataSource, + private val restoreBackupDataSource: RestoreBackupDataSource, + private val refreshTokenManager: RefreshTokenManager, + private val preferencesProvider: PreferencesProvider +) : ViewModel() { + + val uiaDataSource = UIADataSourceProvider.getDataSourceOrThrow() + + val subtitleLiveData: LiveData<Pair<Int, Int>> = uiaDataSource.subtitleLiveData + val stagesNavigationLiveData = uiaDataSource.stagesNavigationLiveData + val navigationLiveData = SingleEventLiveData<AuthUIAScreenNavigationEvent>() + val restoreKeysLiveData = SingleEventLiveData<Response<Unit>>() + val passPhraseLoadingLiveData: MediatorLiveData<LoadingData> = + MediatorLiveData<LoadingData>().also { + it.addSource(restoreBackupDataSource.loadingLiveData) { value -> + it.postValue(value) + } + it.addSource(createPassPhraseDataSource.loadingLiveData) { value -> + it.postValue(value) + } + } + val finishUIAEventLiveData = uiaDataSource.finishUIAEventLiveData + val createBackupResultLiveData = SingleEventLiveData<Response<Unit>>() + + fun restoreBackupWithPassPhrase(passphrase: String) { + launchBg { + val restoreResult = createResult { + restoreBackupDataSource.restoreKeysWithPassPhase(passphrase) + } + handleRestoreResult(restoreResult) + } + } + + fun restoreBackupWithRawKey(rawKey: String) { + launchBg { + val restoreResult = createResult { + restoreBackupDataSource.restoreKeysWithRawKey(rawKey) + } + handleRestoreResult(restoreResult) + } + } + + fun restoreBackup(uri: Uri) { + launchBg { + val restoreResult = createResult { + restoreBackupDataSource.restoreKeysWithRecoveryKey(uri) + } + handleRestoreResult(restoreResult) + } + } + + fun finishLogin(session: Session) { + launchBg { + passPhraseLoadingLiveData.postValue( + LoadingData(messageId = R.string.initial_sync, isLoading = true) + ) + MatrixSessionProvider.awaitForSessionSync(session) + passPhraseLoadingLiveData.postValue(LoadingData(isLoading = false)) + refreshTokenManager.scheduleTokenRefreshIfNeeded(session) + handleKeysBackup() + BSSpekeClientProvider.clear() + } + } + + fun finishSignup(session: Session) { + launchBg { + val result = createResult { + MatrixInstanceProvider.matrix.authenticationService().reset() + MatrixSessionProvider.awaitForSessionStart(session) + preferencesProvider.setShouldShowAllExplanations() + createPassPhraseDataSource.createPassPhraseBackup() + BSSpekeClientProvider.clear() + } + (result as? Response.Success)?.let { + navigationLiveData.postValue(AuthUIAScreenNavigationEvent.ConfigureWorkspace) + } + createBackupResultLiveData.postValue(result) + } + } + + fun finishForgotPassword(session: Session) { + launchBg { + val result = createResult { + MatrixSessionProvider.awaitForSessionSync(session) + createPassPhraseDataSource.createPassPhraseBackup() + BSSpekeClientProvider.clear() + } + (result as? Response.Success)?.let { + navigationLiveData.postValue(AuthUIAScreenNavigationEvent.Home) + } + createBackupResultLiveData.postValue(result) + } + } + + private suspend fun handleKeysBackup() { + if (encryptionAlgorithmHelper.isBcryptAlgorithm()) { + (uiaDataSource as? LoginStagesDataSource)?.userPassword?.let { + restoreAndMigrateBCrypt(it) + } + } else { + if (encryptionAlgorithmHelper.isBsSpekePassPhrase()) restoreBsSpekeBackup() + else navigationLiveData.postValue(AuthUIAScreenNavigationEvent.PassPhrase) + } + } + + private suspend fun restoreBsSpekeBackup() { + val restoreResult = createResult { restoreBackupDataSource.restoreWithBsSpekeKey() } + handleRestoreResult(restoreResult) + } + + private suspend fun restoreAndMigrateBCrypt(passphrase: String) { + val restoreResult = createResult { + restoreBackupDataSource.restoreBcryptWithPassPhase(passphrase) + createPassPhraseDataSource.replaceToNewKeyBackup() + } + handleRestoreResult(restoreResult) + } + + private fun handleRestoreResult(restoreResult: Response<Unit>) { + when (restoreResult) { + is Response.Error -> { + restoreKeysLiveData.postValue(restoreResult) + navigationLiveData.postValue(AuthUIAScreenNavigationEvent.PassPhrase) + } + + is Response.Success -> navigationLiveData.postValue(AuthUIAScreenNavigationEvent.Home) + } + } + + fun cancelRestore() { + launchBg { + val session = MatrixSessionProvider.currentSession ?: return@launchBg + val sessionId = session.sessionParams.credentials.sessionId() + refreshTokenManager.cancelTokenRefreshing(session) + MatrixSessionProvider.removeListenersAndStopSync() + MatrixInstanceProvider.matrix.authenticationService().removeSession(sessionId) + } + } + +} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/uia/flow/LoginStagesDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/flow/LoginStagesDataSource.kt new file mode 100644 index 0000000000000000000000000000000000000000..b685d27820567599e3f9547e7ccc0515022c9bd6 --- /dev/null +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/flow/LoginStagesDataSource.kt @@ -0,0 +1,59 @@ +package org.futo.circles.auth.feature.uia.flow + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import org.futo.circles.auth.R +import org.futo.circles.auth.feature.uia.UIADataSource +import org.futo.circles.core.extensions.Response +import org.futo.circles.core.extensions.createResult +import org.futo.circles.core.provider.MatrixInstanceProvider +import org.matrix.android.sdk.api.auth.registration.RegistrationResult +import org.matrix.android.sdk.api.auth.registration.Stage +import org.matrix.android.sdk.api.util.JsonDict +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class LoginStagesDataSource @Inject constructor( + @ApplicationContext private val context: Context +) : UIADataSource() { + + + var userPassword: String = "" + + override suspend fun startUIAStages( + stages: List<Stage>, + serverDomain: String, + name: String? + ) { + userPassword = "" + name ?: throw IllegalArgumentException("Username is required for login") + super.startUIAStages(stages, serverDomain, name) + } + + override suspend fun performUIAStage( + authParams: JsonDict, + name: String?, + password: String? + ): Response<RegistrationResult> { + val wizard = MatrixInstanceProvider.matrix.authenticationService().getLoginWizard() + val result = createResult { + wizard.loginStageCustom( + authParams, + getIdentifier(), + context.getString(R.string.initial_device_name), + true + ) + } + (result as? Response.Success)?.let { + password?.let { userPassword = it } + stageCompleted(result.data) + } + return result + } + + private fun getIdentifier() = mapOf( + USER_PARAM_KEY to "@$userName:$domain", + TYPE_PARAM_KEY to LOGIN_PASSWORD_USER_ID_TYPE + ) +} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/uia/flow/SignUpStagesDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/flow/SignUpStagesDataSource.kt new file mode 100644 index 0000000000000000000000000000000000000000..c9fb31553f36ef81b24c8c19ddf8dc7127308a19 --- /dev/null +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/flow/SignUpStagesDataSource.kt @@ -0,0 +1,41 @@ +package org.futo.circles.auth.feature.uia.flow + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import org.futo.circles.auth.R +import org.futo.circles.auth.feature.uia.UIADataSource +import org.futo.circles.core.extensions.Response +import org.futo.circles.core.extensions.createResult +import org.futo.circles.core.provider.MatrixInstanceProvider +import org.matrix.android.sdk.api.auth.registration.RegistrationResult +import org.matrix.android.sdk.api.util.JsonDict +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SignUpStagesDataSource @Inject constructor( + @ApplicationContext private val context: Context +) : UIADataSource() { + + override suspend fun performUIAStage( + authParams: JsonDict, + name: String?, + password: String? + ): Response<RegistrationResult> { + val wizard = MatrixInstanceProvider.matrix.authenticationService().getRegistrationWizard() + val result = createResult { + wizard.registrationCustom( + authParams, + context.getString(R.string.initial_device_name), + true + ) + } + + (result as? Response.Success)?.let { + name?.let { userName = it } + stageCompleted(result.data) + } + return result + } + +} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/reauth/AuthConfirmationProvider.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/flow/reauth/AuthConfirmationProvider.kt similarity index 55% rename from auth/src/main/java/org/futo/circles/auth/feature/reauth/AuthConfirmationProvider.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/flow/reauth/AuthConfirmationProvider.kt index 4e83420c6c358204739b5a791f22e6d2b6d87817..1df07df5019cd8e75ee8c2c649b2d0a8c14311f4 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/reauth/AuthConfirmationProvider.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/flow/reauth/AuthConfirmationProvider.kt @@ -1,6 +1,13 @@ -package org.futo.circles.auth.feature.reauth +package org.futo.circles.auth.feature.uia.flow.reauth +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.futo.circles.auth.feature.uia.UIADataSource +import org.futo.circles.auth.feature.uia.UIADataSourceProvider +import org.futo.circles.auth.model.UIAFlowType import org.futo.circles.core.base.SingleEventLiveData +import org.futo.circles.core.extensions.coroutineScope +import org.futo.circles.core.provider.MatrixSessionProvider import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse @@ -10,7 +17,7 @@ import kotlin.coroutines.Continuation import kotlin.coroutines.resumeWithException class AuthConfirmationProvider @Inject constructor( - private val reAuthStagesDataSource: ReAuthStagesDataSource + private val uiaFactory: UIADataSource.Factory ) : UserInteractiveAuthInterceptor { val startReAuthEventLiveData = SingleEventLiveData<Unit>() @@ -24,9 +31,13 @@ class AuthConfirmationProvider @Inject constructor( if (flowResponse.completedStages.isNullOrEmpty()) { val stages = flowResponse.toFlowsWithStages().firstOrNull() ?: emptyList() startReAuthEventLiveData.postValue(Unit) - reAuthStagesDataSource.startReAuthStages(flowResponse.session ?: "", stages, promise) + MatrixSessionProvider.getSessionOrThrow().coroutineScope.launch(Dispatchers.IO) { + UIADataSourceProvider.create(UIAFlowType.ReAuth, uiaFactory) + .apply { startUIAStages(stages, flowResponse.session ?: "", promise) } + } } else { - reAuthStagesDataSource.onStageResult(promise, flowResponse, errCode) + UIADataSourceProvider.getDataSourceOrThrow() + .onStageResult(promise, flowResponse, errCode) } } } \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/reauth/ReAuthCancellationListener.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/flow/reauth/ReAuthCancellationListener.kt similarity index 55% rename from auth/src/main/java/org/futo/circles/auth/feature/reauth/ReAuthCancellationListener.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/flow/reauth/ReAuthCancellationListener.kt index a5fbeb54c1fe763b85796266584b1a224006cf86..fbd4b1b23e236d5901437d6c736cc87c24a853cf 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/reauth/ReAuthCancellationListener.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/flow/reauth/ReAuthCancellationListener.kt @@ -1,4 +1,4 @@ -package org.futo.circles.auth.feature.reauth +package org.futo.circles.auth.feature.uia.flow.reauth interface ReAuthCancellationListener { fun onReAuthCanceled() diff --git a/auth/src/main/java/org/futo/circles/auth/feature/reauth/ReAuthStagesDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/flow/reauth/ReAuthStagesDataSource.kt similarity index 69% rename from auth/src/main/java/org/futo/circles/auth/feature/reauth/ReAuthStagesDataSource.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/flow/reauth/ReAuthStagesDataSource.kt index 8beada3ac14f0d272b5d22c93824521ab1592437..77ad13a926862e13227772eb71f3e7a2ebe45056 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/reauth/ReAuthStagesDataSource.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/flow/reauth/ReAuthStagesDataSource.kt @@ -1,10 +1,7 @@ -package org.futo.circles.auth.feature.reauth +package org.futo.circles.auth.feature.uia.flow.reauth -import android.content.Context -import dagger.hilt.android.qualifiers.ApplicationContext -import org.futo.circles.auth.base.BaseLoginStagesDataSource +import org.futo.circles.auth.feature.uia.UIADataSource import org.futo.circles.auth.model.CustomUIAuth -import org.futo.circles.core.base.SingleEventLiveData import org.futo.circles.core.extensions.Response import org.futo.circles.core.provider.MatrixSessionProvider import org.matrix.android.sdk.api.auth.UIABaseAuth @@ -20,18 +17,15 @@ import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @Singleton -class ReAuthStagesDataSource @Inject constructor( - @ApplicationContext context: Context, -) : BaseLoginStagesDataSource(context) { +class ReAuthStagesDataSource @Inject constructor() : UIADataSource() { - val finishReAuthEventLiveData = SingleEventLiveData<Unit>() private var authPromise: Continuation<UIABaseAuth>? = null private var sessionId: String = "" private var stageResultContinuation: Continuation<Response<RegistrationResult>>? = null - fun startReAuthStages( + override suspend fun startUIAStages( + stages: List<Stage>, session: String, - loginStages: List<Stage>, promise: Continuation<UIABaseAuth> ) { sessionId = session @@ -40,11 +34,13 @@ class ReAuthStagesDataSource @Inject constructor( val userId = MatrixSessionProvider.getSessionOrThrow().myUserId val domain = userId.substringAfter(":") val name = userId.substringAfter("@").substringBefore(":") - super.startLoginStages(loginStages, name, domain) + startUIAStages(stages, domain, name) } - override suspend fun performLoginStage( + + override suspend fun performUIAStage( authParams: JsonDict, + name: String?, password: String? ): Response<RegistrationResult> { authPromise?.resume(CustomUIAuth(sessionId, authParams)) @@ -54,13 +50,12 @@ class ReAuthStagesDataSource @Inject constructor( else awaitForStageResult() (result as? Response.Success)?.let { - password?.let { userPassword = it } stageCompleted(it.data) - } ?: run { finishReAuthEventLiveData.postValue(Unit) } + } ?: run { finishUIAEventLiveData.postValue(MatrixSessionProvider.getSessionOrThrow()) } return result } - fun onStageResult( + override fun onStageResult( promise: Continuation<UIABaseAuth>, flowResponse: RegistrationFlowResponse, errCode: String? @@ -72,14 +67,8 @@ class ReAuthStagesDataSource @Inject constructor( stageResultContinuation?.resume(result) } - private fun stageCompleted(result: RegistrationResult) { - if (isStageRetry(result)) return - (result as? RegistrationResult.Success)?.let { - finishReAuthEventLiveData.postValue(Unit) - } ?: navigateToNextStage() - } - private suspend fun awaitForStageResult() = suspendCoroutine { stageResultContinuation = it } private fun isLastStage() = stagesToComplete.last() == currentStage + } \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/EmptyFragment.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/EmptyFragment.kt similarity index 72% rename from auth/src/main/java/org/futo/circles/auth/feature/log_in/EmptyFragment.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/stages/EmptyFragment.kt index 65bf991491b3da367e9732f0689a24506e55c2aa..ffdd3e0472343eae8b8e9cfa6c3ef568fc3885f6 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/log_in/EmptyFragment.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/EmptyFragment.kt @@ -1,4 +1,4 @@ -package org.futo.circles.auth.feature.log_in +package org.futo.circles.auth.feature.uia.stages import androidx.fragment.app.Fragment import org.futo.circles.auth.R diff --git a/auth/src/main/java/org/futo/circles/auth/base/BaseBsSpekeStageDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/password/BsSpekeStageDataSource.kt similarity index 73% rename from auth/src/main/java/org/futo/circles/auth/base/BaseBsSpekeStageDataSource.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/stages/password/BsSpekeStageDataSource.kt index 01e9b0b4a33c15507e09748c1e9ce2575feeaa63..fb2a8b92813fdbcb858298e6775c518c8152755d 100644 --- a/auth/src/main/java/org/futo/circles/auth/base/BaseBsSpekeStageDataSource.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/password/BsSpekeStageDataSource.kt @@ -1,46 +1,34 @@ -package org.futo.circles.auth.base +package org.futo.circles.auth.feature.uia.stages.password import android.content.Context import android.util.Base64 +import dagger.hilt.android.qualifiers.ApplicationContext import org.futo.circles.auth.R -import org.futo.circles.auth.base.BaseLoginStagesDataSource.Companion.LOGIN_BSSPEKE_OPRF_TYPE -import org.futo.circles.auth.base.BaseLoginStagesDataSource.Companion.LOGIN_BSSPEKE_VERIFY_TYPE -import org.futo.circles.auth.base.BaseLoginStagesDataSource.Companion.TYPE_PARAM_KEY import org.futo.circles.auth.bsspeke.BSSpekeClient import org.futo.circles.auth.bsspeke.BSSpekeClientProvider -import org.futo.circles.auth.feature.log_in.stages.password.LoginBsSpekeDataSource -import org.futo.circles.auth.feature.sign_up.SignUpDataSource.Companion.REGISTRATION_BSSPEKE_OPRF_TYPE -import org.futo.circles.auth.feature.sign_up.SignUpDataSource.Companion.REGISTRATION_BSSPEKE_SAVE_TYPE -import org.futo.circles.auth.feature.sign_up.password.SignupBsSpekeDataSource +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.ENROLL_BSSPEKE_OPRF_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.ENROLL_BSSPEKE_SAVE_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.LOGIN_BSSPEKE_OPRF_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.LOGIN_BSSPEKE_VERIFY_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.TYPE_PARAM_KEY +import org.futo.circles.auth.feature.uia.UIADataSourceProvider import org.futo.circles.core.extensions.Response import org.matrix.android.sdk.api.auth.registration.RegistrationResult import org.matrix.android.sdk.api.auth.registration.Stage -import org.matrix.android.sdk.api.util.JsonDict import javax.inject.Inject -abstract class BaseBsSpekeStageDataSource(private val context: Context) : PasswordDataSource { +class BsSpekeStageDataSource @Inject constructor( + @ApplicationContext private val context: Context +) { - class Factory @Inject constructor( - private val loginBsSpekeDataSourceFactory: LoginBsSpekeDataSource.Factory, - private val signupBsSpekeDataSource: SignupBsSpekeDataSource - ) { - fun create(isSignup: Boolean, isReAuth: Boolean, isChangePasswordEnroll: Boolean) = - if (isSignup) signupBsSpekeDataSource - else loginBsSpekeDataSourceFactory.create(isReAuth, isChangePasswordEnroll) - } - - protected abstract val userName: String - protected abstract val domain: String - protected abstract val isLoginMode: Boolean - protected abstract fun getStages(): List<Stage> - protected abstract suspend fun performAuthStage( - authParams: JsonDict, - password: String? = null - ): Response<RegistrationResult> + private val uiaDataSource = UIADataSourceProvider.getDataSourceOrThrow() + private var isVerifyStages = true - override suspend fun processPasswordStage(password: String): Response<Unit> { - BSSpekeClientProvider.initClient(userName, domain, password) + suspend fun processPasswordStage(password: String): Response<Unit> { + val currentStageKey = uiaDataSource.getCurrentStageKey() + isVerifyStages = currentStageKey == LOGIN_BSSPEKE_OPRF_TYPE || currentStageKey== LOGIN_BSSPEKE_VERIFY_TYPE + BSSpekeClientProvider.initClient(uiaDataSource.userName, uiaDataSource.domain, password) val bsSpekeClient = BSSpekeClientProvider.getClientOrThrow() return when (val oprfResult = performOprfStage(bsSpekeClient)) { is Response.Success -> processOprfSuccessResponse( @@ -51,12 +39,12 @@ abstract class BaseBsSpekeStageDataSource(private val context: Context) : Passwo } } - private fun getOprfTypeKey() = - if (isLoginMode) LOGIN_BSSPEKE_OPRF_TYPE else REGISTRATION_BSSPEKE_OPRF_TYPE + private fun getOprfTypeKey() = if (isVerifyStages) LOGIN_BSSPEKE_OPRF_TYPE + else ENROLL_BSSPEKE_OPRF_TYPE private suspend fun performOprfStage( bsSpekeClient: BSSpekeClient - ): Response<RegistrationResult> = performAuthStage( + ): Response<RegistrationResult> = uiaDataSource.performUIAStage( mapOf( TYPE_PARAM_KEY to getOprfTypeKey(), CURVE_PARAM_KEY to getCurve(), @@ -79,7 +67,7 @@ abstract class BaseBsSpekeStageDataSource(private val context: Context) : Passwo return Response.Error(e.message ?: "") } return when (val result = - if (isLoginMode) performVerifyStage( + if (isVerifyStages) performVerifyStage( oprfResult, bsSpekeClient, password, blocks, iterations ) else performSaveStage( @@ -100,14 +88,14 @@ abstract class BaseBsSpekeStageDataSource(private val context: Context) : Passwo ): Response<RegistrationResult> { val PandV: Pair<String, String> try { - val blindSalt = getBlindSalt(context, oprfResult, REGISTRATION_BSSPEKE_SAVE_TYPE) + val blindSalt = getBlindSalt(context, oprfResult, ENROLL_BSSPEKE_SAVE_TYPE) PandV = bsSpekeClient.generateBase64PandV(blindSalt, blocks, iterations) } catch (e: Exception) { return Response.Error(e.message ?: "") } - return performAuthStage( + return uiaDataSource.performUIAStage( mapOf( - TYPE_PARAM_KEY to REGISTRATION_BSSPEKE_SAVE_TYPE, + TYPE_PARAM_KEY to ENROLL_BSSPEKE_SAVE_TYPE, P_PARAM_KEY to PandV.first, V_PARAM_KEY to PandV.second, PHF_PARAM_KEY to phfParams @@ -133,7 +121,7 @@ abstract class BaseBsSpekeStageDataSource(private val context: Context) : Passwo } catch (e: Exception) { return Response.Error(e.message ?: "") } - return performAuthStage( + return uiaDataSource.performUIAStage( mapOf( TYPE_PARAM_KEY to LOGIN_BSSPEKE_VERIFY_TYPE, A_PARAM_KEY to A, @@ -196,11 +184,11 @@ abstract class BaseBsSpekeStageDataSource(private val context: Context) : Passwo } private fun getOprfStage(): Stage? = - getStages().firstOrNull { (it as? Stage.Other)?.type == getOprfTypeKey() } + uiaDataSource.stagesToComplete.firstOrNull { (it as? Stage.Other)?.type == getOprfTypeKey() } companion object { - const val CURVE_PARAM_KEY = "curve" - const val PHF_PARAM_KEY = "phf_params" + private const val CURVE_PARAM_KEY = "curve" + private const val PHF_PARAM_KEY = "phf_params" private const val BLIND_SALT_PARAM_KEY = "blind_salt" private const val PHF_ITERATIONS_PARAM_KEY = "iterations" private const val PHF_BLOCKS_PARAM_KEY = "blocks" diff --git a/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/password/PasswordDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/password/PasswordDataSource.kt new file mode 100644 index 0000000000000000000000000000000000000000..99ed81dedb96b8f6a0be9f9b326a8a6e3d4fc3cc --- /dev/null +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/password/PasswordDataSource.kt @@ -0,0 +1,90 @@ +package org.futo.circles.auth.feature.uia.stages.password + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import org.futo.circles.auth.R +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.DIRECT_LOGIN_PASSWORD_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.LOGIN_BSSPEKE_OPRF_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.LOGIN_PASSWORD_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.ENROLL_BSSPEKE_OPRF_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.ENROLL_BSSPEKE_SAVE_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.ENROLL_PASSWORD_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.LOGIN_BSSPEKE_VERIFY_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.TYPE_PARAM_KEY +import org.futo.circles.auth.feature.uia.UIADataSourceProvider +import org.futo.circles.core.extensions.Response +import org.futo.circles.core.extensions.createResult +import org.futo.circles.core.provider.MatrixInstanceProvider +import org.matrix.android.sdk.api.auth.registration.RegistrationResult +import org.matrix.android.sdk.api.auth.registration.Stage +import javax.inject.Inject + +class PasswordDataSource @Inject constructor( + @ApplicationContext private val context: Context, + private val bsSpekeStageDataSource: BsSpekeStageDataSource +) { + + private val uiaDataSource = UIADataSourceProvider.getDataSourceOrThrow() + + suspend fun processPasswordStage(password: String): Response<Unit> = + when (uiaDataSource.getCurrentStageKey()) { + LOGIN_BSSPEKE_OPRF_TYPE, ENROLL_BSSPEKE_OPRF_TYPE, + LOGIN_BSSPEKE_VERIFY_TYPE, ENROLL_BSSPEKE_SAVE_TYPE-> + bsSpekeStageDataSource.processPasswordStage(password) + + ENROLL_PASSWORD_TYPE -> processRegistrationPasswordStage(password) + LOGIN_PASSWORD_TYPE -> processPasswordStageL(password) + DIRECT_LOGIN_PASSWORD_TYPE -> processDirectPasswordStage(password) + else -> throw IllegalArgumentException("Unsupported password stage") + } + + + private suspend fun processRegistrationPasswordStage(password: String): Response<Unit> = + when (val result = uiaDataSource.performUIAStage( + mapOf( + TYPE_PARAM_KEY to ENROLL_PASSWORD_TYPE, + REGISTRATION_PASSWORD_PARAM_KEY to password + ) + )) { + is Response.Success -> Response.Success(Unit) + is Response.Error -> result + } + + + private suspend fun processPasswordStageL(password: String): Response<Unit> { + val result = uiaDataSource.performUIAStage( + mapOf( + TYPE_PARAM_KEY to LOGIN_PASSWORD_TYPE, + LOGIN_PASSWORD_PARAM_KEY to password + ), password + ) + return when (result) { + is Response.Success -> Response.Success(Unit) + is Response.Error -> result + } + } + + private suspend fun processDirectPasswordStage(password: String): Response<Unit> { + val result = createResult { + MatrixInstanceProvider.matrix.authenticationService().getLoginWizard().login( + login = uiaDataSource.userName, + password = password, + initialDeviceName = context.getString(R.string.initial_device_name) + ) + } + return when (result) { + is Response.Success -> { + uiaDataSource.stageCompleted(RegistrationResult.Success(result.data)) + Response.Success(Unit) + } + + is Response.Error -> result + } + } + + companion object { + private const val REGISTRATION_PASSWORD_PARAM_KEY = "new_password" + private const val LOGIN_PASSWORD_PARAM_KEY = "password" + } + +} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/password/PasswordFragment.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/password/PasswordFragment.kt similarity index 83% rename from auth/src/main/java/org/futo/circles/auth/feature/sign_up/password/PasswordFragment.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/stages/password/PasswordFragment.kt index 58b8ad2611c21bd0e942a3a9161ba40141b2dafc..a021c6bfc587ef3088a1e67dda08751d16e8f24b 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/password/PasswordFragment.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/password/PasswordFragment.kt @@ -1,4 +1,4 @@ -package org.futo.circles.auth.feature.sign_up.password +package org.futo.circles.auth.feature.uia.stages.password import android.os.Bundle import android.view.View @@ -6,13 +6,13 @@ import android.view.inputmethod.EditorInfo import androidx.core.widget.doAfterTextChanged import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels -import androidx.navigation.fragment.navArgs import by.kirich1409.viewbindingdelegate.viewBinding import dagger.hilt.android.AndroidEntryPoint import org.futo.circles.auth.R import org.futo.circles.auth.databinding.FragmentPasswordBinding -import org.futo.circles.auth.feature.sign_up.password.confirmation.SetupPasswordWarningDialog -import org.futo.circles.auth.model.PasswordModeArg +import org.futo.circles.auth.feature.uia.UIADataSource +import org.futo.circles.auth.feature.uia.UIADataSourceProvider +import org.futo.circles.auth.feature.uia.stages.password.confirmation.SetupPasswordWarningDialog import org.futo.circles.core.base.fragment.HasLoadingState import org.futo.circles.core.base.fragment.ParentBackPressOwnerFragment import org.futo.circles.core.extensions.getText @@ -24,7 +24,6 @@ import org.futo.circles.core.extensions.showError class PasswordFragment : ParentBackPressOwnerFragment(R.layout.fragment_password), HasLoadingState { - private val args: PasswordFragmentArgs by navArgs() private val viewModel by viewModels<PasswordViewModel>() override val fragment: Fragment = this private val binding by viewBinding(FragmentPasswordBinding::bind) @@ -64,9 +63,11 @@ class PasswordFragment : ParentBackPressOwnerFragment(R.layout.fragment_password } } - private fun isSignupMode() = when (args.mode) { - PasswordModeArg.ReAuthBsSpekeSignup, PasswordModeArg.SignupPasswordStage, PasswordModeArg.SignupBsSpekeStage -> true - else -> false + private fun isSignupMode(): Boolean { + val currentStageKey = UIADataSourceProvider.getDataSourceOrThrow().getCurrentStageKey() + return currentStageKey == UIADataSource.ENROLL_PASSWORD_TYPE + || currentStageKey == UIADataSource.ENROLL_BSSPEKE_OPRF_TYPE + || currentStageKey == UIADataSource.ENROLL_BSSPEKE_SAVE_TYPE } private fun setupObservers() { diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/password/PasswordViewModel.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/password/PasswordViewModel.kt similarity index 88% rename from auth/src/main/java/org/futo/circles/auth/feature/sign_up/password/PasswordViewModel.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/stages/password/PasswordViewModel.kt index 21c50f808114feaf4eb8af63c3e5c35bd93b9017..6db8e20ddddcf50156835b352fa6126f7d994435 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/password/PasswordViewModel.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/password/PasswordViewModel.kt @@ -1,8 +1,7 @@ -package org.futo.circles.auth.feature.sign_up.password +package org.futo.circles.auth.feature.uia.stages.password import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel -import org.futo.circles.auth.base.PasswordDataSource import org.futo.circles.core.base.SingleEventLiveData import org.futo.circles.core.extensions.Response import org.futo.circles.core.extensions.launchBg diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/password/confirmation/SetupPasswordWarningDialog.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/password/confirmation/SetupPasswordWarningDialog.kt similarity index 93% rename from auth/src/main/java/org/futo/circles/auth/feature/sign_up/password/confirmation/SetupPasswordWarningDialog.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/stages/password/confirmation/SetupPasswordWarningDialog.kt index c82824761cc92a1bb1023325a7073fbf061817d4..3c3f4c0f62e37b77404b078ae96d12f1f475b0bd 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/password/confirmation/SetupPasswordWarningDialog.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/password/confirmation/SetupPasswordWarningDialog.kt @@ -1,4 +1,4 @@ -package org.futo.circles.auth.feature.sign_up.password.confirmation +package org.futo.circles.auth.feature.uia.stages.password.confirmation import android.app.ActionBar import android.content.Context diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/subscription_stage/SubscriptionStageDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/subscription_stage/SubscriptionStageDataSource.kt similarity index 65% rename from auth/src/main/java/org/futo/circles/auth/feature/sign_up/subscription_stage/SubscriptionStageDataSource.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/stages/subscription_stage/SubscriptionStageDataSource.kt index c5504441bd078e6d26374535a7fe74a13e051dba..d1601e52eebcd79a076c6726f06c70f34add04ba 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/subscription_stage/SubscriptionStageDataSource.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/subscription_stage/SubscriptionStageDataSource.kt @@ -1,24 +1,24 @@ -package org.futo.circles.auth.feature.sign_up.subscription_stage +package org.futo.circles.auth.feature.uia.stages.subscription_stage -import org.futo.circles.auth.base.BaseLoginStagesDataSource.Companion.TYPE_PARAM_KEY -import org.futo.circles.auth.feature.sign_up.SignUpDataSource -import org.futo.circles.auth.feature.sign_up.SignUpDataSource.Companion.REGISTRATION_SUBSCRIPTION_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.SUBSCRPTION_GOOGLE_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.TYPE_PARAM_KEY +import org.futo.circles.auth.feature.uia.UIADataSourceProvider import org.futo.circles.auth.model.SubscriptionReceiptData import org.futo.circles.core.extensions.Response import org.matrix.android.sdk.api.auth.registration.RegistrationResult import org.matrix.android.sdk.api.auth.registration.Stage import javax.inject.Inject -class SubscriptionStageDataSource @Inject constructor( - private val signUpDataSource: SignUpDataSource -) { +class SubscriptionStageDataSource @Inject constructor() { + + private val uiaDataSource = UIADataSourceProvider.getDataSourceOrThrow() suspend fun validateSubscription( subscriptionReceiptData: SubscriptionReceiptData ): Response<RegistrationResult> = - signUpDataSource.performRegistrationStage( + uiaDataSource.performUIAStage( mapOf( - TYPE_PARAM_KEY to REGISTRATION_SUBSCRIPTION_TYPE, + TYPE_PARAM_KEY to SUBSCRPTION_GOOGLE_TYPE, ORDER_ID_KEY to subscriptionReceiptData.orderId, PACKAGE_KEY to subscriptionReceiptData.packageName, SUBSCRIPTION_ID_KEY to subscriptionReceiptData.productId, @@ -26,7 +26,7 @@ class SubscriptionStageDataSource @Inject constructor( ) ) - fun getProductIdsList() = ((signUpDataSource.currentStage as? Stage.Other) + fun getProductIdsList() = ((uiaDataSource.currentStage as? Stage.Other) ?.params?.get(SUBSCRIPTION_IDS_PARAMS_KEY) as? List<*>) ?.map { it.toString() } ?: emptyList() diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/subscription_stage/SubscriptionStageFragment.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/subscription_stage/SubscriptionStageFragment.kt similarity index 94% rename from auth/src/main/java/org/futo/circles/auth/feature/sign_up/subscription_stage/SubscriptionStageFragment.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/stages/subscription_stage/SubscriptionStageFragment.kt index 6e723478ab4b6c616af2f1559e655f86bec18c3d..92f6fb7fadc52fbe4a01cb490cd8774015591648 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/subscription_stage/SubscriptionStageFragment.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/subscription_stage/SubscriptionStageFragment.kt @@ -1,4 +1,4 @@ -package org.futo.circles.auth.feature.sign_up.subscription_stage +package org.futo.circles.auth.feature.uia.stages.subscription_stage import android.os.Bundle import android.view.View @@ -9,7 +9,7 @@ import com.google.android.material.divider.MaterialDividerItemDecoration import dagger.hilt.android.AndroidEntryPoint import org.futo.circles.auth.R import org.futo.circles.auth.databinding.FragmentSubscriptionStageBinding -import org.futo.circles.auth.feature.sign_up.subscription_stage.list.SubscriptionsAdapter +import org.futo.circles.auth.feature.uia.stages.subscription_stage.list.SubscriptionsAdapter import org.futo.circles.auth.model.SubscriptionReceiptData import org.futo.circles.auth.subscriptions.ItemPurchasedListener import org.futo.circles.auth.subscriptions.SubscriptionProvider diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/subscription_stage/SubscriptionStageViewModel.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/subscription_stage/SubscriptionStageViewModel.kt similarity index 97% rename from auth/src/main/java/org/futo/circles/auth/feature/sign_up/subscription_stage/SubscriptionStageViewModel.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/stages/subscription_stage/SubscriptionStageViewModel.kt index 65d58bc5e206c0c1510c65ae44b45ff15ea5526c..cd838a003c7bfac461526326f2aff1737c3b068d 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/subscription_stage/SubscriptionStageViewModel.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/subscription_stage/SubscriptionStageViewModel.kt @@ -1,4 +1,4 @@ -package org.futo.circles.auth.feature.sign_up.subscription_stage +package org.futo.circles.auth.feature.uia.stages.subscription_stage import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/subscription_stage/list/SubscriptionViewHolder.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/subscription_stage/list/SubscriptionViewHolder.kt similarity index 92% rename from auth/src/main/java/org/futo/circles/auth/feature/sign_up/subscription_stage/list/SubscriptionViewHolder.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/stages/subscription_stage/list/SubscriptionViewHolder.kt index b14da3fa1c70fea6658c18d8639899a4cd67bcbd..2e84a24dfaa9e3cff6bd58470ff3d378bfb124c6 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/subscription_stage/list/SubscriptionViewHolder.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/subscription_stage/list/SubscriptionViewHolder.kt @@ -1,4 +1,4 @@ -package org.futo.circles.auth.feature.sign_up.subscription_stage.list +package org.futo.circles.auth.feature.uia.stages.subscription_stage.list import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/subscription_stage/list/SubscriptionsAdapter.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/subscription_stage/list/SubscriptionsAdapter.kt similarity index 90% rename from auth/src/main/java/org/futo/circles/auth/feature/sign_up/subscription_stage/list/SubscriptionsAdapter.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/stages/subscription_stage/list/SubscriptionsAdapter.kt index 4adda9540a9e86bb7446265ca4e85c302e416dff..d8160878b5069a9e37a1ee93ea77e8aa678e5db2 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/subscription_stage/list/SubscriptionsAdapter.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/subscription_stage/list/SubscriptionsAdapter.kt @@ -1,4 +1,4 @@ -package org.futo.circles.auth.feature.sign_up.subscription_stage.list +package org.futo.circles.auth.feature.uia.stages.subscription_stage.list import android.view.ViewGroup import org.futo.circles.core.base.list.BaseRvAdapter diff --git a/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/terms/AcceptTermsDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/terms/AcceptTermsDataSource.kt new file mode 100644 index 0000000000000000000000000000000000000000..f67bb85d22a0c22f2676359347535ce239655c4c --- /dev/null +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/terms/AcceptTermsDataSource.kt @@ -0,0 +1,37 @@ +package org.futo.circles.auth.feature.uia.stages.terms + +import androidx.lifecycle.MutableLiveData +import org.futo.circles.auth.extensions.toTermsListItems +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.TYPE_PARAM_KEY +import org.futo.circles.auth.feature.uia.UIADataSourceProvider +import org.futo.circles.auth.model.TermsListItem +import org.futo.circles.core.extensions.Response +import org.matrix.android.sdk.api.auth.data.LoginFlowTypes +import org.matrix.android.sdk.api.auth.registration.Stage +import javax.inject.Inject + +class AcceptTermsDataSource @Inject constructor() { + + private val uiaDataSource = UIADataSourceProvider.getDataSourceOrThrow() + val termsListLiveData by lazy { MutableLiveData(getTermsList()) } + + suspend fun acceptTerms(): Response<Unit> { + val result = uiaDataSource.performUIAStage( + mapOf(TYPE_PARAM_KEY to LoginFlowTypes.TERMS) + ) + return when (result) { + is Response.Success -> Response.Success(Unit) + is Response.Error -> result + } + } + + fun changeTermCheck(item: TermsListItem) { + termsListLiveData.value = + termsListLiveData.value?.map { if (it.id == item.id) it.copy(isChecked = !it.isChecked) else it } + } + + private fun getTermsList() = + (uiaDataSource.currentStage as? Stage.Terms)?.policies?.toTermsListItems() + ?: emptyList() + +} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/terms/AcceptTermsFragment.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/terms/AcceptTermsFragment.kt similarity index 95% rename from auth/src/main/java/org/futo/circles/auth/feature/sign_up/terms/AcceptTermsFragment.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/stages/terms/AcceptTermsFragment.kt index ee5643d5c2e7e50a7965262832dfde828435fa10..8b0912dc799c37fdca32a3762bb3256a92403cdf 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/terms/AcceptTermsFragment.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/terms/AcceptTermsFragment.kt @@ -1,4 +1,4 @@ -package org.futo.circles.auth.feature.sign_up.terms +package org.futo.circles.auth.feature.uia.stages.terms import android.os.Bundle import android.view.View @@ -10,7 +10,7 @@ import dagger.hilt.android.AndroidEntryPoint import org.futo.circles.auth.R import org.futo.circles.auth.databinding.FragmentAcceptTermsBinding import org.futo.circles.auth.extensions.openCustomTabUrl -import org.futo.circles.auth.feature.sign_up.terms.list.TermsListAdapter +import org.futo.circles.auth.feature.uia.stages.terms.list.TermsListAdapter import org.futo.circles.auth.model.TermsListItem import org.futo.circles.core.base.fragment.HasLoadingState import org.futo.circles.core.base.fragment.ParentBackPressOwnerFragment diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/terms/AcceptTermsViewModel.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/terms/AcceptTermsViewModel.kt similarity index 65% rename from auth/src/main/java/org/futo/circles/auth/feature/sign_up/terms/AcceptTermsViewModel.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/stages/terms/AcceptTermsViewModel.kt index fea3071a07fbc71f9a95485a3d35f2dfe73123cb..aef81d8b116faa1c983d7cfc0a32a037d8f0f8b3 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/terms/AcceptTermsViewModel.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/terms/AcceptTermsViewModel.kt @@ -1,23 +1,18 @@ -package org.futo.circles.auth.feature.sign_up.terms +package org.futo.circles.auth.feature.uia.stages.terms -import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel -import org.futo.circles.auth.base.BaseAcceptTermsDataSource import org.futo.circles.auth.model.TermsListItem import org.futo.circles.core.base.SingleEventLiveData import org.futo.circles.core.extensions.Response -import org.futo.circles.core.extensions.getOrThrow import org.futo.circles.core.extensions.launchBg import javax.inject.Inject @HiltViewModel class AcceptTermsViewModel @Inject constructor( - savedStateHandle: SavedStateHandle, - dataSourceFactory: BaseAcceptTermsDataSource.Factory + private val dataSource: AcceptTermsDataSource ) : ViewModel() { - private val dataSource = dataSourceFactory.create(savedStateHandle.getOrThrow("mode")) val acceptTermsLiveData = SingleEventLiveData<Response<Unit>>() val termsListLiveData = dataSource.termsListLiveData diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/terms/list/TermsItemViewHolder.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/terms/list/TermsItemViewHolder.kt similarity index 94% rename from auth/src/main/java/org/futo/circles/auth/feature/sign_up/terms/list/TermsItemViewHolder.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/stages/terms/list/TermsItemViewHolder.kt index 12a2d1082ce369e994737630fa0d6e5f30609bc8..1bf7ecbc75011eff6c445e31ed75b3e7ac6db3fa 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/terms/list/TermsItemViewHolder.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/terms/list/TermsItemViewHolder.kt @@ -1,4 +1,4 @@ -package org.futo.circles.auth.feature.sign_up.terms.list +package org.futo.circles.auth.feature.uia.stages.terms.list import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/terms/list/TermsListAdapter.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/terms/list/TermsListAdapter.kt similarity index 92% rename from auth/src/main/java/org/futo/circles/auth/feature/sign_up/terms/list/TermsListAdapter.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/stages/terms/list/TermsListAdapter.kt index b63db703ab1633b080cb4157cb851cd9d5c176d3..f3065d4b84f43622bc10f192f52e44603854fb2b 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/terms/list/TermsListAdapter.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/terms/list/TermsListAdapter.kt @@ -1,4 +1,4 @@ -package org.futo.circles.auth.feature.sign_up.terms.list +package org.futo.circles.auth.feature.uia.stages.terms.list import android.view.ViewGroup diff --git a/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/username/UsernameDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/username/UsernameDataSource.kt new file mode 100644 index 0000000000000000000000000000000000000000..9b21f76a52acd7eeb359239aded128462f67d764 --- /dev/null +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/username/UsernameDataSource.kt @@ -0,0 +1,28 @@ +package org.futo.circles.auth.feature.uia.stages.username + +import androidx.lifecycle.MutableLiveData +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.ENROLL_USERNAME_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.TYPE_PARAM_KEY +import org.futo.circles.auth.feature.uia.UIADataSourceProvider +import org.futo.circles.core.extensions.Response +import org.matrix.android.sdk.api.auth.registration.RegistrationResult +import javax.inject.Inject + +class UsernameDataSource @Inject constructor() { + + private val uiaDataSource = UIADataSourceProvider.getDataSourceOrThrow() + + val domainLiveData = MutableLiveData(uiaDataSource.domain) + + suspend fun processUsernameStage(username: String): Response<RegistrationResult> = + uiaDataSource.performUIAStage( + mapOf( + TYPE_PARAM_KEY to ENROLL_USERNAME_TYPE, + USERNAME_PARAM_KEY to username + ), name = username + ) + + companion object { + private const val USERNAME_PARAM_KEY = "username" + } +} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/username/UsernameFragment.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/username/UsernameFragment.kt similarity index 97% rename from auth/src/main/java/org/futo/circles/auth/feature/sign_up/username/UsernameFragment.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/stages/username/UsernameFragment.kt index b1e07753e7d5fceec0714fb25eded0e9e6f15fd3..682e7238f9abd13092870a63ffb3982623a98560 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/username/UsernameFragment.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/username/UsernameFragment.kt @@ -1,4 +1,4 @@ -package org.futo.circles.auth.feature.sign_up.username +package org.futo.circles.auth.feature.uia.stages.username import android.os.Bundle import android.text.InputFilter diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/username/UsernameViewModel.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/username/UsernameViewModel.kt similarity index 93% rename from auth/src/main/java/org/futo/circles/auth/feature/sign_up/username/UsernameViewModel.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/stages/username/UsernameViewModel.kt index 6a7ac038d09aa9c4a7da63f43f9be9138c2fa1d1..870e462fa34defdf4d1af09a5cfc55137b853034 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/username/UsernameViewModel.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/username/UsernameViewModel.kt @@ -1,4 +1,4 @@ -package org.futo.circles.auth.feature.sign_up.username +package org.futo.circles.auth.feature.uia.stages.username import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel diff --git a/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/validate_email/ValidateEmailDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/validate_email/ValidateEmailDataSource.kt new file mode 100644 index 0000000000000000000000000000000000000000..7a369e356ee345aaae7166976158ea5a772d9de8 --- /dev/null +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/validate_email/ValidateEmailDataSource.kt @@ -0,0 +1,56 @@ +package org.futo.circles.auth.feature.uia.stages.validate_email + +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.ENROLL_EMAIL_REQUEST_TOKEN_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.ENROLL_EMAIL_SUBMIT_TOKEN_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.LOGIN_EMAIL_REQUEST_TOKEN_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.LOGIN_EMAIL_SUBMIT_TOKEN_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.TYPE_PARAM_KEY +import org.futo.circles.auth.feature.uia.UIADataSourceProvider +import org.futo.circles.core.extensions.Response +import org.matrix.android.sdk.api.auth.registration.RegistrationResult +import org.matrix.android.sdk.api.auth.registration.Stage +import javax.inject.Inject + +class ValidateEmailDataSource @Inject constructor() { + + private val uiaDataSource = UIADataSourceProvider.getDataSourceOrThrow() + + suspend fun sendValidationCode( + email: String, subscribeToUpdates: Boolean + ): Response<RegistrationResult> = uiaDataSource.performUIAStage( + mapOf( + TYPE_PARAM_KEY to if (isLogin()) LOGIN_EMAIL_REQUEST_TOKEN_TYPE else ENROLL_EMAIL_REQUEST_TOKEN_TYPE, + EMAIL_PARAM_KEY to email, + SUBSCRIBE_TO_LIST to subscribeToUpdates + ) + ) + + suspend fun validateEmail(code: String): Response<RegistrationResult> = + uiaDataSource.performUIAStage( + mapOf( + TYPE_PARAM_KEY to if (isLogin()) LOGIN_EMAIL_REQUEST_TOKEN_TYPE else ENROLL_EMAIL_SUBMIT_TOKEN_TYPE, + TOKEN_PARAM_KEY to code + ) + ) + + private fun isLogin(): Boolean { + val currentStageKey = UIADataSourceProvider.getDataSourceOrThrow().getCurrentStageKey() + return currentStageKey == LOGIN_EMAIL_REQUEST_TOKEN_TYPE || currentStageKey == LOGIN_EMAIL_SUBMIT_TOKEN_TYPE + } + + fun getPrefilledEmail(): String? = + if (isLogin()) ((uiaDataSource.currentStage as? Stage.Other)?.params?.get(EMAILS_LIST_KEY) as? List<*>)?.firstOrNull() + ?.toString() else null + + fun shouldShowSubscribeToEmail(): Boolean = + (uiaDataSource.currentStage as? Stage.Other)?.params?.get(OFFER_LIST_SUBSCRIPTION_KEY) as? Boolean + ?: false + + companion object { + private const val EMAIL_PARAM_KEY = "email" + private const val TOKEN_PARAM_KEY = "token" + private const val SUBSCRIBE_TO_LIST = "subscribe_to_list" + private const val OFFER_LIST_SUBSCRIPTION_KEY = "offer_list_subscription" + private const val EMAILS_LIST_KEY = "addresses" + } +} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/validate_email/ValidateEmailFragment.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/validate_email/ValidateEmailFragment.kt similarity index 88% rename from auth/src/main/java/org/futo/circles/auth/feature/sign_up/validate_email/ValidateEmailFragment.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/stages/validate_email/ValidateEmailFragment.kt index 93f52bb96cac30c7725189e9e1b786511603f7fe..96f7ff493e7894f65d4af37c27d6adf1ff95b364 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/validate_email/ValidateEmailFragment.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/validate_email/ValidateEmailFragment.kt @@ -1,7 +1,8 @@ -package org.futo.circles.auth.feature.sign_up.validate_email +package org.futo.circles.auth.feature.uia.stages.validate_email import android.os.Bundle import android.view.View +import androidx.core.view.isVisible import androidx.core.widget.doAfterTextChanged import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels @@ -37,6 +38,7 @@ class ValidateEmailFragment : ParentBackPressOwnerFragment(R.layout.fragment_val private fun setupViews() { with(binding) { + setAlwaysDisabledViews(listOf(binding.etEmail)) tilEmail.editText?.doAfterTextChanged { it?.let { btnSendCode.isEnabled = it.isValidEmail() } } @@ -57,7 +59,7 @@ class ValidateEmailFragment : ParentBackPressOwnerFragment(R.layout.fragment_val } btnSendCode.setOnClickListener { startLoading(btnSendCode) - viewModel.sendCode(getEmailInput(), cbEmailUpdates.isChecked) + viewModel.sendCode(getEmailInput(), cbEmailUpdates.isChecked && cbEmailUpdates.isVisible) } btnValidate.setOnClickListener { startLoading(btnValidate) @@ -75,6 +77,14 @@ class ValidateEmailFragment : ParentBackPressOwnerFragment(R.layout.fragment_val viewModel.showSubscribeCheckLiveData.observeData(this) { binding.cbEmailUpdates.setIsVisible(it) } + viewModel.usersEmailLiveData.observeData(this) {email-> + email?.let { + binding.etEmail.apply { + setText(it) + isEnabled = false + } + } + } } private fun getEmailInput(): String = binding.tilEmail.getText() diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/validate_email/ValidateEmailViewModel.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/validate_email/ValidateEmailViewModel.kt similarity index 89% rename from auth/src/main/java/org/futo/circles/auth/feature/sign_up/validate_email/ValidateEmailViewModel.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/stages/validate_email/ValidateEmailViewModel.kt index dcd9b7d452c4f7d8284bcdc1bf2feef5fd128da8..b7a1b86fd90d372a973af0763c5707c0fc498476 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/validate_email/ValidateEmailViewModel.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/validate_email/ValidateEmailViewModel.kt @@ -1,4 +1,4 @@ -package org.futo.circles.auth.feature.sign_up.validate_email +package org.futo.circles.auth.feature.uia.stages.validate_email import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -18,6 +18,7 @@ class ValidateEmailViewModel @Inject constructor( val validateEmailLiveData = SingleEventLiveData<Response<RegistrationResult>>() val showSubscribeCheckLiveData = MutableLiveData(dataSource.shouldShowSubscribeToEmail()) + val usersEmailLiveData = MutableLiveData(dataSource.getPrefilledEmail()) fun sendCode(email: String, subscribeToUpdates: Boolean) { launchBg { diff --git a/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/validate_token/ValidateTokenDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/validate_token/ValidateTokenDataSource.kt new file mode 100644 index 0000000000000000000000000000000000000000..a42a543df6b3769a3cefae83a89f9ba5d9bef4be --- /dev/null +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/validate_token/ValidateTokenDataSource.kt @@ -0,0 +1,25 @@ +package org.futo.circles.auth.feature.uia.stages.validate_token + +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.LOGIN_REGISTRATION_TOKEN_TYPE +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.TYPE_PARAM_KEY +import org.futo.circles.auth.feature.uia.UIADataSourceProvider +import org.futo.circles.core.extensions.Response +import org.matrix.android.sdk.api.auth.registration.RegistrationResult +import javax.inject.Inject + +class ValidateTokenDataSource @Inject constructor() { + + private val uiaDataSource = UIADataSourceProvider.getDataSourceOrThrow() + + suspend fun validateToken(token: String): Response<RegistrationResult> = + uiaDataSource.performUIAStage( + mapOf( + TYPE_PARAM_KEY to LOGIN_REGISTRATION_TOKEN_TYPE, + TOKEN_PARAM_KEY to token + ) + ) + + companion object { + private const val TOKEN_PARAM_KEY = "token" + } +} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/validate_token/ValidateTokenFragment.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/validate_token/ValidateTokenFragment.kt similarity index 96% rename from auth/src/main/java/org/futo/circles/auth/feature/sign_up/validate_token/ValidateTokenFragment.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/stages/validate_token/ValidateTokenFragment.kt index 32cec67c49c678e3696677eb7d0469a047565a16..a7131300009e60d58870219acd1b3c0a89c34d39 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/validate_token/ValidateTokenFragment.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/validate_token/ValidateTokenFragment.kt @@ -1,4 +1,4 @@ -package org.futo.circles.auth.feature.sign_up.validate_token +package org.futo.circles.auth.feature.uia.stages.validate_token import android.os.Bundle import android.view.View diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/validate_token/ValidateTokenViewModel.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/validate_token/ValidateTokenViewModel.kt similarity index 91% rename from auth/src/main/java/org/futo/circles/auth/feature/sign_up/validate_token/ValidateTokenViewModel.kt rename to auth/src/main/java/org/futo/circles/auth/feature/uia/stages/validate_token/ValidateTokenViewModel.kt index a17e35e81441ae7bcaa0a1380dd15b0ef83b81bd..05c34f9ebba287d1c949c2a3a00e62d343bb3d9e 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/validate_token/ValidateTokenViewModel.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/stages/validate_token/ValidateTokenViewModel.kt @@ -1,4 +1,4 @@ -package org.futo.circles.auth.feature.sign_up.validate_token +package org.futo.circles.auth.feature.uia.stages.validate_token import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel diff --git a/auth/src/main/java/org/futo/circles/auth/model/AuthUIAScreenNavigationEvent.kt b/auth/src/main/java/org/futo/circles/auth/model/AuthUIAScreenNavigationEvent.kt new file mode 100644 index 0000000000000000000000000000000000000000..c90c32c354d0559c78a7a71f8cdaa0340a595fe7 --- /dev/null +++ b/auth/src/main/java/org/futo/circles/auth/model/AuthUIAScreenNavigationEvent.kt @@ -0,0 +1,7 @@ +package org.futo.circles.auth.model + +enum class AuthUIAScreenNavigationEvent { + Home, + ConfigureWorkspace, + PassPhrase +} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/model/ConfirmationType.kt b/auth/src/main/java/org/futo/circles/auth/model/ConfirmationType.kt index c1e5c6799db06ffbab997b9ce1f455e22e0a154b..ac7d8d417757e40b90ef292a5ba92ef4ecc4540b 100644 --- a/auth/src/main/java/org/futo/circles/auth/model/ConfirmationType.kt +++ b/auth/src/main/java/org/futo/circles/auth/model/ConfirmationType.kt @@ -24,11 +24,17 @@ data class LogOut( data class RemoveSession( override val titleRes: Int = R.string.remove_session, override val messageRes: Int = R.string.remove_session_message, - override val positiveButtonRes: Int = org.futo.circles.core.R.string.remove + override val positiveButtonRes: Int = org.futo.circles.core.R.string.remove ) : ConfirmationType(titleRes, messageRes, positiveButtonRes) data class ResetKeys( override val titleRes: Int = R.string.reset_keys, override val messageRes: Int = R.string.reset_keys_message, override val positiveButtonRes: Int = R.string.confirm +) : ConfirmationType(titleRes, messageRes, positiveButtonRes) + +data class ForgotPassword( + override val titleRes: Int = R.string.forgot_password, + override val messageRes: Int = R.string.forgot_password_message, + override val positiveButtonRes: Int = R.string.confirm ) : ConfirmationType(titleRes, messageRes, positiveButtonRes) \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/model/CustomUIAuth.kt b/auth/src/main/java/org/futo/circles/auth/model/CustomUIAuth.kt index 13c641c2b3732d1709ddc70a30477d07e4f4e61b..098c3cafac5bed5f82b24707d344687f73417ffb 100644 --- a/auth/src/main/java/org/futo/circles/auth/model/CustomUIAuth.kt +++ b/auth/src/main/java/org/futo/circles/auth/model/CustomUIAuth.kt @@ -1,6 +1,6 @@ package org.futo.circles.auth.model -import org.futo.circles.auth.base.BaseLoginStagesDataSource +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.USER_PARAM_KEY import org.futo.circles.core.provider.MatrixSessionProvider import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.util.JsonDict @@ -15,8 +15,7 @@ data class CustomUIAuth( override fun asMap(): Map<String, *> = auth.toMutableMap().apply { this["session"] = session - this[BaseLoginStagesDataSource.USER_PARAM_KEY] = - MatrixSessionProvider.currentSession?.myUserId ?: "" + this[USER_PARAM_KEY] = MatrixSessionProvider.currentSession?.myUserId ?: "" } } diff --git a/auth/src/main/java/org/futo/circles/auth/model/PasswordModeArg.kt b/auth/src/main/java/org/futo/circles/auth/model/PasswordModeArg.kt deleted file mode 100644 index b3c401020eb142f1ecc30c04ec5f6862f108e33e..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/model/PasswordModeArg.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.futo.circles.auth.model - -enum class PasswordModeArg { - LoginPasswordStage, - LoginDirect, - LoginBsSpekeStage, - ReAuthPassword, - ReAuthBsSpekeLogin, - ReAuthBsSpekeSignup, - SignupPasswordStage, - SignupBsSpekeStage -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/model/TermsModeArg.kt b/auth/src/main/java/org/futo/circles/auth/model/TermsModeArg.kt deleted file mode 100644 index 40b08bb4f04871ee41d014f20b36c2410d300003..0000000000000000000000000000000000000000 --- a/auth/src/main/java/org/futo/circles/auth/model/TermsModeArg.kt +++ /dev/null @@ -1,4 +0,0 @@ -package org.futo.circles.auth.model - - -enum class TermsModeArg { Login, Signup, ReAuth } \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/model/UIAFlowType.kt b/auth/src/main/java/org/futo/circles/auth/model/UIAFlowType.kt new file mode 100644 index 0000000000000000000000000000000000000000..0dcc9c4e3c62a64511420a37195aa5a0bdd78f2d --- /dev/null +++ b/auth/src/main/java/org/futo/circles/auth/model/UIAFlowType.kt @@ -0,0 +1,3 @@ +package org.futo.circles.auth.model + +enum class UIAFlowType { Login, Signup, ReAuth, ForgotPassword } \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/model/UIANavigationEvent.kt b/auth/src/main/java/org/futo/circles/auth/model/UIANavigationEvent.kt new file mode 100644 index 0000000000000000000000000000000000000000..e839433b95f10531f2736945cd9f4fc40b2e47af --- /dev/null +++ b/auth/src/main/java/org/futo/circles/auth/model/UIANavigationEvent.kt @@ -0,0 +1,11 @@ +package org.futo.circles.auth.model + + +enum class UIANavigationEvent { + TokenValidation, + Subscription, + AcceptTerm, + ValidateEmail, + Password, + Username +} \ No newline at end of file diff --git a/auth/src/main/res/layout/fragment_login_stages.xml b/auth/src/main/res/layout/dialog_fragment_uia.xml similarity index 97% rename from auth/src/main/res/layout/fragment_login_stages.xml rename to auth/src/main/res/layout/dialog_fragment_uia.xml index dfdf2442a4bad9bd92f45501b6c7b906b126f4ac..e1fc82dc65361ace8efe120613a20af4bb6d511a 100644 --- a/auth/src/main/res/layout/fragment_login_stages.xml +++ b/auth/src/main/res/layout/dialog_fragment_uia.xml @@ -37,7 +37,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/toolbarDivider" - app:navGraph="@navigation/log_in_nav_graph" /> + app:navGraph="@navigation/uia_nav_graph" /> </androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/auth/src/main/res/layout/fragment_log_in.xml b/auth/src/main/res/layout/fragment_log_in.xml index d1dc63aea7da5c1b998bb4d4c0c4d71ecd5e9136..748cae09c33131182cf4a47b759afb6a998d44cb 100644 --- a/auth/src/main/res/layout/fragment_log_in.xml +++ b/auth/src/main/res/layout/fragment_log_in.xml @@ -142,6 +142,19 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tilUserName" /> + <com.google.android.material.button.MaterialButton + android:id="@+id/btnForgotPassword" + style="@style/Widget.Material3.Button.TextButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:text="@string/forgot_password_question" + android:textColor="@color/blue" + android:textSize="17sp" + android:textStyle="bold" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/btnLogin" /> <TextView android:id="@+id/tvResumeSession" @@ -155,7 +168,7 @@ app:layout_constraintBottom_toTopOf="@id/rvSwitchUsers" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/btnLogin" + app:layout_constraintTop_toBottomOf="@+id/btnForgotPassword" app:layout_constraintVertical_chainStyle="packed" tools:visibility="visible" /> diff --git a/auth/src/main/res/layout/fragment_select_sign_up_type.xml b/auth/src/main/res/layout/fragment_select_sign_up_type.xml deleted file mode 100644 index c6e1fef7a6e78dd825d0682b64452a51c5f488f0..0000000000000000000000000000000000000000 --- a/auth/src/main/res/layout/fragment_select_sign_up_type.xml +++ /dev/null @@ -1,98 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<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:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingHorizontal="24dp" - android:paddingVertical="8dp"> - - <androidx.constraintlayout.widget.Guideline - android:id="@+id/guidelineLogo" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal" - app:layout_constraintGuide_percent="0.3" /> - - <ImageView - android:id="@+id/ivLogo" - style="@style/AuthLogoStyle" - android:layout_width="0dp" - android:layout_height="0dp" - app:layout_constraintBottom_toTopOf="@id/guidelineLogo" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - - - <TextView - android:id="@+id/tvServerTitle" - style="@style/headline" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:text="@string/server" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/guidelineLogo" /> - - <RadioGroup - android:id="@+id/serverDomainGroup" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/tvServerTitle" /> - - - <LinearLayout - android:id="@+id/lButtonsContainer" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginTop="24dp" - android:orientation="vertical" - android:visibility="gone" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/serverDomainGroup" - tools:visibility="visible"> - - <org.futo.circles.core.view.LoadingButton - android:id="@+id/btnFree" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="16dp" - android:text="@string/sign_up_for_free" /> - - <TextView - android:id="@+id/tvOr" - style="@style/headline" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:gravity="center" - android:text="@string/or" /> - - <org.futo.circles.core.view.LoadingButton - android:id="@+id/btnSubscription" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:text="@string/sign_up_with_subscription" /> - - </LinearLayout> - - <ProgressBar - android:id="@+id/flowProgress" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:visibility="gone" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/serverDomainGroup" - tools:visibility="visible" /> - - -</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/auth/src/main/res/layout/fragment_sign_up.xml b/auth/src/main/res/layout/fragment_sign_up.xml index 22f65cecca3bb69516579e638d699dc453ec4949..c6e1fef7a6e78dd825d0682b64452a51c5f488f0 100644 --- a/auth/src/main/res/layout/fragment_sign_up.xml +++ b/auth/src/main/res/layout/fragment_sign_up.xml @@ -1,42 +1,98 @@ <?xml version="1.0" encoding="utf-8"?> <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:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:paddingHorizontal="24dp" + android:paddingVertical="8dp"> - <com.google.android.material.appbar.MaterialToolbar - android:id="@+id/toolbar" + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guidelineLogo" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.3" /> + + <ImageView + android:id="@+id/ivLogo" + style="@style/AuthLogoStyle" android:layout_width="0dp" - android:layout_height="?attr/actionBarSize" + android:layout_height="0dp" + app:layout_constraintBottom_toTopOf="@id/guidelineLogo" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:navigationContentDescription="@string/back" - app:navigationIcon="?attr/homeAsUpIndicator" - app:subtitleCentered="true" - app:title="@string/sign_up" - app:titleCentered="true" /> - - <View - android:id="@+id/toolbarDivider" - android:layout_width="0dp" - android:layout_height="@dimen/divider_height" - android:background="@color/divider_color" + app:layout_constraintTop_toTopOf="parent" /> + + + <TextView + android:id="@+id/tvServerTitle" + style="@style/headline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:text="@string/server" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/toolbar" /> + app:layout_constraintTop_toBottomOf="@id/guidelineLogo" /> + + <RadioGroup + android:id="@+id/serverDomainGroup" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/tvServerTitle" /> + - <androidx.fragment.app.FragmentContainerView - android:id="@+id/nav_host_fragment" - android:name="androidx.navigation.fragment.NavHostFragment" + <LinearLayout + android:id="@+id/lButtonsContainer" android:layout_width="0dp" - android:layout_height="0dp" - app:defaultNavHost="true" + android:layout_height="wrap_content" + android:layout_marginTop="24dp" + android:orientation="vertical" + android:visibility="gone" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/serverDomainGroup" + tools:visibility="visible"> + + <org.futo.circles.core.view.LoadingButton + android:id="@+id/btnFree" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="16dp" + android:text="@string/sign_up_for_free" /> + + <TextView + android:id="@+id/tvOr" + style="@style/headline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:gravity="center" + android:text="@string/or" /> + + <org.futo.circles.core.view.LoadingButton + android:id="@+id/btnSubscription" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:text="@string/sign_up_with_subscription" /> + + </LinearLayout> + + <ProgressBar + android:id="@+id/flowProgress" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/toolbarDivider" - app:navGraph="@navigation/sign_up_nav_graph" /> + app:layout_constraintTop_toBottomOf="@id/serverDomainGroup" + tools:visibility="visible" /> </androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/auth/src/main/res/navigation/log_in_nav_graph.xml b/auth/src/main/res/navigation/log_in_nav_graph.xml deleted file mode 100644 index ecbed868195ba4ae37ba79e995078d3c0b27750b..0000000000000000000000000000000000000000 --- a/auth/src/main/res/navigation/log_in_nav_graph.xml +++ /dev/null @@ -1,130 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<navigation 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/log_in_nav_graph" - app:startDestination="@id/emptyFragment"> - - <action - android:id="@+id/to_acceptTerms" - app:destination="@id/acceptTermsFragmentLogin"> - - <argument - android:name="mode" - android:defaultValue="Login" - app:argType="org.futo.circles.auth.model.TermsModeArg" - app:nullable="false" /> - - </action> - - <action - android:id="@+id/to_ReAuthAcceptTerms" - app:destination="@id/acceptTermsFragmentLogin"> - - <argument - android:name="mode" - android:defaultValue="ReAuth" - app:argType="org.futo.circles.auth.model.TermsModeArg" - app:nullable="false" /> - - </action> - - <action - android:id="@+id/to_direct_login" - app:destination="@id/passwordFragmentLogin"> - - <argument - android:name="mode" - android:defaultValue="LoginDirect" - app:argType="org.futo.circles.auth.model.PasswordModeArg" - app:nullable="false" /> - - </action> - - <action - android:id="@+id/to_password" - app:destination="@id/passwordFragmentLogin"> - - <argument - android:name="mode" - android:defaultValue="LoginPasswordStage" - app:argType="org.futo.circles.auth.model.PasswordModeArg" - app:nullable="false" /> - - </action> - - <action - android:id="@+id/to_bsspeke" - app:destination="@id/passwordFragmentLogin"> - - <argument - android:name="mode" - android:defaultValue="LoginBsSpekeStage" - app:argType="org.futo.circles.auth.model.PasswordModeArg" - app:nullable="false" /> - - </action> - - <action - android:id="@+id/to_reAuthPassword" - app:destination="@id/passwordFragmentLogin"> - - <argument - android:name="mode" - android:defaultValue="ReAuthPassword" - app:argType="org.futo.circles.auth.model.PasswordModeArg" - app:nullable="false" /> - - </action> - - <action - android:id="@+id/to_reAuthBsSpekeLogin" - app:destination="@id/passwordFragmentLogin"> - - <argument - android:name="mode" - android:defaultValue="ReAuthBsSpekeLogin" - app:argType="org.futo.circles.auth.model.PasswordModeArg" - app:nullable="false" /> - - </action> - - <action - android:id="@+id/to_reAuthBsSpekeSignup" - app:destination="@id/passwordFragmentLogin"> - - <argument - android:name="mode" - android:defaultValue="ReAuthBsSpekeSignup" - app:argType="org.futo.circles.auth.model.PasswordModeArg" - app:nullable="false" /> - - </action> - - <fragment - android:id="@+id/acceptTermsFragmentLogin" - android:name="org.futo.circles.auth.feature.sign_up.terms.AcceptTermsFragment" - tools:layout="@layout/fragment_accept_terms"> - - <argument - android:name="mode" - app:argType="org.futo.circles.auth.model.TermsModeArg" - app:nullable="false" /> - - </fragment> - <fragment - android:id="@+id/passwordFragmentLogin" - android:name="org.futo.circles.auth.feature.sign_up.password.PasswordFragment" - tools:layout="@layout/fragment_password"> - - <argument - android:name="mode" - app:argType="org.futo.circles.auth.model.PasswordModeArg" - app:nullable="false" /> - - </fragment> - <fragment - android:id="@+id/emptyFragment" - android:name="org.futo.circles.auth.feature.log_in.EmptyFragment" - tools:layout="@layout/fragment_empty" /> -</navigation> \ No newline at end of file diff --git a/auth/src/main/res/navigation/log_in_sessions_nav_graph.xml b/auth/src/main/res/navigation/log_in_sessions_nav_graph.xml index bee1fe4e5e88206bda7b2f3e726a0d9123b055f4..fc797603864930459cf1eaa1763e3eeefb3fae7a 100644 --- a/auth/src/main/res/navigation/log_in_sessions_nav_graph.xml +++ b/auth/src/main/res/navigation/log_in_sessions_nav_graph.xml @@ -11,8 +11,8 @@ android:label="ActiveSessionsDialogFragment" tools:layout="@layout/dialog_fragment_active_sessions"> <action - android:id="@+id/to_reAuthStagesDialogFragment" - app:destination="@id/reAuthStagesDialogFragment" /> + android:id="@+id/to_uiaDialogFragment" + app:destination="@id/UIADialogFragment" /> <action android:id="@+id/to_verifySessionDialogFragment" @@ -38,7 +38,7 @@ </dialog> <dialog - android:id="@+id/reAuthStagesDialogFragment" - android:name="org.futo.circles.auth.feature.reauth.ReAuthStagesDialogFragment" - tools:layout="@layout/fragment_login_stages" /> + android:id="@+id/UIADialogFragment" + android:name="org.futo.circles.auth.feature.uia.UIADialogFragment" + tools:layout="@layout/dialog_fragment_uia" /> </navigation> diff --git a/auth/src/main/res/navigation/nav_graph_auth.xml b/auth/src/main/res/navigation/nav_graph_auth.xml index 70937bba43d1467a34a17b41f04db31465c5ffb6..f397b02cd2b5cf4678712103b7b63a55e53fcd2d 100644 --- a/auth/src/main/res/navigation/nav_graph_auth.xml +++ b/auth/src/main/res/navigation/nav_graph_auth.xml @@ -11,39 +11,35 @@ android:label="Log In" tools:layout="@layout/fragment_log_in"> <action - android:id="@+id/to_signUpFragment" - app:destination="@id/signUpFragment" /> - <action - android:id="@+id/to_loginStagesFragment" - app:destination="@id/loginStagesFragment" /> + android:id="@+id/to_uiaFragment" + app:destination="@id/uiaFragment" /> <action android:id="@+id/to_homeFragment" app:destination="@id/homeFragment" app:popUpTo="@id/logInFragment" app:popUpToInclusive="true" /> + <action + android:id="@+id/to_signUpFragment" + app:destination="@id/signUpFragment" /> </fragment> - <fragment - android:id="@+id/loginStagesFragment" - android:name="org.futo.circles.auth.feature.log_in.stages.LogInStagesFragment" - tools:layout="@layout/fragment_login_stages"> + + <dialog + android:id="@+id/uiaFragment" + android:name="org.futo.circles.auth.feature.uia.UIADialogFragment" + tools:layout="@layout/dialog_fragment_uia"> <action android:id="@+id/to_homeFragment" app:destination="@id/homeFragment" app:popUpTo="@id/logInFragment" app:popUpToInclusive="true" /> - </fragment> - <fragment - android:id="@+id/signUpFragment" - android:name="org.futo.circles.auth.feature.sign_up.SignUpFragment" - android:label="Sign up" - tools:layout="@layout/fragment_sign_up"> + <action android:id="@+id/to_ConfigureWorkspace" app:destination="@id/configureWorkspaceFragment" app:popUpTo="@id/logInFragment" app:popUpToInclusive="true" /> - </fragment> + </dialog> <fragment android:id="@+id/setupProfileFragment" android:name="org.futo.circles.auth.feature.profile.setup.SetupProfileFragment" @@ -66,4 +62,12 @@ app:popUpTo="@id/configureWorkspaceFragment" app:popUpToInclusive="true" /> </fragment> + <fragment + android:id="@+id/signUpFragment" + android:name="org.futo.circles.auth.feature.sign_up.SignUpFragment" + tools:layout="@layout/fragment_sign_up"> + <action + android:id="@+id/to_uiaFragment" + app:destination="@id/uiaFragment" /> + </fragment> </navigation> diff --git a/auth/src/main/res/navigation/sign_up_nav_graph.xml b/auth/src/main/res/navigation/sign_up_nav_graph.xml deleted file mode 100644 index 37e439db00db11b4550c4170fa5654a925ff2ea3..0000000000000000000000000000000000000000 --- a/auth/src/main/res/navigation/sign_up_nav_graph.xml +++ /dev/null @@ -1,102 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<navigation 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/sign_up_nav_graph" - app:startDestination="@id/selectSignUpTypeFragment"> - - <action - android:id="@+id/to_validateToken" - app:destination="@id/validateTokenFragment" /> - - <action - android:id="@+id/to_validateEmail" - app:destination="@id/validateEmailFragment" /> - - <action - android:id="@+id/to_acceptTerms" - app:destination="@id/acceptTermsFragment" /> - - <action - android:id="@+id/to_subscriptions" - app:destination="@id/subscriptionStageFragment" /> - - <action - android:id="@+id/to_username" - app:destination="@id/usernameFragment" /> - - <action - android:id="@+id/to_password" - app:destination="@id/passwordFragment"> - - <argument - android:name="mode" - android:defaultValue="SignupPasswordStage" - app:argType="org.futo.circles.auth.model.PasswordModeArg" - app:nullable="false" /> - - </action> - - <action - android:id="@+id/to_bsspeke" - app:destination="@id/passwordFragment"> - - <argument - android:name="mode" - android:defaultValue="SignupBsSpekeStage" - app:argType="org.futo.circles.auth.model.PasswordModeArg" - app:nullable="false" /> - - </action> - - <fragment - android:id="@+id/selectSignUpTypeFragment" - android:name="org.futo.circles.auth.feature.sign_up.sign_up_type.SelectSignUpTypeFragment" - tools:layout="@layout/fragment_select_sign_up_type" /> - - <fragment - android:id="@+id/validateTokenFragment" - android:name="org.futo.circles.auth.feature.sign_up.validate_token.ValidateTokenFragment" - tools:layout="@layout/fragment_validate_token" /> - - <fragment - android:id="@+id/validateEmailFragment" - android:name="org.futo.circles.auth.feature.sign_up.validate_email.ValidateEmailFragment" - tools:layout="@layout/fragment_validate_email" /> - - <fragment - android:id="@+id/acceptTermsFragment" - android:name="org.futo.circles.auth.feature.sign_up.terms.AcceptTermsFragment" - tools:layout="@layout/fragment_accept_terms"> - - <argument - android:name="mode" - android:defaultValue="Signup" - app:argType="org.futo.circles.auth.model.TermsModeArg" - app:nullable="false" /> - - </fragment> - - <fragment - android:id="@+id/subscriptionStageFragment" - android:name="org.futo.circles.auth.feature.sign_up.subscription_stage.SubscriptionStageFragment" - tools:layout="@layout/fragment_subscription_stage" /> - <fragment - android:id="@+id/passwordFragment" - android:name="org.futo.circles.auth.feature.sign_up.password.PasswordFragment" - tools:layout="@layout/fragment_password"> - - <argument - android:name="mode" - app:argType="org.futo.circles.auth.model.PasswordModeArg" - app:nullable="false" /> - - </fragment> - - <fragment - android:id="@+id/usernameFragment" - android:name="org.futo.circles.auth.feature.sign_up.username.UsernameFragment" - tools:layout="@layout/fragment_username" /> - - -</navigation> \ No newline at end of file diff --git a/auth/src/main/res/navigation/uia_nav_graph.xml b/auth/src/main/res/navigation/uia_nav_graph.xml new file mode 100644 index 0000000000000000000000000000000000000000..50842c7330d467183b74ecf29bca0b60a2bff7ce --- /dev/null +++ b/auth/src/main/res/navigation/uia_nav_graph.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="utf-8"?> +<navigation 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/uia_nav_graph" + app:startDestination="@id/emptyFragment"> + + <action + android:id="@+id/to_validateToken" + app:destination="@id/validateTokenFragment" /> + + <action + android:id="@+id/to_validateEmail" + app:destination="@id/validateEmailFragment" /> + + <action + android:id="@+id/to_subscriptions" + app:destination="@id/subscriptionStageFragment" /> + + <action + android:id="@+id/to_username" + app:destination="@id/usernameFragment" /> + + + <action + android:id="@+id/to_acceptTerms" + app:destination="@id/acceptTermsFragment" /> + + <action + android:id="@+id/to_password" + app:destination="@id/passwordFragment" /> + + + <fragment + android:id="@+id/validateTokenFragment" + android:name="org.futo.circles.auth.feature.uia.stages.validate_token.ValidateTokenFragment" + tools:layout="@layout/fragment_validate_token" /> + + <fragment + android:id="@+id/validateEmailFragment" + android:name="org.futo.circles.auth.feature.uia.stages.validate_email.ValidateEmailFragment" + tools:layout="@layout/fragment_validate_email" /> + + <fragment + android:id="@+id/acceptTermsFragment" + android:name="org.futo.circles.auth.feature.uia.stages.terms.AcceptTermsFragment" + tools:layout="@layout/fragment_accept_terms" /> + + <fragment + android:id="@+id/subscriptionStageFragment" + android:name="org.futo.circles.auth.feature.uia.stages.subscription_stage.SubscriptionStageFragment" + tools:layout="@layout/fragment_subscription_stage" /> + <fragment + android:id="@+id/passwordFragment" + android:name="org.futo.circles.auth.feature.uia.stages.password.PasswordFragment" + tools:layout="@layout/fragment_password" /> + + <fragment + android:id="@+id/usernameFragment" + android:name="org.futo.circles.auth.feature.uia.stages.username.UsernameFragment" + tools:layout="@layout/fragment_username" /> + + <fragment + android:id="@+id/emptyFragment" + android:name="org.futo.circles.auth.feature.uia.stages.EmptyFragment" + tools:layout="@layout/fragment_empty" /> + + +</navigation> \ No newline at end of file diff --git a/auth/src/main/res/values/strings.xml b/auth/src/main/res/values/strings.xml index 946848f9ac26a139ce791252d89fe2969e21550e..a0368d8b476996342e9b164c2d473eccd944ca65 100644 --- a/auth/src/main/res/values/strings.xml +++ b/auth/src/main/res/values/strings.xml @@ -8,7 +8,7 @@ <string name="initial_sync">Initial sync</string> <string name="not_found_login_flow_for_user">Log In flow for user not found</string> <string name="unsupported_login_method">Unsupported login method</string> - <string name="discard_current_login_progress">Discard current login progress?</string> + <string name="discard_current_auth_progress">Discard current auth progress?</string> <string name="set_password">Set password</string> <string name="log_in">Log In</string> <string name="wrong_signup_config">Wrong signup config!</string> @@ -16,7 +16,6 @@ <string name="eu_server_format"><![CDATA[<b>EU</b> - (%s)]]></string> <string name="sign_up_using_active_subscription">Sign Up using your active subscription</string> <string name="sign_up">Sign Up</string> - <string name="not_supported_stage_format">Not supported stage %s</string> <string name="sign_up_stage_subtitle_format">Stage %1$d of %2$d</string> <string name="no_backup_message">Keys backup not found</string> <string name="username_can_not_be_empty">Username can not be empty.</string> @@ -49,7 +48,6 @@ <string name="importing_keys">Importing keys.</string> <string name="decrypting_key">Decrypting key.</string> <string name="failed_to_get_restore_keys_version">Failed to get latest restore keys version.</string> - <string name="discard_current_registration_progress">Discard current registration progress?</string> <string name="domain">Domain</string> <string name="accept_terms_to_continue">Accept terms to continue</string> <string name="username">username</string> @@ -106,6 +104,7 @@ <string name="remove_session_message">In order to sign out from this session you should confirm your auth.</string> <string name="reset_keys">Reset keys</string> <string name="reset_keys_message">Confirm auth to reset keys and enable cross signing</string> + <string name="forgot_password_message">After verifying your email the new password and key backup will be set.\n\nWarning: With a new key backup you will not be able to see all your previous encrypted messages, this action can not be undone.</string> <string name="confirm">Confirm</string> <string name="failed_to_read_qr_code">Failed to read QR code</string> <string name="current_session">Current session</string> @@ -176,6 +175,8 @@ Or, think about all of your friends across all of the places you\'ve ever lived. <string name="username_not_found">Username not found</string> <string name="invalid_validation_code">Invalid validation code</string> <string name="invalid_password">Invalid password</string> + <string name="forgot_password_question">Forgot password?</string> + <string name="forgot_password">Forgot password</string> <plurals name="days"> diff --git a/core/build.gradle b/core/build.gradle index e315a488049d4d8a470e92d420de0d26260f1bf6..25be662b05a876935384d957d7f90ed66ef9629b 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -76,7 +76,7 @@ dependencies { kapt "com.google.dagger:hilt-compiler:$rootProject.ext.hilt_version" //Matrix - api('org.futo.gitlab.circles:matrix-android-sdk:v1.6.10.19@aar') { + api('org.futo.gitlab.circles:matrix-android-sdk:v1.6.10.21@aar') { transitive = true }