From 92b0cadd3d99cc63a9f307ad07939ec1466c6d20 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 20 Apr 2026 11:05:01 -0700 Subject: [PATCH] Fix regressions from merged pull request updates in 2.7.10 --- .../CoreData/MessageEntityExtension.swift | 2 + Meshtastic/Helpers/EmojiOnlyTextField.swift | 105 ------------------ Meshtastic/Persistence/UpdateCoreData.swift | 10 +- Meshtastic/Router/Router.swift | 24 ++-- .../Views/Messages/ChannelMessageList.swift | 1 + Meshtastic/Views/Messages/MessageText.swift | 2 +- .../Views/Messages/TapbackResponses.swift | 2 +- .../TextMessageField/TextMessageField.swift | 1 + .../Views/Messages/UserMessageList.swift | 1 + .../Views/Nodes/Helpers/Map/MapLegend.swift | 22 ++-- .../Nodes/Helpers/Map/WaypointForm.swift | 4 +- Widgets/MeshActivityAttributes.swift | 17 +++ 12 files changed, 56 insertions(+), 135 deletions(-) delete mode 100644 Meshtastic/Helpers/EmojiOnlyTextField.swift diff --git a/Meshtastic/Extensions/CoreData/MessageEntityExtension.swift b/Meshtastic/Extensions/CoreData/MessageEntityExtension.swift index a6f232fd..6da31891 100644 --- a/Meshtastic/Extensions/CoreData/MessageEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/MessageEntityExtension.swift @@ -50,6 +50,8 @@ extension MessageEntity { format: "replyID == %lld AND isEmoji == true", self.messageId ) + fetchRequest.fetchLimit = 20 + fetchRequest.returnsObjectsAsFaults = false return (try? context.fetch(fetchRequest)) ?? [MessageEntity]() } diff --git a/Meshtastic/Helpers/EmojiOnlyTextField.swift b/Meshtastic/Helpers/EmojiOnlyTextField.swift deleted file mode 100644 index aae9e3a3..00000000 --- a/Meshtastic/Helpers/EmojiOnlyTextField.swift +++ /dev/null @@ -1,105 +0,0 @@ -// -// EmojiKeyboard.swift -// Meshtastic -// -// Copyright(c) Garth Vander Houwen 1/10/23. -// -import SwiftUI - -class SwiftUIEmojiTextField: UITextField { - var shouldBecomeFirstResponderOnAppear = false - - func setEmoji() { - _ = self.textInputMode - } - - override var textInputContextIdentifier: String? { - return "" - } - - override var textInputMode: UITextInputMode? { - for mode in UITextInputMode.activeInputModes where mode.primaryLanguage == "emoji" { - self.keyboardType = .default // do not remove this - return mode - } - return nil - } - - override func didMoveToWindow() { - super.didMoveToWindow() - if shouldBecomeFirstResponderOnAppear && window != nil { - DispatchQueue.main.async { [weak self] in - self?.becomeFirstResponder() - } - } - } -} - -struct EmojiOnlyTextField: UIViewRepresentable { - @Binding var text: String - var placeholder: String = "" - var onBecomeFirstResponder: (() -> Void)? - var onKeyboardTypeChanged: ((Bool) -> Void)? // true if NOT emoji (should dismiss), false if emoji - var onKeyboardDismissed: (() -> Void)? // Called when keyboard is dismissed - - func makeUIView(context: Context) -> SwiftUIEmojiTextField { - let emojiTextField = SwiftUIEmojiTextField() - emojiTextField.placeholder = placeholder - emojiTextField.text = text - emojiTextField.delegate = context.coordinator - emojiTextField.shouldBecomeFirstResponderOnAppear = true - context.coordinator.textField = emojiTextField - return emojiTextField - } - - func updateUIView(_ uiView: SwiftUIEmojiTextField, context: Context) { - uiView.text = text - context.coordinator.onBecomeFirstResponder = onBecomeFirstResponder - context.coordinator.onKeyboardTypeChanged = onKeyboardTypeChanged - context.coordinator.onKeyboardDismissed = onKeyboardDismissed - } - - func makeCoordinator() -> Coordinator { - Coordinator(parent: self) - } - - class Coordinator: NSObject, UITextFieldDelegate { - var parent: EmojiOnlyTextField - var textField: SwiftUIEmojiTextField? - var onBecomeFirstResponder: (() -> Void)? - var onKeyboardTypeChanged: ((Bool) -> Void)? - var onKeyboardDismissed: (() -> Void)? - var previousInputMode: String? - - init(parent: EmojiOnlyTextField) { - self.parent = parent - } - - func textFieldDidBeginEditing(_ textField: UITextField) { - onBecomeFirstResponder?() - checkInputMode(textField) - } - - func textFieldDidEndEditing(_ textField: UITextField) { - // Keyboard was dismissed - onKeyboardDismissed?() - } - - func textFieldDidChangeSelection(_ textField: UITextField) { - DispatchQueue.main.async { [weak self] in - self?.parent.text = textField.text ?? "" - } - checkInputMode(textField) - } - - private func checkInputMode(_ textField: UITextField) { - if let inputMode = textField.textInputMode { - let isEmoji = inputMode.primaryLanguage == "emoji" - if previousInputMode != inputMode.primaryLanguage { - previousInputMode = inputMode.primaryLanguage - onKeyboardTypeChanged?(!isEmoji) // true if NOT emoji (should dismiss) - } - } - } - } -} diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 4f67a0d2..479b28d7 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -189,7 +189,7 @@ extension MeshPackets { // crash when this method is called with a background context. let fetchRequest = MessageEntity.fetchRequest() fetchRequest.predicate = user.messageFetchRequest.predicate - let objects = (try? context.fetch(fetchRequest)) ?? [] + let objects = try context.fetch(fetchRequest) for object in objects { context.delete(object) } @@ -435,13 +435,19 @@ extension MeshPackets { } } + let myInfoEntity = MyInfoEntity(context: context) + myInfoEntity.myNodeNum = Int64(packet.from) + myInfoEntity.rebootCount = 0 + newNode.myInfo = myInfoEntity + do { try context.save() Logger.data.info("💾 [NodeInfo] Saved a NodeInfo for node number: \(packet.from.toHex(), privacy: .public)") + Logger.data.info("💾 [MyInfoEntity] Saved a new myInfo for node number: \(packet.from.toHex(), privacy: .public)") } catch { context.rollback() let nsError = error as NSError - Logger.data.error("💥 [NodeInfoEntity] Error Inserting New Core Data: \(nsError, privacy: .public)") + Logger.data.error("💥 [MyInfoEntity] Error Inserting New Core Data: \(nsError, privacy: .public)") } } else { diff --git a/Meshtastic/Router/Router.swift b/Meshtastic/Router/Router.swift index 4874f114..3e421843 100644 --- a/Meshtastic/Router/Router.swift +++ b/Meshtastic/Router/Router.swift @@ -23,23 +23,15 @@ class Router: ObservableObject { /// Computed property that assembles the individual per-tab properties into a `NavigationState`. /// Provided for backward compatibility (e.g. tests) and convenience. + /// Use the individual `@Published` properties to mutate navigation state. var navigationState: NavigationState { - get { - NavigationState( - selectedTab: selectedTab, - messages: messagesState, - nodeListSelectedNodeNum: nodeListSelectedNodeNum, - map: mapState, - settings: settingsState - ) - } - set { - selectedTab = newValue.selectedTab - messagesState = newValue.messages - nodeListSelectedNodeNum = newValue.nodeListSelectedNodeNum - mapState = newValue.map - settingsState = newValue.settings - } + NavigationState( + selectedTab: selectedTab, + messages: messagesState, + nodeListSelectedNodeNum: nodeListSelectedNodeNum, + map: mapState, + settings: settingsState + ) } // MARK: Node Object ID Cache diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index a1c70b89..b77f0f34 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -130,6 +130,7 @@ struct ChannelMessageList: View { replyMessageId: $replyMessageId, isFocused: $messageFieldFocused ) + .fixedSize(horizontal: false, vertical: true) } .navigationBarTitleDisplayMode(.inline) .toolbar { diff --git a/Meshtastic/Views/Messages/MessageText.swift b/Meshtastic/Views/Messages/MessageText.swift index 6343b91a..a972df24 100644 --- a/Meshtastic/Views/Messages/MessageText.swift +++ b/Meshtastic/Views/Messages/MessageText.swift @@ -121,7 +121,7 @@ struct MessageText: View { .keyboardType(.emoji) .scrollDismissesKeyboard(.immediately) .focused($isTapbackInputFocused) - .frame(width: 0, height: 0) + .frame(width: 1, height: 1) .opacity(0) .onChange(of: tapbackText) { processTapback() diff --git a/Meshtastic/Views/Messages/TapbackResponses.swift b/Meshtastic/Views/Messages/TapbackResponses.swift index b46e65f3..e1504f8e 100644 --- a/Meshtastic/Views/Messages/TapbackResponses.swift +++ b/Meshtastic/Views/Messages/TapbackResponses.swift @@ -13,7 +13,7 @@ struct TapbackResponses: View { if !tapbacks.isEmpty { VStack(alignment: .trailing) { HStack { - ForEach( tapbacks ) { (tapback: MessageEntity) in + ForEach(tapbacks) { (tapback: MessageEntity) in VStack { let image = tapback.messagePayload!.image(fontSize: 20) Image(uiImage: image!).font(.caption) diff --git a/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift b/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift index e13ac808..0a605ec5 100644 --- a/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift +++ b/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift @@ -36,6 +36,7 @@ struct TextMessageField: View { } } TextField("Message", text: $typingMessage, axis: .vertical) + .frame(minHeight: 36) .padding(10) .background( RoundedRectangle(cornerRadius: 20) diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index dc417565..57280e7e 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -130,6 +130,7 @@ struct UserMessageList: View { replyMessageId: $replyMessageId, isFocused: $messageFieldFocused ) + .fixedSize(horizontal: false, vertical: true) } .navigationBarTitleDisplayMode(.inline) .toolbar { diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapLegend.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapLegend.swift index 2d04e861..51a97770 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapLegend.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapLegend.swift @@ -236,17 +236,23 @@ struct MapLegend: View { } private var routeStartSymbol: some View { - Circle() - .fill(Color.green) - .strokeBorder(Color.white, lineWidth: 2) - .frame(width: 15, height: 15) + ZStack { + Circle() + .fill(Color.green) + Circle() + .strokeBorder(Color.white, lineWidth: 2) + } + .frame(width: 15, height: 15) } private var routeEndSymbol: some View { - Circle() - .fill(Color.black) - .strokeBorder(Color.white, lineWidth: 2) - .frame(width: 15, height: 15) + ZStack { + Circle() + .fill(Color.black) + Circle() + .strokeBorder(Color.white, lineWidth: 2) + } + .frame(width: 15, height: 15) } private var routeLineSymbol: some View { diff --git a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift index c227298a..eec17eb0 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift @@ -239,8 +239,8 @@ struct WaypointForm: View { newWaypoint.longitudeI = waypoint.longitudeI // Unicode scalar value for the icon emoji string let unicodeScalers = icon.unicodeScalars - // First element as an UInt32 - let unicode = unicodeScalers[unicodeScalers.startIndex].value + // First element as an UInt32 (fall back to 📍 if empty) + let unicode = unicodeScalers.first?.value ?? 128205 newWaypoint.icon = unicode if locked { if lockedTo == 0 { diff --git a/Widgets/MeshActivityAttributes.swift b/Widgets/MeshActivityAttributes.swift index 7d2bd5a8..a41ad3ae 100644 --- a/Widgets/MeshActivityAttributes.swift +++ b/Widgets/MeshActivityAttributes.swift @@ -35,6 +35,23 @@ struct MeshActivityAttributes: ActivityAttributes { var nodeNum: Int var name: String var shortName: String + + enum CodingKeys: String, CodingKey { + case nodeNum, name, shortName + } + + init(nodeNum: Int, name: String, shortName: String) { + self.nodeNum = nodeNum + self.name = name + self.shortName = shortName + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + nodeNum = try container.decode(Int.self, forKey: .nodeNum) + name = try container.decode(String.self, forKey: .name) + shortName = try container.decodeIfPresent(String.self, forKey: .shortName) ?? "?" + } } #endif #endif