diff --git a/app/src/main/java/com/futo/circles/extensions/LiveDataExtensions.kt b/app/src/main/java/com/futo/circles/extensions/LiveDataExtensions.kt index 91ffff1f547b5e740c18005df1906c11ac18b6be..037a1c64a83b0a4ec2b35dd243793be5c0a25749 100644 --- a/app/src/main/java/com/futo/circles/extensions/LiveDataExtensions.kt +++ b/app/src/main/java/com/futo/circles/extensions/LiveDataExtensions.kt @@ -8,7 +8,7 @@ import org.matrix.android.sdk.api.failure.Failure fun <T> SingleEventLiveData<Response<T>>.observeResponse( hasLoadingState: HasLoadingState, - success: (T) -> Unit, + success: (T) -> Unit = {}, error: ((String) -> Unit)? = null, onRequestInvoked: (() -> Unit)? = null ) { diff --git a/app/src/main/java/com/futo/circles/feature/log_in/LogInViewModel.kt b/app/src/main/java/com/futo/circles/feature/log_in/LogInViewModel.kt index 4470e2e0377d693b3ee25973fdecf9da7700c086..fe1564a11bcfea4972c81149e924d497856b73d4 100644 --- a/app/src/main/java/com/futo/circles/feature/log_in/LogInViewModel.kt +++ b/app/src/main/java/com/futo/circles/feature/log_in/LogInViewModel.kt @@ -5,6 +5,7 @@ import com.futo.circles.core.SingleEventLiveData import com.futo.circles.extensions.Response import com.futo.circles.extensions.launchBg import com.futo.circles.feature.log_in.data_source.LoginDataSource +import org.matrix.android.sdk.api.auth.data.LoginFlowResult import org.matrix.android.sdk.api.session.Session class LogInViewModel( @@ -12,7 +13,7 @@ class LogInViewModel( ) : ViewModel() { var loginResultLiveData = SingleEventLiveData<Response<Session>>() - var signUpEventResultLiveData = SingleEventLiveData<Response<Unit?>>() + var signUpEventResultLiveData = SingleEventLiveData<Response<LoginFlowResult>>() fun logIn(name: String, password: String) { launchBg { diff --git a/app/src/main/java/com/futo/circles/feature/log_in/data_source/LoginDataSource.kt b/app/src/main/java/com/futo/circles/feature/log_in/data_source/LoginDataSource.kt index 69f54f5f277ca6ea5f4307c5a861810abf34336e..b978b92d592f46c51d58cbaf0b7c092d24ee9167 100644 --- a/app/src/main/java/com/futo/circles/feature/log_in/data_source/LoginDataSource.kt +++ b/app/src/main/java/com/futo/circles/feature/log_in/data_source/LoginDataSource.kt @@ -5,16 +5,11 @@ import android.net.Uri import com.futo.circles.BuildConfig import com.futo.circles.R import com.futo.circles.extensions.createResult -import com.futo.circles.feature.sign_up.data_source.SignUpDataSource import com.futo.circles.provider.MatrixInstanceProvider import com.futo.circles.provider.MatrixSessionProvider import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig -import org.matrix.android.sdk.api.auth.registration.RegistrationResult -class LoginDataSource( - private val context: Context, - private val signUpDataSource: SignUpDataSource -) { +class LoginDataSource(private val context: Context) { private val homeServerConnectionConfig by lazy { HomeServerConnectionConfig @@ -42,9 +37,5 @@ class LoginDataSource( suspend fun startSignUp() = createResult { authService.getLoginFlow(homeServerConnectionConfig) - (authService.getRegistrationWizard() - .getRegistrationFlow() as? RegistrationResult.FlowResponse)?.let { - signUpDataSource.startNewRegistration(it.flowResult.missingStages) - } } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/circles/feature/sign_up/SignUpFragment.kt b/app/src/main/java/com/futo/circles/feature/sign_up/SignUpFragment.kt index 5edbfe360069870a59c2d846eb4f2b30669d8197..f794ab3cd3909c93d0d6e86246f54a50b187988a 100644 --- a/app/src/main/java/com/futo/circles/feature/sign_up/SignUpFragment.kt +++ b/app/src/main/java/com/futo/circles/feature/sign_up/SignUpFragment.kt @@ -2,11 +2,15 @@ package com.futo.circles.feature.sign_up import android.os.Bundle import android.view.View +import androidx.activity.OnBackPressedCallback import androidx.fragment.app.Fragment +import androidx.navigation.findNavController import androidx.navigation.fragment.findNavController import by.kirich1409.viewbindingdelegate.viewBinding import com.futo.circles.R import com.futo.circles.databinding.SignUpFragmentBinding +import com.futo.circles.extensions.observeData +import com.futo.circles.feature.sign_up.data_source.NavigationEvents import org.koin.androidx.viewmodel.ext.android.viewModel class SignUpFragment : Fragment(R.layout.sign_up_fragment) { @@ -16,7 +20,47 @@ class SignUpFragment : Fragment(R.layout.sign_up_fragment) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - binding.toolbar.setNavigationOnClickListener { findNavController().popBackStack() } + binding.toolbar.setNavigationOnClickListener { activity?.onBackPressed() } + + setupObservers() + setupBackPressCallback() + } + + private fun setupObservers() { + viewModel.subtitleLiveData.observeData(this) { + binding.toolbar.subtitle = it + } + viewModel.navigationLiveData.observeData(this) { + handleNavigation(it) + } + } + + private fun setupBackPressCallback() { + activity?.onBackPressedDispatcher + ?.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + val canPop = binding.navHostFragment.findNavController().popBackStack() + if (!canPop) { + isEnabled = false + requireActivity().onBackPressed() + } + } + } + ) + } + + private fun handleNavigation(event: NavigationEvents) { + val childNavigationController = binding.navHostFragment.findNavController() + + when (event) { + NavigationEvents.TokenValidation -> childNavigationController.navigate(R.id.to_validateToken) + NavigationEvents.AcceptTerm -> childNavigationController.navigate(R.id.to_acceptTerms) + NavigationEvents.VerifyEmail -> childNavigationController.navigate(R.id.to_createAccount) + NavigationEvents.SetupAvatar -> TODO() + NavigationEvents.SetupCircles -> TODO() + NavigationEvents.FinishSignUp -> findNavController() + .navigate(SignUpFragmentDirections.toBottomNavigationFragment()) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/circles/feature/sign_up/SignUpViewModel.kt b/app/src/main/java/com/futo/circles/feature/sign_up/SignUpViewModel.kt index 7677ea8b1040209b593ba9116a2aad4d2b99a5f9..947bd48f696fa5700896f871eff6a6afcdfe7a7e 100644 --- a/app/src/main/java/com/futo/circles/feature/sign_up/SignUpViewModel.kt +++ b/app/src/main/java/com/futo/circles/feature/sign_up/SignUpViewModel.kt @@ -7,5 +7,8 @@ class SignUpViewModel( datasource: SignUpDataSource ) : ViewModel() { + val subtitleLiveData = datasource.subtitleLiveData + + val navigationLiveData = datasource.navigationLiveData } \ No newline at end of file diff --git a/app/src/main/java/com/futo/circles/feature/sign_up/data_source/SignUpDataSource.kt b/app/src/main/java/com/futo/circles/feature/sign_up/data_source/SignUpDataSource.kt index cf5da59c0d256f29c11230efade6f617f197ad68..04a26cf58a9db7426fe785aaf4a7b41de8ea768b 100644 --- a/app/src/main/java/com/futo/circles/feature/sign_up/data_source/SignUpDataSource.kt +++ b/app/src/main/java/com/futo/circles/feature/sign_up/data_source/SignUpDataSource.kt @@ -1,24 +1,88 @@ package com.futo.circles.feature.sign_up.data_source -import com.futo.circles.extensions.getPendingSignUpSessionId -import com.futo.circles.provider.MatrixInstanceProvider +import android.content.Context +import androidx.lifecycle.MutableLiveData +import com.futo.circles.R +import com.futo.circles.core.SingleEventLiveData +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 -class SignUpDataSource { +enum class ExtraSignUpStages { Avatar, Circles } + +enum class NavigationEvents { TokenValidation, AcceptTerm, VerifyEmail, SetupAvatar, SetupCircles, FinishSignUp } + +class SignUpDataSource( + private val context: Context +) { + + val subtitleLiveData = MutableLiveData<String>() + val navigationLiveData = SingleEventLiveData<NavigationEvents>() private val stagesToComplete = mutableListOf<Stage>() + private val completedStages = mutableListOf<Stage>() + + var currentStage: Stage? = null + private set - private val registrationWizard by lazy { - MatrixInstanceProvider.matrix.authenticationService().getRegistrationWizard() - } - fun startNewRegistration(stages: List<Stage>) { + fun startSignUpStages(stages: List<Stage>) { + currentStage = null + completedStages.clear() stagesToComplete.clear() + stagesToComplete.addAll(stages) + ExtraSignUpStages.values().forEach { + stagesToComplete.add(Stage.Other(false, it.name, null)) + } + navigateToNextStage() + } + + fun stageCompleted(result: RegistrationResult?) { + (result as? RegistrationResult.Success)?.let { + finishRegistration(it.session) + } ?: run { + currentStage?.let { completedStages.add(it) } + navigateToNextStage() + } + } + + fun clearSubtitle() { + subtitleLiveData.postValue("") } - fun getPendingSessionId() = registrationWizard.getPendingSignUpSessionId() + private fun finishRegistration(session: Session) { + navigationLiveData.postValue(NavigationEvents.FinishSignUp) + } + + private fun getCurrentStageIndex() = + stagesToComplete.indexOf(currentStage).takeIf { it != -1 } ?: 0 + + private fun navigateToNextStage() { + val stage = currentStage?.let { + stagesToComplete.getOrNull(getCurrentStageIndex() + 1) + } ?: stagesToComplete.firstOrNull() + + currentStage = stage + + val event = when (stage) { + is Stage.Email -> NavigationEvents.VerifyEmail + is Stage.Terms -> NavigationEvents.AcceptTerm + is Stage.Other -> NavigationEvents.TokenValidation + else -> throw IllegalArgumentException("Not supported stage $stage") + } + + navigationLiveData.postValue(event) + + updatePageSubtitle() + } + + 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) + } - fun getCurrentStage() = stagesToComplete.first() } \ No newline at end of file diff --git a/app/src/main/java/com/futo/circles/feature/sign_up_type/SelectSignUpTypeFragment.kt b/app/src/main/java/com/futo/circles/feature/sign_up_type/SelectSignUpTypeFragment.kt index ccb60867fadc0f3e2fee22badc3d45855689dec9..e92f3b78df8317ccd750a889facfd2670e1719f3 100644 --- a/app/src/main/java/com/futo/circles/feature/sign_up_type/SelectSignUpTypeFragment.kt +++ b/app/src/main/java/com/futo/circles/feature/sign_up_type/SelectSignUpTypeFragment.kt @@ -3,23 +3,31 @@ package com.futo.circles.feature.sign_up_type import android.os.Bundle import android.view.View import androidx.fragment.app.Fragment -import androidx.navigation.fragment.findNavController import by.kirich1409.viewbindingdelegate.viewBinding import com.futo.circles.R +import com.futo.circles.core.HasLoadingState import com.futo.circles.databinding.SelectSignUpTypeFragmentBinding +import com.futo.circles.extensions.observeResponse +import org.koin.androidx.viewmodel.ext.android.viewModel -class SelectSignUpTypeFragment : Fragment(R.layout.select_sign_up_type_fragment) { +class SelectSignUpTypeFragment : Fragment(R.layout.select_sign_up_type_fragment), HasLoadingState { + + override val fragment: Fragment = this private val binding by viewBinding(SelectSignUpTypeFragmentBinding::bind) + private val viewModel by viewModel<SelectSignUpTypeViewModel>() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - binding.btnToken.setOnClickListener { navigateToTokenValidation() } - } + viewModel.clearSubtitle() - private fun navigateToTokenValidation() { - findNavController() - .navigate(SelectSignUpTypeFragmentDirections.toValidateTokenFragment()) + binding.btnToken.setOnClickListener { + startLoading(binding.btnToken) + viewModel.startSignUp() + } + + viewModel.startSignUpEventLiveData.observeResponse(this) } + } \ No newline at end of file diff --git a/app/src/main/java/com/futo/circles/feature/validate_token/ValidateTokenFragment.kt b/app/src/main/java/com/futo/circles/feature/validate_token/ValidateTokenFragment.kt index 6ee7a2364210e2f54e8e181a9be83d08d3e84c1b..147ea74028d788238bca3bbd476bf57170f761a3 100644 --- a/app/src/main/java/com/futo/circles/feature/validate_token/ValidateTokenFragment.kt +++ b/app/src/main/java/com/futo/circles/feature/validate_token/ValidateTokenFragment.kt @@ -43,9 +43,6 @@ class ValidateTokenFragment : Fragment(R.layout.validate_token_fragment), HasLoa } private fun setupObservers() { - viewModel.validateLiveData.observeResponse( - this, - success = { } - ) + viewModel.validateLiveData.observeResponse(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/circles/feature/validate_token/ValidateTokenViewModel.kt b/app/src/main/java/com/futo/circles/feature/validate_token/ValidateTokenViewModel.kt index 2d098ba966f14fdff2e665af0356ec0e434d6b3e..437459369540845e943ffeaa45ce198c2d93c191 100644 --- a/app/src/main/java/com/futo/circles/feature/validate_token/ValidateTokenViewModel.kt +++ b/app/src/main/java/com/futo/circles/feature/validate_token/ValidateTokenViewModel.kt @@ -5,15 +5,18 @@ import com.futo.circles.core.SingleEventLiveData import com.futo.circles.extensions.Response import com.futo.circles.extensions.launchBg import com.futo.circles.feature.validate_token.data_source.ValidateTokenDataSource +import okhttp3.ResponseBody class ValidateTokenViewModel( private val dataSource: ValidateTokenDataSource ) : ViewModel() { - val validateLiveData = SingleEventLiveData<Response<okhttp3.Response>>() + val validateLiveData = SingleEventLiveData<Response<ResponseBody>>() fun validateToken(token: String) { - launchBg { dataSource.validateToken(token) } + launchBg { + validateLiveData.postValue(dataSource.validateToken(token)) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/circles/feature/validate_token/data_source/ValidateTokenDataSource.kt b/app/src/main/java/com/futo/circles/feature/validate_token/data_source/ValidateTokenDataSource.kt index fe3f8687066f3ae59ee2e8015a9fcff95b65af8f..7dc5cb89ab99ffbd9a800238c10fe33cb63792fd 100644 --- a/app/src/main/java/com/futo/circles/feature/validate_token/data_source/ValidateTokenDataSource.kt +++ b/app/src/main/java/com/futo/circles/feature/validate_token/data_source/ValidateTokenDataSource.kt @@ -1,8 +1,14 @@ package com.futo.circles.feature.validate_token.data_source +import com.futo.circles.extensions.Response import com.futo.circles.extensions.createResult +import com.futo.circles.extensions.getPendingSignUpSessionId import com.futo.circles.feature.sign_up.data_source.SignUpDataSource +import com.futo.circles.io.request.ValidateSignUpTokenRequestBody +import com.futo.circles.io.request.ValidateTokenRequestParams import com.futo.circles.io.service.CirclesSignUpService +import com.futo.circles.provider.MatrixInstanceProvider +import okhttp3.ResponseBody import org.matrix.android.sdk.api.auth.registration.Stage class ValidateTokenDataSource( @@ -10,14 +16,29 @@ class ValidateTokenDataSource( private val signUpDataSource: SignUpDataSource ) { - suspend fun validateToken(token: String) = createResult { - val type = (signUpDataSource.getCurrentStage() as? Stage.Other)?.type - ?: throw IllegalArgumentException() + private val pendingSessionId by lazy { + MatrixInstanceProvider.matrix.authenticationService() + .getRegistrationWizard().getPendingSignUpSessionId() + } + + suspend fun validateToken(token: String): Response<ResponseBody> { + val result = createResult { + val type = (signUpDataSource.currentStage as? Stage.Other)?.type + ?: throw IllegalArgumentException() + + service.validateSignUpToken( + ValidateSignUpTokenRequestBody( + ValidateTokenRequestParams( + type = type, + token = token, + session = pendingSessionId + ) + ) + ) + } + + (result as? Response.Success)?.let { signUpDataSource.stageCompleted(null) } - service.validateSignUpToken( - type = type, - token = token, - session = signUpDataSource.getPendingSessionId() - ) + return result } } \ No newline at end of file diff --git a/app/src/main/res/layout/select_sign_up_type_fragment.xml b/app/src/main/res/layout/select_sign_up_type_fragment.xml index c653423bd95bc191eb6ceaba72bacd9db1561800..683a1a8bce7d10a0520eabf669750d8f7689540b 100644 --- a/app/src/main/res/layout/select_sign_up_type_fragment.xml +++ b/app/src/main/res/layout/select_sign_up_type_fragment.xml @@ -37,9 +37,8 @@ app:layout_constraintVertical_bias="0.3" app:layout_constraintVertical_chainStyle="packed" /> - <com.google.android.material.button.MaterialButton + <com.futo.circles.view.LoadingButton android:id="@+id/btnToken" - style="@style/AccentButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="8dp" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2174648849ae1b8944ca882542f7a3170fe424b8..7ec8611f62fdbe875524ca8e85ba7093ebdfb969 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -63,6 +63,7 @@ <string name="sign_up_token_format">abcd–efgh–1234–5678</string> <string name="sign_up_token">SignUp token</string> <string name="sign_up_token_explanation">In order to sign up for a service, every new user must present a valid registration token.\n\nIf you found out about the app from a friend or from the posting online, you should be able to get a signup token from the same source.</string> + <string name="sign_up_stage_subtitle_format">Stage %d of %d</string> <plurals name="member_plurals"> <item quantity="one">%d member</item>