2023-01-17 17:47:37 +00:00
|
|
|
//
|
|
|
|
// Created by Avently on 17.01.2023.
|
|
|
|
// Copyright (c) 2023 SimpleX Chat. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import SwiftUI
|
|
|
|
import SimpleXChat
|
|
|
|
|
|
|
|
struct UserProfilesView: View {
|
|
|
|
@EnvironmentObject private var m: ChatModel
|
2024-07-03 22:42:13 +01:00
|
|
|
@EnvironmentObject private var theme: AppTheme
|
2023-01-17 17:47:37 +00:00
|
|
|
@Environment(\.editMode) private var editMode
|
2023-03-22 15:58:01 +00:00
|
|
|
@AppStorage(DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE) private var showHiddenProfilesNotice = true
|
|
|
|
@AppStorage(DEFAULT_SHOW_MUTE_PROFILE_ALERT) private var showMuteProfileAlert = true
|
2023-01-24 19:00:30 +00:00
|
|
|
@State private var showDeleteConfirmation = false
|
2023-03-29 14:01:24 +01:00
|
|
|
@State private var userToDelete: User?
|
2023-01-20 12:38:38 +00:00
|
|
|
@State private var alert: UserProfilesAlert?
|
2023-03-22 15:58:01 +00:00
|
|
|
@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
|
2023-03-29 14:01:24 +01:00
|
|
|
@State private var profileAction: UserProfileAction?
|
|
|
|
@State private var actionPassword = ""
|
2024-11-13 11:41:39 +00:00
|
|
|
@State private var navigateToProfileCreate = false
|
2023-01-20 12:38:38 +00:00
|
|
|
|
2023-04-04 21:53:25 +01:00
|
|
|
var trimmedSearchTextOrPassword: String { searchTextOrPassword.trimmingCharacters(in: .whitespaces)}
|
|
|
|
|
2023-01-20 12:38:38 +00:00
|
|
|
private enum UserProfilesAlert: Identifiable {
|
2023-03-29 14:01:24 +01:00
|
|
|
case deleteUser(user: User, delSMPQueues: Bool)
|
2023-03-22 15:58:01 +00:00
|
|
|
case hiddenProfilesNotice
|
|
|
|
case muteProfileAlert
|
2023-01-31 15:55:41 +00:00
|
|
|
case activateUserError(error: String)
|
2024-07-28 17:54:58 +01:00
|
|
|
case error(title: LocalizedStringKey, error: LocalizedStringKey?)
|
2023-01-20 12:38:38 +00:00
|
|
|
|
|
|
|
var id: String {
|
|
|
|
switch self {
|
2023-03-29 14:01:24 +01:00
|
|
|
case let .deleteUser(user, delSMPQueues): return "deleteUser \(user.userId) \(delSMPQueues)"
|
2023-03-22 15:58:01 +00:00
|
|
|
case .hiddenProfilesNotice: return "hiddenProfilesNotice"
|
|
|
|
case .muteProfileAlert: return "muteProfileAlert"
|
2023-01-31 15:55:41 +00:00
|
|
|
case let .activateUserError(err): return "activateUserError \(err)"
|
2023-01-20 12:38:38 +00:00
|
|
|
case let .error(title, _): return "error \(title)"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-01-17 17:47:37 +00:00
|
|
|
|
2023-03-29 14:01:24 +01:00
|
|
|
private enum UserProfileAction: Identifiable {
|
|
|
|
case deleteUser(user: User, delSMPQueues: Bool)
|
2023-03-29 19:28:06 +01:00
|
|
|
case unhideUser(user: User)
|
2023-03-29 14:01:24 +01:00
|
|
|
|
|
|
|
var id: String {
|
|
|
|
switch self {
|
|
|
|
case let .deleteUser(user, delSMPQueues): return "deleteUser \(user.userId) \(delSMPQueues)"
|
2023-03-29 19:28:06 +01:00
|
|
|
case let .unhideUser(user): return "unhideUser \(user.userId)"
|
2023-03-29 14:01:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-17 17:47:37 +00:00
|
|
|
var body: some View {
|
|
|
|
List {
|
2023-03-22 15:58:01 +00:00
|
|
|
if profileHidden {
|
|
|
|
Button {
|
|
|
|
withAnimation { profileHidden = false }
|
|
|
|
} label: {
|
|
|
|
Label("Enter password above to show!", systemImage: "lock.open")
|
|
|
|
}
|
|
|
|
}
|
2023-01-20 12:38:38 +00:00
|
|
|
Section {
|
2023-03-22 15:58:01 +00:00
|
|
|
let users = filteredUsers()
|
2023-04-14 17:51:55 +02:00
|
|
|
let v = ForEach(users) { u in
|
2024-11-13 11:41:39 +00:00
|
|
|
userView(u)
|
2023-01-17 17:47:37 +00:00
|
|
|
}
|
2023-04-14 17:51:55 +02:00
|
|
|
if #available(iOS 16, *) {
|
|
|
|
v.onDelete { indexSet in
|
|
|
|
if let i = indexSet.first {
|
2024-11-13 11:41:39 +00:00
|
|
|
withAuth {
|
|
|
|
confirmDeleteUser(users[i].user)
|
|
|
|
}
|
2023-03-22 15:58:01 +00:00
|
|
|
}
|
2023-01-20 12:38:38 +00:00
|
|
|
}
|
2023-04-14 17:51:55 +02:00
|
|
|
} else {
|
|
|
|
v
|
2023-01-17 17:47:37 +00:00
|
|
|
}
|
2023-01-20 12:38:38 +00:00
|
|
|
|
2023-04-04 21:53:25 +01:00
|
|
|
if trimmedSearchTextOrPassword == "" {
|
2024-11-13 11:41:39 +00:00
|
|
|
NavigationLink(
|
|
|
|
destination: CreateProfile(),
|
|
|
|
isActive: $navigateToProfileCreate
|
|
|
|
) {
|
2023-03-22 15:58:01 +00:00
|
|
|
Label("Add profile", systemImage: "plus")
|
2024-11-13 11:41:39 +00:00
|
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
|
|
.frame(height: 38)
|
|
|
|
.padding(.leading, 16).padding(.vertical, 8).padding(.trailing, 32)
|
|
|
|
.contentShape(Rectangle())
|
|
|
|
.onTapGesture {
|
|
|
|
withAuth {
|
|
|
|
self.navigateToProfileCreate = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.padding(.leading, -16).padding(.vertical, -8).padding(.trailing, -32)
|
2023-03-22 15:58:01 +00:00
|
|
|
}
|
2023-01-17 17:47:37 +00:00
|
|
|
}
|
2023-01-20 12:38:38 +00:00
|
|
|
} footer: {
|
2023-03-22 15:58:01 +00:00
|
|
|
Text("Tap to activate profile.")
|
2024-07-03 22:42:13 +01:00
|
|
|
.foregroundColor(theme.colors.secondary)
|
2023-03-22 15:58:01 +00:00
|
|
|
.font(.body)
|
|
|
|
.padding(.top, 8)
|
|
|
|
|
2023-01-17 17:47:37 +00:00
|
|
|
}
|
|
|
|
}
|
2023-04-14 17:51:55 +02:00
|
|
|
.toolbar {
|
|
|
|
if #available(iOS 16, *) {
|
|
|
|
EditButton()
|
|
|
|
}
|
|
|
|
}
|
2023-02-08 13:39:41 +03:00
|
|
|
.navigationTitle("Your chat profiles")
|
2024-07-03 22:42:13 +01:00
|
|
|
.modifier(ThemedBackground(grouped: true))
|
2023-03-22 15:58:01 +00:00
|
|
|
.searchable(text: $searchTextOrPassword, placement: .navigationBarDrawer(displayMode: .always))
|
|
|
|
.autocorrectionDisabled(true)
|
|
|
|
.textInputAutocapitalization(.never)
|
|
|
|
.onAppear {
|
|
|
|
if showHiddenProfilesNotice && m.users.count > 1 {
|
|
|
|
alert = .hiddenProfilesNotice
|
|
|
|
}
|
|
|
|
}
|
2023-01-24 19:00:30 +00:00
|
|
|
.confirmationDialog("Delete chat profile?", isPresented: $showDeleteConfirmation, titleVisibility: .visible) {
|
|
|
|
deleteModeButton("Profile and server connections", true)
|
|
|
|
deleteModeButton("Local profile data only", false)
|
|
|
|
}
|
2024-06-01 04:47:57 +07:00
|
|
|
.appSheet(item: $selectedUser) { user in
|
2023-03-22 15:58:01 +00:00
|
|
|
HiddenProfileView(user: user, profileHidden: $profileHidden)
|
|
|
|
}
|
|
|
|
.onChange(of: profileHidden) { _ in
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
|
|
|
|
withAnimation { profileHidden = false }
|
|
|
|
}
|
|
|
|
}
|
2024-06-01 04:47:57 +07:00
|
|
|
.appSheet(item: $profileAction) { action in
|
2023-03-29 14:01:24 +01:00
|
|
|
profileActionView(action)
|
|
|
|
}
|
2023-01-20 12:38:38 +00:00
|
|
|
.alert(item: $alert) { alert in
|
|
|
|
switch alert {
|
2023-03-29 14:01:24 +01:00
|
|
|
case let .deleteUser(user, delSMPQueues):
|
2023-01-20 12:38:38 +00:00
|
|
|
return Alert(
|
|
|
|
title: Text("Delete user profile?"),
|
|
|
|
message: Text("All chats and messages will be deleted - this cannot be undone!"),
|
|
|
|
primaryButton: .destructive(Text("Delete")) {
|
2023-03-29 14:01:24 +01:00
|
|
|
Task { await removeUser(user, delSMPQueues, viewPwd: userViewPassword(user)) }
|
2023-01-20 12:38:38 +00:00
|
|
|
},
|
|
|
|
secondaryButton: .cancel()
|
|
|
|
)
|
2023-03-22 15:58:01 +00:00
|
|
|
case .hiddenProfilesNotice:
|
|
|
|
return Alert(
|
|
|
|
title: Text("Make profile private!"),
|
2023-06-17 09:58:35 +01:00
|
|
|
message: Text("You can hide or mute a user profile - swipe it to the right."),
|
2023-03-22 15:58:01 +00:00
|
|
|
primaryButton: .default(Text("Don't show again")) {
|
|
|
|
showHiddenProfilesNotice = false
|
|
|
|
},
|
|
|
|
secondaryButton: .default(Text("Ok"))
|
|
|
|
)
|
|
|
|
case .muteProfileAlert:
|
|
|
|
return Alert(
|
|
|
|
title: Text("Muted when inactive!"),
|
|
|
|
message: Text("You will still receive calls and notifications from muted profiles when they are active."),
|
|
|
|
primaryButton: .default(Text("Don't show again")) {
|
|
|
|
showMuteProfileAlert = false
|
|
|
|
},
|
|
|
|
secondaryButton: .default(Text("Ok"))
|
|
|
|
)
|
2023-01-31 15:55:41 +00:00
|
|
|
case let .activateUserError(error: err):
|
|
|
|
return Alert(
|
|
|
|
title: Text("Error switching profile!"),
|
|
|
|
message: Text(err)
|
|
|
|
)
|
2023-01-20 12:38:38 +00:00
|
|
|
case let .error(title, error):
|
2024-07-28 17:54:58 +01:00
|
|
|
return mkAlert(title: title, message: error)
|
2023-01-20 12:38:38 +00:00
|
|
|
}
|
|
|
|
}
|
2023-01-17 17:47:37 +00:00
|
|
|
}
|
|
|
|
|
2023-03-22 15:58:01 +00:00
|
|
|
private func filteredUsers() -> [UserInfo] {
|
2023-04-04 21:53:25 +01:00
|
|
|
let s = trimmedSearchTextOrPassword
|
2023-03-22 15:58:01 +00:00
|
|
|
let lower = s.localizedLowercase
|
|
|
|
return m.users.filter { u in
|
2023-03-30 09:02:57 +01:00
|
|
|
if (u.user.activeUser || !u.user.hidden) && (s == "" || u.user.chatViewName.localizedLowercase.contains(lower)) {
|
2023-03-22 15:58:01 +00:00
|
|
|
return true
|
|
|
|
}
|
2023-03-30 09:02:57 +01:00
|
|
|
return correctPassword(u.user, s)
|
2023-03-22 15:58:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private var visibleUsersCount: Int {
|
|
|
|
m.users.filter({ u in !u.user.hidden }).count
|
|
|
|
}
|
2024-11-13 11:41:39 +00:00
|
|
|
|
|
|
|
private func withAuth(_ action: @escaping () -> Void) {
|
|
|
|
if authorized {
|
|
|
|
action()
|
|
|
|
} else {
|
|
|
|
authenticate(
|
2024-12-05 21:42:53 +00:00
|
|
|
reason: NSLocalizedString("Change chat profiles", comment: "authentication reason")
|
2024-11-13 11:41:39 +00:00
|
|
|
) { laResult in
|
|
|
|
switch laResult {
|
|
|
|
case .success, .unavailable:
|
|
|
|
authorized = true
|
|
|
|
AppSheetState.shared.scenePhaseActive = true
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: action)
|
|
|
|
case .failed: authorized = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-29 14:01:24 +01:00
|
|
|
private func correctPassword(_ user: User, _ pwd: String) -> Bool {
|
|
|
|
if let ph = user.viewPwdHash {
|
|
|
|
return pwd != "" && chatPasswordHash(pwd, ph.salt) == ph.hash
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-03-22 15:58:01 +00:00
|
|
|
private func userViewPassword(_ user: User) -> String? {
|
2023-04-04 21:53:25 +01:00
|
|
|
!user.hidden ? nil : trimmedSearchTextOrPassword
|
2023-03-29 14:01:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@ViewBuilder private func profileActionView(_ action: UserProfileAction) -> some View {
|
|
|
|
let passwordValid = actionPassword == actionPassword.trimmingCharacters(in: .whitespaces)
|
2023-03-29 19:28:06 +01:00
|
|
|
let passwordField = PassphraseField(key: $actionPassword, placeholder: "Profile password", valid: passwordValid)
|
|
|
|
let actionEnabled: (User) -> Bool = { user in actionPassword != "" && passwordValid && correctPassword(user, actionPassword) }
|
|
|
|
List {
|
|
|
|
switch action {
|
|
|
|
case let .deleteUser(user, delSMPQueues):
|
2023-03-30 09:02:57 +01:00
|
|
|
actionHeader("Delete profile", user)
|
2023-03-29 14:01:24 +01:00
|
|
|
Section {
|
2023-03-29 19:28:06 +01:00
|
|
|
passwordField
|
2024-07-03 22:42:13 +01:00
|
|
|
settingsRow("trash", color: theme.colors.secondary) {
|
2023-03-30 09:02:57 +01:00
|
|
|
Button("Delete chat profile", role: .destructive) {
|
2024-11-13 11:41:39 +00:00
|
|
|
withAuth {
|
|
|
|
profileAction = nil
|
|
|
|
Task { await removeUser(user, delSMPQueues, viewPwd: actionPassword) }
|
|
|
|
}
|
2023-03-29 14:01:24 +01:00
|
|
|
}
|
2023-03-29 19:28:06 +01:00
|
|
|
.disabled(!actionEnabled(user))
|
2023-03-29 14:01:24 +01:00
|
|
|
}
|
|
|
|
} footer: {
|
2023-03-29 19:28:06 +01:00
|
|
|
if actionEnabled(user) {
|
2023-03-29 14:01:24 +01:00
|
|
|
Text("All chats and messages will be deleted - this cannot be undone!")
|
2024-07-03 22:42:13 +01:00
|
|
|
.foregroundColor(theme.colors.secondary)
|
2023-03-29 14:01:24 +01:00
|
|
|
.font(.callout)
|
|
|
|
}
|
|
|
|
}
|
2023-03-29 19:28:06 +01:00
|
|
|
case let .unhideUser(user):
|
2023-03-30 09:02:57 +01:00
|
|
|
actionHeader("Unhide profile", user)
|
2023-03-29 19:28:06 +01:00
|
|
|
Section {
|
|
|
|
passwordField
|
2024-07-03 22:42:13 +01:00
|
|
|
settingsRow("lock.open", color: theme.colors.secondary) {
|
2023-03-30 09:02:57 +01:00
|
|
|
Button("Unhide chat profile") {
|
2024-11-13 11:41:39 +00:00
|
|
|
withAuth{
|
|
|
|
profileAction = nil
|
|
|
|
setUserPrivacy(user) { try await apiUnhideUser(user.userId, viewPwd: actionPassword) }
|
|
|
|
}
|
2023-03-29 19:28:06 +01:00
|
|
|
}
|
|
|
|
.disabled(!actionEnabled(user))
|
|
|
|
}
|
|
|
|
}
|
2023-03-29 14:01:24 +01:00
|
|
|
}
|
|
|
|
}
|
2024-07-03 22:42:13 +01:00
|
|
|
.modifier(ThemedBackground())
|
2023-03-22 15:58:01 +00:00
|
|
|
}
|
|
|
|
|
2023-03-29 19:28:06 +01:00
|
|
|
@ViewBuilder func actionHeader(_ title: LocalizedStringKey, _ user: User) -> some View {
|
|
|
|
Text(title)
|
|
|
|
.font(.title)
|
|
|
|
.bold()
|
|
|
|
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
|
|
|
.listRowBackground(Color.clear)
|
|
|
|
Section() {
|
|
|
|
ProfilePreview(profileOf: user).padding(.leading, -8)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-24 19:00:30 +00:00
|
|
|
private func deleteModeButton(_ title: LocalizedStringKey, _ delSMPQueues: Bool) -> some View {
|
|
|
|
Button(title, role: .destructive) {
|
2024-11-13 11:41:39 +00:00
|
|
|
withAuth {
|
|
|
|
if let user = userToDelete {
|
|
|
|
if passwordEntryRequired(user) {
|
|
|
|
profileAction = .deleteUser(user: user, delSMPQueues: delSMPQueues)
|
|
|
|
} else {
|
|
|
|
alert = .deleteUser(user: user, delSMPQueues: delSMPQueues)
|
|
|
|
}
|
2023-03-29 14:01:24 +01:00
|
|
|
}
|
2023-01-24 19:00:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-29 19:28:06 +01:00
|
|
|
private func passwordEntryRequired(_ user: User) -> Bool {
|
2023-04-04 21:53:25 +01:00
|
|
|
user.hidden && user.activeUser && !correctPassword(user, trimmedSearchTextOrPassword)
|
2023-03-29 19:28:06 +01:00
|
|
|
}
|
|
|
|
|
2023-03-29 14:01:24 +01:00
|
|
|
private func removeUser(_ user: User, _ delSMPQueues: Bool, viewPwd: String?) async {
|
2023-01-19 16:22:56 +00:00
|
|
|
do {
|
2023-03-29 14:01:24 +01:00
|
|
|
if user.activeUser {
|
2025-01-13 23:40:07 +07:00
|
|
|
ChatModel.shared.removeWallpaperFilesFromAllChats(user)
|
2023-03-22 15:58:01 +00:00
|
|
|
if let newActive = m.users.first(where: { u in !u.user.activeUser && !u.user.hidden }) {
|
|
|
|
try await changeActiveUserAsync_(newActive.user.userId, viewPwd: nil)
|
2023-03-29 14:01:24 +01:00
|
|
|
try await deleteUser()
|
2024-01-19 22:52:13 +07:00
|
|
|
} else {
|
|
|
|
// Deleting the last visible user while having hidden one(s)
|
|
|
|
try await deleteUser()
|
|
|
|
try await changeActiveUserAsync_(nil, viewPwd: nil)
|
2024-03-21 20:58:04 +07:00
|
|
|
try? await stopChatAsync()
|
2024-01-19 22:52:13 +07:00
|
|
|
await MainActor.run {
|
|
|
|
onboardingStageDefault.set(.step1_SimpleXInfo)
|
|
|
|
m.onboardingStage = .step1_SimpleXInfo
|
2024-09-10 09:31:53 +01:00
|
|
|
dismissAllSheets()
|
2024-01-19 22:52:13 +07:00
|
|
|
}
|
2023-01-24 19:00:30 +00:00
|
|
|
}
|
|
|
|
} else {
|
2023-03-29 14:01:24 +01:00
|
|
|
try await deleteUser()
|
2023-01-24 19:00:30 +00:00
|
|
|
}
|
2023-01-20 12:38:38 +00:00
|
|
|
} catch let error {
|
2024-01-19 22:52:13 +07:00
|
|
|
logger.error("Error deleting user profile: \(error)")
|
2023-01-20 12:38:38 +00:00
|
|
|
let a = getErrorAlert(error, "Error deleting user profile")
|
|
|
|
alert = .error(title: a.title, error: a.message)
|
2023-01-19 16:22:56 +00:00
|
|
|
}
|
2023-01-24 19:00:30 +00:00
|
|
|
|
2023-03-29 14:01:24 +01:00
|
|
|
func deleteUser() async throws {
|
|
|
|
try await apiDeleteUser(user.userId, delSMPQueues, viewPwd: viewPwd)
|
2025-01-13 23:40:07 +07:00
|
|
|
removeWallpaperFilesFromTheme(user.uiThemes)
|
2023-03-22 15:58:01 +00:00
|
|
|
await MainActor.run { withAnimation { m.removeUser(user) } }
|
2023-01-24 19:00:30 +00:00
|
|
|
}
|
2023-01-19 16:22:56 +00:00
|
|
|
}
|
2023-01-20 12:38:38 +00:00
|
|
|
|
2024-11-13 11:41:39 +00:00
|
|
|
@ViewBuilder private func userView(_ userInfo: UserInfo) -> some View {
|
|
|
|
let user = userInfo.user
|
2023-04-14 17:51:55 +02:00
|
|
|
let v = Button {
|
2023-03-22 15:58:01 +00:00
|
|
|
Task {
|
|
|
|
do {
|
|
|
|
try await changeActiveUserAsync_(user.userId, viewPwd: userViewPassword(user))
|
2024-09-10 09:31:53 +01:00
|
|
|
dismissAllSheets()
|
2023-03-22 15:58:01 +00:00
|
|
|
} catch {
|
|
|
|
await MainActor.run { alert = .activateUserError(error: responseError(error)) }
|
|
|
|
}
|
2023-01-31 15:55:41 +00:00
|
|
|
}
|
2023-01-17 17:47:37 +00:00
|
|
|
} label: {
|
2023-01-20 12:38:38 +00:00
|
|
|
HStack {
|
2024-09-10 09:31:53 +01:00
|
|
|
ProfileImage(imageStr: user.image, size: 38)
|
2023-01-20 12:38:38 +00:00
|
|
|
.padding(.trailing, 12)
|
|
|
|
Text(user.chatViewName)
|
|
|
|
Spacer()
|
2023-03-22 15:58:01 +00:00
|
|
|
if user.activeUser {
|
2024-07-03 22:42:13 +01:00
|
|
|
Image(systemName: "checkmark").foregroundColor(theme.colors.onBackground)
|
2023-03-22 15:58:01 +00:00
|
|
|
} else {
|
2024-11-13 11:41:39 +00:00
|
|
|
if userInfo.unreadCount > 0 {
|
|
|
|
UnreadBadge(userInfo: userInfo)
|
|
|
|
}
|
|
|
|
if user.hidden {
|
|
|
|
Image(systemName: "lock").foregroundColor(theme.colors.secondary)
|
|
|
|
} else if userInfo.unreadCount == 0 {
|
|
|
|
if !user.showNtfs {
|
|
|
|
Image(systemName: "speaker.slash").foregroundColor(theme.colors.secondary)
|
|
|
|
} else {
|
|
|
|
Image(systemName: "checkmark").foregroundColor(.clear)
|
|
|
|
}
|
|
|
|
}
|
2023-03-22 15:58:01 +00:00
|
|
|
}
|
2023-01-20 12:38:38 +00:00
|
|
|
}
|
2023-01-17 17:47:37 +00:00
|
|
|
}
|
2024-07-03 22:42:13 +01:00
|
|
|
.foregroundColor(theme.colors.onBackground)
|
2023-03-22 15:58:01 +00:00
|
|
|
.swipeActions(edge: .leading, allowsFullSwipe: true) {
|
|
|
|
if user.hidden {
|
|
|
|
Button("Unhide") {
|
2024-11-13 11:41:39 +00:00
|
|
|
withAuth {
|
|
|
|
if passwordEntryRequired(user) {
|
|
|
|
profileAction = .unhideUser(user: user)
|
|
|
|
} else {
|
|
|
|
setUserPrivacy(user) { try await apiUnhideUser(user.userId, viewPwd: trimmedSearchTextOrPassword) }
|
|
|
|
}
|
2023-03-29 19:28:06 +01:00
|
|
|
}
|
2023-03-22 15:58:01 +00:00
|
|
|
}
|
|
|
|
.tint(.green)
|
|
|
|
} else {
|
2023-06-17 09:58:35 +01:00
|
|
|
if visibleUsersCount > 1 {
|
2023-03-22 15:58:01 +00:00
|
|
|
Button("Hide") {
|
2024-11-13 11:41:39 +00:00
|
|
|
withAuth {
|
|
|
|
selectedUser = user
|
|
|
|
}
|
2023-03-22 15:58:01 +00:00
|
|
|
}
|
|
|
|
.tint(.gray)
|
|
|
|
}
|
|
|
|
Group {
|
|
|
|
if user.showNtfs {
|
|
|
|
Button("Mute") {
|
2024-11-13 11:41:39 +00:00
|
|
|
withAuth {
|
|
|
|
setUserPrivacy(user, successAlert: showMuteProfileAlert ? .muteProfileAlert : nil) {
|
|
|
|
try await apiMuteUser(user.userId)
|
|
|
|
}
|
2023-03-22 15:58:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Button("Unmute") {
|
2024-11-13 11:41:39 +00:00
|
|
|
withAuth {
|
|
|
|
setUserPrivacy(user) { try await apiUnmuteUser(user.userId) }
|
|
|
|
}
|
2023-03-22 15:58:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-07-03 22:42:13 +01:00
|
|
|
.tint(theme.colors.primary)
|
2023-03-22 15:58:01 +00:00
|
|
|
}
|
|
|
|
}
|
2023-04-14 17:51:55 +02:00
|
|
|
if #available(iOS 16, *) {
|
|
|
|
v
|
|
|
|
} else {
|
|
|
|
v.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
|
|
|
Button("Delete", role: .destructive) {
|
2024-11-13 11:41:39 +00:00
|
|
|
withAuth {
|
|
|
|
confirmDeleteUser(user)
|
|
|
|
}
|
2023-04-14 17:51:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func confirmDeleteUser(_ user: User) {
|
2024-01-19 22:52:13 +07:00
|
|
|
showDeleteConfirmation = true
|
|
|
|
userToDelete = user
|
2023-03-22 15:58:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private func setUserPrivacy(_ user: User, successAlert: UserProfilesAlert? = nil, _ api: @escaping () async throws -> User) {
|
|
|
|
Task {
|
|
|
|
do {
|
|
|
|
let u = try await api()
|
|
|
|
await MainActor.run {
|
|
|
|
withAnimation { m.updateUser(u) }
|
|
|
|
if successAlert != nil {
|
|
|
|
alert = successAlert
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch let error {
|
|
|
|
let a = getErrorAlert(error, "Error updating user privacy")
|
|
|
|
alert = .error(title: a.title, error: a.message)
|
|
|
|
}
|
|
|
|
}
|
2023-01-17 17:47:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-22 15:58:01 +00:00
|
|
|
public func chatPasswordHash(_ pwd: String, _ salt: String) -> String {
|
|
|
|
var cPwd = pwd.cString(using: .utf8)!
|
|
|
|
var cSalt = salt.cString(using: .utf8)!
|
|
|
|
let cHash = chat_password_hash(&cPwd, &cSalt)!
|
|
|
|
let hash = fromCString(cHash)
|
|
|
|
return hash
|
|
|
|
}
|
|
|
|
|
2024-08-23 13:20:07 +01:00
|
|
|
public func correctPassword(_ user: User, _ pwd: String) -> Bool {
|
|
|
|
if let ph = user.viewPwdHash {
|
|
|
|
return pwd != "" && chatPasswordHash(pwd, ph.salt) == ph.hash
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-01-17 17:47:37 +00:00
|
|
|
struct UserProfilesView_Previews: PreviewProvider {
|
|
|
|
static var previews: some View {
|
2024-09-10 09:31:53 +01:00
|
|
|
UserProfilesView()
|
2023-01-17 17:47:37 +00:00
|
|
|
}
|
|
|
|
}
|