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 <avently@local>
This commit is contained in:
Stanislav Dmitrenko 2024-01-19 22:52:13 +07:00 committed by GitHub
parent 0dd1f13d3b
commit ab9a6dcab5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 72 additions and 60 deletions

View File

@ -139,7 +139,7 @@ final class ChatModel: ObservableObject {
} }
func removeUser(_ user: User) { func removeUser(_ user: User) {
if let i = getUserIndex(user), users[i].user.userId != currentUser?.userId { if let i = getUserIndex(user) {
users.remove(at: i) users.remove(at: i)
} }
} }

View File

@ -1339,8 +1339,12 @@ private func changeActiveUser_(_ userId: Int64, viewPwd: String?) throws {
try getUserChatData() try getUserChatData()
} }
func changeActiveUserAsync_(_ userId: Int64, viewPwd: String?) async throws { func changeActiveUserAsync_(_ userId: Int64?, viewPwd: String?) async throws {
let currentUser = try await apiSetActiveUserAsync(userId, viewPwd: viewPwd) let currentUser = if let userId = userId {
try await apiSetActiveUserAsync(userId, viewPwd: viewPwd)
} else {
try apiGetActiveUser()
}
let users = try await listUsersAsync() let users = try await listUsersAsync()
await MainActor.run { await MainActor.run {
let m = ChatModel.shared let m = ChatModel.shared
@ -1349,7 +1353,7 @@ func changeActiveUserAsync_(_ userId: Int64, viewPwd: String?) async throws {
} }
try await getUserChatDataAsync() try await getUserChatDataAsync()
await MainActor.run { 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 invitation.user = currentUser
activateCall(invitation) activateCall(invitation)
} }
@ -1365,15 +1369,22 @@ func getUserChatData() throws {
} }
private func getUserChatDataAsync() async throws { private func getUserChatDataAsync() async throws {
let m = ChatModel.shared
if m.currentUser != nil {
let userAddress = try await apiGetUserAddressAsync() let userAddress = try await apiGetUserAddressAsync()
let chatItemTTL = try await getChatItemTTLAsync() let chatItemTTL = try await getChatItemTTLAsync()
let chats = try await apiGetChatsAsync() let chats = try await apiGetChatsAsync()
await MainActor.run { await MainActor.run {
let m = ChatModel.shared
m.userAddress = userAddress m.userAddress = userAddress
m.chatItemTTL = chatItemTTL m.chatItemTTL = chatItemTTL
m.chats = chats.map { Chat.init($0) } m.chats = chats.map { Chat.init($0) }
} }
} else {
await MainActor.run {
m.userAddress = nil
m.chats = []
}
}
} }
class ChatReceiver { class ChatReceiver {

View File

@ -159,10 +159,11 @@ struct SettingsView: View {
} }
@ViewBuilder func settingsView() -> some View { @ViewBuilder func settingsView() -> some View {
let user: User = chatModel.currentUser! let user = chatModel.currentUser
NavigationView { NavigationView {
List { List {
Section("You") { Section("You") {
if let user = user {
NavigationLink { NavigationLink {
UserProfile() UserProfile()
.navigationTitle("Your current profile") .navigationTitle("Your current profile")
@ -170,15 +171,18 @@ struct SettingsView: View {
ProfilePreview(profileOf: user) ProfilePreview(profileOf: user)
.padding(.leading, -8) .padding(.leading, -8)
} }
}
NavigationLink { NavigationLink {
UserProfilesView() UserProfilesView(showSettings: $showSettings)
} label: { } label: {
settingsRow("person.crop.rectangle.stack") { Text("Your chat profiles") } settingsRow("person.crop.rectangle.stack") { Text("Your chat profiles") }
} }
if let user = user {
NavigationLink { NavigationLink {
UserAddressView(shareViaProfile: chatModel.currentUser!.addressShared) UserAddressView(shareViaProfile: user.addressShared)
.navigationTitle("SimpleX address") .navigationTitle("SimpleX address")
.navigationBarTitleDisplayMode(.large) .navigationBarTitleDisplayMode(.large)
} label: { } label: {
@ -191,6 +195,7 @@ struct SettingsView: View {
} label: { } label: {
settingsRow("switch.2") { Text("Chat preferences") } settingsRow("switch.2") { Text("Chat preferences") }
} }
}
NavigationLink { NavigationLink {
ConnectDesktopView(viaSettings: true) ConnectDesktopView(viaSettings: true)
@ -250,6 +255,7 @@ struct SettingsView: View {
} }
Section("Help") { Section("Help") {
if let user = user {
NavigationLink { NavigationLink {
ChatHelp(showSettings: $showSettings) ChatHelp(showSettings: $showSettings)
.navigationTitle("Welcome \(user.displayName)!") .navigationTitle("Welcome \(user.displayName)!")
@ -257,6 +263,7 @@ struct SettingsView: View {
} label: { } label: {
settingsRow("questionmark") { Text("How to use it") } settingsRow("questionmark") { Text("How to use it") }
} }
}
NavigationLink { NavigationLink {
WhatsNewView(viaSettings: true) WhatsNewView(viaSettings: true)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)

View File

@ -8,6 +8,7 @@ import SimpleXChat
struct UserProfilesView: View { struct UserProfilesView: View {
@EnvironmentObject private var m: ChatModel @EnvironmentObject private var m: ChatModel
@Binding var showSettings: Bool
@Environment(\.editMode) private var editMode @Environment(\.editMode) private var editMode
@AppStorage(DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE) private var showHiddenProfilesNotice = true @AppStorage(DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE) private var showHiddenProfilesNotice = true
@AppStorage(DEFAULT_SHOW_MUTE_PROFILE_ALERT) private var showMuteProfileAlert = true @AppStorage(DEFAULT_SHOW_MUTE_PROFILE_ALERT) private var showMuteProfileAlert = true
@ -25,7 +26,6 @@ struct UserProfilesView: View {
private enum UserProfilesAlert: Identifiable { private enum UserProfilesAlert: Identifiable {
case deleteUser(user: User, delSMPQueues: Bool) case deleteUser(user: User, delSMPQueues: Bool)
case cantDeleteLastUser
case hiddenProfilesNotice case hiddenProfilesNotice
case muteProfileAlert case muteProfileAlert
case activateUserError(error: String) case activateUserError(error: String)
@ -34,7 +34,6 @@ struct UserProfilesView: View {
var id: String { var id: String {
switch self { switch self {
case let .deleteUser(user, delSMPQueues): return "deleteUser \(user.userId) \(delSMPQueues)" case let .deleteUser(user, delSMPQueues): return "deleteUser \(user.userId) \(delSMPQueues)"
case .cantDeleteLastUser: return "cantDeleteLastUser"
case .hiddenProfilesNotice: return "hiddenProfilesNotice" case .hiddenProfilesNotice: return "hiddenProfilesNotice"
case .muteProfileAlert: return "muteProfileAlert" case .muteProfileAlert: return "muteProfileAlert"
case let .activateUserError(err): return "activateUserError \(err)" case let .activateUserError(err): return "activateUserError \(err)"
@ -78,7 +77,7 @@ struct UserProfilesView: View {
Section { Section {
let users = filteredUsers() let users = filteredUsers()
let v = ForEach(users) { u in let v = ForEach(users) { u in
userView(u.user, allowDelete: users.count > 1) userView(u.user)
} }
if #available(iOS 16, *) { if #available(iOS 16, *) {
v.onDelete { indexSet in v.onDelete { indexSet in
@ -146,13 +145,6 @@ struct UserProfilesView: View {
}, },
secondaryButton: .cancel() 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: case .hiddenProfilesNotice:
return Alert( return Alert(
title: Text("Make profile private!"), 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 }) { 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 changeActiveUserAsync_(newActive.user.userId, viewPwd: nil)
try await deleteUser() 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 { } else {
try await deleteUser() try await deleteUser()
} }
} catch let error { } catch let error {
logger.error("Error deleting user profile: \(error)")
let a = getErrorAlert(error, "Error deleting user profile") let a = getErrorAlert(error, "Error deleting user profile")
alert = .error(title: a.title, error: a.message) 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 { let v = Button {
Task { Task {
do { do {
@ -323,9 +325,7 @@ struct UserProfilesView: View {
} }
} }
} }
.disabled(user.activeUser)
.foregroundColor(.primary) .foregroundColor(.primary)
.deleteDisabled(!allowDelete)
.swipeActions(edge: .leading, allowsFullSwipe: true) { .swipeActions(edge: .leading, allowsFullSwipe: true) {
if user.hidden { if user.hidden {
Button("Unhide") { Button("Unhide") {
@ -361,8 +361,6 @@ struct UserProfilesView: View {
} }
if #available(iOS 16, *) { if #available(iOS 16, *) {
v v
} else if !allowDelete {
v
} else { } else {
v.swipeActions(edge: .trailing, allowsFullSwipe: true) { v.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button("Delete", role: .destructive) { Button("Delete", role: .destructive) {
@ -373,12 +371,8 @@ struct UserProfilesView: View {
} }
private func confirmDeleteUser(_ user: User) { private func confirmDeleteUser(_ user: User) {
if m.users.count > 1 && (user.hidden || visibleUsersCount > 1) {
showDeleteConfirmation = true showDeleteConfirmation = true
userToDelete = user userToDelete = user
} else {
alert = .cantDeleteLastUser
}
} }
private func setUserPrivacy(_ user: User, successAlert: UserProfilesAlert? = nil, _ api: @escaping () async throws -> 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 { struct UserProfilesView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
UserProfilesView() UserProfilesView(showSettings: Binding.constant(true))
} }
} }