Skip to content

Commit

Permalink
Improve account reset and delete flow
Browse files Browse the repository at this point in the history
  • Loading branch information
phillipthelen committed Jan 12, 2024
1 parent 6b69f52 commit 0fad2fd
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 82 deletions.
4 changes: 4 additions & 0 deletions HabitRPG/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2202,6 +2202,8 @@ public enum L10n {
public static var dayStartAdjustment: String { return L10n.tr("Mainstrings", "settings.day_start_adjustment") }
/// Delete Account
public static var deleteAccount: String { return L10n.tr("Mainstrings", "settings.delete_account") }
/// Are you sure you want to delete?
public static var deleteAccountConfirm: String { return L10n.tr("Mainstrings", "settings.delete_account_confirm") }
/// This will delete your account forever, and it can never be restored! Banked or spent Gems will not be refunded. If you’re absolutely certain, type your password into the text box below.
public static var deleteAccountDescription: String { return L10n.tr("Mainstrings", "settings.delete_account_description") }
/// This will delete your account forever, and it can never be restored! Banked or spent Gems will not be refunded. If you’re absolutely certain, type DELETE into the text box below.
Expand Down Expand Up @@ -2284,6 +2286,8 @@ public enum L10n {
public static var reminder: String { return L10n.tr("Mainstrings", "settings.reminder") }
/// Reset Account
public static var resetAccount: String { return L10n.tr("Mainstrings", "settings.reset_account") }
/// Are you sure you want to reset?
public static var resetAccountConfirm: String { return L10n.tr("Mainstrings", "settings.reset_account_confirm") }
/// 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, except Subscriber items and free commemorative items, 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, Achievements, and your Pets and Mounts. To confirm reset, type your password below.
public static var resetAccountDescription: String { return L10n.tr("Mainstrings", "settings.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, except Subscriber items and free commemorative items, 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, Achievements, and your Pets and Mounts. If you’re absolutely certain, type DELETE into the text box below.
Expand Down
2 changes: 2 additions & 0 deletions HabitRPG/Strings/Base.lproj/Mainstrings.strings
Original file line number Diff line number Diff line change
Expand Up @@ -309,12 +309,14 @@
"settings.danger_zone" = "Danger Zone";
"settings.login_methods" = "Login Methods";
"settings.reset_account" = "Reset Account";
"settings.reset_account_confirm" = "Are you sure you want to reset?";
"settings.updated_email" = "Email updated";
"settings.updated_password" = "Password updated";
"settings.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, except Subscriber items and free commemorative items, 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, Achievements, and your Pets and Mounts. To confirm reset, type your password below.";
"settings.reset_account_description_social" = "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, except Subscriber items and free commemorative items, 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, Achievements, and your Pets and Mounts. If you’re absolutely certain, type DELETE into the text box below.";

"settings.delete_account" = "Delete Account";
"settings.delete_account_confirm" = "Are you sure you want to delete?";
"settings.delete_account_description" = "This will delete your account forever, and it can never be restored! Banked or spent Gems will not be refunded. If you’re absolutely certain, type your password into the text box below.";
"settings.delete_account_description_social" = "This will delete your account forever, and it can never be restored! Banked or spent Gems will not be refunded. If you’re absolutely certain, type DELETE into the text box below.";
"settings.change_email" = "Change Email";
Expand Down
194 changes: 112 additions & 82 deletions HabitRPG/UI/Settings/AccountSettingsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import UIKit
import Eureka
import ReactiveSwift
import Habitica_Models
import SwiftUI

// swiftlint:disable:next type_body_length
class AccountSettingsViewController: FormViewController, Themeable, UITextFieldDelegate {
Expand Down Expand Up @@ -326,49 +327,16 @@ class AccountSettingsViewController: FormViewController, Themeable, UITextFieldD
}

private func showDeleteAccountAlert() {
let alertController = HabiticaAlertController(title: L10n.Settings.deleteAccount)

let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 8
let textView = UITextView()
if user?.authentication?.local?.email != nil {
textView.text = L10n.Settings.deleteAccountDescription
} else {
textView.text = L10n.Settings.deleteAccountDescriptionSocial
}
textView.font = UIFont.preferredFont(forTextStyle: .subheadline)
textView.textColor = ThemeService.shared.theme.ternaryTextColor
textView.isEditable = false
textView.isScrollEnabled = true
textView.isSelectable = false
textView.textAlignment = .center
textView.addHeightConstraint(height: 150)
textView.backgroundColor = ThemeService.shared.theme.contentBackgroundColor
stackView.addArrangedSubview(textView)
let textField = PaddedTextField()
if user?.authentication?.local?.email != nil {
textField.attributedPlaceholder = NSAttributedString(string: L10n.password, attributes: [.foregroundColor: ThemeService.shared.theme.dimmedTextColor])
}
configureTextField(textField)
textField.isSecureTextEntry = true
textField.returnKeyType = .done
textField.delegate = self
textField.addHeightConstraint(height: 44)
stackView.addArrangedSubview(textField)
alertController.contentView = stackView

alertController.buttonAxis = .horizontal
alertController.addCancelAction()
alertController.addAction(title: L10n.Settings.deleteAccount, style: .destructive, isMainAction: true) {[weak self] _ in
if let password = textField.text {
self?.deleteAccount(password: password)
}
}
alertController.onKeyboardChange = { isVisible in
textView.isHidden = isVisible
}
alertController.show()
let navController = UINavigationController()
let controller = UIHostingController(rootView: DeleteAccountView(dismisser: {
navController.dismiss()
}, onDelete: {[weak self] password in
navController.dismiss()
self?.deleteAccount(password: password)
}, isSocial: user?.authentication?.local?.email == nil))
controller.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(dismissController))
navController.setViewControllers([controller], animated: false)
present(navController, animated: true)
}

private func deleteAccount(password: String) {
Expand All @@ -385,45 +353,21 @@ class AccountSettingsViewController: FormViewController, Themeable, UITextFieldD
}

private func showResetAccountAlert() {
let alertController = HabiticaAlertController(title: L10n.Settings.resetAccount)

let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 8
let textView = UITextView()
if user?.authentication?.local?.email != nil {
textView.text = L10n.Settings.resetAccountDescription
} else {
textView.text = L10n.Settings.resetAccountDescriptionSocial
}
textView.font = UIFont.preferredFont(forTextStyle: .subheadline)
textView.textColor = ThemeService.shared.theme.ternaryTextColor
textView.isEditable = false
textView.isScrollEnabled = true
textView.isSelectable = false
textView.textAlignment = .center
textView.addHeightConstraint(height: 150)
textView.backgroundColor = ThemeService.shared.theme.contentBackgroundColor
stackView.addArrangedSubview(textView)
let textField = PaddedTextField()
if user?.authentication?.local?.email != nil {
textField.attributedPlaceholder = NSAttributedString(string: L10n.password, attributes: [.foregroundColor: ThemeService.shared.theme.dimmedTextColor])
}
configureTextField(textField)
textField.isSecureTextEntry = true
textField.returnKeyType = .done
textField.delegate = self
textField.addHeightConstraint(height: 44)
stackView.addArrangedSubview(textField)
alertController.contentView = stackView

alertController.addAction(title: L10n.Settings.resetAccount, style: .destructive, isMainAction: true) {[weak self] _ in
if let password = textField.text {
self?.userRepository.resetAccount(password: password).observeCompleted {}
}
}
alertController.addCancelAction()
alertController.show()
let navController = UINavigationController()
let controller = UIHostingController(rootView: ResetAccountView(dismisser: {
navController.dismiss()
}, onReset: {[weak self] password in
navController.dismiss()
self?.userRepository.resetAccount(password: password).observeCompleted {}
}, isSocial: user?.authentication?.local?.email == nil))
controller.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(dismissController))
navController.setViewControllers([controller], animated: false)
present(navController, animated: true)
}

@objc
func dismissController(_ view: UIView) {
view.nearestNavigationController?.dismiss()
}

private func showEmailChangeAlert() {
Expand Down Expand Up @@ -550,3 +494,89 @@ class AccountSettingsViewController: FormViewController, Themeable, UITextFieldD
return true
}
}

struct ResetAccountView: View {
let dismisser: () -> Void
let onReset: (String) -> Void
let isSocial: Bool
@State var text: String = ""

private func isValidInput() -> Bool {
if isSocial {
return text == "RESET"
} else {
return !text.isEmpty
}
}

var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 12) {
Text(L10n.Settings.resetAccountConfirm).font(.headline)
if isSocial {
Text(L10n.Settings.resetAccountDescriptionSocial).font(.body).foregroundColor(Color(ThemeService.shared.theme.secondaryTextColor))
} else {
Text(L10n.Settings.resetAccountDescription).font(.body).foregroundColor(Color(ThemeService.shared.theme.secondaryTextColor))
}
if #available(iOS 15.0, *) {
TextField(text: $text, prompt: Text(L10n.password)) {

}
.padding(12)
.overlay(RoundedRectangle(cornerRadius: 8).stroke().foregroundColor(Color(ThemeService.shared.theme.tableviewSeparatorColor)))
} else {
TextField(text: $text) {

}
.padding(12)
.overlay(RoundedRectangle(cornerRadius: 8).stroke().foregroundColor(Color(ThemeService.shared.theme.tableviewSeparatorColor)))
}
HabiticaButtonUI(label: Text(L10n.Settings.resetAccount), color: Color(isValidInput() ? ThemeService.shared.theme.errorColor : ThemeService.shared.theme.dimmedColor)) {
onReset(text)
}
}.padding(16)
}
}
}

