From ab9a6dcab5512260d15ed03cf4b851f844b1de9d Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 19 Jan 2024 22:52:13 +0700 Subject: [PATCH] ios: allow to delete the last profile (#3707) * ios: allow to delete the last profile * less changes * no flash of empty view --------- Co-authored-by: Avently --- apps/ios/Shared/Model/ChatModel.swift | 2 +- apps/ios/Shared/Model/SimpleXAPI.swift | 33 +++++++---- .../Views/UserSettings/SettingsView.swift | 59 +++++++++++-------- .../Views/UserSettings/UserProfilesView.swift | 38 +++++------- 4 files changed, 72 insertions(+), 60 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 6a15a3965..c31ad579a 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -139,7 +139,7 @@ final class ChatModel: ObservableObject { } func removeUser(_ user: User) { - if let i = getUserIndex(user), users[i].user.userId != currentUser?.userId { + if let i = getUserIndex(user) { users.remove(at: i) } } diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index c1d0a264b..e4d2a8318 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1339,8 +1339,12 @@ private func changeActiveUser_(_ userId: Int64, viewPwd: String?) throws { try getUserChatData() } -func changeActiveUserAsync_(_ userId: Int64, viewPwd: String?) async throws { - let currentUser = try await apiSetActiveUserAsync(userId, viewPwd: viewPwd) +func changeActiveUserAsync_(_ userId: Int64?, viewPwd: String?) async throws { + let currentUser = if let userId = userId { + try await apiSetActiveUserAsync(userId, viewPwd: viewPwd) + } else { + try apiGetActiveUser() + } let users = try await listUsersAsync() await MainActor.run { let m = ChatModel.shared @@ -1349,7 +1353,7 @@ func changeActiveUserAsync_(_ userId: Int64, viewPwd: String?) async throws { } try await getUserChatDataAsync() await MainActor.run { - if var (_, invitation) = ChatModel.shared.callInvitations.first(where: { _, inv in inv.user.userId == userId }) { + if let currentUser = currentUser, var (_, invitation) = ChatModel.shared.callInvitations.first(where: { _, inv in inv.user.userId == userId }) { invitation.user = currentUser activateCall(invitation) } @@ -1365,14 +1369,21 @@ func getUserChatData() throws { } private func getUserChatDataAsync() async throws { - let userAddress = try await apiGetUserAddressAsync() - let chatItemTTL = try await getChatItemTTLAsync() - let chats = try await apiGetChatsAsync() - await MainActor.run { - let m = ChatModel.shared - m.userAddress = userAddress - m.chatItemTTL = chatItemTTL - m.chats = chats.map { Chat.init($0) } + let m = ChatModel.shared + if m.currentUser != nil { + let userAddress = try await apiGetUserAddressAsync() + let chatItemTTL = try await getChatItemTTLAsync() + let chats = try await apiGetChatsAsync() + await MainActor.run { + m.userAddress = userAddress + m.chatItemTTL = chatItemTTL + m.chats = chats.map { Chat.init($0) } + } + } else { + await MainActor.run { + m.userAddress = nil + m.chats = [] + } } } diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index b73d6b867..a691e6afc 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -159,37 +159,42 @@ struct SettingsView: View { } @ViewBuilder func settingsView() -> some View { - let user: User = chatModel.currentUser! + let user = chatModel.currentUser NavigationView { List { Section("You") { - NavigationLink { - UserProfile() - .navigationTitle("Your current profile") - } label: { - ProfilePreview(profileOf: user) - .padding(.leading, -8) + if let user = user { + NavigationLink { + UserProfile() + .navigationTitle("Your current profile") + } label: { + ProfilePreview(profileOf: user) + .padding(.leading, -8) + } } NavigationLink { - UserProfilesView() + UserProfilesView(showSettings: $showSettings) } label: { settingsRow("person.crop.rectangle.stack") { Text("Your chat profiles") } } - NavigationLink { - UserAddressView(shareViaProfile: chatModel.currentUser!.addressShared) - .navigationTitle("SimpleX address") - .navigationBarTitleDisplayMode(.large) - } label: { - settingsRow("qrcode") { Text("Your SimpleX address") } - } - NavigationLink { - PreferencesView(profile: user.profile, preferences: user.fullPreferences, currentPreferences: user.fullPreferences) - .navigationTitle("Your preferences") - } label: { - settingsRow("switch.2") { Text("Chat preferences") } + if let user = user { + NavigationLink { + UserAddressView(shareViaProfile: user.addressShared) + .navigationTitle("SimpleX address") + .navigationBarTitleDisplayMode(.large) + } label: { + settingsRow("qrcode") { Text("Your SimpleX address") } + } + + NavigationLink { + PreferencesView(profile: user.profile, preferences: user.fullPreferences, currentPreferences: user.fullPreferences) + .navigationTitle("Your preferences") + } label: { + settingsRow("switch.2") { Text("Chat preferences") } + } } NavigationLink { @@ -250,12 +255,14 @@ struct SettingsView: View { } Section("Help") { - NavigationLink { - ChatHelp(showSettings: $showSettings) - .navigationTitle("Welcome \(user.displayName)!") - .frame(maxHeight: .infinity, alignment: .top) - } label: { - settingsRow("questionmark") { Text("How to use it") } + if let user = user { + NavigationLink { + ChatHelp(showSettings: $showSettings) + .navigationTitle("Welcome \(user.displayName)!") + .frame(maxHeight: .infinity, alignment: .top) + } label: { + settingsRow("questionmark") { Text("How to use it") } + } } NavigationLink { WhatsNewView(viaSettings: true) diff --git a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift index f6c7bf37e..f2cac59da 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift @@ -8,6 +8,7 @@ import SimpleXChat struct UserProfilesView: View { @EnvironmentObject private var m: ChatModel + @Binding var showSettings: Bool @Environment(\.editMode) private var editMode @AppStorage(DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE) private var showHiddenProfilesNotice = true @AppStorage(DEFAULT_SHOW_MUTE_PROFILE_ALERT) private var showMuteProfileAlert = true @@ -25,7 +26,6 @@ struct UserProfilesView: View { private enum UserProfilesAlert: Identifiable { case deleteUser(user: User, delSMPQueues: Bool) - case cantDeleteLastUser case hiddenProfilesNotice case muteProfileAlert case activateUserError(error: String) @@ -34,7 +34,6 @@ struct UserProfilesView: View { var id: String { switch self { case let .deleteUser(user, delSMPQueues): return "deleteUser \(user.userId) \(delSMPQueues)" - case .cantDeleteLastUser: return "cantDeleteLastUser" case .hiddenProfilesNotice: return "hiddenProfilesNotice" case .muteProfileAlert: return "muteProfileAlert" case let .activateUserError(err): return "activateUserError \(err)" @@ -78,7 +77,7 @@ struct UserProfilesView: View { Section { let users = filteredUsers() let v = ForEach(users) { u in - userView(u.user, allowDelete: users.count > 1) + userView(u.user) } if #available(iOS 16, *) { v.onDelete { indexSet in @@ -146,13 +145,6 @@ struct UserProfilesView: View { }, secondaryButton: .cancel() ) - case .cantDeleteLastUser: - return Alert( - title: Text("Can't delete user profile!"), - message: m.users.count > 1 - ? Text("There should be at least one visible user profile.") - : Text("There should be at least one user profile.") - ) case .hiddenProfilesNotice: return Alert( title: Text("Make profile private!"), @@ -280,11 +272,21 @@ struct UserProfilesView: View { 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() + } else { + // Deleting the last visible user while having hidden one(s) + try await deleteUser() + try await changeActiveUserAsync_(nil, viewPwd: nil) + await MainActor.run { + onboardingStageDefault.set(.step1_SimpleXInfo) + m.onboardingStage = .step1_SimpleXInfo + showSettings = false + } } } else { try await deleteUser() } } catch let error { + logger.error("Error deleting user profile: \(error)") let a = getErrorAlert(error, "Error deleting user profile") alert = .error(title: a.title, error: a.message) } @@ -295,7 +297,7 @@ struct UserProfilesView: View { } } - @ViewBuilder private func userView(_ user: User, allowDelete: Bool) -> some View { + @ViewBuilder private func userView(_ user: User) -> some View { let v = Button { Task { do { @@ -323,9 +325,7 @@ struct UserProfilesView: View { } } } - .disabled(user.activeUser) .foregroundColor(.primary) - .deleteDisabled(!allowDelete) .swipeActions(edge: .leading, allowsFullSwipe: true) { if user.hidden { Button("Unhide") { @@ -361,8 +361,6 @@ struct UserProfilesView: View { } if #available(iOS 16, *) { v - } else if !allowDelete { - v } else { v.swipeActions(edge: .trailing, allowsFullSwipe: true) { Button("Delete", role: .destructive) { @@ -373,12 +371,8 @@ struct UserProfilesView: View { } private func confirmDeleteUser(_ user: User) { - if m.users.count > 1 && (user.hidden || visibleUsersCount > 1) { - showDeleteConfirmation = true - userToDelete = user - } else { - alert = .cantDeleteLastUser - } + showDeleteConfirmation = true + userToDelete = user } private func setUserPrivacy(_ user: User, successAlert: UserProfilesAlert? = nil, _ api: @escaping () async throws -> User) { @@ -409,6 +403,6 @@ public func chatPasswordHash(_ pwd: String, _ salt: String) -> String { struct UserProfilesView_Previews: PreviewProvider { static var previews: some View { - UserProfilesView() + UserProfilesView(showSettings: Binding.constant(true)) } }