2021-12-24 23:41:18 -08:00
//
2022-01-01 08:03:46 -08:00
// U s e r M e s s a g e L i s t . s w i f t
2022-06-09 22:11:54 -07:00
// M e s h t a s t i c A p p l e
2022-01-01 08:03:46 -08:00
//
// C r e a t e d b y G a r t h V a n d e r H o u w e n o n 1 2 / 2 4 / 2 1 .
//
import SwiftUI
import CoreData
2022-11-05 08:26:27 -07:00
struct ChannelMessageList : View {
2023-03-06 10:33:18 -08:00
2022-01-01 08:03:46 -08:00
@ Environment ( \ . managedObjectContext ) var context
@ EnvironmentObject var bleManager : BLEManager
2023-03-06 10:33:18 -08:00
2022-01-01 08:03:46 -08:00
enum Field : Hashable {
case messageText
}
2023-03-06 10:33:18 -08:00
2022-01-01 08:03:46 -08:00
// K e y b o a r d S t a t e
@ State var typingMessage : String = " "
@ State private var totalBytes = 0
var maxbytes = 228
@ FocusState var focusedField : Field ?
2023-03-06 10:33:18 -08:00
2022-11-05 08:26:27 -07:00
@ ObservedObject var channel : ChannelEntity
2022-01-01 08:03:46 -08:00
@ State var showDeleteMessageAlert = false
@ State private var deleteMessageId : Int64 = 0
2022-01-01 22:55:25 -08:00
@ State private var replyMessageId : Int64 = 0
2022-02-17 18:07:41 -08:00
@ State private var sendPositionWithMessage : Bool = false
2023-03-06 10:33:18 -08:00
2022-11-05 08:26:27 -07:00
var body : some View {
2022-10-15 01:00:13 -07:00
NavigationStack {
2022-12-31 00:26:59 -08:00
let localeDateFormat = DateFormatter . dateFormat ( fromTemplate : " yyMMddjmmssa " , options : 0 , locale : Locale . current )
let dateFormatString = ( localeDateFormat ? ? " MM/dd/YY j:mm:ss a " )
2022-01-01 15:45:00 -08:00
ScrollViewReader { scrollView in
ScrollView {
2022-11-19 13:21:24 -08:00
LazyVStack {
2022-11-07 18:31:12 -08:00
ForEach ( channel . allPrivateMessages ) { ( message : MessageEntity ) in
2023-03-04 08:52:40 -08:00
let currentUser : Bool = ( bleManager . connectedPeripheral ? . num ? ? - 1 = = message . fromUser ? . num ? true : false )
2022-11-07 18:31:12 -08:00
if message . replyID > 0 {
let messageReply = channel . allPrivateMessages . first ( where : { $0 . messageId = = message . replyID } )
HStack {
2022-11-17 17:05:14 -08:00
Text ( messageReply ? . messagePayload ? ? " EMPTY MESSAGE " ) . foregroundColor ( . accentColor ) . font ( . caption2 )
2022-11-07 18:31:12 -08:00
. padding ( 10 )
. overlay (
RoundedRectangle ( cornerRadius : 18 )
. stroke ( Color . blue , lineWidth : 0.5 )
)
Image ( systemName : " arrowshape.turn.up.left.fill " )
. symbolRenderingMode ( . hierarchical )
2022-11-17 17:05:14 -08:00
. imageScale ( . large ) . foregroundColor ( . accentColor )
2022-11-07 18:31:12 -08:00
. padding ( . trailing )
}
}
2023-03-06 10:33:18 -08:00
HStack ( alignment : . top ) {
if currentUser { Spacer ( minLength : 50 ) }
2022-11-07 18:31:12 -08:00
if ! currentUser {
2023-04-01 02:35:31 -07:00
CircleText ( text : message . fromUser ? . shortName ? ? " ???? " , color : Color ( UIColor ( hex : UInt32 ( message . fromUser ? . num ? ? 0 ) ) ) , circleSize : 44 , fontSize : 14 , textColor : UIColor ( hex : UInt32 ( message . fromUser ? . num ? ? 0 ) ) . isLight ( ) ? . black : . white )
2022-11-07 18:31:12 -08:00
. padding ( . all , 5 )
. offset ( y : - 5 )
}
VStack ( alignment : currentUser ? . trailing : . leading ) {
2022-12-23 23:05:16 -08:00
let markdownText : LocalizedStringKey = LocalizedStringKey . init ( message . messagePayloadMarkdown ? ? ( message . messagePayload ? ? " EMPTY MESSAGE " ) )
2022-12-24 17:03:53 -08:00
let linkBlue = Color ( red : 0.4627 , green : 0.8392 , blue : 1 ) /* # 7 6 d 6 f f */
2022-12-23 20:48:47 -08:00
Text ( markdownText )
2022-12-24 17:03:53 -08:00
. tint ( linkBlue )
2022-11-07 18:31:12 -08:00
. padding ( 10 )
. foregroundColor ( . white )
2022-11-17 17:05:14 -08:00
. background ( currentUser ? . accentColor : Color ( . gray ) )
2022-11-07 18:31:12 -08:00
. cornerRadius ( 15 )
. contextMenu {
2023-03-06 10:33:18 -08:00
VStack {
2022-12-13 17:47:23 -08:00
Text ( " channel " ) + Text ( " : \( message . channel ) " )
2022-11-07 18:31:12 -08:00
}
2022-12-13 17:47:23 -08:00
Menu ( " tapback " ) {
2022-11-07 18:31:12 -08:00
ForEach ( Tapbacks . allCases ) { tb in
2022-01-01 21:21:04 -08:00
Button ( action : {
2022-11-07 18:51:17 -08:00
if bleManager . sendMessage ( message : tb . emojiString , toUserNum : 0 , channel : channel . index , isEmoji : true , replyID : message . messageId ) {
2022-11-07 18:31:12 -08:00
print ( " Sent \( tb . emojiString ) Tapback " )
self . context . refresh ( channel , mergeChanges : true )
} else { print ( " \( tb . emojiString ) Tapback Failed " ) }
2023-03-06 10:33:18 -08:00
2022-10-17 18:52:49 -07:00
} ) {
2022-11-07 18:31:12 -08:00
Text ( tb . description )
let image = tb . emojiString . image ( )
Image ( uiImage : image ! )
2022-01-04 22:57:33 -08:00
}
2022-08-05 15:58:12 -07:00
}
2022-01-01 08:03:46 -08:00
}
2022-11-07 18:31:12 -08:00
Button ( action : {
self . replyMessageId = message . messageId
self . focusedField = . messageText
print ( " I want to reply to \( message . messageId ) " )
} ) {
2022-12-13 17:47:23 -08:00
Text ( " reply " )
2022-11-07 18:31:12 -08:00
Image ( systemName : " arrowshape.turn.up.left.2.fill " )
}
Button ( action : {
UIPasteboard . general . string = message . messagePayload
} ) {
2022-12-13 17:47:23 -08:00
Text ( " copy " )
2022-11-07 18:31:12 -08:00
Image ( systemName : " doc.on.doc " )
}
2022-12-13 17:47:23 -08:00
Menu ( " message.details " ) {
2022-11-07 18:31:12 -08:00
VStack {
let messageDate = Date ( timeIntervalSince1970 : TimeInterval ( message . messageTimestamp ) )
2022-12-31 00:26:59 -08:00
Text ( " \( messageDate . formattedDate ( format : dateFormatString ) ) " ) . foregroundColor ( . gray )
2022-11-07 18:31:12 -08:00
}
2022-11-11 07:07:48 -08:00
if ! currentUser {
VStack {
2022-11-11 11:05:55 -08:00
Text ( " SNR \( String ( format : " %.2f " , message . snr ) ) dB " )
2022-11-11 07:07:48 -08:00
}
}
2022-10-17 18:52:49 -07:00
if currentUser && message . receivedACK {
2022-11-07 18:31:12 -08:00
VStack {
2022-12-13 17:47:23 -08:00
Text ( " received.ack " ) + Text ( " \( message . receivedACK ? " ✔️ " : " " ) " )
2022-11-07 18:31:12 -08:00
}
2022-10-17 18:52:49 -07:00
} else if currentUser && message . ackError = = 0 {
// E m p t y E r r o r
2022-12-13 17:47:23 -08:00
Text ( " waiting " )
2022-10-17 18:52:49 -07:00
} else if currentUser && message . ackError > 0 {
let ackErrorVal = RoutingError ( rawValue : Int ( message . ackError ) )
2022-12-30 17:24:01 -08:00
Text ( " \( ackErrorVal ? . display ? ? " Empty Ack Error " ) " ) . fixedSize ( horizontal : false , vertical : true )
2022-11-07 18:31:12 -08:00
}
if currentUser {
VStack {
let ackDate = Date ( timeIntervalSince1970 : TimeInterval ( message . ackTimestamp ) )
let sixMonthsAgo = Calendar . current . date ( byAdding : . month , value : - 6 , to : Date ( ) )
if ackDate >= sixMonthsAgo ! {
2022-12-31 00:26:59 -08:00
Text ( " Ack Time: \( ackDate . formattedDate ( format : " h:mm:ss a " ) ) " ) . foregroundColor ( . gray )
2022-11-07 18:31:12 -08:00
} else {
2022-12-31 00:26:59 -08:00
Text ( " unknown.age " ) . foregroundColor ( . gray )
2022-11-07 18:31:12 -08:00
}
}
}
if message . ackSNR != 0 {
VStack {
2022-12-31 02:23:21 -08:00
Text ( " Ack SNR: \( String ( format : " %.2f " , message . ackSNR ) ) dB " )
2022-11-07 18:31:12 -08:00
. foregroundColor ( . gray )
}
2022-10-17 18:52:49 -07:00
}
2022-10-14 22:18:28 -07:00
}
2022-11-07 18:31:12 -08:00
Divider ( )
Button ( role : . destructive , action : {
self . showDeleteMessageAlert = true
self . deleteMessageId = message . messageId
print ( deleteMessageId )
} ) {
2022-12-12 20:35:38 -08:00
Text ( " delete " )
2022-11-07 18:31:12 -08:00
Image ( systemName : " trash " )
}
2022-01-01 08:03:46 -08:00
}
2023-03-06 15:30:10 -08:00
let tapbacks = message . value ( forKey : " tapbacks " ) as ? [ MessageEntity ] ? ? [ ]
2022-11-07 18:31:12 -08:00
if tapbacks . count > 0 {
2023-03-06 10:33:18 -08:00
VStack ( alignment : . trailing ) {
HStack {
2022-11-07 18:31:12 -08:00
ForEach ( tapbacks ) { ( tapback : MessageEntity ) in
VStack {
let image = tapback . messagePayload ! . image ( fontSize : 20 )
Image ( uiImage : image ! ) . font ( . caption )
Text ( " \( tapback . fromUser ? . shortName ? ? " ???? " ) " )
. font ( . caption2 )
. foregroundColor ( . gray )
. fixedSize ( )
. padding ( . bottom , 1 )
}
2022-10-15 01:00:13 -07:00
}
2022-01-01 21:44:47 -08:00
}
2022-11-07 18:31:12 -08:00
. padding ( 10 )
. overlay (
RoundedRectangle ( cornerRadius : 18 )
. stroke ( Color . gray , lineWidth : 1 )
)
}
}
HStack {
if currentUser && message . receivedACK {
// A c k R e c e i v e d
Text ( " Acknowledged " ) . font ( . caption2 ) . foregroundColor ( . gray )
} else if currentUser && message . ackError = = 0 {
// E m p t y E r r o r
Text ( " Waiting to be acknowledged. . . " ) . font ( . caption2 ) . foregroundColor ( . orange )
} else if currentUser && message . ackError > 0 {
let ackErrorVal = RoutingError ( rawValue : Int ( message . ackError ) )
2022-12-30 17:24:01 -08:00
Text ( " \( ackErrorVal ? . display ? ? " Empty Ack Error " ) " ) . fixedSize ( horizontal : false , vertical : true )
2022-11-07 18:31:12 -08:00
. font ( . caption2 ) . foregroundColor ( . red )
}
2022-10-15 01:00:13 -07:00
}
2022-01-01 08:03:46 -08:00
}
2022-11-07 18:31:12 -08:00
. padding ( . bottom )
. id ( channel . allPrivateMessages . firstIndex ( of : message ) )
if ! currentUser {
2023-03-06 10:33:18 -08:00
Spacer ( minLength : 50 )
2022-11-07 18:31:12 -08:00
}
}
. padding ( [ . leading , . trailing ] )
. frame ( maxWidth : . infinity )
. id ( message . messageId )
. alert ( isPresented : $ showDeleteMessageAlert ) {
Alert ( title : Text ( " Are you sure you want to delete this message? " ) , message : Text ( " This action is permanent. " ) , primaryButton : . destructive ( Text ( " Delete " ) ) {
print ( " OK button tapped " )
if deleteMessageId > 0 {
let message = channel . allPrivateMessages . first ( where : { $0 . messageId = = deleteMessageId } )
context . delete ( message ! )
do {
try context . save ( )
deleteMessageId = 0
} catch {
print ( " Failed to delete message \( deleteMessageId ) " )
}
}
} , secondaryButton : . cancel ( ) )
2022-01-01 21:44:47 -08:00
}
2022-01-01 08:03:46 -08:00
}
}
2022-01-01 15:45:00 -08:00
}
2022-10-15 01:34:50 -07:00
. padding ( [ . top ] )
2022-09-17 12:41:27 -07:00
. scrollDismissesKeyboard ( . immediately )
2022-01-01 15:45:00 -08:00
. onAppear ( perform : {
self . bleManager . context = context
2022-11-07 18:31:12 -08:00
if channel . allPrivateMessages . count > 0 {
scrollView . scrollTo ( channel . allPrivateMessages . last ! . messageId )
2022-10-06 08:56:15 -07:00
}
2022-06-04 09:53:59 -07:00
} )
2023-03-06 10:33:18 -08:00
. onChange ( of : channel . allPrivateMessages , perform : { _ in
2022-11-07 18:31:12 -08:00
if channel . allPrivateMessages . count > 0 {
scrollView . scrollTo ( channel . allPrivateMessages . last ! . messageId )
2022-10-17 18:52:49 -07:00
}
2022-01-02 10:05:13 -08:00
} )
2022-01-01 15:45:00 -08:00
}
2022-11-16 08:07:00 -08:00
#if targetEnvironment ( macCatalyst )
HStack {
Spacer ( )
2023-04-01 12:53:10 -07:00
Button {
let bell = " 🔔 Alert Bell Character! \ u{7} "
print ( bell )
typingMessage += bell
} label : {
Text ( " Alert Bell " )
Image ( systemName : " bell.fill " )
. symbolRenderingMode ( . hierarchical )
. imageScale ( . large ) . foregroundColor ( . accentColor )
}
Spacer ( )
2022-11-16 08:07:00 -08:00
Button {
let userLongName = bleManager . connectedPeripheral != nil ? bleManager . connectedPeripheral . longName : " Unknown "
sendPositionWithMessage = true
2023-04-26 10:25:39 -07:00
if UserDefaults . meshtasticUsername . count > 0 {
2023-03-06 10:33:18 -08:00
2023-04-26 10:25:39 -07:00
typingMessage += " 📍 " + UserDefaults . meshtasticUsername + " has shared their position with you from node " + userLongName
2023-03-06 10:33:18 -08:00
2022-11-16 08:07:00 -08:00
} else {
2023-03-06 10:33:18 -08:00
2023-04-01 12:53:10 -07:00
typingMessage += " 📍 " + userLongName + " has shared their position with you. "
2022-11-16 08:07:00 -08:00
}
2023-03-06 10:33:18 -08:00
2022-11-16 08:07:00 -08:00
} label : {
2022-12-13 17:47:23 -08:00
Text ( " share.position " )
2022-11-16 08:07:00 -08:00
Image ( systemName : " mappin.and.ellipse " )
. symbolRenderingMode ( . hierarchical )
. imageScale ( . large ) . foregroundColor ( . accentColor )
}
2023-05-05 09:27:24 -07:00
ProgressView ( " \( " bytes " . localized ) : \( totalBytes ) / \( maxbytes ) " , value : Double ( totalBytes ) , total : Double ( maxbytes ) )
2022-11-16 08:07:00 -08:00
. frame ( width : 130 )
. padding ( 5 )
. font ( . subheadline )
. accentColor ( . accentColor )
. padding ( . trailing )
}
#endif
2022-01-01 15:45:00 -08:00
HStack ( alignment : . top ) {
2023-03-06 10:33:18 -08:00
2022-01-01 15:45:00 -08:00
ZStack {
2022-12-13 17:47:23 -08:00
TextField ( " message " , text : $ typingMessage , axis : . vertical )
2022-01-01 15:45:00 -08:00
. onChange ( of : typingMessage , perform : { value in
2022-10-15 01:34:50 -07:00
totalBytes = value . utf8 . count
2022-06-26 20:43:00 -07:00
// O n l y m e s s w i t h t h e v a l u e i f i t i s t o o b i g
2022-06-26 21:05:55 -07:00
if totalBytes > maxbytes {
2022-06-26 20:43:00 -07:00
let firstNBytes = Data ( typingMessage . utf8 . prefix ( maxbytes ) )
if let maxBytesString = String ( data : firstNBytes , encoding : String . Encoding . utf8 ) {
// S e t t h e m e s s a g e b a c k t o t h e l a s t p l a c e w h e r e i t w a s t h e r i g h t s i z e
typingMessage = maxBytesString
} else {
print ( " not a valid UTF-8 sequence " )
}
2022-01-01 08:03:46 -08:00
}
2022-01-01 15:45:00 -08:00
} )
2023-04-25 21:44:34 -07:00
. keyboardType ( . default )
2022-01-01 15:45:00 -08:00
. toolbar {
ToolbarItemGroup ( placement : . keyboard ) {
2022-12-13 07:49:46 -08:00
Button ( " dismiss.keyboard " ) {
2022-01-01 15:45:00 -08:00
focusedField = nil
}
. font ( . subheadline )
Spacer ( )
2023-04-01 12:53:10 -07:00
Button {
let bell = " 🔔 Alert Bell Character! \ u{7} "
print ( bell )
typingMessage += bell
} label : {
Text ( " Alert " )
Image ( systemName : " bell.fill " )
. symbolRenderingMode ( . hierarchical )
. imageScale ( . large ) . foregroundColor ( . accentColor )
}
Spacer ( )
2022-02-17 18:07:41 -08:00
Button {
let userLongName = bleManager . connectedPeripheral != nil ? bleManager . connectedPeripheral . longName : " Unknown "
sendPositionWithMessage = true
2023-04-26 09:19:45 -07:00
if UserDefaults . meshtasticUsername . count > 0 {
2023-03-06 10:33:18 -08:00
2023-04-26 09:19:45 -07:00
typingMessage = " 📍 " + UserDefaults . meshtasticUsername + " has shared their position with you from node " + userLongName
2023-03-06 10:33:18 -08:00
2022-02-18 06:22:02 -08:00
} else {
2022-11-07 18:31:12 -08:00
typingMessage = " 📍 " + userLongName + " has shared their position with you. "
2022-02-18 06:22:02 -08:00
}
2023-03-06 10:33:18 -08:00
2022-02-17 18:07:41 -08:00
} label : {
2022-02-18 06:22:02 -08:00
Image ( systemName : " mappin.and.ellipse " )
. symbolRenderingMode ( . hierarchical )
. imageScale ( . large ) . foregroundColor ( . accentColor )
2022-02-17 18:07:41 -08:00
}
2023-03-06 10:33:18 -08:00
2023-05-05 09:27:24 -07:00
ProgressView ( " \( " bytes " . localized ) : \( totalBytes ) / \( maxbytes ) " , value : Double ( totalBytes ) , total : Double ( maxbytes ) )
2022-01-01 15:45:00 -08:00
. frame ( width : 130 )
. padding ( 5 )
. font ( . subheadline )
. accentColor ( . accentColor )
}
2022-01-01 08:03:46 -08:00
}
2022-01-01 15:45:00 -08:00
. padding ( . horizontal , 8 )
. focused ( $ focusedField , equals : . messageText )
. multilineTextAlignment ( . leading )
2022-10-15 01:34:50 -07:00
. frame ( minHeight : 50 )
2022-12-17 23:53:06 -08:00
. keyboardShortcut ( . defaultAction )
. onSubmit {
2022-12-24 22:06:28 -08:00
#if targetEnvironment ( macCatalyst )
2022-12-17 23:53:06 -08:00
if bleManager . sendMessage ( message : typingMessage , toUserNum : 0 , channel : channel . index , isEmoji : false , replyID : replyMessageId ) {
typingMessage = " "
focusedField = nil
replyMessageId = 0
if sendPositionWithMessage {
2023-03-28 17:19:13 -07:00
if bleManager . sendPosition ( destNum : Int64 ( channel . index ) , wantResponse : false , smartPosition : false ) {
2022-12-17 23:53:06 -08:00
print ( " Location Sent " )
}
}
}
2022-12-24 22:06:28 -08:00
#endif
2022-12-17 23:53:06 -08:00
}
2022-01-01 15:45:00 -08:00
Text ( typingMessage ) . opacity ( 0 ) . padding ( . all , 0 )
}
. overlay ( RoundedRectangle ( cornerRadius : 20 ) . stroke ( . tertiary , lineWidth : 1 ) )
. padding ( . bottom , 15 )
Button ( action : {
2022-11-07 18:51:17 -08:00
if bleManager . sendMessage ( message : typingMessage , toUserNum : 0 , channel : channel . index , isEmoji : false , replyID : replyMessageId ) {
2022-01-01 15:45:00 -08:00
typingMessage = " "
focusedField = nil
2022-01-01 22:55:25 -08:00
replyMessageId = 0
2022-02-17 18:07:41 -08:00
if sendPositionWithMessage {
2023-03-28 14:52:11 -07:00
if bleManager . sendPosition ( destNum : Int64 ( channel . index ) , wantResponse : false , smartPosition : false ) {
2022-08-12 08:58:10 -07:00
print ( " Location Sent " )
2022-02-17 18:07:41 -08:00
}
}
2022-01-01 08:03:46 -08:00
}
2022-01-01 15:45:00 -08:00
} ) {
2022-11-17 17:05:14 -08:00
Image ( systemName : " arrow.up.circle.fill " ) . font ( . largeTitle ) . foregroundColor ( . accentColor )
2022-01-01 08:03:46 -08:00
}
}
2022-01-01 15:45:00 -08:00
. padding ( . all , 15 )
}
2022-01-01 08:03:46 -08:00
. navigationBarTitleDisplayMode ( . inline )
. toolbar {
ToolbarItem ( placement : . principal ) {
HStack {
2022-11-17 17:05:14 -08:00
CircleText ( text : String ( channel . index ) , color : . accentColor , circleSize : 44 , fontSize : 30 ) . fixedSize ( )
2023-05-05 09:27:24 -07:00
Text ( String ( channel . name ? ? " unknown " . localized ) . camelCaseToWords ( ) ) . font ( . headline )
2022-01-01 08:03:46 -08:00
}
}
ToolbarItem ( placement : . navigationBarTrailing ) {
ZStack {
ConnectedDevice (
bluetoothOn : bleManager . isSwitchedOn ,
deviceConnected : bleManager . connectedPeripheral != nil ,
2022-07-01 19:44:25 -07:00
name : ( bleManager . connectedPeripheral != nil ) ? bleManager . connectedPeripheral . shortName : " ???? " )
2022-01-01 08:03:46 -08:00
}
}
}
2022-11-05 08:26:27 -07:00
}
2022-01-01 08:03:46 -08:00
}