mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
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.
This commit is contained in:
parent
416d5e5f41
commit
3882add56a
8 changed files with 275 additions and 250 deletions
|
|
@ -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 = "<group>"; };
|
||||
6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageEntityExtension.swift; sourceTree = "<group>"; };
|
||||
A65FA974296876BF00A97686 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
B3E905B02B71F7F300654D07 /* TextMessageField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextMessageField.swift; sourceTree = "<group>"; };
|
||||
C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMBTileOverlay.swift; sourceTree = "<group>"; };
|
||||
D9C9839C2B79CFD700BDBE6A /* TextMessageSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextMessageSize.swift; sourceTree = "<group>"; };
|
||||
D9C9839F2B79D0E800BDBE6A /* AlertButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertButton.swift; sourceTree = "<group>"; };
|
||||
D9C983A12B79D1A600BDBE6A /* RequestPositionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestPositionButton.swift; sourceTree = "<group>"; };
|
||||
DD007BAD2AA4E91200F5FA12 /* MyInfoEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyInfoEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DD007BAF2AA5981000F5FA12 /* NodeInfoEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DD05296F2B77F454008E44CD /* MeshtasticDataModelV 26.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 26.xcdatamodel"; sourceTree = "<group>"; };
|
||||
|
|
@ -487,6 +495,17 @@
|
|||
path = Custom;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D9C9839E2B79D0C600BDBE6A /* TextMessageField */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B3E905B02B71F7F300654D07 /* TextMessageField.swift */,
|
||||
D9C9839F2B79D0E800BDBE6A /* AlertButton.swift */,
|
||||
D9C983A12B79D1A600BDBE6A /* RequestPositionButton.swift */,
|
||||
D9C9839C2B79CFD700BDBE6A /* TextMessageSize.swift */,
|
||||
);
|
||||
path = TextMessageField;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 */,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
21
Meshtastic/Views/Messages/TextMessageField/AlertButton.swift
Normal file
21
Meshtastic/Views/Messages/TextMessageField/AlertButton.swift
Normal file
|
|
@ -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 {}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue