ios: confirm password when deleting active hidden user (#2095)

This commit is contained in:
Evgeny Poberezkin
2023-03-29 14:01:24 +01:00
committed by GitHub
parent 08dd321311
commit ade7bba97b
5 changed files with 87 additions and 25 deletions

View File

@@ -39,7 +39,7 @@ fun DeveloperView(
SettingsPreferenceItem(Icons.Outlined.Code, stringResource(R.string.show_developer_options), developerTools, devTools)
}
SectionTextFooter(
generalGetString(if (devTools.value) R.string.show_dev_options else R.string.hide_dev_options) +
generalGetString(if (devTools.value) R.string.show_dev_options else R.string.hide_dev_options) + " " +
generalGetString(R.string.developer_options)
)
SectionSpacer()

View File

@@ -511,8 +511,8 @@
<string name="core_version">Core version: v%s</string>
<string name="core_build_timestamp">Core built at: %s</string>
<string name="core_simplexmq_version">simplexmq: v%s (%2s)</string>
<string name="show_dev_options">Show:\ </string>
<string name="hide_dev_options">Hide:\ </string>
<string name="show_dev_options">Show:</string>
<string name="hide_dev_options">Hide:</string>
<string name="show_developer_options">Show developer options</string>
<string name="developer_options">Database IDs and Transport isolation option.</string>

View File

@@ -39,7 +39,7 @@ struct DeveloperView: View {
Toggle("Show developer options", isOn: $developerTools)
}
} footer: {
(developerTools ? Text("Show: ") : Text("Hide: ")) + Text("Database IDs and Transport isolation option.")
(developerTools ? Text("Show:") : Text("Hide:")) + Text(" ") + Text("Database IDs and Transport isolation option.")
}
Section {

View File

@@ -33,7 +33,7 @@ struct HiddenProfileView: View {
}
Section {
PassphraseField(key: $hidePassword, placeholder: "Password to show", valid: true, showStrength: true)
PassphraseField(key: $hidePassword, placeholder: "Password to show", valid: passwordValid, showStrength: true)
PassphraseField(key: $confirmHidePassword, placeholder: "Confirm password", valid: confirmValid)
settingsRow("lock") {
@@ -72,9 +72,11 @@ struct HiddenProfileView: View {
}
}
var passwordValid: Bool { hidePassword == hidePassword.trimmingCharacters(in: .whitespaces) }
var confirmValid: Bool { confirmHidePassword == "" || hidePassword == confirmHidePassword }
var saveDisabled: Bool { hidePassword == "" || confirmHidePassword == "" || !confirmValid }
var saveDisabled: Bool { hidePassword == "" || !passwordValid || confirmHidePassword == "" || !confirmValid }
}
struct ProfilePrivacyView_Previews: PreviewProvider {

View File

@@ -13,15 +13,17 @@ struct UserProfilesView: View {
@AppStorage(DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE) private var showHiddenProfilesNotice = true
@AppStorage(DEFAULT_SHOW_MUTE_PROFILE_ALERT) private var showMuteProfileAlert = true
@State private var showDeleteConfirmation = false
@State private var userToDelete: UserInfo?
@State private var userToDelete: User?
@State private var alert: UserProfilesAlert?
@State private var authorized = !UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA)
@State private var searchTextOrPassword = ""
@State private var selectedUser: User?
@State private var profileHidden = false
@State private var profileAction: UserProfileAction?
@State private var actionPassword = ""
private enum UserProfilesAlert: Identifiable {
case deleteUser(userInfo: UserInfo, delSMPQueues: Bool)
case deleteUser(user: User, delSMPQueues: Bool)
case cantDeleteLastUser
case hiddenProfilesNotice
case muteProfileAlert
@@ -30,7 +32,7 @@ struct UserProfilesView: View {
var id: String {
switch self {
case let .deleteUser(userInfo, delSMPQueues): return "deleteUser \(userInfo.user.userId) \(delSMPQueues)"
case let .deleteUser(user, delSMPQueues): return "deleteUser \(user.userId) \(delSMPQueues)"
case .cantDeleteLastUser: return "cantDeleteLastUser"
case .hiddenProfilesNotice: return "hiddenProfilesNotice"
case .muteProfileAlert: return "muteProfileAlert"
@@ -40,6 +42,16 @@ struct UserProfilesView: View {
}
}
private enum UserProfileAction: Identifiable {
case deleteUser(user: User, delSMPQueues: Bool)
var id: String {
switch self {
case let .deleteUser(user, delSMPQueues): return "deleteUser \(user.userId) \(delSMPQueues)"
}
}
}
var body: some View {
if authorized {
userProfilesView()
@@ -69,7 +81,7 @@ struct UserProfilesView: View {
if let i = indexSet.first {
if m.users.count > 1 && (m.users[i].user.hidden || visibleUsersCount > 1) {
showDeleteConfirmation = true
userToDelete = users[i]
userToDelete = users[i].user
} else {
alert = .cantDeleteLastUser
}
@@ -114,14 +126,17 @@ struct UserProfilesView: View {
withAnimation { profileHidden = false }
}
}
.sheet(item: $profileAction) { action in
profileActionView(action)
}
.alert(item: $alert) { alert in
switch alert {
case let .deleteUser(userInfo, delSMPQueues):
case let .deleteUser(user, delSMPQueues):
return Alert(
title: Text("Delete user profile?"),
message: Text("All chats and messages will be deleted - this cannot be undone!"),
primaryButton: .destructive(Text("Delete")) {
Task { await removeUser(userInfo, delSMPQueues) }
Task { await removeUser(user, delSMPQueues, viewPwd: userViewPassword(user)) }
},
secondaryButton: .cancel()
)
@@ -179,36 +194,81 @@ struct UserProfilesView: View {
m.users.filter({ u in !u.user.hidden }).count
}
private func userViewPassword(_ user: User) -> String? {
user.activeUser || !user.hidden ? nil : searchTextOrPassword
private func correctPassword(_ user: User, _ pwd: String) -> Bool {
if let ph = user.viewPwdHash {
return pwd != "" && chatPasswordHash(pwd, ph.salt) == ph.hash
}
return false
}
private func deleteModeButton(_ title: LocalizedStringKey, _ delSMPQueues: Bool) -> some View {
Button(title, role: .destructive) {
if let userInfo = userToDelete {
alert = .deleteUser(userInfo: userInfo, delSMPQueues: delSMPQueues)
private func userViewPassword(_ user: User) -> String? {
!user.hidden ? nil : searchTextOrPassword
}
@ViewBuilder private func profileActionView(_ action: UserProfileAction) -> some View {
let passwordValid = actionPassword == actionPassword.trimmingCharacters(in: .whitespaces)
switch action {
case let .deleteUser(user, delSMPQueues):
let actionEnabled = actionPassword != "" && passwordValid && correctPassword(user, actionPassword)
List {
Text("Delete user")
.font(.title)
.bold()
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
.listRowBackground(Color.clear)
Section() {
ProfilePreview(profileOf: user).padding(.leading, -8)
}
Section {
PassphraseField(key: $actionPassword, placeholder: "Profile password", valid: passwordValid)
settingsRow("trash") {
Button("Delete user", role: .destructive) {
profileAction = nil
Task { await removeUser(user, delSMPQueues, viewPwd: actionPassword) }
}
.disabled(!actionEnabled)
}
} footer: {
if actionEnabled {
Text("All chats and messages will be deleted - this cannot be undone!")
.font(.callout)
}
}
}
}
}
private func removeUser(_ userInfo: UserInfo, _ delSMPQueues: Bool) async {
private func deleteModeButton(_ title: LocalizedStringKey, _ delSMPQueues: Bool) -> some View {
Button(title, role: .destructive) {
if let user = userToDelete {
if user.hidden && user.activeUser && !correctPassword(user, searchTextOrPassword) {
profileAction = .deleteUser(user: user, delSMPQueues: delSMPQueues)
} else {
alert = .deleteUser(user: user, delSMPQueues: delSMPQueues)
}
}
}
}
private func removeUser(_ user: User, _ delSMPQueues: Bool, viewPwd: String?) async {
do {
let u = userInfo.user
if u.activeUser {
if user.activeUser {
if let newActive = m.users.first(where: { u in !u.user.activeUser && !u.user.hidden }) {
try await changeActiveUserAsync_(newActive.user.userId, viewPwd: nil)
try await deleteUser(u)
try await deleteUser()
}
} else {
try await deleteUser(u)
try await deleteUser()
}
} catch let error {
let a = getErrorAlert(error, "Error deleting user profile")
alert = .error(title: a.title, error: a.message)
}
func deleteUser(_ user: User) async throws {
try await apiDeleteUser(user.userId, delSMPQueues, viewPwd: userViewPassword(user))
func deleteUser() async throws {
try await apiDeleteUser(user.userId, delSMPQueues, viewPwd: viewPwd)
await MainActor.run { withAnimation { m.removeUser(user) } }
}
}