ios: confirm password when deleting active hidden user (#2095)
This commit is contained in:
committed by
GitHub
parent
08dd321311
commit
ade7bba97b
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) } }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user