SoftLogout: use Epoxy

This commit is contained in:
Benoit Marty 2019-12-12 22:58:15 +01:00
parent 6811d31a6d
commit 00d0c34363
22 changed files with 763 additions and 305 deletions

View file

@ -20,6 +20,8 @@ import im.vector.riotx.core.platform.VectorViewModelAction
sealed class SoftLogoutAction : VectorViewModelAction {
object RetryLoginFlow : SoftLogoutAction()
object TogglePassword : SoftLogoutAction()
data class SignInAgain(val password: String) : SoftLogoutAction()
// TODO Add reset pwd...
}

View file

@ -0,0 +1,150 @@
/*
* 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.signout
import com.airbnb.epoxy.EpoxyController
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Success
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.loadingItem
import im.vector.riotx.core.error.ErrorFormatter
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.login.LoginMode
import im.vector.riotx.features.signout.epoxy.*
import javax.inject.Inject
class SoftLogoutController @Inject constructor(
private val stringProvider: StringProvider,
private val errorFormatter: ErrorFormatter
) : EpoxyController() {
var listener: Listener? = null
private var viewState: SoftLogoutViewState? = null
init {
// We are requesting a model build directly as the first build of epoxy is on the main thread.
// It avoids to build the whole list of breadcrumbs on the main thread.
requestModelBuild()
}
fun update(viewState: SoftLogoutViewState) {
this.viewState = viewState
requestModelBuild()
}
override fun buildModels() {
val safeViewState = viewState ?: return
buildHeader(safeViewState)
buildForm(safeViewState)
buildClearDataSection()
}
private fun buildHeader(state: SoftLogoutViewState) {
loginHeaderItem {
id("header")
}
loginTitleItem {
id("title")
text(stringProvider.getString(R.string.soft_logout_title))
}
loginTitleSmallItem {
id("signTitle")
text(stringProvider.getString(R.string.soft_logout_signin_title))
}
loginTextItem {
id("signText1")
text(stringProvider.getString(R.string.soft_logout_signin_notice,
state.homeServerUrl,
state.userDisplayName,
state.userId))
}
if (state.hasUnsavedKeys) {
loginTextItem {
id("signText2")
text(stringProvider.getString(R.string.soft_logout_signin_e2e_warning_notice))
}
}
}
private fun buildForm(state: SoftLogoutViewState) {
when (state.asyncHomeServerLoginFlowRequest) {
is Incomplete -> {
loadingItem {
id("loading")
}
}
is Fail -> {
loginErrorWithRetryItem {
id("errorRetry")
text(errorFormatter.toHumanReadable(state.asyncHomeServerLoginFlowRequest.error))
listener { listener?.retry() }
}
}
is Success -> {
when (state.asyncHomeServerLoginFlowRequest.invoke()) {
LoginMode.Password -> {
loginPasswordFormItem {
id("passwordForm")
stringProvider(stringProvider)
passwordShown(state.passwordShown)
errorText((state.asyncLoginAction as? Fail)?.error?.let { errorFormatter.toHumanReadable(it) })
passwordRevealClickListener { listener?.revealPasswordClicked() }
forgetPasswordClickListener { listener?.forgetPasswordClicked() }
submitClickListener { password -> listener?.submit(password) }
}
}
LoginMode.Sso -> {
loginCenterButtonItem {
id("sso")
listener { listener?.ssoSubmit() }
}
}
LoginMode.Unknown -> Unit // Should not happen
LoginMode.Unsupported -> Unit // Should not happen
}
}
}
}
private fun buildClearDataSection() {
loginTitleSmallItem {
id("clearDataTitle")
text(stringProvider.getString(R.string.soft_logout_clear_data_title))
}
loginTextItem {
id("clearDataText")
text(stringProvider.getString(R.string.soft_logout_clear_data_notice))
}
loginRedButtonItem {
id("clearDataSubmit")
text(stringProvider.getString(R.string.soft_logout_clear_data_submit))
listener { listener?.clearData() }
}
}
interface Listener {
fun retry()
fun submit(password: String)
fun ssoSubmit()
fun clearData()
fun forgetPasswordClicked()
fun revealPasswordClicked()
}
}

View file

@ -17,87 +17,78 @@
package im.vector.riotx.features.signout
import android.content.DialogInterface
import android.os.Build
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.autofill.HintConstants
import androidx.core.view.isVisible
import butterknife.OnClick
import com.airbnb.mvrx.*
import com.jakewharton.rxbinding3.widget.textChanges
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
import im.vector.riotx.R
import im.vector.riotx.core.dialogs.withColoredButton
import im.vector.riotx.core.error.ErrorFormatter
import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.configureWith
import im.vector.riotx.core.extensions.hideKeyboard
import im.vector.riotx.core.extensions.setTextOrHide
import im.vector.riotx.core.extensions.showPassword
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.MainActivity
import im.vector.riotx.features.MainActivityArgs
import im.vector.riotx.features.login.LoginMode
import io.reactivex.rxkotlin.subscribeBy
import kotlinx.android.synthetic.main.fragment_soft_logout.*
import kotlinx.android.synthetic.main.item_error_retry.*
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
import javax.inject.Inject
/**
* In this screen:
* - the user is asked to enter a password to sign in again to a homeserver.
* - or to cleanup all the data
* TODO: migrate to Epoxy (along with all the login screen?)
*/
class SoftLogoutFragment @Inject constructor(
private val errorFormatter: ErrorFormatter
) : VectorBaseFragment() {
private var passwordShown = false
private val errorFormatter: ErrorFormatter,
private val softLogoutController: SoftLogoutController
) : VectorBaseFragment(), SoftLogoutController.Listener {
private val softLogoutViewModel: SoftLogoutViewModel by activityViewModel()
override fun getLayoutResId() = R.layout.fragment_soft_logout
override fun getLayoutResId() = R.layout.fragment_generic_recycler
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupSubmitButton()
setupPasswordReveal()
setupAutoFill()
setupRecyclerView()
// TODO setupSubmitButton()
// TODO setupPasswordReveal()
}
private fun setupAutoFill() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
softLogoutPasswordField.setAutofillHints(HintConstants.AUTOFILL_HINT_PASSWORD)
}
private fun setupRecyclerView() {
recyclerView.configureWith(softLogoutController)
softLogoutController.listener = this
}
@OnClick(R.id.itemErrorRetryButton)
fun retry() {
override fun onDestroyView() {
recyclerView.cleanup()
softLogoutController.listener = null
super.onDestroyView()
}
override fun retry() {
softLogoutViewModel.handle(SoftLogoutAction.RetryLoginFlow)
}
@OnClick(R.id.softLogoutSubmit)
fun submit() {
override fun submit(password: String) {
cleanupUi()
val password = softLogoutPasswordField.text.toString()
softLogoutViewModel.handle(SoftLogoutAction.SignInAgain(password))
}
@OnClick(R.id.softLogoutFormSsoSubmit)
fun ssoSubmit() {
override fun ssoSubmit() {
// TODO
}
@OnClick(R.id.softLogoutClearDataSubmit)
fun clearData() {
override fun clearData() {
withState(softLogoutViewModel) { state ->
cleanupUi()
val messageResId = if (state.hasUnsavedKeys) {
R.string.soft_logout_clear_data_dialog_content
} else {
R.string.soft_logout_clear_data_dialog_e2e_warning_content
} else {
R.string.soft_logout_clear_data_dialog_content
}
AlertDialog.Builder(requireActivity())
@ -117,83 +108,30 @@ class SoftLogoutFragment @Inject constructor(
}
private fun cleanupUi() {
softLogoutSubmit.hideKeyboard()
softLogoutPasswordFieldTil.error = null
}
private fun setupUi(state: SoftLogoutViewState) {
softLogoutNotice.text = getString(R.string.soft_logout_signin_notice,
state.homeServerUrl,
state.userDisplayName,
state.userId)
softLogoutE2eWarningNotice.isVisible = state.hasUnsavedKeys
}
private fun setupForm(state: SoftLogoutViewState) {
softLogoutFormLoading.isVisible = state.asyncHomeServerLoginFlowRequest is Loading
softLogoutFormSsoSubmit.isVisible = state.asyncHomeServerLoginFlowRequest.invoke() == LoginMode.Sso
softLogoutFormPassword.isVisible = state.asyncHomeServerLoginFlowRequest.invoke() == LoginMode.Password
softLogoutFormError.isVisible = state.asyncHomeServerLoginFlowRequest is Fail
itemErrorRetryText.setTextOrHide((state.asyncHomeServerLoginFlowRequest as? Fail)?.error?.let { errorFormatter.toHumanReadable(it) })
recyclerView.hideKeyboard()
// TODO softLogoutPasswordFieldTil.error = null
}
private fun setupSubmitButton() {
softLogoutPasswordField.textChanges()
.map { it.trim().isNotEmpty() }
.subscribeBy {
softLogoutPasswordFieldTil.error = null
softLogoutSubmit.isEnabled = it
}
.disposeOnDestroyView()
// softLogoutPasswordField.textChanges()
// .map { it.trim().isNotEmpty() }
// .subscribeBy {
// softLogoutPasswordFieldTil.error = null
// softLogoutSubmit.isEnabled = it
// }
// .disposeOnDestroyView()
}
@OnClick(R.id.softLogoutForgetPasswordButton)
fun forgetPasswordClicked() {
override fun forgetPasswordClicked() {
// TODO
// loginSharedActionViewModel.post(LoginNavigation.OnForgetPasswordClicked)
}
private fun setupPasswordReveal() {
passwordShown = false
softLogoutPasswordReveal.setOnClickListener {
passwordShown = !passwordShown
renderPasswordField()
}
renderPasswordField()
}
private fun renderPasswordField() {
softLogoutPasswordField.showPassword(passwordShown)
if (passwordShown) {
softLogoutPasswordReveal.setImageResource(R.drawable.ic_eye_closed_black)
softLogoutPasswordReveal.contentDescription = getString(R.string.a11y_hide_password)
} else {
softLogoutPasswordReveal.setImageResource(R.drawable.ic_eye_black)
softLogoutPasswordReveal.contentDescription = getString(R.string.a11y_show_password)
}
override fun revealPasswordClicked() {
softLogoutViewModel.handle(SoftLogoutAction.TogglePassword)
}
override fun invalidate() = withState(softLogoutViewModel) { state ->
setupUi(state)
setupForm(state)
setupAutoFill()
when (state.asyncLoginAction) {
is Loading -> {
// Ensure password is hidden
passwordShown = false
renderPasswordField()
}
is Fail -> {
softLogoutPasswordFieldTil.error = errorFormatter.toHumanReadable(state.asyncLoginAction.error)
}
// Success is handled by the SoftLogoutActivity
is Success -> Unit
}
softLogoutController.update(state)
}
}

View file

@ -145,11 +145,28 @@ class SoftLogoutViewModel @AssistedInject constructor(
when (action) {
is SoftLogoutAction.RetryLoginFlow -> getSupportedLoginFlow()
is SoftLogoutAction.SignInAgain -> handleSignInAgain(action)
is SoftLogoutAction.TogglePassword -> handleTogglePassword()
}
}
private fun handleTogglePassword() {
withState {
setState {
copy(
passwordShown = !this.passwordShown
)
}
}
}
private fun handleSignInAgain(action: SoftLogoutAction.SignInAgain) {
setState { copy(asyncLoginAction = Loading()) }
setState {
copy(
asyncLoginAction = Loading(),
// Ensure password is hidden
passwordShown = false
)
}
currentTask = session.signInAgain(action.password,
object : MatrixCallback<Unit> {
override fun onFailure(failure: Throwable) {

View file

@ -28,7 +28,8 @@ data class SoftLogoutViewState(
val homeServerUrl: String,
val userId: String,
val userDisplayName: String,
val hasUnsavedKeys: Boolean
val hasUnsavedKeys: Boolean,
val passwordShown: Boolean = false
) : MvRxState {
fun isLoading(): Boolean {

View file

@ -0,0 +1,45 @@
/*
* 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.signout.epoxy
import android.widget.Button
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
import im.vector.riotx.core.extensions.setTextOrHide
@EpoxyModelClass(layout = R.layout.item_login_centered_button)
abstract class LoginCenterButtonItem : VectorEpoxyModel<LoginCenterButtonItem.Holder>() {
@EpoxyAttribute var text: String? = null
@EpoxyAttribute var listener: (() -> Unit)? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.button.setTextOrHide(text)
holder.button.setOnClickListener {
listener?.invoke()
}
}
class Holder : VectorEpoxyHolder() {
val button by bind<Button>(R.id.itemLoginCenteredButton)
}
}

View file

@ -0,0 +1,45 @@
/*
* 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.signout.epoxy
import android.widget.Button
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
@EpoxyModelClass(layout = R.layout.item_login_error_retry)
abstract class LoginErrorWithRetryItem : VectorEpoxyModel<LoginErrorWithRetryItem.Holder>() {
@EpoxyAttribute
var text: String? = null
@EpoxyAttribute
var listener: (() -> Unit)? = null
override fun bind(holder: Holder) {
holder.textView.text = text
holder.buttonView.setOnClickListener { listener?.invoke() }
}
class Holder : VectorEpoxyHolder() {
val textView by bind<TextView>(R.id.itemLoginErrorRetryText)
val buttonView by bind<Button>(R.id.itemLoginErrorRetryButton)
}
}

View file

@ -0,0 +1,27 @@
/*
* 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.signout.epoxy
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
@EpoxyModelClass(layout = R.layout.item_login_header)
abstract class LoginHeaderItem : VectorEpoxyModel<LoginHeaderItem.Holder>() {
class Holder : VectorEpoxyHolder()
}

View file

@ -0,0 +1,79 @@
/*
* 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.signout.epoxy
import android.os.Build
import android.widget.Button
import android.widget.ImageView
import androidx.autofill.HintConstants
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
import im.vector.riotx.core.extensions.showPassword
import im.vector.riotx.core.resources.StringProvider
@EpoxyModelClass(layout = R.layout.item_login_password_form)
abstract class LoginPasswordFormItem : VectorEpoxyModel<LoginPasswordFormItem.Holder>() {
@EpoxyAttribute var passwordShown: Boolean = false
@EpoxyAttribute var errorText: String? = null
@EpoxyAttribute lateinit var stringProvider: StringProvider
@EpoxyAttribute var passwordRevealClickListener: (() -> Unit)? = null
@EpoxyAttribute var forgetPasswordClickListener: (() -> Unit)? = null
@EpoxyAttribute var submitClickListener: ((String) -> Unit)? = null
override fun bind(holder: Holder) {
super.bind(holder)
setupAutoFill(holder)
holder.passwordFieldTil.error = errorText
renderPasswordField(holder)
holder.passwordReveal.setOnClickListener { passwordRevealClickListener?.invoke() }
holder.forgetPassword.setOnClickListener { forgetPasswordClickListener?.invoke() }
holder.submit.setOnClickListener { submitClickListener?.invoke(holder.passwordField.text.toString()) }
}
private fun setupAutoFill(holder: Holder) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
holder.passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_PASSWORD)
}
}
private fun renderPasswordField(holder: Holder) {
holder.passwordField.showPassword(passwordShown)
if (passwordShown) {
holder.passwordReveal.setImageResource(R.drawable.ic_eye_closed_black)
holder.passwordReveal.contentDescription = stringProvider.getString(R.string.a11y_hide_password)
} else {
holder.passwordReveal.setImageResource(R.drawable.ic_eye_black)
holder.passwordReveal.contentDescription = stringProvider.getString(R.string.a11y_show_password)
}
}
class Holder : VectorEpoxyHolder() {
val passwordField by bind<TextInputEditText>(R.id.itemLoginPasswordFormPasswordField)
val passwordFieldTil by bind<TextInputLayout>(R.id.itemLoginPasswordFormPasswordFieldTil)
val passwordReveal by bind<ImageView>(R.id.itemLoginPasswordFormPasswordReveal)
val forgetPassword by bind<Button>(R.id.itemLoginPasswordFormForgetPasswordButton)
val submit by bind<Button>(R.id.itemLoginPasswordFormSubmit)
}
}

View file

@ -0,0 +1,45 @@
/*
* 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.signout.epoxy
import android.widget.Button
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
import im.vector.riotx.core.extensions.setTextOrHide
@EpoxyModelClass(layout = R.layout.item_login_red_button)
abstract class LoginRedButtonItem : VectorEpoxyModel<LoginRedButtonItem.Holder>() {
@EpoxyAttribute var text: String? = null
@EpoxyAttribute var listener: (() -> Unit)? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.button.setTextOrHide(text)
holder.button.setOnClickListener {
listener?.invoke()
}
}
class Holder : VectorEpoxyHolder() {
val button by bind<Button>(R.id.itemLoginRedButton)
}
}

View file

@ -0,0 +1,41 @@
/*
* 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.signout.epoxy
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
import im.vector.riotx.core.extensions.setTextOrHide
@EpoxyModelClass(layout = R.layout.item_login_text)
abstract class LoginTextItem : VectorEpoxyModel<LoginTextItem.Holder>() {
@EpoxyAttribute var text: String? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.textView.setTextOrHide(text)
}
class Holder : VectorEpoxyHolder() {
val textView by bind<TextView>(R.id.itemLoginText)
}
}

View file

@ -0,0 +1,41 @@
/*
* 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.signout.epoxy
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
import im.vector.riotx.core.extensions.setTextOrHide
@EpoxyModelClass(layout = R.layout.item_login_title)
abstract class LoginTitleItem : VectorEpoxyModel<LoginTitleItem.Holder>() {
@EpoxyAttribute var text: String? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.textView.setTextOrHide(text)
}
class Holder : VectorEpoxyHolder() {
val textView by bind<TextView>(R.id.itemLoginTitleText)
}
}

View file

@ -0,0 +1,41 @@
/*
* 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.signout.epoxy
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
import im.vector.riotx.core.extensions.setTextOrHide
@EpoxyModelClass(layout = R.layout.item_login_title_small)
abstract class LoginTitleSmallItem : VectorEpoxyModel<LoginTitleSmallItem.Holder>() {
@EpoxyAttribute var text: String? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.textView.setTextOrHide(text)
}
class Holder : VectorEpoxyHolder() {
val textView by bind<TextView>(R.id.itemLoginTitleSmallText)
}
}

View file

@ -1,200 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/softLogout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_background">
<!-- Missing attributes are in the style -->
<ImageView
style="@style/LoginLogo"
tools:ignore="ContentDescription,MissingConstraints" />
<!-- Missing attributes are in the style -->
<androidx.core.widget.NestedScrollView
style="@style/LoginFormScrollView"
tools:ignore="MissingConstraints">
<LinearLayout
style="@style/LoginFormContainer"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/soft_logout_title"
android:textAppearance="@style/TextAppearance.Vector.Login.Title" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:text="@string/soft_logout_signin_title"
android:textAppearance="@style/TextAppearance.Vector.Login.Title.Small" />
<TextView
android:id="@+id/softLogoutNotice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="start"
android:textAppearance="@style/TextAppearance.Vector.Login.Text"
tools:text="@string/soft_logout_signin_notice" />
<TextView
android:id="@+id/softLogoutE2eWarningNotice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="start"
android:text="@string/soft_logout_signin_e2e_warning_notice"
android:textAppearance="@style/TextAppearance.Vector.Login.Text"
android:visibility="gone"
tools:visibility="visible" />
<FrameLayout
android:id="@+id/softLogoutFormContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<!-- Displayed while loading -->
<ProgressBar
android:id="@+id/softLogoutFormLoading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
<!-- Displayed for SSO mode -->
<com.google.android.material.button.MaterialButton
android:id="@+id/softLogoutFormSsoSubmit"
style="@style/Style.Vector.Login.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/login_signin_sso"
android:visibility="gone"
tools:visibility="visible" />
<!-- Displayed in case of error -->
<FrameLayout
android:id="@+id/softLogoutFormError"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include layout="@layout/item_error_retry" />
</FrameLayout>
<!-- Displayed for password mode -->
<LinearLayout
android:id="@+id/softLogoutFormPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<FrameLayout
android:id="@+id/softLogoutPasswordContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/softLogoutPasswordFieldTil"
style="@style/VectorTextInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/soft_logout_signin_password_hint"
app:errorEnabled="true"
app:errorIconDrawable="@null">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/softLogoutPasswordField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPassword"
android:maxLines="1"
android:paddingEnd="48dp"
android:paddingRight="48dp"
tools:ignore="RtlSymmetry" />
</com.google.android.material.textfield.TextInputLayout>
<ImageView
android:id="@+id/softLogoutPasswordReveal"
android:layout_width="@dimen/layout_touch_size"
android:layout_height="@dimen/layout_touch_size"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:background="?attr/selectableItemBackground"
android:scaleType="center"
android:src="@drawable/ic_eye_black"
android:tint="?attr/colorAccent"
tools:contentDescription="@string/a11y_show_password" />
</FrameLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/softLogoutForgetPasswordButton"
style="@style/Style.Vector.Login.Button.Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:text="@string/auth_forgot_password" />
<com.google.android.material.button.MaterialButton
android:id="@+id/softLogoutSubmit"
style="@style/Style.Vector.Login.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_gravity="end"
android:text="@string/soft_logout_signin_submit"
tools:enabled="false"
tools:ignore="RelativeOverlap" />
</RelativeLayout>
</LinearLayout>
</FrameLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:text="@string/soft_logout_clear_data_title"
android:textAppearance="@style/TextAppearance.Vector.Login.Title.Small" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="start"
android:text="@string/soft_logout_clear_data_notice"
android:textAppearance="@style/TextAppearance.Vector.Login.Text" />
<com.google.android.material.button.MaterialButton
android:id="@+id/softLogoutClearDataSubmit"
style="@style/Style.Vector.Login.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:text="@string/soft_logout_clear_data_submit"
app:backgroundTint="@color/vector_error_color" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.button.MaterialButton xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/itemLoginCenteredButton"
style="@style/Style.Vector.Login.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
tools:text="@string/login_signin_sso" />

View file

@ -0,0 +1,35 @@
<?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:background="?riotx_background"
android:paddingStart="32dp"
android:paddingTop="16dp"
android:paddingEnd="32dp">
<TextView
android:id="@+id/itemLoginErrorRetryText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:textColor="@color/riotx_notice"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Error" />
<com.google.android.material.button.MaterialButton
android:id="@+id/itemLoginErrorRetryButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/global_retry"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/itemLoginErrorRetryText" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="32dp"
android:importantForAccessibility="no"
android:src="@drawable/riotx_logo" />

View file

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_marginTop="16dp"
android:orientation="vertical"
android:paddingStart="36dp"
android:paddingEnd="36dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/itemLoginPasswordFormPasswordFieldTil"
style="@style/VectorTextInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/soft_logout_signin_password_hint"
app:errorEnabled="true"
app:errorIconDrawable="@null">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/itemLoginPasswordFormPasswordField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPassword"
android:maxLines="1"
android:paddingEnd="48dp"
android:paddingRight="48dp"
tools:ignore="RtlSymmetry" />
</com.google.android.material.textfield.TextInputLayout>
<ImageView
android:id="@+id/itemLoginPasswordFormPasswordReveal"
android:layout_width="@dimen/layout_touch_size"
android:layout_height="@dimen/layout_touch_size"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:background="?attr/selectableItemBackground"
android:scaleType="center"
android:src="@drawable/ic_eye_black"
android:tint="?attr/colorAccent"
tools:contentDescription="@string/a11y_show_password" />
</FrameLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/itemLoginPasswordFormForgetPasswordButton"
style="@style/Style.Vector.Login.Button.Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:text="@string/auth_forgot_password" />
<com.google.android.material.button.MaterialButton
android:id="@+id/itemLoginPasswordFormSubmit"
style="@style/Style.Vector.Login.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_gravity="end"
android:text="@string/soft_logout_signin_submit"
tools:enabled="false"
tools:ignore="RelativeOverlap" />
</RelativeLayout>
</LinearLayout>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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">
<com.google.android.material.button.MaterialButton
android:id="@+id/itemLoginRedButton"
style="@style/Style.Vector.Login.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:layout_marginEnd="36dp"
app:backgroundTint="@color/vector_error_color"
tools:text="@string/soft_logout_clear_data_submit" />
</FrameLayout>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/itemLoginText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="12dp"
android:paddingStart="36dp"
android:paddingEnd="36dp"
android:textAppearance="@style/TextAppearance.Vector.Login.Text"
tools:text="Login Title" />

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/itemLoginTitleText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="32dp"
android:paddingStart="36dp"
android:paddingEnd="36dp"
android:textAppearance="@style/TextAppearance.Vector.Login.Title"
tools:text="Login Title" />

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/itemLoginTitleSmallText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:paddingStart="36dp"
android:paddingEnd="36dp"
android:textAppearance="@style/TextAppearance.Vector.Login.Title.Small"
tools:text="Login Title" />