2022-07-26 10:55:58 +04:00
//
// A d d G r o u p M e m b e r s 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 2 2 . 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 AddGroupMembersView : View {
@ EnvironmentObject var chatModel : ChatModel
2022-07-30 13:03:44 +01:00
@ Environment ( \ . dismiss ) var dismiss : DismissAction
2022-07-26 10:55:58 +04:00
var chat : Chat
2022-07-27 11:16:07 +04:00
var groupInfo : GroupInfo
2022-07-30 18:46:10 +01:00
var showSkip : Bool = false
var addedMembersCb : ( ( Set < Int64 > ) -> Void ) ? = nil
2022-07-26 10:55:58 +04:00
@ State private var selectedContacts = Set < Int64 > ( )
2022-07-27 11:16:07 +04:00
@ State private var selectedRole : GroupMemberRole = . admin
2022-08-23 18:18:12 +04:00
@ State private var alert : AddGroupMembersAlert ?
private enum AddGroupMembersAlert : Identifiable {
case prohibitedToInviteIncognito
case warnUnsafeToInviteIncognito
case error ( title : LocalizedStringKey , error : String = " " )
var id : String {
switch self {
case . prohibitedToInviteIncognito : return " prohibitedToInviteIncognito "
case . warnUnsafeToInviteIncognito : return " warnUnsafeToInviteIncognito "
case let . error ( title , _ ) : return " error \( title ) "
}
}
}
2022-07-26 10:55:58 +04:00
var body : some View {
2022-07-27 11:16:07 +04:00
NavigationView {
2022-08-09 13:43:19 +04:00
let membersToAdd = filterMembersToAdd ( chatModel . groupMembers )
2022-08-23 18:18:12 +04:00
let nonIncognitoConnectionsSelected = membersToAdd
. filter { selectedContacts . contains ( $0 . apiId ) }
. contains ( where : { ! $0 . contactConnIncognito } )
let unsafeToInviteIncognito = chat . chatInfo . incognito && nonIncognitoConnectionsSelected
2022-08-09 13:43:19 +04:00
2022-07-30 18:46:10 +01:00
let v = List {
2022-07-27 11:16:07 +04:00
ChatInfoToolbar ( chat : chat , imageSize : 48 )
. frame ( maxWidth : . infinity , alignment : . center )
. listRowBackground ( Color . clear )
. listRowSeparator ( . hidden )
2022-07-30 13:03:44 +01:00
if ( membersToAdd . isEmpty ) {
2022-07-27 11:16:07 +04:00
Text ( " No contacts to add " )
. foregroundColor ( . secondary )
. padding ( )
. frame ( maxWidth : . infinity , alignment : . center )
. listRowBackground ( Color . clear )
} else {
2022-07-26 10:55:58 +04:00
let count = selectedContacts . count
2022-07-27 11:16:07 +04:00
Section {
rolePicker ( )
2022-08-23 18:18:12 +04:00
inviteMembersButton ( unsafeToInviteIncognito )
2022-07-27 11:16:07 +04:00
. disabled ( count < 1 )
} footer : {
if ( count >= 1 ) {
HStack {
Button { selectedContacts . removeAll ( ) } label : { Text ( " Clear " ) }
Spacer ( )
Text ( " \( count ) contact(s) selected " )
2022-07-26 10:55:58 +04:00
}
2022-07-27 11:16:07 +04:00
} else {
Text ( " No contacts selected " )
. frame ( maxWidth : . infinity , alignment : . trailing )
2022-07-26 10:55:58 +04:00
}
}
2022-07-27 11:16:07 +04:00
Section {
2022-07-30 13:03:44 +01:00
ForEach ( membersToAdd ) { contact in
2022-07-27 11:16:07 +04:00
contactCheckView ( contact )
2022-07-26 10:55:58 +04:00
}
}
}
}
2022-07-30 18:46:10 +01:00
if ( showSkip ) {
v . toolbar {
ToolbarItem ( placement : . navigationBarTrailing ) {
if showSkip {
Button ( " Skip " ) {
if let cb = addedMembersCb { cb ( selectedContacts ) }
}
}
}
}
} else {
v . navigationBarHidden ( true )
}
2022-07-26 10:55:58 +04:00
}
. frame ( maxHeight : . infinity , alignment : . top )
2022-08-23 18:18:12 +04:00
. alert ( item : $ alert ) { alert in
switch alert {
case . prohibitedToInviteIncognito :
return Alert (
title : Text ( " Can't invite contact! " ) ,
message : Text ( " You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile " )
)
case . warnUnsafeToInviteIncognito :
return Alert (
title : Text ( " Your main profile may be shared " ) ,
message : Text ( " Some selected contacts have your main profile. If they use SimpleX app older than v3.2 or some other client, they may share your main profile instead of a random incognito profile with other members. " ) ,
primaryButton : . destructive ( Text ( " Invite anyway " ) ) {
inviteMembers ( )
} , secondaryButton : . cancel ( )
)
case let . error ( title , error ) :
return Alert ( title : Text ( title ) , message : Text ( " \( error ) " ) )
}
}
2022-07-26 10:55:58 +04:00
}
2022-08-23 18:18:12 +04:00
private func inviteMembersButton ( _ unsafeToInviteIncognito : Bool ) -> some View {
2022-07-27 11:16:07 +04:00
Button {
2022-08-23 18:18:12 +04:00
if unsafeToInviteIncognito {
alert = . warnUnsafeToInviteIncognito
} else {
inviteMembers ( )
2022-07-27 11:16:07 +04:00
}
} label : {
HStack {
Text ( " Invite to group " )
Image ( systemName : " checkmark " )
}
}
. frame ( maxWidth : . infinity , alignment : . trailing )
}
2022-08-23 18:18:12 +04:00
private func inviteMembers ( ) {
Task {
do {
for contactId in selectedContacts {
let member = try await apiAddMember ( groupInfo . groupId , contactId , selectedRole )
await MainActor . run { _ = ChatModel . shared . upsertGroupMember ( groupInfo , member ) }
}
await MainActor . run { dismiss ( ) }
if let cb = addedMembersCb { cb ( selectedContacts ) }
} catch {
alert = . error ( title : " Error adding member(s) " , error : responseError ( error ) )
}
}
}
private func rolePicker ( ) -> some View {
2022-07-27 11:16:07 +04:00
Picker ( " New member role " , selection : $ selectedRole ) {
ForEach ( GroupMemberRole . allCases ) { role in
if role <= groupInfo . membership . memberRole {
Text ( role . text )
}
}
}
}
2022-08-23 18:18:12 +04:00
private func contactCheckView ( _ contact : Contact ) -> some View {
2022-07-26 10:55:58 +04:00
let checked = selectedContacts . contains ( contact . apiId )
2022-08-23 18:18:12 +04:00
let prohibitedToInviteIncognito = ! chat . chatInfo . incognito && contact . contactConnIncognito
let safeToInviteIncognito = chat . chatInfo . incognito && contact . contactConnIncognito
var icon : String
var iconColor : Color
if prohibitedToInviteIncognito {
icon = " theatermasks.circle.fill "
iconColor = Color ( uiColor : . tertiaryLabel )
} else {
2022-07-26 10:55:58 +04:00
if checked {
2022-08-23 18:18:12 +04:00
icon = " checkmark.circle.fill "
iconColor = . accentColor
2022-07-26 10:55:58 +04:00
} else {
2022-08-23 18:18:12 +04:00
icon = " circle "
iconColor = Color ( uiColor : . tertiaryLabel )
}
}
return Button {
if prohibitedToInviteIncognito {
alert = . prohibitedToInviteIncognito
} else {
if checked {
selectedContacts . remove ( contact . apiId )
} else {
selectedContacts . insert ( contact . apiId )
}
2022-07-26 10:55:58 +04:00
}
} label : {
HStack {
ProfileImage ( imageStr : contact . image )
. frame ( width : 30 , height : 30 )
. padding ( . trailing , 2 )
Text ( ChatInfo . direct ( contact : contact ) . chatViewName )
2022-08-23 18:18:12 +04:00
. foregroundColor ( prohibitedToInviteIncognito ? . secondary : . primary )
2022-07-26 10:55:58 +04:00
. lineLimit ( 1 )
Spacer ( )
2022-08-23 18:18:12 +04:00
if safeToInviteIncognito {
Image ( systemName : " theatermasks " )
. foregroundColor ( . indigo )
. font ( . footnote )
}
Image ( systemName : icon )
. foregroundColor ( iconColor )
2022-07-26 10:55:58 +04:00
}
}
}
}
struct AddGroupMembersView_Previews : PreviewProvider {
static var previews : some View {
2022-08-09 13:43:19 +04:00
AddGroupMembersView ( chat : Chat ( chatInfo : ChatInfo . sampleData . group ) , groupInfo : GroupInfo . sampleData )
2022-07-26 10:55:58 +04:00
}
}