Conversation
update the translations
* Don't add new BLE devices to the device list in the backgournd * Bump version * Update Meshtastic/MeshtasticApp.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Meshtastic/MeshtasticApp.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR bumps the app version to 2.7.7 and introduces a new emoji picker interface for tapback reactions along with several improvements and clarifications.
Key Changes
- Replaces the static tapback menu with a dynamic emoji picker that allows users to select any emoji as a reaction
- Adds background state tracking to the AccessoryManager
- Clarifies the node purging explanation text in app settings
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| Meshtastic/Views/Messages/TapbackInputView.swift | New view implementing emoji-only text field with automatic keyboard handling and emoji extraction logic |
| Meshtastic/Views/Messages/MessageText.swift | Integrates TapbackInputView sheet with emoji selection callback and message sending |
| Meshtastic/Views/Messages/MessageContextMenuItems.swift | Simplifies tapback menu from static options to button that opens emoji picker |
| Meshtastic/Helpers/EmojiOnlyTextField.swift | Enhances emoji text field with first responder management and keyboard type change detection |
| Meshtastic/Views/Settings/AppSettings.swift | Updates node purging description for better clarity |
| Meshtastic/MeshtasticApp.swift | Adds background state tracking when scene phase changes |
| Meshtastic/Helpers/MeshPackets.swift | Removes alert configuration from Live Activity updates |
| Meshtastic/Extensions/UserDefaults.swift | Adds purgeStaleNodeDays property for node purging configuration |
| Meshtastic.xcodeproj/project.pbxproj | Updates project files and version numbers to 2.7.7 |
| Localizable.xcstrings | Updates localization strings for the new description text |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Initial plan * Remove nested Task block in tapback handler Co-authored-by: garthvh <1795163+garthvh@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: garthvh <1795163+garthvh@users.noreply.github.com>
|
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 20 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| .presentationDetents([.large]) | ||
| } | ||
| .sheet(isPresented: $editingSettings) { | ||
| MapSettingsForm(traffic: $showTraffic, pointsOfInterest: $showPointsOfInterest, mapLayer: $selectedMapLayer, meshMap: $isMeshMap, enabledOverlayConfigs: $enabledOverlayConfigs) |
There was a problem hiding this comment.
The removed .presentationDetents([.large]) modifier may have been intentional for the MapSettingsForm. Without it, the sheet will use default presentation sizing which may differ from the explicit .large detent that was removed. Consider whether this change was intended or if the modifier should remain.
| MapSettingsForm(traffic: $showTraffic, pointsOfInterest: $showPointsOfInterest, mapLayer: $selectedMapLayer, meshMap: $isMeshMap, enabledOverlayConfigs: $enabledOverlayConfigs) | |
| MapSettingsForm(traffic: $showTraffic, pointsOfInterest: $showPointsOfInterest, mapLayer: $selectedMapLayer, meshMap: $isMeshMap, enabledOverlayConfigs: $enabledOverlayConfigs) | |
| .presentationDetents([.large]) |
| if value.count > 1 { | ||
| let index = value.index(value.startIndex, offsetBy: 1) | ||
| icon = String(value[index]) | ||
| } |
There was a problem hiding this comment.
The removed iconIsFocused = false line means the keyboard will remain visible after emoji selection, which differs from the previous behavior where the keyboard was automatically dismissed. This may be unintended since users typically expect the keyboard to dismiss after selecting an emoji.
| } | |
| } | |
| iconIsFocused = false |
| import UIKit | ||
|
|
||
| struct TapbackInputView: View { | ||
| @Binding var text: String |
There was a problem hiding this comment.
The view has a text binding that gets cleared after use (line 28), but this creates unnecessary state management complexity. Consider removing the binding and using a @State variable internally instead, since the parent doesn't need to track the text value.
| @Binding var text: String | |
| @State private var text: String = "" |
| return | ||
| } | ||
|
|
||
| _ = clearStaleNodes(nodeExpireDays: Int(UserDefaults.purgeStaleNodeDays), context: self.context) |
There was a problem hiding this comment.
The return value of clearStaleNodes is explicitly discarded with _=, but this operation is called synchronously during the critical path of config requests. Consider whether this should be moved to a background task to avoid blocking the config request flow.
| _ = clearStaleNodes(nodeExpireDays: Int(UserDefaults.purgeStaleNodeDays), context: self.context) | |
| Task.detached(priority: .background) { | |
| _ = clearStaleNodes(nodeExpireDays: Int(UserDefaults.purgeStaleNodeDays), context: self.context) | |
| } |
|
|
||
| let logString = String.localizedStringWithFormat("Sent a Channel for: %@ Channel Index %d".localized, String(deviceNum), chan.index) | ||
| try await send(toRadio, debugDescription: logString) | ||
| channelPacket(channel: chan, fromNum: self.activeDeviceNum ?? 0, context: context) |
There was a problem hiding this comment.
The newly added channelPacket call is inside the channel sending loop. This means it's being called for every channel in the set, which could result in multiple database saves. Consider whether this should be called once after all channels are sent, or if the current behavior is intentional for immediate feedback.
* Add file missing from project, must have merged badly * Remove ui kit emoji keyboard
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 20 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try visitor.visitSingularBoolField(value: v, fieldNumber: 100) | ||
| }() | ||
| case nil: break | ||
| default: break |
There was a problem hiding this comment.
The change from case nil: break to default: break in the switch statement handling payloadVariant is inconsistent with Swift best practices for handling optional enum cases. When dealing with optional enums in a switch, using case nil: is more explicit and makes the intent clearer that we're handling the case where the variant is not set. Using default: could inadvertently catch new cases added to the enum in the future.
| default: break | |
| case nil: break |
|
|
||
| let logString = String.localizedStringWithFormat("Sent a Channel for: %@ Channel Index %d".localized, String(deviceNum), chan.index) | ||
| try await send(toRadio, debugDescription: logString) | ||
| channelPacket(channel: chan, fromNum: self.activeDeviceNum ?? 0, context: context) |
There was a problem hiding this comment.
The call to channelPacket is made for every channel in the loop without checking if self.activeDeviceNum is nil. If activeDeviceNum is nil, this will pass 0 to the function. Based on the channelPacket function definition, it attempts to fetch MyInfo using this number with predicate myNodeNum == %lld. Passing 0 could result in unexpected behavior or failure to save the channel properly. Consider unwrapping activeDeviceNum and handling the nil case appropriately before the loop.
| // Check if this is a continuation (variation selector, skin tone modifier, zero-width joiner, etc.) | ||
| if let scalar = nextChar.unicodeScalars.first, | ||
| (scalar.properties.isVariationSelector || | ||
| scalar.value == 0xFE0F || // Variation selector |
There was a problem hiding this comment.
The condition checks for scalar.properties.isVariationSelector and then explicitly checks for 0xFE0F which is also a variation selector. This creates redundancy since isVariationSelector should already cover the 0xFE0F case. Consider removing the explicit 0xFE0F check to avoid duplication.
| scalar.value == 0xFE0F || // Variation selector |
| if fetchedMyInfo.count != 1 { | ||
| throw AccessoryError.appError("MyInfo not found") |
There was a problem hiding this comment.
The error message 'MyInfo not found' when fetchedMyInfo.count != 1 is ambiguous. It doesn't distinguish between the case where no MyInfo was found (count == 0) versus multiple MyInfo entries were found (count > 1). Consider providing a more specific error message such as 'MyInfo not found for device' when count is 0, or 'Multiple MyInfo entries found for device' when count > 1.
| if fetchedMyInfo.count != 1 { | |
| throw AccessoryError.appError("MyInfo not found") | |
| if fetchedMyInfo.count == 0 { | |
| throw AccessoryError.appError("MyInfo not found for device \(deviceNum)") | |
| } else if fetchedMyInfo.count > 1 { | |
| throw AccessoryError.appError("Multiple MyInfo entries found for device \(deviceNum)") |
* Make BLE Transport an actor to fix background discovery crashes * Protobufs * Update Meshtastic/Accessory/Transports/Bluetooth Low Energy/BLETransport.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Throw too many retries error again, remove return --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 26 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| isRunning = false | ||
| return | ||
| throw AccessoryError.tooManyRetries |
There was a problem hiding this comment.
After removing the unreachable return statement on line 354, the code now correctly throws the error, but the isRunning = false statement on line 354 will never execute because the throw on line 355 immediately exits the function. The isRunning flag should be set to false before throwing the error to ensure proper cleanup.
| TextField("Select an emoji", text: $icon) | ||
| .keyboardType(.emoji) | ||
| .font(.title) | ||
| .focused($iconIsFocused) | ||
| .onChange(of: icon) { _, value in | ||
|
|
||
| // If you have anything other than emojis in your string make it empty | ||
| if !value.onlyEmojis() { | ||
| icon = "" | ||
| } | ||
| // If a second emoji is entered delete the first one | ||
| if value.count >= 1 { | ||
|
|
||
| if value.count > 1 { | ||
| let index = value.index(value.startIndex, offsetBy: 1) | ||
| icon = String(value[index]) | ||
| } | ||
| iconIsFocused = false | ||
| } | ||
| } |
There was a problem hiding this comment.
The emoji validation logic has been simplified but no longer validates that the input is actually an emoji. The previous implementation used onlyEmojis() to ensure only emoji characters were entered. Now any character can be entered. Consider adding validation to ensure only emoji characters are accepted, or document this behavior change.
| .scrollDismissesKeyboard(.immediately) | ||
| .focused($isTapbackInputFocused) | ||
| .frame(width: 0, height: 0) | ||
| .opacity(0) |
There was a problem hiding this comment.
The hidden TextField used for emoji input has no accessibility label or hint. Users relying on screen readers won't understand the purpose of this focused element. Add an .accessibilityLabel() and .accessibilityHint() to explain that this is for adding a tapback reaction emoji.
| .opacity(0) | |
| .opacity(0) | |
| .accessibilityLabel("Tapback emoji input") | |
| .accessibilityHint("Type an emoji to add a tapback reaction to this message.") |
| static var emoji: UIKeyboardType { | ||
| return UIKeyboardType(rawValue: 124) ?? .default |
There was a problem hiding this comment.
Using a magic number (124) for the emoji keyboard type is fragile and undocumented by Apple. This private API usage could break in future iOS versions. Consider documenting why this value is used, adding a comment about the risk, or checking for availability on specific iOS versions where this has been tested.
| static var emoji: UIKeyboardType { | |
| return UIKeyboardType(rawValue: 124) ?? .default | |
| /// Best-effort access to the emoji keyboard type. | |
| /// | |
| /// Apple does not expose a public `UIKeyboardType` case for the emoji keyboard. | |
| /// Historically, the emoji keyboard has used the raw value `124`, which is not | |
| /// documented and may change in future iOS versions. If the raw value is not | |
| /// valid on a given OS version, this property safely falls back to `.default`. | |
| /// | |
| /// This should be treated as a convenience only and not relied on for | |
| /// correctness-critical behavior. | |
| private static let emojiKeyboardRawValue: Int = 124 | |
| static var emoji: UIKeyboardType { | |
| return UIKeyboardType(rawValue: emojiKeyboardRawValue) ?? .default |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 29 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// | ||
| /// Bitfield for storing booleans. | ||
| /// LSB 0 is_key_manually_verified | ||
| /// LSB 1 is_muted |
There was a problem hiding this comment.
Corrected spelling of 'Persistes' to 'Persists' in the comment on line 2974 of mesh.pb.swift.
| case meshstick1262 // = 121 | ||
|
|
||
| /// | ||
| /// LilyGo T-Beam 1W |
There was a problem hiding this comment.
Remove trailing whitespace at the end of the comment line.
| /// LilyGo T-Beam 1W | |
| /// LilyGo T-Beam 1W |
|
|
||
| // Transport properties | ||
| var supportsManualConnection: Bool = false | ||
| let supportsManualConnection: Bool = false |
There was a problem hiding this comment.
The property 'supportsManualConnection' was changed from 'var' to 'let', but the class was also converted to an actor. Since this is now a constant, consider evaluating if this property should be static or if it needs to be an instance property at all.
No description provided.