diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 6f7c7e58..6b02e8f1 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1669,7 +1669,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5.1; + MARKETING_VERSION = 2.5.2; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1704,7 +1704,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5.1; + MARKETING_VERSION = 2.5.2; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1730,13 +1730,13 @@ INFOPLIST_FILE = Widgets/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Widgets; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 16.4; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.5.1; + MARKETING_VERSION = 2.5.2; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1763,13 +1763,13 @@ INFOPLIST_FILE = Widgets/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Widgets; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 16.4; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.5.1; + MARKETING_VERSION = 2.5.2; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 6f2520ee..93ba6493 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -818,6 +818,7 @@ func upsertSecurityConfigPacket(config: Config.SecurityConfig, nodeNum: Int64, s newSecurityConfig.serialEnabled = config.serialEnabled newSecurityConfig.debugLogApiEnabled = config.debugLogApiEnabled newSecurityConfig.bluetoothLoggingEnabled = config.bluetoothLoggingEnabled + newSecurityConfig.adminChannelEnabled = config.adminChannelEnabled fetchedNode[0].securityConfig = newSecurityConfig } else { fetchedNode[0].securityConfig?.publicKey = config.publicKey @@ -827,6 +828,7 @@ func upsertSecurityConfigPacket(config: Config.SecurityConfig, nodeNum: Int64, s fetchedNode[0].securityConfig?.serialEnabled = config.serialEnabled fetchedNode[0].securityConfig?.debugLogApiEnabled = config.debugLogApiEnabled fetchedNode[0].securityConfig?.bluetoothLoggingEnabled = config.bluetoothLoggingEnabled + fetchedNode[0].securityConfig?.adminChannelEnabled = config.adminChannelEnabled } if sessionPasskey?.count != 0 { fetchedNode[0].sessionPasskey = sessionPasskey diff --git a/Meshtastic/Views/Helpers/Weather/LocalWeatherConditions.swift b/Meshtastic/Views/Helpers/Weather/LocalWeatherConditions.swift index eb93897f..3ea1dee3 100644 --- a/Meshtastic/Views/Helpers/Weather/LocalWeatherConditions.swift +++ b/Meshtastic/Views/Helpers/Weather/LocalWeatherConditions.swift @@ -95,14 +95,21 @@ struct WeatherConditionsCompactWidget: View { let description: String var body: some View { VStack(alignment: .leading) { - Label { Text(description) } icon: { Image(systemName: symbolName).symbolRenderingMode(.multicolor) } - .font(.caption) + HStack(spacing: 5.0) { + Image(systemName: symbolName) + .foregroundColor(.accentColor) + .font(.callout) + Text(description) + .lineLimit(2) + .allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) + .fixedSize(horizontal: false, vertical: true) + .font(.caption) + } Text(temperature) - .font(temperature.length < 4 ? .system(size: 80) : .system(size: 60) ) + .font(temperature.length < 4 ? .system(size: 76) : .system(size: 60) ) } - .padding(10) - .frame(maxWidth: .infinity) - .frame(height: 175) + .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140) + .padding() .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) } } @@ -112,19 +119,24 @@ struct HumidityCompactWidget: View { let dewPoint: String var body: some View { VStack(alignment: .leading) { - Label { Text("HUMIDITY") } icon: { Image(systemName: "humidity").symbolRenderingMode(.multicolor) } - .font(.caption) + HStack(spacing: 5.0) { + Image(systemName: "humidity") + .foregroundColor(.accentColor) + .font(.callout) + Text("HUMIDITY") + .font(.caption) + } Text("\(humidity)%") .font(.largeTitle) - .padding(.bottom) + .padding(.bottom, 5) Text("The dew point is \(dewPoint) right now.") .lineLimit(3) + .allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) .fixedSize(horizontal: false, vertical: true) - .font(.caption) + .font(.caption2) } - .padding(10) - .frame(maxWidth: .infinity) - .frame(height: 175) + .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140) + .padding() .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) } } @@ -135,17 +147,21 @@ struct PressureCompactWidget: View { let low: Bool var body: some View { VStack(alignment: .leading) { - Label { Text("PRESSURE") } icon: { Image(systemName: "gauge").symbolRenderingMode(.multicolor) } - .font(.caption) + HStack(spacing: 5.0) { + Image(systemName: "gauge") + .foregroundColor(.accentColor) + .font(.callout) + Text("PRESSURE") + .font(.caption) + } Text(pressure) .font(pressure.length < 7 ? .system(size: 35) : .system(size: 30) ) Text(low ? "LOW" : "HIGH") - .padding(.bottom) + .padding(.bottom, 10) Text(unit) } - .padding(10) - .frame(maxWidth: .infinity) - .frame(height: 175) + .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140) + .padding() .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) } } @@ -158,15 +174,16 @@ struct WindCompactWidget: View { VStack(alignment: .leading) { Label { Text("WIND") } icon: { Image(systemName: "wind").foregroundColor(.accentColor) } Text("\(direction)") - .font(.caption) + .font(gust.isEmpty ? .callout : .caption) .padding(.bottom, 10) Text(speed) - .font(.system(size: 35)) - Text("Gusts \(gust)") + .font(gust.isEmpty ? .system(size: 45) : .system(size: 35)) + if !gust.isEmpty { + Text("Gusts \(gust)") + } } - .padding(10) - .frame(maxWidth: .infinity) - .frame(height: 175) + .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140) + .padding() .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) } } diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index c0852a36..74a4538a 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -128,14 +128,14 @@ struct ChannelMessageList: View { } .padding([.top]) .scrollDismissesKeyboard(.immediately) - .onAppear { - if channel.allPrivateMessages.count > 0 { - scrollView.scrollTo(channel.allPrivateMessages.last!.messageId) + .onFirstAppear { + withAnimation { + scrollView.scrollTo(channel.allPrivateMessages.last?.messageId ?? 0, anchor: .bottom) } } .onChange(of: channel.allPrivateMessages, perform: { _ in - if channel.allPrivateMessages.count > 0 { - scrollView.scrollTo(channel.allPrivateMessages.last!.messageId) + withAnimation { + scrollView.scrollTo(channel.allPrivateMessages.last?.messageId ?? 0, anchor: .bottom) } }) } diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index b0ec3a10..a53f788d 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -116,14 +116,14 @@ struct UserMessageList: View { } .padding([.top]) .scrollDismissesKeyboard(.immediately) - .onAppear { - if user.messageList.count > 0 { - scrollView.scrollTo(user.messageList.last!.messageId) + .onFirstAppear { + withAnimation { + scrollView.scrollTo(user.messageList.last?.messageId ?? 0, anchor: .bottom) } } .onChange(of: user.messageList, perform: { _ in - if user.messageList.count > 0 { - scrollView.scrollTo(user.messageList.last!.messageId) + withAnimation { + scrollView.scrollTo(user.messageList.last?.messageId ?? 0, anchor: .bottom) } }) } diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index 571511cf..0cd35248 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -94,10 +94,12 @@ struct EnvironmentMetricsLog: View { } } TableColumn("Wind Speed") { em in - Text("\(String(format: "%.1f", em.windSpeed)) hPa") + let windSpeed = Measurement(value: Double(em.windSpeed), unit: UnitSpeed.kilometersPerHour) + Text(windSpeed.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0))))) } TableColumn("Wind Direction") { em in - Text("\(String(format: "%.1f", em.windDirection)) hPa") + let direction = cardinalValue(from: Double(em.windDirection)) + Text(direction) } TableColumn("timestamp") { em in Text(em.time?.formattedDate(format: dateFormatString) ?? "unknown.age".localized) diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index 8a78c08c..6724e805 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -194,7 +194,11 @@ struct NodeDetail: View { PressureCompactWidget(pressure: String(format: "%.2f", node.latestEnvironmentMetrics?.barometricPressure ?? 0.0), unit: "hPA", low: node.latestEnvironmentMetrics?.barometricPressure ?? 0.0 <= 1009.144) } if node.latestEnvironmentMetrics?.windSpeed ?? 0.0 > 0.0 { - WindCompactWidget(speed: String(node.latestEnvironmentMetrics?.windSpeed ?? 0.0), gust: String(node.latestEnvironmentMetrics?.windGust ?? 0.0), direction: "") + let windSpeed = Measurement(value: Double(node.latestEnvironmentMetrics?.windSpeed ?? 0.0), unit: UnitSpeed.metersPerSecond) + let windGust = Measurement(value: Double(node.latestEnvironmentMetrics?.windGust ?? 0.0), unit: UnitSpeed.metersPerSecond) + let direction = cardinalValue(from: Double(node.latestEnvironmentMetrics?.windDirection ?? 0)) + WindCompactWidget(speed: windSpeed.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))), + gust: node.latestEnvironmentMetrics?.windGust ?? 0.0 > 0.0 ? windGust.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))) : "", direction: direction) } } .padding(node.latestEnvironmentMetrics?.iaq ?? -1 > 0 ? .bottom : .vertical) @@ -411,3 +415,28 @@ struct NodeDetail: View { } } } + +func cardinalValue(from heading: Double) -> String { + switch heading { + case 0 ..< 22.5: + return "North" + case 22.5 ..< 67.5: + return "North East" + case 67.5 ..< 112.5: + return "East" + case 112.5 ..< 157.5: + return "South East" + case 157.5 ..< 202.5: + return "South" + case 202.5 ..< 247.5: + return "South West" + case 247.5 ..< 292.5: + return "West" + case 292.5 ..< 337.5: + return "North West" + case 337.5 ... 360.0: + return "North" + default: + return "" + } +}