Merge pull request #484 from Austinpayne/fix/slow-typing

fix: slow typing speed when lots of messages
This commit is contained in:
Garth Vander Houwen 2024-02-12 18:05:09 -08:00 committed by GitHub
commit 8f50e7f447
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 275 additions and 250 deletions

View file

@ -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 */,

View file

@ -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()

View file

@ -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 {

View 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 {}
}
}

View file

@ -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 {}
}
}

View file

@ -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
}
}
}

View file

@ -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)
}
}

View file

@ -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 {