Update sync auth activity ui

This commit is contained in:
Koitharu
2025-08-14 10:39:06 +03:00
parent 7a7d20dbf4
commit 6e181a59a3
2 changed files with 192 additions and 253 deletions

View File

@@ -5,18 +5,20 @@ import android.accounts.AccountAuthenticatorResponse
import android.accounts.AccountManager
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.View
import android.widget.Button
import android.view.ViewGroup.MarginLayoutParams
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.activity.viewModels
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.fragment.app.FragmentResultListener
import androidx.transition.Fade
import androidx.transition.TransitionManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.transition.MaterialSharedAxis
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.BaseActivity
@@ -31,13 +33,20 @@ import org.koitharu.kotatsu.databinding.ActivitySyncAuthBinding
import org.koitharu.kotatsu.sync.data.SyncSettings
import org.koitharu.kotatsu.sync.domain.SyncAuthResult
private const val PAGE_EMAIL = 0
private const val PAGE_PASSWORD = 1
private const val PASSWORD_MIN_LENGTH = 4
@AndroidEntryPoint
class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickListener, FragmentResultListener {
class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickListener, FragmentResultListener,
DefaultTextWatcher {
private var accountAuthenticatorResponse: AccountAuthenticatorResponse? = null
private var resultBundle: Bundle? = null
private val pageBackCallback = PageBackCallback()
private val regexEmail = Regex("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", RegexOption.IGNORE_CASE)
private val viewModel by viewModels<SyncAuthViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
@@ -46,14 +55,11 @@ class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickLi
accountAuthenticatorResponse =
intent.getParcelableExtraCompat(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE)
accountAuthenticatorResponse?.onRequestContinued()
viewBinding.buttonCancel.setOnClickListener(this)
viewBinding.buttonNext.setOnClickListener(this)
viewBinding.buttonBack.setOnClickListener(this)
viewBinding.buttonDone.setOnClickListener(this)
viewBinding.layoutProgress.setOnClickListener(this)
viewBinding.buttonSettings.setOnClickListener(this)
viewBinding.editEmail.addTextChangedListener(EmailTextWatcher(viewBinding.buttonNext))
viewBinding.editPassword.addTextChangedListener(PasswordTextWatcher(viewBinding.buttonDone))
viewBinding.editEmail.addTextChangedListener(this)
viewBinding.editPassword.addTextChangedListener(this)
onBackPressedDispatcher.addCallback(pageBackCallback)
@@ -65,17 +71,25 @@ class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickLi
}
supportFragmentManager.setFragmentResultListener(SyncHostDialogFragment.REQUEST_KEY, this, this)
pageBackCallback.update()
if (savedInstanceState == null) {
setPage(PAGE_EMAIL)
} else {
pageBackCallback.update()
}
}
override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
val barsInsets = insets.systemBarsInsets
val basePadding = resources.getDimensionPixelOffset(R.dimen.screen_padding)
viewBinding.root.setPadding(
barsInsets.left + basePadding,
barsInsets.top + basePadding,
barsInsets.right + basePadding,
barsInsets.bottom + basePadding,
viewBinding.root.updatePadding(top = barsInsets.top)
viewBinding.dockedToolbarChild.updateLayoutParams<MarginLayoutParams> {
leftMargin = barsInsets.left
rightMargin = barsInsets.right
bottomMargin = barsInsets.bottom
}
val basePadding = viewBinding.layoutContent.paddingBottom
viewBinding.layoutContent.updatePadding(
left = barsInsets.left + basePadding,
right = barsInsets.right + basePadding,
)
return insets.consumeAllSystemBarsInsets()
}
@@ -88,22 +102,18 @@ class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickLi
}
R.id.button_next -> {
viewBinding.groupLogin.isVisible = false
viewBinding.groupPassword.isVisible = true
pageBackCallback.update()
setPage(PAGE_PASSWORD)
viewBinding.editPassword.requestFocus()
}
R.id.button_back -> {
viewBinding.groupPassword.isVisible = false
viewBinding.groupLogin.isVisible = true
pageBackCallback.update()
setPage(PAGE_EMAIL)
viewBinding.editEmail.requestFocus()
}
R.id.button_done -> {
viewModel.obtainToken(
email = viewBinding.editEmail.text.toString(),
email = viewBinding.editEmail.text.toString().trim(),
password = viewBinding.editPassword.text.toString(),
)
}
@@ -128,12 +138,39 @@ class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickLi
super.finish()
}
override fun afterTextChanged(s: Editable?) {
val isLoading = viewModel.isLoading.value
val email = viewBinding.editEmail.text?.trim()?.toString()
val password = viewBinding.editPassword.text?.toString()
viewBinding.buttonNext.isEnabled = !isLoading && !email.isNullOrEmpty() && regexEmail.matches(email)
viewBinding.buttonDone.isEnabled = !isLoading && password != null && password.length >= PASSWORD_MIN_LENGTH
}
private fun onLoadingStateChanged(isLoading: Boolean) {
if (isLoading == viewBinding.layoutProgress.isVisible) {
return
with(viewBinding) {
progressBar.isInvisible = !isLoading
editEmail.isEnabled = !isLoading
editPassword.isEnabled = !isLoading
}
afterTextChanged(null)
pageBackCallback.update()
}
private fun setPage(page: Int) {
with(viewBinding) {
val currentPage = if (layoutEmail.isVisible) PAGE_EMAIL else PAGE_PASSWORD
if (currentPage != page) {
val transition = MaterialSharedAxis(MaterialSharedAxis.X, page > currentPage)
TransitionManager.beginDelayedTransition(layoutContent, transition)
}
buttonNext.isVisible = page == PAGE_EMAIL
buttonBack.isVisible = page == PAGE_PASSWORD
buttonSettings.isVisible = page == PAGE_EMAIL
buttonDone.isVisible = page == PAGE_PASSWORD
buttonCancel.isVisible = page == PAGE_EMAIL
layoutEmail.isVisible = page == PAGE_EMAIL
layoutPassword.isVisible = page == PAGE_PASSWORD
}
TransitionManager.beginDelayedTransition(viewBinding.root, Fade())
viewBinding.layoutProgress.isVisible = isLoading
pageBackCallback.update()
}
@@ -174,43 +211,16 @@ class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickLi
super.finishAfterTransition()
}
private class EmailTextWatcher(
private val button: Button,
) : TextWatcher {
private val regexEmail = Regex("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", RegexOption.IGNORE_CASE)
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
override fun afterTextChanged(s: Editable?) {
val text = s?.toString()
button.isEnabled = !text.isNullOrEmpty() && regexEmail.matches(text)
}
}
private class PasswordTextWatcher(
private val button: Button,
) : DefaultTextWatcher {
override fun afterTextChanged(s: Editable?) {
val text = s?.toString()
button.isEnabled = text != null && text.length >= 4
}
}
private inner class PageBackCallback : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
viewBinding.groupLogin.isVisible = true
viewBinding.groupPassword.isVisible = false
setPage(PAGE_EMAIL)
viewBinding.editEmail.requestFocus()
update()
}
fun update() {
isEnabled = !viewBinding.layoutProgress.isVisible && viewBinding.groupPassword.isVisible
isEnabled = !viewBinding.progressBar.isVisible && viewBinding.editPassword.isVisible
}
}
}

