Merge pull request #5737 from vector-im/feature/adm/ftue-captcha
FTUE - Registration Captcha and T&Cs screens
This commit is contained in:
commit
dc5902e8f5
1
changelog.d/5279.wip
Normal file
1
changelog.d/5279.wip
Normal file
@ -0,0 +1 @@
|
||||
Updates the Captcha and T&Cs registration screens in the FTUE flow to match the updated UI style
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?vctr_content_tertiary" android:state_checked="false" />
|
||||
<item android:color="?colorPrimary" android:state_checked="true" />
|
||||
</selector>
|
@ -102,6 +102,7 @@ import im.vector.app.features.onboarding.ftueauth.FtueAuthCaptchaFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthChooseDisplayNameFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthChooseProfilePictureFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthGenericTextInputFormFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthLegacyStyleCaptchaFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthLoginFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthPersonalizationCompleteFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordFragment
|
||||
@ -114,6 +115,7 @@ import im.vector.app.features.onboarding.ftueauth.FtueAuthSplashFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthUseCaseFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthWaitForEmailFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthWebFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthLegacyStyleTermsFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsFragment
|
||||
import im.vector.app.features.pin.PinFragment
|
||||
import im.vector.app.features.poll.create.CreatePollFragment
|
||||
@ -407,6 +409,11 @@ interface FragmentModule {
|
||||
@FragmentKey(LoginWaitForEmailFragment2::class)
|
||||
fun bindLoginWaitForEmailFragment2(fragment: LoginWaitForEmailFragment2): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(FtueAuthLegacyStyleCaptchaFragment::class)
|
||||
fun bindFtueAuthLegacyStyleCaptchaFragment(fragment: FtueAuthLegacyStyleCaptchaFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(FtueAuthCaptchaFragment::class)
|
||||
@ -472,6 +479,11 @@ interface FragmentModule {
|
||||
@FragmentKey(FtueAuthWebFragment::class)
|
||||
fun bindFtueAuthWebFragment(fragment: FtueAuthWebFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(FtueAuthLegacyStyleTermsFragment::class)
|
||||
fun bindFtueAuthLegacyStyleTermsFragment(fragment: FtueAuthLegacyStyleTermsFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(FtueAuthTermsFragment::class)
|
||||
|
@ -32,3 +32,12 @@ fun View.showKeyboard(andRequestFocus: Boolean = false) {
|
||||
val imm = context?.getSystemService<InputMethodManager>()
|
||||
imm?.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
|
||||
}
|
||||
|
||||
fun View.setHorizontalPadding(padding: Int) {
|
||||
setPadding(
|
||||
padding,
|
||||
paddingTop,
|
||||
padding,
|
||||
paddingBottom
|
||||
)
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ class PolicyController @Inject constructor() : TypedEpoxyController<List<Localiz
|
||||
|
||||
var listener: PolicyControllerListener? = null
|
||||
|
||||
var horizontalPadding: Int? = null
|
||||
var homeServer: String? = null
|
||||
|
||||
override fun buildModels(data: List<LocalizedFlowDataLoginTermsChecked>) {
|
||||
@ -32,6 +33,7 @@ class PolicyController @Inject constructor() : TypedEpoxyController<List<Localiz
|
||||
policyItem {
|
||||
id(entry.localizedFlowDataLoginTerms.policyName)
|
||||
checked(entry.checked)
|
||||
horizontalPadding(host.horizontalPadding)
|
||||
title(entry.localizedFlowDataLoginTerms.localizedName)
|
||||
subtitle(host.homeServer)
|
||||
clickListener { host.listener?.openPolicy(entry.localizedFlowDataLoginTerms) }
|
||||
|
@ -26,6 +26,7 @@ import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.ClickListener
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.onClick
|
||||
import im.vector.app.core.extensions.setHorizontalPadding
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_policy)
|
||||
abstract class PolicyItem : EpoxyModelWithHolder<PolicyItem.Holder>() {
|
||||
@ -38,6 +39,9 @@ abstract class PolicyItem : EpoxyModelWithHolder<PolicyItem.Holder>() {
|
||||
@EpoxyAttribute
|
||||
var subtitle: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var horizontalPadding: Int? = null
|
||||
|
||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||
var checkChangeListener: CompoundButton.OnCheckedChangeListener? = null
|
||||
|
||||
@ -46,13 +50,12 @@ abstract class PolicyItem : EpoxyModelWithHolder<PolicyItem.Holder>() {
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.let {
|
||||
it.checkbox.isChecked = checked
|
||||
it.checkbox.setOnCheckedChangeListener(checkChangeListener)
|
||||
it.title.text = title
|
||||
it.subtitle.text = subtitle
|
||||
it.view.onClick(clickListener)
|
||||
}
|
||||
horizontalPadding?.let { holder.view.setHorizontalPadding(it) }
|
||||
holder.checkbox.isChecked = checked
|
||||
holder.checkbox.setOnCheckedChangeListener(checkChangeListener)
|
||||
holder.title.text = title
|
||||
holder.subtitle.text = subtitle
|
||||
holder.view.onClick(clickListener)
|
||||
}
|
||||
|
||||
// Ensure checkbox behaves as expected (remove the listener)
|
||||
|
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.onboarding.ftueauth
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.Bitmap
|
||||
import android.net.http.SslError
|
||||
import android.os.Build
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.webkit.SslErrorHandler
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.utils.AssetReader
|
||||
import im.vector.app.features.login.JavascriptResponse
|
||||
import im.vector.app.features.onboarding.OnboardingViewState
|
||||
import org.matrix.android.sdk.api.util.MatrixJsonParser
|
||||
import timber.log.Timber
|
||||
import java.net.URLDecoder
|
||||
import java.util.Formatter
|
||||
import javax.inject.Inject
|
||||
|
||||
class CaptchaWebview @Inject constructor(
|
||||
private val assetReader: AssetReader
|
||||
) {
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
fun setupWebView(
|
||||
container: Fragment,
|
||||
webView: WebView,
|
||||
progressView: View,
|
||||
siteKey: String,
|
||||
state: OnboardingViewState,
|
||||
onSuccess: (String) -> Unit
|
||||
) {
|
||||
webView.settings.javaScriptEnabled = true
|
||||
|
||||
val reCaptchaPage = assetReader.readAssetFile("reCaptchaPage.html") ?: error("missing asset reCaptchaPage.html")
|
||||
|
||||
val html = Formatter().format(reCaptchaPage, siteKey).toString()
|
||||
val mime = "text/html"
|
||||
val encoding = "utf-8"
|
||||
|
||||
val homeServerUrl = state.selectedHomeserver.upstreamUrl ?: error("missing url of homeserver")
|
||||
webView.loadDataWithBaseURL(homeServerUrl, html, mime, encoding, null)
|
||||
webView.requestLayout()
|
||||
|
||||
webView.webViewClient = object : WebViewClient() {
|
||||
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
|
||||
super.onPageStarted(view, url, favicon)
|
||||
if (container.isAdded) {
|
||||
progressView.isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView, url: String) {
|
||||
super.onPageFinished(view, url)
|
||||
if (container.isAdded) {
|
||||
progressView.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) {
|
||||
Timber.d("## onReceivedSslError() : ${error.certificate}")
|
||||
if (container.isAdded) {
|
||||
showSslErrorDialog(container, handler)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onError(errorMessage: String) {
|
||||
Timber.e("## onError() : $errorMessage")
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
override fun onReceivedHttpError(view: WebView, request: WebResourceRequest, errorResponse: WebResourceResponse) {
|
||||
super.onReceivedHttpError(view, request, errorResponse)
|
||||
when {
|
||||
request.url.toString().endsWith("favicon.ico") -> {
|
||||
// ignore favicon errors
|
||||
}
|
||||
else -> onError(errorResponse.toText())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) {
|
||||
@Suppress("DEPRECATION")
|
||||
super.onReceivedError(view, errorCode, description, failingUrl)
|
||||
onError(description)
|
||||
}
|
||||
|
||||
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
|
||||
if (url?.startsWith("js:") == true) {
|
||||
val javascriptResponse = parseJsonFromUrl(url)
|
||||
val response = javascriptResponse?.response
|
||||
if (javascriptResponse?.action == "verifyCallback" && response != null) {
|
||||
onSuccess(response)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun parseJsonFromUrl(url: String): JavascriptResponse? {
|
||||
return try {
|
||||
val json = URLDecoder.decode(url.substringAfter("js:"), "UTF-8")
|
||||
MatrixJsonParser.getMoshi().adapter(JavascriptResponse::class.java).fromJson(json)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## shouldOverrideUrlLoading(): failed")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSslErrorDialog(container: Fragment, handler: SslErrorHandler) {
|
||||
MaterialAlertDialogBuilder(container.requireActivity())
|
||||
.setMessage(R.string.ssl_could_not_verify)
|
||||
.setPositiveButton(R.string.ssl_trust) { _, _ ->
|
||||
Timber.d("## onReceivedSslError() : the user trusted")
|
||||
handler.proceed()
|
||||
}
|
||||
.setNegativeButton(R.string.ssl_do_not_trust) { _, _ ->
|
||||
Timber.d("## onReceivedSslError() : the user did not trust")
|
||||
handler.cancel()
|
||||
}
|
||||
.setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event ->
|
||||
if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
handler.cancel()
|
||||
Timber.d("## onReceivedSslError() : the user dismisses the trust dialog.")
|
||||
dialog.dismiss()
|
||||
return@OnKeyListener true
|
||||
}
|
||||
false
|
||||
})
|
||||
.setCancelable(false)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun WebResourceResponse.toText() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) reasonPhrase else toString()
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022 New Vector Ltd
|
||||
* Copyright (c) 2022 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.
|
||||
@ -16,35 +16,15 @@
|
||||
|
||||
package im.vector.app.features.onboarding.ftueauth
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.Bitmap
|
||||
import android.net.http.SslError
|
||||
import android.os.Build
|
||||
import android.os.Parcelable
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.SslErrorHandler
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.mvrx.args
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.utils.AssetReader
|
||||
import im.vector.app.databinding.FragmentLoginCaptchaBinding
|
||||
import im.vector.app.features.login.JavascriptResponse
|
||||
import im.vector.app.databinding.FragmentFtueLoginCaptchaBinding
|
||||
import im.vector.app.features.onboarding.OnboardingAction
|
||||
import im.vector.app.features.onboarding.OnboardingViewState
|
||||
import im.vector.app.features.onboarding.RegisterAction
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.api.util.MatrixJsonParser
|
||||
import timber.log.Timber
|
||||
import java.net.URLDecoder
|
||||
import java.util.Formatter
|
||||
import javax.inject.Inject
|
||||
|
||||
@Parcelize
|
||||
@ -53,141 +33,17 @@ data class FtueAuthCaptchaFragmentArgument(
|
||||
) : Parcelable
|
||||
|
||||
/**
|
||||
* In this screen, the user is asked to confirm he is not a robot
|
||||
* In this screen, the user is asked to confirm they are not a robot
|
||||
*/
|
||||
class FtueAuthCaptchaFragment @Inject constructor(
|
||||
private val assetReader: AssetReader
|
||||
) : AbstractFtueAuthFragment<FragmentLoginCaptchaBinding>() {
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginCaptchaBinding {
|
||||
return FragmentLoginCaptchaBinding.inflate(inflater, container, false)
|
||||
}
|
||||
private val captchaWebview: CaptchaWebview
|
||||
) : AbstractFtueAuthFragment<FragmentFtueLoginCaptchaBinding>() {
|
||||
|
||||
private val params: FtueAuthCaptchaFragmentArgument by args()
|
||||
|
||||
private var isWebViewLoaded = false
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private fun setupWebView(state: OnboardingViewState) {
|
||||
views.loginCaptchaWevView.settings.javaScriptEnabled = true
|
||||
|
||||
val reCaptchaPage = assetReader.readAssetFile("reCaptchaPage.html") ?: error("missing asset reCaptchaPage.html")
|
||||
|
||||
val html = Formatter().format(reCaptchaPage, params.siteKey).toString()
|
||||
val mime = "text/html"
|
||||
val encoding = "utf-8"
|
||||
|
||||
val homeServerUrl = state.selectedHomeserver.upstreamUrl ?: error("missing url of homeserver")
|
||||
views.loginCaptchaWevView.loadDataWithBaseURL(homeServerUrl, html, mime, encoding, null)
|
||||
views.loginCaptchaWevView.requestLayout()
|
||||
|
||||
views.loginCaptchaWevView.webViewClient = object : WebViewClient() {
|
||||
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
|
||||
super.onPageStarted(view, url, favicon)
|
||||
|
||||
if (!isAdded) {
|
||||
return
|
||||
}
|
||||
|
||||
// Show loader
|
||||
views.loginCaptchaProgress.isVisible = true
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView, url: String) {
|
||||
super.onPageFinished(view, url)
|
||||
|
||||
if (!isAdded) {
|
||||
return
|
||||
}
|
||||
|
||||
// Hide loader
|
||||
views.loginCaptchaProgress.isVisible = false
|
||||
}
|
||||
|
||||
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) {
|
||||
Timber.d("## onReceivedSslError() : ${error.certificate}")
|
||||
|
||||
if (!isAdded) {
|
||||
return
|
||||
}
|
||||
|
||||
MaterialAlertDialogBuilder(requireActivity())
|
||||
.setMessage(R.string.ssl_could_not_verify)
|
||||
.setPositiveButton(R.string.ssl_trust) { _, _ ->
|
||||
Timber.d("## onReceivedSslError() : the user trusted")
|
||||
handler.proceed()
|
||||
}
|
||||
.setNegativeButton(R.string.ssl_do_not_trust) { _, _ ->
|
||||
Timber.d("## onReceivedSslError() : the user did not trust")
|
||||
handler.cancel()
|
||||
}
|
||||
.setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event ->
|
||||
if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
handler.cancel()
|
||||
Timber.d("## onReceivedSslError() : the user dismisses the trust dialog.")
|
||||
dialog.dismiss()
|
||||
return@OnKeyListener true
|
||||
}
|
||||
false
|
||||
})
|
||||
.setCancelable(false)
|
||||
.show()
|
||||
}
|
||||
|
||||
// common error message
|
||||
private fun onError(errorMessage: String) {
|
||||
Timber.e("## onError() : $errorMessage")
|
||||
|
||||
// TODO
|
||||
// Toast.makeText(this@AccountCreationCaptchaActivity, errorMessage, Toast.LENGTH_LONG).show()
|
||||
|
||||
// on error case, close this activity
|
||||
// runOnUiThread(Runnable { finish() })
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
override fun onReceivedHttpError(view: WebView, request: WebResourceRequest, errorResponse: WebResourceResponse) {
|
||||
super.onReceivedHttpError(view, request, errorResponse)
|
||||
|
||||
if (request.url.toString().endsWith("favicon.ico")) {
|
||||
// Ignore this error
|
||||
return
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
onError(errorResponse.reasonPhrase)
|
||||
} else {
|
||||
onError(errorResponse.toString())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) {
|
||||
@Suppress("DEPRECATION")
|
||||
super.onReceivedError(view, errorCode, description, failingUrl)
|
||||
onError(description)
|
||||
}
|
||||
|
||||
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
|
||||
if (url?.startsWith("js:") == true) {
|
||||
var json = url.substring(3)
|
||||
var javascriptResponse: JavascriptResponse? = null
|
||||
|
||||
try {
|
||||
// URL decode
|
||||
json = URLDecoder.decode(json, "UTF-8")
|
||||
javascriptResponse = MatrixJsonParser.getMoshi().adapter(JavascriptResponse::class.java).fromJson(json)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## shouldOverrideUrlLoading(): failed")
|
||||
}
|
||||
|
||||
val response = javascriptResponse?.response
|
||||
if (javascriptResponse?.action == "verifyCallback" && response != null) {
|
||||
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CaptchaDone(response)))
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueLoginCaptchaBinding {
|
||||
return FragmentFtueLoginCaptchaBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun resetViewModel() {
|
||||
@ -196,7 +52,9 @@ class FtueAuthCaptchaFragment @Inject constructor(
|
||||
|
||||
override fun updateWithState(state: OnboardingViewState) {
|
||||
if (!isWebViewLoaded) {
|
||||
setupWebView(state)
|
||||
captchaWebview.setupWebView(this, views.loginCaptchaWevView, views.loginCaptchaProgress, params.siteKey, state) {
|
||||
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CaptchaDone(it)))
|
||||
}
|
||||
isWebViewLoaded = true
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.extensions.hidePassword
|
||||
import im.vector.app.core.extensions.realignPercentagesToParent
|
||||
import im.vector.app.core.extensions.toReducedUrl
|
||||
import im.vector.app.databinding.FragmentFtueSignUpCombinedBinding
|
||||
import im.vector.app.databinding.FragmentFtueCombinedRegisterBinding
|
||||
import im.vector.app.features.login.LoginMode
|
||||
import im.vector.app.features.login.SSORedirectRouterActivity
|
||||
import im.vector.app.features.login.SocialLoginButtonsView
|
||||
@ -56,10 +56,10 @@ import org.matrix.android.sdk.api.failure.isUsernameInUse
|
||||
import org.matrix.android.sdk.api.failure.isWeakPassword
|
||||
import javax.inject.Inject
|
||||
|
||||
class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAuthFragment<FragmentFtueSignUpCombinedBinding>() {
|
||||
class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAuthFragment<FragmentFtueCombinedRegisterBinding>() {
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueSignUpCombinedBinding {
|
||||
return FragmentFtueSignUpCombinedBinding.inflate(inflater, container, false)
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueCombinedRegisterBinding {
|
||||
return FragmentFtueCombinedRegisterBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2022 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.app.features.onboarding.ftueauth
|
||||
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import com.airbnb.mvrx.args
|
||||
import im.vector.app.databinding.FragmentLoginCaptchaBinding
|
||||
import im.vector.app.features.onboarding.OnboardingAction
|
||||
import im.vector.app.features.onboarding.OnboardingViewState
|
||||
import im.vector.app.features.onboarding.RegisterAction
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import javax.inject.Inject
|
||||
|
||||
@Parcelize
|
||||
data class FtueAuthLegacyStyleCaptchaFragmentArgument(
|
||||
val siteKey: String
|
||||
) : Parcelable
|
||||
|
||||
/**
|
||||
* In this screen, the user is asked to confirm they are not a robot
|
||||
*/
|
||||
class FtueAuthLegacyStyleCaptchaFragment @Inject constructor(
|
||||
private val captchaWebview: CaptchaWebview
|
||||
) : AbstractFtueAuthFragment<FragmentLoginCaptchaBinding>() {
|
||||
|
||||
private val params: FtueAuthLegacyStyleCaptchaFragmentArgument by args()
|
||||
private var isWebViewLoaded = false
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginCaptchaBinding {
|
||||
return FragmentLoginCaptchaBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun resetViewModel() {
|
||||
viewModel.handle(OnboardingAction.ResetAuthenticationAttempt)
|
||||
}
|
||||
|
||||
override fun updateWithState(state: OnboardingViewState) {
|
||||
if (!isWebViewLoaded) {
|
||||
captchaWebview.setupWebView(this, views.loginCaptchaWevView, views.loginCaptchaProgress, params.siteKey, state) {
|
||||
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CaptchaDone(it)))
|
||||
}
|
||||
isWebViewLoaded = true
|
||||
}
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
package im.vector.app.features.onboarding.ftueauth
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Parcelable
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.ViewCompat
|
||||
@ -51,8 +52,9 @@ import im.vector.app.features.onboarding.OnboardingVariant
|
||||
import im.vector.app.features.onboarding.OnboardingViewEvents
|
||||
import im.vector.app.features.onboarding.OnboardingViewModel
|
||||
import im.vector.app.features.onboarding.OnboardingViewState
|
||||
import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthLegacyStyleTermsFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsFragmentArgument
|
||||
import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsLegacyStyleFragmentArgument
|
||||
import org.matrix.android.sdk.api.auth.registration.FlowResult
|
||||
import org.matrix.android.sdk.api.auth.registration.Stage
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
@ -201,20 +203,18 @@ class FtueAuthVariant(
|
||||
is OnboardingViewEvents.OnSendEmailSuccess -> {
|
||||
// Pop the enter email Fragment
|
||||
supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
||||
activity.addFragmentToBackstack(views.loginFragmentContainer,
|
||||
addRegistrationStageFragmentToBackstack(
|
||||
FtueAuthWaitForEmailFragment::class.java,
|
||||
FtueAuthWaitForEmailFragmentArgument(viewEvents.email),
|
||||
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
||||
option = commonOption)
|
||||
)
|
||||
}
|
||||
is OnboardingViewEvents.OnSendMsisdnSuccess -> {
|
||||
// Pop the enter Msisdn Fragment
|
||||
supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
||||
activity.addFragmentToBackstack(views.loginFragmentContainer,
|
||||
addRegistrationStageFragmentToBackstack(
|
||||
FtueAuthGenericTextInputFormFragment::class.java,
|
||||
FtueAuthGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, viewEvents.msisdn),
|
||||
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
||||
option = commonOption)
|
||||
)
|
||||
}
|
||||
is OnboardingViewEvents.Failure,
|
||||
is OnboardingViewEvents.Loading ->
|
||||
@ -245,12 +245,7 @@ class FtueAuthVariant(
|
||||
}
|
||||
|
||||
private fun openCombinedRegister() {
|
||||
activity.addFragmentToBackstack(
|
||||
views.loginFragmentContainer,
|
||||
FtueAuthCombinedRegisterFragment::class.java,
|
||||
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
||||
option = commonOption
|
||||
)
|
||||
addRegistrationStageFragmentToBackstack(FtueAuthCombinedRegisterFragment::class.java)
|
||||
}
|
||||
|
||||
private fun registrationShouldFallback(registrationFlowResult: OnboardingViewEvents.RegistrationFlowResult) =
|
||||
@ -382,30 +377,46 @@ class FtueAuthVariant(
|
||||
supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
||||
|
||||
when (stage) {
|
||||
is Stage.ReCaptcha -> activity.addFragmentToBackstack(views.loginFragmentContainer,
|
||||
FtueAuthCaptchaFragment::class.java,
|
||||
FtueAuthCaptchaFragmentArgument(stage.publicKey),
|
||||
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
||||
option = commonOption)
|
||||
is Stage.Email -> activity.addFragmentToBackstack(views.loginFragmentContainer,
|
||||
is Stage.ReCaptcha -> onCaptcha(stage)
|
||||
is Stage.Email -> addRegistrationStageFragmentToBackstack(
|
||||
FtueAuthGenericTextInputFormFragment::class.java,
|
||||
FtueAuthGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetEmail, stage.mandatory),
|
||||
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
||||
option = commonOption)
|
||||
is Stage.Msisdn -> activity.addFragmentToBackstack(views.loginFragmentContainer,
|
||||
)
|
||||
is Stage.Msisdn -> addRegistrationStageFragmentToBackstack(
|
||||
FtueAuthGenericTextInputFormFragment::class.java,
|
||||
FtueAuthGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetMsisdn, stage.mandatory),
|
||||
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
||||
option = commonOption)
|
||||
is Stage.Terms -> activity.addFragmentToBackstack(views.loginFragmentContainer,
|
||||
FtueAuthTermsFragment::class.java,
|
||||
FtueAuthTermsFragmentArgument(stage.policies.toLocalizedLoginTerms(activity.getString(R.string.resources_language))),
|
||||
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
||||
option = commonOption)
|
||||
)
|
||||
is Stage.Terms -> onTerms(stage)
|
||||
else -> Unit // Should not happen
|
||||
}
|
||||
}
|
||||
|
||||
private fun onTerms(stage: Stage.Terms) {
|
||||
when {
|
||||
vectorFeatures.isOnboardingCombinedRegisterEnabled() -> addRegistrationStageFragmentToBackstack(
|
||||
FtueAuthTermsFragment::class.java,
|
||||
FtueAuthTermsLegacyStyleFragmentArgument(stage.policies.toLocalizedLoginTerms(activity.getString(R.string.resources_language))),
|
||||
)
|
||||
else -> addRegistrationStageFragmentToBackstack(
|
||||
FtueAuthLegacyStyleTermsFragment::class.java,
|
||||
FtueAuthTermsLegacyStyleFragmentArgument(stage.policies.toLocalizedLoginTerms(activity.getString(R.string.resources_language))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCaptcha(stage: Stage.ReCaptcha) {
|
||||
when {
|
||||
vectorFeatures.isOnboardingCombinedRegisterEnabled() -> addRegistrationStageFragmentToBackstack(
|
||||
FtueAuthCaptchaFragment::class.java,
|
||||
FtueAuthCaptchaFragmentArgument(stage.publicKey),
|
||||
)
|
||||
else -> addRegistrationStageFragmentToBackstack(
|
||||
FtueAuthLegacyStyleCaptchaFragment::class.java,
|
||||
FtueAuthLegacyStyleCaptchaFragmentArgument(stage.publicKey),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onAccountSignedIn() {
|
||||
navigateToHome(createdAccount = false)
|
||||
}
|
||||
@ -447,4 +458,14 @@ class FtueAuthVariant(
|
||||
useCustomAnimation = true
|
||||
)
|
||||
}
|
||||
|
||||
private fun addRegistrationStageFragmentToBackstack(fragmentClass: Class<out Fragment>, params: Parcelable? = null) {
|
||||
activity.addFragmentToBackstack(
|
||||
views.loginFragmentContainer,
|
||||
fragmentClass,
|
||||
params,
|
||||
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
||||
option = commonOption
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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.app.features.onboarding.ftueauth.terms
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.airbnb.mvrx.args
|
||||
import im.vector.app.core.extensions.cleanup
|
||||
import im.vector.app.core.extensions.configureWith
|
||||
import im.vector.app.core.extensions.toReducedUrl
|
||||
import im.vector.app.core.utils.openUrlInChromeCustomTab
|
||||
import im.vector.app.databinding.FragmentLoginTermsBinding
|
||||
import im.vector.app.features.login.terms.LocalizedFlowDataLoginTermsChecked
|
||||
import im.vector.app.features.login.terms.LoginTermsViewState
|
||||
import im.vector.app.features.login.terms.PolicyController
|
||||
import im.vector.app.features.onboarding.OnboardingAction
|
||||
import im.vector.app.features.onboarding.OnboardingViewState
|
||||
import im.vector.app.features.onboarding.RegisterAction
|
||||
import im.vector.app.features.onboarding.ftueauth.AbstractFtueAuthFragment
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms
|
||||
import javax.inject.Inject
|
||||
|
||||
@Parcelize
|
||||
data class FtueAuthTermsLegacyStyleFragmentArgument(
|
||||
val localizedFlowDataLoginTerms: List<LocalizedFlowDataLoginTerms>
|
||||
) : Parcelable
|
||||
|
||||
/**
|
||||
* LoginTermsFragment displays the list of policies the user has to accept
|
||||
*/
|
||||
class FtueAuthLegacyStyleTermsFragment @Inject constructor(
|
||||
private val policyController: PolicyController
|
||||
) : AbstractFtueAuthFragment<FragmentLoginTermsBinding>(),
|
||||
PolicyController.PolicyControllerListener {
|
||||
|
||||
private val params: FtueAuthTermsLegacyStyleFragmentArgument by args()
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginTermsBinding {
|
||||
return FragmentLoginTermsBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
private var loginTermsViewState: LoginTermsViewState = LoginTermsViewState(emptyList())
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
setupViews()
|
||||
views.loginTermsPolicyList.configureWith(policyController)
|
||||
policyController.listener = this
|
||||
|
||||
val list = ArrayList<LocalizedFlowDataLoginTermsChecked>()
|
||||
|
||||
params.localizedFlowDataLoginTerms
|
||||
.forEach {
|
||||
list.add(LocalizedFlowDataLoginTermsChecked(it))
|
||||
}
|
||||
|
||||
loginTermsViewState = LoginTermsViewState(list)
|
||||
}
|
||||
|
||||
private fun setupViews() {
|
||||
views.loginTermsSubmit.setOnClickListener { submit() }
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
views.loginTermsPolicyList.cleanup()
|
||||
policyController.listener = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun renderState() {
|
||||
policyController.setData(loginTermsViewState.localizedFlowDataLoginTermsChecked)
|
||||
views.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) {
|
||||
localizedFlowDataLoginTerms.localizedUrl
|
||||
?.takeIf { it.isNotBlank() }
|
||||
?.let {
|
||||
openUrlInChromeCustomTab(requireContext(), null, it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun submit() {
|
||||
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.AcceptTerms))
|
||||
}
|
||||
|
||||
override fun updateWithState(state: OnboardingViewState) {
|
||||
policyController.homeServer = state.selectedHomeserver.userFacingUrl.toReducedUrl()
|
||||
renderState()
|
||||
}
|
||||
|
||||
override fun resetViewModel() {
|
||||
viewModel.handle(OnboardingAction.ResetAuthenticationAttempt)
|
||||
}
|
||||
}
|
40
vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt
Executable file → Normal file
40
vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt
Executable file → Normal file
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018 New Vector Ltd
|
||||
* Copyright (c) 2022 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.
|
||||
@ -17,16 +17,18 @@
|
||||
package im.vector.app.features.onboarding.ftueauth.terms
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.doOnLayout
|
||||
import com.airbnb.mvrx.args
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.cleanup
|
||||
import im.vector.app.core.extensions.configureWith
|
||||
import im.vector.app.core.extensions.toReducedUrl
|
||||
import im.vector.app.core.utils.openUrlInChromeCustomTab
|
||||
import im.vector.app.databinding.FragmentLoginTermsBinding
|
||||
import im.vector.app.databinding.FragmentFtueLoginTermsBinding
|
||||
import im.vector.app.features.login.terms.LocalizedFlowDataLoginTermsChecked
|
||||
import im.vector.app.features.login.terms.LoginTermsViewState
|
||||
import im.vector.app.features.login.terms.PolicyController
|
||||
@ -34,50 +36,46 @@ import im.vector.app.features.onboarding.OnboardingAction
|
||||
import im.vector.app.features.onboarding.OnboardingViewState
|
||||
import im.vector.app.features.onboarding.RegisterAction
|
||||
import im.vector.app.features.onboarding.ftueauth.AbstractFtueAuthFragment
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms
|
||||
import javax.inject.Inject
|
||||
|
||||
@Parcelize
|
||||
data class FtueAuthTermsFragmentArgument(
|
||||
val localizedFlowDataLoginTerms: List<LocalizedFlowDataLoginTerms>
|
||||
) : Parcelable
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* LoginTermsFragment displays the list of policies the user has to accept
|
||||
*/
|
||||
class FtueAuthTermsFragment @Inject constructor(
|
||||
private val policyController: PolicyController
|
||||
) : AbstractFtueAuthFragment<FragmentLoginTermsBinding>(),
|
||||
) : AbstractFtueAuthFragment<FragmentFtueLoginTermsBinding>(),
|
||||
PolicyController.PolicyControllerListener {
|
||||
|
||||
private val params: FtueAuthTermsFragmentArgument by args()
|
||||
private val params: FtueAuthTermsLegacyStyleFragmentArgument by args()
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginTermsBinding {
|
||||
return FragmentLoginTermsBinding.inflate(inflater, container, false)
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueLoginTermsBinding {
|
||||
return FragmentFtueLoginTermsBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
private var loginTermsViewState: LoginTermsViewState = LoginTermsViewState(emptyList())
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
setupViews()
|
||||
views.loginTermsPolicyList.configureWith(policyController)
|
||||
policyController.listener = this
|
||||
|
||||
val list = ArrayList<LocalizedFlowDataLoginTermsChecked>()
|
||||
|
||||
params.localizedFlowDataLoginTerms
|
||||
.forEach {
|
||||
list.add(LocalizedFlowDataLoginTermsChecked(it))
|
||||
}
|
||||
|
||||
loginTermsViewState = LoginTermsViewState(list)
|
||||
}
|
||||
|
||||
private fun setupViews() {
|
||||
views.loginTermsSubmit.setOnClickListener { submit() }
|
||||
views.termsSubmit.setOnClickListener { submit() }
|
||||
views.loginTermsPolicyList.setHasFixedSize(false)
|
||||
views.loginTermsPolicyList.configureWith(policyController, hasFixedSize = false, dividerDrawable = R.drawable.divider_horizontal)
|
||||
views.termsGutterStart.doOnLayout {
|
||||
val gutterSize = views.contentRoot.width * (views.termsGutterStart.layoutParams as ConstraintLayout.LayoutParams).guidePercent
|
||||
policyController.horizontalPadding = gutterSize.roundToInt()
|
||||
}
|
||||
policyController.listener = this
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
@ -90,7 +88,7 @@ class FtueAuthTermsFragment @Inject constructor(
|
||||
policyController.setData(loginTermsViewState.localizedFlowDataLoginTermsChecked)
|
||||
|
||||
// Button is enabled only if all checkboxes are checked
|
||||
views.loginTermsSubmit.isEnabled = loginTermsViewState.allChecked()
|
||||
views.termsSubmit.isEnabled = loginTermsViewState.allChecked()
|
||||
}
|
||||
|
||||
override fun setChecked(localizedFlowDataLoginTerms: LocalizedFlowDataLoginTerms, isChecked: Boolean) {
|
||||
|
10
vector/src/main/res/drawable/ic_privacy_policy.xml
Normal file
10
vector/src/main/res/drawable/ic_privacy_policy.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="71dp"
|
||||
android:height="70dp"
|
||||
android:viewportWidth="71"
|
||||
android:viewportHeight="70">
|
||||
<path
|
||||
android:pathData="M19.778,21.722C19.778,18.501 22.389,15.889 25.611,15.889H45.056C48.277,15.889 50.889,18.501 50.889,21.722V48.944C50.889,52.166 48.277,54.778 45.056,54.778H25.611C22.389,54.778 19.778,52.166 19.778,48.944V21.722ZM25.611,39.708C25.611,38.903 26.264,38.25 27.069,38.25H43.597C44.403,38.25 45.056,38.903 45.056,39.708C45.056,40.514 44.403,41.167 43.597,41.167H27.069C26.264,41.167 25.611,40.514 25.611,39.708ZM27.069,45.056C26.264,45.056 25.611,45.708 25.611,46.514C25.611,47.319 26.264,47.972 27.069,47.972H35.819C36.625,47.972 37.278,47.319 37.278,46.514C37.278,45.708 36.625,45.056 35.819,45.056H27.069Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
106
vector/src/main/res/layout/fragment_ftue_login_captcha.xml
Normal file
106
vector/src/main/res/layout/fragment_ftue_login_captcha.xml
Normal file
@ -0,0 +1,106 @@
|
||||
<?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:id="@+id/captchaRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:colorBackground">
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/captchaGutterStart"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="@dimen/ftue_auth_gutter_start_percent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/captchaGutterEnd"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="@dimen/ftue_auth_gutter_end_percent" />
|
||||
|
||||
<Space
|
||||
android:id="@+id/headerSpacing"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="52dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/captchaHeaderIcon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/captchaHeaderIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="@drawable/circle"
|
||||
android:backgroundTint="?colorSecondary"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_user_fg"
|
||||
app:layout_constraintBottom_toTopOf="@id/captchaHeaderTitle"
|
||||
app:layout_constraintEnd_toEndOf="@id/captchaGutterEnd"
|
||||
app:layout_constraintHeight_percent="0.10"
|
||||
app:layout_constraintStart_toStartOf="@id/captchaGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/headerSpacing"
|
||||
app:tint="@color/palette_white" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/captchaHeaderTitle"
|
||||
style="@style/Widget.Vector.TextView.Title.Medium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/ftue_auth_create_account_title"
|
||||
android:textColor="?vctr_content_primary"
|
||||
app:layout_constraintBottom_toTopOf="@id/captchaHeaderSubtitle"
|
||||
app:layout_constraintEnd_toEndOf="@id/captchaGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/captchaGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/captchaHeaderIcon" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/captchaHeaderSubtitle"
|
||||
style="@style/Widget.Vector.TextView.Subtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/auth_recaptcha_message"
|
||||
android:textColor="?vctr_content_secondary"
|
||||
app:layout_constraintBottom_toTopOf="@id/titleContentSpacing"
|
||||
app:layout_constraintEnd_toEndOf="@id/captchaGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/captchaGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/captchaHeaderTitle" />
|
||||
|
||||
<Space
|
||||
android:id="@+id/titleContentSpacing"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/loginCaptchaWevView"
|
||||
app:layout_constraintHeight_percent="0.03"
|
||||
app:layout_constraintTop_toBottomOf="@id/captchaHeaderSubtitle" />
|
||||
|
||||
<WebView
|
||||
android:id="@+id/loginCaptchaWevView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:contentDescription="@string/login_a11y_captcha_container"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/captchaGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/captchaGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/titleContentSpacing" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/loginCaptchaProgress"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_gravity="center"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/captchaGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/captchaGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/headerSpacing"
|
||||
tools:ignore="UnknownId,NotSibling" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
121
vector/src/main/res/layout/fragment_ftue_login_terms.xml
Normal file
121
vector/src/main/res/layout/fragment_ftue_login_terms.xml
Normal file
@ -0,0 +1,121 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="@style/LoginFormScrollView"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:colorBackground"
|
||||
android:fillViewport="true"
|
||||
android:paddingTop="0dp"
|
||||
android:paddingBottom="0dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/contentRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/termsGutterStart"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="@dimen/ftue_auth_gutter_start_percent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/termsGutterEnd"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="@dimen/ftue_auth_gutter_end_percent" />
|
||||
|
||||
<Space
|
||||
android:id="@+id/headerSpacing"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="52dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/termsHeaderIcon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/termsHeaderIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="@drawable/circle"
|
||||
android:backgroundTint="?colorSecondary"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_privacy_policy"
|
||||
app:layout_constraintBottom_toTopOf="@id/termsHeaderTitle"
|
||||
app:layout_constraintEnd_toEndOf="@id/termsGutterEnd"
|
||||
app:layout_constraintHeight_percent="0.12"
|
||||
app:layout_constraintStart_toStartOf="@id/termsGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/headerSpacing"
|
||||
app:tint="@color/palette_white" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/termsHeaderTitle"
|
||||
style="@style/Widget.Vector.TextView.Title.Medium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/ftue_auth_terms_title"
|
||||
android:textColor="?vctr_content_primary"
|
||||
app:layout_constraintBottom_toTopOf="@id/termsHeaderSubtitle"
|
||||
app:layout_constraintEnd_toEndOf="@id/termsGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/termsGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/termsHeaderIcon" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/termsHeaderSubtitle"
|
||||
style="@style/Widget.Vector.TextView.Subtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/ftue_auth_terms_subtitle"
|
||||
android:textColor="?vctr_content_secondary"
|
||||
app:layout_constraintBottom_toTopOf="@id/titleContentSpacing"
|
||||
app:layout_constraintEnd_toEndOf="@id/termsGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/termsGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/termsHeaderTitle" />
|
||||
|
||||
<Space
|
||||
android:id="@+id/titleContentSpacing"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/loginTermsPolicyList"
|
||||
app:layout_constraintHeight_percent="0.03"
|
||||
app:layout_constraintTop_toBottomOf="@id/termsHeaderSubtitle" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/loginTermsPolicyList"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toTopOf="@id/entrySpacing"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/titleContentSpacing" />
|
||||
|
||||
<Space
|
||||
android:id="@+id/entrySpacing"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/termsSubmit"
|
||||
app:layout_constraintHeight_percent="0.05"
|
||||
app:layout_constraintTop_toBottomOf="@id/loginTermsPolicyList" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/termsSubmit"
|
||||
style="@style/Widget.Vector.Button.Login"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_signup_submit"
|
||||
android:textAllCaps="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/termsGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/termsGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/entrySpacing" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
@ -29,7 +29,6 @@
|
||||
android:text="@string/auth_recaptcha_message"
|
||||
android:textColor="?vctr_content_secondary" />
|
||||
|
||||
<!-- contentDescription does not work on WebView? -->
|
||||
<WebView
|
||||
android:id="@+id/loginCaptchaWevView"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -48,6 +48,8 @@
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/loginTermsSubmit"
|
||||
app:layout_constraintTop_toBottomOf="@id/loginTermsNotice"
|
||||
tools:listitem="@layout/item_policy" />
|
||||
|
@ -52,6 +52,8 @@
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/loginTermsSubmit"
|
||||
app:layout_constraintTop_toBottomOf="@id/loginTermsNotice"
|
||||
tools:listitem="@layout/item_policy" />
|
||||
|
@ -6,24 +6,22 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:minHeight="72dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
tools:viewBindingIgnore="true">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/adapter_item_policy_checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:buttonTint="@color/checkbox_tint_selector"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/adapter_item_policy_title"
|
||||
style="@style/Widget.Vector.TextView.Body"
|
||||
style="@style/Widget.Vector.TextView.Subtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="8dp"
|
||||
android:textColor="?vctr_content_primary"
|
||||
app:layout_constraintBottom_toTopOf="@id/adapter_item_policy_subtitle"
|
||||
app:layout_constraintEnd_toStartOf="@id/adapter_item_policy_arrow"
|
||||
@ -50,16 +48,15 @@
|
||||
<ImageView
|
||||
android:id="@+id/adapter_item_policy_arrow"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="16dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:importantForAccessibility="no"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:rotationY="@integer/rtl_mirror_flip"
|
||||
android:src="@drawable/ic_arrow_right"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="?vctr_content_secondary"
|
||||
app:tint="?vctr_content_tertiary"
|
||||
tools:ignore="MissingPrefix" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
@ -28,4 +28,7 @@
|
||||
<string name="ftue_auth_choose_server_ems_subtitle">Element Matrix Services (EMS) is a robust and reliable hosting service for fast, secure and real time communication. Find out how on <a href="${ftue_ems_url}">element.io/ems</a></string>
|
||||
<string name="ftue_auth_choose_server_ems_cta">Get in touch</string>
|
||||
|
||||
<string name="ftue_auth_terms_title">Privacy policy</string>
|
||||
<string name="ftue_auth_terms_subtitle">Please read through T&C. You must accept in order to continue.</string>
|
||||
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user