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:
parent
0dd1f13d3b
commit
ab9a6dcab5
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,14 +1369,21 @@ func getUserChatData() throws {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func getUserChatDataAsync() async throws {
|
private func getUserChatDataAsync() async throws {
|
||||||
let userAddress = try await apiGetUserAddressAsync()
|
let m = ChatModel.shared
|
||||||
let chatItemTTL = try await getChatItemTTLAsync()
|
if m.currentUser != nil {
|
||||||
let chats = try await apiGetChatsAsync()
|
let userAddress = try await apiGetUserAddressAsync()
|
||||||
await MainActor.run {
|
let chatItemTTL = try await getChatItemTTLAsync()
|
||||||
let m = ChatModel.shared
|
let chats = try await apiGetChatsAsync()
|
||||||
m.userAddress = userAddress
|
await MainActor.run {
|
||||||
m.chatItemTTL = chatItemTTL
|
m.userAddress = userAddress
|
||||||
m.chats = chats.map { Chat.init($0) }
|
m.chatItemTTL = chatItemTTL
|
||||||
|
m.chats = chats.map { Chat.init($0) }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await MainActor.run {
|
||||||
|
m.userAddress = nil
|
||||||
|
m.chats = []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,37 +159,42 @@ 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") {
|
||||||
NavigationLink {
|
if let user = user {
|
||||||
UserProfile()
|
NavigationLink {
|
||||||
.navigationTitle("Your current profile")
|
UserProfile()
|
||||||
} label: {
|
.navigationTitle("Your current profile")
|
||||||
ProfilePreview(profileOf: user)
|
} label: {
|
||||||
.padding(.leading, -8)
|
ProfilePreview(profileOf: user)
|
||||||
|
.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") }
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationLink {
|
|
||||||
UserAddressView(shareViaProfile: chatModel.currentUser!.addressShared)
|
|
||||||
.navigationTitle("SimpleX address")
|
|
||||||
.navigationBarTitleDisplayMode(.large)
|
|
||||||
} label: {
|
|
||||||
settingsRow("qrcode") { Text("Your SimpleX address") }
|
|
||||||
}
|
|
||||||
|
|
||||||
NavigationLink {
|
if let user = user {
|
||||||
PreferencesView(profile: user.profile, preferences: user.fullPreferences, currentPreferences: user.fullPreferences)
|
NavigationLink {
|
||||||
.navigationTitle("Your preferences")
|
UserAddressView(shareViaProfile: user.addressShared)
|
||||||
} label: {
|
.navigationTitle("SimpleX address")
|
||||||
settingsRow("switch.2") { Text("Chat preferences") }
|
.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 {
|
NavigationLink {
|
||||||
@ -250,12 +255,14 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Section("Help") {
|
Section("Help") {
|
||||||
NavigationLink {
|
if let user = user {
|
||||||
ChatHelp(showSettings: $showSettings)
|
NavigationLink {
|
||||||
.navigationTitle("Welcome \(user.displayName)!")
|
ChatHelp(showSettings: $showSettings)
|
||||||
.frame(maxHeight: .infinity, alignment: .top)
|
.navigationTitle("Welcome \(user.displayName)!")
|
||||||
} label: {
|
.frame(maxHeight: .infinity, alignment: .top)
|
||||||
settingsRow("questionmark") { Text("How to use it") }
|
} label: {
|
||||||
|
settingsRow("questionmark") { Text("How to use it") }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
WhatsNewView(viaSettings: true)
|
WhatsNewView(viaSettings: true)
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user