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
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
2022-07-26 12:33:10 +04:00
@ State private var alert : GroupChatInfoViewAlert ? = nil
2022-10-15 18:09:25 +04:00
@ State private var groupLink : String ?
2023-03-06 13:54:43 +00:00
@ State private var groupLinkMemberRole : GroupMemberRole = . member
2022-07-26 12:33:10 +04:00
@ State private var showAddMembersSheet : 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 ?
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
2022-07-14 16:40:32 +04:00
2022-07-26 12:33:10 +04:00
var id : GroupChatInfoViewAlert { get { self } }
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
. filter { $0 . memberStatus != . memLeft && $0 . memberStatus != . memRemoved }
. sorted { $0 . displayName . lowercased ( ) < $1 . displayName . lowercased ( ) }
2022-07-26 12:33:10 +04:00
List {
groupInfoHeader ( )
. listRowBackground ( Color . clear )
2022-11-16 20:26:43 +04:00
Section {
2022-11-17 12:59:13 +04:00
if groupInfo . canEdit {
editGroupButton ( )
2022-11-16 20:26:43 +04:00
}
2023-05-03 15:57:10 +03:00
if groupInfo . groupProfile . description != nil || groupInfo . canEdit {
addOrEditWelcomeMessage ( )
}
2022-12-04 11:30:51 +00:00
groupPreferencesButton ( $ groupInfo )
2022-11-16 20:26:43 +04:00
} header : {
2022-11-17 12:59:13 +04:00
Text ( " " )
2022-11-16 20:26:43 +04:00
} footer : {
Text ( " Only group owners can change group preferences. " )
}
2022-07-30 13:03:44 +01:00
Section ( " \( members . count + 1 ) members " ) {
2022-07-30 16:49:34 +04:00
if groupInfo . canAddMembers {
2022-10-15 18:09:25 +04:00
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 ( )
}
2022-07-27 11:16:07 +04:00
}
2023-06-19 11:49:45 +01:00
if members . count > 8 {
searchFieldView ( text : $ searchText , focussed : $ searchFocussed )
. padding ( . leading , 8 )
}
let s = searchText . trimmingCharacters ( in : . whitespaces ) . localizedLowercase
let filteredMembers = s = = " " ? members : members . filter { $0 . chatViewName . localizedLowercase . contains ( s ) }
2022-07-27 11:16:07 +04:00
memberView ( groupInfo . membership , user : true )
2023-06-19 11:49:45 +01:00
ForEach ( filteredMembers ) { member in
2022-12-26 17:45:02 +04:00
ZStack {
NavigationLink {
memberInfoView ( member . groupMemberId )
} label : {
EmptyView ( )
}
. opacity ( 0 )
memberView ( member )
}
2022-07-26 12:33:10 +04:00
}
2022-07-27 11:16:07 +04:00
}
2022-07-26 12:33:10 +04:00
Section {
clearChatButton ( )
if groupInfo . canDelete {
deleteGroupButton ( )
}
2022-07-30 16:49:34 +04:00
if groupInfo . membership . memberCurrent {
2022-07-27 11:16:07 +04:00
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-26 12:33:10 +04:00
}
2022-07-14 16:40:32 +04:00
}
2022-07-26 12:33:10 +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 ) {
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 ( )
2022-10-15 18:09:25 +04:00
}
}
. onAppear {
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
}
}
2023-07-10 13:53:46 +01:00
. keyboardPadding ( )
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
2022-07-26 12:33:10 +04:00
ChatInfoImage ( chat : chat , color : Color ( uiColor : . tertiarySystemFill ) )
. frame ( width : 192 , height : 192 )
. padding ( . top , 12 )
. padding ( )
2022-07-27 11:16:07 +04:00
Text ( cInfo . displayName )
2022-07-26 12:33:10 +04:00
. font ( . largeTitle )
. lineLimit ( 1 )
. padding ( . bottom , 2 )
2022-07-27 11:16:07 +04:00
if cInfo . fullName != " " && cInfo . fullName != cInfo . displayName {
Text ( cInfo . fullName )
. font ( . title2 )
. lineLimit ( 2 )
}
2022-07-26 12:33:10 +04:00
}
. frame ( maxWidth : . infinity , alignment : . center )
}
private func addMembersButton ( ) -> some View {
2022-12-12 08:59:35 +00:00
NavigationLink {
AddGroupMembersView ( chat : chat , groupInfo : groupInfo )
. onAppear {
2023-06-19 11:49:45 +01:00
searchFocussed = false
2023-05-03 12:17:39 +04:00
Task {
let groupMembers = await apiListMembers ( groupInfo . groupId )
await MainActor . run {
ChatModel . shared . groupMembers = groupMembers
}
}
2022-08-09 13:43:19 +04:00
}
2022-07-26 12:33:10 +04:00
} label : {
Label ( " Invite members " , systemImage : " plus " )
}
2022-07-14 16:40:32 +04:00
}
2022-12-12 08:59:35 +00:00
private func memberView ( _ member : GroupMember , user : Bool = false ) -> some View {
2022-07-27 11:16:07 +04:00
HStack {
ProfileImage ( imageStr : member . image )
. frame ( width : 38 , height : 38 )
. 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 ) {
2022-12-12 08:59:35 +00:00
let t = Text ( member . chatViewName ) . foregroundColor ( member . memberIncognito ? . indigo : . primary )
( member . verified ? memberVerifiedShield + t : t )
2022-07-27 11:16:07 +04:00
. lineLimit ( 1 )
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 )
2022-07-26 12:33:10 +04:00
}
}
}
2022-12-12 08:59:35 +00:00
private var memberVerifiedShield : Text {
( Text ( Image ( systemName : " checkmark.shield " ) ) + Text ( " " ) )
. font ( . caption )
. baselineOffset ( 2 )
. kerning ( - 2 )
. foregroundColor ( . secondary )
}
@ ViewBuilder private func memberInfoView ( _ groupMemberId : Int64 ? ) -> some View {
if let mId = groupMemberId , let member = chatModel . groupMembers . first ( where : { $0 . groupMemberId = = mId } ) {
GroupMemberInfoView ( groupInfo : groupInfo , member : member )
. navigationBarHidden ( false )
}
}
2022-10-15 18:09:25 +04:00
private func groupLinkButton ( ) -> some View {
NavigationLink {
2023-03-06 13:54:43 +00:00
GroupLinkView ( groupId : groupInfo . groupId , groupLink : $ groupLink , groupLinkMemberRole : $ groupLinkMemberRole )
2022-11-17 12:59:13 +04:00
. navigationBarTitle ( " Group link " )
. navigationBarTitleDisplayMode ( . large )
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
}
}
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 " )
. 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 {
GroupWelcomeView ( groupId : groupInfo . groupId , groupInfo : $ groupInfo )
. navigationTitle ( " Welcome message " )
. 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
}
}
2022-12-12 08:59:35 +00:00
private func deleteGroupButton ( ) -> some View {
2022-07-26 12:33:10 +04:00
Button ( role : . destructive ) {
alert = . deleteGroupAlert
} label : {
Label ( " Delete group " , systemImage : " trash " )
. 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 )
}
}
2022-12-12 08:59:35 +00:00
private func leaveGroupButton ( ) -> some View {
2022-07-26 12:33:10 +04:00
Button ( role : . destructive ) {
alert = . leaveGroupAlert
} label : {
Label ( " Leave group " , systemImage : " rectangle.portrait.and.arrow.right " )
. 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 {
2022-09-14 21:45:59 +04:00
return Alert (
2022-07-26 12:33:10 +04:00
title : Text ( " Delete group? " ) ,
2022-09-14 21:45:59 +04:00
message : deleteGroupAlertMessage ( ) ,
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 {
chatModel . removeChat ( chat . chatInfo . id )
2022-08-29 14:08:46 +01:00
chatModel . chatId = nil
2022-07-30 13:03:44 +01:00
dismiss ( )
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 ( )
)
}
2022-09-14 21:45:59 +04:00
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 ( ) }
2022-07-26 12:33:10 +04:00
}
} ,
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-12-04 11:30:51 +00:00
func groupPreferencesButton ( _ groupInfo : Binding < GroupInfo > , _ creatingGroup : Bool = false ) -> some View {
NavigationLink {
GroupPreferencesView (
groupInfo : groupInfo ,
preferences : groupInfo . wrappedValue . fullGroupPreferences ,
currentPreferences : groupInfo . wrappedValue . fullGroupPreferences ,
creatingGroup : creatingGroup
)
. navigationBarTitle ( " Group preferences " )
. navigationBarTitleDisplayMode ( . large )
} label : {
if creatingGroup {
Text ( " Set group preferences " )
} else {
Label ( " Group preferences " , systemImage : " switch.2 " )
}
}
}
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
}
}