diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index d7f1f687..7bd5cdf7 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -286,6 +286,7 @@ DD23D9AB2B7133F6003F5CBE /* MeshtasticDataModelV 25.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 25.xcdatamodel"; sourceTree = ""; }; DD2553562855B02500E55709 /* LoRaConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaConfig.swift; sourceTree = ""; }; DD2553582855B52700E55709 /* PositionConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionConfig.swift; sourceTree = ""; }; + DD268D8C2BCC7D11008073AE /* MeshtasticDataModelV 35.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 35.xcdatamodel"; sourceTree = ""; }; DD295CE92B323ED9002CC4AC /* MeshtasticDataModelV22.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV22.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 = ""; }; @@ -1815,6 +1816,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DD268D8C2BCC7D11008073AE /* MeshtasticDataModelV 35.xcdatamodel */, DDDBC87C2BC65682001E8DF7 /* MeshtasticDataModelV 34.xcdatamodel */, DDF45C382BC46B16005ED5F2 /* MeshtasticDataModelV33.xcdatamodel */, DD9681A22BBB22BE00FD2C47 /* MeshtasticDataModelV32.xcdatamodel */, @@ -1850,7 +1852,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DDDBC87C2BC65682001E8DF7 /* MeshtasticDataModelV 34.xcdatamodel */; + currentVersion = DD268D8C2BCC7D11008073AE /* MeshtasticDataModelV 35.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Extensions/Date.swift b/Meshtastic/Extensions/Date.swift index 224f0b03..2cb46f53 100644 --- a/Meshtastic/Extensions/Date.swift +++ b/Meshtastic/Extensions/Date.swift @@ -8,7 +8,7 @@ import Foundation extension Date { - + func formattedDate(format: String) -> String { let dateformat = DateFormatter() dateformat.dateFormat = format @@ -18,4 +18,15 @@ extension Date { return "unknown.age".localized } } + func relativeTimeOfDay() -> String { + let hour = Calendar.current.component(.hour, from: self) + + switch hour { + case 6..<12 : return "relativetimeofday.morning".localized + case 12 : return "relativetimeofday.midday".localized + case 13..<17 : return "relativetimeofday.afternoon".localized + case 17..<22 : return "relativetimeofday.evening".localized + default: return "relativetimeofday.nighttime".localized + } + } } diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 554e9b2e..f837dfda 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV 34.xcdatamodel + MeshtasticDataModelV 35.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 34.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 34.xcdatamodel/contents index 296e4582..98ab4f5e 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 34.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 34.xcdatamodel/contents @@ -326,7 +326,7 @@ - + diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 35.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 35.xcdatamodel/contents new file mode 100644 index 00000000..296e4582 --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 35.xcdatamodel/contents @@ -0,0 +1,455 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Protobufs/meshtastic/admin.pb.swift b/Meshtastic/Protobufs/meshtastic/admin.pb.swift index 1f2b193e..230f5304 100644 --- a/Meshtastic/Protobufs/meshtastic/admin.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/admin.pb.swift @@ -924,7 +924,7 @@ struct AdminMessage { extension AdminMessage.ConfigType: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [AdminMessage.ConfigType] = [ + static let allCases: [AdminMessage.ConfigType] = [ .deviceConfig, .positionConfig, .powerConfig, @@ -937,7 +937,7 @@ extension AdminMessage.ConfigType: CaseIterable { extension AdminMessage.ModuleConfigType: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [AdminMessage.ModuleConfigType] = [ + static let allCases: [AdminMessage.ModuleConfigType] = [ .mqttConfig, .serialConfig, .extnotifConfig, diff --git a/Meshtastic/Protobufs/meshtastic/apponly.pb.swift b/Meshtastic/Protobufs/meshtastic/apponly.pb.swift index ffce4849..11abf7af 100644 --- a/Meshtastic/Protobufs/meshtastic/apponly.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/apponly.pb.swift @@ -75,7 +75,15 @@ extension ChannelSet: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio var _settings: [ChannelSettings] = [] var _loraConfig: Config.LoRaConfig? = nil - static let defaultInstance = _StorageClass() + #if swift(>=5.10) + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + #else + static let defaultInstance = _StorageClass() + #endif private init() {} diff --git a/Meshtastic/Protobufs/meshtastic/atak.pb.swift b/Meshtastic/Protobufs/meshtastic/atak.pb.swift index f1bc14ad..77c35e7c 100644 --- a/Meshtastic/Protobufs/meshtastic/atak.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/atak.pb.swift @@ -136,7 +136,7 @@ enum Team: SwiftProtobuf.Enum { extension Team: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [Team] = [ + static let allCases: [Team] = [ .unspecifedColor, .white, .yellow, @@ -239,7 +239,7 @@ enum MemberRole: SwiftProtobuf.Enum { extension MemberRole: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [MemberRole] = [ + static let allCases: [MemberRole] = [ .unspecifed, .teamMember, .teamLead, diff --git a/Meshtastic/Protobufs/meshtastic/channel.pb.swift b/Meshtastic/Protobufs/meshtastic/channel.pb.swift index 493230ba..b2c55540 100644 --- a/Meshtastic/Protobufs/meshtastic/channel.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/channel.pb.swift @@ -215,7 +215,7 @@ struct Channel { extension Channel.Role: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [Channel.Role] = [ + static let allCases: [Channel.Role] = [ .disabled, .primary, .secondary, diff --git a/Meshtastic/Protobufs/meshtastic/config.pb.swift b/Meshtastic/Protobufs/meshtastic/config.pb.swift index c0e81a83..db2b7f04 100644 --- a/Meshtastic/Protobufs/meshtastic/config.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/config.pb.swift @@ -1387,7 +1387,7 @@ struct Config { extension Config.DeviceConfig.Role: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [Config.DeviceConfig.Role] = [ + static let allCases: [Config.DeviceConfig.Role] = [ .client, .clientMute, .router, @@ -1404,7 +1404,7 @@ extension Config.DeviceConfig.Role: CaseIterable { extension Config.DeviceConfig.RebroadcastMode: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [Config.DeviceConfig.RebroadcastMode] = [ + static let allCases: [Config.DeviceConfig.RebroadcastMode] = [ .all, .allSkipDecoding, .localOnly, @@ -1414,7 +1414,7 @@ extension Config.DeviceConfig.RebroadcastMode: CaseIterable { extension Config.PositionConfig.PositionFlags: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [Config.PositionConfig.PositionFlags] = [ + static let allCases: [Config.PositionConfig.PositionFlags] = [ .unset, .altitude, .altitudeMsl, @@ -1431,7 +1431,7 @@ extension Config.PositionConfig.PositionFlags: CaseIterable { extension Config.PositionConfig.GpsMode: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [Config.PositionConfig.GpsMode] = [ + static let allCases: [Config.PositionConfig.GpsMode] = [ .disabled, .enabled, .notPresent, @@ -1440,7 +1440,7 @@ extension Config.PositionConfig.GpsMode: CaseIterable { extension Config.NetworkConfig.AddressMode: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [Config.NetworkConfig.AddressMode] = [ + static let allCases: [Config.NetworkConfig.AddressMode] = [ .dhcp, .static, ] @@ -1448,7 +1448,7 @@ extension Config.NetworkConfig.AddressMode: CaseIterable { extension Config.DisplayConfig.GpsCoordinateFormat: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [Config.DisplayConfig.GpsCoordinateFormat] = [ + static let allCases: [Config.DisplayConfig.GpsCoordinateFormat] = [ .dec, .dms, .utm, @@ -1460,7 +1460,7 @@ extension Config.DisplayConfig.GpsCoordinateFormat: CaseIterable { extension Config.DisplayConfig.DisplayUnits: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [Config.DisplayConfig.DisplayUnits] = [ + static let allCases: [Config.DisplayConfig.DisplayUnits] = [ .metric, .imperial, ] @@ -1468,7 +1468,7 @@ extension Config.DisplayConfig.DisplayUnits: CaseIterable { extension Config.DisplayConfig.OledType: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [Config.DisplayConfig.OledType] = [ + static let allCases: [Config.DisplayConfig.OledType] = [ .oledAuto, .oledSsd1306, .oledSh1106, @@ -1478,7 +1478,7 @@ extension Config.DisplayConfig.OledType: CaseIterable { extension Config.DisplayConfig.DisplayMode: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [Config.DisplayConfig.DisplayMode] = [ + static let allCases: [Config.DisplayConfig.DisplayMode] = [ .default, .twocolor, .inverted, @@ -1488,7 +1488,7 @@ extension Config.DisplayConfig.DisplayMode: CaseIterable { extension Config.LoRaConfig.RegionCode: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [Config.LoRaConfig.RegionCode] = [ + static let allCases: [Config.LoRaConfig.RegionCode] = [ .unset, .us, .eu433, @@ -1513,7 +1513,7 @@ extension Config.LoRaConfig.RegionCode: CaseIterable { extension Config.LoRaConfig.ModemPreset: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [Config.LoRaConfig.ModemPreset] = [ + static let allCases: [Config.LoRaConfig.ModemPreset] = [ .longFast, .longSlow, .veryLongSlow, @@ -1527,7 +1527,7 @@ extension Config.LoRaConfig.ModemPreset: CaseIterable { extension Config.BluetoothConfig.PairingMode: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [Config.BluetoothConfig.PairingMode] = [ + static let allCases: [Config.BluetoothConfig.PairingMode] = [ .randomPin, .fixedPin, .noPin, diff --git a/Meshtastic/Protobufs/meshtastic/deviceonly.pb.swift b/Meshtastic/Protobufs/meshtastic/deviceonly.pb.swift index 433bffd3..ebd5e3f7 100644 --- a/Meshtastic/Protobufs/meshtastic/deviceonly.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/deviceonly.pb.swift @@ -66,7 +66,7 @@ enum ScreenFonts: SwiftProtobuf.Enum { extension ScreenFonts: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [ScreenFonts] = [ + static let allCases: [ScreenFonts] = [ .fontSmall, .fontMedium, .fontLarge, @@ -509,7 +509,15 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat var _hopsAway: UInt32 = 0 var _isFavorite: Bool = false - static let defaultInstance = _StorageClass() + #if swift(>=5.10) + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + #else + static let defaultInstance = _StorageClass() + #endif private init() {} @@ -649,7 +657,15 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati var _nodeRemoteHardwarePins: [NodeRemoteHardwarePin] = [] var _nodeDbLite: [NodeInfoLite] = [] - static let defaultInstance = _StorageClass() + #if swift(>=5.10) + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + #else + static let defaultInstance = _StorageClass() + #endif private init() {} diff --git a/Meshtastic/Protobufs/meshtastic/localonly.pb.swift b/Meshtastic/Protobufs/meshtastic/localonly.pb.swift index 6e215220..0778e962 100644 --- a/Meshtastic/Protobufs/meshtastic/localonly.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/localonly.pb.swift @@ -314,7 +314,15 @@ extension LocalConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati var _bluetooth: Config.BluetoothConfig? = nil var _version: UInt32 = 0 - static let defaultInstance = _StorageClass() + #if swift(>=5.10) + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + #else + static let defaultInstance = _StorageClass() + #endif private init() {} @@ -450,7 +458,15 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem var _paxcounter: ModuleConfig.PaxcounterConfig? = nil var _version: UInt32 = 0 - static let defaultInstance = _StorageClass() + #if swift(>=5.10) + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + #else + static let defaultInstance = _StorageClass() + #endif private init() {} diff --git a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift index b868d7d7..c9c26377 100644 --- a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift @@ -408,7 +408,7 @@ enum HardwareModel: SwiftProtobuf.Enum { extension HardwareModel: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [HardwareModel] = [ + static let allCases: [HardwareModel] = [ .unset, .tloraV2, .tloraV1, @@ -514,7 +514,7 @@ enum Constants: SwiftProtobuf.Enum { extension Constants: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [Constants] = [ + static let allCases: [Constants] = [ .zero, .dataPayloadLen, ] @@ -627,7 +627,7 @@ enum CriticalErrorCode: SwiftProtobuf.Enum { extension CriticalErrorCode: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [CriticalErrorCode] = [ + static let allCases: [CriticalErrorCode] = [ .none, .txWatchdog, .sleepEnterWait, @@ -946,7 +946,7 @@ struct Position { extension Position.LocSource: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [Position.LocSource] = [ + static let allCases: [Position.LocSource] = [ .locUnset, .locManual, .locInternal, @@ -956,7 +956,7 @@ extension Position.LocSource: CaseIterable { extension Position.AltSource: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [Position.AltSource] = [ + static let allCases: [Position.AltSource] = [ .altUnset, .altManual, .altInternal, @@ -1237,7 +1237,7 @@ struct Routing { extension Routing.Error: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [Routing.Error] = [ + static let allCases: [Routing.Error] = [ .none, .noRoute, .gotNak, @@ -1755,7 +1755,7 @@ struct MeshPacket { extension MeshPacket.Priority: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [MeshPacket.Priority] = [ + static let allCases: [MeshPacket.Priority] = [ .unset, .min, .background, @@ -1768,7 +1768,7 @@ extension MeshPacket.Priority: CaseIterable { extension MeshPacket.Delayed: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [MeshPacket.Delayed] = [ + static let allCases: [MeshPacket.Delayed] = [ .noDelay, .broadcast, .direct, @@ -2022,7 +2022,7 @@ struct LogRecord { extension LogRecord.Level: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [LogRecord.Level] = [ + static let allCases: [LogRecord.Level] = [ .unset, .critical, .error, @@ -2843,7 +2843,15 @@ extension Position: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB var _seqNumber: UInt32 = 0 var _precisionBits: UInt32 = 0 - static let defaultInstance = _StorageClass() + #if swift(>=5.10) + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + #else + static let defaultInstance = _StorageClass() + #endif private init() {} @@ -3505,7 +3513,15 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio var _viaMqtt: Bool = false var _hopStart: UInt32 = 0 - static let defaultInstance = _StorageClass() + #if swift(>=5.10) + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + #else + static let defaultInstance = _StorageClass() + #endif private init() {} @@ -3717,7 +3733,15 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB var _hopsAway: UInt32 = 0 var _isFavorite: Bool = false - static let defaultInstance = _StorageClass() + #if swift(>=5.10) + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + #else + static let defaultInstance = _StorageClass() + #endif private init() {} @@ -4009,7 +4033,15 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation var _id: UInt32 = 0 var _payloadVariant: FromRadio.OneOf_PayloadVariant? - static let defaultInstance = _StorageClass() + #if swift(>=5.10) + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + #else + static let defaultInstance = _StorageClass() + #endif private init() {} diff --git a/Meshtastic/Protobufs/meshtastic/module_config.pb.swift b/Meshtastic/Protobufs/meshtastic/module_config.pb.swift index b688479b..f6c28745 100644 --- a/Meshtastic/Protobufs/meshtastic/module_config.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/module_config.pb.swift @@ -64,7 +64,7 @@ enum RemoteHardwarePinType: SwiftProtobuf.Enum { extension RemoteHardwarePinType: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [RemoteHardwarePinType] = [ + static let allCases: [RemoteHardwarePinType] = [ .unknown, .digitalRead, .digitalWrite, @@ -1152,7 +1152,7 @@ struct ModuleConfig { extension ModuleConfig.AudioConfig.Audio_Baud: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [ModuleConfig.AudioConfig.Audio_Baud] = [ + static let allCases: [ModuleConfig.AudioConfig.Audio_Baud] = [ .codec2Default, .codec23200, .codec22400, @@ -1167,7 +1167,7 @@ extension ModuleConfig.AudioConfig.Audio_Baud: CaseIterable { extension ModuleConfig.SerialConfig.Serial_Baud: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [ModuleConfig.SerialConfig.Serial_Baud] = [ + static let allCases: [ModuleConfig.SerialConfig.Serial_Baud] = [ .baudDefault, .baud110, .baud300, @@ -1189,7 +1189,7 @@ extension ModuleConfig.SerialConfig.Serial_Baud: CaseIterable { extension ModuleConfig.SerialConfig.Serial_Mode: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [ModuleConfig.SerialConfig.Serial_Mode] = [ + static let allCases: [ModuleConfig.SerialConfig.Serial_Mode] = [ .default, .simple, .proto, @@ -1201,7 +1201,7 @@ extension ModuleConfig.SerialConfig.Serial_Mode: CaseIterable { extension ModuleConfig.CannedMessageConfig.InputEventChar: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [ModuleConfig.CannedMessageConfig.InputEventChar] = [ + static let allCases: [ModuleConfig.CannedMessageConfig.InputEventChar] = [ .none, .up, .down, diff --git a/Meshtastic/Protobufs/meshtastic/portnums.pb.swift b/Meshtastic/Protobufs/meshtastic/portnums.pb.swift index c8948d7d..a67e5ae6 100644 --- a/Meshtastic/Protobufs/meshtastic/portnums.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/portnums.pb.swift @@ -277,7 +277,7 @@ enum PortNum: SwiftProtobuf.Enum { extension PortNum: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [PortNum] = [ + static let allCases: [PortNum] = [ .unknownApp, .textMessageApp, .remoteHardwareApp, diff --git a/Meshtastic/Protobufs/meshtastic/remote_hardware.pb.swift b/Meshtastic/Protobufs/meshtastic/remote_hardware.pb.swift index 1f24d0db..b26a80a2 100644 --- a/Meshtastic/Protobufs/meshtastic/remote_hardware.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/remote_hardware.pb.swift @@ -119,7 +119,7 @@ struct HardwareMessage { extension HardwareMessage.TypeEnum: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [HardwareMessage.TypeEnum] = [ + static let allCases: [HardwareMessage.TypeEnum] = [ .unset, .writeGpios, .watchGpios, diff --git a/Meshtastic/Protobufs/meshtastic/storeforward.pb.swift b/Meshtastic/Protobufs/meshtastic/storeforward.pb.swift index a8fa5f90..697b4e87 100644 --- a/Meshtastic/Protobufs/meshtastic/storeforward.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/storeforward.pb.swift @@ -344,7 +344,7 @@ struct StoreAndForward { extension StoreAndForward.RequestResponse: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [StoreAndForward.RequestResponse] = [ + static let allCases: [StoreAndForward.RequestResponse] = [ .unset, .routerError, .routerHeartbeat, diff --git a/Meshtastic/Protobufs/meshtastic/telemetry.pb.swift b/Meshtastic/Protobufs/meshtastic/telemetry.pb.swift index d7837ab9..d878629a 100644 --- a/Meshtastic/Protobufs/meshtastic/telemetry.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/telemetry.pb.swift @@ -144,7 +144,7 @@ enum TelemetrySensorType: SwiftProtobuf.Enum { extension TelemetrySensorType: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [TelemetrySensorType] = [ + static let allCases: [TelemetrySensorType] = [ .sensorUnset, .bme280, .bme680, @@ -189,6 +189,10 @@ struct DeviceMetrics { /// Percent of airtime for transmission used within the last hour. var airUtilTx: Float = 0 + /// + /// How long the device has been running since the last reboot (in seconds) + var uptimeSeconds: UInt32 = 0 + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} @@ -473,6 +477,7 @@ extension DeviceMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa 2: .same(proto: "voltage"), 3: .standard(proto: "channel_utilization"), 4: .standard(proto: "air_util_tx"), + 5: .standard(proto: "uptime_seconds"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -485,6 +490,7 @@ extension DeviceMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa case 2: try { try decoder.decodeSingularFloatField(value: &self.voltage) }() case 3: try { try decoder.decodeSingularFloatField(value: &self.channelUtilization) }() case 4: try { try decoder.decodeSingularFloatField(value: &self.airUtilTx) }() + case 5: try { try decoder.decodeSingularUInt32Field(value: &self.uptimeSeconds) }() default: break } } @@ -503,6 +509,9 @@ extension DeviceMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa if self.airUtilTx != 0 { try visitor.visitSingularFloatField(value: self.airUtilTx, fieldNumber: 4) } + if self.uptimeSeconds != 0 { + try visitor.visitSingularUInt32Field(value: self.uptimeSeconds, fieldNumber: 5) + } try unknownFields.traverse(visitor: &visitor) } @@ -511,6 +520,7 @@ extension DeviceMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa if lhs.voltage != rhs.voltage {return false} if lhs.channelUtilization != rhs.channelUtilization {return false} if lhs.airUtilTx != rhs.airUtilTx {return false} + if lhs.uptimeSeconds != rhs.uptimeSeconds {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Meshtastic/Protobufs/meshtastic/xmodem.pb.swift b/Meshtastic/Protobufs/meshtastic/xmodem.pb.swift index a70841f2..4df972b8 100644 --- a/Meshtastic/Protobufs/meshtastic/xmodem.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/xmodem.pb.swift @@ -88,7 +88,7 @@ struct XModem { extension XModem.Control: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [XModem.Control] = [ + static let allCases: [XModem.Control] = [ .nul, .soh, .stx, diff --git a/Meshtastic/Views/Settings/RouteRecorder.swift b/Meshtastic/Views/Settings/RouteRecorder.swift index 88c4a44e..3fba7f8b 100644 --- a/Meshtastic/Views/Settings/RouteRecorder.swift +++ b/Meshtastic/Views/Settings/RouteRecorder.swift @@ -158,10 +158,10 @@ struct RouteRecorder: View { locationsHandler.locationsArray.removeAll() locationsHandler.recordingStarted = Date() let newRoute = RouteEntity(context: context) - newRoute.name = String("Route Recording") + newRoute.date = Date() + newRoute.name = "\(newRoute.date?.relativeTimeOfDay() ?? "morning".localized) hike" newRoute.id = Int32.random(in: Int32(Int8.max) ... Int32.max) newRoute.color = Int64(UIColor.random.hex) - newRoute.date = Date() newRoute.enabled = false color = Color(UIColor(hex: UInt32(newRoute.color))) self.recording = newRoute diff --git a/Meshtastic/Views/Settings/Routes.swift b/Meshtastic/Views/Settings/Routes.swift index 14e01d0f..34e8225f 100644 --- a/Meshtastic/Views/Settings/Routes.swift +++ b/Meshtastic/Views/Settings/Routes.swift @@ -21,89 +21,95 @@ struct Routes: View { @State var isExporting = false @State var exportString = "" + @State var hasChanges = false + @State var name = "" + @State var notes = "" + @State var enabled = true + @State var color = Color(red: 51, green: 199, blue: 88) + @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "enabled", ascending: false), NSSortDescriptor(key: "name", ascending: true), NSSortDescriptor(key: "date", ascending: false)], animation: .default) var routes: FetchedResults var body: some View { + VStack { - Button("Import Route") { - importing = true - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - - .alert(isPresented: $isShowingBadFileAlert) { - Alert(title: Text("Not a valid route file"), message: Text("Your route file must have both Latitude and Longitude columns and headers."), dismissButton: .default(Text("OK"))) - } - .fileImporter( - isPresented: $importing, - allowedContentTypes: [.commaSeparatedText], - allowsMultipleSelection: false - ) { result in - do { - guard let selectedFile: URL = try result.get().first else { return } - guard selectedFile.startAccessingSecurityScopedResource() else { - return - } - + if selectedRoute == nil { + Button("Import Route") { + importing = true + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + + .alert(isPresented: $isShowingBadFileAlert) { + Alert(title: Text("Not a valid route file"), message: Text("Your route file must have both Latitude and Longitude columns and headers."), dismissButton: .default(Text("OK"))) + } + .fileImporter( + isPresented: $importing, + allowedContentTypes: [.commaSeparatedText], + allowsMultipleSelection: false + ) { result in do { - guard let fileContent = String(data: try Data(contentsOf: selectedFile), encoding: .utf8) else { return } - let routeName = selectedFile.lastPathComponent.dropLast(4) - let lines = fileContent.components(separatedBy: "\n") - let headers = lines.first?.components(separatedBy: ",") - var latIndex = -1 - var longIndex = -1 - for index in headers!.indices { - print("\(index): \( headers![index])") - if headers![index].trimmingCharacters(in: .whitespaces) == "Latitude" { - latIndex = index - } else if headers![index].trimmingCharacters(in: .whitespaces) == "Longitude" { - longIndex = index - } + guard let selectedFile: URL = try result.get().first else { return } + guard selectedFile.startAccessingSecurityScopedResource() else { + return } - if latIndex >= 0 && longIndex >= 0 { - let newRoute = RouteEntity(context: context) - newRoute.name = String(routeName) - newRoute.id = Int32.random(in: Int32(Int8.max) ... Int32.max) - newRoute.color = Int64(UIColor.random.hex) - newRoute.date = Date() - newRoute.enabled = true - var newLocations = [LocationEntity]() - lines.dropFirst().forEach { line in - let data = line.components(separatedBy: ",") - if data.count > 1 { - let latitude = latIndex >= 0 ? data[latIndex].trimmingCharacters(in: .whitespaces) : "0" - let longitude = longIndex >= 0 ? data[longIndex].trimmingCharacters(in: .whitespaces) : "0" - let loc = LocationEntity(context: context) - loc.latitudeI = Int32((Double(latitude) ?? 0) * 1e7) - loc.longitudeI = Int32((Double(longitude) ?? 0) * 1e7) - newLocations.append(loc) - print("Longitude: \(longitude) Latitude: \(latitude)") + + do { + guard let fileContent = String(data: try Data(contentsOf: selectedFile), encoding: .utf8) else { return } + let routeName = selectedFile.lastPathComponent.dropLast(4) + let lines = fileContent.components(separatedBy: "\n") + let headers = lines.first?.components(separatedBy: ",") + var latIndex = -1 + var longIndex = -1 + for index in headers!.indices { + print("\(index): \( headers![index])") + if headers![index].trimmingCharacters(in: .whitespaces) == "Latitude" { + latIndex = index + } else if headers![index].trimmingCharacters(in: .whitespaces) == "Longitude" { + longIndex = index } } - newRoute.locations? = NSOrderedSet(array: newLocations) - do { - try context.save() - } catch let error as NSError { - print("Error: \(error.localizedDescription)") + if latIndex >= 0 && longIndex >= 0 { + let newRoute = RouteEntity(context: context) + newRoute.name = String(routeName) + newRoute.id = Int32.random(in: Int32(Int8.max) ... Int32.max) + newRoute.color = Int64(UIColor.random.hex) + newRoute.date = Date() + newRoute.enabled = true + var newLocations = [LocationEntity]() + lines.dropFirst().forEach { line in + let data = line.components(separatedBy: ",") + if data.count > 1 { + let latitude = latIndex >= 0 ? data[latIndex].trimmingCharacters(in: .whitespaces) : "0" + let longitude = longIndex >= 0 ? data[longIndex].trimmingCharacters(in: .whitespaces) : "0" + let loc = LocationEntity(context: context) + loc.latitudeI = Int32((Double(latitude) ?? 0) * 1e7) + loc.longitudeI = Int32((Double(longitude) ?? 0) * 1e7) + newLocations.append(loc) + print("Longitude: \(longitude) Latitude: \(latitude)") + } + } + newRoute.locations? = NSOrderedSet(array: newLocations) + do { + try context.save() + } catch let error as NSError { + print("Error: \(error.localizedDescription)") + isShowingBadFileAlert = true + } + } else { isShowingBadFileAlert = true } - } else { - isShowingBadFileAlert = true + + } catch { + print("error: \(error)") // to do deal with errors } } catch { - print("error: \(error)") // to do deal with errors + print("CSV Import Error") } - - } catch { - print("CSV Import Error") } - } - - VStack { List(routes, id: \.self, selection: $selectedRoute) { route in let routeColor = Color(UIColor(hex: route.color >= 0 ? UInt32(route.color) : 0)) Label { @@ -153,72 +159,152 @@ struct Routes: View { } .listStyle(.plain) - } - .navigationTitle("Route List") - VStack { - if selectedRoute != nil { - let locationArray = selectedRoute?.locations?.array as? [LocationEntity] ?? [] - let lineCoords = locationArray.compactMap({(location) -> CLLocationCoordinate2D in - return location.locationCoordinate ?? LocationHelper.DefaultLocation - }) - - Map() { - Annotation("Start", coordinate: lineCoords.first ?? LocationHelper.DefaultLocation) { - ZStack { - Circle() - .fill(Color(.green)) - .strokeBorder(.white, lineWidth: 3) - .frame(width: 15, height: 15) + } else { + VStack { + if selectedRoute != nil { + let locationArray = selectedRoute?.locations?.array as? [LocationEntity] ?? [] + let lineCoords = locationArray.compactMap({(location) -> CLLocationCoordinate2D in + return location.locationCoordinate ?? LocationHelper.DefaultLocation + }) + Form { + HStack { + Text("Name") + Spacer() + TextField( + "Name", + text: $name, + axis: .vertical + ) + .foregroundColor(Color.gray) + .onChange(of: name, perform: { _ in + let totalBytes = name.utf8.count + // Only mess with the value if it is too big + if totalBytes > 100 { + name = String(name.dropLast()) + } + }) } + Toggle("Enabled", isOn: $enabled) + .toggleStyle(.switch) + + ColorPicker("Color", selection: $color, supportsOpacity: false) + .padding(5) + + TextField( + "Notes", + text: $notes, + axis: .vertical + ) + .lineLimit(4...6) + .foregroundColor(Color.gray) } - .annotationTitles(.automatic) - Annotation("Finish", coordinate: lineCoords.last ?? LocationHelper.DefaultLocation) { - ZStack { - Circle() - .fill(Color(.black)) - .strokeBorder(.white, lineWidth: 3) - .frame(width: 15, height: 15) + .onAppear { + name = selectedRoute?.name ?? "unknown".localized + notes = selectedRoute?.notes ?? "" + enabled = selectedRoute?.enabled ?? false + color = Color(UIColor(hex: UInt32(selectedRoute?.color ?? 0))) + hasChanges = false + } + HStack { + + Button("cancel", role: .cancel) { + selectedRoute = nil } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + + Button("save") { + selectedRoute?.name = name + selectedRoute?.notes = notes + selectedRoute?.enabled = enabled + selectedRoute?.color = Int64(UIColor(color).hex) + do { + try context.save() + selectedRoute = nil + print("💾 Saved a route") + } catch { + context.rollback() + let nsError = error as NSError + print("💥 Error Saving RouteEntity from the Route Editor \(nsError)") + } + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .disabled(!hasChanges) } - .annotationTitles(.automatic) - let solid = StrokeStyle( - lineWidth: 3, - lineCap: .round, lineJoin: .round - ) - MapPolyline(coordinates: lineCoords) - .stroke(Color(UIColor(hex: UInt32(selectedRoute?.color ?? 0))), style: solid) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .safeAreaInset(edge: .bottom, alignment: UIDevice.current.userInterfaceIdiom == .phone ? .leading : .trailing) { - Button { - exportString = routeToCsvFile(locations: selectedRoute!.locations!.array as? [LocationEntity] ?? []) - isExporting = true - } label: { - Label("save", systemImage: "square.and.arrow.down") + .onChange(of: name) { newName in + hasChanges = true + } + .onChange(of: notes) { newNotes in + hasChanges = true + } + .onChange(of: enabled) { newEnabled in + hasChanges = true + } + .onChange(of: color) { newColor in + hasChanges = true + } + Map() { + Annotation("Start", coordinate: lineCoords.first ?? LocationHelper.DefaultLocation) { + ZStack { + Circle() + .fill(Color(.green)) + .strokeBorder(.white, lineWidth: 3) + .frame(width: 15, height: 15) + } + } + .annotationTitles(.automatic) + Annotation("Finish", coordinate: lineCoords.last ?? LocationHelper.DefaultLocation) { + ZStack { + Circle() + .fill(Color(.black)) + .strokeBorder(.white, lineWidth: 3) + .frame(width: 15, height: 15) + } + } + .annotationTitles(.automatic) + let solid = StrokeStyle( + lineWidth: 3, + lineCap: .round, lineJoin: .round + ) + MapPolyline(coordinates: lineCoords) + .stroke(Color(UIColor(hex: UInt32(selectedRoute?.color ?? 0))), style: solid) + } + .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity) + .safeAreaInset(edge: .bottom, alignment: UIDevice.current.userInterfaceIdiom == .phone ? .leading : .trailing) { + Button { + exportString = routeToCsvFile(locations: selectedRoute!.locations!.array as? [LocationEntity] ?? []) + isExporting = true + } label: { + Label("export", systemImage: "square.and.arrow.down") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding(.bottom) + .padding(.leading) } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding(.bottom) - .padding(.leading) } - } - }.navigationTitle(" \(selectedRoute?.name ?? "Unknown Route") \(selectedRoute?.locations?.count ?? 0) points") + .fileExporter( + isPresented: $isExporting, + document: CsvDocument(emptyCsv: exportString), + contentType: .commaSeparatedText, + defaultFilename: String("\(selectedRoute?.name ?? "Route") Log"), + onCompletion: { result in + if case .success = result { + print("Route log download succeeded.") + self.isExporting = false + } else { + print("Route log download failed: \(result).") + } + } + ) + } } - .fileExporter( - isPresented: $isExporting, - document: CsvDocument(emptyCsv: exportString), - contentType: .commaSeparatedText, - defaultFilename: String("\(selectedRoute?.name ?? "Route") Log"), - onCompletion: { result in - if case .success = result { - print("Route log download succeeded.") - self.isExporting = false - } else { - print("Route log download failed: \(result).") - } - } - ) + .navigationTitle(selectedRoute != nil ? name : "Route List") + .navigationBarTitleDisplayMode(.inline) } } diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index bda5169b..98a7733f 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -278,6 +278,11 @@ "reboot.node"="Reboot node?"; "received.ack"="Received Ack"; "received.ack.real"="Recipient Ack"; +"relativetimeofday.morning"="Morning"; +"relativetimeofday.midday"="Midday"; +"relativetimeofday.afternoon"="Afternoon"; +"relativetimeofday.evening"="Evening"; +"relativetimeofday.nighttime"="Nighttime"; "resume"="Resume"; "ringtone"="Ringtone"; "ringtone.config"="Ringtone Config"; diff --git a/protobufs b/protobufs index 22cbd0d4..f92900c5 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 22cbd0d4cfafa4b8c1e64517e06edc2d7a22cca9 +Subproject commit f92900c5f884b04388fb7abf61d4df66783015e4