2022-01-31 21:28:07 +00:00
//
// C h a t I t e m 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 3 0 / 0 1 / 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-01-31 21:28:07 +00:00
2024-08-25 21:21:24 +03:00
extension EnvironmentValues {
struct ShowTimestamp : EnvironmentKey {
static let defaultValue : Bool = true
}
2024-10-15 10:58:54 +03:00
struct Revealed : EnvironmentKey {
static let defaultValue : Bool = true
}
2025-05-11 14:15:14 +01:00
struct ContainerBackground : EnvironmentKey {
static let defaultValue : UIColor = . clear
}
2024-08-25 21:21:24 +03:00
var showTimestamp : Bool {
get { self [ ShowTimestamp . self ] }
set { self [ ShowTimestamp . self ] = newValue }
}
2024-10-15 10:58:54 +03:00
var revealed : Bool {
get { self [ Revealed . self ] }
set { self [ Revealed . self ] = newValue }
}
2025-05-11 14:15:14 +01:00
var containerBackground : UIColor {
get { self [ ContainerBackground . self ] }
set { self [ ContainerBackground . self ] = newValue }
}
2024-08-25 21:21:24 +03:00
}
2022-01-31 21:28:07 +00:00
struct ChatItemView : View {
2023-10-31 09:44:57 +00:00
@ ObservedObject var chat : Chat
2024-07-03 22:42:13 +01:00
@ EnvironmentObject var theme : AppTheme
2024-08-25 21:21:24 +03:00
@ Environment ( \ . showTimestamp ) var showTimestamp : Bool
2024-10-15 10:58:54 +03:00
@ Environment ( \ . revealed ) var revealed : Bool
2022-01-31 21:28:07 +00:00
var chatItem : ChatItem
2025-02-18 01:21:40 +07:00
var scrollToItemId : ( ChatItem . ID ) -> Void
2022-04-19 12:29:03 +04:00
var maxWidth : CGFloat = . infinity
2023-05-14 20:07:34 +03:00
@ Binding var allowMenu : Bool
2024-07-24 00:11:42 +07:00
2023-10-31 09:44:57 +00:00
init (
chat : Chat ,
chatItem : ChatItem ,
2025-02-18 01:21:40 +07:00
scrollToItemId : @ escaping ( ChatItem . ID ) -> Void ,
2023-10-31 09:44:57 +00:00
showMember : Bool = false ,
maxWidth : CGFloat = . infinity ,
2024-07-24 00:11:42 +07:00
allowMenu : Binding < Bool > = . constant ( false )
2023-10-31 09:44:57 +00:00
) {
self . chat = chat
2023-05-14 20:07:34 +03:00
self . chatItem = chatItem
2025-02-18 01:21:40 +07:00
self . scrollToItemId = scrollToItemId
2023-05-14 20:07:34 +03:00
self . maxWidth = maxWidth
_allowMenu = allowMenu
}
2022-12-03 15:40:31 +04:00
var body : some View {
let ci = chatItem
2023-10-31 09:44:57 +00:00
if chatItem . meta . itemDeleted != nil && ( ! revealed || chatItem . isDeletedContent ) {
2024-10-15 10:58:54 +03:00
MarkedDeletedItemView ( chat : chat , chatItem : chatItem )
2024-04-12 12:56:09 +04:00
} else if ci . quotedItem = = nil && ci . meta . itemForwarded = = nil && ci . meta . itemDeleted = = nil && ! ci . meta . isLive {
2022-12-03 15:40:31 +04:00
if let mc = ci . content . msgContent , mc . isText && isShortEmoji ( ci . content . text ) {
2023-10-31 09:44:57 +00:00
EmojiItemView ( chat : chat , chatItem : ci )
2022-12-03 15:40:31 +04:00
} else if ci . content . text . isEmpty , case let . voice ( _ , duration ) = ci . content . msgContent {
2024-07-24 00:11:42 +07:00
CIVoiceView ( chat : chat , chatItem : ci , recordingFile : ci . file , duration : duration , allowMenu : $ allowMenu )
2022-12-03 15:40:31 +04:00
} else if ci . content . msgContent = = nil {
2024-10-15 10:58:54 +03:00
ChatItemContentView ( chat : chat , chatItem : chatItem , msgContentView : { Text ( ci . text ) } ) // m s g C o n t e n t i s u n r e a c h a b l e b r a n c h i n t h i s c a s e
2022-12-03 15:40:31 +04:00
} else {
framedItemView ( )
}
} else {
framedItemView ( )
}
}
private func framedItemView ( ) -> some View {
2024-07-03 10:24:26 +01:00
let preview = chatItem . content . msgContent
. flatMap {
switch $0 {
case let . image ( _ , image ) : image
case let . video ( _ , image , _ ) : image
default : nil
}
}
2024-09-04 14:49:01 +01:00
. flatMap { imageFromBase64 ( $0 ) }
2024-07-03 10:24:26 +01:00
let adjustedMaxWidth = {
if let preview , preview . size . width <= preview . size . height {
maxWidth * 0.75
} else {
maxWidth
}
} ( )
return FramedItemView (
chat : chat ,
chatItem : chatItem ,
2025-02-18 01:21:40 +07:00
scrollToItemId : scrollToItemId ,
2024-07-03 10:24:26 +01:00
preview : preview ,
maxWidth : maxWidth ,
imgWidth : adjustedMaxWidth ,
videoWidth : adjustedMaxWidth ,
2024-07-24 00:11:42 +07:00
allowMenu : $ allowMenu
2024-07-03 10:24:26 +01:00
)
2022-12-03 15:40:31 +04:00
}
}
struct ChatItemContentView < Content : View > : View {
2023-08-14 17:34:22 +04:00
@ EnvironmentObject var chatModel : ChatModel
2024-07-03 22:42:13 +01:00
@ EnvironmentObject var theme : AppTheme
2024-10-15 10:58:54 +03:00
@ Environment ( \ . revealed ) var revealed : Bool
2023-10-31 09:44:57 +00:00
@ ObservedObject var chat : Chat
2022-12-03 15:40:31 +04:00
var chatItem : ChatItem
var msgContentView : ( ) -> Content
2023-09-25 16:38:48 +04:00
@ AppStorage ( DEFAULT_DEVELOPER_TOOLS ) private var developerTools = false
2022-01-31 21:28:07 +00:00
var body : some View {
2022-05-21 12:13:37 +01:00
switch chatItem . content {
2022-12-03 15:40:31 +04:00
case . sndMsgContent : msgContentView ( )
case . rcvMsgContent : msgContentView ( )
2022-05-21 12:13:37 +01:00
case . sndDeleted : deletedItemView ( )
case . rcvDeleted : deletedItemView ( )
case let . sndCall ( status , duration ) : callItemView ( status , duration )
case let . rcvCall ( status , duration ) : callItemView ( status , duration )
2023-09-25 16:38:48 +04:00
case let . rcvIntegrityError ( msgError ) :
if developerTools {
2023-10-31 09:44:57 +00:00
IntegrityErrorItemView ( chat : chat , msgError : msgError , chatItem : chatItem )
2023-09-25 16:38:48 +04:00
} else {
ZStack { }
}
2023-10-31 09:44:57 +00:00
case let . rcvDecryptionError ( msgDecryptError , msgCount ) : CIRcvDecryptionError ( chat : chat , msgDecryptError : msgDecryptError , msgCount : msgCount , chatItem : chatItem )
2022-07-18 21:58:32 +04:00
case let . rcvGroupInvitation ( groupInvitation , memberRole ) : groupInvitationItemView ( groupInvitation , memberRole )
case let . sndGroupInvitation ( groupInvitation , memberRole ) : groupInvitationItemView ( groupInvitation , memberRole )
2023-09-27 20:07:32 +04:00
case . rcvDirectEvent : eventItemView ( )
2023-09-20 12:26:16 +04:00
case . rcvGroupEvent ( . memberCreatedContact ) : CIMemberCreatedContactView ( chatItem : chatItem )
2022-11-01 20:30:53 +00:00
case . rcvGroupEvent : eventItemView ( )
case . sndGroupEvent : eventItemView ( )
case . rcvConnEvent : eventItemView ( )
case . sndConnEvent : eventItemView ( )
2024-07-03 22:42:13 +01:00
case let . rcvChatFeature ( feature , enabled , _ ) : chatFeatureView ( feature , enabled . iconColor ( theme . colors . secondary ) )
case let . sndChatFeature ( feature , enabled , _ ) : chatFeatureView ( feature , enabled . iconColor ( theme . colors . secondary ) )
2022-12-22 21:01:29 +00:00
case let . rcvChatPreference ( feature , allowed , param ) :
2023-10-31 09:44:57 +00:00
CIFeaturePreferenceView ( chat : chat , chatItem : chatItem , feature : feature , allowed : allowed , param : param )
2022-12-22 21:01:29 +00:00
case let . sndChatPreference ( feature , _ , _ ) :
2024-10-15 10:58:54 +03:00
CIChatFeatureView ( chat : chat , chatItem : chatItem , feature : feature , icon : feature . icon , iconColor : theme . colors . secondary )
2024-07-03 22:42:13 +01:00
case let . rcvGroupFeature ( feature , preference , _ , role ) : chatFeatureView ( feature , preference . enabled ( role , for : chat . chatInfo . groupInfo ? . membership ) . iconColor ( theme . colors . secondary ) )
case let . sndGroupFeature ( feature , preference , _ , role ) : chatFeatureView ( feature , preference . enabled ( role , for : chat . chatInfo . groupInfo ? . membership ) . iconColor ( theme . colors . secondary ) )
2022-11-23 11:04:08 +00:00
case let . rcvChatFeatureRejected ( feature ) : chatFeatureView ( feature , . red )
2022-11-29 15:19:20 +00:00
case let . rcvGroupFeatureRejected ( feature ) : chatFeatureView ( feature , . red )
2023-02-09 15:10:35 +04:00
case . sndModerated : deletedItemView ( )
case . rcvModerated : deletedItemView ( )
2024-01-20 14:33:36 +04:00
case . rcvBlocked : deletedItemView ( )
2024-03-11 10:36:36 +00:00
case let . sndDirectE2EEInfo ( e2eeInfo ) : CIEventView ( eventText : directE2EEInfoText ( e2eeInfo ) )
case let . rcvDirectE2EEInfo ( e2eeInfo ) : CIEventView ( eventText : directE2EEInfoText ( e2eeInfo ) )
case . sndGroupE2EEInfo : CIEventView ( eventText : e2eeInfoNoPQText ( ) )
case . rcvGroupE2EEInfo : CIEventView ( eventText : e2eeInfoNoPQText ( ) )
2024-03-11 16:36:59 +04:00
case let . invalidJSON ( json ) : CIInvalidJSONView ( json : json )
2022-05-21 12:13:37 +01:00
}
}
private func deletedItemView ( ) -> some View {
2023-10-31 09:44:57 +00:00
DeletedItemView ( chat : chat , chatItem : chatItem )
2022-05-21 12:13:37 +01:00
}
private func callItemView ( _ status : CICallStatus , _ duration : Int ) -> some View {
2023-10-31 09:44:57 +00:00
CICallItemView ( chat : chat , chatItem : chatItem , status : status , duration : duration )
2022-05-21 12:13:37 +01:00
}
2022-07-18 21:58:32 +04:00
private func groupInvitationItemView ( _ groupInvitation : CIGroupInvitation , _ memberRole : GroupMemberRole ) -> some View {
2024-04-25 12:41:20 +04:00
CIGroupInvitationView ( chat : chat , chatItem : chatItem , groupInvitation : groupInvitation , memberRole : memberRole , chatIncognito : chat . chatInfo . incognito )
2022-07-18 21:58:32 +04:00
}
2022-07-21 17:01:13 +04:00
2022-11-01 20:30:53 +00:00
private func eventItemView ( ) -> some View {
2024-07-03 22:42:13 +01:00
CIEventView ( eventText : eventItemViewText ( theme . colors . secondary ) )
2023-08-14 17:34:22 +04:00
}
2024-07-03 22:42:13 +01:00
private func eventItemViewText ( _ secondaryColor : Color ) -> Text {
2023-10-31 09:44:57 +00:00
if ! revealed , let t = mergedGroupEventText {
2024-11-27 19:01:16 +00:00
return chatEventText ( t + textSpace + chatItem . timestampText , secondaryColor )
2023-10-31 09:44:57 +00:00
} else if let member = chatItem . memberDisplayName {
2023-08-15 13:02:23 +01:00
return Text ( member + " " )
2023-08-14 17:34:22 +04:00
. font ( . caption )
2024-07-03 22:42:13 +01:00
. foregroundColor ( secondaryColor )
2023-08-14 17:34:22 +04:00
. fontWeight ( . light )
2024-07-03 22:42:13 +01:00
+ chatEventText ( chatItem , secondaryColor )
2023-08-14 17:34:22 +04:00
} else {
2024-07-03 22:42:13 +01:00
return chatEventText ( chatItem , secondaryColor )
2023-08-14 17:34:22 +04:00
}
2022-07-21 17:01:13 +04:00
}
2022-11-22 12:50:56 +00:00
2022-12-21 22:36:05 +00:00
private func chatFeatureView ( _ feature : Feature , _ iconColor : Color ) -> some View {
2024-10-15 10:58:54 +03:00
CIChatFeatureView ( chat : chat , chatItem : chatItem , feature : feature , iconColor : iconColor )
2022-11-22 12:50:56 +00:00
}
2023-08-14 17:34:22 +04:00
2023-10-31 09:44:57 +00:00
private var mergedGroupEventText : Text ? {
let ( count , ns ) = chatModel . getConnectedMemberNames ( chatItem )
let members : LocalizedStringKey =
switch ns . count {
case 1 : " \( ns [ 0 ] ) connected "
case 2 : " \( ns [ 0 ] ) and \( ns [ 1 ] ) connected "
case 3 : " \( ns [ 0 ] + " , " + ns [ 1 ] ) and \( ns [ 2 ] ) connected "
default :
ns . count > 3
? " \( ns [ 0 ] ) , \( ns [ 1 ] ) and \( ns . count - 2 ) other members connected "
: " "
}
return if count <= 1 {
nil
} else if ns . count = = 0 {
Text ( " \( count ) group events " )
} else if count > ns . count {
2024-11-27 19:01:16 +00:00
Text ( members ) + textSpace + Text ( " and \( count - ns . count ) other events " )
2023-08-14 17:34:22 +04:00
} else {
2023-10-31 09:44:57 +00:00
Text ( members )
2023-08-14 17:34:22 +04:00
}
}
2024-03-11 10:36:36 +00:00
private func directE2EEInfoText ( _ info : E2EEInfo ) -> Text {
info . pqEnabled
2024-03-11 16:36:59 +04:00
? Text ( " Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. " )
2024-03-11 10:36:36 +00:00
. font ( . caption )
2024-07-03 22:42:13 +01:00
. foregroundColor ( theme . colors . secondary )
2024-03-11 10:36:36 +00:00
. fontWeight ( . light )
: e2eeInfoNoPQText ( )
}
private func e2eeInfoNoPQText ( ) -> Text {
Text ( " Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. " )
. font ( . caption )
2024-07-03 22:42:13 +01:00
. foregroundColor ( theme . colors . secondary )
2024-03-11 10:36:36 +00:00
. fontWeight ( . light )
}
2023-08-14 17:34:22 +04:00
}
2024-07-03 22:42:13 +01:00
func chatEventText ( _ text : Text , _ secondaryColor : Color ) -> Text {
2023-10-31 09:44:57 +00:00
text
2023-08-14 17:34:22 +04:00
. font ( . caption )
2024-07-03 22:42:13 +01:00
. foregroundColor ( secondaryColor )
2023-08-14 17:34:22 +04:00
. fontWeight ( . light )
}
2024-07-03 22:42:13 +01:00
func chatEventText ( _ eventText : LocalizedStringKey , _ ts : Text , _ secondaryColor : Color ) -> Text {
2024-11-27 19:01:16 +00:00
chatEventText ( Text ( eventText ) + textSpace + ts , secondaryColor )
2023-10-31 09:44:57 +00:00
}
2024-07-03 22:42:13 +01:00
func chatEventText ( _ ci : ChatItem , _ secondaryColor : Color ) -> Text {
chatEventText ( " \( ci . content . text ) " , ci . timestampText , secondaryColor )
2022-01-31 21:28:07 +00:00
}
struct ChatItemView_Previews : PreviewProvider {
static var previews : some View {
Group {
2025-02-18 01:21:40 +07:00
ChatItemView ( chat : Chat . sampleData , chatItem : ChatItem . getSample ( 1 , . directSnd , . now , " hello " ) , scrollToItemId : { _ in } )
ChatItemView ( chat : Chat . sampleData , chatItem : ChatItem . getSample ( 2 , . directRcv , . now , " hello there too " ) , scrollToItemId : { _ in } )
ChatItemView ( chat : Chat . sampleData , chatItem : ChatItem . getSample ( 1 , . directSnd , . now , " 🙂 " ) , scrollToItemId : { _ in } )
ChatItemView ( chat : Chat . sampleData , chatItem : ChatItem . getSample ( 2 , . directRcv , . now , " 🙂🙂🙂🙂🙂 " ) , scrollToItemId : { _ in } )
ChatItemView ( chat : Chat . sampleData , chatItem : ChatItem . getSample ( 2 , . directRcv , . now , " 🙂🙂🙂🙂🙂🙂 " ) , scrollToItemId : { _ in } )
ChatItemView ( chat : Chat . sampleData , chatItem : ChatItem . getDeletedContentSample ( ) , scrollToItemId : { _ in } )
ChatItemView ( chat : Chat . sampleData , chatItem : ChatItem . getSample ( 1 , . directSnd , . now , " hello " , . sndSent ( sndProgress : . complete ) , itemDeleted : . deleted ( deletedTs : . now ) ) , scrollToItemId : { _ in } )
ChatItemView ( chat : Chat . sampleData , chatItem : ChatItem . getSample ( 1 , . directSnd , . now , " 🙂 " , . sndSent ( sndProgress : . complete ) , itemLive : true ) , scrollToItemId : { _ in } ) . environment ( \ . revealed , true )
ChatItemView ( chat : Chat . sampleData , chatItem : ChatItem . getSample ( 1 , . directSnd , . now , " hello " , . sndSent ( sndProgress : . complete ) , itemLive : true ) , scrollToItemId : { _ in } ) . environment ( \ . revealed , true )
2022-12-03 15:40:31 +04:00
}
2024-10-15 10:58:54 +03:00
. environment ( \ . revealed , false )
2022-12-03 15:40:31 +04:00
. previewLayout ( . fixed ( width : 360 , height : 70 ) )
2022-12-21 12:59:45 +00:00
. environmentObject ( Chat . sampleData )
2022-12-03 15:40:31 +04:00
}
}
struct ChatItemView_NonMsgContentDeleted_Previews : PreviewProvider {
static var previews : some View {
2022-12-21 12:59:45 +00:00
let ciFeatureContent = CIContent . rcvChatFeature ( feature : . fullDelete , enabled : FeatureEnabled ( forUser : false , forContact : false ) , param : nil )
2022-12-03 15:40:31 +04:00
Group {
ChatItemView (
2023-10-31 09:44:57 +00:00
chat : Chat . sampleData ,
2022-12-03 15:40:31 +04:00
chatItem : ChatItem (
chatDir : . directRcv ,
2023-05-19 18:50:48 +02:00
meta : CIMeta . getSample ( 1 , . now , " 1 skipped message " , . rcvRead , itemDeleted : . deleted ( deletedTs : . now ) ) ,
2022-12-03 15:40:31 +04:00
content : . rcvIntegrityError ( msgError : . msgSkipped ( fromMsgId : 1 , toMsgId : 2 ) ) ,
quotedItem : nil ,
file : nil
2025-02-18 01:21:40 +07:00
) ,
scrollToItemId : { _ in }
2022-12-03 15:40:31 +04:00
)
2023-04-16 12:35:45 +02:00
ChatItemView (
2023-10-31 09:44:57 +00:00
chat : Chat . sampleData ,
2023-04-16 12:35:45 +02:00
chatItem : ChatItem (
chatDir : . directRcv ,
meta : CIMeta . getSample ( 1 , . now , " 1 skipped message " , . rcvRead ) ,
content : . rcvDecryptionError ( msgDecryptError : . ratchetHeader , msgCount : 2 ) ,
quotedItem : nil ,
file : nil
2025-02-18 01:21:40 +07:00
) ,
scrollToItemId : { _ in }
2023-04-16 12:35:45 +02:00
)
2022-12-03 15:40:31 +04:00
ChatItemView (
2023-10-31 09:44:57 +00:00
chat : Chat . sampleData ,
2022-12-03 15:40:31 +04:00
chatItem : ChatItem (
chatDir : . directRcv ,
2023-05-19 18:50:48 +02:00
meta : CIMeta . getSample ( 1 , . now , " received invitation to join group team as admin " , . rcvRead , itemDeleted : . deleted ( deletedTs : . now ) ) ,
2022-12-03 15:40:31 +04:00
content : . rcvGroupInvitation ( groupInvitation : CIGroupInvitation . getSample ( status : . pending ) , memberRole : . admin ) ,
quotedItem : nil ,
file : nil
2025-02-18 01:21:40 +07:00
) ,
scrollToItemId : { _ in }
2022-12-03 15:40:31 +04:00
)
ChatItemView (
2023-10-31 09:44:57 +00:00
chat : Chat . sampleData ,
2022-12-03 15:40:31 +04:00
chatItem : ChatItem (
chatDir : . directRcv ,
2023-05-19 18:50:48 +02:00
meta : CIMeta . getSample ( 1 , . now , " group event text " , . rcvRead , itemDeleted : . deleted ( deletedTs : . now ) ) ,
2022-12-03 15:40:31 +04:00
content : . rcvGroupEvent ( rcvGroupEvent : . memberAdded ( groupMemberId : 1 , profile : Profile . sampleData ) ) ,
quotedItem : nil ,
file : nil
2025-02-18 01:21:40 +07:00
) ,
scrollToItemId : { _ in }
2022-12-03 15:40:31 +04:00
)
ChatItemView (
2023-10-31 09:44:57 +00:00
chat : Chat . sampleData ,
2022-12-03 15:40:31 +04:00
chatItem : ChatItem (
chatDir : . directRcv ,
2023-05-19 18:50:48 +02:00
meta : CIMeta . getSample ( 1 , . now , ciFeatureContent . text , . rcvRead , itemDeleted : . deleted ( deletedTs : . now ) ) ,
2022-12-03 15:40:31 +04:00
content : ciFeatureContent ,
quotedItem : nil ,
file : nil
2025-02-18 01:21:40 +07:00
) ,
scrollToItemId : { _ in }
2022-12-03 15:40:31 +04:00
)
2022-01-31 21:28:07 +00:00
}
2024-10-15 10:58:54 +03:00
. environment ( \ . revealed , true )
2022-02-04 22:13:52 +00:00
. previewLayout ( . fixed ( width : 360 , height : 70 ) )
2022-12-21 12:59:45 +00:00
. environmentObject ( Chat . sampleData )
2022-01-31 21:28:07 +00:00
}
}