Meshtastic-Apple/Meshtastic/Views/Messages/ChannelMessageList.swift
2024-02-17 14:10:25 -07:00

193 lines
6.7 KiB
Swift

//
// UserMessageList.swift
// MeshtasticApple
//
// Created by Garth Vander Houwen on 12/24/21.
//
import SwiftUI
import CoreData
struct ChannelMessageList: View {
@StateObject var appState = AppState.shared
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
// Keyboard State
@FocusState var messageFieldFocused: Bool
@ObservedObject var myInfo: MyInfoEntity
@ObservedObject var channel: ChannelEntity
@State private var replyMessageId: Int64 = 0
@AppStorage("preferredPeripheralNum") private var preferredPeripheralNum = -1
var body: some View {
VStack {
ScrollViewReader { scrollView in
ScrollView {
LazyVStack {
ForEach( channel.allPrivateMessages ) { (message: MessageEntity) in
let currentUser: Bool = (Int64(preferredPeripheralNum) == message.fromUser?.num ? true : false)
if message.replyID > 0 {
let messageReply = channel.allPrivateMessages.first(where: { $0.messageId == message.replyID })
HStack {
Text(messageReply?.messagePayload ?? "EMPTY MESSAGE").foregroundColor(.accentColor).font(.caption2)
.padding(10)
.overlay(
RoundedRectangle(cornerRadius: 18)
.stroke(Color.blue, lineWidth: 0.5)
)
Image(systemName: "arrowshape.turn.up.left.fill")
.symbolRenderingMode(.hierarchical)
.imageScale(.large).foregroundColor(.accentColor)
.padding(.trailing)
}
}
HStack(alignment: .top) {
if currentUser { Spacer(minLength: 50) }
if !currentUser {
CircleText(text: message.fromUser?.shortName ?? "?", color: Color(UIColor(hex: UInt32(message.fromUser?.num ?? 0))), circleSize: 44)
.padding(.all, 5)
.offset(y: -5)
}
VStack(alignment: currentUser ? .trailing : .leading) {
let isDetectionSensorMessage = message.portNum == Int32(PortNum.detectionSensorApp.rawValue)
MessageText(
message: message,
tapBackDestination: .channel(channel),
isCurrentUser: currentUser
) {
self.replyMessageId = message.messageId
self.messageFieldFocused = true
}
let tapbacks = message.value(forKey: "tapbacks") as? [MessageEntity] ?? []
if tapbacks.count > 0 {
VStack(alignment: .trailing) {
HStack {
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)
}
.onAppear {
if !tapback.read {
tapback.read = true
do {
try context.save()
print("📖 Read message \(message.messageId) ")
appState.unreadChannelMessages = myInfo.unreadMessages
UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages
context.refresh(myInfo, mergeChanges: true)
} catch {
print("Failed to read tapback \(tapback.messageId)")
}
}
}
}
}
.padding(10)
.overlay(
RoundedRectangle(cornerRadius: 18)
.stroke(Color.gray, lineWidth: 1)
)
}
}
HStack {
if currentUser && message.receivedACK {
// Ack Received
Text("Acknowledged").font(.caption2).foregroundColor(.gray)
} else if currentUser && message.ackError == 0 {
// Empty Error
Text("Waiting to be acknowledged. . .").font(.caption2).foregroundColor(.orange)
} else if currentUser && message.ackError > 0 {
let ackErrorVal = RoutingError(rawValue: Int(message.ackError))
Text("\(ackErrorVal?.display ?? "Empty Ack Error")").fixedSize(horizontal: false, vertical: true)
.font(.caption2).foregroundColor(.red)
} else if isDetectionSensorMessage {
let messageDate = message.timestamp
Text(" \(messageDate.formattedDate(format: MessageText.dateFormatString))").font(.caption2).foregroundColor(.gray)
}
}
}
.padding(.bottom)
.id(channel.allPrivateMessages.firstIndex(of: message))
if currentUser && (message.ackError == 9 || message.ackError == 5 || message.ackError == 3) {
RetryButton(message: message)
}
if !currentUser {
Spacer(minLength: 50)
}
}
.padding([.leading, .trailing])
.frame(maxWidth: .infinity)
.id(message.messageId)
.onAppear {
if !message.read {
message.read = true
do {
try context.save()
print("📖 Read message \(message.messageId) ")
appState.unreadChannelMessages = myInfo.unreadMessages
UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages
context.refresh(myInfo, mergeChanges: true)
} catch {
print("Failed to read message \(message.messageId)")
}
}
}
}
}
}
.padding([.top])
.scrollDismissesKeyboard(.immediately)
.onAppear {
if self.bleManager.context == nil {
self.bleManager.context = context
}
if channel.allPrivateMessages.count > 0 {
scrollView.scrollTo(channel.allPrivateMessages.last!.messageId)
}
}
.onChange(of: channel.allPrivateMessages, perform: { _ in
if channel.allPrivateMessages.count > 0 {
scrollView.scrollTo(channel.allPrivateMessages.last!.messageId)
}
})
}
TextMessageField(
destination: .channel(channel),
replyMessageId: $replyMessageId,
isFocused: $messageFieldFocused
) {
context.refresh(channel, mergeChanges: true)
}
}
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
HStack {
CircleText(text: String(channel.index), color: .accentColor, circleSize: 44).fixedSize()
Text(String(channel.name ?? "unknown".localized).camelCaseToWords()).font(.headline)
}
}
ToolbarItem(placement: .navigationBarTrailing) {
ZStack {
ConnectedDevice(
bluetoothOn: bleManager.isSwitchedOn,
deviceConnected: bleManager.connectedPeripheral != nil,
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
}
}
}
}
}