View File

@@ -1,227 +1,156 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
<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="match_parent"
android:orientation="vertical"
android:padding="@dimen/screen_padding">
android:orientation="vertical">
<TextView
android:id="@+id/textView_title"
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="16dp"
android:layout_marginHorizontal="@dimen/screen_padding"
android:layout_marginTop="24dp"
android:drawablePadding="@dimen/screen_padding"
android:gravity="center_horizontal"
android:text="@string/sync_title"
android:textAppearance="?textAppearanceHeadline5"
app:drawableTint="?colorPrimary"
app:drawableTopCompat="@drawable/ic_sync"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:drawableTopCompat="@drawable/ic_sync" />
<ImageButton
android:id="@+id/button_settings"
android:layout_width="wrap_content"
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/settings"
android:tooltipText="@string/settings"
android:padding="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_settings" />
android:layout_marginHorizontal="@dimen/screen_padding"
android:layout_marginTop="@dimen/screen_padding"
android:max="100"
android:visibility="invisible" />
<TextView
android:id="@+id/textView_subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:gravity="center_horizontal"
android:text="@string/email_enter_hint"
android:textAppearance="?textAppearanceSubtitle1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_title" />
<ScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_email"
style="?textInputOutlinedStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
app:errorIconDrawable="@null"
app:helperText="@string/sync_auth_hint"
app:hintEnabled="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_subtitle">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edit_email"
<LinearLayout
android:id="@+id/layout_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="username"
android:imeOptions="actionDone"
android:inputType="textEmailAddress"
android:singleLine="true"
android:textSize="16sp"
tools:text="test@mail.com" />
android:orientation="vertical"
android:paddingHorizontal="@dimen/screen_padding"
android:paddingBottom="@dimen/screen_padding">
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/textView_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/sync_auth_hint"
android:textAppearance="?textAppearanceBodySmall" />
<Button
android:id="@+id/button_cancel"
style="?materialButtonOutlinedStyle"
android:layout_width="wrap_content"
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_email"
style="?textInputOutlinedStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
app:errorIconDrawable="@null">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edit_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="username"
android:hint="@string/email"
android:imeOptions="actionDone"
android:inputType="textEmailAddress"
android:singleLine="true"
android:textSize="16sp" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_password"
style="?textInputOutlinedStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
app:endIconMode="password_toggle"
app:errorIconDrawable="@null">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edit_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="password"
android:hint="@string/password"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:maxLength="24"
android:singleLine="true"
android:textSize="16sp" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/button_settings"
style="?borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/settings" />
</LinearLayout>
</ScrollView>
<com.google.android.material.dockedtoolbar.DockedToolbarLayout
android:id="@+id/docked_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@android:string/cancel"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
android:fitsSystemWindows="false">
<Button
android:id="@+id/button_next"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:enabled="false"
android:text="@string/next"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/textView_subtitle_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:gravity="center_horizontal"
android:text="@string/enter_password"
android:textAppearance="?textAppearanceSubtitle1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_title" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_password"
style="?textInputOutlinedStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
app:endIconMode="password_toggle"
app:errorIconDrawable="@null"
app:hintEnabled="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_subtitle_2">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edit_password"
<FrameLayout
android:id="@+id/docked_toolbar_child"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="password"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:maxLength="24"
android:singleLine="true"
android:textSize="16sp"
tools:text="qwerty" />
android:layout_height="@dimen/m3_comp_toolbar_docked_container_height">
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/button_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:text="@android:string/cancel" />
<Button
android:id="@+id/button_back"
style="?materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/back"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/button_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:text="@string/back"
android:visibility="gone" />
<Button
android:id="@+id/button_done"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:enabled="false"
android:text="@string/done"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/button_next"
style="?materialButtonTonalStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:enabled="false"
android:text="@string/next" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="textView_subtitle,button_cancel,button_next,layout_email" />
<Button
android:id="@+id/button_done"
style="?materialButtonTonalStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:enabled="false"
android:text="@string/done"
android:visibility="gone" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_password"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="textView_subtitle_2,button_back,button_done,layout_password" />
</FrameLayout>
</com.google.android.material.dockedtoolbar.DockedToolbarLayout>
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier_input"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="layout_email,layout_password" />
<View
android:id="@+id/view_line"
android:layout_width="4dp"
android:layout_height="0dp"
android:background="@drawable/bg_chip"
android:backgroundTint="?colorError"
app:layout_constraintBottom_toBottomOf="@id/textView_hint_body"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/textView_hint_title" />
<TextView
android:id="@+id/textView_hint_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="32dp"
android:drawablePadding="10dp"
android:gravity="center_vertical"
android:text="@string/unstable_feature"
android:textAppearance="?textAppearanceTitleMedium"
app:drawableStartCompat="@drawable/ic_alert_outline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/view_line"
app:layout_constraintTop_toBottomOf="@id/barrier_input" />
<TextView
android:id="@+id/textView_hint_body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:paddingBottom="6dp"
android:text="@string/unstable_feature_summary"
android:textAppearance="?textAppearanceBodyMedium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/textView_hint_title"
app:layout_constraintTop_toBottomOf="@id/textView_hint_title" />
<FrameLayout
android:id="@+id/layout_progress"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?android:windowBackground"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_title">
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/circularProgressIndicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>