2022-07-14 16:40:32 +04:00
//
// G r o u p C h a t I n f o V i e w . s w i f t
// S i m p l e X ( i O S )
//
// C r e a t e d b y J R o b e r t s o n 1 4 . 0 7 . 2 0 2 2 .
// C o p y r i g h t © 2 0 2 2 S i m p l e X C h a t . A l l r i g h t s r e s e r v e d .
//
import SwiftUI
import SimpleXChat
2023-07-28 13:16:52 +04:00
let SMALL_GROUPS_RCPS_MEM_LIMIT : Int = 20
2022-07-14 16:40:32 +04:00
struct GroupChatInfoView : View {
@ EnvironmentObject var chatModel : ChatModel
2024-07-03 22:42:13 +01:00
@ EnvironmentObject var theme : AppTheme
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
2023-10-31 09:44:57 +00:00
@ Binding var groupInfo : GroupInfo
2024-08-05 12:58:24 +01:00
var onSearch : ( ) -> Void
2025-01-20 18:06:00 +00:00
@ State var localAlias : String
@ FocusState private var aliasTextFieldFocused : Bool
2022-07-26 12:33:10 +04:00
@ State private var alert : GroupChatInfoViewAlert ? = nil
2025-04-14 21:25:32 +01:00
@ State private var groupLink : CreatedConnLink ?
2023-03-06 13:54:43 +00:00
@ State private var groupLinkMemberRole : GroupMemberRole = . member
2024-08-05 12:58:24 +01:00
@ State private var groupLinkNavLinkActive : Bool = false
@ State private var addMembersNavLinkActive : Bool = false
2022-08-02 14:48:31 +04:00
@ State private var connectionStats : ConnectionStats ?
2022-12-12 08:59:35 +00:00
@ State private var connectionCode : String ?
2023-07-28 13:16:52 +04:00
@ State private var sendReceipts = SendReceipts . userDefault ( true )
@ State private var sendReceiptsUserDefault = true
2025-01-20 18:06:00 +00:00
@ State private var progressIndicator = false
2022-08-02 17:00:12 +04:00
@ AppStorage ( DEFAULT_DEVELOPER_TOOLS ) private var developerTools = false
2023-06-19 11:49:45 +01:00
@ State private var searchText : String = " "
@ FocusState private var searchFocussed
2022-07-14 16:40:32 +04:00
2022-07-26 12:33:10 +04:00
enum GroupChatInfoViewAlert : Identifiable {
case deleteGroupAlert
2022-07-14 16:40:32 +04:00
case clearChatAlert
2022-07-26 12:33:10 +04:00
case leaveGroupAlert
2022-08-29 14:47:29 +04:00
case cantInviteIncognitoAlert
2023-07-28 13:16:52 +04:00
case largeGroupReceiptsDisabled
2023-10-31 09:44:57 +00:00
case blockMemberAlert ( mem : GroupMember )
case unblockMemberAlert ( mem : GroupMember )
2024-01-20 14:33:36 +04:00
case blockForAllAlert ( mem : GroupMember )
case unblockForAllAlert ( mem : GroupMember )
2023-10-31 09:44:57 +00:00
case removeMemberAlert ( mem : GroupMember )
2024-07-28 17:54:58 +01:00
case error ( title : LocalizedStringKey , error : LocalizedStringKey ? )
2023-10-31 09:44:57 +00:00
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 ) "
2024-01-20 14:33:36 +04:00
case let . blockForAllAlert ( mem ) : return " blockForAllAlert \( mem . groupMemberId ) "
case let . unblockForAllAlert ( mem ) : return " unblockForAllAlert \( mem . groupMemberId ) "
2023-10-31 09:44:57 +00:00
case let . removeMemberAlert ( mem ) : return " removeMemberAlert \( mem . groupMemberId ) "
case let . error ( title , _ ) : return " error \( title ) "
}
}
2022-07-14 16:40:32 +04:00
}
var body : some View {
2022-07-26 12:33:10 +04:00
NavigationView {
2022-08-09 13:43:19 +04:00
let members = chatModel . groupMembers
2023-10-31 09:44:57 +00:00
. filter { m in let status = m . wrapped . memberStatus ; return status != . memLeft && status != . memRemoved }
2024-04-21 10:57:54 +01:00
. sorted { $0 . wrapped . memberRole > $1 . wrapped . memberRole }
2022-08-09 13:43:19 +04:00
2025-01-20 18:06:00 +00:00
ZStack {
List {
groupInfoHeader ( )
. listRowBackground ( Color . clear )
localAliasTextEdit ( )
. listRowBackground ( Color . clear )
. listRowSeparator ( . hidden )
. padding ( . bottom , 18 )
infoActionButtons ( )
. padding ( . horizontal )
. frame ( maxWidth : . infinity )
. frame ( height : infoViewActionButtonHeight )
. listRowBackground ( Color . clear )
. listRowSeparator ( . hidden )
. listRowInsets ( EdgeInsets ( top : 0 , leading : 0 , bottom : 0 , trailing : 0 ) )
Section {
if groupInfo . isOwner && groupInfo . businessChat = = nil {
editGroupButton ( )
2024-12-02 21:40:22 +04:00
}
2025-01-20 18:06:00 +00:00
if groupInfo . groupProfile . description != nil || ( groupInfo . isOwner && groupInfo . businessChat = = nil ) {
addOrEditWelcomeMessage ( )
}
GroupPreferencesButton ( groupInfo : $ groupInfo , preferences : groupInfo . fullGroupPreferences , currentPreferences : groupInfo . fullGroupPreferences )
if members . filter ( { $0 . wrapped . memberCurrent } ) . count <= SMALL_GROUPS_RCPS_MEM_LIMIT {
sendReceiptsOption ( )
2022-08-29 14:47:29 +04:00
} else {
2025-01-20 18:06:00 +00:00
sendReceiptsOptionDisabled ( )
}
NavigationLink {
ChatWallpaperEditorSheet ( chat : chat )
} label : {
Label ( " Chat theme " , systemImage : " photo " )
2022-08-29 14:47:29 +04:00
}
2025-01-20 18:06:00 +00:00
} header : {
Text ( " " )
} 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 )
2022-07-27 11:16:07 +04:00
}
2025-01-20 18:06:00 +00:00
Section {
ChatTTLOption ( chat : chat , progressIndicator : $ progressIndicator )
} footer : {
Text ( " Delete chat messages from your device. " )
2023-06-19 11:49:45 +01:00
}
2025-01-20 18:06:00 +00:00
Section ( header : Text ( " \( members . count + 1 ) members " ) . foregroundColor ( theme . colors . secondary ) ) {
if groupInfo . canAddMembers {
if groupInfo . businessChat = = nil {
groupLinkButton ( )
}
if ( chat . chatInfo . incognito ) {
Label ( " Invite members " , systemImage : " plus " )
. foregroundColor ( Color ( uiColor : . tertiaryLabel ) )
. onTapGesture { alert = . cantInviteIncognitoAlert }
} else {
addMembersButton ( )
}
}
2025-02-03 20:47:32 +00:00
searchFieldView ( text : $ searchText , focussed : $ searchFocussed , theme . colors . onBackground , theme . colors . secondary )
. padding ( . leading , 8 )
2025-01-20 18:06:00 +00:00
let s = searchText . trimmingCharacters ( in : . whitespaces ) . localizedLowercase
2025-02-03 20:47:32 +00:00
let filteredMembers = s = = " "
? members
: members . filter { $0 . wrapped . localAliasAndFullName . localizedLowercase . contains ( s ) }
2025-05-15 14:58:40 +01:00
MemberRowView ( chat : chat , groupInfo : groupInfo , groupMember : GMember ( groupInfo . membership ) , user : true , alert : $ alert )
2025-01-20 18:06:00 +00:00
ForEach ( filteredMembers ) { member in
2025-05-15 14:58:40 +01:00
MemberRowView ( chat : chat , groupInfo : groupInfo , groupMember : member , alert : $ alert )
2022-12-26 17:45:02 +04:00
}
2022-07-26 12:33:10 +04:00
}
2025-01-20 18:06:00 +00:00
Section {
clearChatButton ( )
if groupInfo . canDelete {
deleteGroupButton ( )
}
if groupInfo . membership . memberCurrent {
leaveGroupButton ( )
}
2022-07-26 12:33:10 +04:00
}
2025-01-20 18:06:00 +00:00
if developerTools {
Section ( header : Text ( " For console " ) . foregroundColor ( theme . colors . secondary ) ) {
infoRow ( " Local name " , chat . chatInfo . localDisplayName )
infoRow ( " Database ID " , " \( chat . chatInfo . apiId ) " )
}
2022-07-27 11:16:07 +04:00
}
}
2025-01-20 18:06:00 +00:00
. modifier ( ThemedBackground ( grouped : true ) )
. navigationBarHidden ( true )
. disabled ( progressIndicator )
. opacity ( progressIndicator ? 0.6 : 1 )
if progressIndicator {
ProgressView ( ) . scaleEffect ( 2 )
2022-07-26 12:33:10 +04:00
}
2022-07-14 16:40:32 +04:00
}
2022-07-26 12:33:10 +04:00
}
. frame ( maxWidth : . infinity , maxHeight : . infinity , alignment : . top )
2022-07-14 16:40:32 +04:00
. alert ( item : $ alert ) { alertItem in
switch ( alertItem ) {
2022-07-26 12:33:10 +04:00
case . deleteGroupAlert : return deleteGroupAlert ( )
2022-07-14 16:40:32 +04:00
case . clearChatAlert : return clearChatAlert ( )
2022-07-26 12:33:10 +04:00
case . leaveGroupAlert : return leaveGroupAlert ( )
2022-08-29 14:47:29 +04:00
case . cantInviteIncognitoAlert : return cantInviteIncognitoAlert ( )
2023-07-28 13:16:52 +04:00
case . largeGroupReceiptsDisabled : return largeGroupReceiptsDisabledAlert ( )
2023-10-31 09:44:57 +00:00
case let . blockMemberAlert ( mem ) : return blockMemberAlert ( groupInfo , mem )
case let . unblockMemberAlert ( mem ) : return unblockMemberAlert ( groupInfo , mem )
2024-01-20 14:33:36 +04:00
case let . blockForAllAlert ( mem ) : return blockForAllAlert ( groupInfo , mem )
case let . unblockForAllAlert ( mem ) : return unblockForAllAlert ( groupInfo , mem )
2023-10-31 09:44:57 +00:00
case let . removeMemberAlert ( mem ) : return removeMemberAlert ( mem )
2024-07-28 17:54:58 +01:00
case let . error ( title , error ) : return mkAlert ( title : title , message : error )
2022-10-15 18:09:25 +04:00
}
}
. onAppear {
2023-07-28 13:16:52 +04:00
if let currentUser = chatModel . currentUser {
sendReceiptsUserDefault = currentUser . sendRcptsSmallGroups
}
sendReceipts = SendReceipts . fromBool ( groupInfo . chatSettings . sendRcpts , userDefault : sendReceiptsUserDefault )
2022-10-15 18:09:25 +04:00
do {
2023-03-06 13:54:43 +00:00
if let link = try apiGetGroupLink ( groupInfo . groupId ) {
( groupLink , groupLinkMemberRole ) = link
}
2022-10-15 18:09:25 +04:00
} catch let error {
logger . error ( " GroupChatInfoView apiGetGroupLink: \( responseError ( error ) ) " )
2022-07-14 16:40:32 +04:00
}
}
2022-07-26 12:33:10 +04:00
}
2022-12-12 08:59:35 +00:00
private func groupInfoHeader ( ) -> some View {
2022-07-26 12:33:10 +04:00
VStack {
2022-07-27 11:16:07 +04:00
let cInfo = chat . chatInfo
2024-04-24 21:20:26 +01:00
ChatInfoImage ( chat : chat , size : 192 , color : Color ( uiColor : . tertiarySystemFill ) )
2022-07-26 12:33:10 +04:00
. padding ( . top , 12 )
. padding ( )
2025-01-20 18:06:00 +00:00
Text ( cInfo . groupInfo ? . groupProfile . displayName ? ? cInfo . displayName )
2022-07-26 12:33:10 +04:00
. font ( . largeTitle )
2023-07-19 15:16:50 +04:00
. multilineTextAlignment ( . center )
. lineLimit ( 4 )
2022-07-26 12:33:10 +04:00
. padding ( . bottom , 2 )
2022-07-27 11:16:07 +04:00
if cInfo . fullName != " " && cInfo . fullName != cInfo . displayName {
Text ( cInfo . fullName )
. font ( . title2 )
2023-07-19 15:16:50 +04:00
. multilineTextAlignment ( . center )
. lineLimit ( 8 )
2022-07-27 11:16:07 +04:00
}
2022-07-26 12:33:10 +04:00
}
. frame ( maxWidth : . infinity , alignment : . center )
}
2025-01-20 18:06:00 +00:00
private func localAliasTextEdit ( ) -> some View {
TextField ( " Set chat name… " , text : $ localAlias )
. disableAutocorrection ( true )
. focused ( $ aliasTextFieldFocused )
. submitLabel ( . done )
. onChange ( of : aliasTextFieldFocused ) { focused in
if ! focused {
setGroupAlias ( )
}
}
. onSubmit {
setGroupAlias ( )
}
. multilineTextAlignment ( . center )
. foregroundColor ( theme . colors . secondary )
}
private func setGroupAlias ( ) {
Task {
do {
if let gInfo = try await apiSetGroupAlias ( groupId : chat . chatInfo . apiId , localAlias : localAlias ) {
await MainActor . run {
chatModel . updateGroup ( gInfo )
}
}
} catch {
logger . error ( " setGroupAlias error: \( responseError ( error ) ) " )
}
}
}
2024-08-05 21:22:09 +04:00
func infoActionButtons ( ) -> some View {
2024-08-06 08:12:42 +01:00
GeometryReader { g in
let buttonWidth = g . size . width / 4
HStack ( alignment : . center , spacing : 8 ) {
searchButton ( width : buttonWidth )
if groupInfo . canAddMembers {
addMembersActionButton ( width : buttonWidth )
}
2025-02-03 20:47:32 +00:00
if let nextNtfMode = chat . chatInfo . nextNtfMode {
muteButton ( width : buttonWidth , nextNtfMode : nextNtfMode )
}
2024-08-05 21:22:09 +04:00
}
2024-08-06 08:12:42 +01:00
. frame ( maxWidth : . infinity , alignment : . center )
2024-08-05 21:22:09 +04:00
}
}
2024-08-06 08:12:42 +01:00
private func searchButton ( width : CGFloat ) -> some View {
InfoViewButton ( image : " magnifyingglass " , title : " search " , width : width ) {
dismiss ( )
onSearch ( )
}
. disabled ( ! groupInfo . ready || chat . chatItems . isEmpty )
2024-08-05 12:58:24 +01:00
}
2025-05-11 14:15:14 +01:00
private func addMembersActionButton ( width : CGFloat ) -> some View {
ZStack {
if chat . chatInfo . incognito {
2024-08-06 08:12:42 +01:00
InfoViewButton ( image : " link.badge.plus " , title : " invite " , width : width ) {
groupLinkNavLinkActive = true
}
2024-08-05 12:58:24 +01:00
NavigationLink ( isActive : $ groupLinkNavLinkActive ) {
groupLinkDestinationView ( )
} label : {
EmptyView ( )
2022-08-09 13:43:19 +04:00
}
2024-08-05 21:22:09 +04:00
. frame ( width : 1 , height : 1 )
2024-08-05 12:58:24 +01:00
. hidden ( )
2025-05-11 14:15:14 +01:00
} else {
2024-08-06 08:12:42 +01:00
InfoViewButton ( image : " person.fill.badge.plus " , title : " invite " , width : width ) {
addMembersNavLinkActive = true
}
2024-08-05 12:58:24 +01:00
NavigationLink ( isActive : $ addMembersNavLinkActive ) {
addMembersDestinationView ( )
} label : {
EmptyView ( )
}
2024-08-05 21:22:09 +04:00
. frame ( width : 1 , height : 1 )
2024-08-05 12:58:24 +01:00
. hidden ( )
}
}
2025-05-11 14:15:14 +01:00
. disabled ( ! groupInfo . ready )
2024-08-05 12:58:24 +01:00
}
2025-02-03 20:47:32 +00:00
private func muteButton ( width : CGFloat , nextNtfMode : MsgFilter ) -> some View {
return InfoViewButton (
image : nextNtfMode . iconFilled ,
title : " \( nextNtfMode . text ( mentions : true ) ) " ,
2024-08-06 08:12:42 +01:00
width : width
) {
2025-02-03 20:47:32 +00:00
toggleNotifications ( chat , enableNtfs : nextNtfMode )
2024-08-05 12:58:24 +01:00
}
. disabled ( ! groupInfo . ready )
}
private func addMembersButton ( ) -> some View {
2024-12-02 21:40:22 +04:00
let label : LocalizedStringKey = switch groupInfo . businessChat ? . chatType {
case . customer : " Add team members "
case . business : " Add friends "
case . none : " Invite members "
}
return NavigationLink {
2024-08-05 12:58:24 +01:00
addMembersDestinationView ( )
2022-07-26 12:33:10 +04:00
} label : {
2024-12-02 21:40:22 +04:00
Label ( label , systemImage : " plus " )
2022-07-26 12:33:10 +04:00
}
2022-07-14 16:40:32 +04:00
}
2024-08-05 12:58:24 +01:00
private func addMembersDestinationView ( ) -> some View {
AddGroupMembersView ( chat : chat , groupInfo : groupInfo )
. onAppear {
searchFocussed = false
Task {
2025-02-03 20:47:32 +00:00
await chatModel . loadGroupMembers ( groupInfo )
2024-08-05 12:58:24 +01:00
}
}
}
2023-10-31 09:44:57 +00:00
private struct MemberRowView : View {
2025-05-15 14:58:40 +01:00
var chat : Chat
2023-10-31 09:44:57 +00:00
var groupInfo : GroupInfo
@ ObservedObject var groupMember : GMember
2024-07-03 22:42:13 +01:00
@ EnvironmentObject var theme : AppTheme
2023-10-31 09:44:57 +00:00
var user : Bool = false
@ Binding var alert : GroupChatInfoViewAlert ?
var body : some View {
let member = groupMember . wrapped
2025-05-15 14:58:40 +01:00
let v1 = HStack {
2024-08-04 22:24:08 +01:00
MemberProfileImage ( member , size : 38 )
2023-10-31 09:44:57 +00:00
. padding ( . trailing , 2 )
// T O D O s e r v e r c o n n e c t i o n s t a t u s
VStack ( alignment : . leading ) {
2024-07-03 22:42:13 +01:00
let t = Text ( member . chatViewName ) . foregroundColor ( member . memberIncognito ? . indigo : theme . colors . onBackground )
2023-10-31 09:44:57 +00:00
( member . verified ? memberVerifiedShield + t : t )
. lineLimit ( 1 )
2024-07-10 00:52:33 +04:00
( user ? Text ( " you: " ) + Text ( member . memberStatus . shortText ) : Text ( memberConnStatus ( member ) ) )
2023-10-31 09:44:57 +00:00
. lineLimit ( 1 )
. font ( . caption )
2024-07-03 22:42:13 +01:00
. foregroundColor ( theme . colors . secondary )
2023-10-31 09:44:57 +00:00
}
Spacer ( )
2024-01-20 14:33:36 +04:00
memberInfo ( member )
2022-07-27 11:16:07 +04:00
}
2025-02-03 20:47:32 +00:00
2025-05-15 14:58:40 +01:00
let v = ZStack {
if user {
v1
} else {
NavigationLink {
memberInfoView ( )
} label : {
EmptyView ( )
}
. opacity ( 0 )
v1
}
}
2023-10-31 09:44:57 +00:00
if user {
v
2024-02-12 17:33:53 +04:00
} else if groupInfo . membership . memberRole >= . admin {
// T O D O i f t h e r e a r e m o r e a c t i o n s , r e f a c t o r w i t h l i s t s o f s w i p e A c t i o n s
let canBlockForAll = member . canBlockForAll ( groupInfo : groupInfo )
let canRemove = member . canBeRemoved ( groupInfo : groupInfo )
if canBlockForAll && canRemove {
removeSwipe ( member , blockForAllSwipe ( member , v ) )
} else if canBlockForAll {
blockForAllSwipe ( member , v )
} else if canRemove {
removeSwipe ( member , v )
} else {
v
}
2023-10-31 09:44:57 +00:00
} else {
2024-02-12 17:33:53 +04:00
if ! member . blockedByAdmin {
blockSwipe ( member , v )
} else {
v
}
2022-07-26 12:33:10 +04:00
}
2024-01-20 14:33:36 +04:00
}
2024-07-10 00:52:33 +04:00
2025-05-15 14:58:40 +01:00
private func memberInfoView ( ) -> some View {
GroupMemberInfoView ( groupInfo : groupInfo , chat : chat , groupMember : groupMember )
. navigationBarHidden ( false )
}
2024-07-10 00:52:33 +04:00
private func memberConnStatus ( _ member : GroupMember ) -> LocalizedStringKey {
if member . activeConn ? . connDisabled ? ? false {
return " disabled "
} else if member . activeConn ? . connInactive ? ? false {
return " inactive "
} else {
return member . memberStatus . shortText
}
}
2024-01-20 14:33:36 +04:00
@ ViewBuilder private func memberInfo ( _ member : GroupMember ) -> some View {
if member . blocked {
Text ( " blocked " )
2024-07-03 22:42:13 +01:00
. foregroundColor ( theme . colors . secondary )
2024-01-20 14:33:36 +04:00
} else {
let role = member . memberRole
if [ . owner , . admin , . observer ] . contains ( role ) {
Text ( member . memberRole . text )
2024-07-03 22:42:13 +01:00
. foregroundColor ( theme . colors . secondary )
2024-01-20 14:33:36 +04:00
}
}
2022-07-26 12:33:10 +04:00
}
2023-10-31 09:44:57 +00:00
private func blockSwipe < V : View > ( _ member : GroupMember , _ v : V ) -> some View {
v . swipeActions ( edge : . leading ) {
if member . memberSettings . showMessages {
Button {
alert = . blockMemberAlert ( mem : member )
} label : {
2024-07-03 22:42:13 +01:00
Label ( " Block member " , systemImage : " hand.raised " ) . foregroundColor ( theme . colors . secondary )
2023-10-31 09:44:57 +00:00
}
} else {
Button {
alert = . unblockMemberAlert ( mem : member )
} label : {
2024-07-03 22:42:13 +01:00
Label ( " Unblock member " , systemImage : " hand.raised.slash " ) . foregroundColor ( theme . colors . primary )
2023-10-31 09:44:57 +00:00
}
}
}
}
2022-12-12 08:59:35 +00:00
2024-01-20 14:33:36 +04:00
private func blockForAllSwipe < V : View > ( _ member : GroupMember , _ v : V ) -> some View {
v . swipeActions ( edge : . leading ) {
if member . blockedByAdmin {
Button {
alert = . unblockForAllAlert ( mem : member )
} label : {
2024-07-03 22:42:13 +01:00
Label ( " Unblock for all " , systemImage : " hand.raised.slash " ) . foregroundColor ( theme . colors . primary )
2024-01-20 14:33:36 +04:00
}
} else {
Button {
alert = . blockForAllAlert ( mem : member )
} label : {
2024-07-03 22:42:13 +01:00
Label ( " Block for all " , systemImage : " hand.raised " ) . foregroundColor ( theme . colors . secondary )
2024-01-20 14:33:36 +04:00
}
}
}
}
2023-10-31 09:44:57 +00:00
private func removeSwipe < V : View > ( _ member : GroupMember , _ v : V ) -> some View {
v . swipeActions ( edge : . trailing ) {
Button ( role : . destructive ) {
alert = . removeMemberAlert ( mem : member )
} label : {
Label ( " Remove member " , systemImage : " trash " )
. foregroundColor ( Color . red )
}
}
2022-12-12 08:59:35 +00:00
}
2024-07-03 22:42:13 +01:00
private var memberVerifiedShield : Text {
2024-11-27 19:01:16 +00:00
( Text ( Image ( systemName : " checkmark.shield " ) ) + textSpace )
2024-07-03 22:42:13 +01:00
. font ( . caption )
. baselineOffset ( 2 )
. kerning ( - 2 )
. foregroundColor ( theme . colors . secondary )
}
2022-12-12 08:59:35 +00:00
}
2023-10-31 09:44:57 +00:00
2022-10-15 18:09:25 +04:00
private func groupLinkButton ( ) -> some View {
NavigationLink {
2024-08-05 12:58:24 +01:00
groupLinkDestinationView ( )
2022-10-15 18:09:25 +04:00
} label : {
2022-12-13 17:15:45 +00:00
if groupLink = = nil {
Label ( " Create group link " , systemImage : " link.badge.plus " )
} else {
Label ( " Group link " , systemImage : " link " )
}
2022-10-15 18:09:25 +04:00
}
}
2024-08-05 12:58:24 +01:00
private func groupLinkDestinationView ( ) -> some View {
GroupLinkView (
groupId : groupInfo . groupId ,
groupLink : $ groupLink ,
groupLinkMemberRole : $ groupLinkMemberRole ,
showTitle : false ,
creatingGroup : false
)
. navigationBarTitle ( " Group link " )
. modifier ( ThemedBackground ( grouped : true ) )
. navigationBarTitleDisplayMode ( . large )
}
2022-12-12 08:59:35 +00:00
private func editGroupButton ( ) -> some View {
2022-11-17 12:59:13 +04:00
NavigationLink {
GroupProfileView (
groupInfo : $ groupInfo ,
groupProfile : groupInfo . groupProfile
)
. navigationBarTitle ( " Group profile " )
2024-07-03 22:42:13 +01:00
. modifier ( ThemedBackground ( ) )
2022-11-17 12:59:13 +04:00
. navigationBarTitleDisplayMode ( . large )
2022-07-30 18:46:10 +01:00
} label : {
Label ( " Edit group profile " , systemImage : " pencil " )
}
}
2023-03-21 18:15:48 +03:00
private func addOrEditWelcomeMessage ( ) -> some View {
NavigationLink {
2024-01-25 16:08:10 +04:00
GroupWelcomeView (
groupInfo : $ groupInfo ,
groupProfile : groupInfo . groupProfile ,
welcomeText : groupInfo . groupProfile . description ? ? " "
)
2023-03-21 18:15:48 +03:00
. navigationTitle ( " Welcome message " )
2024-07-03 22:42:13 +01:00
. modifier ( ThemedBackground ( grouped : true ) )
2023-03-21 18:15:48 +03:00
. navigationBarTitleDisplayMode ( . large )
} label : {
groupInfo . groupProfile . description = = nil
2023-03-22 17:45:55 +00:00
? Label ( " Add welcome message " , systemImage : " plus.message " )
: Label ( " Welcome message " , systemImage : " message " )
2023-03-21 18:15:48 +03:00
}
}
2024-12-03 19:25:15 +04:00
@ ViewBuilder private func deleteGroupButton ( ) -> some View {
let label : LocalizedStringKey = groupInfo . businessChat = = nil ? " Delete group " : " Delete chat "
2022-07-26 12:33:10 +04:00
Button ( role : . destructive ) {
alert = . deleteGroupAlert
} label : {
2024-12-03 19:25:15 +04:00
Label ( label , systemImage : " trash " )
2022-07-26 12:33:10 +04:00
. foregroundColor ( Color . red )
}
}
2022-12-12 08:59:35 +00:00
private func clearChatButton ( ) -> some View {
2022-07-26 12:33:10 +04:00
Button ( ) {
alert = . clearChatAlert
} label : {
Label ( " Clear conversation " , systemImage : " gobackward " )
. foregroundColor ( Color . orange )
}
}
2025-05-11 14:15:14 +01:00
private func leaveGroupButton ( ) -> some View {
2024-12-03 19:25:15 +04:00
let label : LocalizedStringKey = groupInfo . businessChat = = nil ? " Leave group " : " Leave chat "
2025-05-11 14:15:14 +01:00
return Button ( role : . destructive ) {
2022-07-26 12:33:10 +04:00
alert = . leaveGroupAlert
} label : {
2024-12-03 19:25:15 +04:00
Label ( label , systemImage : " rectangle.portrait.and.arrow.right " )
2022-07-26 12:33:10 +04:00
. foregroundColor ( Color . red )
}
}
// T O D O r e u s e t h i s a n d c l e a r C h a t A l e r t w i t h C h a t I n f o V i e w
private func deleteGroupAlert ( ) -> Alert {
2024-12-03 19:25:15 +04:00
let label : LocalizedStringKey = groupInfo . businessChat = = nil ? " Delete group? " : " Delete chat? "
2022-09-14 21:45:59 +04:00
return Alert (
2024-12-03 19:25:15 +04:00
title : Text ( label ) ,
message : deleteGroupAlertMessage ( groupInfo ) ,
2022-07-14 16:40:32 +04:00
primaryButton : . destructive ( Text ( " Delete " ) ) {
Task {
do {
2022-07-26 12:33:10 +04:00
try await apiDeleteChat ( type : chat . chatInfo . chatType , id : chat . chatInfo . apiId )
await MainActor . run {
2022-07-30 13:03:44 +01:00
dismiss ( )
2023-09-22 12:23:53 +04:00
chatModel . chatId = nil
chatModel . removeChat ( chat . chatInfo . id )
2022-07-14 16:40:32 +04:00
}
} catch let error {
2022-07-26 12:33:10 +04:00
logger . error ( " deleteGroupAlert apiDeleteChat error: \( error . localizedDescription ) " )
2022-07-14 16:40:32 +04:00
}
}
} ,
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 )
2022-07-30 13:03:44 +01:00
await MainActor . run { dismiss ( ) }
2022-07-26 12:33:10 +04:00
}
} ,
secondaryButton : . cancel ( )
)
}
private func leaveGroupAlert ( ) -> Alert {
2024-12-03 19:25:15 +04:00
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 ) ,
2022-07-26 12:33:10 +04:00
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 ( )
)
}
2023-07-28 13:16:52 +04:00
private func sendReceiptsOption ( ) -> some View {
Picker ( selection : $ sendReceipts ) {
ForEach ( [ . yes , . no , . userDefault ( sendReceiptsUserDefault ) ] ) { ( opt : SendReceipts ) in
Text ( opt . text )
}
} label : {
Label ( " Send receipts " , systemImage : " checkmark.message " )
}
. frame ( height : 36 )
. 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
}
}
2023-10-31 09:44:57 +00:00
private func removeMemberAlert ( _ mem : GroupMember ) -> Alert {
2024-12-03 19:25:15 +04:00
let messageLabel : LocalizedStringKey = (
groupInfo . businessChat = = nil
? " Member will be removed from group - this cannot be undone! "
: " Member will be removed from chat - this cannot be undone! "
)
return Alert (
2023-10-31 09:44:57 +00:00
title : Text ( " Remove member? " ) ,
2024-12-03 19:25:15 +04:00
message : Text ( messageLabel ) ,
2023-10-31 09:44:57 +00:00
primaryButton : . destructive ( Text ( " Remove " ) ) {
Task {
do {
2025-03-01 01:55:17 +07:00
let updatedMembers = try await apiRemoveMembers ( groupInfo . groupId , [ mem . groupMemberId ] )
2023-10-31 09:44:57 +00:00
await MainActor . run {
2025-03-01 01:55:17 +07:00
updatedMembers . forEach { updatedMember in
_ = chatModel . upsertGroupMember ( groupInfo , updatedMember )
}
2023-10-31 09:44:57 +00:00
}
} catch let error {
2025-03-01 01:55:17 +07:00
logger . error ( " apiRemoveMembers error: \( responseError ( error ) ) " )
2023-10-31 09:44:57 +00:00
let a = getErrorAlert ( error , " Error removing member " )
alert = . error ( title : a . title , error : a . message )
}
}
} ,
secondaryButton : . cancel ( )
)
}
2022-07-14 16:40:32 +04:00
}
2024-12-03 19:25:15 +04:00
func deleteGroupAlertMessage ( _ groupInfo : GroupInfo ) -> Text {
groupInfo . businessChat = = nil ? (
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! " )
) : (
groupInfo . membership . memberCurrent ? Text ( " Chat will be deleted for all members - this cannot be undone! " ) : Text ( " Chat will be deleted for you - this cannot be undone! " )
)
}
2024-12-06 15:55:15 +00:00
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 "
2024-12-06 10:21:58 +00:00
)
2024-12-06 15:55:15 +00:00
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 " )
2024-12-06 10:21:58 +00:00
}
}
2024-12-06 15:55:15 +00:00
}
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 ) ) " )
}
2022-12-04 11:30:51 +00:00
}
}
2024-12-06 15:55:15 +00:00
2022-12-04 11:30:51 +00:00
}
2024-12-06 15:55:15 +00:00
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 " )
)
}
2023-07-28 13:16:52 +04:00
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. " )
)
}
2022-07-14 16:40:32 +04:00
struct GroupChatInfoView_Previews : PreviewProvider {
static var previews : some View {
2023-10-31 09:44:57 +00:00
GroupChatInfoView (
chat : Chat ( chatInfo : ChatInfo . sampleData . group , chatItems : [ ] ) ,
2024-08-05 12:58:24 +01:00
groupInfo : Binding . constant ( GroupInfo . sampleData ) ,
2025-01-20 18:06:00 +00:00
onSearch : { } ,
localAlias : " "
2023-10-31 09:44:57 +00:00
)
2022-07-14 16:40:32 +04:00
}
}