Create Account Dialog fragment for resetting and deleting account

This commit is contained in:
Hafiz 2022-04-26 04:15:33 -04:00
parent 9b5ff2b1e8
commit c3240be6d8
8 changed files with 319 additions and 37 deletions

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/gray_100" android:state_focused="true"/>
<item android:color="@color/gray_100" android:state_hovered="true"/>
<item android:color="@color/gray_100"/>
</selector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@android:color/black">
<path
android:fillColor="@android:color/white"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>

View file

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.cardview.widget.CardView
android:id="@+id/toolbar_cardview"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:cardElevation="0dp"
app:cardBackgroundColor="@color/gray_2_alpha"
app:cardCornerRadius="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageButton
android:id="@+id/back_imagebutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="8dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="8dp"
android:src="@drawable/arrow_back" />
<TextView
android:id="@+id/confirm_action_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="10dp"
android:alpha=".4"
android:background="?attr/selectableItemBackground"
android:fontFamily="@string/font_family_medium"
android:padding="8dp"
android:text="@string/delete_account"
android:textColor="@color/gray_10"
android:textSize="18sp" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
<TextView
android:id="@+id/title_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/toolbar_cardview"
android:layout_marginStart="@dimen/spacing_large"
android:layout_marginTop="@dimen/spacing_large"
android:layout_marginEnd="@dimen/spacing_large"
android:fontFamily="@string/font_family_medium"
android:text="@string/are_you_sure_you_want_to_delete"
android:textColor="@color/gray_50"
android:textSize="20sp" />
<TextView
android:id="@+id/warning_description_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/title_textview"
android:layout_marginStart="@dimen/spacing_large"
android:layout_marginTop="@dimen/spacing_large"
android:layout_marginEnd="@dimen/spacing_large"
android:alpha=".8"
android:fontFamily="@string/font_family_regular"
android:lineSpacingExtra="5dp"
android:text="@string/delete_account_description"
android:textColor="@color/gray_50"
android:textSize="16sp" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/confirmation_text_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/layout_rounded_bg_window"
android:layout_below="@id/warning_description_textview"
android:layout_marginStart="@dimen/spacing_large"
style="@style/DialogHabiticaAccountInputLayout"
android:layout_marginTop="@dimen/spacing_large"
android:layout_marginEnd="@dimen/spacing_large"
android:hint="@string/password"
app:hintTextColor="@color/gray_10"
android:textColorHint="@color/text_secondary"
app:hintTextAppearance="@style/DialogHabiticaAccountHintLabel">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/confirmation_input_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="@android:color/transparent"
android:letterSpacing=".35"
android:textSize="20sp"
android:inputType="textPassword"
android:paddingStart="@dimen/spacing_large"
android:paddingEnd="@dimen/spacing_large"
android:textColorHint="@color/text_secondary" />
</com.google.android.material.textfield.TextInputLayout>
</RelativeLayout>

View file

@ -118,6 +118,7 @@
<color name="black_20_alpha">#33000000</color>
<color name="black_50_alpha">#89000000</color>
<color name="gray_20_alpha">#331a181d</color>
<color name="gray_2_alpha">#051A181D</color>
<color name="blue_500_24">#3CA9DCF6</color>
<color name="light_gray_bg">#F6F4F8</color>

View file

@ -585,10 +585,13 @@
<string name="per_abbrv">Per</string>
<string name="int_abbrv">Int</string>
<string name="con_abbrv">Con</string>
<string name="reset_caps">RESET</string>
<string name="reset_account">Reset Account</string>
<string name="reset_account_description">WARNING! This resets many parts of your account. This is highly discouraged, but some people find it useful in the beginning after playing with the site for a short time.\n\nYou will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks and equipment.</string>
<string name="reset_account_title">Are you sure you want to reset?</string>
<string name="confirm_reset">Confirm reset</string>
<string name="reset_account_description">You will lose all your levels, Gold, and Experience. All your tasks and their historical data will be deleted (Challenge tasks will stay). You will lose all equipment, including limited edition or subscriber equipment, but you will be able to buy it back (you will need to be the correct class to re-buy class-specific gear). You will keep your current class and your Pets and Mounts. To confirm reset, type RESET below.</string>
<string name="delete_account">Delete Account</string>
<string name="delete_account_description">Deleted accounts are permanent and cannot be restored. Gems cannot be refunded. If you still want to delete, type your password below.</string>
<string name="delete_account_description">This will delete your account forever and it can never be restored! Banked or spent Gems will not be refunded. If youre absolutely certain, type your password into the text box below.</string>
<string name="delete_oauth_account_description">Deleted accounts are permanent and cannot be restored. Gems cannot be refunded. If you still want to delete, type DELETE below.</string>
<string name="reset_account_confirmation">Reset my Account</string>
<string name="delete_account_confirmation">Delete my Account</string>
@ -1225,4 +1228,5 @@
<string name="compact">Compact</string>
<string name="minimal">Minimal</string>
<string name="are_you_sure_you_want_to_delete">Are you sure you want to delete?</string>
</resources>