struct DeleteAccountView: View {
let dismisser: () -> Void
let onDelete: (String) -> Void
let isSocial: Bool
@State var text: String = ""

private func isValidInput() -> Bool {
if isSocial {
return text == "DELETE"
} else {
return !text.isEmpty
}
}

var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 12) {
Text(L10n.Settings.deleteAccountConfirm).font(.headline)
if isSocial {
Text(L10n.Settings.deleteAccountDescriptionSocial).font(.body).foregroundColor(Color(ThemeService.shared.theme.secondaryTextColor))
} else {
Text(L10n.Settings.deleteAccountDescription).font(.body).foregroundColor(Color(ThemeService.shared.theme.secondaryTextColor))
}
if #available(iOS 15.0, *) {
TextField(text: $text, prompt: Text(L10n.password)) {
}
.padding(12)
.overlay(RoundedRectangle(cornerRadius: 8).stroke().foregroundColor(Color(ThemeService.shared.theme.tableviewSeparatorColor)))
} else {
TextField(text: $text) {
}
.padding(12)
.overlay(RoundedRectangle(cornerRadius: 8).stroke().foregroundColor(Color(ThemeService.shared.theme.tableviewSeparatorColor)))
}
HabiticaButtonUI(label: Text(L10n.Settings.deleteAccount), color: Color(isValidInput() ? ThemeService.shared.theme.errorColor : ThemeService.shared.theme.dimmedColor)) {
onDelete(text)
}
}.padding(16)
}
}
}

0 comments on commit 0fad2fd

Please sign in to comment.