diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..788e7a2e --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,21 @@ +## What changed? + + +## Why did it change? + + +## How is this tested? + + +## Screenshots/Videos (when applicable) + + + +## Checklist + +- [ ] My code adheres to the project's coding and style guidelines. +- [ ] I have conducted a self-review of my code. +- [ ] I have commented my code, particularly in complex areas. +- [ ] I have made corresponding changes to the documentation. +- [ ] I have tested the change to ensure that it works as intended. + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..94ade3d3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,128 @@ +# Contributing to Meshtastic + +Thank you for considering contributing to Meshtastic! We appreciate your time and effort in helping to improve the project. This document outlines the guidelines for contributing to the project. + +## Table of Contents + +1. [Getting Started](#getting-started) +2. [Development Workflow](#development-workflow) + - [Targeting `main`](#targeting-main) + - [Small, Incremental Changes](#small-incremental-changes) + - [Rebase Commits](#rebase-commits) +3. [Creating a Branch](#creating-a-branch) +4. [Making Changes](#making-changes) +5. [Commit Messages](#commit-messages) +6. [Merging Changes](#merging-changes) +7. [Testing](#testing) +8. [Code Review](#code-review) +9. [Documentation](#documentation) +10. [Style Guides](#style-guides) + - [Git Commit Messages](#git-commit-messages) + - [Code Style](#code-style) +11. [Community](#community) + +## Getting Started + +1. Fork the repository on GitLab. +2. Clone your fork to your local machine: + ```sh + git clone https://gitlab.com//Meshtastic-Apple.git + ``` +3. Navigate to the project directory: + ```sh + cd Meshtastic-Apple + ``` +4. Open the Meshtastic.xcworkspace + ```sh + open Meshtastic.xcworkspace + ``` + +## Development Workflow + +### Targeting `main` + +In accordance with trunk-based development, all changes should target the `main` branch. + +### Small, Incremental Changes + +To facilitate easy code reviews and minimize merge conflicts, we encourage making small, incremental changes. Each change should be a self-contained, logically coherent unit of work that addresses a specific task or fixes a particular issue. + +### Rebase Commits + +To keep the project history clean, please use rebasing over merging when incorporating changes from the `main` branch into your feature branches. To rebase your branch on `main`, you can perform the following steps. + +```sh +git fetch +git rebase main +``` + +To enable pulls to rebase by default, you can use this git configuration option. + +```sh +git config pull.rebase true +``` + +## Creating a Branch + +1. Always create a new branch for your work. Use a descriptive name for your branch: + ```sh + git checkout -b your-branch-name + ``` + +## Making Changes + +1. Make your changes in the new branch. +2. Ensure your changes adhere to the project’s coding standards and conventions. +3. Keep your changes focused and avoid combining multiple unrelated tasks in a single branch. + +## Commit Messages + +1. Write clear and concise commit messages following the guidelines in [Git Commit Messages](#git-commit-messages). + +## Merging Changes + +1. Push your changes to your fork: + ```sh + git push origin your-branch-name + ``` +2. Create a pull request (PR) targeting the `main` branch. +3. Ensure your PR adheres to the project's guidelines and includes a clear description of the changes. +4. Request a code review from the project maintainers. + +## Testing + +1. Ensure all existing tests pass before submitting your PR. +2. Write new tests for any new features or bug fixes. +3. Run the tests locally + +## Code Review + +1. Address any feedback or changes requested by the reviewers. +2. Once approved, the PR will be merged into the `main` branch by a project maintainer. + +## Documentation + +1. Update the documentation to reflect any changes you have made. +2. Ensure the documentation is clear and concise. + +## Style Guides + +### Git Commit Messages + +- Use the imperative mood in the subject line (e.g., "Fix bug" instead of "Fixed bug"). +- Use the body to explain what and why, not how. + +### Code Style + +- This project requires swiftLint - see https://github.com/realm/SwiftLint +- Use SwiftUI +- Use SFSymbols for icons +- Use Core Data for persistence +- Ensure your code is clean and well-documented. + +## Community + +- Join our community on [Discord](https://discord.com/invite/ktMAKGBnBs). +- Participate in discussions and share your ideas. + +Thank you for contributing to Meshtastic! \ No newline at end of file diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 5592563a..967567c0 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -673,7 +673,10 @@ } } }, - "AirTm" : { + "Airtime" : { + + }, + "Airtime %@%%" : { }, "Alert" : { @@ -1406,6 +1409,9 @@ }, "Backup Database" : { + }, + "Bad" : { + }, "Bandwidth" : { @@ -1418,9 +1424,6 @@ }, "Barometric Pressure" : { - }, - "Batt" : { - }, "Battery Level %" : { @@ -2782,6 +2785,9 @@ }, "Channel Role" : { + }, + "Channel Utilization %@%% " : { + }, "channel.role.disabled" : { "extractionState" : "migrated", @@ -3081,9 +3087,6 @@ }, "CHG" : { - }, - "ChUtil" : { - }, "Clear" : { @@ -5049,9 +5052,6 @@ }, "Debug Log" : { - }, - "Debug Logs" : { - }, "Debug Logs%@" : { @@ -5193,6 +5193,9 @@ }, "Delete Node" : { + }, + "Delete Node?" : { + }, "Description" : { @@ -6811,9 +6814,6 @@ }, "Empty" : { - }, - "Enable MB Tiles" : { - }, "Enable Notifications" : { @@ -8264,13 +8264,13 @@ "How long the screen remains on after the user button is pressed or messages are received." : { }, - "How often device metrics are sent out over the mesh. Default is 15 minutes." : { + "How often device metrics are sent out over the mesh. Default is 30 minutes." : { }, - "How often power metrics are sent out over the mesh. Default is 15 minutes." : { + "How often power metrics are sent out over the mesh. Default is 30 minutes." : { }, - "How often sensor metrics are sent out over the mesh. Default is 15 minutes." : { + "How often sensor metrics are sent out over the mesh. Default is 30 minutes." : { }, "How often should we try to get a GPS position." : { @@ -10843,15 +10843,15 @@ }, "Licensed Operator" : { - }, - "Limit" : { - }, "Limit all periodic broadcast intervals especially telemetry and position. If you need to increase hops, do it on nodes at the edges, not the ones in the middle. MQTT is not advised when you are duty cycle restricted because the gateway node is then doing all the work." : { }, "Line Series" : { + }, + "Loading Logs. . ." : { + }, "Location: %@" : { @@ -14631,6 +14631,9 @@ }, "Mininum time between detection broadcasts. Default is 45 seconds." : { + }, + "Minute" : { + }, "mode" : { "localizations" : { @@ -15225,6 +15228,12 @@ } } } + }, + "Network Status Orange" : { + + }, + "Network Status Red" : { + }, "network.config" : { "localizations" : { @@ -15295,9 +15304,6 @@ }, "No Environment Metrics" : { - }, - "No Logs Available" : { - }, "No Positions" : { @@ -18786,6 +18792,15 @@ }, "Secondary" : { + }, + "Select a channel" : { + + }, + "Select a conversation" : { + + }, + "Select a conversation type" : { + }, "Select a Trace Route" : { @@ -18853,6 +18868,7 @@ } }, "select.menu.item" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -20882,9 +20898,6 @@ }, "The last 4 of the device MAC address will be appended to the short name to set the device's BLE Name. Short name can be up to 4 bytes long." : { - }, - "The latest MBTiles file shared with meshtastic will be loaded into the map." : { - }, "The maximum interval that can elapse without a node broadcasting a position" : { @@ -22269,9 +22282,6 @@ }, "Via Mqtt" : { - }, - "Volt" : { - }, "voltage" : { "localizations" : { @@ -22330,6 +22340,9 @@ } } } + }, + "Volts %@ " : { + }, "waiting" : { "localizations" : { diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index a849eb6a..dca5ef1c 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -19,15 +19,19 @@ 25A978BA2C13F8ED0003AAE7 /* MeshtasticProtobufs in Frameworks */ = {isa = PBXBuildFile; productRef = 25A978B92C13F8ED0003AAE7 /* MeshtasticProtobufs */; }; 25A978BC2C13F90D0003AAE7 /* MeshtasticProtobufs in Frameworks */ = {isa = PBXBuildFile; productRef = 25A978BB2C13F90D0003AAE7 /* MeshtasticProtobufs */; }; 25AECD4F2C2F723200862C8E /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 25AECD4E2C2F723200862C8E /* Localizable.xcstrings */; }; + 25C49D902C471AEA0024FBD1 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25C49D8F2C471AEA0024FBD1 /* Constants.swift */; }; 25F26B1E2C2F610D00C9CD9D /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD5BB0C2C285F00007E03CA /* Logger.swift */; }; 25F26B1F2C2F611300C9CD9D /* AppData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD5BB152C28B1E4007E03CA /* AppData.swift */; }; + 25F5D5BE2C3F6D87008036E3 /* NavigationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25F5D5BD2C3F6D87008036E3 /* NavigationState.swift */; }; + 25F5D5C02C3F6DA6008036E3 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25F5D5BF2C3F6DA6008036E3 /* Router.swift */; }; + 25F5D5C22C3F6E4B008036E3 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25F5D5C12C3F6E4B008036E3 /* AppState.swift */; }; + 25F5D5D12C4375DF008036E3 /* RouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25F5D5D02C4375DF008036E3 /* RouterTests.swift */; }; 6D825E622C34786C008DBEE4 /* CommonRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D825E612C34786C008DBEE4 /* CommonRegex.swift */; }; 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 */; }; B399E8A42B6F486400E4488E /* RetryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B399E8A32B6F486400E4488E /* RetryButton.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 */; }; D93068D32B8129510066FBC8 /* MessageContextMenuItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D22B8129510066FBC8 /* MessageContextMenuItems.swift */; }; D93068D52B812B700066FBC8 /* MessageDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D42B812B700066FBC8 /* MessageDestination.swift */; }; @@ -63,6 +67,7 @@ DD3501892852FC3B000FC853 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3501882852FC3B000FC853 /* Settings.swift */; }; DD354FD92BD96A0B0061A25F /* IAQScale.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD354FD82BD96A0B0061A25F /* IAQScale.swift */; }; DD3619152B1EF9F900C41C8C /* LocationsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3619142B1EF9F900C41C8C /* LocationsHandler.swift */; }; + DD3CC24C2C498D6C001BD3A2 /* BatteryCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC24B2C498D6C001BD3A2 /* BatteryCompact.swift */; }; DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */; }; DD3CC6BC28E366DF00FA9159 /* Meshtastic.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */; }; DD3CC6BE28E4CD9800FA9159 /* BatteryGauge.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */; }; @@ -205,6 +210,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 25F5D5CB2C4375A8008036E3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DDC2E14C26CE248E0042C5E4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = DDC2E15326CE248E0042C5E4; + remoteInfo = Meshtastic; + }; DDDE5A0129AF163E00490C6C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = DDC2E14C26CE248E0042C5E4 /* Project object */; @@ -236,13 +248,18 @@ 2519268F2C3CB44900249DF5 /* ClientHistoryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientHistoryButton.swift; sourceTree = ""; }; 251926912C3CB52300249DF5 /* DeleteNodeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteNodeButton.swift; sourceTree = ""; }; 25AECD4E2C2F723200862C8E /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; + 25C49D8F2C471AEA0024FBD1 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + 25F5D5BD2C3F6D87008036E3 /* NavigationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationState.swift; sourceTree = ""; }; + 25F5D5BF2C3F6DA6008036E3 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = ""; }; + 25F5D5C12C3F6E4B008036E3 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; + 25F5D5C72C4375A8008036E3 /* MeshtasticTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeshtasticTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 25F5D5D02C4375DF008036E3 /* RouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouterTests.swift; sourceTree = ""; }; 6D825E612C34786C008DBEE4 /* CommonRegex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonRegex.swift; sourceTree = ""; }; 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticAppDelegate.swift; sourceTree = ""; }; 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorLog.swift; sourceTree = ""; }; 6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageEntityExtension.swift; sourceTree = ""; }; B399E8A32B6F486400E4488E /* RetryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryButton.swift; 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 = ""; }; D93068D22B8129510066FBC8 /* MessageContextMenuItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageContextMenuItems.swift; sourceTree = ""; }; D93068D42B812B700066FBC8 /* MessageDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageDestination.swift; sourceTree = ""; }; D93068D62B8146690066FBC8 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = ""; }; @@ -279,6 +296,7 @@ DD268D8C2BCC7D11008073AE /* MeshtasticDataModelV 35.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 35.xcdatamodel"; sourceTree = ""; }; DD268D8D2BCC90E2008073AE /* RouteEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteEnums.swift; sourceTree = ""; }; DD295CE92B323ED9002CC4AC /* MeshtasticDataModelV22.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV22.xcdatamodel; sourceTree = ""; }; + DD2984A82C5AEF7500B1268D /* MeshtasticDataModelV 41.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 41.xcdatamodel"; sourceTree = ""; }; DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewSwiftUI.swift; sourceTree = ""; }; DD2CC2E52ABFE04E00EDFDA7 /* MeshtasticDataModelV19.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV19.xcdatamodel; sourceTree = ""; }; DD31B04D2BDC6FD30024FA63 /* MeshtasticDataModelV 36.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 36.xcdatamodel"; sourceTree = ""; }; @@ -289,6 +307,7 @@ DD3619132B1EE20700C41C8C /* MeshtasticDataModelV21.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV21.xcdatamodel; sourceTree = ""; }; DD3619142B1EF9F900C41C8C /* LocationsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationsHandler.swift; sourceTree = ""; }; DD398EBD2B93F640002B4C51 /* MeshtasticDataModelV 29.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 29.xcdatamodel"; sourceTree = ""; }; + DD3CC24B2C498D6C001BD3A2 /* BatteryCompact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryCompact.swift; sourceTree = ""; }; DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannels.swift; sourceTree = ""; }; DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModel.xcdatamodel; sourceTree = ""; }; DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryGauge.swift; sourceTree = ""; }; @@ -464,6 +483,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 25F5D5C42C4375A8008036E3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; DDC2E15126CE248E0042C5E4 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -500,6 +526,23 @@ path = Actions; sourceTree = ""; }; + 25F5D5BC2C3F6D7B008036E3 /* Router */ = { + isa = PBXGroup; + children = ( + 25F5D5BD2C3F6D87008036E3 /* NavigationState.swift */, + 25F5D5BF2C3F6DA6008036E3 /* Router.swift */, + ); + path = Router; + sourceTree = ""; + }; + 25F5D5C82C4375A8008036E3 /* MeshtasticTests */ = { + isa = PBXGroup; + children = ( + 25F5D5D02C4375DF008036E3 /* RouterTests.swift */, + ); + path = MeshtasticTests; + sourceTree = ""; + }; C9483F6B2773016700998F6B /* MapKitMap */ = { isa = PBXGroup; children = ( @@ -513,7 +556,6 @@ C9A7BC0E27759A6800760B50 /* Custom */ = { isa = PBXGroup; children = ( - C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */, DD964FC32974767D007C176F /* MapViewFitExtension.swift */, DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */, DDDB443529F6287000EE2349 /* MapButtons.swift */, @@ -739,6 +781,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */, DDC2E15626CE248E0042C5E4 /* Meshtastic */, DDDE59F729AF163D00490C6C /* Widgets */, + 25F5D5C82C4375A8008036E3 /* MeshtasticTests */, DDC2E15526CE248E0042C5E4 /* Products */, DD8EDE9226F97A2B00A5A10B /* Frameworks */, ); @@ -750,6 +793,7 @@ children = ( DDC2E15426CE248E0042C5E4 /* Meshtastic.app */, DDDE59F429AF163D00490C6C /* WidgetsExtension.appex */, + 25F5D5C72C4375A8008036E3 /* MeshtasticTests.xctest */, ); name = Products; sourceTree = ""; @@ -757,6 +801,7 @@ DDC2E15626CE248E0042C5E4 /* Meshtastic */ = { isa = PBXGroup; children = ( + 25F5D5BC2C3F6D7B008036E3 /* Router */, DD7709392AA1ABA1007A8BF0 /* Tips */, DD90860A26F645B700DC5189 /* Meshtastic.entitlements */, DD8ED9C6289CE4A100B3B0AB /* Enums */, @@ -768,6 +813,7 @@ DDC2E18926CE24F70042C5E4 /* Resources */, DDC2E18726CE24E40042C5E4 /* Views */, DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */, + 25F5D5C12C3F6E4B008036E3 /* AppState.swift */, DDC2E16526CE248F0042C5E4 /* Info.plist */, DDC2E15D26CE248F0042C5E4 /* Preview Content */, 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */, @@ -836,6 +882,7 @@ isa = PBXGroup; children = ( DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */, + DD3CC24B2C498D6C001BD3A2 /* BatteryCompact.swift */, DDB75A222A13CDA9006ED576 /* BatteryLevelCompact.swift */, DD457187293C7E63000C49FB /* BLESignalStrengthIndicator.swift */, DD47E3D526F17ED900029299 /* CircleText.swift */, @@ -917,6 +964,7 @@ DDDB444529F8A96500EE2349 /* Character.swift */, DDDB444929F8AA3A00EE2349 /* CLLocationCoordinate2D.swift */, DDDB444B29F8AAA600EE2349 /* Color.swift */, + 25C49D8F2C471AEA0024FBD1 /* Constants.swift */, DDDB445329F8AD1600EE2349 /* Data.swift */, DDDB445129F8ACF900EE2349 /* Date.swift */, DDDB444129F8A88700EE2349 /* Double.swift */, @@ -962,6 +1010,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 25F5D5C62C4375A8008036E3 /* MeshtasticTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 25F5D5CF2C4375A8008036E3 /* Build configuration list for PBXNativeTarget "MeshtasticTests" */; + buildPhases = ( + 25F5D5C32C4375A8008036E3 /* Sources */, + 25F5D5C42C4375A8008036E3 /* Frameworks */, + 25F5D5C52C4375A8008036E3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 25F5D5CC2C4375A8008036E3 /* PBXTargetDependency */, + ); + name = MeshtasticTests; + productName = MeshtasticTests; + productReference = 25F5D5C72C4375A8008036E3 /* MeshtasticTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; DDC2E15326CE248E0042C5E4 /* Meshtastic */ = { isa = PBXNativeTarget; buildConfigurationList = DDC2E17E26CE248F0042C5E4 /* Build configuration list for PBXNativeTarget "Meshtastic" */; @@ -1014,9 +1080,13 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastSwiftUpdateCheck = 1420; + LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1540; TargetAttributes = { + 25F5D5C62C4375A8008036E3 = { + CreatedOnToolsVersion = 15.4; + TestTargetID = DDC2E15326CE248E0042C5E4; + }; DDC2E15326CE248E0042C5E4 = { CreatedOnToolsVersion = 12.5.1; LastSwiftMigration = 1340; @@ -1055,11 +1125,19 @@ targets = ( DDC2E15326CE248E0042C5E4 /* Meshtastic */, DDDE59F329AF163D00490C6C /* WidgetsExtension */, + 25F5D5C62C4375A8008036E3 /* MeshtasticTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 25F5D5C52C4375A8008036E3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; DDC2E15226CE248E0042C5E4 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1106,6 +1184,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 25F5D5C32C4375A8008036E3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 25F5D5D12C4375DF008036E3 /* RouterTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; DDC2E15026CE248E0042C5E4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1160,6 +1246,7 @@ DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */, DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */, DDC94FC129CE063B0082EA6E /* BatteryLevel.swift in Sources */, + 25F5D5BE2C3F6D87008036E3 /* NavigationState.swift in Sources */, DD354FD92BD96A0B0061A25F /* IAQScale.swift in Sources */, DDDB445429F8AD1600EE2349 /* Data.swift in Sources */, DDDB26462AACC0B7003AFCB7 /* NodeInfoItem.swift in Sources */, @@ -1186,7 +1273,9 @@ DD3CC6BE28E4CD9800FA9159 /* BatteryGauge.swift in Sources */, DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */, DD3619152B1EF9F900C41C8C /* LocationsHandler.swift in Sources */, + 25F5D5C02C3F6DA6008036E3 /* Router.swift in Sources */, DDDB444A29F8AA3A00EE2349 /* CLLocationCoordinate2D.swift in Sources */, + 25C49D902C471AEA0024FBD1 /* Constants.swift in Sources */, DD41582628582E9B009B0E59 /* DeviceConfig.swift in Sources */, DDF45C372BC46A5A005ED5F2 /* TimeZone.swift in Sources */, DD007BAE2AA4E91200F5FA12 /* MyInfoEntityExtension.swift in Sources */, @@ -1232,6 +1321,7 @@ DDB75A112A059258006ED576 /* Url.swift in Sources */, DDAD49ED2AFB39DC00B4425D /* MeshMap.swift in Sources */, DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */, + DD3CC24C2C498D6C001BD3A2 /* BatteryCompact.swift in Sources */, DD1B8F402B35E2F10022AABC /* GPSStatus.swift in Sources */, DD8ED9C52898D51F00B3B0AB /* NetworkConfig.swift in Sources */, DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */, @@ -1264,10 +1354,10 @@ DDB6CCFB2AAF805100945AF6 /* NodeMapSwiftUI.swift in Sources */, DD73FD1128750779000852D6 /* PositionLog.swift in Sources */, DD15E4F52B8BFC8E00654F61 /* PaxCounterLog.swift in Sources */, + 25F5D5C22C3F6E4B008036E3 /* AppState.swift in Sources */, DD3CC6C028E7A60700FA9159 /* MessagingEnums.swift in Sources */, DD97E96628EFD9820056DDA4 /* MeshtasticLogo.swift in Sources */, DDAB580D2B0DAA9E00147258 /* Routes.swift in Sources */, - C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */, D93068D52B812B700066FBC8 /* MessageDestination.swift in Sources */, DDA9515E2BC6F56F00CEA535 /* IndoorAirQuality.swift in Sources */, DDDB444E29F8AB0E00EE2349 /* Int.swift in Sources */, @@ -1303,6 +1393,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 25F5D5CC2C4375A8008036E3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DDC2E15326CE248E0042C5E4 /* Meshtastic */; + targetProxy = 25F5D5CB2C4375A8008036E3 /* PBXContainerItemProxy */; + }; DDDE5A0229AF163E00490C6C /* PBXTargetDependency */ = { isa = PBXTargetDependency; platformFilter = ios; @@ -1312,6 +1407,52 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 25F5D5CD2C4375A8008036E3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = GCH7VS5Y9R; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.MeshtasticTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Meshtastic.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Meshtastic"; + }; + name = Debug; + }; + 25F5D5CE2C4375A8008036E3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = GCH7VS5Y9R; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.MeshtasticTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Meshtastic.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Meshtastic"; + }; + name = Release; + }; DDC2E17C26CE248F0042C5E4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1459,7 +1600,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.3.17; + MARKETING_VERSION = 2.4.1; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1489,12 +1630,12 @@ INFOPLIST_FILE = Meshtastic/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Meshtastic; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; - IPHONEOS_DEPLOYMENT_TARGET = 16.4; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.3.17; + MARKETING_VERSION = 2.4.1; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1526,7 +1667,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.3.17; + MARKETING_VERSION = 2.4.1; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1559,7 +1700,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.3.17; + MARKETING_VERSION = 2.4.1; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1574,6 +1715,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 25F5D5CF2C4375A8008036E3 /* Build configuration list for PBXNativeTarget "MeshtasticTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 25F5D5CD2C4375A8008036E3 /* Debug */, + 25F5D5CE2C4375A8008036E3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; DDC2E14F26CE248E0042C5E4 /* Build configuration list for PBXProject "Meshtastic" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -1662,6 +1812,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DD2984A82C5AEF7500B1268D /* MeshtasticDataModelV 41.xcdatamodel */, DD68BAE72C417A74004C01A0 /* MeshtasticDataModelV 40.xcdatamodel */, DD3D17DC2C3D7B1400561584 /* MeshtasticDataModelV 39.xcdatamodel */, DDD5BB142C28680D007E03CA /* MeshtasticDataModelV 38.xcdatamodel */, @@ -1703,7 +1854,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DD68BAE72C417A74004C01A0 /* MeshtasticDataModelV 40.xcdatamodel */; + currentVersion = DD2984A82C5AEF7500B1268D /* MeshtasticDataModelV 41.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic.xcodeproj/xcshareddata/xcschemes/Meshtastic.xcscheme b/Meshtastic.xcodeproj/xcshareddata/xcschemes/Meshtastic.xcscheme index 68ab4aa3..9ef67c6d 100644 --- a/Meshtastic.xcodeproj/xcshareddata/xcschemes/Meshtastic.xcscheme +++ b/Meshtastic.xcodeproj/xcshareddata/xcschemes/Meshtastic.xcscheme @@ -33,7 +33,7 @@ skipped = "NO"> diff --git a/Meshtastic.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Meshtastic.xcworkspace/xcshareddata/swiftpm/Package.resolved index 10db40a7..e358787d 100644 --- a/Meshtastic.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Meshtastic.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "74b3ad6215f078d89f4436b6ce0e318f145842efa3453bbe055ab76057de7d6b", + "originHash" : "af29d93455cb8f728684674f544d815b5becb17e049287cc1df8079a4855d0fc", "pins" : [ { "identity" : "cocoamqtt", @@ -42,8 +42,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-protobuf.git", "state" : { - "revision" : "9f0c76544701845ad98716f3f6a774a892152bcb", - "version" : "1.26.0" + "revision" : "d57a5aecf24a25b32ec4a74be2f5d0a995a47c4b", + "version" : "1.27.0" } } ], diff --git a/Meshtastic/AppState.swift b/Meshtastic/AppState.swift new file mode 100644 index 00000000..72f0d23a --- /dev/null +++ b/Meshtastic/AppState.swift @@ -0,0 +1,33 @@ +import Combine +import SwiftUI + +class AppState: ObservableObject { + @Published + var router: Router + + @Published + var unreadChannelMessages: Int + + @Published + var unreadDirectMessages: Int + + var totalUnreadMessages: Int { + unreadChannelMessages + unreadDirectMessages + } + + private var cancellables: Set = [] + + init(router: Router) { + self.router = router + self.unreadChannelMessages = 0 + self.unreadDirectMessages = 0 + + // Keep app icon badge count in sync with messages read status + $unreadChannelMessages.combineLatest($unreadDirectMessages) + .sink(receiveValue: { badgeCounts in + UNUserNotificationCenter.current() + .setBadgeCount(badgeCounts.0 + badgeCounts.1) + }) + .store(in: &cancellables) + } +} diff --git a/Meshtastic/Assets.xcassets/ANDROIDSIM.imageset/Contents.json b/Meshtastic/Assets.xcassets/ANDROIDSIM.imageset/Contents.json index 06ebbbb1..1482d0f8 100644 --- a/Meshtastic/Assets.xcassets/ANDROIDSIM.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/ANDROIDSIM.imageset/Contents.json @@ -1,19 +1,8 @@ { "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, { "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { diff --git a/Meshtastic/Assets.xcassets/ANDROIDSIM.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/ANDROIDSIM.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/ANDROIDSIM.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/ANDROIDSIM.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/ANDROIDSIM.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/ANDROIDSIM.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/CANARY1.imageset/Contents.json b/Meshtastic/Assets.xcassets/CANARY1.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/CANARY1.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/CANARY1.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/CANARY1.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/CANARY1.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/CANARY1.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/CANARY1.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/CANARY1.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/CANARY1.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/CANARY1.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/CANARY1.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/CDEBYTEEORAS3.imageset/Contents.json b/Meshtastic/Assets.xcassets/CDEBYTEEORAS3.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/CDEBYTEEORAS3.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/CDEBYTEEORAS3.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/CDEBYTEEORAS3.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/CDEBYTEEORAS3.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/CDEBYTEEORAS3.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/CDEBYTEEORAS3.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/CDEBYTEEORAS3.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/CDEBYTEEORAS3.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/CDEBYTEEORAS3.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/CDEBYTEEORAS3.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/CHATTER2.imageset/Contents.json b/Meshtastic/Assets.xcassets/CHATTER2.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/CHATTER2.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/CHATTER2.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/CHATTER2.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/CHATTER2.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/CHATTER2.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/CHATTER2.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/CHATTER2.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/CHATTER2.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/CHATTER2.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/CHATTER2.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/DIYV1.imageset/Contents.json b/Meshtastic/Assets.xcassets/DIYV1.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/DIYV1.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/DIYV1.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/DIYV1.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/DIYV1.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/DIYV1.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/DIYV1.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/DIYV1.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/DIYV1.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/DIYV1.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/DIYV1.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/DRDEV.imageset/Contents.json b/Meshtastic/Assets.xcassets/DRDEV.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/DRDEV.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/DRDEV.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/DRDEV.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/DRDEV.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/DRDEV.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/DRDEV.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/DRDEV.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/DRDEV.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/DRDEV.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/DRDEV.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/EBYTEESP32S3.imageset/Contents.json b/Meshtastic/Assets.xcassets/EBYTEESP32S3.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/EBYTEESP32S3.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/EBYTEESP32S3.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/EBYTEESP32S3.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/EBYTEESP32S3.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/EBYTEESP32S3.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/EBYTEESP32S3.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/EBYTEESP32S3.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/EBYTEESP32S3.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/EBYTEESP32S3.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/EBYTEESP32S3.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/ESP32S3PICO.imageset/Contents.json b/Meshtastic/Assets.xcassets/ESP32S3PICO.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/ESP32S3PICO.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/ESP32S3PICO.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/ESP32S3PICO.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/ESP32S3PICO.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/ESP32S3PICO.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/ESP32S3PICO.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/ESP32S3PICO.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/ESP32S3PICO.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/ESP32S3PICO.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/ESP32S3PICO.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/GENIEBLOCKS.imageset/Contents.json b/Meshtastic/Assets.xcassets/GENIEBLOCKS.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/GENIEBLOCKS.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/GENIEBLOCKS.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/GENIEBLOCKS.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/GENIEBLOCKS.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/GENIEBLOCKS.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/GENIEBLOCKS.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/GENIEBLOCKS.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/GENIEBLOCKS.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/GENIEBLOCKS.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/GENIEBLOCKS.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECHT62.imageset/Contents.json b/Meshtastic/Assets.xcassets/HELTECHT62.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/HELTECHT62.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/HELTECHT62.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/HELTECHT62.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECHT62.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECHT62.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/HELTECHT62.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECHT62.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECHT62.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/HELTECHT62.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECHT62.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECV1.imageset/Contents.json b/Meshtastic/Assets.xcassets/HELTECV1.imageset/Contents.json deleted file mode 100644 index 3055cd51..00000000 --- a/Meshtastic/Assets.xcassets/HELTECV1.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "Heltec_turq.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "Heltec_turq 1.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "Heltec_turq 2.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/HELTECV1.imageset/Heltec_turq 1.png b/Meshtastic/Assets.xcassets/HELTECV1.imageset/Heltec_turq 1.png deleted file mode 100644 index c4454bcc..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECV1.imageset/Heltec_turq 1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECV1.imageset/Heltec_turq 2.png b/Meshtastic/Assets.xcassets/HELTECV1.imageset/Heltec_turq 2.png deleted file mode 100644 index c4454bcc..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECV1.imageset/Heltec_turq 2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECV1.imageset/Heltec_turq.png b/Meshtastic/Assets.xcassets/HELTECV1.imageset/Heltec_turq.png deleted file mode 100644 index c4454bcc..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECV1.imageset/Heltec_turq.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECV20.imageset/Contents.json b/Meshtastic/Assets.xcassets/HELTECV20.imageset/Contents.json deleted file mode 100644 index a02a95fb..00000000 --- a/Meshtastic/Assets.xcassets/HELTECV20.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "Heltec_turq 1.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "Heltec_turq 2.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "Heltec_turq.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/HELTECV20.imageset/Heltec_turq 1.png b/Meshtastic/Assets.xcassets/HELTECV20.imageset/Heltec_turq 1.png deleted file mode 100644 index c4454bcc..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECV20.imageset/Heltec_turq 1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECV20.imageset/Heltec_turq 2.png b/Meshtastic/Assets.xcassets/HELTECV20.imageset/Heltec_turq 2.png deleted file mode 100644 index c4454bcc..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECV20.imageset/Heltec_turq 2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECV20.imageset/Heltec_turq.png b/Meshtastic/Assets.xcassets/HELTECV20.imageset/Heltec_turq.png deleted file mode 100644 index c4454bcc..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECV20.imageset/Heltec_turq.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECV21.imageset/Contents.json b/Meshtastic/Assets.xcassets/HELTECV21.imageset/Contents.json deleted file mode 100644 index a02a95fb..00000000 --- a/Meshtastic/Assets.xcassets/HELTECV21.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "Heltec_turq 1.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "Heltec_turq 2.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "Heltec_turq.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/HELTECV21.imageset/Heltec_turq 1.png b/Meshtastic/Assets.xcassets/HELTECV21.imageset/Heltec_turq 1.png deleted file mode 100644 index c4454bcc..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECV21.imageset/Heltec_turq 1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECV21.imageset/Heltec_turq 2.png b/Meshtastic/Assets.xcassets/HELTECV21.imageset/Heltec_turq 2.png deleted file mode 100644 index c4454bcc..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECV21.imageset/Heltec_turq 2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECV21.imageset/Heltec_turq.png b/Meshtastic/Assets.xcassets/HELTECV21.imageset/Heltec_turq.png deleted file mode 100644 index c4454bcc..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECV21.imageset/Heltec_turq.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECV3.imageset/Contents.json b/Meshtastic/Assets.xcassets/HELTECV3.imageset/Contents.json index a02a95fb..98595042 100644 --- a/Meshtastic/Assets.xcassets/HELTECV3.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/HELTECV3.imageset/Contents.json @@ -1,19 +1,8 @@ { "images" : [ - { - "filename" : "Heltec_turq 1.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "Heltec_turq 2.png", - "idiom" : "universal", - "scale" : "2x" - }, { "filename" : "Heltec_turq.png", - "idiom" : "universal", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { diff --git a/Meshtastic/Assets.xcassets/HELTECV3.imageset/Heltec_turq 1.png b/Meshtastic/Assets.xcassets/HELTECV3.imageset/Heltec_turq 1.png deleted file mode 100644 index c4454bcc..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECV3.imageset/Heltec_turq 1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECV3.imageset/Heltec_turq 2.png b/Meshtastic/Assets.xcassets/HELTECV3.imageset/Heltec_turq 2.png deleted file mode 100644 index c4454bcc..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECV3.imageset/Heltec_turq 2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/Contents.json b/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/Contents.json index d363b690..1a8d07dc 100644 --- a/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/Contents.json @@ -2,18 +2,7 @@ "images" : [ { "filename" : "Paper-Meshtastic-2 copy.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "Paper-Meshtastic-2 copy 1.jpg", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "Paper-Meshtastic-2 copy 2.jpg", - "idiom" : "universal", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/Paper-Meshtastic-2 copy 1.jpg b/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/Paper-Meshtastic-2 copy 1.jpg deleted file mode 100644 index 36692599..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/Paper-Meshtastic-2 copy 1.jpg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/Paper-Meshtastic-2 copy 2.jpg b/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/Paper-Meshtastic-2 copy 2.jpg deleted file mode 100644 index 36692599..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/Paper-Meshtastic-2 copy 2.jpg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPERV10.imageset/Contents.json b/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPERV10.imageset/Contents.json deleted file mode 100644 index d363b690..00000000 --- a/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPERV10.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "Paper-Meshtastic-2 copy.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "Paper-Meshtastic-2 copy 1.jpg", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "Paper-Meshtastic-2 copy 2.jpg", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPERV10.imageset/Paper-Meshtastic-2 copy 1.jpg b/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPERV10.imageset/Paper-Meshtastic-2 copy 1.jpg deleted file mode 100644 index 36692599..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPERV10.imageset/Paper-Meshtastic-2 copy 1.jpg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPERV10.imageset/Paper-Meshtastic-2 copy 2.jpg b/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPERV10.imageset/Paper-Meshtastic-2 copy 2.jpg deleted file mode 100644 index 36692599..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPERV10.imageset/Paper-Meshtastic-2 copy 2.jpg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPERV10.imageset/Paper-Meshtastic-2 copy.jpg b/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPERV10.imageset/Paper-Meshtastic-2 copy.jpg deleted file mode 100644 index 36692599..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPERV10.imageset/Paper-Meshtastic-2 copy.jpg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/Contents.json b/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/Contents.json index 0a33483b..3b6b227c 100644 --- a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/Contents.json @@ -1,19 +1,8 @@ { "images" : [ { - "filename" : "images.jpeg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "images 1.jpeg", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "images 2.jpeg", - "idiom" : "universal", - "scale" : "3x" + "filename" : "images.png", + "idiom" : "universal" } ], "info" : { diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/images 1.jpeg b/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/images 1.jpeg deleted file mode 100644 index adbb512c..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/images 1.jpeg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/images 2.jpeg b/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/images 2.jpeg deleted file mode 100644 index adbb512c..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/images 2.jpeg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/images.jpeg b/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/images.jpeg deleted file mode 100644 index adbb512c..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/images.jpeg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/images.png b/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/images.png new file mode 100644 index 00000000..4e9336c5 Binary files /dev/null and b/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/images.png differ diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKERV10.imageset/Contents.json b/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKERV10.imageset/Contents.json deleted file mode 100644 index 0a33483b..00000000 --- a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKERV10.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "images.jpeg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "images 1.jpeg", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "images 2.jpeg", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKERV10.imageset/images 1.jpeg b/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKERV10.imageset/images 1.jpeg deleted file mode 100644 index adbb512c..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKERV10.imageset/images 1.jpeg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKERV10.imageset/images 2.jpeg b/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKERV10.imageset/images 2.jpeg deleted file mode 100644 index adbb512c..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKERV10.imageset/images 2.jpeg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKERV10.imageset/images.jpeg b/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKERV10.imageset/images.jpeg deleted file mode 100644 index adbb512c..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKERV10.imageset/images.jpeg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/Contents.json b/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/Contents.json index 7f0651c4..aed717e4 100644 --- a/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/Contents.json @@ -1,19 +1,8 @@ { "images" : [ - { - "filename" : "heltecwsl 2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "heltecwsl 1.png", - "idiom" : "universal", - "scale" : "2x" - }, { "filename" : "heltecwsl.png", - "idiom" : "universal", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { diff --git a/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/heltecwsl 1.png b/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/heltecwsl 1.png deleted file mode 100644 index 63ecd895..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/heltecwsl 1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/heltecwsl 2.png b/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/heltecwsl 2.png deleted file mode 100644 index 63ecd895..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/heltecwsl 2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/heltecwsl.png b/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/heltecwsl.png index 63ecd895..8881d0e1 100644 Binary files a/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/heltecwsl.png and b/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/heltecwsl.png differ diff --git a/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/Contents.json b/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/Contents.json index dc52568b..892d20eb 100644 --- a/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/Contents.json @@ -1,19 +1,8 @@ { "images" : [ { - "filename" : "tbeam_supreme 2.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "tbeam_supreme 1.jpg", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "tbeam_supreme.jpg", - "idiom" : "universal", - "scale" : "3x" + "filename" : "tbeam_supreme.png", + "idiom" : "universal" } ], "info" : { diff --git a/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/tbeam_supreme 1.jpg b/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/tbeam_supreme 1.jpg deleted file mode 100644 index 47fa5964..00000000 Binary files a/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/tbeam_supreme 1.jpg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/tbeam_supreme 2.jpg b/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/tbeam_supreme 2.jpg deleted file mode 100644 index 47fa5964..00000000 Binary files a/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/tbeam_supreme 2.jpg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/tbeam_supreme.jpg b/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/tbeam_supreme.jpg deleted file mode 100644 index 47fa5964..00000000 Binary files a/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/tbeam_supreme.jpg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/tbeam_supreme.png b/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/tbeam_supreme.png new file mode 100644 index 00000000..6a618653 Binary files /dev/null and b/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/tbeam_supreme.png differ diff --git a/Meshtastic/Assets.xcassets/LORARELAYV1.imageset/Contents.json b/Meshtastic/Assets.xcassets/LORARELAYV1.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/LORARELAYV1.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/LORARELAYV1.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/LORARELAYV1.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/LORARELAYV1.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/LORARELAYV1.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/LORARELAYV1.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/LORARELAYV1.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/LORARELAYV1.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/LORARELAYV1.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/LORARELAYV1.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/LORATYPE.imageset/Contents.json b/Meshtastic/Assets.xcassets/LORATYPE.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/LORATYPE.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/LORATYPE.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/LORATYPE.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/LORATYPE.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/LORATYPE.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/LORATYPE.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/LORATYPE.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/LORATYPE.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/LORATYPE.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/LORATYPE.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/M5STACK.imageset/Contents.json b/Meshtastic/Assets.xcassets/M5STACK.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/M5STACK.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/M5STACK.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/M5STACK.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/M5STACK.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/M5STACK.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/M5STACK.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/M5STACK.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/M5STACK.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/M5STACK.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/M5STACK.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/NANOG1.imageset/2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 2.png b/Meshtastic/Assets.xcassets/NANOG1.imageset/2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 2.png deleted file mode 100644 index 7c5895c3..00000000 Binary files a/Meshtastic/Assets.xcassets/NANOG1.imageset/2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/NANOG1.imageset/2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 3.png b/Meshtastic/Assets.xcassets/NANOG1.imageset/2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 3.png deleted file mode 100644 index 7c5895c3..00000000 Binary files a/Meshtastic/Assets.xcassets/NANOG1.imageset/2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/NANOG1.imageset/Contents.json b/Meshtastic/Assets.xcassets/NANOG1.imageset/Contents.json index dab7c834..e8161263 100644 --- a/Meshtastic/Assets.xcassets/NANOG1.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/NANOG1.imageset/Contents.json @@ -1,19 +1,8 @@ { "images" : [ - { - "filename" : "2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 3.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 2.png", - "idiom" : "universal", - "scale" : "2x" - }, { "filename" : "2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 1.png", - "idiom" : "universal", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { diff --git a/Meshtastic/Assets.xcassets/NANOG1EXPLORER.imageset/2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 1.png b/Meshtastic/Assets.xcassets/NANOG1EXPLORER.imageset/2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 1.png deleted file mode 100644 index 7c5895c3..00000000 Binary files a/Meshtastic/Assets.xcassets/NANOG1EXPLORER.imageset/2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/NANOG1EXPLORER.imageset/2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 2.png b/Meshtastic/Assets.xcassets/NANOG1EXPLORER.imageset/2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 2.png deleted file mode 100644 index 7c5895c3..00000000 Binary files a/Meshtastic/Assets.xcassets/NANOG1EXPLORER.imageset/2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/NANOG1EXPLORER.imageset/2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 3.png b/Meshtastic/Assets.xcassets/NANOG1EXPLORER.imageset/2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 3.png deleted file mode 100644 index 7c5895c3..00000000 Binary files a/Meshtastic/Assets.xcassets/NANOG1EXPLORER.imageset/2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/NANOG1EXPLORER.imageset/Contents.json b/Meshtastic/Assets.xcassets/NANOG1EXPLORER.imageset/Contents.json deleted file mode 100644 index dab7c834..00000000 --- a/Meshtastic/Assets.xcassets/NANOG1EXPLORER.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 3.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 2.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 1.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/NANOG2ULTRA.imageset/Contents.json b/Meshtastic/Assets.xcassets/NANOG2ULTRA.imageset/Contents.json index 736a3f97..2f074381 100644 --- a/Meshtastic/Assets.xcassets/NANOG2ULTRA.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/NANOG2ULTRA.imageset/Contents.json @@ -2,18 +2,7 @@ "images" : [ { "filename" : "nano_g2_ultra_product_image.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "nano_g2_ultra_product_image 1.jpg", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "nano_g2_ultra_product_image 2.jpg", - "idiom" : "universal", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { diff --git a/Meshtastic/Assets.xcassets/NANOG2ULTRA.imageset/nano_g2_ultra_product_image 1.jpg b/Meshtastic/Assets.xcassets/NANOG2ULTRA.imageset/nano_g2_ultra_product_image 1.jpg deleted file mode 100644 index 18f2b472..00000000 Binary files a/Meshtastic/Assets.xcassets/NANOG2ULTRA.imageset/nano_g2_ultra_product_image 1.jpg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/NANOG2ULTRA.imageset/nano_g2_ultra_product_image 2.jpg b/Meshtastic/Assets.xcassets/NANOG2ULTRA.imageset/nano_g2_ultra_product_image 2.jpg deleted file mode 100644 index 18f2b472..00000000 Binary files a/Meshtastic/Assets.xcassets/NANOG2ULTRA.imageset/nano_g2_ultra_product_image 2.jpg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/NRF52840DK.imageset/Contents.json b/Meshtastic/Assets.xcassets/NRF52840DK.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/NRF52840DK.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/NRF52840DK.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/NRF52840DK.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/NRF52840DK.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/NRF52840DK.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/NRF52840DK.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/NRF52840DK.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/NRF52840DK.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/NRF52840DK.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/NRF52840DK.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/NRF52840PCA10059.imageset/Contents.json b/Meshtastic/Assets.xcassets/NRF52840PCA10059.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/NRF52840PCA10059.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/NRF52840PCA10059.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/NRF52840PCA10059.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/NRF52840PCA10059.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/NRF52840PCA10059.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/NRF52840PCA10059.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/NRF52840PCA10059.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/NRF52840PCA10059.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/NRF52840PCA10059.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/NRF52840PCA10059.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/NRF52UNKNOWN.imageset/Contents.json b/Meshtastic/Assets.xcassets/NRF52UNKNOWN.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/NRF52UNKNOWN.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/NRF52UNKNOWN.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/NRF52UNKNOWN.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/NRF52UNKNOWN.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/NRF52UNKNOWN.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/NRF52UNKNOWN.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/NRF52UNKNOWN.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/NRF52UNKNOWN.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/NRF52UNKNOWN.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/NRF52UNKNOWN.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/PICOMPUTERS3.imageset/Contents.json b/Meshtastic/Assets.xcassets/PICOMPUTERS3.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/PICOMPUTERS3.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/PICOMPUTERS3.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/PICOMPUTERS3.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/PICOMPUTERS3.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/PICOMPUTERS3.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/PICOMPUTERS3.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/PICOMPUTERS3.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/PICOMPUTERS3.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/PICOMPUTERS3.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/PICOMPUTERS3.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/PORTDUINO.imageset/Contents.json b/Meshtastic/Assets.xcassets/PORTDUINO.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/PORTDUINO.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/PORTDUINO.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/PORTDUINO.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/PORTDUINO.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/PORTDUINO.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/PORTDUINO.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/PORTDUINO.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/PORTDUINO.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/PORTDUINO.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/PORTDUINO.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/PPR.imageset/Contents.json b/Meshtastic/Assets.xcassets/PPR.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/PPR.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/PPR.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/PPR.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/PPR.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/PPR.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/PPR.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/PPR.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/PPR.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/PPR.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/PPR.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/PRIVATEHW.imageset/Contents.json b/Meshtastic/Assets.xcassets/PRIVATEHW.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/PRIVATEHW.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/PRIVATEHW.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/PRIVATEHW.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/PRIVATEHW.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/PRIVATEHW.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/PRIVATEHW.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/PRIVATEHW.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/PRIVATEHW.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/PRIVATEHW.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/PRIVATEHW.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/RAK11200.imageset/Contents.json b/Meshtastic/Assets.xcassets/RAK11200.imageset/Contents.json index 0038d1c1..ed6c2585 100644 --- a/Meshtastic/Assets.xcassets/RAK11200.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/RAK11200.imageset/Contents.json @@ -1,19 +1,8 @@ { "images" : [ - { - "filename" : "RAK_DEV_KIT-2 1.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "RAK_DEV_KIT-1.jpg", - "idiom" : "universal", - "scale" : "2x" - }, { "filename" : "RAK_DEV_KIT-2.jpg", - "idiom" : "universal", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { diff --git a/Meshtastic/Assets.xcassets/RAK11200.imageset/RAK_DEV_KIT-1.jpg b/Meshtastic/Assets.xcassets/RAK11200.imageset/RAK_DEV_KIT-1.jpg deleted file mode 100644 index 9300bed0..00000000 Binary files a/Meshtastic/Assets.xcassets/RAK11200.imageset/RAK_DEV_KIT-1.jpg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/RAK11200.imageset/RAK_DEV_KIT-2 1.jpg b/Meshtastic/Assets.xcassets/RAK11200.imageset/RAK_DEV_KIT-2 1.jpg deleted file mode 100644 index 9300bed0..00000000 Binary files a/Meshtastic/Assets.xcassets/RAK11200.imageset/RAK_DEV_KIT-2 1.jpg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/RAK11310.imageset/Contents.json b/Meshtastic/Assets.xcassets/RAK11310.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/RAK11310.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/RAK11310.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/RAK11310.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/RAK11310.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/RAK11310.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/RAK11310.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/RAK11310.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/RAK11310.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/RAK11310.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/RAK11310.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/RAK4631.imageset/Contents.json b/Meshtastic/Assets.xcassets/RAK4631.imageset/Contents.json index 52e85275..feb2e6c0 100644 --- a/Meshtastic/Assets.xcassets/RAK4631.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/RAK4631.imageset/Contents.json @@ -1,19 +1,8 @@ { "images" : [ - { - "filename" : "RAK 6.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "RAK 5.png", - "idiom" : "universal", - "scale" : "2x" - }, { "filename" : "RAK 4.png", - "idiom" : "universal", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { diff --git a/Meshtastic/Assets.xcassets/RAK4631.imageset/RAK 5.png b/Meshtastic/Assets.xcassets/RAK4631.imageset/RAK 5.png deleted file mode 100644 index e34322b8..00000000 Binary files a/Meshtastic/Assets.xcassets/RAK4631.imageset/RAK 5.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/RAK4631.imageset/RAK 6.png b/Meshtastic/Assets.xcassets/RAK4631.imageset/RAK 6.png deleted file mode 100644 index e34322b8..00000000 Binary files a/Meshtastic/Assets.xcassets/RAK4631.imageset/RAK 6.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/RPIPICO.imageset/Contents.json b/Meshtastic/Assets.xcassets/RPIPICO.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/RPIPICO.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/RPIPICO.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/RPIPICO.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/RPIPICO.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/RPIPICO.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/RPIPICO.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/RPIPICO.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/RPIPICO.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/RPIPICO.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/RPIPICO.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/Contents.json b/Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/Contents.json index af90d4b0..f8a70d36 100644 --- a/Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/Contents.json @@ -1,19 +1,8 @@ { "images" : [ { - "filename" : "solarnode.jpeg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "solarnode 1.jpeg", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "solar_node.jpeg", - "idiom" : "universal", - "scale" : "3x" + "filename" : "solar_node.png", + "idiom" : "universal" } ], "info" : { diff --git a/Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solar_node.jpeg b/Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solar_node.jpeg deleted file mode 100644 index bb84f38f..00000000 Binary files a/Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solar_node.jpeg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solar_node.png b/Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solar_node.png new file mode 100644 index 00000000..fae3ea0d Binary files /dev/null and b/Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solar_node.png differ diff --git a/Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solarnode 1.jpeg b/Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solarnode 1.jpeg deleted file mode 100644 index 547de73b..00000000 Binary files a/Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solarnode 1.jpeg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solarnode.jpeg b/Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solarnode.jpeg deleted file mode 100644 index 547de73b..00000000 Binary files a/Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solarnode.jpeg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/STATIONG1.imageset/Contents.json b/Meshtastic/Assets.xcassets/STATIONG1.imageset/Contents.json index 59d1fc69..d72dfe5f 100644 --- a/Meshtastic/Assets.xcassets/STATIONG1.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/STATIONG1.imageset/Contents.json @@ -2,18 +2,7 @@ "images" : [ { "filename" : "meshtastic_mesh_device_station_edition_overview 1.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "meshtastic_mesh_device_station_edition_overview 2.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "meshtastic_mesh_device_station_edition_overview 3.png", - "idiom" : "universal", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { diff --git a/Meshtastic/Assets.xcassets/STATIONG1.imageset/meshtastic_mesh_device_station_edition_overview 2.png b/Meshtastic/Assets.xcassets/STATIONG1.imageset/meshtastic_mesh_device_station_edition_overview 2.png deleted file mode 100644 index a8b03e02..00000000 Binary files a/Meshtastic/Assets.xcassets/STATIONG1.imageset/meshtastic_mesh_device_station_edition_overview 2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/STATIONG1.imageset/meshtastic_mesh_device_station_edition_overview 3.png b/Meshtastic/Assets.xcassets/STATIONG1.imageset/meshtastic_mesh_device_station_edition_overview 3.png deleted file mode 100644 index a8b03e02..00000000 Binary files a/Meshtastic/Assets.xcassets/STATIONG1.imageset/meshtastic_mesh_device_station_edition_overview 3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TBEAM.imageset/Contents.json b/Meshtastic/Assets.xcassets/TBEAM.imageset/Contents.json index 382e42da..64a09f22 100644 --- a/Meshtastic/Assets.xcassets/TBEAM.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/TBEAM.imageset/Contents.json @@ -1,19 +1,8 @@ { "images" : [ - { - "filename" : "tbeam 2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "tbeam 1.png", - "idiom" : "universal", - "scale" : "2x" - }, { "filename" : "tbeam.png", - "idiom" : "universal", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { diff --git a/Meshtastic/Assets.xcassets/TBEAM.imageset/tbeam 1.png b/Meshtastic/Assets.xcassets/TBEAM.imageset/tbeam 1.png deleted file mode 100644 index 75fec7be..00000000 Binary files a/Meshtastic/Assets.xcassets/TBEAM.imageset/tbeam 1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TBEAM.imageset/tbeam 2.png b/Meshtastic/Assets.xcassets/TBEAM.imageset/tbeam 2.png deleted file mode 100644 index 75fec7be..00000000 Binary files a/Meshtastic/Assets.xcassets/TBEAM.imageset/tbeam 2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TBEAMV0P7.imageset/Contents.json b/Meshtastic/Assets.xcassets/TBEAMV0P7.imageset/Contents.json deleted file mode 100644 index 382e42da..00000000 --- a/Meshtastic/Assets.xcassets/TBEAMV0P7.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "tbeam 2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "tbeam 1.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "tbeam.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/TBEAMV0P7.imageset/tbeam 1.png b/Meshtastic/Assets.xcassets/TBEAMV0P7.imageset/tbeam 1.png deleted file mode 100644 index 75fec7be..00000000 Binary files a/Meshtastic/Assets.xcassets/TBEAMV0P7.imageset/tbeam 1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TBEAMV0P7.imageset/tbeam 2.png b/Meshtastic/Assets.xcassets/TBEAMV0P7.imageset/tbeam 2.png deleted file mode 100644 index 75fec7be..00000000 Binary files a/Meshtastic/Assets.xcassets/TBEAMV0P7.imageset/tbeam 2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TBEAMV0P7.imageset/tbeam.png b/Meshtastic/Assets.xcassets/TBEAMV0P7.imageset/tbeam.png deleted file mode 100644 index 75fec7be..00000000 Binary files a/Meshtastic/Assets.xcassets/TBEAMV0P7.imageset/tbeam.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TDECK.imageset/Contents.json b/Meshtastic/Assets.xcassets/TDECK.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/TDECK.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/TDECK.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/TDECK.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/TDECK.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TDECK.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/TDECK.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/TDECK.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TDECK.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/TDECK.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/TDECK.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TDLORAC.imageset/Contents.json b/Meshtastic/Assets.xcassets/TDLORAC.imageset/Contents.json deleted file mode 100644 index 05b5d301..00000000 --- a/Meshtastic/Assets.xcassets/TDLORAC.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-2 1.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-2 2.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/TDLORAC.imageset/play_store_icon_114px-2 1.png b/Meshtastic/Assets.xcassets/TDLORAC.imageset/play_store_icon_114px-2 1.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/TDLORAC.imageset/play_store_icon_114px-2 1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TDLORAC.imageset/play_store_icon_114px-2 2.png b/Meshtastic/Assets.xcassets/TDLORAC.imageset/play_store_icon_114px-2 2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/TDLORAC.imageset/play_store_icon_114px-2 2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TDLORAC.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/TDLORAC.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/TDLORAC.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TECHO.imageset/Contents.json b/Meshtastic/Assets.xcassets/TECHO.imageset/Contents.json index 4d28850d..f380b7af 100644 --- a/Meshtastic/Assets.xcassets/TECHO.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/TECHO.imageset/Contents.json @@ -1,19 +1,8 @@ { "images" : [ - { - "filename" : "LILYGO-TTGO-SoftRF-T-Echo-NRF52840-LoRa-SX1262-433-868-915MHz-Wireless-Module-L76K-GPS-1 2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "LILYGO-TTGO-SoftRF-T-Echo-NRF52840-LoRa-SX1262-433-868-915MHz-Wireless-Module-L76K-GPS-1 1.png", - "idiom" : "universal", - "scale" : "2x" - }, { "filename" : "LILYGO-TTGO-SoftRF-T-Echo-NRF52840-LoRa-SX1262-433-868-915MHz-Wireless-Module-L76K-GPS-1.png", - "idiom" : "universal", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { diff --git a/Meshtastic/Assets.xcassets/TECHO.imageset/LILYGO-TTGO-SoftRF-T-Echo-NRF52840-LoRa-SX1262-433-868-915MHz-Wireless-Module-L76K-GPS-1 1.png b/Meshtastic/Assets.xcassets/TECHO.imageset/LILYGO-TTGO-SoftRF-T-Echo-NRF52840-LoRa-SX1262-433-868-915MHz-Wireless-Module-L76K-GPS-1 1.png deleted file mode 100644 index 7b2f9f96..00000000 Binary files a/Meshtastic/Assets.xcassets/TECHO.imageset/LILYGO-TTGO-SoftRF-T-Echo-NRF52840-LoRa-SX1262-433-868-915MHz-Wireless-Module-L76K-GPS-1 1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TECHO.imageset/LILYGO-TTGO-SoftRF-T-Echo-NRF52840-LoRa-SX1262-433-868-915MHz-Wireless-Module-L76K-GPS-1 2.png b/Meshtastic/Assets.xcassets/TECHO.imageset/LILYGO-TTGO-SoftRF-T-Echo-NRF52840-LoRa-SX1262-433-868-915MHz-Wireless-Module-L76K-GPS-1 2.png deleted file mode 100644 index 7b2f9f96..00000000 Binary files a/Meshtastic/Assets.xcassets/TECHO.imageset/LILYGO-TTGO-SoftRF-T-Echo-NRF52840-LoRa-SX1262-433-868-915MHz-Wireless-Module-L76K-GPS-1 2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TLORABOARD.imageset/Contents.json b/Meshtastic/Assets.xcassets/TLORABOARD.imageset/Contents.json new file mode 100644 index 00000000..f8356864 --- /dev/null +++ b/Meshtastic/Assets.xcassets/TLORABOARD.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Meshtastic/Assets.xcassets/TLORABOARD.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.png b/Meshtastic/Assets.xcassets/TLORABOARD.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.png new file mode 100644 index 00000000..ff3da639 Binary files /dev/null and b/Meshtastic/Assets.xcassets/TLORABOARD.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.png differ diff --git a/Meshtastic/Assets.xcassets/TLORAT3S3.imageset/Contents.json b/Meshtastic/Assets.xcassets/TLORAT3S3.imageset/Contents.json deleted file mode 100644 index 75ee9d5f..00000000 --- a/Meshtastic/Assets.xcassets/TLORAT3S3.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 1.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 2.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/TLORAT3S3.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 1.png b/Meshtastic/Assets.xcassets/TLORAT3S3.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 1.png deleted file mode 100644 index fd25f87c..00000000 Binary files a/Meshtastic/Assets.xcassets/TLORAT3S3.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TLORAT3S3.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 2.png b/Meshtastic/Assets.xcassets/TLORAT3S3.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 2.png deleted file mode 100644 index fd25f87c..00000000 Binary files a/Meshtastic/Assets.xcassets/TLORAT3S3.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TLORAT3S3.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.png b/Meshtastic/Assets.xcassets/TLORAT3S3.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.png deleted file mode 100644 index fd25f87c..00000000 Binary files a/Meshtastic/Assets.xcassets/TLORAT3S3.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TLORAV1.imageset/Contents.json b/Meshtastic/Assets.xcassets/TLORAV1.imageset/Contents.json index 6ead5861..093c722d 100644 --- a/Meshtastic/Assets.xcassets/TLORAV1.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/TLORAV1.imageset/Contents.json @@ -1,19 +1,8 @@ { "images" : [ - { - "filename" : "TLORA_olive 2.png", - "idiom" : "universal", - "scale" : "1x" - }, { "filename" : "TLORA_olive 1.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "TLORA_olive 3.png", - "idiom" : "universal", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { diff --git a/Meshtastic/Assets.xcassets/TLORAV1.imageset/TLORA_olive 2.png b/Meshtastic/Assets.xcassets/TLORAV1.imageset/TLORA_olive 2.png deleted file mode 100644 index e8980a2c..00000000 Binary files a/Meshtastic/Assets.xcassets/TLORAV1.imageset/TLORA_olive 2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TLORAV1.imageset/TLORA_olive 3.png b/Meshtastic/Assets.xcassets/TLORAV1.imageset/TLORA_olive 3.png deleted file mode 100644 index e8980a2c..00000000 Binary files a/Meshtastic/Assets.xcassets/TLORAV1.imageset/TLORA_olive 3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TLORAV11P3.imageset/Contents.json b/Meshtastic/Assets.xcassets/TLORAV11P3.imageset/Contents.json deleted file mode 100644 index 41f74464..00000000 --- a/Meshtastic/Assets.xcassets/TLORAV11P3.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "TLORA_olive 5.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "TLORA_olive 3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "TLORA_olive 4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/TLORAV11P3.imageset/TLORA_olive 3.png b/Meshtastic/Assets.xcassets/TLORAV11P3.imageset/TLORA_olive 3.png deleted file mode 100644 index e8980a2c..00000000 Binary files a/Meshtastic/Assets.xcassets/TLORAV11P3.imageset/TLORA_olive 3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TLORAV11P3.imageset/TLORA_olive 4.png b/Meshtastic/Assets.xcassets/TLORAV11P3.imageset/TLORA_olive 4.png deleted file mode 100644 index e8980a2c..00000000 Binary files a/Meshtastic/Assets.xcassets/TLORAV11P3.imageset/TLORA_olive 4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TLORAV11P3.imageset/TLORA_olive 5.png b/Meshtastic/Assets.xcassets/TLORAV11P3.imageset/TLORA_olive 5.png deleted file mode 100644 index e8980a2c..00000000 Binary files a/Meshtastic/Assets.xcassets/TLORAV11P3.imageset/TLORA_olive 5.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TLORAV2.imageset/Contents.json b/Meshtastic/Assets.xcassets/TLORAV2.imageset/Contents.json deleted file mode 100644 index 38e3dcb7..00000000 --- a/Meshtastic/Assets.xcassets/TLORAV2.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.jpeg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp-1.jpeg", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp-2.jpeg", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/TLORAV2.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp-1.jpeg b/Meshtastic/Assets.xcassets/TLORAV2.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp-1.jpeg deleted file mode 100644 index 211ca924..00000000 Binary files a/Meshtastic/Assets.xcassets/TLORAV2.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp-1.jpeg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TLORAV2.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp-2.jpeg b/Meshtastic/Assets.xcassets/TLORAV2.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp-2.jpeg deleted file mode 100644 index 211ca924..00000000 Binary files a/Meshtastic/Assets.xcassets/TLORAV2.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp-2.jpeg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TLORAV2.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.jpeg b/Meshtastic/Assets.xcassets/TLORAV2.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.jpeg deleted file mode 100644 index 211ca924..00000000 Binary files a/Meshtastic/Assets.xcassets/TLORAV2.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.jpeg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TLORAV211P6.imageset/Contents.json b/Meshtastic/Assets.xcassets/TLORAV211P6.imageset/Contents.json deleted file mode 100644 index 0e7bd231..00000000 --- a/Meshtastic/Assets.xcassets/TLORAV211P6.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 1.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/TLORAV211P6.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 1.png b/Meshtastic/Assets.xcassets/TLORAV211P6.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 1.png deleted file mode 100644 index fd25f87c..00000000 Binary files a/Meshtastic/Assets.xcassets/TLORAV211P6.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TLORAV211P6.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 2.png b/Meshtastic/Assets.xcassets/TLORAV211P6.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 2.png deleted file mode 100644 index fd25f87c..00000000 Binary files a/Meshtastic/Assets.xcassets/TLORAV211P6.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TLORAV211P6.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.png b/Meshtastic/Assets.xcassets/TLORAV211P6.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.png deleted file mode 100644 index fd25f87c..00000000 Binary files a/Meshtastic/Assets.xcassets/TLORAV211P6.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TLORAV211P8.imageset/Contents.json b/Meshtastic/Assets.xcassets/TLORAV211P8.imageset/Contents.json deleted file mode 100644 index 75ee9d5f..00000000 --- a/Meshtastic/Assets.xcassets/TLORAV211P8.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 1.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 2.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/TLORAV211P8.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 1.png b/Meshtastic/Assets.xcassets/TLORAV211P8.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 1.png deleted file mode 100644 index fd25f87c..00000000 Binary files a/Meshtastic/Assets.xcassets/TLORAV211P8.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TLORAV211P8.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 2.png b/Meshtastic/Assets.xcassets/TLORAV211P8.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 2.png deleted file mode 100644 index fd25f87c..00000000 Binary files a/Meshtastic/Assets.xcassets/TLORAV211P8.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp 2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TLORAV211P8.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.png b/Meshtastic/Assets.xcassets/TLORAV211P8.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.png deleted file mode 100644 index fd25f87c..00000000 Binary files a/Meshtastic/Assets.xcassets/TLORAV211P8.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TWATCHS3.imageset/Contents.json b/Meshtastic/Assets.xcassets/TWATCHS3.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/TWATCHS3.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/TWATCHS3.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/TWATCHS3.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/TWATCHS3.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TWATCHS3.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/TWATCHS3.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/TWATCHS3.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TWATCHS3.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/TWATCHS3.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/TWATCHS3.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TWCMESHV4.imageset/Contents.json b/Meshtastic/Assets.xcassets/TWCMESHV4.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/TWCMESHV4.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/TWCMESHV4.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/TWCMESHV4.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/TWCMESHV4.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TWCMESHV4.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/TWCMESHV4.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/TWCMESHV4.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TWCMESHV4.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/TWCMESHV4.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/TWCMESHV4.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/UNPHONE.imageset/Contents.json b/Meshtastic/Assets.xcassets/UNPHONE.imageset/Contents.json index 9781e82b..32acad21 100644 --- a/Meshtastic/Assets.xcassets/UNPHONE.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/UNPHONE.imageset/Contents.json @@ -2,18 +2,7 @@ "images" : [ { "filename" : "UNPHONE.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "UNPHONE@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "UNPHONE@3x.png", - "idiom" : "universal", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { diff --git a/Meshtastic/Assets.xcassets/UNPHONE.imageset/UNPHONE@2x.png b/Meshtastic/Assets.xcassets/UNPHONE.imageset/UNPHONE@2x.png deleted file mode 100644 index 06a38558..00000000 Binary files a/Meshtastic/Assets.xcassets/UNPHONE.imageset/UNPHONE@2x.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/UNPHONE.imageset/UNPHONE@3x.png b/Meshtastic/Assets.xcassets/UNPHONE.imageset/UNPHONE@3x.png deleted file mode 100644 index 06a38558..00000000 Binary files a/Meshtastic/Assets.xcassets/UNPHONE.imageset/UNPHONE@3x.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/UNSET.imageset/Contents.json b/Meshtastic/Assets.xcassets/UNSET.imageset/Contents.json index c36a508f..04be44d5 100644 --- a/Meshtastic/Assets.xcassets/UNSET.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/UNSET.imageset/Contents.json @@ -1,19 +1,8 @@ { "images" : [ - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, { "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { diff --git a/Meshtastic/Assets.xcassets/UNSET.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/UNSET.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/UNSET.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/UNSET.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/UNSET.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/UNSET.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/WIPHONE.imageset/Contents.json b/Meshtastic/Assets.xcassets/WIPHONE.imageset/Contents.json deleted file mode 100644 index 06ebbbb1..00000000 --- a/Meshtastic/Assets.xcassets/WIPHONE.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "play_store_icon_114px-2.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "play_store_icon_114px-3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "play_store_icon_114px-4.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Assets.xcassets/WIPHONE.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/WIPHONE.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/WIPHONE.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/WIPHONE.imageset/play_store_icon_114px-3.png b/Meshtastic/Assets.xcassets/WIPHONE.imageset/play_store_icon_114px-3.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/WIPHONE.imageset/play_store_icon_114px-3.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/WIPHONE.imageset/play_store_icon_114px-4.png b/Meshtastic/Assets.xcassets/WIPHONE.imageset/play_store_icon_114px-4.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/WIPHONE.imageset/play_store_icon_114px-4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/logo-black.imageset/Contents.json b/Meshtastic/Assets.xcassets/logo-black.imageset/Contents.json index 5ef35f53..41877b9d 100644 --- a/Meshtastic/Assets.xcassets/logo-black.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/logo-black.imageset/Contents.json @@ -1,19 +1,8 @@ { "images" : [ - { - "filename" : "Mesh_Logo_Black_Small.svg", - "idiom" : "universal", - "scale" : "1x" - }, { "filename" : "Mesh_Logo_Black.svg", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "Mesh_Logo_Black_Large.svg", - "idiom" : "universal", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { diff --git a/Meshtastic/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black_Large.svg b/Meshtastic/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black_Large.svg deleted file mode 100644 index a3eec0a0..00000000 --- a/Meshtastic/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black_Large.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/Meshtastic/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black_Small.svg b/Meshtastic/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black_Small.svg deleted file mode 100644 index b13ff954..00000000 --- a/Meshtastic/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black_Small.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/Meshtastic/Assets.xcassets/logo-white.imageset/Contents.json b/Meshtastic/Assets.xcassets/logo-white.imageset/Contents.json index c2d1f573..c4481011 100644 --- a/Meshtastic/Assets.xcassets/logo-white.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/logo-white.imageset/Contents.json @@ -1,19 +1,8 @@ { "images" : [ - { - "filename" : "Mesh_Logo_White_Small.svg", - "idiom" : "universal", - "scale" : "1x" - }, { "filename" : "Mesh_Logo_White.svg", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "Mesh_Logo_White_Large.svg", - "idiom" : "universal", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { diff --git a/Meshtastic/Assets.xcassets/logo-white.imageset/Mesh_Logo_White_Large.svg b/Meshtastic/Assets.xcassets/logo-white.imageset/Mesh_Logo_White_Large.svg deleted file mode 100644 index 517dd23d..00000000 --- a/Meshtastic/Assets.xcassets/logo-white.imageset/Mesh_Logo_White_Large.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/Meshtastic/Assets.xcassets/logo-white.imageset/Mesh_Logo_White_Small.svg b/Meshtastic/Assets.xcassets/logo-white.imageset/Mesh_Logo_White_Small.svg deleted file mode 100644 index fab02c18..00000000 --- a/Meshtastic/Assets.xcassets/logo-white.imageset/Mesh_Logo_White_Small.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/Meshtastic/Extensions/Constants.swift b/Meshtastic/Extensions/Constants.swift new file mode 100644 index 00000000..03a3cc31 --- /dev/null +++ b/Meshtastic/Extensions/Constants.swift @@ -0,0 +1,11 @@ +import Foundation + +enum Constants { + /// `UInt32.max` or FFFF,FFFF in hex is used to identify messages that are being + /// sent to a channel and are not a DM to an individual user. This is used + /// in the `to` field of some mesh packets. + static let maximumNodeNum = UInt32.max + /// Based on the NUM_RESERVED from the firmware. + /// https://github.com/meshtastic/firmware/blob/46d7b82ac1a4292ba52ca690e1a433d3a501a9e5/src/mesh/NodeDB.cpp#L522 + static let minimumNodeNum = 4 +} diff --git a/Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift b/Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift index 62f7eff0..2ff2746c 100644 --- a/Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift @@ -11,8 +11,11 @@ import MeshtasticProtobufs extension ChannelEntity { var allPrivateMessages: [MessageEntity] { + let context = PersistenceController.shared.container.viewContext + let fetchRequest = MessageEntity.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "channel == %ld AND toUser == nil AND isEmoji == false", self.index) - self.value(forKey: "allPrivateMessages") as? [MessageEntity] ?? [MessageEntity]() + return (try? context.fetch(fetchRequest)) ?? [MessageEntity]() } var unreadMessages: Int { diff --git a/Meshtastic/Extensions/CoreData/ExternalNotificationConfigEntityExtension.swift b/Meshtastic/Extensions/CoreData/ExternalNotificationConfigEntityExtension.swift index 72853b43..5fa8589c 100644 --- a/Meshtastic/Extensions/CoreData/ExternalNotificationConfigEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/ExternalNotificationConfigEntityExtension.swift @@ -23,7 +23,7 @@ extension ExternalNotificationConfigEntity { self.nagTimeout = Int32(config.nagTimeout) self.useI2SAsBuzzer = config.useI2SAsBuzzer } - + func update(with config: ModuleConfig.ExternalNotificationConfig) { enabled = config.enabled usePWM = config.usePwm diff --git a/Meshtastic/Extensions/CoreData/MessageEntityExtension.swift b/Meshtastic/Extensions/CoreData/MessageEntityExtension.swift index 81e106b8..db3a0899 100644 --- a/Meshtastic/Extensions/CoreData/MessageEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/MessageEntityExtension.swift @@ -14,11 +14,19 @@ import SwiftUI extension MessageEntity { var timestamp: Date { - let time = messageTimestamp <= 0 ? receivedTimestamp : messageTimestamp + let time = messageTimestamp return Date(timeIntervalSince1970: TimeInterval(time)) } var canRetry: Bool { return ackError == 9 || ackError == 5 || ackError == 3 } + + var tapbacks: [MessageEntity] { + let context = PersistenceController.shared.container.viewContext + let fetchRequest = MessageEntity.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "replyID == %lld AND isEmoji == true", self.messageId) + + return (try? context.fetch(fetchRequest)) ?? [MessageEntity]() + } } diff --git a/Meshtastic/Extensions/CoreData/MyInfoEntityExtension.swift b/Meshtastic/Extensions/CoreData/MyInfoEntityExtension.swift index 80ba19c8..d2172400 100644 --- a/Meshtastic/Extensions/CoreData/MyInfoEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/MyInfoEntityExtension.swift @@ -10,7 +10,11 @@ import Foundation extension MyInfoEntity { var messageList: [MessageEntity] { - self.value(forKey: "allMessages") as? [MessageEntity] ?? [MessageEntity]() + let context = PersistenceController.shared.container.viewContext + let fetchRequest = MessageEntity.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "toUser == nil") + + return (try? context.fetch(fetchRequest)) ?? [MessageEntity]() } var unreadMessages: Int { diff --git a/Meshtastic/Extensions/CoreData/UserEntityExtension.swift b/Meshtastic/Extensions/CoreData/UserEntityExtension.swift index 0595e752..c91ce34b 100644 --- a/Meshtastic/Extensions/CoreData/UserEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/UserEntityExtension.swift @@ -12,17 +12,65 @@ import MeshtasticProtobufs extension UserEntity { var messageList: [MessageEntity] { - self.value(forKey: "allMessages") as? [MessageEntity] ?? [MessageEntity]() + let context = PersistenceController.shared.container.viewContext + let fetchRequest = MessageEntity.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "((toUser == %@) OR (fromUser == %@)) AND toUser != nil AND fromUser != nil AND isEmoji == false AND admin = false AND portNum != 10", self, self) + + return (try? context.fetch(fetchRequest)) ?? [MessageEntity]() } var sensorMessageList: [MessageEntity] { - self.value(forKey: "detectionSensorMessages") as? [MessageEntity] ?? [MessageEntity]() + let context = PersistenceController.shared.container.viewContext + let fetchRequest = MessageEntity.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "(fromUser == %@) AND portNum = 10", self) + + return (try? context.fetch(fetchRequest)) ?? [MessageEntity]() } var unreadMessages: Int { let unreadMessages = messageList.filter { ($0 as AnyObject).read == false } return unreadMessages.count } + + var hardwareImage: String? { + guard let hwModel else { return nil } + switch hwModel { + case "HELTECV1", "HELTECV3", "HELTECV20", "HELTECV21": + return "HELTECV3" + case "HELTECWIRELESSPAPER", "HELTECWIRELESSPAPERV10": + return "HELTECWIRELESSPAPER" + case "HELTECWIRELESSTRACKER", "HELTECWIRELESSTRACKERV10": + return "HELTECWIRELESSTRACKER" + case "HELTECWSLV3": + return "HELTECWSLV3" + case "LILYGOTBEAMSCORE": + return "LILYGOTBEAMS3CORE" + case "NANOG1", "NANOG1EXPLORER": + return "NANOG1" + case "NANOG2ULTRA": + return "NANOG2ULTRA" + case "RAK4631": + return "RAK4631" + case "RAK11200": + return "RAK11200" + case "SOLAR_NODE": + return "SOLAR_NODE" + case "STATIONG1": + return "STATIONG1" + case "ТВЕАМ", "TBEAMVOP7": + return "ТВЕАМ" + case "TECHO": + return "TECHO" + case "TLORAV1", "TLORAV11P3": + return "TLORAV1" + case "TLORAV2", "TLORAT3S3", "TLORAV211P6", "TLORAV211P8": + return "TLORABOARD" + case "UNPHONE": + return "UNPHONE" + default: + return "UNSET" + } + } } public func createUser(num: Int64, context: NSManagedObjectContext) -> UserEntity { diff --git a/Meshtastic/Extensions/UserDefaults.swift b/Meshtastic/Extensions/UserDefaults.swift index 11049bfb..0fbd680b 100644 --- a/Meshtastic/Extensions/UserDefaults.swift +++ b/Meshtastic/Extensions/UserDefaults.swift @@ -57,7 +57,6 @@ extension UserDefaults { case enableMapTraffic case enableMapPointsOfInterest case enableOfflineMaps - case enableOfflineMapsMBTiles case mapTileServer case enableOverlayServer case mapOverlayServer @@ -121,9 +120,6 @@ extension UserDefaults { @UserDefault(.enableOfflineMaps, defaultValue: false) static var enableOfflineMaps: Bool - @UserDefault(.enableOfflineMapsMBTiles, defaultValue: false) - static var enableOfflineMapsMBTiles: Bool - @UserDefault(.mapTileServer, defaultValue: .openStreetMap) static var mapTileServer: MapTileServer diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index de548ba1..93879e02 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -12,10 +12,12 @@ import OSLog // --------------------------------------------------------------------------------------- class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate, ObservableObject { - var context: NSManagedObjectContext? + let appState: AppState + + let context: NSManagedObjectContext - static let shared = BLEManager() private var centralManager: CBCentralManager! + @Published var peripherals: [Peripheral] = [] @Published var connectedPeripheral: Peripheral! @Published var lastConnectionError: String @@ -24,7 +26,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate @Published var automaticallyReconnect: Bool = true @Published var mqttProxyConnected: Bool = false @Published var mqttError: String = "" - @StateObject var appState = AppState.shared public var minimumVersion = "2.0.0" public var connectedVersion: String public var isConnecting: Bool = false @@ -34,7 +35,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var timeoutTimer: Timer? var timeoutTimerCount = 0 var positionTimer: Timer? - static let emptyNodeNum: UInt32 = 4294967295 let mqttManager = MqttClientProxyManager.shared var wantRangeTestPackets = false var wantStoreAndForwardPackets = false @@ -52,8 +52,15 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let LEGACY_LOGRADIO_UUID = CBUUID(string: "0x6C6FD238-78FA-436B-AACF-15C5BE1EF2E2") let LOGRADIO_UUID = CBUUID(string: "0x5a3d6e49-06e6-4423-9944-e9de8cdf9547") - // MARK: init BLEManager - override init() { + // MARK: init + + init( + appState: AppState, + context: NSManagedObjectContext + ) { + self.appState = appState + self.context = context + self.lastConnectionError = "" self.connectedVersion = "0.0.0" super.init() @@ -238,7 +245,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate subtitle: "\(peripheral.name ?? "unknown".localized)", content: e.localizedDescription, target: "bluetooth", - path: "meshtastic://bluetooth" + path: "meshtastic:///bluetooth" ) ] manager.schedule() @@ -258,7 +265,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate subtitle: "\(peripheral.name ?? "unknown".localized)", content: e.localizedDescription, target: "bluetooth", - path: "meshtastic://bluetooth" + path: "meshtastic:///bluetooth" ) ] manager.schedule() @@ -440,11 +447,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) success = true - let traceRoute = TraceRouteEntity(context: context!) + let traceRoute = TraceRouteEntity(context: context) let nodes = NodeInfoEntity.fetchRequest() nodes.predicate = NSPredicate(format: "num IN %@", [destNum, self.connectedPeripheral.num]) do { - let fetchedNodes = try context!.fetch(nodes) + let fetchedNodes = try context.fetch(nodes) let receivingNode = fetchedNodes.first(where: { $0.num == destNum }) let connectedNode = fetchedNodes.first(where: { $0.num == self.connectedPeripheral.num }) traceRoute.id = Int64(meshPacket.id) @@ -460,10 +467,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } do { - try context!.save() + try context.save() Logger.data.info("💾 Saved TraceRoute sent to node: \(String(receivingNode?.user?.longName ?? "unknown".localized), privacy: .public)") } catch { - context!.rollback() + context.rollback() let nsError = error as NSError Logger.data.error("Error Updating Core Data BluetoothConfigEntity: \(nsError, privacy: .public)") } @@ -559,7 +566,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate Logger.radio.debug("📟 \(log, privacy: .public)") } } - + func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { if let error { @@ -600,8 +607,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate message = "DEBUG | \(message)" } handleRadioLog(radioLog: message) - } - catch { + } catch { // Ignore fail to parse as LogRecord } @@ -642,13 +648,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // Handle Any local only packets we get over BLE case .unknownApp: var nowKnown = false - guard let ctx = context else { - return - } // MyInfo from initial connection if decodedInfo.myInfo.isInitialized && decodedInfo.myInfo.myNodeNum > 0 { - let myInfo = myInfoPacket(myInfo: decodedInfo.myInfo, peripheralId: self.connectedPeripheral.id, context: ctx) + let myInfo = myInfoPacket(myInfo: decodedInfo.myInfo, peripheralId: self.connectedPeripheral.id, context: context) if myInfo != nil { UserDefaults.preferredPeripheralNum = Int(myInfo?.myNodeNum ?? 0) @@ -666,9 +669,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate do { disconnectPeripheral(reconnect: false) try container.restorePersistentStore(from: databasePath) - context?.refreshAllObjects() + context.refreshAllObjects() let request = MyInfoEntity.fetchRequest() - try context?.fetch(request) + try context.fetch(request) UserDefaults.preferredPeripheralNum = Int(myInfo?.myNodeNum ?? 0) connectTo(peripheral: peripheral) Logger.data.notice("🗂️ Restored Core data for /\(UserDefaults.preferredPeripheralNum, privacy: .public)") @@ -684,7 +687,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // NodeInfo if decodedInfo.nodeInfo.num > 0 { nowKnown = true - if let nodeInfo = nodeInfoPacket(nodeInfo: decodedInfo.nodeInfo, channel: decodedInfo.packet.channel, context: ctx) { + if let nodeInfo = nodeInfoPacket(nodeInfo: decodedInfo.nodeInfo, channel: decodedInfo.packet.channel, context: context) { if self.connectedPeripheral != nil && self.connectedPeripheral.num == nodeInfo.num { if nodeInfo.user != nil { connectedPeripheral.shortName = nodeInfo.user?.shortName ?? "?" @@ -696,17 +699,17 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // Channels if decodedInfo.channel.isInitialized && connectedPeripheral != nil { nowKnown = true - channelPacket(channel: decodedInfo.channel, fromNum: Int64(truncatingIfNeeded: connectedPeripheral.num), context: ctx) + channelPacket(channel: decodedInfo.channel, fromNum: Int64(truncatingIfNeeded: connectedPeripheral.num), context: context) } // Config if decodedInfo.config.isInitialized && !invalidVersion && connectedPeripheral != nil { nowKnown = true - localConfig(config: decodedInfo.config, context: ctx, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral.num), nodeLongName: self.connectedPeripheral.longName) + localConfig(config: decodedInfo.config, context: context, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral.num), nodeLongName: self.connectedPeripheral.longName) } // Module Config if decodedInfo.moduleConfig.isInitialized && !invalidVersion && self.connectedPeripheral?.num != 0 { nowKnown = true - moduleConfig(config: decodedInfo.moduleConfig, context: ctx, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral?.num ?? 0), nodeLongName: self.connectedPeripheral.longName) + moduleConfig(config: decodedInfo.moduleConfig, context: context, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral?.num ?? 0), nodeLongName: self.connectedPeripheral.longName) if decodedInfo.moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(decodedInfo.moduleConfig.cannedMessage) { if decodedInfo.moduleConfig.cannedMessage.enabled { _ = self.getCannedMessageModuleMessages(destNum: self.connectedPeripheral.num, wantResponse: true) @@ -716,7 +719,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // Device Metadata if decodedInfo.metadata.firmwareVersion.count > 0 && !invalidVersion { nowKnown = true - deviceMetadataPacket(metadata: decodedInfo.metadata, fromNum: connectedPeripheral.num, context: ctx) + deviceMetadataPacket(metadata: decodedInfo.metadata, fromNum: connectedPeripheral.num, context: context) connectedPeripheral.firmwareVersion = decodedInfo.metadata.firmwareVersion let lastDotIndex = decodedInfo.metadata.firmwareVersion.lastIndex(of: ".") if lastDotIndex == nil { @@ -726,7 +729,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let version = decodedInfo.metadata.firmwareVersion[...(lastDotIndex ?? String.Index(utf16Offset: 6, in: decodedInfo.metadata.firmwareVersion))] nowKnown = true connectedVersion = String(version.dropLast()) - appState.firmwareVersion = connectedVersion UserDefaults.firmwareVersion = connectedVersion } let supportedVersion = connectedVersion == "0.0.0" || self.minimumVersion.compare(connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(connectedVersion, options: .numeric) == .orderedSame @@ -739,22 +741,34 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // Log any other unknownApp calls if !nowKnown { MeshLogger.log("🕸️ MESH PACKET received for Unknown App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") } case .textMessageApp, .detectionSensorApp: - textMessageAppPacket(packet: decodedInfo.packet, wantRangeTestPackets: wantRangeTestPackets, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) + textMessageAppPacket( + packet: decodedInfo.packet, + wantRangeTestPackets: wantRangeTestPackets, + connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), + context: context, + appState: appState + ) case .remoteHardwareApp: MeshLogger.log("🕸️ MESH PACKET received for Remote Hardware App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") case .positionApp: - upsertPositionPacket(packet: decodedInfo.packet, context: context!) + upsertPositionPacket(packet: decodedInfo.packet, context: context) case .waypointApp: - waypointPacket(packet: decodedInfo.packet, context: context!) + waypointPacket(packet: decodedInfo.packet, context: context) case .nodeinfoApp: - if !invalidVersion { upsertNodeInfoPacket(packet: decodedInfo.packet, context: context!) } + if !invalidVersion { upsertNodeInfoPacket(packet: decodedInfo.packet, context: context) } case .routingApp: - if !invalidVersion { routingPacket(packet: decodedInfo.packet, connectedNodeNum: self.connectedPeripheral.num, context: context!) } + if !invalidVersion { routingPacket(packet: decodedInfo.packet, connectedNodeNum: self.connectedPeripheral.num, context: context) } case .adminApp: - adminAppPacket(packet: decodedInfo.packet, context: context!) + adminAppPacket(packet: decodedInfo.packet, context: context) case .replyApp: MeshLogger.log("🕸️ MESH PACKET received for Reply App handling as a text message") - textMessageAppPacket(packet: decodedInfo.packet, wantRangeTestPackets: wantRangeTestPackets, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) + textMessageAppPacket( + packet: decodedInfo.packet, + wantRangeTestPackets: wantRangeTestPackets, + connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), + context: context, + appState: appState + ) case .ipTunnelApp: // MeshLogger.log("🕸️ MESH PACKET received for IP Tunnel App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") MeshLogger.log("🕸️ MESH PACKET received for IP Tunnel App UNHANDLED UNHANDLED") @@ -763,18 +777,24 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate MeshLogger.log("🕸️ MESH PACKET received for Serial App UNHANDLED UNHANDLED") case .storeForwardApp: if wantStoreAndForwardPackets { - storeAndForwardPacket(packet: decodedInfo.packet, connectedNodeNum: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) + storeAndForwardPacket(packet: decodedInfo.packet, connectedNodeNum: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context) } else { MeshLogger.log("🕸️ MESH PACKET received for Store and Forward App - Store and Forward is disabled.") } case .rangeTestApp: if wantRangeTestPackets { - textMessageAppPacket(packet: decodedInfo.packet, wantRangeTestPackets: true, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) + textMessageAppPacket( + packet: decodedInfo.packet, + wantRangeTestPackets: true, + connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), + context: context, + appState: appState + ) } else { MeshLogger.log("🕸️ MESH PACKET received for Range Test App Range testing is disabled.") } case .telemetryApp: - if !invalidVersion { telemetryPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) } + if !invalidVersion { telemetryPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context) } case .textMessageCompressedApp: // MeshLogger.log("🕸️ MESH PACKET received for Text Message Compressed App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") MeshLogger.log("🕸️ MESH PACKET received for Text Message Compressed App UNHANDLED") @@ -795,7 +815,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate MeshLogger.log("🕸️ MESH PACKET received for Audio App UNHANDLED UNHANDLED") case .tracerouteApp: if let routingMessage = try? RouteDiscovery(serializedData: decodedInfo.packet.decoded.payload) { - let traceRoute = getTraceRoute(id: Int64(decodedInfo.packet.decoded.requestID), context: context!) + let traceRoute = getTraceRoute(id: Int64(decodedInfo.packet.decoded.requestID), context: context) traceRoute?.response = true traceRoute?.route = routingMessage.route if routingMessage.route.count == 0 { @@ -806,11 +826,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var routeString = "You --> " var hopNodes: [TraceRouteHopEntity] = [] for node in routingMessage.route { - var hopNode = getNodeInfo(id: Int64(node), context: context!) - if hopNode == nil && hopNode?.num ?? 0 > 0 { - hopNode = createNodeInfo(num: Int64(node), context: context!) + var hopNode = getNodeInfo(id: Int64(node), context: context) + if hopNode == nil && hopNode?.num ?? 0 > 0 && node != 4294967295 { + hopNode = createNodeInfo(num: Int64(node), context: context) } - let traceRouteHop = TraceRouteHopEntity(context: context!) + let traceRouteHop = TraceRouteHopEntity(context: context) traceRouteHop.time = Date() if hopNode?.hasPositions ?? false { traceRoute?.hasPositions = true @@ -832,16 +852,16 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } hopNodes.append(traceRouteHop) } - routeString += "\(hopNode?.user?.longName ?? "unknown".localized) \(hopNode?.viaMqtt ?? false ? "MQTT" : "") --> " + routeString += "\(hopNode?.user?.longName ?? (node == 4294967295 ? "Repeater" : String(hopNode?.num.toHex() ?? "unknown".localized))) \(hopNode?.viaMqtt ?? false ? "MQTT" : "") --> " } routeString += traceRoute?.node?.user?.longName ?? "unknown".localized traceRoute?.routeText = routeString traceRoute?.hops = NSOrderedSet(array: hopNodes) do { - try context!.save() + try context.save() Logger.data.info("💾 Saved Trace Route") } catch { - context!.rollback() + context.rollback() let nsError = error as NSError Logger.data.error("Error Updating Core Data TraceRouteHOp: \(nsError, privacy: .public)") } @@ -855,7 +875,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate MeshLogger.log("🕸️ MESH PACKET received for Neighbor Info App UNHANDLED \(neighborInfo)") } case .paxcounterApp: - paxCounterPacket(packet: decodedInfo.packet, context: context!) + paxCounterPacket(packet: decodedInfo.packet, context: context) case .mapReportApp: MeshLogger.log("🕸️ MESH PACKET received Map Report App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") case .UNRECOGNIZED: @@ -882,7 +902,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(connectedPeripheral.num)) do { - let fetchedNodeInfo = try context?.fetch(fetchNodeInfoRequest) ?? [] + let fetchedNodeInfo = try context.fetch(fetchNodeInfoRequest) ?? [] if fetchedNodeInfo.count == 1 { // Subscribe to Mqtt Client Proxy if enabled if fetchedNodeInfo[0].mqttConfig != nil && fetchedNodeInfo[0].mqttConfig?.enabled ?? false && fetchedNodeInfo[0].mqttConfig?.proxyToClientEnabled ?? false { @@ -893,12 +913,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } // Set initial unread message badge states - let appState = AppState.shared appState.unreadChannelMessages = fetchedNodeInfo[0].myInfo?.unreadMessages ?? 0 appState.unreadDirectMessages = fetchedNodeInfo[0].user?.unreadMessages ?? 0 - // appState.connectedNode = fetchedNodeInfo[0] - UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages - } if fetchedNodeInfo.count == 1 && fetchedNodeInfo[0].rangeTestConfig?.enabled == true { wantRangeTestPackets = true @@ -962,7 +978,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate success = false } else { - guard let context else { return false } let fromUserNum: Int64 = self.connectedPeripheral.num let messageUsers = UserEntity.fetchRequest() @@ -1011,7 +1026,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if toUserNum > 0 { meshPacket.to = UInt32(toUserNum) } else { - meshPacket.to = Self.emptyNodeNum + meshPacket.to = Constants.maximumNodeNum } meshPacket.channel = UInt32(channel) meshPacket.from = UInt32(fromUserNum) @@ -1046,7 +1061,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } } catch { - + Logger.data.error("💥 Send message failure \(self.connectedPeripheral.num.toHex(), privacy: .public) to \(toUserNum.toHex(), privacy: .public)") } } return success @@ -1059,7 +1074,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var success = false let fromNodeNum = UInt32(connectedPeripheral.num) var meshPacket = MeshPacket() - meshPacket.to = Self.emptyNodeNum + meshPacket.to = Constants.maximumNodeNum meshPacket.from = fromNodeNum meshPacket.wantAck = true var dataMessage = DataMessage() @@ -1083,7 +1098,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) success = true - let wayPointEntity = getWaypoint(id: Int64(waypoint.id), context: context!) + let wayPointEntity = getWaypoint(id: Int64(waypoint.id), context: context) wayPointEntity.id = Int64(waypoint.id) wayPointEntity.name = waypoint.name.count >= 1 ? waypoint.name : "Dropped Pin" wayPointEntity.longDescription = waypoint.description_p @@ -1106,10 +1121,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate wayPointEntity.lastUpdated = Date() } do { - try context!.save() + try context.save() Logger.data.info("💾 Updated Waypoint from Waypoint App Packet From: \(fromNodeNum.toHex(), privacy: .public)") } catch { - context!.rollback() + context.rollback() let nsError = error as NSError Logger.data.error("Error Saving NodeInfoEntity from WAYPOINT_APP \(nsError, privacy: .public)") } @@ -1117,6 +1132,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return success } + @MainActor public func getPositionFromPhoneGPS(destNum: Int64) -> Position? { var positionPacket = Position() if #available(iOS 17.0, macOS 14.0, *) { @@ -1162,6 +1178,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return positionPacket } + @MainActor public func setFixedPosition(fromUser: UserEntity, channel: Int32) -> Bool { var adminPacket = AdminMessage() guard let positionPacket = getPositionFromPhoneGPS(destNum: fromUser.num) else { @@ -1216,6 +1233,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return false } + @MainActor public func sendPosition(channel: Int32, destNum: Int64, wantResponse: Bool) -> Bool { let fromNodeNum = connectedPeripheral.num guard let positionPacket = getPositionFromPhoneGPS(destNum: destNum) else { @@ -1256,6 +1274,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return false } } + + @MainActor @objc func positionTimerFired(timer: Timer) { // Check for connected node if connectedPeripheral != nil { @@ -1503,7 +1523,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let fetchMyInfoRequest = MyInfoEntity.fetchRequest() fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(connectedPeripheral.num)) do { - let fetchedMyInfo = try context?.fetch(fetchMyInfoRequest) ?? [] + let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) ?? [] if fetchedMyInfo.count == 1 { i = Int32(fetchedMyInfo[0].channels?.count ?? -1) myInfo = fetchedMyInfo[0] @@ -1655,12 +1675,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { do { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) - context!.delete(node.user!) - context!.delete(node) - try context!.save() + context.delete(node.user!) + context.delete(node) + try context.save() return true } catch { - context!.rollback() + context.rollback() let nsError = error as NSError Logger.data.error("🚫 Error deleting node from core data: \(nsError)") } @@ -1769,7 +1789,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let messageDescription = "🛟 Saved Bluetooth Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertBluetoothConfigPacket(config: config, nodeNum: toUser.num, context: context!) + upsertBluetoothConfigPacket(config: config, nodeNum: toUser.num, context: context) return Int64(meshPacket.id) } @@ -1797,7 +1817,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.decoded = dataMessage let messageDescription = "🛟 Saved Device Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertDeviceConfigPacket(config: config, nodeNum: toUser.num, context: context!) + upsertDeviceConfigPacket(config: config, nodeNum: toUser.num, context: context) return Int64(meshPacket.id) } return 0 @@ -1824,7 +1844,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.decoded = dataMessage let messageDescription = "🛟 Saved Display Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertDisplayConfigPacket(config: config, nodeNum: toUser.num, context: context!) + upsertDisplayConfigPacket(config: config, nodeNum: toUser.num, context: context) return Int64(meshPacket.id) } return 0 @@ -1851,7 +1871,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let messageDescription = "🛟 Saved LoRa Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertLoRaConfigPacket(config: config, nodeNum: toUser.num, context: context!) + upsertLoRaConfigPacket(config: config, nodeNum: toUser.num, context: context) return Int64(meshPacket.id) } @@ -1882,7 +1902,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let messageDescription = "🛟 Saved Position Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertPositionConfigPacket(config: config, nodeNum: toUser.num, context: context!) + upsertPositionConfigPacket(config: config, nodeNum: toUser.num, context: context) return Int64(meshPacket.id) } @@ -1913,7 +1933,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let messageDescription = "🛟 Saved Power Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertPowerConfigPacket(config: config, nodeNum: toUser.num, context: context!) + upsertPowerConfigPacket(config: config, nodeNum: toUser.num, context: context) return Int64(meshPacket.id) } @@ -1944,7 +1964,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let messageDescription = "🛟 Saved Network Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertNetworkConfigPacket(config: config, nodeNum: toUser.num, context: context!) + upsertNetworkConfigPacket(config: config, nodeNum: toUser.num, context: context) return Int64(meshPacket.id) } @@ -1974,7 +1994,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let messageDescription = "🛟 Saved Ambient Lighting Module Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertAmbientLightingModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) + upsertAmbientLightingModuleConfigPacket(config: config, nodeNum: toUser.num, context: context) return Int64(meshPacket.id) } @@ -2004,7 +2024,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let messageDescription = "🛟 Saved Canned Message Module Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertCannedMessagesModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) + upsertCannedMessagesModuleConfigPacket(config: config, nodeNum: toUser.num, context: context) return Int64(meshPacket.id) } @@ -2065,7 +2085,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let messageDescription = "🛟 Saved Detection Sensor Module Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertDetectionSensorModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) + upsertDetectionSensorModuleConfigPacket(config: config, nodeNum: toUser.num, context: context) return Int64(meshPacket.id) } return 0 @@ -2094,7 +2114,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let messageDescription = "🛟 Saved External Notification Module Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertExternalNotificationModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) + upsertExternalNotificationModuleConfigPacket(config: config, nodeNum: toUser.num, context: context) return Int64(meshPacket.id) } return 0 @@ -2123,7 +2143,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let messageDescription = "🛟 Saved PAX Counter Module Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertPaxCounterModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) + upsertPaxCounterModuleConfigPacket(config: config, nodeNum: toUser.num, context: context) return Int64(meshPacket.id) } @@ -2153,7 +2173,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let messageDescription = "🛟 Saved RTTTL Ringtone Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertRtttlConfigPacket(ringtone: ringtone, nodeNum: toUser.num, context: context!) + upsertRtttlConfigPacket(ringtone: ringtone, nodeNum: toUser.num, context: context) return Int64(meshPacket.id) } @@ -2184,7 +2204,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let messageDescription = "🛟 Saved MQTT Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertMqttModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) + upsertMqttModuleConfigPacket(config: config, nodeNum: toUser.num, context: context) return Int64(meshPacket.id) } return 0 @@ -2213,7 +2233,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let messageDescription = "🛟 Saved Range Test Module Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertRangeTestModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) + upsertRangeTestModuleConfigPacket(config: config, nodeNum: toUser.num, context: context) return Int64(meshPacket.id) } @@ -2243,7 +2263,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let messageDescription = "🛟 Saved Serial Module Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertSerialModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) + upsertSerialModuleConfigPacket(config: config, nodeNum: toUser.num, context: context) return Int64(meshPacket.id) } return 0 @@ -2272,7 +2292,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let messageDescription = "🛟 Saved Store & Forward Module Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertStoreForwardModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) + upsertStoreForwardModuleConfigPacket(config: config, nodeNum: toUser.num, context: context) return Int64(meshPacket.id) } return 0 @@ -2301,7 +2321,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let messageDescription = "Saved Telemetry Module Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertTelemetryModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) + upsertTelemetryModuleConfigPacket(config: config, nodeNum: toUser.num, context: context) return Int64(meshPacket.id) } return 0 @@ -3060,16 +3080,29 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate MeshLogger.log("📮 Store and Forward \(storeAndForwardMessage.rr) message received from \(packet.from.toHex())") case .routerTextDirect: MeshLogger.log("💬 Store and Forward \(storeAndForwardMessage.rr) message received from \(packet.from.toHex())") - textMessageAppPacket(packet: packet, wantRangeTestPackets: false, connectedNode: connectedNodeNum, storeForward: true, context: context) + textMessageAppPacket( + packet: packet, + wantRangeTestPackets: false, + connectedNode: connectedNodeNum, + storeForward: true, + context: context, + appState: appState + ) case .routerTextBroadcast: MeshLogger.log("✉️ Store and Forward \(storeAndForwardMessage.rr) message received from \(packet.from.toHex())") - textMessageAppPacket(packet: packet, wantRangeTestPackets: false, connectedNode: connectedNodeNum, storeForward: true, context: context) + textMessageAppPacket( + packet: packet, + wantRangeTestPackets: false, + connectedNode: connectedNodeNum, + storeForward: true, + context: context, + appState: appState + ) } } } public func tryClearExistingChannels() { - guard let context else { return } // Before we get started delete the existing channels from the myNodeInfo let fetchMyInfoRequest = MyInfoEntity.fetchRequest() fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(connectedPeripheral.num)) diff --git a/Meshtastic/Helpers/CommonRegex.swift b/Meshtastic/Helpers/CommonRegex.swift index 683f8fd7..3cc788d1 100644 --- a/Meshtastic/Helpers/CommonRegex.swift +++ b/Meshtastic/Helpers/CommonRegex.swift @@ -8,8 +8,7 @@ import Foundation import RegexBuilder -class CommonRegex -{ +class CommonRegex { static let COORDS_REGEX = Regex { Capture { Regex { diff --git a/Meshtastic/Helpers/LocalNotificationManager.swift b/Meshtastic/Helpers/LocalNotificationManager.swift index d9ff996a..3e41c8f3 100644 --- a/Meshtastic/Helpers/LocalNotificationManager.swift +++ b/Meshtastic/Helpers/LocalNotificationManager.swift @@ -40,18 +40,19 @@ class LocalNotificationManager { content.interruptionLevel = .timeSensitive if notification.target != nil { - content.userInfo["target"] = notification.target + content.userInfo["target"] = notification.target } if notification.path != nil { - content.userInfo["path"] = notification.path + content.userInfo["path"] = notification.path } let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) let request = UNNotificationRequest(identifier: notification.id, content: content, trigger: trigger) UNUserNotificationCenter.current().add(request) { error in - - guard error == nil else { return } + if let error { + Logger.services.error("Error Scheduling Notification: \(error.localizedDescription)") + } } } } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 9f7ee8ab..bb3687b5 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -290,7 +290,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje newUser.isLicensed = nodeInfo.user.isLicensed newUser.role = Int32(nodeInfo.user.role.rawValue) newNode.user = newUser - } else if nodeInfo.num > Int16.max { + } else if nodeInfo.num > Constants.minimumNodeNum { let newUser = createUser(num: Int64(nodeInfo.num), context: context) newNode.user = newUser } @@ -355,7 +355,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje fetchedNode[0].user!.role = Int32(nodeInfo.user.role.rawValue) fetchedNode[0].user!.hwModel = String(describing: nodeInfo.user.hwModel).uppercased() } else { - if fetchedNode[0].user == nil && nodeInfo.num > Int16.max { + if fetchedNode[0].user == nil && nodeInfo.num > Constants.minimumNodeNum { let newUser = createUser(num: Int64(nodeInfo.num), context: context) fetchedNode[0].user = newUser @@ -648,6 +648,10 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage } else { // If it is the connected node } + if telemetryMessage.variant != Telemetry.OneOf_Variant.deviceMetrics(telemetryMessage.deviceMetrics) && telemetryMessage.variant != Telemetry.OneOf_Variant.environmentMetrics(telemetryMessage.environmentMetrics) { + /// Other unhandled telemetry packets + return + } let telemetry = TelemetryEntity(context: context) @@ -657,6 +661,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage do { let fetchedNode = try context.fetch(fetchNodeTelemetryRequest) if fetchedNode.count == 1 { + /// Currently only Device Metrics and Environment Telemetry are supported in the app if telemetryMessage.variant == Telemetry.OneOf_Variant.deviceMetrics(telemetryMessage.deviceMetrics) { // Device Metrics telemetry.airUtilTx = telemetryMessage.deviceMetrics.airUtilTx @@ -665,7 +670,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage telemetry.voltage = telemetryMessage.deviceMetrics.voltage telemetry.uptimeSeconds = Int32(telemetryMessage.deviceMetrics.uptimeSeconds) telemetry.metricsType = 0 - Logger.statistics.info("📈 [Mesh Statistics] Channel Utilization: \(telemetryMessage.deviceMetrics.channelUtilization, privacy: .public) Airtime: \(telemetryMessage.deviceMetrics.airUtilTx, privacy: .public)) for Node: \(packet.from.toHex(), privacy: .public))") + Logger.statistics.info("📈 [Mesh Statistics] Channel Utilization: \(telemetryMessage.deviceMetrics.channelUtilization, privacy: .public) Airtime: \(telemetryMessage.deviceMetrics.airUtilTx, privacy: .public) for Node: \(packet.from.toHex(), privacy: .public)") } else if telemetryMessage.variant == Telemetry.OneOf_Variant.environmentMetrics(telemetryMessage.environmentMetrics) { // Environment Metrics telemetry.barometricPressure = telemetryMessage.environmentMetrics.barometricPressure @@ -710,7 +715,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage subtitle: "AKA \(telemetry.nodeTelemetry?.user?.shortName ?? "UNK")", content: "Time to charge your radio, there is \(telemetry.batteryLevel)% battery remaining.", target: "nodes", - path: "meshtastic://nodes?nodenum=\(telemetry.nodeTelemetry?.num ?? 0)" + path: "meshtastic:///nodes?nodenum=\(telemetry.nodeTelemetry?.num ?? 0)" ) ] manager.schedule() @@ -744,7 +749,14 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage } } -func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connectedNode: Int64, storeForward: Bool = false, context: NSManagedObjectContext) { +func textMessageAppPacket( + packet: MeshPacket, + wantRangeTestPackets: Bool, + connectedNode: Int64, + storeForward: Bool = false, + context: NSManagedObjectContext, + appState: AppState +) { var messageText = String(bytes: packet.decoded.payload, encoding: .utf8) let rangeRef = Reference(Int.self) @@ -783,7 +795,6 @@ func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connec let newMessage = MessageEntity(context: context) newMessage.messageId = Int64(packet.id) newMessage.messageTimestamp = Int32(bitPattern: packet.rxTime) - newMessage.receivedTimestamp = Int32(Date().timeIntervalSince1970) newMessage.receivedACK = false newMessage.snr = packet.rxSnr newMessage.rssi = packet.rxRssi @@ -799,7 +810,7 @@ func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connec newMessage.replyID = Int64(packet.decoded.replyID) } - if fetchedUsers.first(where: { $0.num == packet.to }) != nil && packet.to != 4294967295 { + if fetchedUsers.first(where: { $0.num == packet.to }) != nil && packet.to != Constants.maximumNodeNum { if !storeForwardBroadcast { newMessage.toUser = fetchedUsers.first(where: { $0.num == packet.to }) } @@ -812,7 +823,7 @@ func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connec } newMessage.messagePayload = messageText newMessage.messagePayloadMarkdown = generateMessageMarkdown(message: messageText!) - if packet.to != 4294967295 && newMessage.fromUser != nil { + if packet.to != Constants.maximumNodeNum && newMessage.fromUser != nil { newMessage.fromUser?.lastMessage = Date() } var messageSaved = false @@ -828,12 +839,10 @@ func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connec if packet.decoded.portnum == PortNum.detectionSensorApp && !UserDefaults.enableDetectionNotifications { return } - let appState = AppState.shared if newMessage.fromUser != nil && newMessage.toUser != nil { // Set Unread Message Indicators if packet.to == connectedNode { appState.unreadDirectMessages = newMessage.toUser?.unreadMessages ?? 0 - UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages } if !(newMessage.fromUser?.mute ?? false) { // Create an iOS Notification for the received DM message and schedule it immediately @@ -845,7 +854,7 @@ func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connec subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")", content: messageText!, target: "messages", - path: "meshtastic://messages?userNum=\(newMessage.fromUser?.num ?? 0)&messageId=\(newMessage.messageId)" + path: "meshtastic:///messages?userNum=\(newMessage.fromUser?.num ?? 0)&messageId=\(newMessage.messageId)" ) ] manager.schedule() @@ -860,7 +869,6 @@ func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connec let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) if !fetchedMyInfo.isEmpty { appState.unreadChannelMessages = fetchedMyInfo[0].unreadMessages - UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages for channel in (fetchedMyInfo[0].channels?.array ?? []) as? [ChannelEntity] ?? [] { if channel.index == newMessage.channel { @@ -876,7 +884,7 @@ func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connec subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")", content: messageText!, target: "messages", - path: "meshtastic://messages?channel=\(newMessage.channel)&messageId=\(newMessage.messageId)") + path: "meshtastic:///messages?channelId=\(newMessage.channel)&messageId=\(newMessage.messageId)") ] manager.schedule() Logger.services.debug("iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized)") @@ -941,10 +949,10 @@ func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) { subtitle: "\(icon) \(waypoint.name ?? "Dropped Pin")", content: "\(waypoint.longDescription ?? "\(latitude), \(longitude)")", target: "map", - path: "meshtastic://map?waypontid=\(waypoint.id)" + path: "meshtastic:///map?waypointid=\(waypoint.id)" ) ] - Logger.data.debug("meshtastic://map?waypontid=\(waypoint.id)") + Logger.data.debug("meshtastic:///map?waypointid=\(waypoint.id)") manager.schedule() } catch { context.rollback() diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 9ae100e3..9deb5562 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV 40.xcdatamodel + MeshtasticDataModelV 41.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 41.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 41.xcdatamodel/contents new file mode 100644 index 00000000..55eccab7 --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 41.xcdatamodel/contents @@ -0,0 +1,448 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index 578f16d4..ff691440 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -11,22 +11,48 @@ import TipKit @main struct MeshtasticAppleApp: App { - @UIApplicationDelegateAdaptor(MeshtasticAppDelegate.self) var appDelegate - let persistenceController = PersistenceController.shared - @ObservedObject private var bleManager: BLEManager = BLEManager.shared + @UIApplicationDelegateAdaptor(MeshtasticAppDelegate.self) + private var appDelegate + + @ObservedObject + var appState: AppState + + @ObservedObject + private var bleManager: BLEManager + + private let persistenceController: PersistenceController @Environment(\.scenePhase) var scenePhase - @State var saveChannels = false @State var incomingUrl: URL? @State var channelSettings: String? @State var addChannels = false - @StateObject var appState = AppState.shared + + init() { + let persistenceController = PersistenceController.shared + let appState = AppState( + router: Router() + ) + self._appState = ObservedObject(wrappedValue: appState) + + self.bleManager = BLEManager( + appState: appState, + context: persistenceController.container.viewContext + ) + self.persistenceController = persistenceController + + // Wire up router + self.appDelegate.router = appState.router + } var body: some Scene { WindowGroup { - ContentView() + ContentView( + appState: appState, + router: appState.router + ) .environment(\.managedObjectContext, persistenceController.container.viewContext) + .environmentObject(appState) .environmentObject(bleManager) .sheet(isPresented: $saveChannels) { SaveChannelQRCode(channelSetLink: channelSettings ?? "Empty Channel URL", addChannels: addChannels, bleManager: bleManager) @@ -34,14 +60,13 @@ struct MeshtasticAppleApp: App { .presentationDragIndicator(.visible) } .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { userActivity in - Logger.mesh.debug("URL received \(userActivity)") self.incomingUrl = userActivity.webpageURL if (self.incomingUrl?.absoluteString.lowercased().contains("meshtastic.org/e/#")) != nil { if let components = self.incomingUrl?.absoluteString.components(separatedBy: "#") { self.addChannels = Bool(self.incomingUrl?["add"] ?? "false") ?? false - if ((self.incomingUrl?.absoluteString.lowercased().contains("?")) != nil) { + if (self.incomingUrl?.absoluteString.lowercased().contains("?")) != nil { guard let cs = components.last!.components(separatedBy: "?").first else { return } @@ -83,57 +108,8 @@ struct MeshtasticAppleApp: App { } self.saveChannels = true Logger.mesh.debug("User wants to open a Channel Settings URL: \(self.incomingUrl?.absoluteString ?? "No QR Code Link")") - } else if url.absoluteString.lowercased().contains("meshtastic://") { - appState.navigationPath = url.absoluteString - let path = appState.navigationPath ?? "" - if path.starts(with: "meshtastic://map") { - AppState.shared.tabSelection = Tab.map - } else if path.starts(with: "meshtastic://nodes") { - AppState.shared.tabSelection = Tab.nodes - } - - } else { - saveChannels = false - Logger.mesh.debug("User wants to import a MBTILES offline map file: \(self.incomingUrl?.absoluteString ?? "No Tiles link")") - } - - /// Only do the map tiles stuff if it is enabled - if UserDefaults.enableOfflineMapsMBTiles { - /// we are expecting a .mbtiles map file that contains raster data - /// save it to the documents directory, and name it offline_map.mbtiles - let fileManager = FileManager.default - let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! - let destination = documentsDirectory.appendingPathComponent("offline_map.mbtiles", isDirectory: false) - - if !self.saveChannels { - - // tell the system we want the file please - guard url.startAccessingSecurityScopedResource() else { - return - } - - // do we need to delete an old one? - if fileManager.fileExists(atPath: destination.path) { - Logger.mesh.info("Found an old map file. Deleting it") - try? fileManager.removeItem(atPath: destination.path) - } - - do { - try fileManager.copyItem(at: url, to: destination) - } catch { - Logger.mesh.error("Copy MB Tile file failed. Error: \(error.localizedDescription)") - } - - if fileManager.fileExists(atPath: destination.path) { - Logger.mesh.info("Saved the map file") - - // need to tell the map view that it needs to update and try loading the new overlay - UserDefaults.standard.set(Date().timeIntervalSince1970, forKey: "lastUpdatedLocalMapFile") - - } else { - Logger.mesh.error("Didn't save the map file") - } - } + } else if url.absoluteString.lowercased().contains("meshtastic:///") { + appState.router.route(url: url) } }) .task { @@ -179,13 +155,3 @@ struct MeshtasticAppleApp: App { } } } - -class AppState: ObservableObject { - static let shared = AppState() - - @Published var tabSelection: Tab = .ble - @Published var unreadDirectMessages: Int = 0 - @Published var unreadChannelMessages: Int = 0 - @Published var firmwareVersion: String = "0.0.0" - @Published var navigationPath: String? -} diff --git a/Meshtastic/MeshtasticAppDelegate.swift b/Meshtastic/MeshtasticAppDelegate.swift index 71f01d81..06d290e9 100644 --- a/Meshtastic/MeshtasticAppDelegate.swift +++ b/Meshtastic/MeshtasticAppDelegate.swift @@ -9,6 +9,9 @@ import SwiftUI import OSLog class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, ObservableObject { + + var router: Router? + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { Logger.services.info("🚀 [App] Meshtstic Apple App launched!") // Default User Default Values @@ -28,23 +31,30 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat return true } // Lets us show the notification in the app in the foreground - func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + func userNotificationCenter( + _ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void + ) { completionHandler([.list, .banner, .sound]) } - // This method is called when a user clicks on the notification - func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { - let userInfo = response.notification.request.content.userInfo - let targetValue = userInfo["target"] as? String - let deepLink = userInfo["path"] as? String - AppState.shared.navigationPath = deepLink - if targetValue == "map" { - AppState.shared.tabSelection = Tab.map - } else if targetValue == "messages" { - AppState.shared.tabSelection = Tab.messages - } else if targetValue == "nodes" { - AppState.shared.tabSelection = Tab.nodes + // This method is called when a user clicks on the notification + func userNotificationCenter( + _ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void + ) { + let userInfo = response.notification.request.content.userInfo + if let targetValue = userInfo["target"] as? String, + let deepLink = userInfo["path"] as? String, + let url = URL(string: deepLink) { + Logger.services.info("userNotificationCenter didReceiveResponse \(targetValue) \(deepLink)") + router?.route(url: url) + } else { + Logger.services.error("Failed to handle notification response: \(userInfo)") } + completionHandler() } } diff --git a/Meshtastic/Persistence/Persistence.swift b/Meshtastic/Persistence/Persistence.swift index 25f9a3e2..96808c73 100644 --- a/Meshtastic/Persistence/Persistence.swift +++ b/Meshtastic/Persistence/Persistence.swift @@ -142,11 +142,11 @@ extension NSPersistentContainer { /// - Parameter backupURL: A file URL containing backup copies of all currently loaded persistent stores. /// - Throws: `CopyPersistentStoreError` in various situations. /// - Returns: Nothing. If no errors are thrown, the restore is complete. - func restorePersistentStore(from backupURL: URL) throws -> Void { + func restorePersistentStore(from backupURL: URL) throws { guard backupURL.isFileURL else { throw CopyPersistentStoreErrors.invalidSource("Backup URL must be a file URL") } - + var isDirectory: ObjCBool = false if FileManager.default.fileExists(atPath: backupURL.path, isDirectory: &isDirectory) { if !isDirectory.boolValue { @@ -185,7 +185,7 @@ extension NSPersistentContainer { /// - overwriting: If `true`, any existing copies of the persistent store will be replaced or updated. If `false`, existing copies will not be changed or remoted. When this is `false`, the destination persistent store file must not already exist. /// - Throws: `CopyPersistentStoreError` /// - Returns: Nothing. If no errors are thrown, all loaded persistent stores will be copied to the destination directory. - func copyPersistentStores(to destinationURL: URL, overwriting: Bool = false) throws -> Void { + func copyPersistentStores(to destinationURL: URL, overwriting: Bool = false) throws { guard !destinationURL.relativeString.contains("/0/") else { throw CopyPersistentStoreErrors.invalidDestination("Invalid 0 Node Id") diff --git a/Meshtastic/Persistence/QueryCoreData.swift b/Meshtastic/Persistence/QueryCoreData.swift index f023eb9b..55889764 100644 --- a/Meshtastic/Persistence/QueryCoreData.swift +++ b/Meshtastic/Persistence/QueryCoreData.swift @@ -29,7 +29,7 @@ public func getStoreAndForwardMessageIds(seconds: Int, context: NSManagedObjectC let fetchMessagesRequest = MessageEntity.fetchRequest() let timeRange = Calendar.current.date(byAdding: .minute, value: time, to: Date()) let milleseconds = Int32(timeRange?.timeIntervalSince1970 ?? 0) - fetchMessagesRequest.predicate = NSPredicate(format: "receivedTimestamp >= %d", milleseconds) + fetchMessagesRequest.predicate = NSPredicate(format: "messageTimestamp >= %d", milleseconds) do { let fetchedMessages = try context.fetch(fetchMessagesRequest) diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index cbf63ce5..3fac0312 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -153,7 +153,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) newNode.rssi = packet.rxRssi newNode.viaMqtt = packet.viaMqtt - if packet.to == 4294967295 || packet.to == UserDefaults.preferredPeripheralNum { + if packet.to == Constants.maximumNodeNum || packet.to == UserDefaults.preferredPeripheralNum { newNode.channel = Int32(packet.channel) } if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) { @@ -164,7 +164,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) if let newUserMessage = try? User(serializedData: packet.decoded.payload) { if newUserMessage.id.isEmpty { - if packet.from > Int16.max { + if packet.from > Constants.minimumNodeNum { let newUser = createUser(num: Int64(packet.from), context: context) newNode.user = newUser } @@ -188,20 +188,20 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) subtitle: "\(newUser.longName ?? "unknown".localized)", content: "New Node has been discovered", target: "nodes", - path: "meshtastic://nodes?nodenum=\(newUser.num)" + path: "meshtastic:///nodes?nodenum=\(newUser.num)" ) ] manager.schedule() } } } else { - if packet.from > Int16.max { + if packet.from > Constants.minimumNodeNum { let newUser = createUser(num: Int64(packet.from), context: context) newNode.user = newUser } } - if newNode.user == nil && packet.from > Int16.max { + if newNode.user == nil && packet.from > Constants.minimumNodeNum { newNode.user = createUser(num: Int64(packet.from), context: context) } @@ -231,7 +231,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) fetchedNode[0].snr = packet.rxSnr fetchedNode[0].rssi = packet.rxRssi fetchedNode[0].viaMqtt = packet.viaMqtt - if packet.to == 4294967295 || packet.to == UserDefaults.preferredPeripheralNum { + if packet.to == Constants.maximumNodeNum || packet.to == UserDefaults.preferredPeripheralNum { fetchedNode[0].channel = Int32(packet.channel) } @@ -263,8 +263,8 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) } if fetchedNode[0].user == nil { let newUser = createUser(num: Int64(truncatingIfNeeded: packet.from), context: context) - fetchedNode[0].user! = newUser - + fetchedNode[0].user? = newUser + } do { try context.save() diff --git a/Meshtastic/Router/NavigationState.swift b/Meshtastic/Router/NavigationState.swift new file mode 100644 index 00000000..b33fc48a --- /dev/null +++ b/Meshtastic/Router/NavigationState.swift @@ -0,0 +1,106 @@ +import Foundation + +// MARK: Messages + +enum MessagesNavigationState: Hashable { + case channels( + channelId: Int32? = nil, + messageId: Int64? = nil + ) + case directMessages( + userNum: Int64? = nil, + messageId: Int64? = nil + ) +} + +// MARK: Map + +enum MapNavigationState: Hashable { + case selectedNode(Int64) + case waypoint(Int64) +} + +// MARK: Settings + +enum SettingsNavigationState: String { + case about + case appSettings + case routes + case routeRecorder + case lora + case channels + case shareQRCode + case user + case bluetooth + case device + case display + case network + case position + case power + case ambientLighting + case cannedMessages + case detectionSensor + case externalNotification + case mqtt + case rangeTest + case paxCounter + case ringtone + case serial + case storeAndForward + case telemetry + case meshLog + case debugLogs + case appFiles + case firmwareUpdates +} + +enum NavigationState: Hashable { + case messages(MessagesNavigationState? = nil) + case bluetooth + case nodes(selectedNodeNum: Int64? = nil) + case map(MapNavigationState? = nil) + case settings(SettingsNavigationState? = nil) +} + +// MARK: Tab Bar + +extension NavigationState { + enum Tab: String, Hashable { + case messages + case bluetooth + case nodes + case map + case settings + } + + var tab: Tab { + get { + switch self { + case .messages: + .messages + case .bluetooth: + .bluetooth + case .nodes: + .nodes + case .map: + .map + case .settings: + .settings + } + } + set { + self = switch newValue { + case .messages: + .messages() + case .bluetooth: + .bluetooth + case .nodes: + .nodes() + case .map: + .map() + case .settings: + .settings() + } + } + } +} diff --git a/Meshtastic/Router/Router.swift b/Meshtastic/Router/Router.swift new file mode 100644 index 00000000..51803ed4 --- /dev/null +++ b/Meshtastic/Router/Router.swift @@ -0,0 +1,117 @@ +import Combine +import CoreData +import OSLog +import SwiftUI + +@MainActor +class Router: ObservableObject { + + @Published + var navigationState: NavigationState + + private var cancellables: Set = [] + + init( + navigationState: NavigationState = .bluetooth + ) { + self.navigationState = navigationState + + $navigationState.sink { destination in + Logger.services.info("🛣 Routed to \(String(describing: destination), privacy: .public)") + }.store(in: &cancellables) + } + + func route(to destination: NavigationState) { + navigationState = destination + } + + func route(url: URL) { + guard url.scheme == "meshtastic" else { + Logger.services.error("🛣 Received routing URL \(url, privacy: .public) with invalid scheme. Ignoring route.") + return + } + guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { + Logger.services.error("🛣 Received routing URL \(url, privacy: .public) with invalid host path. Ignoring route.") + return + } + + if components.path == "/messages" { + routeMessages(components) + } else if components.path == "/bluetooth" { + route(to: .bluetooth) + } else if components.path == "/nodes" { + routeNodes(components) + } else if components.path == "/map" { + routeMap(components) + } else if components.path.hasPrefix("/settings") { + routeSettings(components) + } else { + Logger.services.warning("Failed to route url: \(url)") + } + } + + // MARK: Routing Helpers + + private func routeMessages( + _ components: URLComponents + ) { + let channelId = components.queryItems? + .first(where: { $0.name == "channelId" })? + .value + .flatMap(Int32.init) + let userNum = components.queryItems? + .first(where: { $0.name == "userNum" })? + .value + .flatMap(Int64.init) + let messageId = components.queryItems? + .first(where: { $0.name == "messageId" })? + .value + .flatMap(Int64.init) + + let state: MessagesNavigationState? = if let channelId { + .channels(channelId: channelId, messageId: messageId) + } else if let userNum { + .directMessages(userNum: userNum, messageId: messageId) + } else { + nil + } + route(to: .messages(state)) + } + + private func routeNodes(_ components: URLComponents) { + let nodeId = components.queryItems? + .first(where: { $0.name == "nodenum" })? + .value + .flatMap(Int64.init) + route(to: .nodes(selectedNodeNum: nodeId)) + } + + private func routeMap(_ components: URLComponents) { + let nodeId = components.queryItems? + .first(where: { $0.name == "nodenum" })? + .value + .flatMap(Int64.init) + let waypointId = components.queryItems? + .first(where: { $0.name == "waypointId" })? + .value + .flatMap(Int64.init) + if let nodeId { + route(to: .map(.selectedNode(nodeId))) + } else if let waypointId { + route(to: .map(.waypoint(waypointId))) + } else { + route(to: .map()) + } + } + + private func routeSettings(_ components: URLComponents) { + let settingFromPath = components.path + .split(separator: "/") + .dropFirst() + .first + .flatMap(String.init) + .flatMap(SettingsNavigationState.init(rawValue:)) + + route(to: .settings(settingFromPath)) + } +} diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index c3745ebd..0048e430 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -323,11 +323,6 @@ struct Connect: View { } } } - .onAppear(perform: { - if self.bleManager.context == nil { - self.bleManager.context = context - } - }) } #if canImport(ActivityKit) func startNodeActivity() { diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index 46f7f903..b109a318 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -6,75 +6,68 @@ import SwiftUI @available(iOS 17.0, *) struct ContentView: View { + @ObservedObject + var appState: AppState + + @ObservedObject + var router: Router - @StateObject var appState = AppState.shared var body: some View { - TabView(selection: $appState.tabSelection) { - Messages() - .tabItem { - Label("messages", systemImage: "message") - } - .tag(Tab.contacts) - .badge(appState.unreadDirectMessages + appState.unreadChannelMessages) + TabView(selection: Binding( + get: { + appState.router.navigationState.tab + }, + set: { newValue in + appState.router.navigationState.tab = newValue + } + )) { + Messages( + router: appState.router, + unreadChannelMessages: $appState.unreadChannelMessages, + unreadDirectMessages: $appState.unreadDirectMessages + ) + .tabItem { + Label("messages", systemImage: "message") + } + .tag(NavigationState.Tab.messages) + .badge(appState.totalUnreadMessages) + Connect() .tabItem { Label("bluetooth", systemImage: "antenna.radiowaves.left.and.right") } - .tag(Tab.ble) - NodeList() - .tabItem { - Label("nodes", systemImage: "flipphone") - } - .tag(Tab.nodes) - if #available(iOS 17.0, macOS 14.0, *) { - if UserDefaults.mapUseLegacy { - NodeMap() - .tabItem { - Label("map", systemImage: "map") - } - .tag(Tab.map) - } else { - MeshMap() - .tabItem { - Label("map", systemImage: "map") - } - .tag(Tab.map) - } - } else { - NodeMap() + .tag(NavigationState.Tab.bluetooth) + + NodeList( + router: appState.router + ) + .tabItem { + Label("nodes", systemImage: "flipphone") + } + .tag(NavigationState.Tab.nodes) + + if #available(iOS 17.0, macOS 14.0, *), !UserDefaults.mapUseLegacy { + MeshMap(router: appState.router) .tabItem { Label("map", systemImage: "map") } - .tag(Tab.map) + .tag(NavigationState.Tab.map) + } else { + NodeMap(router: appState.router) + .tabItem { + Label("map", systemImage: "map") + } + .tag(NavigationState.Tab.map) } - Settings() - .tabItem { - Label("settings", systemImage: "gear") - .font(.title) - } - .tag(Tab.settings) + + Settings( + router: appState.router + ) + .tabItem { + Label("settings", systemImage: "gear") + .font(.title) + } + .tag(NavigationState.Tab.settings) } } } -// #Preview { -// if #available(iOS 17.0, *) { -// // ContentView(deepLinkManager: .init()) -// } else { -// // Fallback on earlier versions -// } -// } - -// struct ContentView_Previews: PreviewProvider { -// static var previews: some View { -// ContentView() -// } -// } - -enum Tab: Hashable { - case contacts - case messages - case map - case ble - case nodes - case settings -} diff --git a/Meshtastic/Views/Helpers/BatteryCompact.swift b/Meshtastic/Views/Helpers/BatteryCompact.swift new file mode 100644 index 00000000..28f62a57 --- /dev/null +++ b/Meshtastic/Views/Helpers/BatteryCompact.swift @@ -0,0 +1,68 @@ +// +// BatteryCompact.swift +// Meshtastic +// +// Created by Garth Vander Houwen on 7/18/24. +// +import SwiftUI + +struct BatteryCompact: View { + @State var batteryLevel: Int32 + var font: Font + var iconFont: Font + var color: Color + + var body: some View { + HStack(alignment: .center, spacing: 0) { + if batteryLevel == 100 { + Image(systemName: "battery.100.bolt") + .font(iconFont) + .foregroundColor(color) + .symbolRenderingMode(.multicolor) + } else if batteryLevel < 100 && batteryLevel > 74 { + Image(systemName: "battery.75") + .font(iconFont) + .foregroundColor(color) + .symbolRenderingMode(.multicolor) + } else if batteryLevel < 75 && batteryLevel > 49 { + Image(systemName: "battery.50") + .font(iconFont) + .foregroundColor(color) + .symbolRenderingMode(.multicolor) + } else if batteryLevel < 50 && batteryLevel > 14 { + Image(systemName: "battery.25") + .font(iconFont) + .foregroundColor(color) + .symbolRenderingMode(.multicolor) + } else if batteryLevel < 15 && batteryLevel > 0 { + Image(systemName: "battery.0") + .font(iconFont) + .foregroundColor(color) + .symbolRenderingMode(.multicolor) + } else if batteryLevel == 0 { + Image(systemName: "battery.0") + .font(iconFont) + .foregroundColor(.red) + .symbolRenderingMode(.multicolor) + } else if batteryLevel > 100 { + Image(systemName: "powerplug") + .font(iconFont) + .foregroundColor(color) + .symbolRenderingMode(.multicolor) + } + if batteryLevel > 100 { + Text("PWD") + .foregroundStyle(.secondary) + .font(font) + } else if batteryLevel == 100 { + Text("CHG") + .foregroundStyle(.secondary) + .font(font) + } else { + Text("\(batteryLevel)%") + .foregroundStyle(.secondary) + .font(font) + } + } + } +} diff --git a/Meshtastic/Views/Helpers/BatteryLevelCompact.swift b/Meshtastic/Views/Helpers/BatteryLevelCompact.swift index 7bb2542c..e1354e23 100644 --- a/Meshtastic/Views/Helpers/BatteryLevelCompact.swift +++ b/Meshtastic/Views/Helpers/BatteryLevelCompact.swift @@ -19,61 +19,7 @@ struct BatteryLevelCompact: View { let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity let batteryLevel = mostRecent?.batteryLevel ?? 0 if deviceMetrics?.count ?? 0 > 0 { - HStack(alignment: .center, spacing: 0) { - if batteryLevel == 100 { - Image(systemName: "battery.100.bolt") - .font(iconFont) - .foregroundColor(color) - .symbolRenderingMode(.hierarchical) - } else if batteryLevel < 100 && batteryLevel > 74 { - - Image(systemName: "battery.75") - .font(iconFont) - .foregroundColor(color) - .symbolRenderingMode(.hierarchical) - } else if batteryLevel < 75 && batteryLevel > 49 { - - Image(systemName: "battery.50") - .font(iconFont) - .foregroundColor(color) - .symbolRenderingMode(.hierarchical) - } else if batteryLevel < 50 && batteryLevel > 14 { - - Image(systemName: "battery.25") - .font(iconFont) - .foregroundColor(color) - .symbolRenderingMode(.hierarchical) - } else if batteryLevel < 15 && batteryLevel > 0 { - - Image(systemName: "battery.0") - .font(iconFont) - .foregroundColor(color) - .symbolRenderingMode(.hierarchical) - } else if batteryLevel == 0 { - Image(systemName: "battery.0") - .font(iconFont) - .foregroundColor(.red) - .symbolRenderingMode(.hierarchical) - } else if batteryLevel > 100 { - Image(systemName: "powerplug") - .font(iconFont) - .foregroundColor(color) - .symbolRenderingMode(.hierarchical) - } - if batteryLevel > 100 { - Text("PWD") - .foregroundStyle(.gray) - .font(font) - } else if batteryLevel == 100 { - Text("CHG") - .foregroundStyle(.gray) - .font(font) - } else { - Text("\(batteryLevel)%") - .foregroundStyle(.gray) - .font(font) - } - } + BatteryCompact(batteryLevel: batteryLevel, font: font, iconFont: iconFont, color: color) } } } diff --git a/Meshtastic/Views/Helpers/MQTTIcon.swift b/Meshtastic/Views/Helpers/MQTTIcon.swift index 4cd615be..79821dd1 100644 --- a/Meshtastic/Views/Helpers/MQTTIcon.swift +++ b/Meshtastic/Views/Helpers/MQTTIcon.swift @@ -23,7 +23,7 @@ struct MQTTIcon: View { // the last one defaults to just showing up/down if it isn't specified b/c on the mqtt config screen, there's no information about uplink/downlink and no good alternative icon Image(systemName: uplink && downlink ? "arrow.up.arrow.down.circle.fill" : uplink ? "arrow.up.circle.fill" : downlink ? "arrow.down.circle.fill" : "arrow.up.arrow.down.circle.fill") .imageScale(.large) - .foregroundColor(connected ? .green : .gray) + .foregroundColor(connected ? .green : .secondary) .symbolRenderingMode(.hierarchical) }.popover(isPresented: self.$isPopoverOpen, arrowEdge: .bottom, content: { VStack(spacing: 0.5) { diff --git a/Meshtastic/Views/MapKitMap/Custom/LocalMBTileOverlay.swift b/Meshtastic/Views/MapKitMap/Custom/LocalMBTileOverlay.swift deleted file mode 100644 index f16d63e3..00000000 --- a/Meshtastic/Views/MapKitMap/Custom/LocalMBTileOverlay.swift +++ /dev/null @@ -1,154 +0,0 @@ -// -// LocalMBTileOverlay.swift -// MeshtasticApple -// -// Copyright(c) Joshua Pirihi 16/01/22. -// - -import UIKit -import MapKit -import SQLite -import OSLog - -extension MKMapRect { - init(coordinates: [CLLocationCoordinate2D]) { - self = MKMapRect() - var coordinates = coordinates - if !coordinates.isEmpty { - let first = coordinates.removeFirst() - var top = first.latitude - var bottom = first.latitude - var left = first.longitude - var right = first.longitude - coordinates.forEach { coordinate in - top = max(top, coordinate.latitude) - bottom = min(bottom, coordinate.latitude) - left = min(left, coordinate.longitude) - right = max(right, coordinate.longitude) - } - let topLeft = MKMapPoint(CLLocationCoordinate2D(latitude: top, longitude: left)) - let bottomRight = MKMapPoint(CLLocationCoordinate2D(latitude: bottom, longitude: right)) - self = MKMapRect(x: topLeft.x, y: topLeft.y, - width: bottomRight.x - topLeft.x, height: bottomRight.y - topLeft.y) - } - } -} - -enum MapTileError: Error { - case invalidFormat - case other -} - -class LocalMBTileOverlay: MKTileOverlay { - - var path: String! - var mb: Connection! - private var _boundingMapRect: MKMapRect! - override var boundingMapRect: MKMapRect { - return _boundingMapRect - } - - init?(mbTilePath path: String) { - - super.init(urlTemplate: nil) - self.path = path - do { - self.mb = try Connection(self.path, readonly: true) - let metadata = Table("metadata") - - let name = Expression(value: "name") - let value = Expression(value: "value") - - // make sure it's raster - let formatQuery = try mb.pluck(metadata.select(value).filter(name == "format")) - if formatQuery?[value] == nil || (formatQuery![value] != "jpeg" && formatQuery![value] != "jpg" && formatQuery![value] != "png") { - throw MapTileError.invalidFormat - } - - let minZQuery = try mb.pluck(metadata.select(value).filter(name == "minzoom")) - self.minimumZ = Int(minZQuery![value])! - - let maxZQuery = try mb.pluck(metadata.select(value).filter(name == "maxzoom")) - self.maximumZ = Int(maxZQuery![value])! - - self.isGeometryFlipped = true - - let boundingBoxString = try mb.pluck(metadata.select(value).filter(name == "bounds")) - let boundCoords = boundingBoxString![value].split(separator: ",") - let coords = [ - CLLocationCoordinate2D(latitude: Double(boundCoords[1]) ?? 0, - longitude: Double(boundCoords[0]) ?? 0), - CLLocationCoordinate2D(latitude: Double(boundCoords[3]) ?? 0, - longitude: Double(boundCoords[2]) ?? 0) - ] - self._boundingMapRect = MKMapRect(coordinates: coords) - - } catch { - Logger.services.error("Map tile error: \(error)") - return nil - } - } - -// override func loadTile(at path: MKTileOverlayPath, result: @escaping (Data?, Error?) -> Void) { -// -// let tileX = Int64(path.x) -// let tileY = Int64(path.y) -// let tileZ = Int64(path.z) -// let tileData = Expression("tile_data") -// let zoomLevel = Expression("zoom_level") -// let tileColumn = Expression("tile_column") -// let tileRow = Expression("tile_row") -// -// if let dataQuery = try? self.mb.pluck(Table("tiles").select(tileData).filter(zoomLevel == tileZ).filter(tileColumn == tileX).filter(tileRow == tileY)) { -// let data = Data(bytes: dataQuery[tileData].bytes, count: dataQuery[tileData].bytes.count)// dataQuery![tileData].bytes -// result(data, nil) -// } else { -// Logger.services.error("No tile here: x:\(tileX) y:\(tileY) z:\(tileZ)") -// let error = NSError(domain: "LocalMBTileOverlay", code: 1, userInfo: ["reason": "no_tile"]) -// result(nil, error) -// } -// } -} - -// public class CustomMapOverlaySource: MKTileOverlay { -// -// // requires folder: tiles/{mapName}/z/y/y,{tileType} -// private var parent: MapViewSwiftUI -// private let mapName: String -// private let tileType: String -// private let defaultTile: DefaultTile? -// -// public init( -// parent: MapViewSwiftUI, -// mapName: String, -// tileType: String, -// defaultTile: DefaultTile? -// ) { -// self.parent = parent -// self.mapName = mapName -// self.tileType = tileType -// self.defaultTile = defaultTile -// super.init(urlTemplate: "") -// } -// -// public override func url(forTilePath path: MKTileOverlayPath) -> URL { -// if let tileUrl = Bundle.main.url( -// forResource: "\(path.y)", -// withExtension: self.tileType, -// subdirectory: "tiles/\(self.mapName)/\(path.z)/\(path.x)", -// localization: nil -// ) { -// return tileUrl -// } else if let defaultTile = self.defaultTile, let defaultTileUrl = Bundle.main.url( -// forResource: defaultTile.tileName, -// withExtension: defaultTile.tileType, -// subdirectory: "tiles/\(self.mapName)", -// localization: nil -// ) { -// return defaultTileUrl -// } else { -// let urlstring = self.mapName+"\(path.z)/\(path.x)/\(path.y).png" -// return URL(string: urlstring)! -// } -// } -// } diff --git a/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift index 5c7291a5..bf196f6d 100644 --- a/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift @@ -105,13 +105,12 @@ struct MapViewSwiftUI: UIViewRepresentable { switch selectedMapLayer { case .offline: mapView.mapType = .standard - if !UserDefaults.enableOfflineMapsMBTiles { - let overlay = TileOverlay() - overlay.canReplaceMapContent = false - overlay.minimumZ = UserDefaults.mapTileServer.zoomRange.startIndex - overlay.maximumZ = UserDefaults.mapTileServer.zoomRange.endIndex - mapView.addOverlay(overlay, level: UserDefaults.mapTilesAboveLabels ? .aboveLabels : .aboveRoads) - } + let overlay = TileOverlay() + overlay.canReplaceMapContent = false + overlay.minimumZ = UserDefaults.mapTileServer.zoomRange.startIndex + overlay.maximumZ = UserDefaults.mapTileServer.zoomRange.endIndex + mapView.addOverlay(overlay, level: UserDefaults.mapTilesAboveLabels ? .aboveLabels : .aboveRoads) + case .satellite: mapView.mapType = .satellite case .hybrid: @@ -133,32 +132,7 @@ struct MapViewSwiftUI: UIViewRepresentable { } } } - private func setMbtilesOverlay(mapView: MKMapView) { - // MBTiles Offline - if UserDefaults.enableOfflineMaps && UserDefaults.enableOfflineMapsMBTiles { - if self.customMapOverlay != self.presentCustomMapOverlayHash || self.loadedLastUpdatedLocalMapFile != self.lastUpdatedLocalMapFile { - mapView.removeOverlays(mapView.overlays) - if self.customMapOverlay != nil { - let fileManager = FileManager.default - let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! - let tilePath = documentsDirectory.appendingPathComponent("offline_map.mbtiles", isDirectory: false).path - if fileManager.fileExists(atPath: tilePath) { - Logger.services.info("Loading local map file") - if let overlay = LocalMBTileOverlay(mbTilePath: tilePath) { - overlay.canReplaceMapContent = false// customMapOverlay.canReplaceMapContent - mapView.addOverlay(overlay) - } - } else { - Logger.services.info("Couldn't find a local map file to load") - } - } - DispatchQueue.main.async { - self.presentCustomMapOverlayHash = self.customMapOverlay - self.loadedLastUpdatedLocalMapFile = self.lastUpdatedLocalMapFile - } - } - } - } + func makeUIView(context: Context) -> MKMapView { currentMapLayer = nil mapView.delegate = context.coordinator @@ -166,8 +140,6 @@ struct MapViewSwiftUI: UIViewRepresentable { return mapView } func updateUIView(_ mapView: MKMapView, context: Context) { - // Set MBTiles overlay layer - setMbtilesOverlay(mapView: mapView) // Set selected map base layer setMapBaseLayer(mapView: mapView) // Set map tile server and weather overlay layers diff --git a/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift b/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift index 8217af2d..d5fd9c01 100644 --- a/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift +++ b/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift @@ -122,7 +122,7 @@ struct WaypointFormMapKit: View { // Loading a waypoint from edit if coordinate.waypointId > 0 { newWaypoint.id = UInt32(coordinate.waypointId) - let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context!) + let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context) newWaypoint.latitudeI = waypoint.latitudeI newWaypoint.longitudeI = waypoint.longitudeI } else { @@ -179,12 +179,12 @@ struct WaypointFormMapKit: View { Menu { Button("For me", action: { - let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context!) - bleManager.context!.delete(waypoint) + let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context) + bleManager.context.delete(waypoint) do { - try bleManager.context!.save() + try bleManager.context.save() } catch { - bleManager.context!.rollback() + bleManager.context.rollback() } dismiss() }) Button("For everyone", action: { @@ -230,7 +230,7 @@ struct WaypointFormMapKit: View { } .onAppear { if coordinate.waypointId > 0 { - let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context!) + let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context) name = waypoint.name ?? "Dropped Pin" description = waypoint.longDescription ?? "" icon = String(UnicodeScalar(Int(waypoint.icon)) ?? "📍") diff --git a/Meshtastic/Views/Messages/ChannelList.swift b/Meshtastic/Views/Messages/ChannelList.swift index 30738fa3..a4fd86bf 100644 --- a/Meshtastic/Views/Messages/ChannelList.swift +++ b/Meshtastic/Views/Messages/ChannelList.swift @@ -11,13 +11,15 @@ import OSLog struct ChannelList: View { - @StateObject var appState = AppState.shared @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - @State var node: NodeInfoEntity? + @Binding + var node: NodeInfoEntity? + + @Binding + var channelSelection: ChannelEntity? - @State private var channelSelection: ChannelEntity? // Nothing selected by default. @State private var isPresentingDeleteChannelMessagesConfirm: Bool = false @State private var isPresentingTraceRouteSentAlert = false @@ -25,14 +27,14 @@ struct ChannelList: View { var restrictedChannels = ["gpio", "mqtt", "serial"] @ViewBuilder - private func makeNavigationLink( + private func makeChannelRow( myInfo: MyInfoEntity, channel: ChannelEntity ) -> some View { let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMdd", options: 0, locale: Locale.current) let dateFormatString = (localeDateFormat ?? "MM/dd/YY") - NavigationLink(destination: ChannelMessageList(myInfo: myInfo, channel: channel)) { + NavigationLink(value: channel) { let mostRecent = channel.allPrivateMessages.last(where: { $0.channel == channel.index }) let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent?.messageTimestamp ?? 0 )))) let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0 @@ -102,57 +104,50 @@ struct ChannelList: View { VStack { // Display Contacts for the rest of the non admin channels if let node, let myInfo = node.myInfo, let channels = myInfo.channels?.array as? [ChannelEntity] { - List(channels, id: \.self, selection: $channelSelection) { (channel: ChannelEntity) in - if !restrictedChannels.contains(channel.name?.lowercased() ?? "") { - makeNavigationLink(myInfo: myInfo, channel: channel) - .frame(height: 62) - .contextMenu { - if channel.allPrivateMessages.count > 0 { - Button(role: .destructive) { - isPresentingDeleteChannelMessagesConfirm = true - channelSelection = channel - } label: { - Label("Delete Messages", systemImage: "trash") - } - } - Button { - channel.mute = !channel.mute - - do { - let adminMessageId = bleManager.saveChannel(channel: channel.protoBuf, fromUser: node.user!, toUser: node.user!) - if adminMessageId > 0 { - context.refresh(channel, mergeChanges: true) + List(selection: $channelSelection) { + ForEach(channels) { (channel: ChannelEntity) in + if !restrictedChannels.contains(channel.name?.lowercased() ?? "") { + makeChannelRow(myInfo: myInfo, channel: channel) + .frame(height: 62) + .contextMenu { + if channel.allPrivateMessages.count > 0 { + Button(role: .destructive) { + isPresentingDeleteChannelMessagesConfirm = true + channelSelection = channel + } label: { + Label("Delete Messages", systemImage: "trash") } - - try context.save() - - } catch { - context.rollback() - Logger.data.error("💥 Save Channel Mute Error") } - } label: { - Label(channel.mute ? "Show Alerts" : "Hide Alerts", systemImage: channel.mute ? "bell" : "bell.slash") + Button { + channel.mute = !channel.mute + do { + let adminMessageId = bleManager.saveChannel(channel: channel.protoBuf, fromUser: node.user!, toUser: node.user!) + if adminMessageId > 0 { + context.refresh(channel, mergeChanges: true) + } + try context.save() + } catch { + context.rollback() + Logger.data.error("💥 Save Channel Mute Error") + } + } label: { + Label(channel.mute ? "Show Alerts" : "Hide Alerts", systemImage: channel.mute ? "bell" : "bell.slash") + } } - } - .confirmationDialog( - "This conversation will be deleted.", - isPresented: $isPresentingDeleteChannelMessagesConfirm, - titleVisibility: .visible - ) { - Button(role: .destructive) { - deleteChannelMessages(channel: channelSelection!, context: context) - context.refresh(myInfo, mergeChanges: true) - UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages - channelSelection = nil - } label: { - Text("delete") + .confirmationDialog( + "This conversation will be deleted.", + isPresented: $isPresentingDeleteChannelMessagesConfirm, + titleVisibility: .visible + ) { + Button(role: .destructive) { + deleteChannelMessages(channel: channelSelection!, context: context) + context.refresh(myInfo, mergeChanges: true) + channelSelection = nil + } label: { + Text("delete") + } } - } - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } + } } } .padding([.top, .bottom]) diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 882dad07..b996e790 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -11,7 +11,7 @@ import OSLog import SwiftUI struct ChannelMessageList: View { - @StateObject var appState = AppState.shared + @EnvironmentObject var appState: AppState @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @@ -80,7 +80,6 @@ struct ChannelMessageList: View { TapbackResponses(message: message) { appState.unreadChannelMessages = myInfo.unreadMessages - UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages context.refresh(myInfo, mergeChanges: true) } @@ -118,7 +117,6 @@ struct ChannelMessageList: View { try context.save() Logger.data.info("📖 [App] Read message \(message.messageId) ") appState.unreadChannelMessages = myInfo.unreadMessages - UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages context.refresh(myInfo, mergeChanges: true) } catch { Logger.data.error("Failed to read message \(message.messageId): \(error.localizedDescription)") @@ -131,9 +129,6 @@ struct ChannelMessageList: View { .padding([.top]) .scrollDismissesKeyboard(.immediately) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } if channel.allPrivateMessages.count > 0 { scrollView.scrollTo(channel.allPrivateMessages.last!.messageId) } diff --git a/Meshtastic/Views/Messages/Messages.swift b/Meshtastic/Views/Messages/Messages.swift index 35206d7c..88a6f2a2 100644 --- a/Meshtastic/Views/Messages/Messages.swift +++ b/Meshtastic/Views/Messages/Messages.swift @@ -14,52 +14,72 @@ import TipKit struct Messages: View { - @StateObject var appState = AppState.shared @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @ObservedObject + var router: Router + + @Binding + var unreadChannelMessages: Int + + @Binding + var unreadDirectMessages: Int + + // Aliases the navigation state for the NavigationSplitView sidebar selection + private var messagesSelection: Binding { + Binding( + get: { + guard case .messages(let state) = router.navigationState else { + return nil + } + return state + }, + set: { newValue in + router.navigationState = .messages(newValue) + } + ) + } + @State var node: NodeInfoEntity? @State private var userSelection: UserEntity? // Nothing selected by default. @State private var channelSelection: ChannelEntity? // Nothing selected by default. @State private var columnVisibility = NavigationSplitViewVisibility.all - enum MessagesSidebar { - case groupMessages - case directMessages - } - var body: some View { - NavigationSplitView(columnVisibility: $columnVisibility) { - // Sidebar - List { - NavigationLink { - ChannelList(node: node) - } label: { - Image(systemName: "person.3") - .symbolRenderingMode(.hierarchical) - .foregroundColor(.accentColor) - .brightness(0.2) - .font(.title) - Text("channels") - .font(.title2) - .badge(appState.unreadChannelMessages) - .padding(.vertical) + List(selection: messagesSelection) { + NavigationLink(value: MessagesNavigationState.channels()) { + Label { + Text("channels") + .badge(unreadChannelMessages) + .font(.title2) + .padding() + } icon: { + Image(systemName: "person.3") + .symbolRenderingMode(.hierarchical) + .foregroundColor(.accentColor) + .font(.title2) + .padding() + } + } - NavigationLink { - UserList(node: node) - } label: { - Image(systemName: "person.circle") - .symbolRenderingMode(.hierarchical) - .foregroundColor(.accentColor) - .brightness(0.2) - .font(.largeTitle) - Text("direct.messages") - .font(.title2) - .badge(appState.unreadDirectMessages) - .padding(.vertical) + NavigationLink(value: MessagesNavigationState.directMessages()) { + Label { + Text("direct.messages") + .badge(unreadDirectMessages) + .font(.title2) + .padding() + } icon: { + Image(systemName: "person.circle") + .symbolRenderingMode(.hierarchical) + .foregroundColor(.accentColor) + .font(.title2) + .padding() + } } + if #available(iOS 17.0, macOS 14.0, *) { TipView(MessagesTip(), arrowEdge: .top) } @@ -67,48 +87,63 @@ struct Messages: View { .navigationTitle("messages") .navigationBarTitleDisplayMode(.large) .navigationBarItems(leading: MeshtasticLogo()) - .onChange(of: (appState.navigationPath)) { newPath in - - if (newPath?.hasPrefix("meshtastic://messages")) != nil { - - if let urlComponent = URLComponents(string: newPath ?? "") { - let queryItems = urlComponent.queryItems - let messageId = queryItems?.first(where: { $0.name == "messageId" })?.value - let channel = queryItems?.first(where: { $0.name == "channel" })?.value - - if let channel { - Logger.services.info("Deep Link Channel \(channel)") - // selectedNode = nodes.first(where: { $0.num == Int64(nodeNum ?? "-1") }) - // AppState.shared.navigationPath = nil - } else { - Logger.services.info("Channel Deep Link not found") - } - } - } - } - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - if UserDefaults.preferredPeripheralId.count > 0 { - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(UserDefaults.preferredPeripheralNum)) - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - // Found a node, check it for a region - if !fetchedNode.isEmpty { - node = fetchedNode[0] - } - } catch { - - } - } - } - } content: { - + if case .messages(.channels) = router.navigationState { + ChannelList(node: $node, channelSelection: $channelSelection) + } else if case .messages(.directMessages) = router.navigationState { + UserList(node: $node, userSelection: $userSelection) + } else if case .messages(nil) = router.navigationState { + Text("Select a conversation type") + } } detail: { + if let myInfo = node?.myInfo, let channelSelection { + ChannelMessageList(myInfo: myInfo, channel: channelSelection) + } else if let userSelection { + UserMessageList(user: userSelection) + } else if case .messages(.channels) = router.navigationState { + Text("Select a channel") + } else if case .messages(.directMessages) = router.navigationState { + Text("Select a conversation") + } + }.onChange(of: router.navigationState) { _ in + setupNavigationState() + } + } + private func setupNavigationState() { + let nodeId = Int64(UserDefaults.preferredPeripheralNum) + if nodeId > 0 { + node = getNodeInfo(id: nodeId, context: context) + } + + guard case .messages(let state) = router.navigationState else { + return + } + + guard let state else { + channelSelection = nil + userSelection = nil + return + } + + switch state { + case .channels(channelId: let channelId, messageId: _): + if let channelId { + channelSelection = node?.myInfo?.channels?.first(where: { channel in + guard let channel = channel as? ChannelEntity else { return false } + return channel.id == channelId + }) as? ChannelEntity + } else { + channelSelection = nil + userSelection = nil + } + case .directMessages(userNum: let userNum, messageId: _): + if let userNum { + userSelection = getUser(id: userNum, context: context) + } else { + channelSelection = nil + userSelection = nil + } } } } diff --git a/Meshtastic/Views/Messages/TapbackResponses.swift b/Meshtastic/Views/Messages/TapbackResponses.swift index fb61b44b..77310184 100644 --- a/Meshtastic/Views/Messages/TapbackResponses.swift +++ b/Meshtastic/Views/Messages/TapbackResponses.swift @@ -9,7 +9,7 @@ struct TapbackResponses: View { @ViewBuilder var body: some View { - let tapbacks = message.value(forKey: "tapbacks") as? [MessageEntity] ?? [] + let tapbacks = message.tapbacks if !tapbacks.isEmpty { VStack(alignment: .trailing) { HStack { diff --git a/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift b/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift index 7ae0fb84..469e57a4 100644 --- a/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift +++ b/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift @@ -126,7 +126,7 @@ private extension MessageDestination { var positionDestNum: Int64 { switch self { case let .user(user): return user.num - case .channel: return Int64(BLEManager.emptyNodeNum) + case .channel: return Int64(Constants.maximumNodeNum) } } diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index 8a7b6333..5c7214fe 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -14,7 +14,6 @@ import TipKit struct UserList: View { - @StateObject var appState = AppState.shared @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @State private var searchText = "" @@ -34,19 +33,20 @@ struct UserList: View { sortDescriptors: [NSSortDescriptor(key: "lastMessage", ascending: false), NSSortDescriptor(key: "userNode.favorite", ascending: false), NSSortDescriptor(key: "longName", ascending: true)], - animation: .default) - + animation: .default + ) private var users: FetchedResults - @State var node: NodeInfoEntity? - @State var selectedUserNum: Int64? - @State private var userSelection: UserEntity? // Nothing selected by default. + + @Binding var node: NodeInfoEntity? + @Binding var userSelection: UserEntity? + @State private var isPresentingDeleteUserMessagesConfirm: Bool = false var body: some View { let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMdd", options: 0, locale: Locale.current) let dateFormatString = (localeDateFormat ?? "MM/dd/YY") VStack { - List { + List(selection: $userSelection) { if #available(iOS 17.0, macOS 14.0, *) { TipView(ContactsTip(), arrowEdge: .bottom) } @@ -55,8 +55,8 @@ struct UserList: View { let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent?.messageTimestamp ?? 0 )))) let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0 let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0 - if user.num != bleManager.connectedPeripheral?.num ?? 0 { - NavigationLink(destination: UserMessageList(user: user)) { + if user.num != bleManager.connectedPeripheral?.num ?? 0 { + NavigationLink(value: user) { ZStack { Image(systemName: "circle.fill") .opacity(user.unreadMessages > 0 ? 1 : 0) @@ -161,7 +161,6 @@ struct UserList: View { Button(role: .destructive) { deleteUserMessages(user: userSelection!, context: context) context.refresh(node!.user!, mergeChanges: true) - UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages } label: { Text("delete") } @@ -207,13 +206,7 @@ struct UserList: View { .onChange(of: distanceFilter) { _ in searchUserList() } - .onChange(of: selectedUserNum) { newUserNum in - userSelection = users.first(where: { $0.num == newUserNum }) - } .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } searchUserList() } .safeAreaInset(edge: .bottom, alignment: .trailing) { diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index 41c83f51..6514ead8 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -10,10 +10,11 @@ import CoreData import OSLog struct UserMessageList: View { - @StateObject var appState = AppState.shared - @Environment(\.managedObjectContext) var context - @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var appState: AppState + @EnvironmentObject var bleManager: BLEManager + @Environment(\.managedObjectContext) var context + // Keyboard State @FocusState var messageFieldFocused: Bool // View State Items @@ -64,7 +65,6 @@ struct UserMessageList: View { TapbackResponses(message: message) { appState.unreadDirectMessages = user.unreadMessages - UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages } HStack { @@ -102,7 +102,6 @@ struct UserMessageList: View { try context.save() Logger.data.info("📖 [App] Read message \(message.messageId) ") appState.unreadDirectMessages = user.unreadMessages - UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages } catch { Logger.data.error("Failed to read message \(message.messageId): \(error.localizedDescription)") @@ -116,9 +115,6 @@ struct UserMessageList: View { .padding([.top]) .scrollDismissesKeyboard(.immediately) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } if user.messageList.count > 0 { scrollView.scrollTo(user.messageList.last!.messageId) } diff --git a/Meshtastic/Views/Nodes/DetectionSensorLog.swift b/Meshtastic/Views/Nodes/DetectionSensorLog.swift index da2e4e09..06c468ca 100644 --- a/Meshtastic/Views/Nodes/DetectionSensorLog.swift +++ b/Meshtastic/Views/Nodes/DetectionSensorLog.swift @@ -124,11 +124,6 @@ struct DetectionSensorLog: View { ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } .fileExporter( isPresented: $isExporting, document: CsvDocument(emptyCsv: exportString), diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 396b6098..d62d62c0 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -12,15 +12,19 @@ struct DeviceMetricsLog: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } @State private var isPresentingClearLogConfirm: Bool = false @State var isExporting = false @State var exportString = "" @State private var batteryChartColor: Color = .blue - @State private var airtimeChartColor: Color = .orange + @State private var airtimeChartColor: Color = .yellow @State private var channelUtilizationChartColor: Color = .green @ObservedObject var node: NodeInfoEntity + @State private var sortOrder = [KeyPathComparator(\TelemetryEntity.time, order: .reverse)] + @State private var selection: TelemetryEntity.ID? + @State private var chartSelection: Date? var body: some View { VStack { @@ -32,67 +36,114 @@ struct DeviceMetricsLog: View { .sorted { $0.time! < $1.time! } if chartData.count > 0 { GroupBox(label: Label("\(deviceMetrics.count) Readings Total", systemImage: "chart.xyaxis.line")) { - - Chart { - ForEach(chartData, id: \.self) { point in - Plot { - LineMark( - x: .value("x", point.time!), - y: .value("y", point.batteryLevel) - ) - } - .accessibilityLabel("Line Series") - .accessibilityValue("X: \(point.time!), Y: \(point.batteryLevel)") - .foregroundStyle(batteryChartColor) - .interpolationMethod(.linear) - - Plot { - PointMark( - x: .value("x", point.time!), - y: .value("y", point.channelUtilization) - ) - .symbolSize(25) - } - .accessibilityLabel("Line Series") - .accessibilityValue("X: \(point.time!), Y: \(point.channelUtilization)") - .foregroundStyle(channelUtilizationChartColor) - - RuleMark(y: .value("Limit", 10)) - .lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10])) + if #available(iOS 17.0, macOS 14.0, *) { + Chart { + ForEach(chartData, id: \.self) { point in + Plot { + LineMark( + x: .value("x", point.time!), + y: .value("y", point.batteryLevel) + ) + } + .accessibilityLabel("Line Series") + .accessibilityValue("X: \(point.time!), Y: \(point.batteryLevel)") + .foregroundStyle(batteryChartColor) + .interpolationMethod(.linear) + Plot { + PointMark( + x: .value("x", point.time!), + y: .value("y", point.channelUtilization) + ) + .symbolSize(25) + } + .accessibilityLabel("Line Series") + .accessibilityValue("X: \(point.time!), Y: \(point.channelUtilization)") + .foregroundStyle(channelUtilizationChartColor) + if let chartSelection { + RuleMark(x: .value("Minute", chartSelection, unit: .minute)) + .foregroundStyle(.tertiary.opacity(0.5)) +// .annotation( +// position: .automatic, +// overflowResolution: .init(x: .fit, y: .disabled) +// ) { +// ZStack { +// Text("\(getTelemetry(for: chartSelection))") +// } +// .padding() +// .background { +// RoundedRectangle(cornerRadius: 4) +// .foregroundStyle(Color.accentColor.opacity(0.2)) +// } +// } + } + RuleMark(y: .value("Network Status Orange", 25)) + .lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10])) + .foregroundStyle(.orange) + RuleMark(y: .value("Network Status Red", 50)) + .lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10])) + .foregroundStyle(.red) + Plot { + PointMark( + x: .value("x", point.time!), + y: .value("y", point.airUtilTx) + ) + .symbolSize(25) + } + .accessibilityLabel("Line Series") + .accessibilityValue("X: \(point.time!), Y: \(point.airUtilTx)") .foregroundStyle(airtimeChartColor) - - Plot { - PointMark( - x: .value("x", point.time!), - y: .value("y", point.airUtilTx) - ) - .symbolSize(25) } - .accessibilityLabel("Line Series") - .accessibilityValue("X: \(point.time!), Y: \(point.airUtilTx)") - .foregroundStyle(airtimeChartColor) } + .chartXAxis(content: { + AxisMarks(position: .top) + }) + .chartXAxis(.automatic) + .chartXSelection(value: $chartSelection) + .chartYScale(domain: 0...100) + .chartForegroundStyleScale([ + idiom == .phone ? "Battery" : "Battery Level": batteryChartColor, + "Channel Utilization": channelUtilizationChartColor, + "Airtime": airtimeChartColor + ]) + .chartLegend(position: .automatic, alignment: .bottom) + } else { + // Fallback on earlier versions } - .chartXAxis(content: { - AxisMarks(position: .top) - }) - .chartXAxis(.automatic) - .chartYScale(domain: 0...100) - .chartForegroundStyleScale([ - "Battery Level": .blue, - "Channel Utilization": .green, - "Airtime": .orange - ]) - .chartLegend(position: .automatic, alignment: .bottom) } - .frame(minHeight: 250) + .frame(minHeight: 240) } let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMdjmma", options: 0, locale: Locale.current) let dateFormatString = (localeDateFormat ?? "M/d/YY j:mma").replacingOccurrences(of: ",", with: "") - if UIScreen.main.bounds.size.width > 768 && (UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac) { - // Add a table for mac and ipad - // Table(Array(deviceMetrics),id: \.self) { - Table(deviceMetrics) { + if idiom == .phone { + /// Single Cell Compact display for phones + Table(deviceMetrics, selection: $selection, sortOrder: $sortOrder) { + TableColumn("battery.level") { dm in + HStack { + Text(dm.time?.formattedDate(format: dateFormatString) ?? "unknown.age".localized) + .font(.caption) + .fontWeight(.semibold) + Spacer() + Image(systemName: "bolt") + .font(.caption) + .symbolRenderingMode(.multicolor) + Text("Volts \(String(format: "%.2f", dm.voltage)) ") + .font(.caption2) + BatteryCompact(batteryLevel: dm.batteryLevel, font: .caption, iconFont: .callout, color: .accentColor) + } + HStack { + Text("Channel Utilization \(String(format: "%.2f", dm.channelUtilization))% ") + .foregroundColor(dm.channelUtilization < 25 ? .green : (dm.channelUtilization > 50 ? .red : .orange)) + Text("Airtime \(String(format: "%.2f", dm.airUtilTx))%") + .foregroundColor(.secondary) + Spacer() + } + .font(.caption) + } + .width(ideal: 200, max: .infinity) + } + } else { + /// Multi Column table for ipads and mac + Table(deviceMetrics, selection: $selection, sortOrder: $sortOrder) { TableColumn("battery.level") { dm in if dm.batteryLevel > 100 { Text("Powered") @@ -105,6 +156,7 @@ struct DeviceMetricsLog: View { } TableColumn("channel.utilization") { dm in Text("\(String(format: "%.2f", dm.channelUtilization))%") + .foregroundColor(dm.channelUtilization < 25 ? .green : (dm.channelUtilization > 50 ? .red : .orange)) } TableColumn("airtime") { dm in Text("\(String(format: "%.2f", dm.airUtilTx))%") @@ -115,61 +167,12 @@ struct DeviceMetricsLog: View { let components = (now.. 100 { - Text("PWD") - .font(.caption) - } else { - Text("\(String(dm.batteryLevel))%") - .font(.caption) - } - Text(String(dm.voltage)) - .font(.caption) - Text("\(String(format: "%.2f", dm.channelUtilization))%") - .font(.caption) - Text("\(String(format: "%.2f", dm.airUtilTx))%") - .font(.caption) - Text(dm.time?.formattedDate(format: dateFormatString) ?? "unknown.age".localized) - .font(.caption) - } - } - } - .padding(.leading, 15) - .padding(.trailing, 5) - } } HStack { Button(role: .destructive) { @@ -179,7 +182,7 @@ struct DeviceMetricsLog: View { } .buttonStyle(.bordered) .buttonBorderShape(.capsule) - .controlSize(.large) + .controlSize(idiom == .phone ? .regular : .large) .padding(.bottom) .padding(.leading) .confirmationDialog( @@ -204,10 +207,16 @@ struct DeviceMetricsLog: View { } .buttonStyle(.bordered) .buttonBorderShape(.capsule) - .controlSize(.large) + .controlSize(idiom == .phone ? .regular : .large) .padding(.bottom) .padding(.trailing) } + .onChange(of: selection) { newSelection in + guard let metrics = deviceMetrics.first(where: { $0.id == newSelection }) else { + return + } + chartSelection = metrics.time + } } else { if #available (iOS 17, *) { ContentUnavailableView("No Device Metrics", systemImage: "slash.circle") @@ -222,11 +231,6 @@ struct DeviceMetricsLog: View { ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } .fileExporter( isPresented: $isExporting, document: CsvDocument(emptyCsv: exportString), diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index d24ad831..f0d2dd04 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -199,11 +199,6 @@ struct EnvironmentMetricsLog: View { ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } .fileExporter( isPresented: $isExporting, document: CsvDocument(emptyCsv: exportString), diff --git a/Meshtastic/Views/Nodes/Helpers/Actions/DeleteNodeButton.swift b/Meshtastic/Views/Nodes/Helpers/Actions/DeleteNodeButton.swift index 247a4380..71c3a317 100644 --- a/Meshtastic/Views/Nodes/Helpers/Actions/DeleteNodeButton.swift +++ b/Meshtastic/Views/Nodes/Helpers/Actions/DeleteNodeButton.swift @@ -10,42 +10,55 @@ struct DeleteNodeButton: View { var connectedNode: NodeInfoEntity var node: NodeInfoEntity + + @Environment(\.dismiss) private var dismiss - @State - private var isPresentingAlert = false + @State private var isPresentingAlert = false - var body: some View { - Button(role: .destructive) { - isPresentingAlert = true - } label: { - Label { - Text("Delete Node") - } icon: { - Image(systemName: "trash") - .symbolRenderingMode(.multicolor) - } - } - .confirmationDialog( - "are.you.sure", - isPresented: $isPresentingAlert, - titleVisibility: .visible - ) { - Button("Delete Node", role: .destructive) { - guard let deleteNode = getNodeInfo( - id: node.num, - context: context - ) else { - Logger.data.error("Unable to find node info to delete node \(node.num)") - return + var body: some View { + if node.num != connectedNode.num { + Button(role: .destructive) { + isPresentingAlert = true + } label: { + Label { + Text("Delete Node") + } icon: { + Image(systemName: "trash") + .symbolRenderingMode(.multicolor) } - let success = bleManager.removeNode( - node: deleteNode, - connectedNodeNum: connectedNode.num - ) - if !success { - Logger.data.error("Failed to delete node \(deleteNode.user?.longName ?? "unknown".localized)") + } + .alert( + "are.you.sure", + isPresented: $isPresentingAlert + ) { + Button("OK") { }.keyboardShortcut(.defaultAction) + } message: { + Text("Delete Node?") + } + .confirmationDialog( + "are.you.sure", + isPresented: $isPresentingAlert, + titleVisibility: .visible + ) { + Button("Delete Node", role: .destructive) { + guard let deleteNode = getNodeInfo( + id: node.num, + context: context + ) else { + Logger.data.error("Unable to find node info to delete node \(node.num)") + return + } + let success = bleManager.removeNode( + node: deleteNode, + connectedNodeNum: connectedNode.num + ) + if !success { + Logger.data.error("Failed to delete node \(deleteNode.user?.longName ?? "unknown".localized)") + } else { + dismiss() + } } } } - } + } } diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift index 2c943be9..edfec3be 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift @@ -11,7 +11,6 @@ import MapKit @available(iOS 17.0, macOS 14.0, *) struct MeshMapContent: MapContent { - @StateObject var appState = AppState.shared /// Parameters @Binding var showUserLocation: Bool @AppStorage("meshMapShowNodeHistory") private var showNodeHistory = false @@ -76,7 +75,7 @@ struct MeshMapContent: MapContent { } } } - .onTapGesture { location in + .onTapGesture { _ in selectedPosition = (selectedPosition == position ? nil : position) } } diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift index 6ac10848..38b96a5d 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift @@ -13,6 +13,7 @@ import MapKit @available(iOS 17.0, macOS 14.0, *) struct MapSettingsForm: View { @Environment(\.dismiss) private var dismiss + @State private var currentDetent = PresentationDetent.medium @AppStorage("meshMapShowNodeHistory") private var nodeHistory = false @AppStorage("meshMapShowRouteLines") private var routeLines = false @AppStorage("enableMapConvexHull") private var convexHull = false @@ -121,8 +122,10 @@ Spacer() .padding(.bottom) #endif } - .presentationDetents([.fraction(meshMap ? 0.55 : 0.45), .fraction(0.65)]) + .presentationDetents([.medium, .large], selection: $currentDetent) + .presentationContentInteraction(.scrolls) .presentationDragIndicator(.visible) + .presentationBackgroundInteraction(.enabled(upThrough: .medium)) } } diff --git a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift index 221bf24f..faa49760 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift @@ -213,7 +213,9 @@ struct PositionPopover: View { #endif } } - .presentationDetents([.fraction(0.65), .fraction(0.75), .fraction(0.85)]) + .presentationDetents([.medium, .large]) + .presentationContentInteraction(.scrolls) .presentationDragIndicator(.visible) + .presentationBackgroundInteraction(.enabled(upThrough: .medium)) } } diff --git a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift index 8d4f79b4..17573745 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift @@ -184,11 +184,11 @@ struct WaypointForm: View { Menu { Button("For me", action: { - bleManager.context!.delete(waypoint) + bleManager.context.delete(waypoint) do { - try bleManager.context!.save() + try bleManager.context.save() } catch { - bleManager.context!.rollback() + bleManager.context.rollback() } dismiss() }) Button("For everyone", action: { @@ -213,11 +213,11 @@ struct WaypointForm: View { newWaypoint.expire = UInt32(1) if bleManager.sendWaypoint(waypoint: newWaypoint) { - bleManager.context!.delete(waypoint) + bleManager.context.delete(waypoint) do { - try bleManager.context!.save() + try bleManager.context.save() } catch { - bleManager.context!.rollback() + bleManager.context.rollback() } dismiss() } else { @@ -351,7 +351,7 @@ struct WaypointForm: View { } .onAppear { if waypoint.id > 0 { - let waypoint = getWaypoint(id: Int64(waypoint.id), context: bleManager.context!) + let waypoint = getWaypoint(id: Int64(waypoint.id), context: bleManager.context) name = waypoint.name ?? "Dropped Pin" description = waypoint.longDescription ?? "" icon = String(UnicodeScalar(Int(waypoint.icon)) ?? "📍") diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index f5445f18..fc9b44c1 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -396,11 +396,6 @@ struct NodeDetail: View { } } .listStyle(.insetGrouped) - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } } } } diff --git a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift index 41ccf866..e1626b6b 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift @@ -29,7 +29,7 @@ struct NodeInfoItem: View { if let user = node.user { VStack(alignment: .center) { if user.hwModel != "UNSET" { - Image(user.hwModel ?? "unset".localized) + Image(user.hardwareImage ?? "UNSET") .resizable() .aspectRatio(contentMode: .fit) .frame(width: 75, height: 75) diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift b/Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift index 457d6a16..6236f7b7 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift @@ -159,6 +159,7 @@ struct NodeListFilter: View { } } } + .listStyle(.insetGrouped) #if targetEnvironment(macCatalyst) Spacer() Button { @@ -172,7 +173,9 @@ struct NodeListFilter: View { .padding(.bottom) #endif } - .presentationDetents([.fraction(roleFilter ? 1.0 : 0.65)]) + .presentationDetents([.medium, .large]) + .presentationContentInteraction(.scrolls) .presentationDragIndicator(.visible) + .presentationBackgroundInteraction(.enabled(upThrough: .medium)) } } diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index 172795e4..a68a63cd 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -35,7 +35,7 @@ struct NodeListItem: View { if node.favorite { Spacer() Image(systemName: "star.fill") - .foregroundColor(.yellow) + .symbolRenderingMode(.multicolor) } } if connected { @@ -75,11 +75,11 @@ struct NodeListItem: View { HStack { Image(systemName: "envelope.arrow.triangle.branch") .font(.callout) - .symbolRenderingMode(.hierarchical) + .symbolRenderingMode(.multicolor) .frame(width: 30) Text("storeforward".localized) .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) - .foregroundColor(.gray) + .foregroundColor(.secondary) } } @@ -94,7 +94,7 @@ struct NodeListItem: View { let metersAway = nodeCoord.distance(from: myCoord) Image(systemName: "lines.measurement.horizontal") .font(.callout) - .symbolRenderingMode(.hierarchical) + .symbolRenderingMode(.multicolor) .frame(width: 30) DistanceText(meters: metersAway) .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) @@ -109,11 +109,11 @@ struct NodeListItem: View { let metersAway = nodeCoord.distance(from: myCoord) Image(systemName: "lines.measurement.horizontal") .font(.callout) - .symbolRenderingMode(.hierarchical) + .symbolRenderingMode(.multicolor) .frame(width: 30) DistanceText(meters: metersAway) .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) - .foregroundColor(.gray) + .foregroundColor(.secondary) } } } @@ -124,18 +124,17 @@ struct NodeListItem: View { HStack { Image(systemName: "\(node.channel).circle.fill") .font(.title2) - .symbolRenderingMode(.hierarchical) + .symbolRenderingMode(.multicolor) .frame(width: 30) - .foregroundColor(.accentColor) Text("Channel") - .foregroundColor(.gray) + .foregroundColor(.secondary) .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) } } if node.viaMqtt && connectedNode != node.num { Image(systemName: "dot.radiowaves.up.forward") - .symbolRenderingMode(.hierarchical) + .symbolRenderingMode(.multicolor) .font(.callout) .frame(width: 30) Text("MQTT") @@ -165,7 +164,7 @@ struct NodeListItem: View { .frame(width: 30) } if node.hasEnvironmentMetrics { - Image(systemName: "cloud.sun.rain") + Image(systemName: "cloud.sun.rain.fill") .symbolRenderingMode(.hierarchical) .font(.callout) .frame(width: 30) @@ -190,13 +189,13 @@ struct NodeListItem: View { HStack { Image(systemName: "hare") .font(.callout) - .symbolRenderingMode(.hierarchical) + .symbolRenderingMode(.multicolor) Text("Hops Away:") - .foregroundColor(.gray) + .foregroundColor(.secondary) .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) Image(systemName: "\(node.hopsAway).square") .font(.title2) - .symbolRenderingMode(.hierarchical) + .symbolRenderingMode(.multicolor) } } else { if node.snr != 0 && !node.viaMqtt { diff --git a/Meshtastic/Views/Nodes/MeshMap.swift b/Meshtastic/Views/Nodes/MeshMap.swift index caa3f5d6..71cbfaf2 100644 --- a/Meshtastic/Views/Nodes/MeshMap.swift +++ b/Meshtastic/Views/Nodes/MeshMap.swift @@ -19,7 +19,10 @@ struct MeshMap: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - @StateObject var appState = AppState.shared + + @ObservedObject + var router: Router + /// Parameters @State var showUserLocation: Bool = true /// Map State User Defaults @@ -106,33 +109,10 @@ struct MeshMap: View { .sheet(isPresented: $isEditingSettings) { MapSettingsForm(traffic: $showTraffic, pointsOfInterest: $showPointsOfInterest, mapLayer: $selectedMapLayer, meshMap: $isMeshMap) } -// .onChange(of: (appState.navigationPath)) { newPath in -// -// if ((newPath?.hasPrefix("meshtastic://open-waypoint")) != nil) { -// guard let url = URL(string: appState.navigationPath ?? "NONE") else { -// logger.error("Invalid URL") -// return -// } -// guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { -// logger.error("Invalid URL Components") -// return -// } -// guard let action = components.host, action == "open-waypoint" else { -// logger.error("Unknown waypoint URL action") -// return -// } -// guard let waypointId = components.queryItems?.first(where: { $0.name == "id" })?.value else { -// logger.error("Waypoint id not found") -// return -// } -// guard let waypoint = waypoints.first(where: { $0.id == Int64(waypointId) }) else { -// logger.error("Waypoint not found") -// return -// } -// //showWaypoints = true -// //position = .camera(MapCamera(centerCoordinate: waypoint.coordinate, distance: 1000, heading: 0, pitch: 60)) -// } -// } + .onChange(of: router.navigationState) { + guard case .map(let selectedNodeNum) = router.navigationState else { return } + //TODO: handle deep link for waypoints + } .onChange(of: (selectedMapLayer)) { newMapLayer in switch selectedMapLayer { case .standard: diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 238fd92a..ac9aa65e 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -9,8 +9,15 @@ import CoreLocation import OSLog struct NodeList: View { + @Environment(\.managedObjectContext) + var context + + @EnvironmentObject + var bleManager: BLEManager + + @ObservedObject + var router: Router - @StateObject var appState = AppState.shared @State private var columnVisibility = NavigationSplitViewVisibility.all @State private var selectedNode: NodeInfoEntity? @State private var searchText = "" @@ -37,19 +44,23 @@ struct NodeList: View { @SceneStorage("selectedDetailView") var selectedDetailView: String? - @Environment(\.managedObjectContext) var context - @EnvironmentObject var bleManager: BLEManager - @FetchRequest( sortDescriptors: [ NSSortDescriptor(key: "favorite", ascending: false), NSSortDescriptor(key: "lastHeard", ascending: false), - NSSortDescriptor(key: "user.longName", ascending: true), + NSSortDescriptor(key: "user.longName", ascending: true) ], animation: .spring ) var nodes: FetchedResults + var connectedNode: NodeInfoEntity? { + getNodeInfo( + id: bleManager.connectedPeripheral?.num ?? 0, + context: context + ) + } + @ViewBuilder func contextMenuActions( node: NodeInfoEntity, @@ -78,19 +89,17 @@ struct NodeList: View { bleManager: bleManager, node: node ) - DeleteNodeButton( - bleManager: bleManager, - context: context, - connectedNode: connectedNode, - node: node - ) +// DeleteNodeButton( +// bleManager: bleManager, +// context: context, +// connectedNode: connectedNode, +// node: node +// ) } } var body: some View { NavigationSplitView(columnVisibility: $columnVisibility) { - let connectedNodeNum = Int(bleManager.connectedPeripheral?.num ?? 0) - let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) List(nodes, id: \.self, selection: $selectedNode) { node in NodeListItem( node: node, @@ -157,10 +166,7 @@ struct NodeList: View { if let node = selectedNode { NavigationStack { NodeDetail( - connectedNode: nodes.first(where: { - let connectedNodeNum = Int(bleManager.connectedPeripheral?.num ?? 0) - return $0.num == connectedNodeNum - }), + connectedNode: connectedNode, node: node, columnVisibility: columnVisibility ) @@ -184,7 +190,6 @@ struct NodeList: View { } ) } - } else { if #available (iOS 17, *) { ContentUnavailableView("select.node", systemImage: "flipphone") @@ -198,7 +203,6 @@ struct NodeList: View { } else { Text("Select something to view") } - } .navigationSplitViewStyle(.balanced) .onChange(of: searchText) { _ in @@ -242,29 +246,22 @@ struct NodeList: View { await searchNodeList() } } - .onChange(of: (appState.navigationPath)) { newPath in - - guard let deepLink = newPath else { - return + .onChange(of: distanceFilter) { _ in + Task { + await searchNodeList() } - if deepLink.hasPrefix("meshtastic://nodes") { - - if let urlComponent = URLComponents(string: deepLink) { - let queryItems = urlComponent.queryItems - let nodeNum = queryItems?.first(where: { $0.name == "nodenum" })?.value - if nodeNum == nil { - Logger.data.debug("nodeNum not found") - } else { - selectedNode = nodes.first(where: { $0.num == Int64(nodeNum ?? "-1") }) - AppState.shared.navigationPath = nil - } + } + .onChange(of: router.navigationState) { _ in + // Handle deep link routing + if case .nodes(let selected) = router.navigationState { + self.selectedNode = selected.flatMap { + getNodeInfo(id: $0, context: context) } + } else { + self.selectedNode = nil } } .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } Task { await searchNodeList() } diff --git a/Meshtastic/Views/Nodes/NodeMap.swift b/Meshtastic/Views/Nodes/NodeMap.swift index cd79ef0d..53b0aae3 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -13,14 +13,15 @@ import CoreData struct NodeMap: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - @StateObject var appState = AppState.shared + + @ObservedObject + var router: Router @State var selectedMapLayer: MapLayer = UserDefaults.mapLayer @State var enableMapRecentering: Bool = UserDefaults.enableMapRecentering @State var enableMapRouteLines: Bool = UserDefaults.enableMapRouteLines @State var enableMapNodeHistoryPins: Bool = UserDefaults.enableMapNodeHistoryPins @State var enableOfflineMaps: Bool = UserDefaults.enableOfflineMaps @State var selectedTileServer: MapTileServer = UserDefaults.mapTileServer - @State var enableOfflineMapsMBTiles: Bool = UserDefaults.enableOfflineMapsMBTiles @State var enableOverlayServer: Bool = UserDefaults.enableOverlayServer @State var selectedOverlayServer: MapOverlayServer = UserDefaults.mapOverlayServer @State var mapTilesAboveLabels: Bool = UserDefaults.mapTilesAboveLabels @@ -169,46 +170,32 @@ struct NodeMap: View { } if enableOfflineMaps { VStack(alignment: .leading) { - if !enableOfflineMapsMBTiles { - Picker(selection: $selectedTileServer, - label: Text("Tile Server")) { - ForEach(MapTileServer.allCases, id: \.self) { tsl in - Text(tsl.description) - } - } - .pickerStyle(DefaultPickerStyle()) - .onChange(of: (selectedTileServer)) { newSelectedTileServer in - UserDefaults.mapTileServer = newSelectedTileServer - } - Text("Attribution:") - .fontWeight(.semibold) - .font(.footnote) - Text(LocalizedStringKey(selectedTileServer.attribution)) - .font(.footnote) - .foregroundColor(.gray) - .padding(0) - Divider() - Toggle(isOn: $mapTilesAboveLabels) { - Text("Tiles above Labels") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - .onTapGesture { - self.mapTilesAboveLabels.toggle() - UserDefaults.mapTilesAboveLabels = self.mapTilesAboveLabels + Picker(selection: $selectedTileServer, + label: Text("Tile Server")) { + ForEach(MapTileServer.allCases, id: \.self) { tsl in + Text(tsl.description) } } + .pickerStyle(DefaultPickerStyle()) + .onChange(of: (selectedTileServer)) { newSelectedTileServer in + UserDefaults.mapTileServer = newSelectedTileServer + } + Text("Attribution:") + .fontWeight(.semibold) + .font(.footnote) + Text(LocalizedStringKey(selectedTileServer.attribution)) + .font(.footnote) + .foregroundColor(.gray) + .padding(0) Divider() - Toggle(isOn: $enableOfflineMapsMBTiles) { - Text("Enable MB Tiles") + Toggle(isOn: $mapTilesAboveLabels) { + Text("Tiles above Labels") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .onTapGesture { - self.enableOfflineMapsMBTiles.toggle() - UserDefaults.enableOfflineMapsMBTiles = self.enableOfflineMapsMBTiles + self.mapTilesAboveLabels.toggle() + UserDefaults.mapTilesAboveLabels = self.mapTilesAboveLabels } - Text("The latest MBTiles file shared with meshtastic will be loaded into the map.") - .font(.footnote) - .foregroundColor(.gray) } } } @@ -240,9 +227,6 @@ struct NodeMap: View { }) .onAppear(perform: { UIApplication.shared.isIdleTimerDisabled = true - if self.bleManager.context == nil { - self.bleManager.context = context - } }) .onDisappear(perform: { UIApplication.shared.isIdleTimerDisabled = false diff --git a/Meshtastic/Views/Nodes/PaxCounterLog.swift b/Meshtastic/Views/Nodes/PaxCounterLog.swift index 9042f091..6d764c63 100644 --- a/Meshtastic/Views/Nodes/PaxCounterLog.swift +++ b/Meshtastic/Views/Nodes/PaxCounterLog.swift @@ -209,11 +209,6 @@ struct PaxCounterLog: View { ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } .fileExporter( isPresented: $isExporting, document: CsvDocument(emptyCsv: exportString), diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index 1c7ef170..43354830 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -179,10 +179,5 @@ struct PositionLog: View { ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } } } diff --git a/Meshtastic/Views/Nodes/TraceRouteLog.swift b/Meshtastic/Views/Nodes/TraceRouteLog.swift index cd44cd30..9556298a 100644 --- a/Meshtastic/Views/Nodes/TraceRouteLog.swift +++ b/Meshtastic/Views/Nodes/TraceRouteLog.swift @@ -145,10 +145,5 @@ struct TraceRouteLog: View { ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } } } diff --git a/Meshtastic/Views/Settings/AppLog.swift b/Meshtastic/Views/Settings/AppLog.swift index 2ed2b081..fd3db810 100644 --- a/Meshtastic/Views/Settings/AppLog.swift +++ b/Meshtastic/Views/Settings/AppLog.swift @@ -9,7 +9,7 @@ import SwiftUI import OSLog /// Needed for TableColumnForEach -@available(iOS 17.4, macOS 14.4, *) +@available(iOS 17.0, macOS 14.0, *) struct AppLog: View { @State private var logs: [OSLogEntryLog] = [] @@ -33,60 +33,100 @@ struct AppLog: View { .secondFraction(.fractional(3)) var body: some View { + HStack { - Table(logs, selection: $selection, sortOrder: $sortOrder) { - if idiom != .phone { - TableColumn("log.time") { value in - Text(value.date.formatted(dateFormatStyle)) - } - .width(min: 125, max: 150) - TableColumn("log.level") { value in - Text(value.level.description) - .foregroundStyle(value.level.color) - } - .width(min: 85, max: 110) - TableColumn("log.category", value: \.category) - .width(min: 80, max: 130) - } - TableColumn("log.message", value: \.composedMessage) { value in - Text(value.composedMessage) - .foregroundStyle(value.level.color) - .font(idiom == .phone ? .caption : .body) - } - .width(ideal: 200, max: .infinity) - } - .monospaced() - - .safeAreaInset(edge: .bottom, alignment: .trailing) { - HStack { - Button(action: { - withAnimation { - isEditingFilters = !isEditingFilters + if idiom == .phone { + Table(logs, selection: $selection, sortOrder: $sortOrder) { + TableColumn("log.message", value: \.composedMessage) { value in + Text(value.composedMessage) + .foregroundStyle(value.level.color) + .font(.caption) } - }) { - Image(systemName: !isEditingFilters ? "line.3.horizontal.decrease.circle" : "line.3.horizontal.decrease.circle.fill") - .padding(.vertical, 5) + .width(ideal: 200, max: .infinity) + } + .monospaced() + .safeAreaInset(edge: .bottom, alignment: .trailing) { + HStack { + Button(action: { + withAnimation { + isEditingFilters = !isEditingFilters + } + }) { + Image(systemName: !isEditingFilters ? "line.3.horizontal.decrease.circle" : "line.3.horizontal.decrease.circle.fill") + .padding(.vertical, 5) + } + .tint(Color(UIColor.secondarySystemBackground)) + .foregroundColor(.accentColor) + .buttonStyle(.borderedProminent) + } + .controlSize(.regular) + .padding(5) + } + .padding(.bottom, 5) + .padding(.trailing, 5) + .searchable(text: $searchText, placement: .navigationBarDrawer, prompt: "Search") + .disabled(selection != nil) + .overlay { + if logs.isEmpty { + ContentUnavailableView("Loading Logs. . .", systemImage: "scroll") + } + } + .refreshable { + await logs = searchAppLogs() + logs.sort(using: sortOrder) + } + } else { + Table(logs, selection: $selection, sortOrder: $sortOrder) { + TableColumn("log.time") { value in + Text(value.date.formatted(dateFormatStyle)) + } + .width(min: 125, max: 150) + TableColumn("log.level") { value in + Text(value.level.description) + .foregroundStyle(value.level.color) + } + .width(min: 85, max: 110) + TableColumn("log.category", value: \.category) + .width(min: 80, max: 130) + TableColumn("log.message", value: \.composedMessage) { value in + Text(value.composedMessage) + .foregroundStyle(value.level.color) + .font(.body) + } + .width(ideal: 200, max: .infinity) + } + .monospaced() + .safeAreaInset(edge: .bottom, alignment: .trailing) { + HStack { + Button(action: { + withAnimation { + isEditingFilters = !isEditingFilters + } + }) { + Image(systemName: !isEditingFilters ? "line.3.horizontal.decrease.circle" : "line.3.horizontal.decrease.circle.fill") + .padding(.vertical, 5) + } + .tint(Color(UIColor.secondarySystemBackground)) + .foregroundColor(.accentColor) + .buttonStyle(.borderedProminent) + } + .controlSize(.regular) + .padding(5) + } + .padding(.bottom, 5) + .padding(.trailing, 5) + .searchable(text: $searchText, placement: .navigationBarDrawer, prompt: "Search") + .disabled(selection != nil) + .overlay { + if logs.isEmpty { + ContentUnavailableView("Loading Logs. . .", systemImage: "scroll") + } + } + .refreshable { + await logs = searchAppLogs() + logs.sort(using: sortOrder) } - .tint(Color(UIColor.secondarySystemBackground)) - .foregroundColor(.accentColor) - .buttonStyle(.borderedProminent) - } - .controlSize(.regular) - .padding(5) - } - .padding(.bottom, 5) - .padding(.trailing, 5) - .searchable(text: $searchText, placement: .navigationBarDrawer, prompt: "Search") - .disabled(selection != nil) - .overlay { - if logs.isEmpty { - ContentUnavailableView("No Logs Available", systemImage: "scroll") - } - } - .refreshable { - await logs = searchAppLogs() - logs.sort(using: sortOrder) } .onChange(of: sortOrder) { _, sortOrder in withAnimation { @@ -176,7 +216,7 @@ struct AppLog: View { } } -@available(iOS 17.4, macOS 14.4, *) +@available(iOS 17.0, macOS 14.0, *) extension AppLog { @MainActor private func searchAppLogs() async -> [OSLogEntryLog] { diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index d99d510b..f9f46cbc 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -103,10 +103,5 @@ struct AppSettings: View { ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } } } diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 816c6eb1..bec86958 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -280,11 +280,6 @@ struct Channels: View { ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } } } diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index 8b4033db..b43813c2 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -107,9 +107,6 @@ struct BluetoothConfig: View { } ) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setBluetoothValues() // Need to request a BluetoothConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node, node.bluetoothConfig == nil { diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 1a872269..114898ca 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -238,9 +238,6 @@ struct DeviceConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setDeviceValues() // Need to request a LoRaConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.deviceConfig == nil { diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index 867f616d..f800ebb6 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -159,9 +159,6 @@ struct DisplayConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setDisplayValues() // Need to request a LoRaConfig from the remote node before allowing changes diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index 210675b2..c98e162c 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -226,9 +226,6 @@ struct LoRaConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setLoRaValues() // Need to request a LoRaConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.loRaConfig == nil { diff --git a/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift b/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift index 3d3d3586..3fe5560d 100644 --- a/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift @@ -85,9 +85,6 @@ struct AmbientLightingConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setAmbientLightingConfigValue() // Need to request a Ambient Lighting Config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.ambientLightingConfig == nil { diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index 37e16f93..670e24e1 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -229,9 +229,6 @@ struct CannedMessagesConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setCannedMessagesValues() // Need to request a CannedMessagesModuleConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.cannedMessageConfig == nil { diff --git a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift index 2e8f715b..480c4e24 100644 --- a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift @@ -185,9 +185,6 @@ struct DetectionSensorConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setDetectionSensorValues() // Need to request a Detection Sensor Module Config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.detectionSensorConfig == nil { diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index a2adc9b6..abdca8a2 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -195,9 +195,6 @@ struct ExternalNotificationConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setExternalNotificationValues() // Need to request a TelemetryModuleConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.externalNotificationConfig == nil { diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index 7eb5a945..f923e560 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -357,9 +357,6 @@ struct MQTTConfig: View { } } .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setMqttValues() // Need to request a TelemetryModuleConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.mqttConfig == nil { diff --git a/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift b/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift index 90a0e195a..d7670a7c 100644 --- a/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift @@ -58,10 +58,6 @@ struct PaxCounterConfig: View { ) }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - setPaxValues() // Need to request a PAX Counter module config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.paxCounterConfig == nil { @@ -110,6 +106,6 @@ struct PaxCounterConfig: View { private func setPaxValues() { enabled = node?.paxCounterConfig?.enabled ?? enabled - paxcounterUpdateInterval = Int(node?.paxCounterConfig?.updateInterval ?? 900) + paxcounterUpdateInterval = Int(node?.paxCounterConfig?.updateInterval ?? 1800) } } diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index 2af3b5fd..0bf99d65 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -77,9 +77,6 @@ struct RangeTestConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setRangeTestValues() // Need to request a RangeTestModule Config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.rangeTestConfig == nil { diff --git a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift index 6df3e504..3128fffe 100644 --- a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift @@ -67,9 +67,6 @@ struct RtttlConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setRtttLConfigValue() // Need to request a Rtttl Config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && (node?.rtttlConfig == nil || node?.rtttlConfig?.ringtone?.count ?? 0 == 0) { diff --git a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift index c4f27c6d..813e1328 100644 --- a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift @@ -133,9 +133,6 @@ struct SerialConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setSerialValues() // Need to request a SerialModuleConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.serialConfig == nil { diff --git a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift index d5aba9cb..e73380f3 100644 --- a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift @@ -142,10 +142,6 @@ struct StoreForwardConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - // Need to request a Detection Sensor Module Config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.storeForwardConfig == nil { Logger.mesh.debug("empty store and forward module config") diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index 05b631b1..a1f827b7 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -42,7 +42,7 @@ struct TelemetryConfig: View { } .pickerStyle(DefaultPickerStyle()) .listRowSeparator(.hidden) - Text("How often device metrics are sent out over the mesh. Default is 15 minutes.") + Text("How often device metrics are sent out over the mesh. Default is 30 minutes.") .foregroundColor(.gray) .font(.callout) .listRowSeparator(.visible) @@ -55,7 +55,7 @@ struct TelemetryConfig: View { } .pickerStyle(DefaultPickerStyle()) .listRowSeparator(.hidden) - Text("How often sensor metrics are sent out over the mesh. Default is 15 minutes.") + Text("How often sensor metrics are sent out over the mesh. Default is 30 minutes.") .foregroundColor(.gray) .font(.callout) } @@ -91,7 +91,7 @@ struct TelemetryConfig: View { } .pickerStyle(DefaultPickerStyle()) .listRowSeparator(.hidden) - Text("How often power metrics are sent out over the mesh. Default is 15 minutes.") + Text("How often power metrics are sent out over the mesh. Default is 30 minutes.") .foregroundColor(.gray) .font(.callout) .listRowSeparator(.visible) @@ -130,9 +130,6 @@ struct TelemetryConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setTelemetryValues() // Need to request a TelemetryModuleConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.telemetryConfig == nil { @@ -186,13 +183,13 @@ struct TelemetryConfig: View { } } func setTelemetryValues() { - self.deviceUpdateInterval = Int(node?.telemetryConfig?.deviceUpdateInterval ?? 900) - self.environmentUpdateInterval = Int(node?.telemetryConfig?.environmentUpdateInterval ?? 900) + self.deviceUpdateInterval = Int(node?.telemetryConfig?.deviceUpdateInterval ?? 1800) + self.environmentUpdateInterval = Int(node?.telemetryConfig?.environmentUpdateInterval ?? 1800) self.environmentMeasurementEnabled = node?.telemetryConfig?.environmentMeasurementEnabled ?? false self.environmentScreenEnabled = node?.telemetryConfig?.environmentScreenEnabled ?? false self.environmentDisplayFahrenheit = node?.telemetryConfig?.environmentDisplayFahrenheit ?? false self.powerMeasurementEnabled = node?.telemetryConfig?.powerMeasurementEnabled ?? false - self.powerUpdateInterval = Int(node?.telemetryConfig?.powerUpdateInterval ?? 900) + self.powerUpdateInterval = Int(node?.telemetryConfig?.powerUpdateInterval ?? 1800) self.powerScreenEnabled = node?.telemetryConfig?.powerScreenEnabled ?? false self.hasChanges = false } diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index 0dedaeed..d969eab9 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -114,9 +114,6 @@ struct NetworkConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setNetworkValues() // Need to request a NetworkConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.networkConfig == nil { diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index a0d4ac97..aa6960a0 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -377,9 +377,6 @@ struct PositionConfig: View { } ) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setPositionValues() supportedVersion = bleManager.connectedVersion == "0.0.0" || self.minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedSame // Need to request a PositionConfig from the remote node before allowing changes diff --git a/Meshtastic/Views/Settings/Config/PowerConfig.swift b/Meshtastic/Views/Settings/Config/PowerConfig.swift index 15b7e44d..d8de7e44 100644 --- a/Meshtastic/Views/Settings/Config/PowerConfig.swift +++ b/Meshtastic/Views/Settings/Config/PowerConfig.swift @@ -118,10 +118,6 @@ struct PowerConfig: View { } } .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - Api().loadDeviceHardwareData { (hw) in for device in hw { diff --git a/Meshtastic/Views/Settings/Firmware.swift b/Meshtastic/Views/Settings/Firmware.swift index 87f2bb88..e591761f 100644 --- a/Meshtastic/Views/Settings/Firmware.swift +++ b/Meshtastic/Views/Settings/Firmware.swift @@ -13,7 +13,7 @@ struct Firmware: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager var node: NodeInfoEntity? - @State var minimumVersion = "2.3.15" + @State var minimumVersion = "2.4.0" @State var version = "" @State private var currentDevice: DeviceHardware? @State private var latestStable: FirmwareRelease? diff --git a/Meshtastic/Views/Settings/Logs/AppLogFilter.swift b/Meshtastic/Views/Settings/Logs/AppLogFilter.swift index 9943c2de..e6371672 100644 --- a/Meshtastic/Views/Settings/Logs/AppLogFilter.swift +++ b/Meshtastic/Views/Settings/Logs/AppLogFilter.swift @@ -97,6 +97,7 @@ enum LogLevels: Int, CaseIterable, Identifiable { struct AppLogFilter: View { @Environment(\.dismiss) private var dismiss + @State private var currentDetent = PresentationDetent.medium /// Filters var filterTitle = "App Log Filters" @Binding var categories: Set @@ -143,7 +144,9 @@ struct AppLogFilter: View { .padding(.bottom) #endif } - .presentationDetents([.fraction(1.0)]) + .presentationDetents([.medium, .large], selection: $currentDetent) + .presentationContentInteraction(.scrolls) .presentationDragIndicator(.visible) + .presentationBackgroundInteraction(.enabled(upThrough: .medium)) } } diff --git a/Meshtastic/Views/Settings/MeshLog.swift b/Meshtastic/Views/Settings/MeshLog.swift index da2b5014..e63c2f34 100644 --- a/Meshtastic/Views/Settings/MeshLog.swift +++ b/Meshtastic/Views/Settings/MeshLog.swift @@ -18,7 +18,7 @@ struct MeshLog: View { let url = logFile! logs.removeAll() var lineCount = 0 - let lineLimit = 1000 + let lineLimit = 10000 // Get the number of lines for try await _ in url.lines { lineCount += 1 diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 487a3585..faae073e 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -14,44 +14,22 @@ import TipKit struct Settings: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "favorite", ascending: false), - NSSortDescriptor(key: "user.longName", ascending: true)], animation: .default) + @FetchRequest( + sortDescriptors: [ + NSSortDescriptor(key: "favorite", ascending: false), + NSSortDescriptor(key: "user.longName", ascending: true) + ], + animation: .default + ) private var nodes: FetchedResults + @State private var selectedNode: Int = 0 @State private var preferredNodeNum: Int = 0 - @State private var selection: SettingsSidebar = .about - enum SettingsSidebar { - case appSettings - case routes - case routeRecorder - case shareChannels - case userConfig - case loraConfig - case channelConfig - case bluetoothConfig - case deviceConfig - case displayConfig - case networkConfig - case paxCounterConfig - case positionConfig - case powerConfig - case ambientLightingConfig - case cannedMessagesConfig - case detectionSensorConfig - case externalNotificationConfig - case mqttConfig - case rangeTestConfig - case ringtoneConfig - case serialConfig - case storeAndForwardConfig - case telemetryConfig - case meshLog - case adminMessageLog - case about - case appLog - case appData - } + @ObservedObject + var router: Router + + // MARK: Views var radioConfigurationSection: some View { Section("radio.configuration") { @@ -77,9 +55,7 @@ struct Settings: View { .foregroundColor(.gray) } - NavigationLink { - LoRaConfig(node: nodes.first(where: { $0.num == selectedNode })) - } label: { + NavigationLink(value: SettingsNavigationState.lora) { Label { Text("lora") } icon: { @@ -87,72 +63,271 @@ struct Settings: View { .rotationEffect(.degrees(-90)) } } - .tag(SettingsSidebar.loraConfig) - NavigationLink { - Channels(node: node) - } label: { + NavigationLink(value: SettingsNavigationState.channels) { Label { Text("channels") } icon: { Image(systemName: "fibrechannel") } } - .tag(SettingsSidebar.channelConfig) .disabled(selectedNode > 0 && selectedNode != preferredNodeNum) - NavigationLink { - ShareChannels(node: node) - } label: { + NavigationLink(value: SettingsNavigationState.shareQRCode) { Label { Text("share.channels") } icon: { Image(systemName: "qrcode") } } - .tag(SettingsSidebar.shareChannels) + .disabled(selectedNode > 0 && selectedNode != preferredNodeNum) + } + } + + var deviceConfigurationSection: some View { + Section("device.configuration") { + NavigationLink(value: SettingsNavigationState.user) { + Label { + Text("user") + } icon: { + Image(systemName: "person.crop.rectangle.fill") + } + } + + NavigationLink(value: SettingsNavigationState.bluetooth) { + Label { + Text("bluetooth") + } icon: { + Image(systemName: "antenna.radiowaves.left.and.right") + } + } + + NavigationLink(value: SettingsNavigationState.device) { + Label { + Text("device") + } icon: { + Image(systemName: "flipphone") + } + } + + NavigationLink(value: SettingsNavigationState.display) { + Label { + Text("display") + } icon: { + Image(systemName: "display") + } + } + + NavigationLink(value: SettingsNavigationState.network) { + Label { + Text("network") + } icon: { + Image(systemName: "network") + } + } + + NavigationLink(value: SettingsNavigationState.position) { + Label { + Text("position") + } icon: { + Image(systemName: "location") + } + } + + NavigationLink(value: SettingsNavigationState.power) { + Label { + Text("config.power.settings") + } icon: { + Image(systemName: "bolt.fill") + } + } + } + } + + var moduleConfigurationSection: some View { + Section("module.configuration") { + if #available(iOS 17.0, macOS 14.0, *) { + NavigationLink(value: SettingsNavigationState.ambientLighting) { + Label { + Text("ambient.lighting") + } icon: { + Image(systemName: "light.max") + } + } + } + + NavigationLink(value: SettingsNavigationState.cannedMessages) { + Label { + Text("canned.messages") + } icon: { + Image(systemName: "list.bullet.rectangle.fill") + } + } + + NavigationLink(value: SettingsNavigationState.detectionSensor) { + Label { + Text("detection.sensor") + } icon: { + Image(systemName: "sensor") + } + } + + NavigationLink(value: SettingsNavigationState.externalNotification) { + Label { + Text("external.notification") + } icon: { + Image(systemName: "megaphone") + } + } + + NavigationLink(value: SettingsNavigationState.mqtt) { + Label { + Text("mqtt") + } icon: { + Image(systemName: "dot.radiowaves.up.forward") + } + } + + NavigationLink(value: SettingsNavigationState.rangeTest) { + Label { + Text("range.test") + } icon: { + Image(systemName: "point.3.connected.trianglepath.dotted") + } + } + + if let node = nodes.first(where: { $0.num == preferredNodeNum }), + node.metadata?.hasWifi ?? false { + NavigationLink(value: SettingsNavigationState.paxCounter) { + Label { + Text("config.module.paxcounter.settings") + } icon: { + Image(systemName: "figure.walk.motion") + } + } + } + + NavigationLink(value: SettingsNavigationState.ringtone) { + Label { + Text("ringtone") + } icon: { + Image(systemName: "music.note.list") + } + } + + NavigationLink(value: SettingsNavigationState.serial) { + Label { + Text("serial") + } icon: { + Image(systemName: "terminal") + } + } + + NavigationLink(value: SettingsNavigationState.storeAndForward) { + Label { + Text("storeforward") + } icon: { + Image(systemName: "envelope.arrow.triangle.branch") + } + } + + NavigationLink(value: SettingsNavigationState.telemetry) { + Label { + Text("telemetry") + } icon: { + Image(systemName: "chart.xyaxis.line") + } + } + } + } + + var loggingSection: some View { + Section(header: Text("logging")) { + if #available (iOS 17.0, *) { + NavigationLink(value: SettingsNavigationState.debugLogs) { + Label { + Text("Logs") + } icon: { + Image(systemName: "scroll") + } + } + } + } + } + + var developersSection: some View { + Section(header: Text("Developers")) { + NavigationLink(value: SettingsNavigationState.meshLog) { + Label { + Text("mesh.log") + } icon: { + Image(systemName: "list.bullet.rectangle") + } + } + NavigationLink(value: SettingsNavigationState.appFiles) { + Label { + Text("App Files") + } icon: { + Image(systemName: "folder") + } + } + } + } + + var firmwareSection: some View { + Section(header: Text("Firmware")) { + NavigationLink(value: SettingsNavigationState.firmwareUpdates) { + Label { + Text("Firmware Updates") + } icon: { + Image(systemName: "arrow.up.arrow.down.square") + } + } .disabled(selectedNode > 0 && selectedNode != preferredNodeNum) } } var body: some View { - NavigationSplitView { + NavigationStack( + path: Binding<[SettingsNavigationState]>( + get: { + guard case .settings(let route) = router.navigationState, let setting = route else { + return [] + } + return [setting] + }, + set: { newPath in + router.navigationState = .settings(newPath.first) + } + ) + ) { let node = nodes.first(where: { $0.num == preferredNodeNum }) List { - NavigationLink { - AboutMeshtastic() - } label: { + NavigationLink(value: SettingsNavigationState.about) { Label { Text("about.meshtastic") } icon: { Image(systemName: "questionmark.app") } } - .tag(SettingsSidebar.about) - NavigationLink { - AppSettings() - } label: { + + NavigationLink(value: SettingsNavigationState.appSettings) { Label { Text("appsettings") } icon: { Image(systemName: "gearshape") } } - .tag(SettingsSidebar.appSettings) if #available(iOS 17.0, macOS 14.0, *) { - NavigationLink { - Routes() - } label: { + NavigationLink(value: SettingsNavigationState.routes) { Label { Text("routes") } icon: { Image(systemName: "road.lanes.curved.right") } } - .tag(SettingsSidebar.routes) - NavigationLink { - RouteRecorder() - } label: { + + NavigationLink(value: SettingsNavigationState.routeRecorder) { Label { Text("route.recorder") } icon: { @@ -160,10 +335,9 @@ struct Settings: View { .foregroundColor(.red) } } - .tag(SettingsSidebar.routeRecorder) } - let hasAdmin = node?.myInfo?.adminIndex ?? 0 > 0 ? true : false + let hasAdmin = node?.myInfo?.adminIndex ?? 0 > 0 if !(node?.deviceConfig?.isManaged ?? false) { if bleManager.connectedPeripheral != nil { @@ -225,246 +399,84 @@ struct Settings: View { } } radioConfigurationSection - Section("device.configuration") { - NavigationLink { - UserConfig(node: nodes.first(where: { $0.num == selectedNode })) - } label: { - Label { - Text("user") - } icon: { - Image(systemName: "person.crop.rectangle.fill") - } - } - .tag(SettingsSidebar.userConfig) - NavigationLink { - BluetoothConfig(node: nodes.first(where: { $0.num == selectedNode })) - } label: { - Label { - Text("bluetooth") - } icon: { - Image(systemName: "antenna.radiowaves.left.and.right") - } - } - .tag(SettingsSidebar.bluetoothConfig) - NavigationLink { - DeviceConfig(node: nodes.first(where: { $0.num == selectedNode })) - } label: { - Label { - Text("device") - } icon: { - Image(systemName: "flipphone") - } - } - .tag(SettingsSidebar.deviceConfig) - NavigationLink { - DisplayConfig(node: nodes.first(where: { $0.num == selectedNode })) - } label: { - Label { - Text("display") - } icon: { - Image(systemName: "display") - } - } - .tag(SettingsSidebar.displayConfig) - NavigationLink { - NetworkConfig(node: nodes.first(where: { $0.num == selectedNode })) - } label: { - Label { - Text("network") - } icon: { - Image(systemName: "network") - } - } - .tag(SettingsSidebar.networkConfig) - NavigationLink { - PositionConfig(node: nodes.first(where: { $0.num == selectedNode })) - } label: { - Label { - Text("position") - } icon: { - Image(systemName: "location") - } - } - .tag(SettingsSidebar.positionConfig) - - NavigationLink { - PowerConfig(node: nodes.first(where: { $0.num == selectedNode })) - } label: { - Label { - Text("config.power.settings") - } icon: { - Image(systemName: "bolt.fill") - } - } - .tag(SettingsSidebar.powerConfig) - } - Section("module.configuration") { - if #available(iOS 17.0, macOS 14.0, *) { - NavigationLink { - AmbientLightingConfig(node: nodes.first(where: { $0.num == selectedNode })) - } label: { - Label { - Text("ambient.lighting") - } icon: { - Image(systemName: "light.max") - } - } - .tag(SettingsSidebar.ambientLightingConfig) - } - NavigationLink { - CannedMessagesConfig(node: nodes.first(where: { $0.num == selectedNode })) - } label: { - Label { - Text("canned.messages") - } icon: { - Image(systemName: "list.bullet.rectangle.fill") - } - } - .tag(SettingsSidebar.cannedMessagesConfig) - NavigationLink { - DetectionSensorConfig(node: nodes.first(where: { $0.num == selectedNode })) - } label: { - Label { - Text("detection.sensor") - } icon: { - Image(systemName: "sensor") - } - } - .tag(SettingsSidebar.detectionSensorConfig) - NavigationLink { - ExternalNotificationConfig(node: nodes.first(where: { $0.num == selectedNode })) - } label: { - Label { - Text("external.notification") - } icon: { - Image(systemName: "megaphone") - } - } - .tag(SettingsSidebar.externalNotificationConfig) - NavigationLink { - MQTTConfig(node: nodes.first(where: { $0.num == selectedNode })) - } label: { - Label { - Text("mqtt") - } icon: { - Image(systemName: "dot.radiowaves.up.forward") - } - } - .tag(SettingsSidebar.mqttConfig) - NavigationLink { - RangeTestConfig(node: nodes.first(where: { $0.num == selectedNode })) - } label: { - Label { - Text("range.test") - } icon: { - Image(systemName: "point.3.connected.trianglepath.dotted") - } - } - .tag(SettingsSidebar.rangeTestConfig) - if node?.metadata?.hasWifi ?? false { - NavigationLink { - PaxCounterConfig(node: nodes.first(where: { $0.num == selectedNode })) - } label: { - Label { - Text("config.module.paxcounter.settings") - } icon: { - Image(systemName: "figure.walk.motion") - } - } - .tag(SettingsSidebar.paxCounterConfig) - } - NavigationLink { - RtttlConfig(node: nodes.first(where: { $0.num == selectedNode })) - } label: { - Label { - Text("ringtone") - } icon: { - Image(systemName: "music.note.list") - } - } - .tag(SettingsSidebar.ringtoneConfig) - NavigationLink { - SerialConfig(node: nodes.first(where: { $0.num == selectedNode })) - } label: { - Label { - Text("serial") - } icon: { - Image(systemName: "terminal") - } - } - .tag(SettingsSidebar.serialConfig) - NavigationLink { - StoreForwardConfig(node: nodes.first(where: { $0.num == selectedNode })) - } label: { - Label { - Text("storeforward") - } icon: { - Image(systemName: "envelope.arrow.triangle.branch") - } - } - .tag(SettingsSidebar.storeAndForwardConfig) - NavigationLink { - TelemetryConfig(node: nodes.first(where: { $0.num == selectedNode })) - } label: { - Label { - Text("telemetry") - } icon: { - Image(systemName: "chart.xyaxis.line") - } - } - .tag(SettingsSidebar.telemetryConfig) - } - Section(header: Text("logging")) { - NavigationLink { - MeshLog() - } label: { - Label { - Text("mesh.log") - } icon: { - Image(systemName: "list.bullet.rectangle") - } - } - .tag(SettingsSidebar.meshLog) - if #available (iOS 17.4, *) { - NavigationLink { - AppLog() - } label: { - Label { - Text("Debug Logs") - } icon: { - Image(systemName: "stethoscope") - } - } - .tag(SettingsSidebar.appLog) - } - } + deviceConfigurationSection + moduleConfigurationSection + loggingSection #if DEBUG - Section(header: Text("Developers")) { - NavigationLink { - AppData() - } label: { - Label { - Text("App Files") - } icon: { - Image(systemName: "folder") - } - } - .tag(SettingsSidebar.appData) - } + developersSection #endif - Section(header: Text("Firmware")) { - NavigationLink { - Firmware(node: nodes.first(where: { $0.num == preferredNodeNum })) - } label: { - Label { - Text("Firmware Updates") - } icon: { - Image(systemName: "arrow.up.arrow.down.square") - } - } - .tag(SettingsSidebar.about) - .disabled(selectedNode > 0 && selectedNode != preferredNodeNum) + firmwareSection + } + } + .navigationDestination(for: SettingsNavigationState.self) { destination in + let node = nodes.first(where: { $0.num == preferredNodeNum }) + switch destination { + case .about: + AboutMeshtastic() + case .appSettings: + AppSettings() + case .routes: + if #available(iOS 17.0, *) { + Routes() } + case .routeRecorder: + if #available(iOS 17.0, *) { + RouteRecorder() + } + case .lora: + LoRaConfig(node: nodes.first(where: { $0.num == selectedNode })) + case .channels: + Channels(node: node) + case .shareQRCode: + ShareChannels(node: node) + case .user: + UserConfig(node: nodes.first(where: { $0.num == selectedNode })) + case .bluetooth: + BluetoothConfig(node: nodes.first(where: { $0.num == selectedNode })) + case .device: + DeviceConfig(node: nodes.first(where: { $0.num == selectedNode })) + case .display: + DisplayConfig(node: nodes.first(where: { $0.num == selectedNode })) + case .network: + NetworkConfig(node: nodes.first(where: { $0.num == selectedNode })) + case .position: + PositionConfig(node: nodes.first(where: { $0.num == selectedNode })) + case .power: + PowerConfig(node: nodes.first(where: { $0.num == selectedNode })) + case .ambientLighting: + if #available(iOS 17.0, macOS 14.0, *) { + AmbientLightingConfig(node: node) + } + case .cannedMessages: + CannedMessagesConfig(node: nodes.first(where: { $0.num == selectedNode })) + case .detectionSensor: + DetectionSensorConfig(node: nodes.first(where: { $0.num == selectedNode })) + case .externalNotification: + ExternalNotificationConfig(node: nodes.first(where: { $0.num == selectedNode })) + case .mqtt: + MQTTConfig(node: nodes.first(where: { $0.num == selectedNode })) + case .rangeTest: + RangeTestConfig(node: nodes.first(where: { $0.num == selectedNode })) + case .paxCounter: + PaxCounterConfig(node: nodes.first(where: { $0.num == selectedNode })) + case .ringtone: + RtttlConfig(node: nodes.first(where: { $0.num == selectedNode })) + case .serial: + SerialConfig(node: nodes.first(where: { $0.num == selectedNode })) + case .storeAndForward: + StoreForwardConfig(node: nodes.first(where: { $0.num == selectedNode })) + case .telemetry: + TelemetryConfig(node: nodes.first(where: { $0.num == selectedNode })) + case .meshLog: + MeshLog() + case .debugLogs: + if #available(iOS 17.0, macOS 14.0, *) { + AppLog() + } + case .appFiles: + AppData() + case .firmwareUpdates: + Firmware(node: node) } } .onChange(of: UserDefaults.preferredPeripheralNum ) { newConnectedNode in @@ -489,18 +501,10 @@ struct Settings: View { } } } - .listStyle(GroupedListStyle()) .navigationTitle("settings") - .navigationBarItems(leading: - MeshtasticLogo() + .navigationBarItems( + leading: MeshtasticLogo() ) } - detail: { - if #available (iOS 17, *) { - ContentUnavailableView("select.menu.item", systemImage: "gear") - } else { - Text("select.menu.item") - } - } } } diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index 3859693e..136b9563 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -238,7 +238,6 @@ struct ShareChannels: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - bleManager.context = context generateChannelSet() } .onChange(of: includeChannel0) { _ in generateChannelSet() } diff --git a/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift index 57e8bde4..6a2906fe 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift @@ -321,6 +321,14 @@ public enum HardwareModel: SwiftProtobuf.Enum { /// specifically adapted for the Meshtatic project case heltecMeshNodeT114 // = 69 + /// + /// Sensecap Indicator from Seeed Studio. ESP32-S3 device with TFT and RP2040 coprocessor + case sensecapIndicator // = 70 + + /// + /// Seeed studio T1000-E tracker card. NRF52840 w/ LR1110 radio, GPS, button, buzzer, and sensors. + case trackerT1000E // = 71 + /// /// ------------------------------------------------------------------------------------------------------------------------------------------ /// Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. @@ -403,6 +411,8 @@ public enum HardwareModel: SwiftProtobuf.Enum { case 67: self = .heltecVisionMasterE213 case 68: self = .heltecVisionMasterE290 case 69: self = .heltecMeshNodeT114 + case 70: self = .sensecapIndicator + case 71: self = .trackerT1000E case 255: self = .privateHw default: self = .UNRECOGNIZED(rawValue) } @@ -479,6 +489,8 @@ public enum HardwareModel: SwiftProtobuf.Enum { case .heltecVisionMasterE213: return 67 case .heltecVisionMasterE290: return 68 case .heltecMeshNodeT114: return 69 + case .sensecapIndicator: return 70 + case .trackerT1000E: return 71 case .privateHw: return 255 case .UNRECOGNIZED(let i): return i } @@ -560,6 +572,8 @@ extension HardwareModel: CaseIterable { .heltecVisionMasterE213, .heltecVisionMasterE290, .heltecMeshNodeT114, + .sensecapIndicator, + .trackerT1000E, .privateHw, ] } @@ -3027,6 +3041,8 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding { 67: .same(proto: "HELTEC_VISION_MASTER_E213"), 68: .same(proto: "HELTEC_VISION_MASTER_E290"), 69: .same(proto: "HELTEC_MESH_NODE_T114"), + 70: .same(proto: "SENSECAP_INDICATOR"), + 71: .same(proto: "TRACKER_T1000_E"), 255: .same(proto: "PRIVATE_HW"), ] } diff --git a/MeshtasticTests/RouterTests.swift b/MeshtasticTests/RouterTests.swift new file mode 100644 index 00000000..81b07276 --- /dev/null +++ b/MeshtasticTests/RouterTests.swift @@ -0,0 +1,57 @@ +import Foundation +import XCTest + +@testable import Meshtastic + +final class RouterTests: XCTestCase { + + func testInitialState() throws { + XCTAssertEqual(Router().navigationState, .bluetooth) + } + + func testRouteTo() throws { + let router = Router(navigationState: .bluetooth) + router.route(to: .settings(.about)) + XCTAssertEqual(router.navigationState, .settings(.about)) + } + + func testRouteURL() throws { + // Messages + try assertRoute("meshtastic:///messages", .messages()) + try assertRoute( + "meshtastic:///messages?channelId=0&messageId=1122334455", + .messages(.channels(channelId: 0, messageId: 1122334455)) + ) + try assertRoute( + "meshtastic:///messages?userNum=123456789&messageId=9876543210", + .messages(.directMessages(userNum: 123456789, messageId: 9876543210)) + ) + + // Bluetooth + try assertRoute("meshtastic:///bluetooth", .bluetooth) + + // Nodes + try assertRoute("meshtastic:///nodes", .nodes()) + try assertRoute("meshtastic:///nodes?nodenum=1234567890", .nodes(selectedNodeNum: 1234567890)) + + // Map + try assertRoute("meshtastic:///map", .map()) + try assertRoute("meshtastic:///map?waypointId=123456", .map(.waypoint(123456))) + try assertRoute("meshtastic:///map?nodenum=1234567890", .map(.selectedNode(1234567890))) + + // Settings + try assertRoute("meshtastic:///settings", .settings()) + try assertRoute("meshtastic:///settings/about", .settings(.about)) + try assertRoute("meshtastic:///settings/invalidSetting", .settings()) + } + + private func assertRoute( + router: Router = Router(), + _ urlString: String, + _ destination: NavigationState + ) throws { + let url = try XCTUnwrap(URL(string: urlString)) + router.route(url: url) + XCTAssertEqual(router.navigationState, destination) + } +} diff --git a/README.md b/README.md index 1b8bed5a..de9af2a2 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,5 @@ # Meshtastic Apple Clients - - Meshtastic App Store Launch Image - - ## Overview SwiftUI client applications for iOS, iPadOS and macOS. @@ -13,12 +9,14 @@ SwiftUI client applications for iOS, iPadOS and macOS. This project is currently using **Xcode 15.4**. 1. Clone the repo. +2. Set up git hooks to automatically lint the project when you commit changes. 2. Open `Meshtastic.xcworkspace` 2. Build and run the `Meshtastic` target. ```sh git clone git@github.com:meshtastic/Meshtastic-Apple.git cd Meshtastic-Apple +./scripts/setup-hooks.sh open Meshtastic.xcworkspace ``` @@ -26,8 +24,10 @@ open Meshtastic.xcworkspace ### Supported Operating Systems -* iOS 16+ -* iPadOS 16+ +The last two operating system versions are supported. Currently that is 16 and 17. + +* iOS 16.6+ +* iPadOS 16.6+ * macOS 13+ ### Code Standards @@ -37,11 +37,16 @@ open Meshtastic.xcworkspace - Use Core Data for persistence ## Updating Protobufs: -- run: - ```bash - ./scripts/gen_protos.sh - ``` -- build, test, commit changes + +1. run +```bash +./scripts/gen_protos.sh +``` +2. Build, test, and commit the changes. + +## Release Process + +For more information on how a new release of Meshtastic is managed, please refer to [RELEASING.md](./RELEASING.md) ## License diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 00000000..087c3a3d --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,45 @@ +# Releasing Meshtastic + +This document outlines the process for preparing and making a release for Meshtastic. + +## Table of Contents + +1. [Branching Strategy](#branching-strategy) +2. [Preparing for a Release](#preparing-for-a-release) +3. [Creating a Release Branch](#creating-a-release-branch) +4. [Finalizing the Release](#finalizing-the-release) + +## Branching Strategy + +- **Main Branch (`main`)**: This is the main development branch where daily development occurs. +- **Release Branch (`X.YY.ZZ-release`)**: This branch is created from `main` for preparing a specific release version. + +## Preparing for a Release + +1. Ensure all desired features and fixes are merged into the `main` branch. +2. Update the version number in the relevant files. +3. Update the project documentation to reflect the upcoming release. + +## Creating a Release Branch + +1. Create a release branch from `main`. + ```sh + ./scripts/create-release-branch.sh + ``` + +## Finalizing the Release + +1. Perform final testing and quality checks on the `X.YY.ZZ-release` branch. + a. If any hotfix changes are required, merge those changes into `X.YY.ZZ-release`. + b. After merging these changes into the release branch, cherry-pick the changes onto `main`. +2. Once everything is ready, create a final tag for the release: + ```sh + git tag -a X.YY.ZZ -m "Release version X.Y.Z" + git push origin X.YY.ZZ + ``` + +Thank you for following the release process and helping to ensure the stability and quality of Meshtastic! + +--- + +Feel free to modify this template to better fit your project's specific needs. \ No newline at end of file diff --git a/Widgets/WidgetsLiveActivity.swift b/Widgets/WidgetsLiveActivity.swift index 80d1fb50..396aaac9 100644 --- a/Widgets/WidgetsLiveActivity.swift +++ b/Widgets/WidgetsLiveActivity.swift @@ -14,7 +14,7 @@ struct WidgetsLiveActivity: Widget { ActivityConfiguration(for: MeshActivityAttributes.self) { context in LiveActivityView(nodeName: context.attributes.name, channelUtilization: context.state.channelUtilization, airtime: context.state.airtime, batteryLevel: context.state.batteryLevel, nodes: 17, nodesOnline: 7, timerRange: context.state.timerRange) - .widgetURL(URL(string: "meshtastic://node/\(context.attributes.name)")) + .widgetURL(URL(string: "meshtastic:///node/\(context.attributes.name)")) } dynamicIsland: { context in DynamicIsland { @@ -95,7 +95,7 @@ struct WidgetsLiveActivity: Widget { .contentMargins(.trailing, 32, for: .expanded) .contentMargins([.leading, .top, .bottom], 6, for: .compactLeading) .contentMargins(.all, 6, for: .minimal) - .widgetURL(URL(string: "meshtastic://node/\(context.attributes.name)")) + .widgetURL(URL(string: "meshtastic:///node/\(context.attributes.name)")) } } } diff --git a/scripts/create-release-branch.sh b/scripts/create-release-branch.sh new file mode 100644 index 00000000..807ef077 --- /dev/null +++ b/scripts/create-release-branch.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# Check if the release version number is provided +if [ -z "$1" ]; then + echo "Usage: $0 " + exit 1 +fi + +# Set the release version number +RELEASE_VERSION=$1 + +# Check if the release branch already exists on the remote repository +if git ls-remote --exit-code --heads origin $RELEASE_BRANCH; then + echo "The branch $RELEASE_BRANCH already exists on the remote repository." + exit 1 +fi + +# Prompt the user for confirmation +echo "You are about to create and push the release branch ${RELEASE_VERSION}-release." +read -p "Are you sure you want to proceed? (Y/n): " confirmation + +# Check the user's response +if [[ ! "$confirmation" =~ ^[Yy]$ ]]; then + echo "Operation cancelled." + exit 0 +fi + +# Check out the main branch and pull the latest changes +git checkout main +git pull origin main + +# Create a new branch for the release +RELEASE_BRANCH="${RELEASE_VERSION}-release" +git checkout -b $RELEASE_BRANCH + +# Push the new release branch to the remote repository +git push origin $RELEASE_BRANCH + +echo "Release branch $RELEASE_BRANCH created and pushed successfully." diff --git a/scripts/hooks/pre-commit b/scripts/hooks/pre-commit new file mode 100644 index 00000000..ac55cf77 --- /dev/null +++ b/scripts/hooks/pre-commit @@ -0,0 +1,3 @@ +#!/bin/bash + +./scripts/lint/lint-fix-changes.sh \ No newline at end of file diff --git a/scripts/lint/lint-fix-changes.sh b/scripts/lint/lint-fix-changes.sh new file mode 100755 index 00000000..e4dae8d0 --- /dev/null +++ b/scripts/lint/lint-fix-changes.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +# Path to swiftlint +SWIFT_LINT=$(which swiftlint) + +# Check if SwiftLint is installed +if [[ -e "${SWIFT_LINT}" ]]; then + count=0 + for file_path in $(git ls-files -m --exclude-from=.gitignore | grep ".swift$"); do + export SCRIPT_INPUT_FILE_$count=$file_path + count=$((count + 1)) + done + + ##### Check for modified files in unstaged/Staged area ##### + for file_path in $(git diff --name-only --cached | grep ".swift$"); do + export SCRIPT_INPUT_FILE_$count=$file_path + count=$((count + 1)) + done + + ##### Make the count available as global variable ##### + export SCRIPT_INPUT_FILE_COUNT=$count + + ##### Fix files or exit if no files found for fixing ##### + if [ "$count" -ne 0 ]; then + echo "Found files to fix! Running swiftLint --fix..." + + # Run SwiftLint --fix on each file + for ((i = 0; i < count; i++)); do + file_var="SCRIPT_INPUT_FILE_$i" + file_path=${!file_var} + echo "Fixing $file_path" + $SWIFT_LINT --fix --path "$file_path" + done + + # Add the fixed files back to staging + for ((i = 0; i < count; i++)); do + file_var="SCRIPT_INPUT_FILE_$i" + file_path=${!file_var} + git add "$file_path" + done + + echo "swiftLint --fix completed and files re-staged." + + # Optionally lint the fixed files + echo "Linting fixed files..." + $SWIFT_LINT lint --use-script-input-files + else + exit 0 + fi + + RESULT=$? + + if [ $RESULT -eq 0 ]; then + exit 0 + else + echo "" + echo "⛔️ Violation found of the type ERROR! Please fix these issues before continuing!" + fi + exit $RESULT + +else + echo "SwiftLint not installed. Please install from https://github.com/realm/SwiftLint" + exit -1 +fi \ No newline at end of file diff --git a/scripts/setup-hooks.sh b/scripts/setup-hooks.sh new file mode 100755 index 00000000..3d50cf41 --- /dev/null +++ b/scripts/setup-hooks.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -e + +# Define the source and destination paths +SOURCE_PATH="./scripts/hooks/pre-commit" +HOOKS_DIR=".git/hooks" +DEST_PATH="$HOOKS_DIR/pre-commit" + +# Check if the hooks directory exists +if [ ! -d "$HOOKS_DIR" ]; then + echo "Error: .git/hooks directory not found. Make sure you're in the root of a Git repository." + exit 1 +fi + +# Copy the script to the hooks directory +cp "$SOURCE_PATH" "$DEST_PATH" + +# Make the hook script executable +chmod +x "$DEST_PATH" + +echo "Pre-commit hooks have been set up successfully."