View file

@ -381,6 +381,15 @@
<item name="backgroundTint">@color/dialog_background</item>
</style>
<style name="HabiticaAccountDialogTheme" parent="AppTheme">
<item name="android:windowNoTitle">true</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowIsFloating">false</item>
<item name="android:statusBarColor">@color/gray_2_alpha</item>
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
<item name="android:navigationBarColor">@color/white</item>
</style>
<style name="NegativeButtonStyle" parent="Widget.MaterialComponents.Button.TextButton.Dialog">
<item name="android:textColor">@color/text_red</item>
@ -731,6 +740,18 @@
<item name="android:textColor">@color/brand_300</item>
</style>
<style name="DialogHabiticaAccountInputLayout" parent="TextAppearance.AppCompat">
<item name="android:textColorHint">@color/white</item>
<item name="colorAccent">@color/white</item>
<item name="colorControlNormal">@color/white</item>
<item name="colorControlActivated">@color/white</item>
<item name="boxStrokeColor">@color/text_input_account_stroke</item>
</style>
<style name="DialogHabiticaAccountHintLabel" parent="TextAppearance.Design.Hint">
<item name="android:textSize">10sp</item>
</style>
<style name="TaskFormTextInputLayoutAppearance" parent="Widget.MaterialComponents.TextInputLayout.FilledBox">
<!-- reference our hint & error styles -->
<item name="boxBackgroundColor">@color/white</item>

View file

