mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 05:31:21 +03:00
Login screens: Terms step for registration
This commit is contained in:
parent
dfbf448bb7
commit
3f80076fb1
17 changed files with 582 additions and 13 deletions
|
@ -27,5 +27,7 @@ interface RegistrationWizard {
|
||||||
|
|
||||||
fun performReCaptcha(response: String, callback: MatrixCallback<RegistrationResult>): Cancelable
|
fun performReCaptcha(response: String, callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||||
|
|
||||||
|
fun acceptTerms(callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||||
|
|
||||||
// TODO Add other method here
|
// TODO Add other method here
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,5 +40,4 @@ sealed class Stage(open val mandatory: Boolean) {
|
||||||
data class Other(override val mandatory: Boolean, val type: String, val params: Map<*, *>?) : Stage(mandatory)
|
data class Other(override val mandatory: Boolean, val type: String, val params: Map<*, *>?) : Stage(mandatory)
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO
|
typealias TermPolicies = Map<*, *>
|
||||||
class TermPolicies
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ import im.vector.matrix.android.api.util.NoOpCancellable
|
||||||
import im.vector.matrix.android.internal.SessionManager
|
import im.vector.matrix.android.internal.SessionManager
|
||||||
import im.vector.matrix.android.internal.auth.AuthAPI
|
import im.vector.matrix.android.internal.auth.AuthAPI
|
||||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||||
|
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
||||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||||
import im.vector.matrix.android.internal.util.CancelableCoroutine
|
import im.vector.matrix.android.internal.util.CancelableCoroutine
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
@ -74,6 +75,21 @@ internal class DefaultRegistrationWizard(private val homeServerConnectionConfig:
|
||||||
), callback)
|
), callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun acceptTerms(callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||||
|
val safeSession = currentSession ?: run {
|
||||||
|
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
|
||||||
|
return NoOpCancellable
|
||||||
|
}
|
||||||
|
|
||||||
|
return performRegistrationRequest(
|
||||||
|
RegistrationParams(
|
||||||
|
auth = AuthParams(
|
||||||
|
type = LoginFlowTypes.TERMS,
|
||||||
|
session = safeSession
|
||||||
|
)
|
||||||
|
), callback)
|
||||||
|
}
|
||||||
|
|
||||||
private fun performRegistrationRequest(registrationParams: RegistrationParams, callback: MatrixCallback<RegistrationResult>): Cancelable {
|
private fun performRegistrationRequest(registrationParams: RegistrationParams, callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||||
val job = GlobalScope.launch(coroutineDispatchers.main) {
|
val job = GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
val result = runCatching {
|
val result = runCatching {
|
||||||
|
|
|
@ -84,7 +84,7 @@ fun RegistrationFlowResponse.toFlowResult(): FlowResult {
|
||||||
LoginFlowTypes.RECAPTCHA -> Stage.ReCaptcha(isMandatory, ((params?.get(type) as? Map<*, *>)?.get("public_key") as? String)
|
LoginFlowTypes.RECAPTCHA -> Stage.ReCaptcha(isMandatory, ((params?.get(type) as? Map<*, *>)?.get("public_key") as? String)
|
||||||
?: "")
|
?: "")
|
||||||
LoginFlowTypes.DUMMY -> Stage.Dummy
|
LoginFlowTypes.DUMMY -> Stage.Dummy
|
||||||
LoginFlowTypes.TERMS -> Stage.Terms(isMandatory, TermPolicies())
|
LoginFlowTypes.TERMS -> Stage.Terms(isMandatory, params?.get(type) as? TermPolicies ?: emptyMap<String, String>())
|
||||||
LoginFlowTypes.EMAIL_IDENTITY -> Stage.Email(isMandatory)
|
LoginFlowTypes.EMAIL_IDENTITY -> Stage.Email(isMandatory)
|
||||||
LoginFlowTypes.MSISDN -> Stage.Msisdn(isMandatory)
|
LoginFlowTypes.MSISDN -> Stage.Msisdn(isMandatory)
|
||||||
else -> Stage.Other(isMandatory, type, (params?.get(type) as? Map<*, *>))
|
else -> Stage.Other(isMandatory, type, (params?.get(type) as? Map<*, *>))
|
||||||
|
|
|
@ -36,6 +36,7 @@ import im.vector.riotx.features.home.group.GroupListFragment
|
||||||
import im.vector.riotx.features.home.room.detail.RoomDetailFragment
|
import im.vector.riotx.features.home.room.detail.RoomDetailFragment
|
||||||
import im.vector.riotx.features.home.room.list.RoomListFragment
|
import im.vector.riotx.features.home.room.list.RoomListFragment
|
||||||
import im.vector.riotx.features.login.*
|
import im.vector.riotx.features.login.*
|
||||||
|
import im.vector.riotx.features.login.terms.LoginTermsFragment
|
||||||
import im.vector.riotx.features.reactions.EmojiSearchResultFragment
|
import im.vector.riotx.features.reactions.EmojiSearchResultFragment
|
||||||
import im.vector.riotx.features.roomdirectory.PublicRoomsFragment
|
import im.vector.riotx.features.roomdirectory.PublicRoomsFragment
|
||||||
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment
|
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment
|
||||||
|
@ -119,6 +120,11 @@ interface FragmentModule {
|
||||||
@FragmentKey(LoginCaptchaFragment::class)
|
@FragmentKey(LoginCaptchaFragment::class)
|
||||||
fun bindLoginCaptchaFragment(fragment: LoginCaptchaFragment): Fragment
|
fun bindLoginCaptchaFragment(fragment: LoginCaptchaFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(LoginTermsFragment::class)
|
||||||
|
fun bindLoginTermsFragment(fragment: LoginTermsFragment): Fragment
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(LoginServerUrlFormFragment::class)
|
@FragmentKey(LoginServerUrlFormFragment::class)
|
||||||
|
|
|
@ -36,6 +36,7 @@ sealed class LoginAction : VectorViewModelAction {
|
||||||
data class AddMsisdn(val msisdn: String) : RegisterAction()
|
data class AddMsisdn(val msisdn: String) : RegisterAction()
|
||||||
data class ConfirmMsisdn(val code: String) : RegisterAction()
|
data class ConfirmMsisdn(val code: String) : RegisterAction()
|
||||||
data class CaptchaDone(val captchaResponse: String) : RegisterAction()
|
data class CaptchaDone(val captchaResponse: String) : RegisterAction()
|
||||||
|
object AcceptTerms : RegisterAction()
|
||||||
|
|
||||||
// Reset actions
|
// Reset actions
|
||||||
open class ResetAction : LoginAction()
|
open class ResetAction : LoginAction()
|
||||||
|
|
|
@ -32,6 +32,9 @@ import im.vector.riotx.core.extensions.addFragmentToBackstack
|
||||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||||
import im.vector.riotx.features.disclaimer.showDisclaimerDialog
|
import im.vector.riotx.features.disclaimer.showDisclaimerDialog
|
||||||
import im.vector.riotx.features.home.HomeActivity
|
import im.vector.riotx.features.home.HomeActivity
|
||||||
|
import im.vector.riotx.features.login.terms.LoginTermsFragment
|
||||||
|
import im.vector.riotx.features.login.terms.LoginTermsFragmentArgument
|
||||||
|
import im.vector.riotx.features.login.terms.toLocalizedLoginTerms
|
||||||
import kotlinx.android.synthetic.main.activity_login.*
|
import kotlinx.android.synthetic.main.activity_login.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -184,21 +187,22 @@ class LoginActivity : VectorBaseActivity() {
|
||||||
|
|
||||||
private fun handleRegistrationNavigation(flowResult: FlowResult) {
|
private fun handleRegistrationNavigation(flowResult: FlowResult) {
|
||||||
// Complete all mandatory stage first
|
// Complete all mandatory stage first
|
||||||
val mandatoryStages = flowResult.missingStages.filter { it.mandatory }
|
val mandatoryStage = flowResult.missingStages.firstOrNull { it.mandatory }
|
||||||
|
|
||||||
if (mandatoryStages.isEmpty()) {
|
if (mandatoryStage != null) {
|
||||||
|
doStage(mandatoryStage)
|
||||||
|
} else {
|
||||||
// Consider optional stages
|
// Consider optional stages
|
||||||
val optionalStages = flowResult.missingStages.filter { !it.mandatory }
|
val optionalStage = flowResult.missingStages.firstOrNull { !it.mandatory && it !is Stage.Dummy }
|
||||||
if (optionalStages.isEmpty()) {
|
if (optionalStage == null) {
|
||||||
// Should not happen...
|
// Should not happen...
|
||||||
} else {
|
} else {
|
||||||
doStage(optionalStages.first())
|
doStage(optionalStage)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
doStage(mandatoryStages.first())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Unstack fragment when stage is complete
|
||||||
private fun doStage(stage: Stage) {
|
private fun doStage(stage: Stage) {
|
||||||
when (stage) {
|
when (stage) {
|
||||||
is Stage.ReCaptcha -> addFragmentToBackstack(R.id.loginFragmentContainer,
|
is Stage.ReCaptcha -> addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||||
|
@ -210,12 +214,13 @@ class LoginActivity : VectorBaseActivity() {
|
||||||
-> addFragmentToBackstack(R.id.loginFragmentContainer,
|
-> addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||||
LoginGenericTextInputFormFragment::class.java,
|
LoginGenericTextInputFormFragment::class.java,
|
||||||
LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetMsisdn, stage.mandatory))
|
LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetMsisdn, stage.mandatory))
|
||||||
is Stage.Terms
|
is Stage.Terms -> addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||||
-> TODO()
|
LoginTermsFragment::class.java,
|
||||||
|
LoginTermsFragmentArgument(stage.policies.toLocalizedLoginTerms(getString(R.string.resources_language))))
|
||||||
|
else -> TODO()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,9 @@ import im.vector.riotx.features.notifications.PushRuleTriggerListener
|
||||||
import im.vector.riotx.features.session.SessionListener
|
import im.vector.riotx.features.session.SessionListener
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO To speed up registration, consider fetching registration flow instead of login flow at startup
|
||||||
|
*/
|
||||||
class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginViewState,
|
class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginViewState,
|
||||||
private val authenticator: Authenticator,
|
private val authenticator: Authenticator,
|
||||||
private val registrationService: RegistrationService,
|
private val registrationService: RegistrationService,
|
||||||
|
@ -100,10 +103,43 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||||
when (action) {
|
when (action) {
|
||||||
is LoginAction.RegisterWith -> handleRegisterWith(action)
|
is LoginAction.RegisterWith -> handleRegisterWith(action)
|
||||||
is LoginAction.CaptchaDone -> handleCaptchaDone(action)
|
is LoginAction.CaptchaDone -> handleCaptchaDone(action)
|
||||||
|
is LoginAction.AcceptTerms -> handleAcceptTerms()
|
||||||
// TODO Add other actions here
|
// TODO Add other actions here
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleAcceptTerms() {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncRegistration = Loading()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTask = registrationWizard?.acceptTerms(object : MatrixCallback<RegistrationResult> {
|
||||||
|
override fun onSuccess(data: RegistrationResult) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncRegistration = Success(data)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
when (data) {
|
||||||
|
is RegistrationResult.Success -> onSessionCreated(data.session)
|
||||||
|
is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
// TODO Handled JobCancellationException
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncRegistration = Fail(failure)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleRegisterWith(action: LoginAction.RegisterWith) {
|
private fun handleRegisterWith(action: LoginAction.RegisterWith) {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.login.terms
|
||||||
|
|
||||||
|
import org.matrix.androidsdk.rest.model.login.LocalizedFlowDataLoginTerms
|
||||||
|
|
||||||
|
data class LocalizedFlowDataLoginTermsChecked(val localizedFlowDataLoginTerms: LocalizedFlowDataLoginTerms,
|
||||||
|
var checked: Boolean = false)
|
104
vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt
Executable file
104
vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt
Executable file
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.login.terms
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.view.View
|
||||||
|
import butterknife.OnClick
|
||||||
|
import com.airbnb.mvrx.args
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.utils.openUrlInExternalBrowser
|
||||||
|
import im.vector.riotx.features.login.AbstractLoginFragment
|
||||||
|
import im.vector.riotx.features.login.LoginAction
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import kotlinx.android.synthetic.main.fragment_login_terms.*
|
||||||
|
import org.matrix.androidsdk.rest.model.login.LocalizedFlowDataLoginTerms
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class LoginTermsFragmentArgument(
|
||||||
|
val localizedFlowDataLoginTerms: List<LocalizedFlowDataLoginTerms>
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LoginTermsFragment displays the list of policies the user has to accept
|
||||||
|
*/
|
||||||
|
class LoginTermsFragment @Inject constructor(private val policyController: PolicyController) : AbstractLoginFragment(),
|
||||||
|
PolicyController.PolicyControllerListener {
|
||||||
|
|
||||||
|
private val params: LoginTermsFragmentArgument by args()
|
||||||
|
|
||||||
|
override fun getLayoutResId() = R.layout.fragment_login_terms
|
||||||
|
|
||||||
|
private var loginTermsViewState: LoginTermsViewState = LoginTermsViewState(emptyList())
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
loginTermsPolicyList.setController(policyController)
|
||||||
|
policyController.listener = this
|
||||||
|
|
||||||
|
val list = ArrayList<LocalizedFlowDataLoginTermsChecked>()
|
||||||
|
|
||||||
|
params.localizedFlowDataLoginTerms
|
||||||
|
.forEach {
|
||||||
|
list.add(LocalizedFlowDataLoginTermsChecked(it))
|
||||||
|
}
|
||||||
|
|
||||||
|
loginTermsViewState = LoginTermsViewState(list)
|
||||||
|
|
||||||
|
renderState()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderState() {
|
||||||
|
policyController.setData(loginTermsViewState.localizedFlowDataLoginTermsChecked)
|
||||||
|
|
||||||
|
// Button is enabled only if all checkboxes are checked
|
||||||
|
loginTermsSubmit.isEnabled = loginTermsViewState.allChecked()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setChecked(localizedFlowDataLoginTerms: LocalizedFlowDataLoginTerms, isChecked: Boolean) {
|
||||||
|
if (isChecked) {
|
||||||
|
loginTermsViewState.check(localizedFlowDataLoginTerms)
|
||||||
|
} else {
|
||||||
|
loginTermsViewState.uncheck(localizedFlowDataLoginTerms)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderState()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun openPolicy(localizedFlowDataLoginTerms: LocalizedFlowDataLoginTerms) {
|
||||||
|
openUrlInExternalBrowser(requireContext(), localizedFlowDataLoginTerms.localizedUrl!!)
|
||||||
|
|
||||||
|
// This code crashed, because user is not authenticated yet
|
||||||
|
//val intent = VectorWebViewActivity.getIntent(requireContext(),
|
||||||
|
// localizedFlowDataLoginTerms.localizedUrl!!,
|
||||||
|
// localizedFlowDataLoginTerms.localizedName!!,
|
||||||
|
// WebViewMode.DEFAULT)
|
||||||
|
//startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.loginTermsSubmit)
|
||||||
|
internal fun submit() {
|
||||||
|
loginViewModel.handle(LoginAction.AcceptTerms)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun resetViewModel() {
|
||||||
|
// No op
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.login.terms
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.MvRxState
|
||||||
|
import org.matrix.androidsdk.rest.model.login.LocalizedFlowDataLoginTerms
|
||||||
|
|
||||||
|
data class LoginTermsViewState(
|
||||||
|
val localizedFlowDataLoginTermsChecked: List<LocalizedFlowDataLoginTermsChecked>
|
||||||
|
) : MvRxState {
|
||||||
|
fun check(data: LocalizedFlowDataLoginTerms) {
|
||||||
|
localizedFlowDataLoginTermsChecked.find { it.localizedFlowDataLoginTerms == data }?.checked = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun uncheck(data: LocalizedFlowDataLoginTerms) {
|
||||||
|
localizedFlowDataLoginTermsChecked.find { it.localizedFlowDataLoginTerms == data }?.checked = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun allChecked(): Boolean {
|
||||||
|
return localizedFlowDataLoginTermsChecked.all { it.checked }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.login.terms
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import org.matrix.androidsdk.rest.model.login.LocalizedFlowDataLoginTerms
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class PolicyController @Inject constructor() : TypedEpoxyController<List<LocalizedFlowDataLoginTermsChecked>>() {
|
||||||
|
|
||||||
|
var listener: PolicyControllerListener? = null
|
||||||
|
|
||||||
|
override fun buildModels(data: List<LocalizedFlowDataLoginTermsChecked>) {
|
||||||
|
data.forEach { entry ->
|
||||||
|
policy {
|
||||||
|
id(entry.localizedFlowDataLoginTerms.policyName)
|
||||||
|
checked(entry.checked)
|
||||||
|
title(entry.localizedFlowDataLoginTerms.localizedName!!)
|
||||||
|
|
||||||
|
clickListener(View.OnClickListener { listener?.openPolicy(entry.localizedFlowDataLoginTerms) })
|
||||||
|
checkChangeListener { _, isChecked ->
|
||||||
|
listener?.setChecked(entry.localizedFlowDataLoginTerms, isChecked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PolicyControllerListener {
|
||||||
|
fun setChecked(localizedFlowDataLoginTerms: LocalizedFlowDataLoginTerms, isChecked: Boolean)
|
||||||
|
fun openPolicy(localizedFlowDataLoginTerms: LocalizedFlowDataLoginTerms)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.login.terms
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.CheckBox
|
||||||
|
import android.widget.CompoundButton
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import com.airbnb.epoxy.EpoxyModelWithHolder
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_policy)
|
||||||
|
abstract class PolicyModel : EpoxyModelWithHolder<PolicyModel.Holder>() {
|
||||||
|
@EpoxyAttribute
|
||||||
|
var checked: Boolean = false
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var title: String? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||||
|
var checkChangeListener: CompoundButton.OnCheckedChangeListener? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||||
|
var clickListener: View.OnClickListener? = null
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
holder.let {
|
||||||
|
it.checkbox.isChecked = checked
|
||||||
|
it.checkbox.setOnCheckedChangeListener(checkChangeListener)
|
||||||
|
it.title.text = title
|
||||||
|
it.view.setOnClickListener(clickListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure checkbox behaves as expected (remove the listener)
|
||||||
|
override fun unbind(holder: Holder) {
|
||||||
|
super.unbind(holder)
|
||||||
|
holder.checkbox.setOnCheckedChangeListener(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val checkbox by bind<CheckBox>(R.id.adapter_item_policy_checkbox)
|
||||||
|
val title by bind<TextView>(R.id.adapter_item_policy_title)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.login.terms
|
||||||
|
|
||||||
|
data class UrlAndName(
|
||||||
|
val url: String,
|
||||||
|
val name: String
|
||||||
|
)
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.login.terms
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.auth.registration.TermPolicies
|
||||||
|
import org.matrix.androidsdk.rest.model.login.LocalizedFlowDataLoginTerms
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method extract the policies from the login terms parameter, regarding the user language.
|
||||||
|
* For each policy, if user language is not found, the default language is used and if not found, the first url and name are used (not predictable)
|
||||||
|
*
|
||||||
|
* Example of Data:
|
||||||
|
* <pre>
|
||||||
|
* "m.login.terms": {
|
||||||
|
* "policies": {
|
||||||
|
* "privacy_policy": {
|
||||||
|
* "version": "1.0",
|
||||||
|
* "en": {
|
||||||
|
* "url": "http:\/\/matrix.org\/_matrix\/consent?v=1.0",
|
||||||
|
* "name": "Terms and Conditions"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*</pre>
|
||||||
|
*
|
||||||
|
* @param userLanguage the user language
|
||||||
|
* @param defaultLanguage the default language to use if the user language is not found for a policy in registrationFlowResponse
|
||||||
|
*/
|
||||||
|
fun TermPolicies.toLocalizedLoginTerms(userLanguage: String,
|
||||||
|
defaultLanguage: String = "en"): List<LocalizedFlowDataLoginTerms> {
|
||||||
|
val result = ArrayList<LocalizedFlowDataLoginTerms>()
|
||||||
|
|
||||||
|
val policies = get("policies")
|
||||||
|
if (policies is Map<*, *>) {
|
||||||
|
policies.keys.forEach { policyName ->
|
||||||
|
val localizedFlowDataLoginTerms = LocalizedFlowDataLoginTerms()
|
||||||
|
localizedFlowDataLoginTerms.policyName = policyName as String
|
||||||
|
|
||||||
|
val policy = policies[policyName]
|
||||||
|
|
||||||
|
// Enter this policy
|
||||||
|
if (policy is Map<*, *>) {
|
||||||
|
// Version
|
||||||
|
localizedFlowDataLoginTerms.version = policy["version"] as String?
|
||||||
|
|
||||||
|
var userLanguageUrlAndName: UrlAndName? = null
|
||||||
|
var defaultLanguageUrlAndName: UrlAndName? = null
|
||||||
|
var firstUrlAndName: UrlAndName? = null
|
||||||
|
|
||||||
|
// Search for language
|
||||||
|
policy.keys.forEach { policyKey ->
|
||||||
|
when (policyKey) {
|
||||||
|
"version" -> Unit // Ignore
|
||||||
|
userLanguage -> {
|
||||||
|
// We found the data for the user language
|
||||||
|
userLanguageUrlAndName = extractUrlAndName(policy[policyKey])
|
||||||
|
}
|
||||||
|
defaultLanguage -> {
|
||||||
|
// We found default language
|
||||||
|
defaultLanguageUrlAndName = extractUrlAndName(policy[policyKey])
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
if (firstUrlAndName == null) {
|
||||||
|
// Get at least some data
|
||||||
|
firstUrlAndName = extractUrlAndName(policy[policyKey])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy found language data by priority
|
||||||
|
when {
|
||||||
|
userLanguageUrlAndName != null -> {
|
||||||
|
localizedFlowDataLoginTerms.localizedUrl = userLanguageUrlAndName!!.url
|
||||||
|
localizedFlowDataLoginTerms.localizedName = userLanguageUrlAndName!!.name
|
||||||
|
}
|
||||||
|
defaultLanguageUrlAndName != null -> {
|
||||||
|
localizedFlowDataLoginTerms.localizedUrl = defaultLanguageUrlAndName!!.url
|
||||||
|
localizedFlowDataLoginTerms.localizedName = defaultLanguageUrlAndName!!.name
|
||||||
|
}
|
||||||
|
firstUrlAndName != null -> {
|
||||||
|
localizedFlowDataLoginTerms.localizedUrl = firstUrlAndName!!.url
|
||||||
|
localizedFlowDataLoginTerms.localizedName = firstUrlAndName!!.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.add(localizedFlowDataLoginTerms)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractUrlAndName(policyData: Any?): UrlAndName? {
|
||||||
|
if (policyData is Map<*, *>) {
|
||||||
|
val url = policyData["url"] as String?
|
||||||
|
val name = policyData["name"] as String?
|
||||||
|
|
||||||
|
if (url != null && name != null) {
|
||||||
|
return UrlAndName(url, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
43
vector/src/main/res/layout/fragment_login_terms.xml
Normal file
43
vector/src/main/res/layout/fragment_login_terms.xml
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?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"
|
||||||
|
style="@style/LoginContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/logoImageView"
|
||||||
|
style="@style/LoginTopIcon"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/loginTermsNotice"
|
||||||
|
style="@style/TextAppearance.Vector.Login.Text.Small"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="27dp"
|
||||||
|
android:text="@string/auth_accept_policies"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/logoImageView" />
|
||||||
|
|
||||||
|
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||||
|
android:id="@+id/loginTermsPolicyList"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/loginTermsSubmit"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/loginTermsNotice"
|
||||||
|
tools:listitem="@layout/item_policy" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/loginTermsSubmit"
|
||||||
|
style="@style/Style.Vector.Login.Button"
|
||||||
|
android:text="@string/accept"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
48
vector/src/main/res/layout/item_policy.xml
Normal file
48
vector/src/main/res/layout/item_policy.xml
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<?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="wrap_content"
|
||||||
|
android:minHeight="48dp">
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/adapter_item_policy_checkbox"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/adapter_item_policy_title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:drawablePadding="8dp"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/adapter_item_policy_arrow"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/adapter_item_policy_checkbox"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="Policy title" />
|
||||||
|
|
||||||
|
<!-- Do not use drawableEnd on the TextView because of RTL support -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/adapter_item_policy_arrow"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:rotationY="@integer/rtl_mirror_flip"
|
||||||
|
android:src="@drawable/ic_material_chevron_right_black"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
Reference in a new issue