2022-01-24 16:07:17 +00:00
//
// C h a t M o d e l . 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 2 2 / 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 Foundation
import Combine
2022-01-31 21:28:07 +00:00
import SwiftUI
2022-01-24 16:07:17 +00:00
final class ChatModel : ObservableObject {
@ Published var currentUser : User ?
2022-02-02 12:51:39 +00:00
// l i s t o f c h a t " p r e v i e w s "
@ Published var chats : [ Chat ] = [ ]
// c u r r e n t c h a t
@ Published var chatId : String ?
2022-01-29 11:10:04 +00:00
@ Published var chatItems : [ ChatItem ] = [ ]
2022-02-12 15:59:43 +00:00
@ Published var chatToTop : String ?
2022-02-02 12:51:39 +00:00
// i t e m s i n t h e t e r m i n a l v i e w
2022-01-29 23:37:02 +00:00
@ Published var terminalItems : [ TerminalItem ] = [ ]
2022-02-01 17:34:06 +00:00
@ Published var userAddress : String ?
2022-03-10 15:45:40 +04:00
@ Published var userSMPServers : [ String ] ?
2022-02-01 20:30:33 +00:00
@ Published var appOpenUrl : URL ?
2022-04-21 20:04:22 +01:00
@ Published var deviceToken : String ?
2022-02-28 10:44:48 +00:00
var messageDelivery : Dictionary < Int64 , ( ) -> Void > = [ : ]
2022-02-09 22:53:06 +00:00
static let shared = ChatModel ( )
2022-02-02 12:51:39 +00:00
func hasChat ( _ id : String ) -> Bool {
chats . first ( where : { $0 . id = = id } ) != nil
}
func getChat ( _ id : String ) -> Chat ? {
chats . first ( where : { $0 . id = = id } )
}
2022-02-05 20:10:47 +00:00
private func getChatIndex ( _ id : String ) -> Int ? {
chats . firstIndex ( where : { $0 . id = = id } )
}
2022-02-02 12:51:39 +00:00
func addChat ( _ chat : Chat ) {
2022-02-02 16:46:05 +00:00
withAnimation {
chats . insert ( chat , at : 0 )
}
2022-02-02 12:51:39 +00:00
}
func updateChatInfo ( _ cInfo : ChatInfo ) {
2022-02-12 15:59:43 +00:00
if let i = getChatIndex ( cInfo . id ) {
chats [ i ] . chatInfo = cInfo
2022-02-02 12:51:39 +00:00
}
}
2022-02-05 20:10:47 +00:00
func updateContact ( _ contact : Contact ) {
let cInfo = ChatInfo . direct ( contact : contact )
if hasChat ( contact . id ) {
updateChatInfo ( cInfo )
} else {
addChat ( Chat ( chatInfo : cInfo , chatItems : [ ] ) )
}
}
func updateNetworkStatus ( _ contact : Contact , _ status : Chat . NetworkStatus ) {
if let ix = getChatIndex ( contact . id ) {
chats [ ix ] . serverInfo . networkStatus = status
}
}
2022-02-02 12:51:39 +00:00
func replaceChat ( _ id : String , _ chat : Chat ) {
2022-02-12 15:59:43 +00:00
if let i = getChatIndex ( id ) {
chats [ i ] = chat
2022-02-02 12:51:39 +00:00
} else {
// i n v a l i d s t a t e , c o r r e c t i n g
chats . insert ( chat , at : 0 )
}
}
func addChatItem ( _ cInfo : ChatInfo , _ cItem : ChatItem ) {
2022-02-12 15:59:43 +00:00
// u p d a t e p r e v i e w s
if let i = getChatIndex ( cInfo . id ) {
chats [ i ] . chatItems = [ cItem ]
if case . rcvNew = cItem . meta . itemStatus {
chats [ i ] . chatStats . unreadCount = chats [ i ] . chatStats . unreadCount + 1
}
if i > 0 {
2022-02-05 14:24:23 +00:00
if chatId = = nil {
2022-02-12 15:59:43 +00:00
withAnimation { popChat_ ( i ) }
} else if chatId = = cInfo . id {
chatToTop = cInfo . id
2022-02-05 14:24:23 +00:00
} else {
2022-02-12 15:59:43 +00:00
popChat_ ( i )
2022-02-05 14:24:23 +00:00
}
2022-02-02 12:51:39 +00:00
}
2022-02-12 15:59:43 +00:00
} else {
addChat ( Chat ( chatInfo : cInfo , chatItems : [ cItem ] ) )
2022-02-02 12:51:39 +00:00
}
2022-02-12 15:59:43 +00:00
// a d d t o c u r r e n t c h a t
2022-02-02 12:51:39 +00:00
if chatId = = cInfo . id {
2022-02-03 07:16:29 +00:00
withAnimation { chatItems . append ( cItem ) }
2022-02-12 15:59:43 +00:00
if case . rcvNew = cItem . meta . itemStatus {
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 1 ) {
if self . chatId = = cInfo . id {
2022-02-24 17:16:41 +00:00
Task { await SimpleX . markChatItemRead ( cInfo , cItem ) }
2022-02-12 15:59:43 +00:00
}
}
}
}
}
func upsertChatItem ( _ cInfo : ChatInfo , _ cItem : ChatItem ) -> Bool {
// u p d a t e p r e v i e w s
var res : Bool
if let chat = getChat ( cInfo . id ) {
if let pItem = chat . chatItems . last , pItem . id = = cItem . id {
chat . chatItems = [ cItem ]
}
res = false
} else {
addChat ( Chat ( chatInfo : cInfo , chatItems : [ cItem ] ) )
res = true
}
// u p d a t e c u r r e n t c h a t
if chatId = = cInfo . id {
if let i = chatItems . firstIndex ( where : { $0 . id = = cItem . id } ) {
withAnimation ( . default ) {
self . chatItems [ i ] = cItem
}
return false
} else {
withAnimation { chatItems . append ( cItem ) }
return true
}
} else {
return res
}
}
2022-03-30 20:37:47 +04:00
func removeChatItem ( _ cInfo : ChatInfo , _ cItem : ChatItem ) {
// u p d a t e p r e v i e w s
if let chat = getChat ( cInfo . id ) {
if let pItem = chat . chatItems . last , pItem . id = = cItem . id {
chat . chatItems = [ cItem ]
}
}
// r e m o v e f r o m c u r r e n t c h a t
if chatId = = cInfo . id {
if let i = chatItems . firstIndex ( where : { $0 . id = = cItem . id } ) {
_ = withAnimation {
self . chatItems . remove ( at : i )
}
}
}
}
2022-02-12 15:59:43 +00:00
func markChatItemsRead ( _ cInfo : ChatInfo ) {
// u p d a t e p r e v i e w
if let chat = getChat ( cInfo . id ) {
chat . chatStats = ChatStats ( )
}
// u p d a t e c u r r e n t c h a t
if chatId = = cInfo . id {
var i = 0
while i < chatItems . count {
if case . rcvNew = chatItems [ i ] . meta . itemStatus {
chatItems [ i ] . meta . itemStatus = . rcvRead
}
i = i + 1
}
2022-02-02 12:51:39 +00:00
}
}
2022-02-12 15:59:43 +00:00
func markChatItemRead ( _ cInfo : ChatInfo , _ cItem : ChatItem ) {
// u p d a t e p r e v i e w
if let i = getChatIndex ( cInfo . id ) {
chats [ i ] . chatStats . unreadCount = chats [ i ] . chatStats . unreadCount - 1
}
// u p d a t e c u r r e n t c h a t
if chatId = = cInfo . id , let j = chatItems . firstIndex ( where : { $0 . id = = cItem . id } ) {
chatItems [ j ] . meta . itemStatus = . rcvRead
}
}
2022-03-30 08:57:42 +01:00
func getPrevChatItem ( _ ci : ChatItem ) -> ChatItem ? {
if let i = chatItems . firstIndex ( where : { $0 . id = = ci . id } ) , i > 0 {
return chatItems [ i - 1 ]
} else {
return nil
}
}
2022-02-12 15:59:43 +00:00
func popChat ( _ id : String ) {
if let i = getChatIndex ( id ) {
popChat_ ( i )
}
}
private func popChat_ ( _ i : Int ) {
let chat = chats . remove ( at : i )
2022-02-05 14:24:23 +00:00
chats . insert ( chat , at : 0 )
}
2022-02-02 12:51:39 +00:00
func removeChat ( _ id : String ) {
2022-02-02 16:46:05 +00:00
withAnimation {
chats . removeAll ( where : { $0 . id = = id } )
}
2022-02-02 12:51:39 +00:00
}
2022-01-24 16:07:17 +00:00
}
2022-02-11 07:42:00 +00:00
struct User : Decodable , NamedChat {
2022-01-24 16:07:17 +00:00
var userId : Int64
var userContactId : Int64
var localDisplayName : ContactName
var profile : Profile
var activeUser : Bool
2022-01-31 21:28:07 +00:00
2022-02-11 07:42:00 +00:00
var displayName : String { get { profile . displayName } }
var fullName : String { get { profile . fullName } }
2022-03-25 22:13:01 +04:00
var image : String ? { get { profile . image } }
2022-02-11 07:42:00 +00:00
2022-02-08 09:19:25 +00:00
static let sampleData = User (
userId : 1 ,
userContactId : 1 ,
localDisplayName : " alice " ,
profile : Profile . sampleData ,
activeUser : true
)
}
2022-01-29 23:37:02 +00:00
2022-01-24 16:07:17 +00:00
typealias ContactName = String
typealias GroupName = String
2022-02-09 22:53:06 +00:00
struct Profile : Codable , NamedChat {
2022-01-24 16:07:17 +00:00
var displayName : String
var fullName : String
2022-03-25 22:13:01 +04:00
var image : String ?
2022-01-24 16:07:17 +00:00
2022-02-08 09:19:25 +00:00
static let sampleData = Profile (
displayName : " alice " ,
fullName : " Alice "
)
}
2022-01-29 23:37:02 +00:00
enum ChatType : String {
2022-01-30 18:27:20 +00:00
case direct = " @ "
case group = " # "
2022-02-01 17:34:06 +00:00
case contactRequest = " <@ "
2022-01-29 23:37:02 +00:00
}
2022-02-09 22:53:06 +00:00
protocol NamedChat {
var displayName : String { get }
var fullName : String { get }
2022-03-25 22:13:01 +04:00
var image : String ? { get }
2022-02-09 22:53:06 +00:00
}
extension NamedChat {
var chatViewName : String {
get { displayName + ( fullName = = " " || fullName = = displayName ? " " : " / \( fullName ) " ) }
}
}
typealias ChatId = String
enum ChatInfo : Identifiable , Decodable , NamedChat {
2022-01-29 11:10:04 +00:00
case direct ( contact : Contact )
2022-01-30 00:35:20 +04:00
case group ( groupInfo : GroupInfo )
2022-02-01 17:34:06 +00:00
case contactRequest ( contactRequest : UserContactRequest )
2022-01-29 11:10:04 +00:00
2022-01-30 18:27:20 +00:00
var localDisplayName : String {
2022-01-29 11:10:04 +00:00
get {
switch self {
2022-02-04 22:13:52 +00:00
case let . direct ( contact ) : return contact . localDisplayName
case let . group ( groupInfo ) : return groupInfo . localDisplayName
case let . contactRequest ( contactRequest ) : return contactRequest . localDisplayName
2022-01-29 11:10:04 +00:00
}
}
}
2022-02-04 22:13:52 +00:00
2022-02-08 09:19:25 +00:00
var displayName : String {
get {
switch self {
2022-02-09 22:53:06 +00:00
case let . direct ( contact ) : return contact . displayName
case let . group ( groupInfo ) : return groupInfo . displayName
case let . contactRequest ( contactRequest ) : return contactRequest . displayName
2022-02-08 09:19:25 +00:00
}
}
}
2022-02-04 22:13:52 +00:00
var fullName : String {
get {
switch self {
2022-02-09 22:53:06 +00:00
case let . direct ( contact ) : return contact . fullName
case let . group ( groupInfo ) : return groupInfo . fullName
case let . contactRequest ( contactRequest ) : return contactRequest . fullName
2022-02-04 22:13:52 +00:00
}
}
}
2022-03-25 22:13:01 +04:00
var image : String ? {
get {
switch self {
case let . direct ( contact ) : return contact . image
case let . group ( groupInfo ) : return groupInfo . image
case let . contactRequest ( contactRequest ) : return contactRequest . image
}
}
}
2022-02-09 22:53:06 +00:00
var id : ChatId {
2022-01-29 11:10:04 +00:00
get {
switch self {
2022-02-01 17:34:06 +00:00
case let . direct ( contact ) : return contact . id
case let . group ( groupInfo ) : return groupInfo . id
case let . contactRequest ( contactRequest ) : return contactRequest . id
2022-01-29 11:10:04 +00:00
}
}
}
2022-01-29 23:37:02 +00:00
var chatType : ChatType {
2022-01-29 11:10:04 +00:00
get {
switch self {
2022-01-29 23:37:02 +00:00
case . direct : return . direct
case . group : return . group
2022-02-01 17:34:06 +00:00
case . contactRequest : return . contactRequest
2022-01-29 11:10:04 +00:00
}
}
}
var apiId : Int64 {
get {
switch self {
2022-02-02 12:51:39 +00:00
case let . direct ( contact ) : return contact . apiId
case let . group ( groupInfo ) : return groupInfo . apiId
case let . contactRequest ( contactRequest ) : return contactRequest . apiId
2022-01-29 11:10:04 +00:00
}
}
}
2022-02-04 22:13:52 +00:00
2022-02-12 15:59:43 +00:00
var ready : Bool {
get {
switch self {
case let . direct ( contact ) : return contact . ready
case let . group ( groupInfo ) : return groupInfo . ready
case let . contactRequest ( contactRequest ) : return contactRequest . ready
}
}
}
2022-02-04 22:13:52 +00:00
var createdAt : Date {
switch self {
case let . direct ( contact ) : return contact . createdAt
case let . group ( groupInfo ) : return groupInfo . createdAt
case let . contactRequest ( contactRequest ) : return contactRequest . createdAt
}
}
2022-01-29 23:37:02 +00:00
2022-02-08 09:19:25 +00:00
struct SampleData {
var direct : ChatInfo
var group : ChatInfo
var contactRequest : ChatInfo
}
2022-01-29 23:37:02 +00:00
2022-02-08 09:19:25 +00:00
static var sampleData : ChatInfo . SampleData = SampleData (
direct : ChatInfo . direct ( contact : Contact . sampleData ) ,
group : ChatInfo . group ( groupInfo : GroupInfo . sampleData ) ,
contactRequest : ChatInfo . contactRequest ( contactRequest : UserContactRequest . sampleData )
)
}
2022-02-01 17:34:06 +00:00
2022-02-02 12:51:39 +00:00
final class Chat : ObservableObject , Identifiable {
@ Published var chatInfo : ChatInfo
@ Published var chatItems : [ ChatItem ]
2022-02-12 15:59:43 +00:00
@ Published var chatStats : ChatStats
2022-02-05 20:10:47 +00:00
@ Published var serverInfo = ServerInfo ( networkStatus : . unknown )
struct ServerInfo : Decodable {
var networkStatus : NetworkStatus
}
enum NetworkStatus : Decodable , Equatable {
case unknown
case connected
case disconnected
case error ( String )
2022-04-16 09:37:01 +01:00
var statusString : LocalizedStringKey {
2022-02-05 20:10:47 +00:00
get {
switch self {
2022-02-07 10:36:11 +00:00
case . connected : return " Server connected "
case let . error ( err ) : return " Connecting server… (error: \( err ) ) "
default : return " Connecting server… "
}
}
}
2022-04-16 09:37:01 +01:00
var statusExplanation : LocalizedStringKey {
2022-02-07 10:36:11 +00:00
get {
switch self {
2022-03-14 20:58:19 +00:00
case . connected : return " You are connected to the server used to receive messages from this contact. "
case let . error ( err ) : return " Trying to connect to the server used to receive messages from this contact (error: \( err ) ). "
default : return " Trying to connect to the server used to receive messages from this contact. "
2022-02-05 20:10:47 +00:00
}
}
}
var imageName : String {
get {
switch self {
case . unknown : return " circle.dotted "
case . connected : return " circle.fill "
case . disconnected : return " ellipsis.circle.fill "
case . error : return " exclamationmark.circle.fill "
}
}
}
}
2022-02-02 12:51:39 +00:00
init ( _ cData : ChatData ) {
self . chatInfo = cData . chatInfo
self . chatItems = cData . chatItems
2022-02-12 15:59:43 +00:00
self . chatStats = cData . chatStats
2022-02-02 12:51:39 +00:00
}
2022-01-29 23:37:02 +00:00
2022-02-12 15:59:43 +00:00
init ( chatInfo : ChatInfo , chatItems : [ ChatItem ] = [ ] , chatStats : ChatStats = ChatStats ( ) ) {
2022-01-29 23:37:02 +00:00
self . chatInfo = chatInfo
self . chatItems = chatItems
2022-02-12 15:59:43 +00:00
self . chatStats = chatStats
2022-01-29 23:37:02 +00:00
}
2022-01-31 21:28:07 +00:00
2022-02-09 22:53:06 +00:00
var id : ChatId { get { chatInfo . id } }
2022-01-29 11:10:04 +00:00
}
2022-02-02 12:51:39 +00:00
struct ChatData : Decodable , Identifiable {
var chatInfo : ChatInfo
var chatItems : [ ChatItem ]
2022-02-12 15:59:43 +00:00
var chatStats : ChatStats
2022-02-02 12:51:39 +00:00
2022-02-09 22:53:06 +00:00
var id : ChatId { get { chatInfo . id } }
2022-02-02 12:51:39 +00:00
}
2022-02-12 15:59:43 +00:00
struct ChatStats : Decodable {
var unreadCount : Int = 0
var minUnreadItemId : Int64 = 0
}
2022-02-09 22:53:06 +00:00
struct Contact : Identifiable , Decodable , NamedChat {
2022-01-24 16:07:17 +00:00
var contactId : Int64
var localDisplayName : ContactName
var profile : Profile
2022-02-01 17:34:06 +00:00
var activeConn : Connection
2022-01-24 16:07:17 +00:00
var viaGroup : Int64 ?
2022-02-04 22:13:52 +00:00
var createdAt : Date
2022-02-05 20:10:47 +00:00
2022-02-09 22:53:06 +00:00
var id : ChatId { get { " @ \( contactId ) " } }
2022-02-02 12:51:39 +00:00
var apiId : Int64 { get { contactId } }
2022-04-22 17:26:17 +01:00
var ready : Bool { get { activeConn . connStatus = = " ready " } }
2022-02-09 22:53:06 +00:00
var displayName : String { get { profile . displayName } }
var fullName : String { get { profile . fullName } }
2022-03-25 22:13:01 +04:00
var image : String ? { get { profile . image } }
2022-01-24 16:07:17 +00:00
2022-02-08 09:19:25 +00:00
static let sampleData = Contact (
contactId : 1 ,
localDisplayName : " alice " ,
profile : Profile . sampleData ,
activeConn : Connection . sampleData ,
createdAt : . now
)
}
2022-02-01 17:34:06 +00:00
2022-02-25 20:26:56 +00:00
struct ContactSubStatus : Decodable {
var contact : Contact
var contactError : ChatError ?
}
2022-02-01 17:34:06 +00:00
struct Connection : Decodable {
var connStatus : String
2022-02-08 09:19:25 +00:00
static let sampleData = Connection ( connStatus : " ready " )
}
2022-02-01 17:34:06 +00:00
2022-02-09 22:53:06 +00:00
struct UserContactRequest : Decodable , NamedChat {
2022-02-01 17:34:06 +00:00
var contactRequestId : Int64
var localDisplayName : ContactName
var profile : Profile
2022-02-04 22:13:52 +00:00
var createdAt : Date
2022-02-01 17:34:06 +00:00
2022-02-09 22:53:06 +00:00
var id : ChatId { get { " <@ \( contactRequestId ) " } }
2022-02-02 12:51:39 +00:00
var apiId : Int64 { get { contactRequestId } }
2022-02-12 15:59:43 +00:00
var ready : Bool { get { true } }
2022-02-09 22:53:06 +00:00
var displayName : String { get { profile . displayName } }
var fullName : String { get { profile . fullName } }
2022-03-25 22:13:01 +04:00
var image : String ? { get { profile . image } }
2022-02-01 17:34:06 +00:00
2022-02-08 09:19:25 +00:00
static let sampleData = UserContactRequest (
contactRequestId : 1 ,
localDisplayName : " alice " ,
profile : Profile . sampleData ,
createdAt : . now
)
}
2022-01-29 23:37:02 +00:00
2022-02-09 22:53:06 +00:00
struct GroupInfo : Identifiable , Decodable , NamedChat {
2022-01-24 16:07:17 +00:00
var groupId : Int64
var localDisplayName : GroupName
var groupProfile : GroupProfile
2022-02-04 22:13:52 +00:00
var createdAt : Date
2022-01-29 11:10:04 +00:00
2022-02-09 22:53:06 +00:00
var id : ChatId { get { " # \( groupId ) " } }
2022-02-02 12:51:39 +00:00
var apiId : Int64 { get { groupId } }
2022-02-12 15:59:43 +00:00
var ready : Bool { get { true } }
2022-02-09 22:53:06 +00:00
var displayName : String { get { groupProfile . displayName } }
var fullName : String { get { groupProfile . fullName } }
2022-03-25 22:13:01 +04:00
var image : String ? { get { groupProfile . image } }
2022-01-24 16:07:17 +00:00
2022-02-08 09:19:25 +00:00
static let sampleData = GroupInfo (
groupId : 1 ,
localDisplayName : " team " ,
groupProfile : GroupProfile . sampleData ,
createdAt : . now
)
}
2022-01-29 23:37:02 +00:00
2022-02-09 22:53:06 +00:00
struct GroupProfile : Codable , NamedChat {
2022-01-24 16:07:17 +00:00
var displayName : String
var fullName : String
2022-03-25 22:13:01 +04:00
var image : String ?
2022-01-24 16:07:17 +00:00
2022-02-08 09:19:25 +00:00
static let sampleData = GroupProfile (
displayName : " team " ,
fullName : " My Team "
)
}
2022-01-29 23:37:02 +00:00
2022-02-01 17:34:06 +00:00
struct GroupMember : Decodable {
2022-02-08 09:19:25 +00:00
var groupMemberId : Int64
var memberId : String
// v a r m e m b e r R o l e : G r o u p M e m b e r R o l e
// v a r m e m b e r C a t e g o r y : G r o u p M e m b e r C a t e g o r y
// v a r m e m b e r S t a t u s : G r o u p M e m b e r S t a t u s
// v a r i n v i t e d B y : I n v i t e d B y
var localDisplayName : ContactName
var memberProfile : Profile
var memberContactId : Int64 ?
// v a r a c t i v e C o n n : C o n n e c t i o n ?
2022-03-30 08:57:42 +01:00
var directChatId : ChatId ? {
get {
if let chatId = memberContactId {
return " @ \( chatId ) "
} else {
return nil
}
}
}
2022-02-08 09:19:25 +00:00
static let sampleData = GroupMember (
groupMemberId : 1 ,
memberId : " abcd " ,
localDisplayName : " alice " ,
memberProfile : Profile . sampleData ,
memberContactId : 1
)
2022-01-29 11:10:04 +00:00
}
2022-02-25 20:26:56 +00:00
struct MemberSubError : Decodable {
var member : GroupMember
var memberError : ChatError
}
2022-01-29 23:37:02 +00:00
struct AChatItem : Decodable {
var chatInfo : ChatInfo
var chatItem : ChatItem
}
struct ChatItem : Identifiable , Decodable {
2022-01-29 11:10:04 +00:00
var chatDir : CIDirection
var meta : CIMeta
var content : CIContent
2022-02-23 08:45:49 +00:00
var formattedText : [ FormattedText ] ?
2022-03-17 09:42:59 +00:00
var quotedItem : CIQuote ?
2022-04-19 12:29:03 +04:00
var file : CIFile ?
2022-03-17 09:42:59 +00:00
2022-01-29 11:10:04 +00:00
var id : Int64 { get { meta . itemId } }
2022-01-24 16:07:17 +00:00
2022-03-09 22:35:33 +00:00
var timestampText : Text { get { meta . timestampText } }
2022-02-12 15:59:43 +00:00
2022-04-19 12:29:03 +04:00
var text : String {
get {
switch ( content . text , file ) {
case let ( " " , . some ( file ) ) : return file . fileName
default : return content . text
}
}
}
2022-02-12 15:59:43 +00:00
func isRcvNew ( ) -> Bool {
if case . rcvNew = meta . itemStatus { return true }
return false
}
2022-03-30 20:37:47 +04:00
func isMsgContent ( ) -> Bool {
switch content {
case . sndMsgContent : return true
case . rcvMsgContent : return true
default : return false
}
}
func isDeletedContent ( ) -> Bool {
switch content {
case . sndDeleted : return true
case . rcvDeleted : return true
default : return false
}
}
2022-02-12 15:59:43 +00:00
2022-03-17 09:42:59 +00:00
var memberDisplayName : String ? {
get {
if case let . groupRcv ( groupMember ) = chatDir {
return groupMember . memberProfile . displayName
} else {
return nil
}
}
}
2022-04-19 12:29:03 +04:00
static func getSample ( _ id : Int64 , _ dir : CIDirection , _ ts : Date , _ text : String , _ status : CIStatus = . sndNew , quotedItem : CIQuote ? = nil , file : CIFile ? = nil , _ itemDeleted : Bool = false , _ itemEdited : Bool = false , _ editable : Bool = true ) -> ChatItem {
2022-02-08 09:19:25 +00:00
ChatItem (
2022-03-30 20:37:47 +04:00
chatDir : dir ,
meta : CIMeta . getSample ( id , ts , text , status , itemDeleted , itemEdited , editable ) ,
content : . sndMsgContent ( msgContent : . text ( text ) ) ,
2022-04-19 12:29:03 +04:00
quotedItem : quotedItem ,
file : file
2022-03-30 20:37:47 +04:00
)
}
static func getDeletedContentSample ( _ id : Int64 = 1 , dir : CIDirection = . directRcv , _ ts : Date = . now , _ text : String = " this item is deleted " , _ status : CIStatus = . rcvRead ) -> ChatItem {
ChatItem (
chatDir : dir ,
meta : CIMeta . getSample ( id , ts , text , status , false , false , false ) ,
content : . rcvDeleted ( deleteMode : . cidmBroadcast ) ,
2022-04-19 12:29:03 +04:00
quotedItem : nil ,
file : nil
2022-02-08 09:19:25 +00:00
)
}
2022-01-31 21:28:07 +00:00
}
2022-01-29 23:37:02 +00:00
enum CIDirection : Decodable {
2022-01-29 11:10:04 +00:00
case directSnd
case directRcv
case groupSnd
2022-02-08 09:19:25 +00:00
case groupRcv ( groupMember : GroupMember )
2022-01-31 21:28:07 +00:00
var sent : Bool {
get {
switch self {
case . directSnd : return true
case . directRcv : return false
case . groupSnd : return true
case . groupRcv : return false
}
}
}
2022-01-29 11:10:04 +00:00
}
2022-01-29 23:37:02 +00:00
struct CIMeta : Decodable {
2022-01-29 11:10:04 +00:00
var itemId : Int64
var itemTs : Date
var itemText : String
2022-02-12 15:59:43 +00:00
var itemStatus : CIStatus
2022-01-29 11:10:04 +00:00
var createdAt : Date
2022-03-25 22:26:05 +04:00
var itemDeleted : Bool
var itemEdited : Bool
var editable : Bool
2022-02-08 09:19:25 +00:00
2022-03-09 22:35:33 +00:00
var timestampText : Text { get { SimpleX . timestampText ( itemTs ) } }
2022-02-12 15:59:43 +00:00
2022-03-25 22:26:05 +04:00
static func getSample ( _ id : Int64 , _ ts : Date , _ text : String , _ status : CIStatus = . sndNew , _ itemDeleted : Bool = false , _ itemEdited : Bool = false , _ editable : Bool = true ) -> CIMeta {
2022-02-08 09:19:25 +00:00
CIMeta (
itemId : id ,
itemTs : ts ,
itemText : text ,
2022-02-12 15:59:43 +00:00
itemStatus : status ,
2022-03-25 22:26:05 +04:00
createdAt : ts ,
itemDeleted : itemDeleted ,
itemEdited : itemEdited ,
editable : editable
2022-02-08 09:19:25 +00:00
)
}
2022-01-29 11:10:04 +00:00
}
2022-03-09 22:35:33 +00:00
let msgTimeFormat = Date . FormatStyle . dateTime . hour ( ) . minute ( )
let msgDateFormat = Date . FormatStyle . dateTime . day ( . twoDigits ) . month ( . twoDigits )
2022-02-12 15:59:43 +00:00
2022-03-09 22:35:33 +00:00
func timestampText ( _ date : Date ) -> Text {
2022-02-15 08:14:50 +00:00
let now = Calendar . current . dateComponents ( [ . day , . hour ] , from : . now )
let dc = Calendar . current . dateComponents ( [ . day , . hour ] , from : date )
2022-03-09 22:35:33 +00:00
let recent = now . day = = dc . day || ( ( now . day ? ? 0 ) - ( dc . day ? ? 0 ) = = 1 && ( dc . hour ? ? 0 ) >= 18 && ( now . hour ? ? 0 ) < 12 )
return Text ( date , format : recent ? msgTimeFormat : msgDateFormat )
2022-02-12 15:59:43 +00:00
}
2022-02-08 11:20:41 +04:00
enum CIStatus : Decodable {
case sndNew
case sndSent
case sndErrorAuth
2022-02-12 15:59:43 +00:00
case sndError ( agentError : AgentErrorType )
2022-02-08 11:20:41 +04:00
case rcvNew
case rcvRead
}
2022-03-30 20:37:47 +04:00
enum CIDeleteMode : String , Decodable {
case cidmBroadcast = " broadcast "
case cidmInternal = " internal "
}
2022-03-17 09:42:59 +00:00
protocol ItemContent {
var text : String { get }
}
enum CIContent : Decodable , ItemContent {
2022-01-29 11:10:04 +00:00
case sndMsgContent ( msgContent : MsgContent )
case rcvMsgContent ( msgContent : MsgContent )
2022-03-30 20:37:47 +04:00
case sndDeleted ( deleteMode : CIDeleteMode )
case rcvDeleted ( deleteMode : CIDeleteMode )
2022-01-29 11:10:04 +00:00
var text : String {
get {
switch self {
2022-02-03 07:16:29 +00:00
case let . sndMsgContent ( mc ) : return mc . text
case let . rcvMsgContent ( mc ) : return mc . text
2022-04-16 09:37:01 +01:00
case . sndDeleted : return NSLocalizedString ( " deleted " , comment : " deleted chat item " )
case . rcvDeleted : return NSLocalizedString ( " deleted " , comment : " deleted chat item " )
2022-01-29 11:10:04 +00:00
}
}
}
2022-04-19 12:29:03 +04:00
2022-04-08 18:17:10 +01:00
var msgContent : MsgContent ? {
get {
switch self {
case let . sndMsgContent ( mc ) : return mc
case let . rcvMsgContent ( mc ) : return mc
default : return nil
}
}
}
2022-01-29 11:10:04 +00:00
}
2022-03-17 09:42:59 +00:00
struct CIQuote : Decodable , ItemContent {
var chatDir : CIDirection ?
var itemId : Int64 ?
var sharedMsgId : String ? = nil
var sentAt : Date
var content : MsgContent
var formattedText : [ FormattedText ] ?
var text : String { get { content . text } }
var sender : String ? {
get {
switch ( chatDir ) {
case . directSnd : return " you "
case . directRcv : return nil
case . groupSnd : return ChatModel . shared . currentUser ? . displayName
case let . groupRcv ( member ) : return member . memberProfile . displayName
case nil : return nil
}
}
}
2022-04-19 12:29:03 +04:00
static func getSample ( _ itemId : Int64 ? , _ sentAt : Date , _ text : String , chatDir : CIDirection ? , image : String ? = nil ) -> CIQuote {
let mc : MsgContent
if let image = image {
mc = . image ( text : text , image : image )
} else {
mc = . text ( text )
}
return CIQuote ( chatDir : chatDir , itemId : itemId , sentAt : sentAt , content : mc )
}
}
struct CIFile : Decodable {
var fileId : Int64
var fileName : String
var fileSize : Int64
var filePath : String ?
var fileStatus : CIFileStatus
static func getSample ( _ fileId : Int64 , _ fileName : String , _ fileSize : Int64 , filePath : String ? , fileStatus : CIFileStatus = . sndStored ) -> CIFile {
CIFile ( fileId : fileId , fileName : fileName , fileSize : fileSize , filePath : filePath , fileStatus : fileStatus )
}
var stored : Bool {
get {
switch self . fileStatus {
case . sndStored : return true
case . sndCancelled : return true
case . rcvComplete : return true
default : return false
}
}
2022-03-17 09:42:59 +00:00
}
}
2022-04-19 12:29:03 +04:00
enum CIFileStatus : String , Decodable {
case sndStored = " snd_stored "
case sndCancelled = " snd_cancelled "
case rcvInvitation = " rcv_invitation "
case rcvTransfer = " rcv_transfer "
case rcvComplete = " rcv_complete "
case rcvCancelled = " rcv_cancelled "
}
2022-01-29 23:37:02 +00:00
enum MsgContent {
2022-01-24 16:07:17 +00:00
case text ( String )
2022-04-08 18:17:10 +01:00
case link ( text : String , preview : LinkPreview )
2022-04-19 12:29:03 +04:00
case image ( text : String , image : String )
2022-03-03 08:32:25 +00:00
// T O D O i n c l u d e o r i g i n a l J S O N , p o s s i b l y u s i n g h t t p s : / / g i t h u b . c o m / z o u l / g e n e r i c - j s o n - s w i f t
2022-01-29 23:37:02 +00:00
case unknown ( type : String , text : String )
2022-01-29 11:10:04 +00:00
2022-02-03 07:16:29 +00:00
var text : String {
2022-01-29 11:10:04 +00:00
get {
switch self {
2022-01-29 23:37:02 +00:00
case let . text ( text ) : return text
2022-04-08 18:17:10 +01:00
case let . link ( text , _ ) : return text
2022-04-19 12:29:03 +04:00
case let . image ( text , _ ) : return text
2022-01-29 23:37:02 +00:00
case let . unknown ( _ , text ) : return text
2022-01-29 11:10:04 +00:00
}
}
}
2022-01-29 23:37:02 +00:00
2022-01-30 18:27:20 +00:00
var cmdString : String {
get {
switch self {
case let . text ( text ) : return " text \( text ) "
2022-04-08 18:17:10 +01:00
case let . link ( text : text , preview : preview ) :
return " json { \" type \" : \" link \" , \" text \" : \( encodeJSON ( text ) ) , \" preview \" : \( encodeJSON ( preview ) ) } "
2022-04-19 12:29:03 +04:00
case let . image ( text : text , image : image ) :
return " json { \" type \" : \" image \" , \" text \" : \( encodeJSON ( text ) ) , \" image \" : \( encodeJSON ( image ) ) } "
2022-01-30 18:27:20 +00:00
default : return " "
}
}
}
2022-01-29 23:37:02 +00:00
enum CodingKeys : String , CodingKey {
case type
case text
2022-04-08 18:17:10 +01:00
case preview
2022-04-19 12:29:03 +04:00
case image
2022-01-29 23:37:02 +00:00
}
}
2022-04-08 18:17:10 +01:00
// T O D O d e f i n e E n c o d a b l e
2022-01-29 23:37:02 +00:00
extension MsgContent : Decodable {
init ( from decoder : Decoder ) throws {
do {
2022-03-03 08:32:25 +00:00
let container = try decoder . container ( keyedBy : CodingKeys . self )
2022-01-29 23:37:02 +00:00
let type = try container . decode ( String . self , forKey : CodingKeys . type )
switch type {
case " text " :
let text = try container . decode ( String . self , forKey : CodingKeys . text )
self = . text ( text )
2022-04-08 18:17:10 +01:00
case " link " :
let text = try container . decode ( String . self , forKey : CodingKeys . text )
let preview = try container . decode ( LinkPreview . self , forKey : CodingKeys . preview )
self = . link ( text : text , preview : preview )
2022-04-19 12:29:03 +04:00
case " image " :
let text = try container . decode ( String . self , forKey : CodingKeys . text )
let image = try container . decode ( String . self , forKey : CodingKeys . image )
self = . image ( text : text , image : image )
2022-01-29 23:37:02 +00:00
default :
let text = try ? container . decode ( String . self , forKey : CodingKeys . text )
self = . unknown ( type : type , text : text ? ? " unknown message format " )
}
} catch {
2022-03-03 08:32:25 +00:00
self = . unknown ( type : " unknown " , text : " invalid message format " )
2022-01-29 23:37:02 +00:00
}
}
2022-01-24 16:07:17 +00:00
}
2022-02-23 08:45:49 +00:00
struct FormattedText : Decodable {
var text : String
var format : Format ?
}
2022-04-08 18:17:10 +01:00
enum Format : Decodable , Equatable {
2022-02-23 08:45:49 +00:00
case bold
case italic
case strikeThrough
case snippet
case secret
2022-02-23 12:30:48 +00:00
case colored ( color : FormatColor )
2022-02-23 08:45:49 +00:00
case uri
case email
case phone
}
2022-02-25 07:16:19 +00:00
enum FormatColor : String , Decodable {
case red = " red "
case green = " green "
case blue = " blue "
case yellow = " yellow "
case cyan = " cyan "
case magenta = " magenta "
case black = " black "
case white = " white "
var uiColor : Color {
get {
switch ( self ) {
case . red : return . red
case . green : return . green
case . blue : return . blue
case . yellow : return . yellow
case . cyan : return . cyan
case . magenta : return . purple
case . black : return . primary
case . white : return . primary
}
}
}
2022-02-23 08:45:49 +00:00
}
2022-04-08 18:17:10 +01:00
// S t r u c t t o u s e w i t h s i m p l e x A P I
struct LinkPreview : Codable {
var uri : URL
var title : String
// T O D O r e m o v e o n c e o p t i o n a l i n h a s k e l l
var description : String = " "
var image : String
}