@ -7,10 +7,8 @@ import android.content.ClipboardManager
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.text.InputType
import android.view.View
import android.widget.EditText
import android.widget.LinearLayout
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getSystemService
@ -28,6 +26,7 @@ import com.habitrpg.android.habitica.extensions.layoutInflater
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.FixCharacterValuesActivity
import com.habitrpg.android.habitica.ui.fragments.preferences.HabiticaAccountDialog.AccountUpdateConfirmed
import com.habitrpg.android.habitica.ui.helpers.KeyboardUtil
import com.habitrpg.android.habitica.ui.viewmodels.AuthenticationViewModel
import com.habitrpg.android.habitica.ui.views.ExtraLabelPreference
@ -40,13 +39,15 @@ import javax.inject.Inject
class AccountPreferenceFragment :
BasePreferencesFragment(),
SharedPreferences.OnSharedPreferenceChangeListener {
SharedPreferences.OnSharedPreferenceChangeListener,
AccountUpdateConfirmed {
@Inject
lateinit var hostConfig: HostConfig
@Inject
lateinit var apiClient: ApiClient
private lateinit var viewModel: AuthenticationViewModel
private lateinit var accountDialog: HabiticaAccountDialog
override var user: User? = null
set(value) {
@ -410,28 +411,11 @@ class AccountPreferenceFragment :
}
private fun showAccountDeleteConfirmation() {
val view = context?.layoutInflater?.inflate(R.layout.dialog_edittext, null)
var deleteMessage = getString(R.string.delete_account_description)
val editText = view?.findViewById<EditText>(R.id.editText)
if (user?.authentication?.localAuthentication?.email != null) {
editText?.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
} else {
deleteMessage = getString(R.string.delete_oauth_account_description)
editText?.inputType = InputType.TYPE_CLASS_TEXT
}
view?.findViewById<TextInputLayout>(R.id.input_layout)?.hint = context?.getString(R.string.confirm_deletion)
context?.let { context ->
val dialog = HabiticaAlertDialog(context)
dialog.setTitle(R.string.delete_account)
dialog.setMessage(deleteMessage)
dialog.addCancelButton()
dialog.addButton(R.string.delete, isPrimary = true, isDestructive = true) { _, _ ->
deleteAccount(editText?.text?.toString() ?: "")
}
dialog.setAdditionalContentView(view)
dialog.setAdditionalContentSidePadding(12.dpToPx(context))
dialog.buttonAxis = LinearLayout.HORIZONTAL
dialog.show()
val habiticaAccountDialog = context?.let { HabiticaAccountDialog(it, "delete_account", this) }
habiticaAccountDialog?.show(parentFragmentManager, "account")
if (habiticaAccountDialog != null) {
accountDialog = habiticaAccountDialog
}
}
@ -440,6 +424,7 @@ class AccountPreferenceFragment :
compositeSubscription.add(
userRepository.deleteAccount(password).subscribe({ _ ->
dialog?.dismiss()
accountDialog.dismiss()
context?.let { HabiticaBaseApplication.logout(it) }
activity?.finish()
}) { throwable ->
@ -450,17 +435,13 @@ class AccountPreferenceFragment :
}
private fun showAccountResetConfirmation() {
val context = context ?: return
val habiticaAccountDialog = context?.let { HabiticaAccountDialog(it, "reset_account", this) }
habiticaAccountDialog?.show(parentFragmentManager, "account")
val dialog = HabiticaAlertDialog(context)
dialog.setTitle(R.string.reset_account)
dialog.setMessage(R.string.reset_account_description)
dialog.addButton(R.string.reset_account_confirmation, true, true) { _, _ ->
resetAccount()
if (habiticaAccountDialog != null) {
accountDialog = habiticaAccountDialog
}
dialog.addCancelButton()
dialog.setAdditionalContentSidePadding(12.dpToPx(context))
dialog.show()
}
private fun showConfirmUsernameDialog() {
@ -479,7 +460,10 @@ class AccountPreferenceFragment :
private fun resetAccount() {
val dialog = HabiticaProgressDialog.show(context, R.string.resetting_account)
compositeSubscription.add(
userRepository.resetAccount().subscribe({ dialog?.dismiss() }) { throwable ->
userRepository.resetAccount().subscribe({
dialog?.dismiss()
accountDialog.dismiss()
}) { throwable ->
dialog?.dismiss()
RxErrorHandler.reportError(throwable)
}
@ -497,4 +481,13 @@ class AccountPreferenceFragment :
override fun onSharedPreferenceChanged(p0: SharedPreferences?, p1: String?) {
}
override fun resetConfirmedClicked() {
resetAccount()
}
override fun deletionConfirmClicked(confirmationString: String) {
deleteAccount(confirmationString)
}
}

View file

@ -0,0 +1,137 @@
package com.habitrpg.android.habitica.ui.fragments.preferences
import android.content.Context
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.fragment.app.DialogFragment
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.habitrpg.android.habitica.R
class HabiticaAccountDialog(private var thisContext: Context, private val accountAction: String, val accountUpdateConfirmed: AccountUpdateConfirmed) : DialogFragment() {
private lateinit var mainView: View
private var backBtn: ImageButton? = null
private var title: TextView? = null
private var warningDescription: TextView? = null
private var confirmationTextInputLayout: TextInputLayout? = null
private var confirmationText: TextInputEditText? = null
private var confirmationAction: TextView? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
mainView = inflater.inflate(R.layout.dialog_habitica_account, container, false)
return mainView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initViews()
when (accountAction) {
"reset_account" -> setResetAccountViews()
"delete_account" -> setDeleteAccountViews()
}
}
private fun setResetAccountViews() {
title?.setText(R.string.reset_account_title)
warningDescription?.setText(R.string.reset_account_description)
confirmationTextInputLayout?.setHint(R.string.confirm_reset)
confirmationAction?.setText(R.string.reset_account)
confirmationText?.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
confirmationAction?.setTextColor(ContextCompat.getColor(thisContext, R.color.gray_10))
confirmationAction?.alpha = .4f
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
if (confirmationText?.text.toString() == context?.getString(R.string.reset_caps)) {
confirmationAction?.setTextColor(ContextCompat.getColor(thisContext, R.color.red_100))
confirmationAction?.alpha = 1.0f
} else {
confirmationAction?.setTextColor(ContextCompat.getColor(thisContext, R.color.gray_10))
confirmationAction?.alpha = .4f
}
}
override fun afterTextChanged(p0: Editable?) {
}
})
confirmationAction?.setOnClickListener {
if (confirmationText?.text.toString() == context?.getString(R.string.reset_caps)) {
accountUpdateConfirmed.resetConfirmedClicked()
}
}
}
private fun setDeleteAccountViews() {
title?.setText(R.string.are_you_sure_you_want_to_delete)
warningDescription?.setText(R.string.delete_account_description)
confirmationTextInputLayout?.setHint(R.string.password)
confirmationAction?.setText(R.string.delete_account)
confirmationText?.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
confirmationAction?.setTextColor(ContextCompat.getColor(thisContext, R.color.gray_10))
confirmationAction?.alpha = .4f
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
if (confirmationText?.length() != null) {
if (confirmationText?.length()!! > 7) {
confirmationAction?.setTextColor(ContextCompat.getColor(thisContext, R.color.red_100))
confirmationAction?.alpha = 1.0f
} else {
confirmationAction?.setTextColor(ContextCompat.getColor(thisContext, R.color.gray_10))
confirmationAction?.alpha = .4f
}
}
}
override fun afterTextChanged(p0: Editable?) {
}
})
confirmationAction?.setOnClickListener {
if (confirmationText?.length() != null) {
if (confirmationText?.length()!! > 7) {
accountUpdateConfirmed.deletionConfirmClicked(confirmationText?.text.toString())
}
}
}
}
private fun initViews() {
backBtn = mainView.findViewById(R.id.back_imagebutton)
backBtn?.setOnClickListener { this.dismiss() }
title = mainView.findViewById(R.id.title_textview)
warningDescription = mainView.findViewById(R.id.warning_description_textview)
confirmationTextInputLayout = mainView.findViewById(R.id.confirmation_text_input_layout)
confirmationText = mainView.findViewById(R.id.confirmation_input_edittext)
confirmationAction = mainView.findViewById(R.id.confirm_action_textview)
}
override fun getTheme(): Int {
return R.style.HabiticaAccountDialogTheme
}
interface AccountUpdateConfirmed {
//API currently does not take password to reset account
fun resetConfirmedClicked()
fun deletionConfirmClicked(confirmationString: String)
}
}