Files
simplex-chat/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift

322 lines
12 KiB
Swift
Raw Normal View History

2022-07-14 16:40:32 +04:00
//
// GroupChatInfoView.swift
// SimpleX (iOS)
//
// Created by JRoberts on 14.07.2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
import SimpleXChat
struct GroupChatInfoView: View {
@EnvironmentObject var chatModel: ChatModel
2022-07-30 13:03:44 +01:00
@Environment(\.dismiss) var dismiss: DismissAction
2022-07-14 16:40:32 +04:00
@ObservedObject var chat: Chat
2022-11-16 20:26:43 +04:00
@State var groupInfo: GroupInfo
2022-07-30 13:03:44 +01:00
@ObservedObject private var alertManager = AlertManager.shared
@State private var alert: GroupChatInfoViewAlert? = nil
@State private var groupLink: String?
@State private var showAddMembersSheet: Bool = false
@State private var selectedMember: GroupMember? = nil
2022-07-30 13:03:44 +01:00
@State private var showGroupProfile: Bool = false
@State private var connectionStats: ConnectionStats?
2022-08-02 17:00:12 +04:00
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
2022-07-14 16:40:32 +04:00
enum GroupChatInfoViewAlert: Identifiable {
case deleteGroupAlert
2022-07-14 16:40:32 +04:00
case clearChatAlert
case leaveGroupAlert
2022-08-29 14:47:29 +04:00
case cantInviteIncognitoAlert
2022-07-14 16:40:32 +04:00
var id: GroupChatInfoViewAlert { get { self } }
2022-07-14 16:40:32 +04:00
}
var body: some View {
NavigationView {
let members = chatModel.groupMembers
.filter { $0.memberStatus != .memLeft && $0.memberStatus != .memRemoved }
.sorted { $0.displayName.lowercased() < $1.displayName.lowercased() }
List {
groupInfoHeader()
.listRowBackground(Color.clear)
2022-11-16 20:26:43 +04:00
Section {
NavigationLink {
GroupPreferencesView(
groupInfo: $groupInfo,
preferences: groupInfo.fullGroupPreferences,
currentPreferences: groupInfo.fullGroupPreferences
)
.navigationBarTitle("Group preferences")
.navigationBarTitleDisplayMode(.large)
} label: {
settingsRow("switch.2") {
Text("Group preferences")
}
2022-11-16 20:26:43 +04:00
}
} header: {
Text("Preferences")
} footer: {
Text("Only group owners can change group preferences.")
}
2022-07-30 13:03:44 +01:00
Section("\(members.count + 1) members") {
if groupInfo.canAddMembers {
groupLinkButton()
2022-08-29 14:47:29 +04:00
if (chat.chatInfo.incognito) {
Label("Invite members", systemImage: "plus")
.foregroundColor(Color(uiColor: .tertiaryLabel))
.onTapGesture { alert = .cantInviteIncognitoAlert }
} else {
addMembersButton()
}
}
memberView(groupInfo.membership, user: true)
ForEach(members) { member in
Button {
Task {
do {
2022-08-29 14:47:29 +04:00
let stats = try await apiGroupMemberInfo(groupInfo.apiId, member.groupMemberId)
await MainActor.run { connectionStats = stats }
} catch let error {
logger.error("apiGroupMemberInfo error: \(responseError(error))")
}
await MainActor.run { selectedMember = member }
}
} label: { memberView(member) }
}
}
.sheet(isPresented: $showAddMembersSheet) {
AddGroupMembersView(chat: chat, groupInfo: groupInfo)
}
.sheet(item: $selectedMember, onDismiss: {
selectedMember = nil
connectionStats = nil
}) { _ in
GroupMemberInfoView(groupInfo: groupInfo, member: $selectedMember, connectionStats: $connectionStats)
}
.sheet(isPresented: $showGroupProfile) {
GroupProfileView(groupId: groupInfo.apiId, groupProfile: groupInfo.groupProfile)
}
Section {
if groupInfo.canEdit {
editGroupButton()
}
clearChatButton()
if groupInfo.canDelete {
deleteGroupButton()
}
if groupInfo.membership.memberCurrent {
leaveGroupButton()
}
}
2022-08-02 17:00:12 +04:00
if developerTools {
Section(header: Text("For console")) {
infoRow("Local name", chat.chatInfo.localDisplayName)
infoRow("Database ID", "\(chat.chatInfo.apiId)")
}
}
2022-07-14 16:40:32 +04:00
}
.navigationBarHidden(true)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
2022-07-14 16:40:32 +04:00
.alert(item: $alert) { alertItem in
switch(alertItem) {
case .deleteGroupAlert: return deleteGroupAlert()
2022-07-14 16:40:32 +04:00
case .clearChatAlert: return clearChatAlert()
case .leaveGroupAlert: return leaveGroupAlert()
2022-08-29 14:47:29 +04:00
case .cantInviteIncognitoAlert: return cantInviteIncognitoAlert()
}
}
.onAppear {
do {
groupLink = try apiGetGroupLink(groupInfo.groupId)
} catch let error {
logger.error("GroupChatInfoView apiGetGroupLink: \(responseError(error))")
2022-07-14 16:40:32 +04:00
}
}
}
func groupInfoHeader() -> some View {
VStack {
let cInfo = chat.chatInfo
ChatInfoImage(chat: chat, color: Color(uiColor: .tertiarySystemFill))
.frame(width: 192, height: 192)
.padding(.top, 12)
.padding()
Text(cInfo.displayName)
.font(.largeTitle)
.lineLimit(1)
.padding(.bottom, 2)
if cInfo.fullName != "" && cInfo.fullName != cInfo.displayName {
Text(cInfo.fullName)
.font(.title2)
.lineLimit(2)
}
}
.frame(maxWidth: .infinity, alignment: .center)
}
private func addMembersButton() -> some View {
Button {
Task {
let groupMembers = await apiListMembers(groupInfo.groupId)
await MainActor.run {
ChatModel.shared.groupMembers = groupMembers
showAddMembersSheet = true
}
}
} label: {
Label("Invite members", systemImage: "plus")
}
2022-07-14 16:40:32 +04:00
}
func serverImage() -> some View {
let status = chat.serverInfo.networkStatus
return Image(systemName: status.imageName)
.foregroundColor(status == .connected ? .green : .secondary)
}
func memberView(_ member: GroupMember, user: Bool = false) -> some View {
HStack{
ProfileImage(imageStr: member.image)
.frame(width: 38, height: 38)
.padding(.trailing, 2)
// TODO server connection status
VStack(alignment: .leading) {
Text(member.chatViewName)
.lineLimit(1)
ios: incognito mode (#945) * ios: incognito types * wip * wip * wip * wip * wip * cleaner interface * CIGroupInvitationView logic * masks not filled * ui improvements * wip * wip * incognito may be compromised alerts * help * remove modifier * Update apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> * Update apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> * Update apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> * Update apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> * Update apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> * Update apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> * Update apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> * Update apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> * contact request * texts * ; * prepare for merge * restore help * wip * update help * wip * update incognito help * the * Update apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> * wording * translations * secondary color * translations * translations * fix Your Chats title Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-08-23 18:18:12 +04:00
.foregroundColor(member.memberIncognito ? .indigo : .primary)
let s = Text(member.memberStatus.shortText)
(user ? Text ("you: ") + s : s)
.lineLimit(1)
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
let role = member.memberRole
if role == .owner || role == .admin {
Text(member.memberRole.text)
.foregroundColor(.secondary)
}
}
}
private func groupLinkButton() -> some View {
NavigationLink {
GroupLinkView(groupId: groupInfo.groupId, groupLink: $groupLink)
.navigationBarTitleDisplayMode(.inline)
} label: {
Label("Group link", systemImage: "link")
.foregroundColor(.accentColor)
}
}
func editGroupButton() -> some View {
Button {
showGroupProfile = true
} label: {
Label("Edit group profile", systemImage: "pencil")
}
}
func deleteGroupButton() -> some View {
Button(role: .destructive) {
alert = .deleteGroupAlert
} label: {
Label("Delete group", systemImage: "trash")
.foregroundColor(Color.red)
}
}
func clearChatButton() -> some View {
Button() {
alert = .clearChatAlert
} label: {
Label("Clear conversation", systemImage: "gobackward")
.foregroundColor(Color.orange)
}
}
func leaveGroupButton() -> some View {
Button(role: .destructive) {
alert = .leaveGroupAlert
} label: {
Label("Leave group", systemImage: "rectangle.portrait.and.arrow.right")
.foregroundColor(Color.red)
}
}
// TODO reuse this and clearChatAlert with ChatInfoView
private func deleteGroupAlert() -> Alert {
return Alert(
title: Text("Delete group?"),
message: deleteGroupAlertMessage(),
2022-07-14 16:40:32 +04:00
primaryButton: .destructive(Text("Delete")) {
Task {
do {
try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId)
await MainActor.run {
chatModel.removeChat(chat.chatInfo.id)
chatModel.chatId = nil
2022-07-30 13:03:44 +01:00
dismiss()
2022-07-14 16:40:32 +04:00
}
} catch let error {
logger.error("deleteGroupAlert apiDeleteChat error: \(error.localizedDescription)")
2022-07-14 16:40:32 +04:00
}
}
},
secondaryButton: .cancel()
)
}
private func deleteGroupAlertMessage() -> Text {
groupInfo.membership.memberCurrent ? Text("Group will be deleted for all members - this cannot be undone!") : Text("Group will be deleted for you - this cannot be undone!")
}
2022-07-14 16:40:32 +04:00
private func clearChatAlert() -> Alert {
Alert(
title: Text("Clear conversation?"),
message: Text("All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you."),
primaryButton: .destructive(Text("Clear")) {
Task {
await clearChat(chat)
2022-07-30 13:03:44 +01:00
await MainActor.run { dismiss() }
}
},
secondaryButton: .cancel()
)
}
private func leaveGroupAlert() -> Alert {
Alert(
title: Text("Leave group?"),
message: Text("You will stop receiving messages from this group. Chat history will be preserved."),
primaryButton: .destructive(Text("Leave")) {
Task {
await leaveGroup(chat.chatInfo.apiId)
2022-07-30 13:03:44 +01:00
await MainActor.run { dismiss() }
2022-07-14 16:40:32 +04:00
}
},
secondaryButton: .cancel()
)
}
}
2022-08-29 14:47:29 +04:00
func cantInviteIncognitoAlert() -> Alert {
Alert(
title: Text("Can't invite contacts!"),
message: Text("You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed")
)
}
2022-07-14 16:40:32 +04:00
struct GroupChatInfoView_Previews: PreviewProvider {
static var previews: some View {
2022-07-30 13:03:44 +01:00
GroupChatInfoView(chat: Chat(chatInfo: ChatInfo.sampleData.group, chatItems: []), groupInfo: GroupInfo.sampleData)
2022-07-14 16:40:32 +04:00
}
}