mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-06-07 15:23:11 +00:00
Added group media settings and group settings page
This commit is contained in:
@@ -26,35 +26,48 @@ struct GroupChatInfoView: View {
|
||||
@State private var groupLinkMemberRole: GroupMemberRole = .member
|
||||
@State private var groupLinkNavLinkActive: Bool = false
|
||||
@State private var addMembersNavLinkActive: Bool = false
|
||||
@State private var settingsNavLinkActive: Bool = false
|
||||
@State private var connectionStats: ConnectionStats?
|
||||
@State private var connectionCode: String?
|
||||
@State private var sendReceipts = SendReceipts.userDefault(true)
|
||||
@State private var sendReceiptsUserDefault = true
|
||||
@State private var progressIndicator = false
|
||||
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
|
||||
@State private var searchText: String = ""
|
||||
@FocusState private var searchFocussed
|
||||
@State private var showSecrets: Set<Int> = []
|
||||
|
||||
@State private var selectedTab: GroupInfoTab = .members
|
||||
|
||||
enum GroupInfoTab: CaseIterable {
|
||||
case members
|
||||
case images
|
||||
case videos
|
||||
case files
|
||||
case links
|
||||
case voices
|
||||
|
||||
var imageName: String {
|
||||
switch self {
|
||||
case .members: return "person.2.fill"
|
||||
case .images: return "photo.fill"
|
||||
case .videos: return "video.fill"
|
||||
case .files: return "doc.fill"
|
||||
case .links: return "link"
|
||||
case .voices: return "mic.fill"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum GroupChatInfoViewAlert: Identifiable {
|
||||
case deleteGroupAlert
|
||||
case clearChatAlert
|
||||
case leaveGroupAlert
|
||||
case cantInviteIncognitoAlert
|
||||
case largeGroupReceiptsDisabled
|
||||
case blockMemberAlert(mem: GroupMember)
|
||||
case unblockMemberAlert(mem: GroupMember)
|
||||
case blockForAllAlert(mem: GroupMember)
|
||||
case unblockForAllAlert(mem: GroupMember)
|
||||
case error(title: LocalizedStringKey, error: LocalizedStringKey?)
|
||||
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case .deleteGroupAlert: return "deleteGroupAlert"
|
||||
case .clearChatAlert: return "clearChatAlert"
|
||||
case .leaveGroupAlert: return "leaveGroupAlert"
|
||||
case .cantInviteIncognitoAlert: return "cantInviteIncognitoAlert"
|
||||
case .largeGroupReceiptsDisabled: return "largeGroupReceiptsDisabled"
|
||||
case let .blockMemberAlert(mem): return "blockMemberAlert \(mem.groupMemberId)"
|
||||
case let .unblockMemberAlert(mem): return "unblockMemberAlert \(mem.groupMemberId)"
|
||||
case let .blockForAllAlert(mem): return "blockForAllAlert \(mem.groupMemberId)"
|
||||
@@ -63,23 +76,23 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
let members = chatModel.groupMembers
|
||||
.filter { m in let status = m.wrapped.memberStatus; return status != .memLeft && status != .memRemoved }
|
||||
.sorted { $0.wrapped.memberRole > $1.wrapped.memberRole }
|
||||
|
||||
|
||||
ZStack {
|
||||
List {
|
||||
groupInfoHeader()
|
||||
.listRowBackground(Color.clear)
|
||||
|
||||
|
||||
localAliasTextEdit()
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.padding(.bottom, 18)
|
||||
|
||||
|
||||
infoActionButtons()
|
||||
.padding(.horizontal)
|
||||
.frame(maxWidth: .infinity)
|
||||
@@ -87,112 +100,26 @@ struct GroupChatInfoView: View {
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
|
||||
Section {
|
||||
if groupInfo.canAddMembers && groupInfo.businessChat == nil {
|
||||
groupLinkButton()
|
||||
}
|
||||
if groupInfo.businessChat == nil && groupInfo.membership.memberRole >= .moderator {
|
||||
memberSupportButton()
|
||||
}
|
||||
if groupInfo.canModerate {
|
||||
GroupReportsChatNavLink(chat: chat, groupInfo: groupInfo, scrollToItemId: $scrollToItemId)
|
||||
}
|
||||
if groupInfo.membership.memberActive
|
||||
&& (groupInfo.membership.memberRole < .moderator || groupInfo.membership.supportChat != nil) {
|
||||
UserSupportChatNavLink(chat: chat, groupInfo: groupInfo, scrollToItemId: $scrollToItemId)
|
||||
}
|
||||
} header: {
|
||||
Text("")
|
||||
}
|
||||
|
||||
Section {
|
||||
if groupInfo.isOwner && groupInfo.businessChat == nil {
|
||||
editGroupButton()
|
||||
}
|
||||
if groupInfo.groupProfile.description != nil || (groupInfo.isOwner && groupInfo.businessChat == nil) {
|
||||
addOrEditWelcomeMessage()
|
||||
}
|
||||
GroupPreferencesButton(groupInfo: $groupInfo, preferences: groupInfo.fullGroupPreferences, currentPreferences: groupInfo.fullGroupPreferences)
|
||||
} footer: {
|
||||
let label: LocalizedStringKey = (
|
||||
groupInfo.businessChat == nil
|
||||
? "Only group owners can change group preferences."
|
||||
: "Only chat owners can change preferences."
|
||||
)
|
||||
Text(label)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
|
||||
Section {
|
||||
if members.filter({ $0.wrapped.memberCurrent }).count <= SMALL_GROUPS_RCPS_MEM_LIMIT {
|
||||
sendReceiptsOption()
|
||||
} else {
|
||||
sendReceiptsOptionDisabled()
|
||||
}
|
||||
NavigationLink {
|
||||
ChatWallpaperEditorSheet(chat: chat)
|
||||
} label: {
|
||||
Label("Chat theme", systemImage: "photo")
|
||||
}
|
||||
ChatTTLOption(chat: chat, progressIndicator: $progressIndicator)
|
||||
} footer: {
|
||||
Text("Delete chat messages from your device.")
|
||||
}
|
||||
|
||||
if !groupInfo.nextConnectPrepared {
|
||||
Section(header: Text("\(members.count + 1) members").foregroundColor(theme.colors.secondary)) {
|
||||
if groupInfo.canAddMembers {
|
||||
if (chat.chatInfo.incognito) {
|
||||
Label("Invite members", systemImage: "plus")
|
||||
.foregroundColor(Color(uiColor: .tertiaryLabel))
|
||||
.onTapGesture { alert = .cantInviteIncognitoAlert }
|
||||
} else {
|
||||
addMembersButton()
|
||||
}
|
||||
}
|
||||
searchFieldView(text: $searchText, focussed: $searchFocussed, theme.colors.onBackground, theme.colors.secondary)
|
||||
.padding(.leading, 8)
|
||||
let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase
|
||||
let filteredMembers = s == ""
|
||||
? members
|
||||
: members.filter { $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s) }
|
||||
MemberRowView(
|
||||
chat: chat,
|
||||
groupInfo: groupInfo,
|
||||
groupMember: GMember(groupInfo.membership),
|
||||
scrollToItemId: $scrollToItemId,
|
||||
user: true,
|
||||
alert: $alert
|
||||
)
|
||||
ForEach(filteredMembers) { member in
|
||||
MemberRowView(chat: chat, groupInfo: groupInfo, groupMember: member, scrollToItemId: $scrollToItemId, alert: $alert)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
clearChatButton()
|
||||
if groupInfo.canDelete {
|
||||
deleteGroupButton()
|
||||
}
|
||||
if groupInfo.membership.memberCurrentOrPending {
|
||||
leaveGroupButton()
|
||||
}
|
||||
}
|
||||
|
||||
if developerTools {
|
||||
Section(header: Text("For console").foregroundColor(theme.colors.secondary)) {
|
||||
infoRow("Local name", chat.chatInfo.localDisplayName)
|
||||
infoRow("Database ID", "\(chat.chatInfo.apiId)")
|
||||
}
|
||||
.padding(.bottom, 18)
|
||||
|
||||
segmentedControl()
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
.clipShape(Rectangle())
|
||||
|
||||
if selectedTab == .members {
|
||||
membersTabContent(members: members)
|
||||
} else {
|
||||
//TODO: After adding media API calls, add exact UI elements
|
||||
noDataAvailableView()
|
||||
}
|
||||
}
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.navigationBarHidden(true)
|
||||
.disabled(progressIndicator)
|
||||
.opacity(progressIndicator ? 0.6 : 1)
|
||||
|
||||
|
||||
if progressIndicator {
|
||||
ProgressView().scaleEffect(2)
|
||||
}
|
||||
@@ -201,11 +128,7 @@ struct GroupChatInfoView: View {
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
||||
.alert(item: $alert) { alertItem in
|
||||
switch(alertItem) {
|
||||
case .deleteGroupAlert: return deleteGroupAlert()
|
||||
case .clearChatAlert: return clearChatAlert()
|
||||
case .leaveGroupAlert: return leaveGroupAlert()
|
||||
case .cantInviteIncognitoAlert: return cantInviteIncognitoAlert()
|
||||
case .largeGroupReceiptsDisabled: return largeGroupReceiptsDisabledAlert()
|
||||
case let .blockMemberAlert(mem): return blockMemberAlert(groupInfo, mem)
|
||||
case let .unblockMemberAlert(mem): return unblockMemberAlert(groupInfo, mem)
|
||||
case let .blockForAllAlert(mem): return blockForAllAlert(groupInfo, mem)
|
||||
@@ -228,7 +151,7 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func groupInfoHeader() -> some View {
|
||||
VStack {
|
||||
let cInfo = chat.chatInfo
|
||||
@@ -260,7 +183,7 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
|
||||
|
||||
private func localAliasTextEdit() -> some View {
|
||||
TextField("Set chat name…", text: $localAlias)
|
||||
.disableAutocorrection(true)
|
||||
@@ -277,7 +200,7 @@ struct GroupChatInfoView: View {
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
|
||||
|
||||
private func setGroupAlias() {
|
||||
Task {
|
||||
do {
|
||||
@@ -291,11 +214,16 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func infoActionButtons() -> some View {
|
||||
GeometryReader { g in
|
||||
let buttonWidth = g.size.width / 4
|
||||
HStack(alignment: .center, spacing: 8) {
|
||||
let spacing: CGFloat = 8
|
||||
let horizontalPadding: CGFloat = 32
|
||||
let availableWidth = g.size.width - horizontalPadding
|
||||
let totalSpacing: CGFloat = spacing * 3
|
||||
let buttonWidth = (availableWidth - totalSpacing) / 4
|
||||
|
||||
HStack(alignment: .center, spacing: spacing) {
|
||||
searchButton(width: buttonWidth)
|
||||
if groupInfo.canAddMembers {
|
||||
addMembersActionButton(width: buttonWidth)
|
||||
@@ -303,11 +231,12 @@ struct GroupChatInfoView: View {
|
||||
if let nextNtfMode = chat.chatInfo.nextNtfMode {
|
||||
muteButton(width: buttonWidth, nextNtfMode: nextNtfMode)
|
||||
}
|
||||
settingsButton(width: buttonWidth)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func searchButton(width: CGFloat) -> some View {
|
||||
InfoViewButton(image: "magnifyingglass", title: "search", width: width) {
|
||||
dismiss()
|
||||
@@ -315,14 +244,14 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
.disabled(!groupInfo.ready || chat.chatItems.isEmpty)
|
||||
}
|
||||
|
||||
|
||||
private func addMembersActionButton(width: CGFloat) -> some View {
|
||||
ZStack {
|
||||
if chat.chatInfo.incognito {
|
||||
InfoViewButton(image: "link.badge.plus", title: "invite", width: width) {
|
||||
groupLinkNavLinkActive = true
|
||||
}
|
||||
|
||||
|
||||
NavigationLink(isActive: $groupLinkNavLinkActive) {
|
||||
groupLinkDestinationView()
|
||||
} label: {
|
||||
@@ -334,7 +263,7 @@ struct GroupChatInfoView: View {
|
||||
InfoViewButton(image: "person.fill.badge.plus", title: "invite", width: width) {
|
||||
addMembersNavLinkActive = true
|
||||
}
|
||||
|
||||
|
||||
NavigationLink(isActive: $addMembersNavLinkActive) {
|
||||
addMembersDestinationView()
|
||||
} label: {
|
||||
@@ -346,7 +275,7 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
.disabled(!groupInfo.ready)
|
||||
}
|
||||
|
||||
|
||||
private func muteButton(width: CGFloat, nextNtfMode: MsgFilter) -> some View {
|
||||
return InfoViewButton(
|
||||
image: nextNtfMode.iconFilled,
|
||||
@@ -357,12 +286,144 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
.disabled(!groupInfo.ready)
|
||||
}
|
||||
|
||||
|
||||
private func settingsButton(width: CGFloat) -> some View {
|
||||
let isOwner = groupInfo.isOwner
|
||||
let image = isOwner ? "pencil" : "info.circle"
|
||||
let title: LocalizedStringKey = isOwner ? "edit" : "info"
|
||||
|
||||
return ZStack {
|
||||
InfoViewButton(image: image, title: title, width: width) {
|
||||
settingsNavLinkActive = true
|
||||
}
|
||||
|
||||
NavigationLink(isActive: $settingsNavLinkActive) {
|
||||
settingsDestinationView()
|
||||
} label: {
|
||||
EmptyView()
|
||||
}
|
||||
.frame(width: 1, height: 1)
|
||||
.hidden()
|
||||
}
|
||||
.disabled(!groupInfo.ready)
|
||||
}
|
||||
|
||||
private func settingsDestinationView() -> some View {
|
||||
GroupSettingsView(
|
||||
chat: chat,
|
||||
groupInfo: $groupInfo,
|
||||
sendReceipts: $sendReceipts,
|
||||
sendReceiptsUserDefault: sendReceiptsUserDefault,
|
||||
progressIndicator: $progressIndicator,
|
||||
setSendReceipts: setSendReceipts,
|
||||
dismiss: dismiss
|
||||
)
|
||||
}
|
||||
|
||||
private func segmentedControl() -> some View {
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
ForEach(GroupInfoTab.allCases, id: \.self) { tab in
|
||||
Button {
|
||||
withAnimation(.spring(response: 0.4, dampingFraction: 0.7)) {
|
||||
selectedTab = tab
|
||||
}
|
||||
} label: {
|
||||
VStack(spacing: 4) {
|
||||
Image(systemName: tab.imageName)
|
||||
.font(.system(size: 16, weight: .medium))
|
||||
.foregroundColor(selectedTab == tab ? theme.colors.primary : Color(.secondaryLabel))
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 8)
|
||||
|
||||
Rectangle()
|
||||
.fill(selectedTab == tab ? theme.colors.primary : Color.clear)
|
||||
.frame(height: 3)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.padding(.top, 12)
|
||||
.padding(.bottom, 8)
|
||||
}
|
||||
|
||||
private func membersTabContent(members: [GMember]) -> some View {
|
||||
Group {
|
||||
Section {
|
||||
if groupInfo.canAddMembers && groupInfo.businessChat == nil {
|
||||
groupLinkButton()
|
||||
}
|
||||
if groupInfo.businessChat == nil && groupInfo.membership.memberRole >= .moderator {
|
||||
memberSupportButton()
|
||||
}
|
||||
if groupInfo.canModerate {
|
||||
GroupReportsChatNavLink(chat: chat, groupInfo: groupInfo, scrollToItemId: $scrollToItemId)
|
||||
}
|
||||
if groupInfo.membership.memberActive
|
||||
&& (groupInfo.membership.memberRole < .moderator || groupInfo.membership.supportChat != nil) {
|
||||
UserSupportChatNavLink(chat: chat, groupInfo: groupInfo, scrollToItemId: $scrollToItemId)
|
||||
}
|
||||
}
|
||||
|
||||
if !groupInfo.nextConnectPrepared {
|
||||
Section(header: Text("\(members.count + 1) members").foregroundColor(theme.colors.secondary)) {
|
||||
if groupInfo.canAddMembers {
|
||||
if (chat.chatInfo.incognito) {
|
||||
Label("Invite members", systemImage: "plus")
|
||||
.foregroundColor(Color(uiColor: .tertiaryLabel))
|
||||
.onTapGesture { alert = .cantInviteIncognitoAlert }
|
||||
} else {
|
||||
addMembersButton()
|
||||
}
|
||||
}
|
||||
searchFieldView(text: $searchText, focussed: $searchFocussed, theme.colors.onBackground, theme.colors.secondary)
|
||||
.padding(.leading, 8)
|
||||
let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase
|
||||
let filteredMembers = s == ""
|
||||
? members
|
||||
: members.filter { $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s) }
|
||||
MemberRowView(
|
||||
chat: chat,
|
||||
groupInfo: groupInfo,
|
||||
groupMember: GMember(groupInfo.membership),
|
||||
scrollToItemId: $scrollToItemId,
|
||||
user: true,
|
||||
alert: $alert
|
||||
)
|
||||
ForEach(filteredMembers) { member in
|
||||
MemberRowView(chat: chat, groupInfo: groupInfo, groupMember: member, scrollToItemId: $scrollToItemId, alert: $alert)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func noDataAvailableView() -> some View {
|
||||
Section {
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("No data available")
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.padding(.vertical, 40)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setSendReceipts() {
|
||||
var chatSettings = chat.chatInfo.chatSettings ?? ChatSettings.defaults
|
||||
chatSettings.sendRcpts = sendReceipts.bool()
|
||||
updateChatSettings(chat, chatSettings: chatSettings)
|
||||
}
|
||||
|
||||
private func addMembersButton() -> some View {
|
||||
let label: LocalizedStringKey = switch groupInfo.businessChat?.chatType {
|
||||
case .customer: "Add team members"
|
||||
case .business: "Add friends"
|
||||
case .none: "Invite members"
|
||||
case .customer: "Add team members"
|
||||
case .business: "Add friends"
|
||||
case .none: "Invite members"
|
||||
}
|
||||
return NavigationLink {
|
||||
addMembersDestinationView()
|
||||
@@ -370,7 +431,7 @@ struct GroupChatInfoView: View {
|
||||
Label(label, systemImage: "plus")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func addMembersDestinationView() -> some View {
|
||||
AddGroupMembersView(chat: chat, groupInfo: groupInfo)
|
||||
.onAppear {
|
||||
@@ -380,7 +441,7 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private struct MemberRowView: View {
|
||||
var chat: Chat
|
||||
var groupInfo: GroupInfo
|
||||
@@ -389,7 +450,7 @@ struct GroupChatInfoView: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
var user: Bool = false
|
||||
@Binding var alert: GroupChatInfoViewAlert?
|
||||
|
||||
|
||||
var body: some View {
|
||||
let member = groupMember.wrapped
|
||||
let v1 = HStack{
|
||||
@@ -408,7 +469,7 @@ struct GroupChatInfoView: View {
|
||||
Spacer()
|
||||
memberInfo(member)
|
||||
}
|
||||
|
||||
|
||||
let v = ZStack {
|
||||
if user {
|
||||
v1
|
||||
@@ -422,7 +483,7 @@ struct GroupChatInfoView: View {
|
||||
v1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if user {
|
||||
v
|
||||
} else if groupInfo.membership.memberRole >= .moderator {
|
||||
@@ -446,12 +507,12 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func memberInfoView() -> some View {
|
||||
GroupMemberInfoView(groupInfo: groupInfo, chat: chat, groupMember: groupMember, scrollToItemId: $scrollToItemId)
|
||||
.navigationBarHidden(false)
|
||||
}
|
||||
|
||||
|
||||
private func memberConnStatus(_ member: GroupMember) -> LocalizedStringKey {
|
||||
if member.activeConn?.connDisabled ?? false {
|
||||
return "disabled"
|
||||
@@ -461,7 +522,7 @@ struct GroupChatInfoView: View {
|
||||
return member.memberStatus.shortText
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ViewBuilder private func memberInfo(_ member: GroupMember) -> some View {
|
||||
if member.blocked {
|
||||
Text("blocked")
|
||||
@@ -474,7 +535,7 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func blockSwipe<V: View>(_ member: GroupMember, _ v: V) -> some View {
|
||||
v.swipeActions(edge: .leading) {
|
||||
if member.memberSettings.showMessages {
|
||||
@@ -492,7 +553,7 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func blockForAllSwipe<V: View>(_ member: GroupMember, _ v: V) -> some View {
|
||||
v.swipeActions(edge: .leading) {
|
||||
if member.blockedByAdmin {
|
||||
@@ -510,7 +571,7 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func removeSwipe<V: View>(_ member: GroupMember, _ v: V) -> some View {
|
||||
v.swipeActions(edge: .trailing) {
|
||||
Button(role: .destructive) {
|
||||
@@ -521,7 +582,7 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var memberVerifiedShield: Text {
|
||||
(Text(Image(systemName: "checkmark.shield")) + textSpace)
|
||||
.font(.caption)
|
||||
@@ -530,7 +591,7 @@ struct GroupChatInfoView: View {
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func groupLinkButton() -> some View {
|
||||
NavigationLink {
|
||||
groupLinkDestinationView()
|
||||
@@ -542,7 +603,7 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func groupLinkDestinationView() -> some View {
|
||||
GroupLinkView(
|
||||
groupId: groupInfo.groupId,
|
||||
@@ -555,7 +616,7 @@ struct GroupChatInfoView: View {
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
}
|
||||
|
||||
|
||||
struct UserSupportChatNavLink: View {
|
||||
@ObservedObject var chat: Chat
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@@ -563,7 +624,7 @@ struct GroupChatInfoView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@Binding var scrollToItemId: ChatItem.ID?
|
||||
@State private var navLinkActive = false
|
||||
|
||||
|
||||
var body: some View {
|
||||
let scopeInfo: GroupChatScopeInfo = .memberSupport(groupMember_: nil)
|
||||
NavigationLink(isActive: $navLinkActive) {
|
||||
@@ -587,7 +648,7 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func memberSupportButton() -> some View {
|
||||
NavigationLink {
|
||||
MemberSupportView(groupInfo: groupInfo, scrollToItemId: $scrollToItemId)
|
||||
@@ -607,7 +668,7 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct GroupReportsChatNavLink: View {
|
||||
@ObservedObject var chat: Chat
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@@ -615,7 +676,7 @@ struct GroupChatInfoView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@Binding var scrollToItemId: ChatItem.ID?
|
||||
@State private var navLinkActive = false
|
||||
|
||||
|
||||
var body: some View {
|
||||
NavigationLink(isActive: $navLinkActive) {
|
||||
SecondaryChatView(
|
||||
@@ -642,152 +703,6 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func editGroupButton() -> some View {
|
||||
NavigationLink {
|
||||
GroupProfileView(
|
||||
groupInfo: $groupInfo,
|
||||
groupProfile: groupInfo.groupProfile
|
||||
)
|
||||
} label: {
|
||||
Label("Edit group profile", systemImage: "pencil")
|
||||
}
|
||||
}
|
||||
|
||||
private func addOrEditWelcomeMessage() -> some View {
|
||||
NavigationLink {
|
||||
GroupWelcomeView(
|
||||
groupInfo: $groupInfo,
|
||||
groupProfile: groupInfo.groupProfile,
|
||||
welcomeText: groupInfo.groupProfile.description ?? ""
|
||||
)
|
||||
.navigationTitle("Welcome message")
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
groupInfo.groupProfile.description == nil
|
||||
? Label("Add welcome message", systemImage: "plus.message")
|
||||
: Label("Welcome message", systemImage: "message")
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func deleteGroupButton() -> some View {
|
||||
let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Delete group" : "Delete chat"
|
||||
Button(role: .destructive) {
|
||||
alert = .deleteGroupAlert
|
||||
} label: {
|
||||
Label(label, systemImage: "trash")
|
||||
.foregroundColor(Color.red)
|
||||
}
|
||||
}
|
||||
|
||||
private func clearChatButton() -> some View {
|
||||
Button() {
|
||||
alert = .clearChatAlert
|
||||
} label: {
|
||||
Label("Clear conversation", systemImage: "gobackward")
|
||||
.foregroundColor(Color.orange)
|
||||
}
|
||||
}
|
||||
|
||||
private func leaveGroupButton() -> some View {
|
||||
let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Leave group" : "Leave chat"
|
||||
return Button(role: .destructive) {
|
||||
alert = .leaveGroupAlert
|
||||
} label: {
|
||||
Label(label, systemImage: "rectangle.portrait.and.arrow.right")
|
||||
.foregroundColor(Color.red)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO reuse this and clearChatAlert with ChatInfoView
|
||||
private func deleteGroupAlert() -> Alert {
|
||||
let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Delete group?" : "Delete chat?"
|
||||
return Alert(
|
||||
title: Text(label),
|
||||
message: deleteGroupAlertMessage(groupInfo),
|
||||
primaryButton: .destructive(Text("Delete")) {
|
||||
Task {
|
||||
do {
|
||||
try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId)
|
||||
await MainActor.run {
|
||||
dismiss()
|
||||
chatModel.chatId = nil
|
||||
chatModel.removeChat(chat.chatInfo.id)
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("deleteGroupAlert apiDeleteChat error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
await MainActor.run { dismiss() }
|
||||
}
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
|
||||
private func leaveGroupAlert() -> Alert {
|
||||
let titleLabel: LocalizedStringKey = groupInfo.businessChat == nil ? "Leave group?" : "Leave chat?"
|
||||
let messageLabel: LocalizedStringKey = (
|
||||
groupInfo.businessChat == nil
|
||||
? "You will stop receiving messages from this group. Chat history will be preserved."
|
||||
: "You will stop receiving messages from this chat. Chat history will be preserved."
|
||||
)
|
||||
return Alert(
|
||||
title: Text(titleLabel),
|
||||
message: Text(messageLabel),
|
||||
primaryButton: .destructive(Text("Leave")) {
|
||||
Task {
|
||||
await leaveGroup(chat.chatInfo.apiId)
|
||||
await MainActor.run { dismiss() }
|
||||
}
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
|
||||
private func sendReceiptsOption() -> some View {
|
||||
WrappedPicker(selection: $sendReceipts) {
|
||||
ForEach([.yes, .no, .userDefault(sendReceiptsUserDefault)]) { (opt: SendReceipts) in
|
||||
Text(opt.text)
|
||||
}
|
||||
} label: {
|
||||
Label("Send receipts", systemImage: "checkmark.message")
|
||||
}
|
||||
.onChange(of: sendReceipts) { _ in
|
||||
setSendReceipts()
|
||||
}
|
||||
}
|
||||
|
||||
private func setSendReceipts() {
|
||||
var chatSettings = chat.chatInfo.chatSettings ?? ChatSettings.defaults
|
||||
chatSettings.sendRcpts = sendReceipts.bool()
|
||||
updateChatSettings(chat, chatSettings: chatSettings)
|
||||
}
|
||||
|
||||
private func sendReceiptsOptionDisabled() -> some View {
|
||||
HStack {
|
||||
Label("Send receipts", systemImage: "checkmark.message")
|
||||
Spacer()
|
||||
Text("disabled")
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.onTapGesture {
|
||||
alert = .largeGroupReceiptsDisabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func showRemoveMemberAlert(_ groupInfo: GroupInfo, _ mem: GroupMember, dismiss: DismissAction? = nil) {
|
||||
@@ -843,71 +758,6 @@ func deleteGroupAlertMessage(_ groupInfo: GroupInfo) -> Text {
|
||||
)
|
||||
}
|
||||
|
||||
struct GroupPreferencesButton: View {
|
||||
@Binding var groupInfo: GroupInfo
|
||||
@State var preferences: FullGroupPreferences
|
||||
@State var currentPreferences: FullGroupPreferences
|
||||
var creatingGroup: Bool = false
|
||||
|
||||
private var label: LocalizedStringKey {
|
||||
groupInfo.businessChat == nil ? "Group preferences" : "Chat preferences"
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationLink {
|
||||
GroupPreferencesView(
|
||||
groupInfo: $groupInfo,
|
||||
preferences: $preferences,
|
||||
currentPreferences: currentPreferences,
|
||||
creatingGroup: creatingGroup,
|
||||
savePreferences: savePreferences
|
||||
)
|
||||
.navigationBarTitle(label)
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
.onDisappear {
|
||||
let saveText = NSLocalizedString(
|
||||
creatingGroup ? "Save" : "Save and notify group members",
|
||||
comment: "alert button"
|
||||
)
|
||||
|
||||
if groupInfo.fullGroupPreferences != preferences {
|
||||
showAlert(
|
||||
title: NSLocalizedString("Save preferences?", comment: "alert title"),
|
||||
buttonTitle: saveText,
|
||||
buttonAction: { savePreferences() },
|
||||
cancelButton: true
|
||||
)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
if creatingGroup {
|
||||
Text("Set group preferences")
|
||||
} else {
|
||||
Label(label, systemImage: "switch.2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func savePreferences() {
|
||||
Task {
|
||||
do {
|
||||
var gp = groupInfo.groupProfile
|
||||
gp.groupPreferences = toGroupPreferences(preferences)
|
||||
let gInfo = try await apiUpdateGroup(groupInfo.groupId, gp)
|
||||
await MainActor.run {
|
||||
groupInfo = gInfo
|
||||
ChatModel.shared.updateGroup(gInfo)
|
||||
currentPreferences = preferences
|
||||
}
|
||||
} catch {
|
||||
logger.error("GroupPreferencesView apiUpdateGroup error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func cantInviteIncognitoAlert() -> Alert {
|
||||
Alert(
|
||||
title: Text("Can't invite contacts!"),
|
||||
@@ -915,13 +765,6 @@ func cantInviteIncognitoAlert() -> Alert {
|
||||
)
|
||||
}
|
||||
|
||||
func largeGroupReceiptsDisabledAlert() -> Alert {
|
||||
Alert(
|
||||
title: Text("Receipts are disabled"),
|
||||
message: Text("This group has over \(SMALL_GROUPS_RCPS_MEM_LIMIT) members, delivery receipts are not sent.")
|
||||
)
|
||||
}
|
||||
|
||||
struct GroupChatInfoView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
GroupChatInfoView(
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
//
|
||||
// GroupSettingsView.swift
|
||||
// SimpleX
|
||||
//
|
||||
// Created by Suren Poghosyan on 12.01.26.
|
||||
// Copyright © 2026 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct GroupSettingsView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@ObservedObject var chat: Chat
|
||||
@Binding var groupInfo: GroupInfo
|
||||
@Binding var sendReceipts: SendReceipts
|
||||
var sendReceiptsUserDefault: Bool
|
||||
@Binding var progressIndicator: Bool
|
||||
var setSendReceipts: () -> Void
|
||||
var dismiss: DismissAction
|
||||
@State private var alert: GroupSettingsViewAlert? = nil
|
||||
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
|
||||
|
||||
enum GroupSettingsViewAlert: Identifiable {
|
||||
case deleteGroupAlert
|
||||
case clearChatAlert
|
||||
case leaveGroupAlert
|
||||
case largeGroupReceiptsDisabled
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case .deleteGroupAlert: return "deleteGroupAlert"
|
||||
case .clearChatAlert: return "clearChatAlert"
|
||||
case .leaveGroupAlert: return "leaveGroupAlert"
|
||||
case .largeGroupReceiptsDisabled: return "largeGroupReceiptsDisabled"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
let members = chatModel.groupMembers
|
||||
.filter { m in let status = m.wrapped.memberStatus; return status != .memLeft && status != .memRemoved }
|
||||
|
||||
List {
|
||||
Section {
|
||||
if groupInfo.isOwner && groupInfo.businessChat == nil {
|
||||
editGroupButton()
|
||||
}
|
||||
if groupInfo.groupProfile.description != nil || (groupInfo.isOwner && groupInfo.businessChat == nil) {
|
||||
addOrEditWelcomeMessage()
|
||||
}
|
||||
GroupPreferencesButton(groupInfo: $groupInfo, preferences: groupInfo.fullGroupPreferences, currentPreferences: groupInfo.fullGroupPreferences)
|
||||
} footer: {
|
||||
let label: LocalizedStringKey = (
|
||||
groupInfo.businessChat == nil
|
||||
? "Only group owners can change group preferences."
|
||||
: "Only chat owners can change preferences."
|
||||
)
|
||||
Text(label)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
|
||||
Section {
|
||||
if members.filter({ $0.wrapped.memberCurrent }).count <= SMALL_GROUPS_RCPS_MEM_LIMIT {
|
||||
sendReceiptsOption()
|
||||
} else {
|
||||
sendReceiptsOptionDisabled()
|
||||
}
|
||||
NavigationLink {
|
||||
ChatWallpaperEditorSheet(chat: chat)
|
||||
} label: {
|
||||
Label("Chat theme", systemImage: "photo")
|
||||
}
|
||||
ChatTTLOption(chat: chat, progressIndicator: $progressIndicator)
|
||||
} footer: {
|
||||
Text("Delete chat messages from your device.")
|
||||
}
|
||||
|
||||
Section {
|
||||
clearChatButton()
|
||||
if groupInfo.canDelete {
|
||||
deleteGroupButton()
|
||||
}
|
||||
if groupInfo.membership.memberCurrentOrPending {
|
||||
leaveGroupButton()
|
||||
}
|
||||
}
|
||||
|
||||
if developerTools {
|
||||
Section(header: Text("For console").foregroundColor(theme.colors.secondary)) {
|
||||
infoRow("Local name", chat.chatInfo.localDisplayName)
|
||||
infoRow("Database ID", "\(chat.chatInfo.apiId)")
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Chat Settings")
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
.alert(item: $alert) { alertItem in
|
||||
switch(alertItem) {
|
||||
case .deleteGroupAlert: return deleteGroupAlert()
|
||||
case .clearChatAlert: return clearChatAlert()
|
||||
case .leaveGroupAlert: return leaveGroupAlert()
|
||||
case .largeGroupReceiptsDisabled: return largeGroupReceiptsDisabledAlert()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func editGroupButton() -> some View {
|
||||
NavigationLink {
|
||||
GroupProfileView(
|
||||
groupInfo: $groupInfo,
|
||||
groupProfile: groupInfo.groupProfile
|
||||
)
|
||||
} label: {
|
||||
Label("Edit group profile", systemImage: "pencil")
|
||||
}
|
||||
}
|
||||
|
||||
private func addOrEditWelcomeMessage() -> some View {
|
||||
NavigationLink {
|
||||
GroupWelcomeView(
|
||||
groupInfo: $groupInfo,
|
||||
groupProfile: groupInfo.groupProfile,
|
||||
welcomeText: groupInfo.groupProfile.description ?? ""
|
||||
)
|
||||
.navigationTitle("Welcome message")
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
groupInfo.groupProfile.description == nil
|
||||
? Label("Add welcome message", systemImage: "plus.message")
|
||||
: Label("Welcome message", systemImage: "message")
|
||||
}
|
||||
}
|
||||
|
||||
private func sendReceiptsOption() -> some View {
|
||||
WrappedPicker(selection: $sendReceipts) {
|
||||
ForEach([.yes, .no, .userDefault(sendReceiptsUserDefault)]) { (opt: SendReceipts) in
|
||||
Text(opt.text)
|
||||
}
|
||||
} label: {
|
||||
Label("Send receipts", systemImage: "checkmark.message")
|
||||
}
|
||||
.onChange(of: sendReceipts) { _ in
|
||||
setSendReceipts()
|
||||
}
|
||||
}
|
||||
|
||||
private func sendReceiptsOptionDisabled() -> some View {
|
||||
HStack {
|
||||
Label("Send receipts", systemImage: "checkmark.message")
|
||||
Spacer()
|
||||
Text("disabled")
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.onTapGesture {
|
||||
alert = .largeGroupReceiptsDisabled
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func deleteGroupButton() -> some View {
|
||||
let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Delete group" : "Delete chat"
|
||||
Button(role: .destructive) {
|
||||
alert = .deleteGroupAlert
|
||||
} label: {
|
||||
Label(label, systemImage: "trash")
|
||||
.foregroundColor(Color.red)
|
||||
}
|
||||
}
|
||||
|
||||
private func clearChatButton() -> some View {
|
||||
Button() {
|
||||
alert = .clearChatAlert
|
||||
} label: {
|
||||
Label("Clear conversation", systemImage: "gobackward")
|
||||
.foregroundColor(Color.orange)
|
||||
}
|
||||
}
|
||||
|
||||
private func leaveGroupButton() -> some View {
|
||||
let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Leave group" : "Leave chat"
|
||||
return Button(role: .destructive) {
|
||||
alert = .leaveGroupAlert
|
||||
} label: {
|
||||
Label(label, systemImage: "rectangle.portrait.and.arrow.right")
|
||||
.foregroundColor(Color.red)
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteGroupAlert() -> Alert {
|
||||
let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Delete group?" : "Delete chat?"
|
||||
return Alert(
|
||||
title: Text(label),
|
||||
message: deleteGroupAlertMessage(groupInfo),
|
||||
primaryButton: .destructive(Text("Delete")) {
|
||||
Task {
|
||||
do {
|
||||
try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId)
|
||||
await MainActor.run {
|
||||
dismiss()
|
||||
chatModel.chatId = nil
|
||||
chatModel.removeChat(chat.chatInfo.id)
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("deleteGroupAlert apiDeleteChat error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
await MainActor.run { dismiss() }
|
||||
}
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
|
||||
private func leaveGroupAlert() -> Alert {
|
||||
let titleLabel: LocalizedStringKey = groupInfo.businessChat == nil ? "Leave group?" : "Leave chat?"
|
||||
let messageLabel: LocalizedStringKey = (
|
||||
groupInfo.businessChat == nil
|
||||
? "You will stop receiving messages from this group. Chat history will be preserved."
|
||||
: "You will stop receiving messages from this chat. Chat history will be preserved."
|
||||
)
|
||||
return Alert(
|
||||
title: Text(titleLabel),
|
||||
message: Text(messageLabel),
|
||||
primaryButton: .destructive(Text("Leave")) {
|
||||
Task {
|
||||
await leaveGroup(chat.chatInfo.apiId)
|
||||
await MainActor.run { dismiss() }
|
||||
}
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
|
||||
private func largeGroupReceiptsDisabledAlert() -> Alert {
|
||||
Alert(
|
||||
title: Text("Receipts are disabled"),
|
||||
message: Text("This group has over \(SMALL_GROUPS_RCPS_MEM_LIMIT) members, delivery receipts are not sent.")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct GroupPreferencesButton: View {
|
||||
@Binding var groupInfo: GroupInfo
|
||||
@State var preferences: FullGroupPreferences
|
||||
@State var currentPreferences: FullGroupPreferences
|
||||
var creatingGroup: Bool = false
|
||||
|
||||
private var label: LocalizedStringKey {
|
||||
groupInfo.businessChat == nil ? "Group preferences" : "Chat preferences"
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationLink {
|
||||
GroupPreferencesView(
|
||||
groupInfo: $groupInfo,
|
||||
preferences: $preferences,
|
||||
currentPreferences: currentPreferences,
|
||||
creatingGroup: creatingGroup,
|
||||
savePreferences: savePreferences
|
||||
)
|
||||
.navigationBarTitle(label)
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
.onDisappear {
|
||||
let saveText = NSLocalizedString(
|
||||
creatingGroup ? "Save" : "Save and notify group members",
|
||||
comment: "alert button"
|
||||
)
|
||||
|
||||
if groupInfo.fullGroupPreferences != preferences {
|
||||
showAlert(
|
||||
title: NSLocalizedString("Save preferences?", comment: "alert title"),
|
||||
buttonTitle: saveText,
|
||||
buttonAction: { savePreferences() },
|
||||
cancelButton: true
|
||||
)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
if creatingGroup {
|
||||
Text("Set group preferences")
|
||||
} else {
|
||||
Label(label, systemImage: "switch.2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func savePreferences() {
|
||||
Task {
|
||||
do {
|
||||
var gp = groupInfo.groupProfile
|
||||
gp.groupPreferences = toGroupPreferences(preferences)
|
||||
let gInfo = try await apiUpdateGroup(groupInfo.groupId, gp)
|
||||
await MainActor.run {
|
||||
groupInfo = gInfo
|
||||
ChatModel.shared.updateGroup(gInfo)
|
||||
currentPreferences = preferences
|
||||
}
|
||||
} catch {
|
||||
logger.error("GroupPreferencesView apiUpdateGroup error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
18415C6C56DBCEC2CBBD2F11 /* WebRTCClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18415323A4082FC92887F906 /* WebRTCClient.swift */; };
|
||||
18415F9A2D551F9757DA4654 /* CIVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18415FD2E36F13F596A45BB4 /* CIVideoView.swift */; };
|
||||
18415FEFE153C5920BFB7828 /* GroupWelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1841516F0CE5992B0EDFB377 /* GroupWelcomeView.swift */; };
|
||||
33AC96122F15068300C672B9 /* GroupSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33AC96112F15068200C672B9 /* GroupSettingsView.swift */; };
|
||||
3CDBCF4227FAE51000354CDD /* ComposeLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDBCF4127FAE51000354CDD /* ComposeLinkView.swift */; };
|
||||
3CDBCF4827FF621E00354CDD /* CILinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDBCF4727FF621E00354CDD /* CILinkView.swift */; };
|
||||
5C00168128C4FE760094D739 /* KeyChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00168028C4FE760094D739 /* KeyChain.swift */; };
|
||||
@@ -335,6 +336,7 @@
|
||||
18415B08031E8FB0F7FC27F9 /* CallViewRenderers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallViewRenderers.swift; sourceTree = "<group>"; };
|
||||
18415DAAAD1ADBEDB0EDA852 /* VideoPlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = "<group>"; };
|
||||
18415FD2E36F13F596A45BB4 /* CIVideoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CIVideoView.swift; sourceTree = "<group>"; };
|
||||
33AC96112F15068200C672B9 /* GroupSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupSettingsView.swift; sourceTree = "<group>"; };
|
||||
3CDBCF4127FAE51000354CDD /* ComposeLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeLinkView.swift; sourceTree = "<group>"; };
|
||||
3CDBCF4727FF621E00354CDD /* CILinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CILinkView.swift; sourceTree = "<group>"; };
|
||||
5C00168028C4FE760094D739 /* KeyChain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyChain.swift; sourceTree = "<group>"; };
|
||||
@@ -1132,6 +1134,7 @@
|
||||
6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */,
|
||||
6440CA02288AECA70062C672 /* AddGroupMembersView.swift */,
|
||||
6442E0BD2880182D00CEC0F9 /* GroupChatInfoView.swift */,
|
||||
33AC96112F15068200C672B9 /* GroupSettingsView.swift */,
|
||||
647F090D288EA27B00644C40 /* GroupMemberInfoView.swift */,
|
||||
5C9C2DA42894777E00CC63B1 /* GroupProfileView.swift */,
|
||||
6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */,
|
||||
@@ -1565,6 +1568,7 @@
|
||||
5C6BA667289BD954009B8ECC /* DismissSheets.swift in Sources */,
|
||||
5C577F7D27C83AA10006112D /* MarkdownHelp.swift in Sources */,
|
||||
6407BA83295DA85D0082BA18 /* CIInvalidJSONView.swift in Sources */,
|
||||
33AC96122F15068300C672B9 /* GroupSettingsView.swift in Sources */,
|
||||
8CAD466F2D15A8100078D18F /* ChatScrollHelpers.swift in Sources */,
|
||||
644EFFDE292BCD9D00525D5B /* ComposeVoiceView.swift in Sources */,
|
||||
5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */,
|
||||
|
||||
Reference in New Issue
Block a user