2022-02-05 20:10:47 +00:00
//
// C h a t I n f o V i e w . s w i f t
// S i m p l e X
//
// C r e a t e d b y E v g e n y P o b e r e z k i n o n 0 5 / 0 2 / 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
2022-05-31 07:55:13 +01:00
import SimpleXChat
2022-02-05 20:10:47 +00:00
2022-07-27 13:40:26 +04:00
func infoRow ( _ title : LocalizedStringKey , _ value : String ) -> some View {
HStack {
Text ( title )
Spacer ( )
Text ( value )
. foregroundStyle ( . secondary )
}
}
func localizedInfoRow ( _ title : LocalizedStringKey , _ value : LocalizedStringKey ) -> some View {
HStack {
Text ( title )
Spacer ( )
Text ( value )
. foregroundStyle ( . secondary )
}
}
@ ViewBuilder func smpServers ( _ title : LocalizedStringKey , _ servers : [ String ] ? ) -> some View {
if let servers = servers ,
servers . count > 0 {
2022-11-01 20:30:53 +00:00
HStack {
Text ( title ) . frame ( width : 120 , alignment : . leading )
Button ( serverHost ( servers [ 0 ] ) ) {
UIPasteboard . general . string = servers . joined ( separator : " ; " )
}
. foregroundColor ( . secondary )
. lineLimit ( 1 )
}
2022-07-27 13:40:26 +04:00
}
}
private func serverHost ( _ s : String ) -> String {
if let i = s . range ( of : " @ " ) ? . lowerBound {
return String ( s [ i . . . ] . dropFirst ( ) )
} else {
return s
}
}
2022-02-05 20:10:47 +00:00
struct ChatInfoView : View {
2022-02-07 10:36:11 +00:00
@ EnvironmentObject var chatModel : ChatModel
2022-07-30 13:03:44 +01:00
@ Environment ( \ . dismiss ) var dismiss : DismissAction
2022-02-05 20:10:47 +00:00
@ ObservedObject var chat : Chat
2022-11-16 20:26:43 +04:00
@ State var contact : Contact
2022-11-01 20:30:53 +00:00
@ Binding var connectionStats : ConnectionStats ?
2022-08-23 18:18:12 +04:00
var customUserProfile : Profile ?
2022-08-25 17:36:26 +04:00
@ State var localAlias : String
@ FocusState private var aliasTextFieldFocused : Bool
2022-07-27 13:40:26 +04:00
@ State private var alert : ChatInfoViewAlert ? = nil
2022-08-02 17:00:12 +04:00
@ AppStorage ( DEFAULT_DEVELOPER_TOOLS ) private var developerTools = false
2022-02-05 20:10:47 +00:00
2022-05-19 16:56:34 +04:00
enum ChatInfoViewAlert : Identifiable {
case deleteContactAlert
case clearChatAlert
2022-07-27 13:40:26 +04:00
case networkStatusAlert
2022-11-02 09:48:20 +00:00
case switchAddressAlert
case error ( title : LocalizedStringKey , error : LocalizedStringKey = " " )
2022-05-19 16:56:34 +04:00
2022-09-21 17:18:48 +04:00
var id : String {
switch self {
case . deleteContactAlert : return " deleteContactAlert "
case . clearChatAlert : return " clearChatAlert "
case . networkStatusAlert : return " networkStatusAlert "
2022-11-02 09:48:20 +00:00
case . switchAddressAlert : return " switchAddressAlert "
case let . error ( title , _ ) : return " error \( title ) "
2022-09-21 17:18:48 +04:00
}
}
2022-05-19 16:56:34 +04:00
}
2022-02-05 20:10:47 +00:00
var body : some View {
2022-07-27 13:40:26 +04:00
NavigationView {
List {
contactInfoHeader ( )
. listRowBackground ( Color . clear )
2022-08-25 17:36:26 +04:00
. contentShape ( Rectangle ( ) )
. onTapGesture {
aliasTextFieldFocused = false
}
localAliasTextEdit ( )
. listRowBackground ( Color . clear )
. listRowSeparator ( . hidden )
2022-02-05 20:10:47 +00:00
2022-08-23 18:18:12 +04:00
if let customUserProfile = customUserProfile {
Section ( " Incognito " ) {
infoRow ( " Your random profile " , customUserProfile . chatViewName )
}
}
2022-11-14 10:12:17 +00:00
Section ( " Preferences " ) {
NavigationLink {
2022-11-16 20:26:43 +04:00
ContactPreferencesView (
contact : $ contact ,
featuresAllowed : contactUserPrefsToFeaturesAllowed ( contact . mergedPreferences ) ,
currentFeaturesAllowed : contactUserPrefsToFeaturesAllowed ( contact . mergedPreferences )
)
. navigationBarTitle ( " Contact preferences " )
. navigationBarTitleDisplayMode ( . large )
2022-11-14 10:12:17 +00:00
} label : {
2022-11-17 06:57:27 +00:00
settingsRow ( " switch.2 " ) {
Text ( " Contact preferences " )
}
2022-11-14 10:12:17 +00:00
}
}
2022-11-01 20:30:53 +00:00
Section ( " Servers " ) {
networkStatusRow ( )
. onTapGesture {
alert = . networkStatusAlert
}
2022-11-02 09:48:20 +00:00
if developerTools {
2022-11-02 20:37:14 +04:00
Button ( " Change receiving address (BETA) " ) {
2022-11-02 09:48:20 +00:00
alert = . switchAddressAlert
}
2022-11-01 20:30:53 +00:00
}
if let connStats = connectionStats {
2022-07-27 13:40:26 +04:00
smpServers ( " Receiving via " , connStats . rcvServers )
smpServers ( " Sending via " , connStats . sndServers )
}
}
2022-02-07 10:36:11 +00:00
2022-07-27 13:40:26 +04:00
Section {
clearChatButton ( )
deleteContactButton ( )
}
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-27 13:40:26 +04:00
}
2022-07-14 16:40:32 +04:00
}
2022-07-27 13:40:26 +04:00
. navigationBarHidden ( true )
2022-07-14 16:40:32 +04:00
}
2022-07-27 13:40:26 +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 . deleteContactAlert : return deleteContactAlert ( )
2022-07-14 16:40:32 +04:00
case . clearChatAlert : return clearChatAlert ( )
2022-07-27 13:40:26 +04:00
case . networkStatusAlert : return networkStatusAlert ( )
2022-11-02 09:48:20 +00:00
case . switchAddressAlert : return switchAddressAlert ( switchContactAddress )
case let . error ( title , error ) : return mkAlert ( title : title , message : error )
2022-07-27 13:40:26 +04:00
}
}
}
func contactInfoHeader ( ) -> some View {
VStack {
let cInfo = chat . chatInfo
ChatInfoImage ( chat : chat , color : Color ( uiColor : . tertiarySystemFill ) )
. frame ( width : 192 , height : 192 )
. padding ( . top , 12 )
. padding ( )
2022-08-25 17:36:26 +04:00
Text ( contact . profile . displayName )
2022-07-27 13:40:26 +04:00
. font ( . largeTitle )
. lineLimit ( 1 )
. padding ( . bottom , 2 )
2022-08-25 17:36:26 +04:00
if cInfo . fullName != " " && cInfo . fullName != cInfo . displayName && cInfo . fullName != contact . profile . displayName {
2022-07-27 13:40:26 +04:00
Text ( cInfo . fullName )
. font ( . title2 )
. lineLimit ( 2 )
}
}
. frame ( maxWidth : . infinity , alignment : . center )
}
2022-08-25 17:36:26 +04:00
func localAliasTextEdit ( ) -> some View {
TextField ( " Set contact name… " , text : $ localAlias )
. disableAutocorrection ( true )
. focused ( $ aliasTextFieldFocused )
. submitLabel ( . done )
. onChange ( of : aliasTextFieldFocused ) { focused in
if ! focused {
setContactAlias ( )
}
}
. onSubmit {
setContactAlias ( )
}
. multilineTextAlignment ( . center )
. foregroundColor ( . secondary )
}
private func setContactAlias ( ) {
Task {
do {
if let contact = try await apiSetContactAlias ( contactId : chat . chatInfo . apiId , localAlias : localAlias ) {
await MainActor . run {
chatModel . updateContact ( contact )
}
}
} catch {
logger . error ( " setContactAlias error: \( responseError ( error ) ) " )
}
}
}
2022-07-27 13:40:26 +04:00
func networkStatusRow ( ) -> some View {
HStack {
Text ( " Network status " )
Image ( systemName : " info.circle " )
. foregroundColor ( . accentColor )
. font ( . system ( size : 14 ) )
Spacer ( )
Text ( chat . serverInfo . networkStatus . statusString )
. foregroundColor ( . secondary )
serverImage ( )
}
2022-02-05 20:10:47 +00:00
}
func serverImage ( ) -> some View {
let status = chat . serverInfo . networkStatus
return Image ( systemName : status . imageName )
. foregroundColor ( status = = . connected ? . green : . secondary )
2022-07-27 13:40:26 +04:00
. font ( . system ( size : 12 ) )
}
func deleteContactButton ( ) -> some View {
Button ( role : . destructive ) {
alert = . deleteContactAlert
} label : {
Label ( " Delete contact " , systemImage : " trash " )
. foregroundColor ( Color . red )
}
}
func clearChatButton ( ) -> some View {
Button ( ) {
alert = . clearChatAlert
} label : {
Label ( " Clear conversation " , systemImage : " gobackward " )
. foregroundColor ( Color . orange )
}
2022-02-05 20:10:47 +00:00
}
2022-02-07 10:36:11 +00:00
2022-07-26 12:33:10 +04:00
private func deleteContactAlert ( ) -> Alert {
2022-02-07 10:36:11 +00:00
Alert (
title : Text ( " Delete contact? " ) ,
2022-04-16 09:37:01 +01:00
message : Text ( " Contact and all messages will be deleted - this cannot be undone! " ) ,
2022-02-07 10:36:11 +00:00
primaryButton : . destructive ( Text ( " Delete " ) ) {
2022-02-24 17:16:41 +00:00
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-02-24 17:16:41 +00:00
}
} catch let error {
2022-11-02 09:48:20 +00:00
logger . error ( " deleteContactAlert apiDeleteChat error: \( responseError ( error ) ) " )
let a = getErrorAlert ( error , " Error deleting contact " )
await MainActor . run {
alert = . error ( title : a . title , error : a . message )
}
2022-02-24 17:16:41 +00:00
}
2022-02-07 10:36:11 +00:00
}
2022-02-12 15:59:43 +00:00
} ,
secondaryButton : . cancel ( )
2022-02-07 10:36:11 +00:00
)
}
2022-05-19 16:56:34 +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-05-19 16:56:34 +04:00
}
} ,
secondaryButton : . cancel ( )
)
}
2022-07-27 13:40:26 +04:00
private func networkStatusAlert ( ) -> Alert {
Alert (
title : Text ( " Network status " ) ,
message : Text ( chat . serverInfo . networkStatus . statusExplanation )
)
}
2022-11-02 09:48:20 +00:00
private func switchContactAddress ( ) {
Task {
do {
try await apiSwitchContact ( contactId : contact . apiId )
} catch let error {
logger . error ( " switchContactAddress apiSwitchContact error: \( responseError ( error ) ) " )
let a = getErrorAlert ( error , " Error changing address " )
await MainActor . run {
alert = . error ( title : a . title , error : a . message )
}
}
}
}
}
func switchAddressAlert ( _ switchAddress : @ escaping ( ) -> Void ) -> Alert {
Alert (
title : Text ( " Change receiving address? " ) ,
message : Text ( " This feature is experimental! It will only work if the other client has version 4.2 installed. You should see the message in the conversation once the address change is completed – please check that you can still receive messages from this contact (or group member). " ) ,
primaryButton : . destructive ( Text ( " Change " ) , action : switchAddress ) ,
secondaryButton : . cancel ( )
)
2022-02-05 20:10:47 +00:00
}
struct ChatInfoView_Previews : PreviewProvider {
static var previews : some View {
2022-11-01 20:30:53 +00:00
ChatInfoView (
chat : Chat ( chatInfo : ChatInfo . sampleData . direct , chatItems : [ ] ) ,
contact : Contact . sampleData ,
connectionStats : Binding . constant ( nil ) ,
localAlias : " "
)
2022-02-05 20:10:47 +00:00
}
}