diff --git a/Shared/Helpers + Extensions/GeometryReader+SizeClasses.swift b/Shared/Helpers + Extensions/GeometryReader+SizeClasses.swift new file mode 100644 index 0000000..6631122 --- /dev/null +++ b/Shared/Helpers + Extensions/GeometryReader+SizeClasses.swift @@ -0,0 +1,27 @@ +// +// GeometryReader+SizeClasses.swift +// Telemetry Viewer +// +// Created by Daniel Jilg on 28.09.22. +// + +import Foundation +import SwiftUI + +extension GeometryProxy { + var isSmallWidth: Bool { + return self.size.width < 200 + } + + var isSmallHeight: Bool { + return self.size.height < 200 + } + + var isTinyWidth: Bool { + return self.size.width < 160 + } + + var isTinyHeight: Bool { + return self.size.height < 80 + } +} diff --git a/Shared/Helpers + Extensions/WidgetFamily+Small.swift b/Shared/Helpers + Extensions/WidgetFamily+Small.swift new file mode 100644 index 0000000..cd65de3 --- /dev/null +++ b/Shared/Helpers + Extensions/WidgetFamily+Small.swift @@ -0,0 +1,31 @@ +// +// WidgetFamily+Small.swift +// Telemetry Viewer +// +// Created by Daniel Jilg on 28.09.22. +// + +import Foundation +import WidgetKit + +extension WidgetFamily { + var isSmall: Bool { + switch self { + case .systemSmall: + return true + case .accessoryCorner, .accessoryCircular, .accessoryRectangular, .accessoryInline: + return true + default: + return false + } + } + + var isTiny: Bool { + switch self { + case .accessoryCorner, .accessoryCircular, .accessoryRectangular, .accessoryInline: + return true + default: + return false + } + } +} diff --git a/SwiftUICharts/Charts/BarChartView.swift b/SwiftUICharts/Charts/BarChartView.swift index 5d32b7d..5da7299 100644 --- a/SwiftUICharts/Charts/BarChartView.swift +++ b/SwiftUICharts/Charts/BarChartView.swift @@ -56,7 +56,9 @@ public struct BarChartView: View { } .padding(.leading, geo.size.width < 200 ? 10 : 15) .padding(.trailing, geo.size.width < 200 ? 2 : 4) - .padding(.bottom) + .if(geo.size.height > 200) { + $0.padding() + } } if hoveringDataEntry != nil { diff --git a/SwiftUICharts/Charts/ChartBottomView.swift b/SwiftUICharts/Charts/ChartBottomView.swift index f17e8aa..c212ac7 100644 --- a/SwiftUICharts/Charts/ChartBottomView.swift +++ b/SwiftUICharts/Charts/ChartBottomView.swift @@ -17,7 +17,7 @@ struct ChartBottomView: View { private var widthPerLabel: CGFloat { var width: CGFloat = 160 - if family == .systemSmall { + if family.isSmall { width = 80 } return width @@ -27,7 +27,7 @@ struct ChartBottomView: View { GeometryReader { geometry in HStack { if let firstDate = insightData?.data.first?.xAxisDate { - if family == .systemSmall { + if family.isSmall { Text(firstDate.dateString(ofStyle: .short)) } else { Text(firstDate, style: .date) @@ -46,7 +46,7 @@ struct ChartBottomView: View { let index: Int = (insightData?.data.count ?? 0) * number / numberOfLabels let indexInsightData = insightData?.data[index] if let indexDate: Date = indexInsightData?.xAxisDate { - if family == .systemSmall { + if family.isSmall { Text(indexDate.dateString(ofStyle: .short)) } else { Text(indexDate, style: .date) @@ -59,7 +59,7 @@ struct ChartBottomView: View { if geometry.size.width > widthPerLabel { if let lastDate = insightData?.data.last?.xAxisDate { - if family == .systemSmall { + if family.isSmall { Text(lastDate.dateString(ofStyle: .short)) } else { Text(lastDate, style: .date) @@ -71,7 +71,7 @@ struct ChartBottomView: View { } } .frame(maxHeight: 12) - .font(family == .systemSmall ? Font.system(size: 12, design: .default) : .footnote) + .font(family.isSmall ? Font.system(size: 12, design: .default) : .footnote) .foregroundColor(isSelected ? .cardBackground : .grayColor) } } diff --git a/SwiftUICharts/Charts/ChartRangeView.swift b/SwiftUICharts/Charts/ChartRangeView.swift index a4cb879..2234408 100644 --- a/SwiftUICharts/Charts/ChartRangeView.swift +++ b/SwiftUICharts/Charts/ChartRangeView.swift @@ -19,29 +19,30 @@ struct ChartRangeView: View { GeometryReader { reader in let percentage = 1 - (lastValue / Double(chartDataSet.highestValue - chartDataSet.lowestValue)) - ZStack { - if percentage < 0.9 { - Text(BigNumberFormatter.shortDisplay(for: "\(chartDataSet.lowestValue)")) - .position(x: 10, y: reader.size.height) - .foregroundColor(isSelected ? .cardBackground : .none) - } - + ZStack(alignment: .trailing) { if percentage > 0.1 { Text(BigNumberFormatter.shortDisplay(for: "\(chartDataSet.highestValue)")) - .position(x: 10, y: 0) + .lineLimit(1) + .position(x: 10, y: family.isTiny ? 5 : 0) .foregroundColor(isSelected ? .cardBackground : .none) } if !percentage.isNaN { Text(BigNumberFormatter.shortDisplay(for: "\(lastValue)")) - .frame(width: 30) - .multilineTextAlignment(.trailing) + + .lineLimit(1) .foregroundColor(.accentColor) .position(x: 10, y: reader.size.height * CGFloat(percentage)) } + + if percentage < 0.9 { + Text(BigNumberFormatter.shortDisplay(for: "\(chartDataSet.lowestValue)")) + .position(x: 10, y: family.isTiny ? reader.size.height - 5 : reader.size.height) + .foregroundColor(isSelected ? .cardBackground : .none) + } } } - .font(family == .systemSmall ? Font.system(size: 12, design: .default) : .footnote) - .frame(width: family == .systemSmall ? 25 : 30) + .font(family.isSmall ? Font.system(size: 12, design: .default) : .footnote) + .frame(maxWidth: family.isSmall ? 25 : 30) } } diff --git a/SwiftUICharts/Charts/LineChartView.swift b/SwiftUICharts/Charts/LineChartView.swift index a61718f..a7e3884 100644 --- a/SwiftUICharts/Charts/LineChartView.swift +++ b/SwiftUICharts/Charts/LineChartView.swift @@ -35,35 +35,41 @@ public struct LineChart: View { } public var body: some View { - VStack { - HStack { - ZStack(alignment: .topTrailing) { - LineChartShape(data: chartDataSet, shouldCloseShape: true, selectedChartDataPoint: nil).fill( - LinearGradient(gradient: Gradient(colors: [Color.accentColor.opacity(0.2), Color.accentColor.opacity(0.0)]), startPoint: .top, endPoint: .bottom) - ) - LineChartShape(data: chartDataSet, shouldCloseShape: false, selectedChartDataPoint: selectedChartDataPoint) - .stroke(Color.accentColor, style: StrokeStyle(lineWidth: 2, lineCap: .round, lineJoin: .round)) - - if selectedChartDataPoint != nil { - ChartHoverLabel(dataEntry: selectedChartDataPoint!, interval: chartDataSet.groupBy ?? .day) - .padding() + GeometryReader { geo in + VStack { + HStack { + ZStack(alignment: .topTrailing) { + LineChartShape(data: chartDataSet, shouldCloseShape: true, selectedChartDataPoint: nil).fill( + LinearGradient(gradient: Gradient(colors: [Color.accentColor.opacity(0.2), Color.accentColor.opacity(0.0)]), startPoint: .top, endPoint: .bottom) + ) + LineChartShape(data: chartDataSet, shouldCloseShape: false, selectedChartDataPoint: selectedChartDataPoint) + .stroke(Color.accentColor, style: StrokeStyle(lineWidth: 2, lineCap: .round, lineJoin: .round)) + + if selectedChartDataPoint != nil { + ChartHoverLabel(dataEntry: selectedChartDataPoint!, interval: chartDataSet.groupBy ?? .day) + .padding() + } + + hoverLayer } - hoverLayer + if let lastValue = chartDataSet.data.last?.yAxisValue { + ChartRangeView(lastValue: Double(lastValue), chartDataSet: chartDataSet, isSelected: isSelected) + } } - if let lastValue = chartDataSet.data.last?.yAxisValue { - ChartRangeView(lastValue: Double(lastValue), chartDataSet: chartDataSet, isSelected: isSelected) + if !geo.isTinyHeight { + ChartBottomView(insightData: chartDataSet, isSelected: isSelected) + .padding(.trailing, 30) + .padding(.leading, 10) } } - - ChartBottomView(insightData: chartDataSet, isSelected: isSelected) - .padding(.trailing, 30) - .padding(.leading, 10) + .font(.footnote) + .foregroundColor(Color.grayColor) + .if(!geo.isTinyHeight) { + $0.padding() + } } - .font(.footnote) - .foregroundColor(Color.grayColor) - .padding(.bottom) } } diff --git a/SwiftUICharts/ValueAndUnitViews/BigNumberFormatter.swift b/SwiftUICharts/ValueAndUnitViews/BigNumberFormatter.swift index b7b7225..a062cb9 100644 --- a/SwiftUICharts/ValueAndUnitViews/BigNumberFormatter.swift +++ b/SwiftUICharts/ValueAndUnitViews/BigNumberFormatter.swift @@ -19,7 +19,11 @@ class BigNumberFormatter { } static func shortDisplay(for number: Double) -> String { - guard number >= 1000 else { return NumberFormatter().string(from: NSNumber(value: number)) ?? "—" } + let numberFormatter = NumberFormatter() + numberFormatter.generatesDecimalNumbers = true + numberFormatter.maximumFractionDigits = 1 + + guard number >= 1000 else { return numberFormatter.string(from: NSNumber(value: number)) ?? "—" } // Available Units let units: [Double] = [ @@ -43,7 +47,10 @@ class BigNumberFormatter { // round to the nearest unit and add its prefix let unitPrefix = unitPrefixes[unit] ?? "" - return "\((number * 10 / unit).rounded() / 10)\(unitPrefix)" + + let roundedNumber = NSNumber(value: (number * 10 / unit).rounded() / 10) + + return "\(numberFormatter.string(from: roundedNumber) ?? "")\(unitPrefix)" } static func shortDisplay(for numberString: String) -> String { diff --git a/Telemetry Viewer.xcodeproj/project.pbxproj b/Telemetry Viewer.xcodeproj/project.pbxproj index 5f99bd5..6099d4e 100644 --- a/Telemetry Viewer.xcodeproj/project.pbxproj +++ b/Telemetry Viewer.xcodeproj/project.pbxproj @@ -15,6 +15,14 @@ 2B0769DA2722EA2F0024D8A7 /* DataTransferObjects in Frameworks */ = {isa = PBXBuildFile; productRef = 2B0769D92722EA2F0024D8A7 /* DataTransferObjects */; }; 2B0769DC2722EA350024D8A7 /* DataTransferObjects in Frameworks */ = {isa = PBXBuildFile; productRef = 2B0769DB2722EA350024D8A7 /* DataTransferObjects */; }; 2B0769DE2722EA3B0024D8A7 /* DataTransferObjects in Frameworks */ = {isa = PBXBuildFile; productRef = 2B0769DD2722EA3B0024D8A7 /* DataTransferObjects */; }; + 2B0ACDA828E45AB00087C50E /* WidgetFamily+Small.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B0ACDA728E45AB00087C50E /* WidgetFamily+Small.swift */; }; + 2B0ACDA928E45AB00087C50E /* WidgetFamily+Small.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B0ACDA728E45AB00087C50E /* WidgetFamily+Small.swift */; }; + 2B0ACDAA28E45AB00087C50E /* WidgetFamily+Small.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B0ACDA728E45AB00087C50E /* WidgetFamily+Small.swift */; }; + 2B0ACDAB28E45AB00087C50E /* WidgetFamily+Small.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B0ACDA728E45AB00087C50E /* WidgetFamily+Small.swift */; }; + 2B0ACDAD28E463A40087C50E /* GeometryReader+SizeClasses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B0ACDAC28E463A40087C50E /* GeometryReader+SizeClasses.swift */; }; + 2B0ACDAE28E463A40087C50E /* GeometryReader+SizeClasses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B0ACDAC28E463A40087C50E /* GeometryReader+SizeClasses.swift */; }; + 2B0ACDAF28E463A40087C50E /* GeometryReader+SizeClasses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B0ACDAC28E463A40087C50E /* GeometryReader+SizeClasses.swift */; }; + 2B0ACDB028E463A40087C50E /* GeometryReader+SizeClasses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B0ACDAC28E463A40087C50E /* GeometryReader+SizeClasses.swift */; }; 2B135E1626E113E000CC0A40 /* Optional+RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B135E1526E113E000CC0A40 /* Optional+RawRepresentable.swift */; }; 2B135E1726E113E000CC0A40 /* Optional+RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B135E1526E113E000CC0A40 /* Optional+RawRepresentable.swift */; }; 2B1D468926CC4423008814A9 /* OrgService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B1D468826CC4423008814A9 /* OrgService.swift */; }; @@ -422,6 +430,8 @@ /* Begin PBXFileReference section */ 2B00509027035C85009C609C /* Color+Hex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Hex.swift"; sourceTree = ""; }; + 2B0ACDA728E45AB00087C50E /* WidgetFamily+Small.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WidgetFamily+Small.swift"; sourceTree = ""; }; + 2B0ACDAC28E463A40087C50E /* GeometryReader+SizeClasses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GeometryReader+SizeClasses.swift"; sourceTree = ""; }; 2B135E1526E113E000CC0A40 /* Optional+RawRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+RawRepresentable.swift"; sourceTree = ""; }; 2B1D468826CC4423008814A9 /* OrgService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrgService.swift; sourceTree = ""; }; 2B1D469726CC4C5D008814A9 /* ErrorService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorService.swift; sourceTree = ""; }; @@ -1005,6 +1015,8 @@ DCDC6F12253EECA90012D9A7 /* Numbers+Bound.swift */, DCD52C492541FFE3003F12C6 /* ColSpan.swift */, 2B135E1526E113E000CC0A40 /* Optional+RawRepresentable.swift */, + 2B0ACDA728E45AB00087C50E /* WidgetFamily+Small.swift */, + 2B0ACDAC28E463A40087C50E /* GeometryReader+SizeClasses.swift */, ); path = "Helpers + Extensions"; sourceTree = ""; @@ -1525,6 +1537,7 @@ C51CB78727566264005A3FB9 /* ChartDataPoint+InsightCalculationResult.swift in Sources */, C5F4D34D28ACF48000EBB667 /* DonutChart.swift in Sources */, C51CB77D2756623E005A3FB9 /* DashedCardView.swift in Sources */, + 2B0ACDAB28E45AB00087C50E /* WidgetFamily+Small.swift in Sources */, C5F4D35D28ACF48000EBB667 /* ValueUnitAndTitleView.swift in Sources */, C51CB79627566504005A3FB9 /* Color+Hex.swift in Sources */, C51CB77C2756623B005A3FB9 /* Helpers.swift in Sources */, @@ -1536,6 +1549,7 @@ C51CB78527566258005A3FB9 /* ConditionalViewModifier.swift in Sources */, C5F4D33D28ACF48000EBB667 /* RawChartView.swift in Sources */, C51CB77F27566243005A3FB9 /* Color.swift in Sources */, + 2B0ACDB028E463A40087C50E /* GeometryReader+SizeClasses.swift in Sources */, C5F4D33128ACF48000EBB667 /* ChartBottomView.swift in Sources */, C51CB7912756646D005A3FB9 /* TelemetrySignalTypes.swift in Sources */, C5F4D36128ACF48000EBB667 /* BigNumberFormatter.swift in Sources */, @@ -1583,6 +1597,7 @@ C5CE3D59271AFCE2005232EC /* AdaptiveStack.swift in Sources */, C5F4D34C28ACF48000EBB667 /* DonutChart.swift in Sources */, C5CE3D4F271AFCE2005232EC /* OptionalToggle.swift in Sources */, + 2B0ACDAA28E45AB00087C50E /* WidgetFamily+Small.swift in Sources */, C5F4D35C28ACF48000EBB667 /* ValueUnitAndTitleView.swift in Sources */, C5A8D87D270C65F90032560A /* InsightService.swift in Sources */, C5A8D882270C66190032560A /* ErrorService.swift in Sources */, @@ -1594,6 +1609,7 @@ C5CE3D57271AFCE2005232EC /* Buttonstyles.swift in Sources */, C5F4D33C28ACF48000EBB667 /* RawChartView.swift in Sources */, 2B6431A72739A5BB009A33C4 /* AsyncOperation.swift in Sources */, + 2B0ACDAF28E463A40087C50E /* GeometryReader+SizeClasses.swift in Sources */, C5F4D33028ACF48000EBB667 /* ChartBottomView.swift in Sources */, C581F4E0271B22FD0031E99C /* Color+Hex.swift in Sources */, C5F4D36028ACF48000EBB667 /* BigNumberFormatter.swift in Sources */, @@ -1640,6 +1656,7 @@ 2BA5D50726CD1403008FBF8F /* InsightCard.swift in Sources */, DC4F7DBA25067C3000B54561 /* OrganizationSettingsView.swift in Sources */, 2B745562272306DD002CBB45 /* ChartDataPoint+InsightCalculationResult.swift in Sources */, + 2B0ACDAD28E463A40087C50E /* GeometryReader+SizeClasses.swift in Sources */, C5F4D34A28ACF48000EBB667 /* DonutChart.swift in Sources */, 2BC522EA2625FCEF00E643AC /* HelpAndFeedbackView.swift in Sources */, DCB5F9F625F29BF4004C4922 /* InsightGroupEditor.swift in Sources */, @@ -1661,6 +1678,7 @@ 2BDD35062710727900CB2AA5 /* EditorModeView.swift in Sources */, C56B91F927C536D60085839A /* QueryView.swift in Sources */, 2B1D468926CC4423008814A9 /* OrgService.swift in Sources */, + 2B0ACDA828E45AB00087C50E /* WidgetFamily+Small.swift in Sources */, 2B21FCDF26FDCAE200A8A55B /* InsightsList.swift in Sources */, 2BBFCA11267D05E40013DC74 /* DetailSidebar.swift in Sources */, C5F4D33628ACF48000EBB667 /* ChartRangeView.swift in Sources */, @@ -1843,8 +1861,10 @@ 2B61B94F264C08D4003F62C4 /* SignalsService.swift in Sources */, C5F4D33F28ACF48000EBB667 /* RoundedCorners.swift in Sources */, DC627EF025A35B6A00C1DF33 /* EmptyAppView.swift in Sources */, + 2B0ACDA928E45AB00087C50E /* WidgetFamily+Small.swift in Sources */, 6351A78A277C9EDA003AF559 /* InsightDisplayMode+Extensions.swift in Sources */, 2BA5D50226CD05CC008FBF8F /* InsightGroupsView.swift in Sources */, + 2B0ACDAE28E463A40087C50E /* GeometryReader+SizeClasses.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/TelemetryDeckWidget/TelemetryDeckWidget.swift b/TelemetryDeckWidget/TelemetryDeckWidget.swift index 733380c..83efe1e 100644 --- a/TelemetryDeckWidget/TelemetryDeckWidget.swift +++ b/TelemetryDeckWidget/TelemetryDeckWidget.swift @@ -15,22 +15,11 @@ import WidgetKit struct Provider: IntentTimelineProvider { let api: APIClient - // I think I can remove all of these except api?! -// let cacheLayer: CacheLayer -// let errors: ErrorService -// let insightService: InsightService -// let insightResultService: InsightResultService - init() { let configuration = TelemetryManagerConfiguration(appID: "79167A27-EBBF-4012-9974-160624E5D07B") TelemetryManager.initialize(with: configuration) self.api = APIClient() -// self.cacheLayer = CacheLayer() -// self.errors = ErrorService() -// -// self.insightService = InsightService(api: api, errors: errors) -// self.insightResultService = InsightResultService(api: api, cache: cacheLayer, errors: errors) } func placeholder(in context: Context) -> SimpleEntry { @@ -117,6 +106,14 @@ enum WidgetDisplayMode { @main struct TelemetryDeckWidget: Widget { let kind: String = "TelemetryDeckWidget" + + func supportedFamilies() -> [WidgetFamily] { + if #available(iOSApplicationExtension 16.0, *) { + return [.accessoryRectangular, .systemSmall, .systemMedium, .systemLarge, .systemExtraLarge] + } else { + return [.systemSmall, .systemMedium, .systemLarge, .systemExtraLarge] + } + } var body: some WidgetConfiguration { IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in @@ -124,6 +121,7 @@ struct TelemetryDeckWidget: Widget { } .configurationDisplayName("Telemetry Deck Widget") .description("If no Insights are available here, make sure you are logged in. You can search for Insights by name, app, or display type") + .supportedFamilies(supportedFamilies()) } } @@ -145,6 +143,12 @@ struct TelemetryDeckWidget_Previews: PreviewProvider { chartDataSet: ChartDataSet(data: result2.data, groupBy: result2.insight.groupBy), widgetDisplayMode: .normalView ) + if #available(iOSApplicationExtension 16.0, *) { + TelemetryDeckWidgetEntryView(entry: entry) + .previewContext(WidgetPreviewContext(family: .accessoryRectangular)) + } else { + // Fallback on earlier versions + } TelemetryDeckWidgetEntryView(entry: entry) .previewContext(WidgetPreviewContext(family: .systemSmall)) TelemetryDeckWidgetEntryView(entry: entry2) diff --git a/TelemetryDeckWidget/TelemetryDeckWidgetEntryViews.swift b/TelemetryDeckWidget/TelemetryDeckWidgetEntryViews.swift index 33223ec..7ba4e38 100644 --- a/TelemetryDeckWidget/TelemetryDeckWidgetEntryViews.swift +++ b/TelemetryDeckWidget/TelemetryDeckWidgetEntryViews.swift @@ -20,14 +20,16 @@ struct TelemetryDeckWidgetEntryView: View { GeometryReader { geometry in ZStack { VStack(alignment: .leading, spacing: 6) { - Text((entry.configuration.ShowAppName?.boolValue ?? false) ? - (entry.configuration.Insight?.appName ?? "Example App").uppercased() + - " • " + entry.insightCalculationResult.insight.title.uppercased() : - entry.insightCalculationResult.insight.title.uppercased()) + if !family.isTiny { + Text((entry.configuration.ShowAppName?.boolValue ?? false) ? + (entry.configuration.Insight?.appName ?? "Example App").uppercased() + + " • " + entry.insightCalculationResult.insight.title.uppercased() : + entry.insightCalculationResult.insight.title.uppercased()) .padding(.top) .padding(.horizontal) - .font(Font.system(size: family == .systemSmall ? 10 : 12)) + .font(Font.system(size: family.isSmall ? 10 : 12)) .foregroundColor(.grayColor) + } switch entry.insightCalculationResult.insight.displayMode { case .raw: