From 3882add56af18dc08bc5b340a26eab0788b1d782 Mon Sep 17 00:00:00 2001 From: Austin Payne Date: Mon, 5 Feb 2024 23:31:54 -0700 Subject: [PATCH] fix: slow typing speed when lots of messages Refactors both the channel and user message views to isolate typing state which prevents excessive re-rendering of large message lists on every new character typed. Also consolidates typing view code of both lists into the new TextMessageField and related sub views. --- Meshtastic.xcodeproj/project.pbxproj | 24 +++ Meshtastic/Helpers/BLEManager.swift | 6 +- .../Views/Messages/ChannelMessageList.swift | 147 +-------------- .../TextMessageField/AlertButton.swift | 21 +++ .../RequestPositionButton.swift | 20 +++ .../TextMessageField/TextMessageField.swift | 170 ++++++++++++++++++ .../TextMessageField/TextMessageSize.swift | 20 +++ .../Views/Messages/UserMessageList.swift | 117 +----------- 8 files changed, 275 insertions(+), 250 deletions(-) create mode 100644 Meshtastic/Views/Messages/TextMessageField/AlertButton.swift create mode 100644 Meshtastic/Views/Messages/TextMessageField/RequestPositionButton.swift create mode 100644 Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift create mode 100644 Meshtastic/Views/Messages/TextMessageField/TextMessageSize.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 6b056a19..b6989aed 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -10,8 +10,12 @@ 6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */; }; 6DEDA55A2A957B8E00321D2E /* DetectionSensorLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */; }; 6DEDA55C2A9592F900321D2E /* MessageEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */; }; + B3E905B12B71F7F300654D07 /* TextMessageField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3E905B02B71F7F300654D07 /* TextMessageField.swift */; }; C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */; }; C9697FA527933B8C00250207 /* SQLite in Frameworks */ = {isa = PBXBuildFile; productRef = C9697FA427933B8C00250207 /* SQLite */; }; + D9C9839D2B79CFD700BDBE6A /* TextMessageSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C9839C2B79CFD700BDBE6A /* TextMessageSize.swift */; }; + D9C983A02B79D0E800BDBE6A /* AlertButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C9839F2B79D0E800BDBE6A /* AlertButton.swift */; }; + D9C983A22B79D1A600BDBE6A /* RequestPositionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C983A12B79D1A600BDBE6A /* RequestPositionButton.swift */; }; DD007BAE2AA4E91200F5FA12 /* MyInfoEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD007BAD2AA4E91200F5FA12 /* MyInfoEntityExtension.swift */; }; DD007BB02AA5981000F5FA12 /* NodeInfoEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD007BAF2AA5981000F5FA12 /* NodeInfoEntityExtension.swift */; }; DD0D3D222A55CEB10066DB71 /* CocoaMQTT in Frameworks */ = {isa = PBXBuildFile; productRef = DD0D3D212A55CEB10066DB71 /* CocoaMQTT */; }; @@ -226,7 +230,11 @@ 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorLog.swift; sourceTree = ""; }; 6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageEntityExtension.swift; sourceTree = ""; }; A65FA974296876BF00A97686 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; + B3E905B02B71F7F300654D07 /* TextMessageField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextMessageField.swift; sourceTree = ""; }; C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMBTileOverlay.swift; sourceTree = ""; }; + D9C9839C2B79CFD700BDBE6A /* TextMessageSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextMessageSize.swift; sourceTree = ""; }; + D9C9839F2B79D0E800BDBE6A /* AlertButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertButton.swift; sourceTree = ""; }; + D9C983A12B79D1A600BDBE6A /* RequestPositionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestPositionButton.swift; sourceTree = ""; }; DD007BAD2AA4E91200F5FA12 /* MyInfoEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyInfoEntityExtension.swift; sourceTree = ""; }; DD007BAF2AA5981000F5FA12 /* NodeInfoEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoEntityExtension.swift; sourceTree = ""; }; DD05296F2B77F454008E44CD /* MeshtasticDataModelV 26.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 26.xcdatamodel"; sourceTree = ""; }; @@ -487,6 +495,17 @@ path = Custom; sourceTree = ""; }; + D9C9839E2B79D0C600BDBE6A /* TextMessageField */ = { + isa = PBXGroup; + children = ( + B3E905B02B71F7F300654D07 /* TextMessageField.swift */, + D9C9839F2B79D0E800BDBE6A /* AlertButton.swift */, + D9C983A12B79D1A600BDBE6A /* RequestPositionButton.swift */, + D9C9839C2B79CFD700BDBE6A /* TextMessageSize.swift */, + ); + path = TextMessageField; + sourceTree = ""; + }; DD007BB12AA59B9A00F5FA12 /* CoreData */ = { isa = PBXGroup; children = ( @@ -818,6 +837,7 @@ DDC2E18B26CE25A70042C5E4 /* Messages */ = { isa = PBXGroup; children = ( + D9C9839E2B79D0C600BDBE6A /* TextMessageField */, DDB8F4132A9EE5F000230ECE /* ChannelList.swift */, DD798B062915928D005217CD /* ChannelMessageList.swift */, DDB8F40F2A9EE5B400230ECE /* Messages.swift */, @@ -1162,6 +1182,7 @@ DDD6EEAF29BC024700383354 /* Firmware.swift in Sources */, DD77093B2AA1ABB8007A8BF0 /* BluetoothTips.swift in Sources */, DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */, + D9C9839D2B79CFD700BDBE6A /* TextMessageSize.swift in Sources */, DDC94FCE29CF55310082EA6E /* RtttlConfig.swift in Sources */, DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */, DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */, @@ -1238,6 +1259,7 @@ DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */, DDDE5A1029AFE69700490C6C /* MeshActivityAttributes.swift in Sources */, DD1925B928CDA93900720036 /* SerialConfigEnums.swift in Sources */, + D9C983A02B79D0E800BDBE6A /* AlertButton.swift in Sources */, DD86D4112881D16900BAEB7A /* WriteCsvFile.swift in Sources */, DDDB445029F8AC9C00EE2349 /* UIImage.swift in Sources */, DD86D40F2881BE4C00BAEB7A /* CsvDocument.swift in Sources */, @@ -1271,6 +1293,7 @@ DD0F791B28713C8A00A6FDAD /* AdminMessageList.swift in Sources */, DD3CC6BC28E366DF00FA9159 /* Meshtastic.xcdatamodeld in Sources */, DDC4C9FF2A8D982900CE201C /* DetectionSensorConfig.swift in Sources */, + D9C983A22B79D1A600BDBE6A /* RequestPositionButton.swift in Sources */, DDDB26442AAC0206003AFCB7 /* NodeDetail.swift in Sources */, DD5E5210298EE33B00D21B61 /* telemetry.pb.swift in Sources */, DD77093F2AA1B146007A8BF0 /* UIColor.swift in Sources */, @@ -1281,6 +1304,7 @@ DD1925B728CDA5A400720036 /* CannedMessagesConfigEnums.swift in Sources */, DDDB444429F8A8DD00EE2349 /* Float.swift in Sources */, DDAB580F2B0DAFBC00147258 /* LocationEntityExtension.swift in Sources */, + B3E905B12B71F7F300654D07 /* TextMessageField.swift in Sources */, DD5E5211298EE33B00D21B61 /* remote_hardware.pb.swift in Sources */, DD5E5204298EE33B00D21B61 /* xmodem.pb.swift in Sources */, DDE5B4062B227E3200FCDD05 /* TraceRouteEntityExtension.swift in Sources */, diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index dad6249a..11dbc360 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -38,7 +38,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var timeoutTimerCount = 0 var positionTimer: Timer? var lastPosition: CLLocationCoordinate2D? - let emptyNodeNum: UInt32 = 4294967295 + static let emptyNodeNum: UInt32 = 4294967295 let mqttManager = MqttClientProxyManager.shared var wantRangeTestPackets = false var wantStoreAndForwardPackets = false @@ -865,7 +865,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if toUserNum > 0 { meshPacket.to = UInt32(toUserNum) } else { - meshPacket.to = emptyNodeNum + meshPacket.to = Self.emptyNodeNum } meshPacket.channel = UInt32(channel) meshPacket.from = UInt32(fromUserNum) @@ -912,7 +912,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var success = false let fromNodeNum = UInt32(connectedPeripheral.num) var meshPacket = MeshPacket() - meshPacket.to = emptyNodeNum + meshPacket.to = Self.emptyNodeNum meshPacket.from = fromNodeNum meshPacket.wantAck = true var dataMessage = DataMessage() diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index e1a8811d..e9f469d6 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -9,27 +9,18 @@ import SwiftUI import CoreData struct ChannelMessageList: View { - @StateObject var appState = AppState.shared @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - enum Field: Hashable { - case messageText - } - // Keyboard State - @State var typingMessage: String = "" - @State private var totalBytes = 0 - var maxbytes = 228 - @FocusState var focusedField: Field? + @FocusState var messageFieldFocused: Bool @ObservedObject var myInfo: MyInfoEntity @ObservedObject var channel: ChannelEntity @State var showDeleteMessageAlert = false @State private var deleteMessageId: Int64 = 0 @State private var replyMessageId: Int64 = 0 - @State private var sendPositionWithMessage: Bool = false @AppStorage("preferredPeripheralNum") private var preferredPeripheralNum = -1 var body: some View { @@ -115,7 +106,7 @@ struct ChannelMessageList: View { } Button(action: { self.replyMessageId = message.messageId - self.focusedField = .messageText + self.messageFieldFocused = true print("I want to reply to \(message.messageId)") }) { Text("reply") @@ -288,134 +279,14 @@ struct ChannelMessageList: View { } }) } - #if targetEnvironment(macCatalyst) - HStack { - Spacer() - 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() - Button { - let userLongName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown" - sendPositionWithMessage = true - typingMessage += "📍 " + userLongName + " has shared their position with you." - } label: { - Text("share.position") - Image(systemName: "mappin.and.ellipse") - .symbolRenderingMode(.hierarchical) - .imageScale(.large).foregroundColor(.accentColor) - } - ProgressView("\("bytes".localized): \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes)) - .frame(width: 130) - .padding(5) - .font(.subheadline) - .accentColor(.accentColor) - .padding(.trailing) + + TextMessageField( + destination: .channel(channel.index), + replyMessageId: $replyMessageId, + isFocused: $messageFieldFocused + ) { + context.refresh(channel, mergeChanges: true) } - #endif - HStack(alignment: .top) { - - ZStack { - TextField("message", text: $typingMessage, axis: .vertical) - .onChange(of: typingMessage, perform: { value in - totalBytes = value.utf8.count - // Only mess with the value if it is too big - if totalBytes > maxbytes { - let firstNBytes = Data(typingMessage.utf8.prefix(maxbytes)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the message back to the last place where it was the right size - typingMessage = maxBytesString - } else { - print("not a valid UTF-8 sequence") - } - } - }) - .keyboardType(.default) - .toolbar { - ToolbarItemGroup(placement: .keyboard) { - Button("dismiss.keyboard") { - focusedField = nil - } - .font(.subheadline) - Spacer() - 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() - Button { - let userLongName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown" - sendPositionWithMessage = true - typingMessage = "📍 " + userLongName + " has shared their position with you." - - } label: { - Image(systemName: "mappin.and.ellipse") - .symbolRenderingMode(.hierarchical) - .imageScale(.large).foregroundColor(.accentColor) - } - - ProgressView("\("bytes".localized): \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes)) - .frame(width: 130) - .padding(5) - .font(.subheadline) - .accentColor(.accentColor) - } - } - .padding(.horizontal, 8) - .focused($focusedField, equals: .messageText) - .multilineTextAlignment(.leading) - .frame(minHeight: 50) - .keyboardShortcut(.defaultAction) - .onSubmit { - #if targetEnvironment(macCatalyst) - if bleManager.sendMessage(message: typingMessage, toUserNum: 0, channel: channel.index, isEmoji: false, replyID: replyMessageId) { - typingMessage = "" - focusedField = nil - replyMessageId = 0 - if sendPositionWithMessage { - if bleManager.sendPosition(channel: Int32(channel.index), destNum: Int64(bleManager.emptyNodeNum), wantResponse: false) { - print("Location Sent") - } - } - } - #endif - } - Text(typingMessage).opacity(0).padding(.all, 0) - } - .overlay(RoundedRectangle(cornerRadius: 20).stroke(.tertiary, lineWidth: 1)) - .padding(.bottom, 15) - Button(action: { - if bleManager.sendMessage(message: typingMessage, toUserNum: 0, channel: channel.index, isEmoji: false, replyID: replyMessageId) { - typingMessage = "" - focusedField = nil - replyMessageId = 0 - if sendPositionWithMessage { - if bleManager.sendPosition(channel: Int32(channel.index), destNum: Int64(bleManager.emptyNodeNum), wantResponse: false) { - print("Location Sent") - } - } - } - }) { - Image(systemName: "arrow.up.circle.fill").font(.largeTitle).foregroundColor(.accentColor) - } - - } - .padding(.all, 15) } .navigationBarTitleDisplayMode(.inline) .toolbar { diff --git a/Meshtastic/Views/Messages/TextMessageField/AlertButton.swift b/Meshtastic/Views/Messages/TextMessageField/AlertButton.swift new file mode 100644 index 00000000..84820bef --- /dev/null +++ b/Meshtastic/Views/Messages/TextMessageField/AlertButton.swift @@ -0,0 +1,21 @@ +import SwiftUI + +struct AlertButton: View { + let action: () -> Void + + var body: some View { + Button(action: action) { + Text("Alert") + Image(systemName: "bell.fill") + .symbolRenderingMode(.hierarchical) + .imageScale(.large) + .foregroundColor(.accentColor) + } + } +} + +struct AlertButtonPreview: PreviewProvider { + static var previews: some View { + AlertButton {} + } +} diff --git a/Meshtastic/Views/Messages/TextMessageField/RequestPositionButton.swift b/Meshtastic/Views/Messages/TextMessageField/RequestPositionButton.swift new file mode 100644 index 00000000..4a69ea50 --- /dev/null +++ b/Meshtastic/Views/Messages/TextMessageField/RequestPositionButton.swift @@ -0,0 +1,20 @@ +import SwiftUI + +struct RequestPositionButton: View { + let action: () -> Void + + var body: some View { + Button(action: action) { + Image(systemName: "mappin.and.ellipse") + .symbolRenderingMode(.hierarchical) + .imageScale(.large) + .foregroundColor(.accentColor) + } + } +} + +struct RequestPositionButtonPreview: PreviewProvider { + static var previews: some View { + RequestPositionButton {} + } +} diff --git a/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift b/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift new file mode 100644 index 00000000..67175642 --- /dev/null +++ b/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift @@ -0,0 +1,170 @@ +import SwiftUI + +struct TextMessageField: View { + static let maxbytes = 228 + @EnvironmentObject var bleManager: BLEManager + + let destination: Destination + @Binding var replyMessageId: Int64 + @FocusState.Binding var isFocused: Bool + let onSubmit: () -> Void + + enum Destination { + case user(Int64) + case channel(Int32) + } + + @State private var typingMessage: String = "" + @State private var totalBytes = 0 + @State private var sendPositionWithMessage = false + + var body: some View { + #if targetEnvironment(macCatalyst) + HStack { + if destination.showAlertButton { + Spacer() + AlertButton { typingMessage += "🔔 Alert Bell Character! \u{7}" } + } + Spacer() + RequestPositionButton(action: requestPosition) + TextMessageSize(maxbytes: Self.maxbytes, totalBytes: totalBytes).padding(.trailing) + } + #endif + + HStack(alignment: .top) { + ZStack { + TextField("message", text: $typingMessage, axis: .vertical) + .onChange(of: typingMessage, perform: { value in + totalBytes = value.utf8.count + // Only mess with the value if it is too big + if totalBytes > Self.maxbytes { + let firstNBytes = Data(typingMessage.utf8.prefix(Self.maxbytes)) + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + // Set the message back to the last place where it was the right size + typingMessage = maxBytesString + } else { + print("not a valid UTF-8 sequence") + } + } + }) + .keyboardType(.default) + .toolbar { + ToolbarItemGroup(placement: .keyboard) { + Button("dismiss.keyboard") { + isFocused = false + } + .font(.subheadline) + + if destination.showAlertButton { + Spacer() + AlertButton { typingMessage += "🔔 Alert Bell Character! \u{7}" } + } + + Spacer() + RequestPositionButton(action: requestPosition) + TextMessageSize(maxbytes: Self.maxbytes, totalBytes: totalBytes) + } + } + .padding(.horizontal, 8) + .focused($isFocused) + .multilineTextAlignment(.leading) + .frame(minHeight: 50) + .keyboardShortcut(.defaultAction) + .onSubmit { + #if targetEnvironment(macCatalyst) + sendMessage() + #endif + } + + Text(typingMessage) + .opacity(0) + .padding(.all, 0) + } + .overlay(RoundedRectangle(cornerRadius: 20).stroke(.tertiary, lineWidth: 1)) + .padding(.bottom, 15) + + Button(action: sendMessage) { + Image(systemName: "arrow.up.circle.fill") + .font(.largeTitle) + .foregroundColor(.accentColor) + } + } + .padding(.all, 15) + } + + private func requestPosition() { + let userLongName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown" + sendPositionWithMessage = true + typingMessage = "📍 " + userLongName + " \(destination.positionShareMessage)." + } + + private func sendMessage() { + let messageSent = bleManager.sendMessage( + message: typingMessage, + toUserNum: destination.userNum, + channel: destination.channelNum, + isEmoji: false, + replyID: replyMessageId + ) + if messageSent { + typingMessage = "" + isFocused = false + replyMessageId = 0 + onSubmit() + if sendPositionWithMessage { + let positionSent = bleManager.sendPosition( + channel: destination.channelNum, + destNum: destination.positionDestNum, + wantResponse: destination.wantPositionResponse + ) + if positionSent { + print("Location Sent") + } + } + } + } +} + +private extension TextMessageField.Destination { + var positionShareMessage: String { + switch self { + case .user: return "has shared their position and requested a response with your position" + case .channel: return "has shared their position with you" + } + } + + var userNum: Int64 { + switch self { + case let .user(num): return num + case .channel: return 0 + } + } + + var channelNum: Int32 { + switch self { + case .user: return 0 + case let .channel(num): return num + } + } + + var positionDestNum: Int64 { + switch self { + case let .user(num): return num + case .channel: return Int64(BLEManager.emptyNodeNum) + } + } + + var showAlertButton: Bool { + switch self { + case .user: return false + case .channel: return true + } + } + + var wantPositionResponse: Bool { + switch self { + case .user: return true + case .channel: return false + } + } +} diff --git a/Meshtastic/Views/Messages/TextMessageField/TextMessageSize.swift b/Meshtastic/Views/Messages/TextMessageField/TextMessageSize.swift new file mode 100644 index 00000000..d7428110 --- /dev/null +++ b/Meshtastic/Views/Messages/TextMessageField/TextMessageSize.swift @@ -0,0 +1,20 @@ +import SwiftUI + +struct TextMessageSize: View { + let maxbytes: Int + let totalBytes: Int + + var body: some View { + ProgressView("\("bytes".localized): \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes)) + .frame(width: 130) + .padding(5) + .font(.subheadline) + .accentColor(.accentColor) + } +} + +struct TextMessageSizePreview: PreviewProvider { + static var previews: some View { + TextMessageSize(maxbytes: 228, totalBytes: 100) + } +} diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index d0c9c0e1..0e63090d 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -9,25 +9,17 @@ import SwiftUI import CoreData struct UserMessageList: View { - @StateObject var appState = AppState.shared @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - enum Field: Hashable { - case messageText - } // Keyboard State - @State var typingMessage: String = "" - @State private var totalBytes = 0 - var maxbytes = 228 - @FocusState var focusedField: Field? + @FocusState var messageFieldFocused: Bool // View State Items @ObservedObject var user: UserEntity @State var showDeleteMessageAlert = false @State private var deleteMessageId: Int64 = 0 @State private var replyMessageId: Int64 = 0 - @State private var sendPositionWithMessage: Bool = false var body: some View { VStack { @@ -88,7 +80,7 @@ struct UserMessageList: View { } Button(action: { self.replyMessageId = message.messageId - self.focusedField = .messageText + self.messageFieldFocused = true print("I want to reply to \(message.messageId)") }) { Text("reply") @@ -265,107 +257,14 @@ struct UserMessageList: View { } }) } - #if targetEnvironment(macCatalyst) - HStack { - Spacer() - Button { - let userLongName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown" - sendPositionWithMessage = true - typingMessage = "📍 " + userLongName + " has shared their position and requested a response with your position." - } label: { - Text("share.position") - Image(systemName: "mappin.and.ellipse") - .symbolRenderingMode(.hierarchical) - .imageScale(.large).foregroundColor(.accentColor) - } - ProgressView("\("bytes".localized): \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes)) - .frame(width: 130) - .padding(5) - .font(.subheadline) - .accentColor(.accentColor) - .padding(.trailing) - } - #endif - HStack(alignment: .top) { - ZStack { - TextField("message", text: $typingMessage, axis: .vertical) - .onChange(of: typingMessage, perform: { value in - totalBytes = value.utf8.count - // Only mess with the value if it is too big - if totalBytes > maxbytes { - let firstNBytes = Data(typingMessage.utf8.prefix(maxbytes)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the message back to the last place where it was the right size - typingMessage = maxBytesString - } else { - print("not a valid UTF-8 sequence") - } - } - }) - .keyboardType(.default) - .toolbar { - ToolbarItemGroup(placement: .keyboard) { - Button("dismiss.keyboard") { - focusedField = nil - } - .font(.subheadline) - Spacer() - Button { - let userLongName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown" - sendPositionWithMessage = true - typingMessage = "📍 " + userLongName + " has shared their position and requested a response with your position." - } label: { - Image(systemName: "mappin.and.ellipse") - .symbolRenderingMode(.hierarchical) - .imageScale(.large).foregroundColor(.accentColor) - } - ProgressView("\("bytes".localized): \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes)) - .frame(width: 130) - .padding(5) - .font(.subheadline) - .accentColor(.accentColor) - } - } - .padding(.horizontal, 8) - .focused($focusedField, equals: .messageText) - .multilineTextAlignment(.leading) - .frame(minHeight: 50) - .keyboardShortcut(.defaultAction) - .onSubmit { - #if targetEnvironment(macCatalyst) - if bleManager.sendMessage(message: typingMessage, toUserNum: user.num, channel: 0, isEmoji: false, replyID: replyMessageId) { - typingMessage = "" - focusedField = nil - replyMessageId = 0 - if sendPositionWithMessage { - if bleManager.sendPosition(channel: 0, destNum: user.num, wantResponse: true) { - print("Location Sent") - } - } - } - #endif - } - Text(typingMessage).opacity(0).padding(.all, 0) - } - .overlay(RoundedRectangle(cornerRadius: 20).stroke(.tertiary, lineWidth: 1)) - .padding(.bottom, 15) - Button(action: { - if bleManager.sendMessage(message: typingMessage, toUserNum: user.num, channel: 0, isEmoji: false, replyID: replyMessageId) { - typingMessage = "" - focusedField = nil - replyMessageId = 0 - if sendPositionWithMessage { - if bleManager.sendPosition(channel: 0, destNum: user.num, wantResponse: true) { - print("Location Sent") - } - } - } - }) { - Image(systemName: "arrow.up.circle.fill").font(.largeTitle).foregroundColor(.accentColor) - } + TextMessageField( + destination: .user(user.num), + replyMessageId: $replyMessageId, + isFocused: $messageFieldFocused + ) { + context.refresh(user, mergeChanges: true) } - .padding(.all, 15) } .navigationBarTitleDisplayMode(.inline) .toolbar {