diff --git a/PiStatsMobile/IntentHandler/Info.plist b/PiStatsMobile/IntentHandler/Info.plist
new file mode 100644
index 0000000..8a89a7a
--- /dev/null
+++ b/PiStatsMobile/IntentHandler/Info.plist
@@ -0,0 +1,42 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ IntentHandler
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ $(MARKETING_VERSION)
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSExtension
+
+ NSExtensionAttributes
+
+ IntentsRestrictedWhileLocked
+
+ IntentsRestrictedWhileProtectedDataUnavailable
+
+ IntentsSupported
+
+ SelectPiholeIntent
+
+
+ NSExtensionPointIdentifier
+ com.apple.intents-service
+ NSExtensionPrincipalClass
+ $(PRODUCT_MODULE_NAME).IntentHandler
+
+
+
diff --git a/PiStatsMobile/IntentHandler/IntentHandler.entitlements b/PiStatsMobile/IntentHandler/IntentHandler.entitlements
new file mode 100644
index 0000000..832a3a7
--- /dev/null
+++ b/PiStatsMobile/IntentHandler/IntentHandler.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.application-groups
+
+ group.dev.bunn.PiStatsMobile
+
+
+
diff --git a/PiStatsMobile/IntentHandler/IntentHandler.swift b/PiStatsMobile/IntentHandler/IntentHandler.swift
new file mode 100644
index 0000000..3abf026
--- /dev/null
+++ b/PiStatsMobile/IntentHandler/IntentHandler.swift
@@ -0,0 +1,40 @@
+//
+// IntentHandler.swift
+// IntentHandler
+//
+// Created by Fernando Bunn on 30/09/2020.
+//
+
+import Intents
+
+class IntentHandler: INExtension, SelectPiholeIntentHandling {
+
+ func providePiholeOptionsCollection(for intent: SelectPiholeIntent, with completion: @escaping (INObjectCollection?, Error?) -> Void) {
+ let piholes = validPiholes().map {
+ PiholeIntent(
+ identifier: $0.id.uuidString,
+ display: $0.title
+ )
+ }
+ let collection = INObjectCollection(items: piholes)
+ completion(collection, nil)
+ }
+
+ func defaultPihole(for intent: SelectPiholeIntent) -> PiholeIntent? {
+ if let pihole = validPiholes().first {
+ return PiholeIntent(
+ identifier: pihole.id.uuidString,
+ display: pihole.title
+ )
+ }
+ return nil
+ }
+
+ override func handler(for intent: INIntent) -> Any {
+ return self
+ }
+
+ private func validPiholes() -> [Pihole] {
+ Pihole.restoreAll().filter{ $0.hasPiMonitor }
+ }
+}
diff --git a/PiStatsMobile/PiStatsMobile.xcodeproj/project.pbxproj b/PiStatsMobile/PiStatsMobile.xcodeproj/project.pbxproj
index 12ab58c..a9ba195 100644
--- a/PiStatsMobile/PiStatsMobile.xcodeproj/project.pbxproj
+++ b/PiStatsMobile/PiStatsMobile.xcodeproj/project.pbxproj
@@ -13,12 +13,32 @@
310647D024BD120600E2DA90 /* PiStatsDisplayWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310647CF24BD120600E2DA90 /* PiStatsDisplayWidgetView.swift */; };
310647D224BD13D700E2DA90 /* MediumStatsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310647D124BD13D700E2DA90 /* MediumStatsItem.swift */; };
31115BC824B29A1700C91212 /* PiholeSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31115BC724B29A1700C91212 /* PiholeSetupView.swift */; };
+ 311A3E1C25252504005464D2 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319B082A24B53246008B3C2F /* Logger.swift */; };
+ 311A3E2225252509005464D2 /* UserPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319B082F24B53269008B3C2F /* UserPreferences.swift */; };
+ 311A3E282525250C005464D2 /* APIToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319B082E24B53268008B3C2F /* APIToken.swift */; };
+ 311A3E2E2525250E005464D2 /* KeyChainPasswordItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319B082C24B53255008B3C2F /* KeyChainPasswordItem.swift */; };
+ 311A3E3425252544005464D2 /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3132CDBE252522DA00542F6F /* IntentHandler.swift */; };
+ 311A3E4425252766005464D2 /* UserDefaultExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3182E28F24BB6DA100BF53D9 /* UserDefaultExtensions.swift */; };
+ 311A3E4A2525276A005464D2 /* Pihole.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319B082624B5321C008B3C2F /* Pihole.swift */; };
+ 311A3E5625252775005464D2 /* UIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311D0A7824B0F405003F18DB /* UIConstants.swift */; };
+ 311A3E5C25252778005464D2 /* StatsItemType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311D0A7624B0EFE3003F18DB /* StatsItemType.swift */; };
+ 311A3E68252527D9005464D2 /* SwiftHole in Frameworks */ = {isa = PBXBuildFile; productRef = 311A3E67252527D9005464D2 /* SwiftHole */; };
+ 311A3E6A252527DC005464D2 /* PiMonitor in Frameworks */ = {isa = PBXBuildFile; productRef = 311A3E69252527DC005464D2 /* PiMonitor */; };
311D0A7524B0ED05003F18DB /* StatsItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311D0A7424B0ED05003F18DB /* StatsItemView.swift */; };
311D0A7724B0EFE3003F18DB /* StatsItemType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311D0A7624B0EFE3003F18DB /* StatsItemType.swift */; };
311D0A7924B0F405003F18DB /* UIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311D0A7824B0F405003F18DB /* UIConstants.swift */; };
311D0A7B24B0F613003F18DB /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311D0A7A24B0F613003F18DB /* StatsView.swift */; };
+ 3132CDBF252522DA00542F6F /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3132CDBE252522DA00542F6F /* IntentHandler.swift */; };
+ 3132CDC3252522DA00542F6F /* IntentHandler.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 3132CDBC252522DA00542F6F /* IntentHandler.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ 3132CDCC252522F400542F6F /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3132CDBE252522DA00542F6F /* IntentHandler.swift */; };
+ 313599D32527224300A12AC9 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313599D22527224300A12AC9 /* Constants.swift */; };
+ 313599D42527224300A12AC9 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313599D22527224300A12AC9 /* Constants.swift */; };
+ 313599D52527224300A12AC9 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313599D22527224300A12AC9 /* Constants.swift */; };
3137D20D24D217CD0021ACD3 /* PiMonitor in Frameworks */ = {isa = PBXBuildFile; productRef = 3137D20C24D217CD0021ACD3 /* PiMonitor */; };
3142B27A24CDE2EE00484AB2 /* MetricsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3142B27924CDE2EE00484AB2 /* MetricsView.swift */; };
+ 315825AD25275812001160EA /* PiholeIntents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 315825B125275812001160EA /* PiholeIntents.intentdefinition */; };
+ 315825AE25275812001160EA /* PiholeIntents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 315825B125275812001160EA /* PiholeIntents.intentdefinition */; };
+ 315825AF25275812001160EA /* PiholeIntents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 315825B125275812001160EA /* PiholeIntents.intentdefinition */; };
315F5EC024BA162600FED38F /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 315F5EBF24BA162600FED38F /* WidgetKit.framework */; };
315F5EC224BA162600FED38F /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 315F5EC124BA162600FED38F /* SwiftUI.framework */; };
315F5EC524BA162600FED38F /* ViewStatsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 315F5EC424BA162600FED38F /* ViewStatsWidget.swift */; };
@@ -40,6 +60,9 @@
3175EB6324AE597A00E7B5FF /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3175EB6224AE597A00E7B5FF /* Preview Assets.xcassets */; };
3175EB6E24AE597A00E7B5FF /* PiStatsMobileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3175EB6D24AE597A00E7B5FF /* PiStatsMobileTests.swift */; };
3175EB7924AE597A00E7B5FF /* PiStatsMobileUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3175EB7824AE597A00E7B5FF /* PiStatsMobileUITests.swift */; };
+ 317636A2252658F5005AED55 /* ViewUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317636A1252658F5005AED55 /* ViewUtils.swift */; };
+ 317636A3252658F5005AED55 /* ViewUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317636A1252658F5005AED55 /* ViewUtils.swift */; };
+ 317636AB25265AB7005AED55 /* PiMonitorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317636A925265AB7005AED55 /* PiMonitorView.swift */; };
3176C733251FB547002DF0A3 /* DisableDurationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3176C732251FB547002DF0A3 /* DisableDurationManager.swift */; };
317D481D24B3ADE200897EA9 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 317D481F24B3ADE200897EA9 /* Localizable.strings */; };
317D482224B3D2F000897EA9 /* CodeScannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317D482124B3D2F000897EA9 /* CodeScannerView.swift */; };
@@ -47,6 +70,7 @@
3182E29024BB6DA100BF53D9 /* UserDefaultExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3182E28F24BB6DA100BF53D9 /* UserDefaultExtensions.swift */; };
3182E29124BB6DA100BF53D9 /* UserDefaultExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3182E28F24BB6DA100BF53D9 /* UserDefaultExtensions.swift */; };
319771AC24DB24BA0005E6BF /* ScannedPihole.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319771AB24DB24BA0005E6BF /* ScannedPihole.swift */; };
+ 31986A9425252AC500261BF5 /* PiMonitorTimelineProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31986A9325252AC500261BF5 /* PiMonitorTimelineProvider.swift */; };
319B082824B5321C008B3C2F /* Pihole.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319B082624B5321C008B3C2F /* Pihole.swift */; };
319B082924B5321C008B3C2F /* PiholeDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319B082724B5321C008B3C2F /* PiholeDataProvider.swift */; };
319B082B24B53246008B3C2F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319B082A24B53246008B3C2F /* Logger.swift */; };
@@ -61,11 +85,21 @@
31AECCFC24C7A55A00E381E0 /* PiholeEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AECCFB24C7A55A00E381E0 /* PiholeEntry.swift */; };
31AECCFE24C7A57300E381E0 /* PiholeTimelineProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AECCFD24C7A57300E381E0 /* PiholeTimelineProvider.swift */; };
31BCBE6324D2197B0077B6AE /* PiMonitor in Frameworks */ = {isa = PBXBuildFile; productRef = 31BCBE6224D2197B0077B6AE /* PiMonitor */; };
+ 31C1A585252691A900431C87 /* PiMonitorStatusHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31C1A584252691A900431C87 /* PiMonitorStatusHeader.swift */; };
31DB2A2A2517B1C200D67F9E /* CustomDurationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31DB2A292517B1C200D67F9E /* CustomDurationsView.swift */; };
+ 31E153072523AD61008DD6CD /* PiMonitorWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E153012523AD44008DD6CD /* PiMonitorWidgetView.swift */; };
+ 31E153102523AD69008DD6CD /* PiMonitorWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E152FB2523AC62008DD6CD /* PiMonitorWidget.swift */; };
31FC1DAA24B64BA900319E6F /* PiholeDataProviderListManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FC1DA924B64BA900319E6F /* PiholeDataProviderListManager.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
+ 3132CDC1252522DA00542F6F /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 3175EB5024AE597900E7B5FF /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 3132CDBB252522DA00542F6F;
+ remoteInfo = IntentHandler;
+ };
315F5EC924BA162800FED38F /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 3175EB5024AE597900E7B5FF /* Project object */;
@@ -97,6 +131,7 @@
dstSubfolderSpec = 13;
files = (
315F5ECB24BA162800FED38F /* PiStatsWidgetExtension.appex in Embed App Extensions */,
+ 3132CDC3252522DA00542F6F /* IntentHandler.appex in Embed App Extensions */,
);
name = "Embed App Extensions";
runOnlyForDeploymentPostprocessing = 0;
@@ -114,13 +149,20 @@
311D0A7624B0EFE3003F18DB /* StatsItemType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsItemType.swift; sourceTree = ""; };
311D0A7824B0F405003F18DB /* UIConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIConstants.swift; sourceTree = ""; };
311D0A7A24B0F613003F18DB /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = ""; };
+ 3132CDBC252522DA00542F6F /* IntentHandler.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = IntentHandler.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+ 3132CDBE252522DA00542F6F /* IntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentHandler.swift; sourceTree = ""; };
+ 3132CDC0252522DA00542F6F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 313599D22527224300A12AC9 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; };
3142B27924CDE2EE00484AB2 /* MetricsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsView.swift; sourceTree = ""; };
+ 315825B025275812001160EA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/PiholeIntents.intentdefinition; sourceTree = ""; };
+ 315825B825275832001160EA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/PiholeIntents.strings; sourceTree = ""; };
315F5EBD24BA162600FED38F /* PiStatsWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PiStatsWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
315F5EBF24BA162600FED38F /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
315F5EC124BA162600FED38F /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
315F5EC424BA162600FED38F /* ViewStatsWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewStatsWidget.swift; sourceTree = ""; };
315F5EC624BA162800FED38F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
315F5EC824BA162800FED38F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 3171772B25277BBA001B1CAB /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/PiholeIntents.strings"; sourceTree = ""; };
3175EB5824AE597900E7B5FF /* PiStatsMobile.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PiStatsMobile.app; sourceTree = BUILT_PRODUCTS_DIR; };
3175EB5B24AE597900E7B5FF /* PiStatsMobileApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiStatsMobileApp.swift; sourceTree = ""; };
3175EB5D24AE597900E7B5FF /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
@@ -133,6 +175,8 @@
3175EB7424AE597A00E7B5FF /* PiStatsMobileUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PiStatsMobileUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3175EB7824AE597A00E7B5FF /* PiStatsMobileUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiStatsMobileUITests.swift; sourceTree = ""; };
3175EB7A24AE597A00E7B5FF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 317636A1252658F5005AED55 /* ViewUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewUtils.swift; sourceTree = ""; };
+ 317636A925265AB7005AED55 /* PiMonitorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiMonitorView.swift; sourceTree = ""; };
3176C732251FB547002DF0A3 /* DisableDurationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisableDurationManager.swift; sourceTree = ""; };
317D481E24B3ADE200897EA9 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; };
317D482024B3ADEA00897EA9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; };
@@ -141,6 +185,8 @@
3182E28B24BB6A2300BF53D9 /* PiStatsMobile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PiStatsMobile.entitlements; sourceTree = ""; };
3182E28F24BB6DA100BF53D9 /* UserDefaultExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultExtensions.swift; sourceTree = ""; };
319771AB24DB24BA0005E6BF /* ScannedPihole.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScannedPihole.swift; sourceTree = ""; };
+ 31986A9325252AC500261BF5 /* PiMonitorTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiMonitorTimelineProvider.swift; sourceTree = ""; };
+ 31986AA025252D9600261BF5 /* IntentHandler.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = IntentHandler.entitlements; sourceTree = ""; };
319B082624B5321C008B3C2F /* Pihole.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pihole.swift; sourceTree = ""; };
319B082724B5321C008B3C2F /* PiholeDataProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PiholeDataProvider.swift; sourceTree = ""; };
319B082A24B53246008B3C2F /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; };
@@ -154,12 +200,24 @@
31AECCC424C7A17400E381E0 /* PiStatsWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiStatsWidgets.swift; sourceTree = ""; };
31AECCFB24C7A55A00E381E0 /* PiholeEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiholeEntry.swift; sourceTree = ""; };
31AECCFD24C7A57300E381E0 /* PiholeTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiholeTimelineProvider.swift; sourceTree = ""; };
+ 31C1A584252691A900431C87 /* PiMonitorStatusHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiMonitorStatusHeader.swift; sourceTree = ""; };
31D1F58A24BB7F8500D50FDB /* StatusHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusHeaderView.swift; sourceTree = ""; };
31DB2A292517B1C200D67F9E /* CustomDurationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDurationsView.swift; sourceTree = ""; };
+ 31E152FB2523AC62008DD6CD /* PiMonitorWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiMonitorWidget.swift; sourceTree = ""; };
+ 31E153012523AD44008DD6CD /* PiMonitorWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiMonitorWidgetView.swift; sourceTree = ""; };
31FC1DA924B64BA900319E6F /* PiholeDataProviderListManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiholeDataProviderListManager.swift; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
+ 3132CDB9252522DA00542F6F /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 311A3E6A252527DC005464D2 /* PiMonitor in Frameworks */,
+ 311A3E68252527D9005464D2 /* SwiftHole in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
315F5EBA24BA162600FED38F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -210,6 +268,7 @@
310647CA24BD10D600E2DA90 /* Views */ = {
isa = PBXGroup;
children = (
+ 31C1A5832526919300431C87 /* PiMonitor */,
310647CB24BD10E900E2DA90 /* CircleBadgeStatus.swift */,
310647CD24BD115F00E2DA90 /* SmallStatsItem.swift */,
310647D124BD13D700E2DA90 /* MediumStatsItem.swift */,
@@ -218,6 +277,16 @@
path = Views;
sourceTree = "";
};
+ 3132CDBD252522DA00542F6F /* IntentHandler */ = {
+ isa = PBXGroup;
+ children = (
+ 31986AA025252D9600261BF5 /* IntentHandler.entitlements */,
+ 3132CDBE252522DA00542F6F /* IntentHandler.swift */,
+ 3132CDC0252522DA00542F6F /* Info.plist */,
+ );
+ path = IntentHandler;
+ sourceTree = "";
+ };
315F5EBE24BA162600FED38F /* Frameworks */ = {
isa = PBXGroup;
children = (
@@ -233,6 +302,7 @@
31AECCFA24C7A54900E381E0 /* Core */,
310647CA24BD10D600E2DA90 /* Views */,
315F5EC424BA162600FED38F /* ViewStatsWidget.swift */,
+ 31E152FB2523AC62008DD6CD /* PiMonitorWidget.swift */,
31AECCC424C7A17400E381E0 /* PiStatsWidgets.swift */,
315F5EC624BA162800FED38F /* Assets.xcassets */,
315F5EC824BA162800FED38F /* Info.plist */,
@@ -259,6 +329,7 @@
3175EB6C24AE597A00E7B5FF /* PiStatsMobileTests */,
3175EB7724AE597A00E7B5FF /* PiStatsMobileUITests */,
315F5EC324BA162600FED38F /* PiStatsWidget */,
+ 3132CDBD252522DA00542F6F /* IntentHandler */,
315F5EBE24BA162600FED38F /* Frameworks */,
3175EB5924AE597900E7B5FF /* Products */,
);
@@ -271,6 +342,7 @@
3175EB6924AE597A00E7B5FF /* PiStatsMobileTests.xctest */,
3175EB7424AE597A00E7B5FF /* PiStatsMobileUITests.xctest */,
315F5EBD24BA162600FED38F /* PiStatsWidgetExtension.appex */,
+ 3132CDBC252522DA00542F6F /* IntentHandler.appex */,
);
name = Products;
sourceTree = "";
@@ -285,6 +357,7 @@
319F965B24B2658300B680D3 /* Views */,
317D481724B3ACCE00897EA9 /* Utils */,
317D481824B3ACDD00897EA9 /* Supporting Files */,
+ 315825B125275812001160EA /* PiholeIntents.intentdefinition */,
);
path = PiStatsMobile;
sourceTree = "";
@@ -322,6 +395,8 @@
317D482124B3D2F000897EA9 /* CodeScannerView.swift */,
3182E28F24BB6DA100BF53D9 /* UserDefaultExtensions.swift */,
319771AB24DB24BA0005E6BF /* ScannedPihole.swift */,
+ 317636A1252658F5005AED55 /* ViewUtils.swift */,
+ 313599D22527224300A12AC9 /* Constants.swift */,
);
path = Utils;
sourceTree = "";
@@ -369,11 +444,22 @@
isa = PBXGroup;
children = (
31AECCFB24C7A55A00E381E0 /* PiholeEntry.swift */,
+ 31986A9325252AC500261BF5 /* PiMonitorTimelineProvider.swift */,
31AECCFD24C7A57300E381E0 /* PiholeTimelineProvider.swift */,
);
path = Core;
sourceTree = "";
};
+ 31C1A5832526919300431C87 /* PiMonitor */ = {
+ isa = PBXGroup;
+ children = (
+ 31E153012523AD44008DD6CD /* PiMonitorWidgetView.swift */,
+ 317636A925265AB7005AED55 /* PiMonitorView.swift */,
+ 31C1A584252691A900431C87 /* PiMonitorStatusHeader.swift */,
+ );
+ path = PiMonitor;
+ sourceTree = "";
+ };
31F3DFE224B50E84001D4679 /* Piholes */ = {
isa = PBXGroup;
children = (
@@ -410,6 +496,27 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
+ 3132CDBB252522DA00542F6F /* IntentHandler */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 3132CDC4252522DA00542F6F /* Build configuration list for PBXNativeTarget "IntentHandler" */;
+ buildPhases = (
+ 3132CDB8252522DA00542F6F /* Sources */,
+ 3132CDB9252522DA00542F6F /* Frameworks */,
+ 3132CDBA252522DA00542F6F /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = IntentHandler;
+ packageProductDependencies = (
+ 311A3E67252527D9005464D2 /* SwiftHole */,
+ 311A3E69252527DC005464D2 /* PiMonitor */,
+ );
+ productName = IntentHandler;
+ productReference = 3132CDBC252522DA00542F6F /* IntentHandler.appex */;
+ productType = "com.apple.product-type.app-extension";
+ };
315F5EBC24BA162600FED38F /* PiStatsWidgetExtension */ = {
isa = PBXNativeTarget;
buildConfigurationList = 315F5ECF24BA162800FED38F /* Build configuration list for PBXNativeTarget "PiStatsWidgetExtension" */;
@@ -444,6 +551,7 @@
);
dependencies = (
315F5ECA24BA162800FED38F /* PBXTargetDependency */,
+ 3132CDC2252522DA00542F6F /* PBXTargetDependency */,
);
name = PiStatsMobile;
packageProductDependencies = (
@@ -499,6 +607,9 @@
LastSwiftUpdateCheck = 1200;
LastUpgradeCheck = 1200;
TargetAttributes = {
+ 3132CDBB252522DA00542F6F = {
+ CreatedOnToolsVersion = 12.0.1;
+ };
315F5EBC24BA162600FED38F = {
CreatedOnToolsVersion = 12.0;
};
@@ -537,11 +648,19 @@
3175EB6824AE597A00E7B5FF /* PiStatsMobileTests */,
3175EB7324AE597A00E7B5FF /* PiStatsMobileUITests */,
315F5EBC24BA162600FED38F /* PiStatsWidgetExtension */,
+ 3132CDBB252522DA00542F6F /* IntentHandler */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
+ 3132CDBA252522DA00542F6F /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
315F5EBB24BA162600FED38F /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -577,25 +696,52 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
+ 3132CDB8252522DA00542F6F /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 311A3E4A2525276A005464D2 /* Pihole.swift in Sources */,
+ 313599D52527224300A12AC9 /* Constants.swift in Sources */,
+ 311A3E1C25252504005464D2 /* Logger.swift in Sources */,
+ 311A3E2225252509005464D2 /* UserPreferences.swift in Sources */,
+ 311A3E4425252766005464D2 /* UserDefaultExtensions.swift in Sources */,
+ 311A3E282525250C005464D2 /* APIToken.swift in Sources */,
+ 311A3E2E2525250E005464D2 /* KeyChainPasswordItem.swift in Sources */,
+ 311A3E5625252775005464D2 /* UIConstants.swift in Sources */,
+ 3132CDBF252522DA00542F6F /* IntentHandler.swift in Sources */,
+ 315825AF25275812001160EA /* PiholeIntents.intentdefinition in Sources */,
+ 311A3E5C25252778005464D2 /* StatsItemType.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
315F5EB924BA162600FED38F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
315F5EDC24BA2D3200FED38F /* Logger.swift in Sources */,
+ 31C1A585252691A900431C87 /* PiMonitorStatusHeader.swift in Sources */,
315F5ED724BA2D0800FED38F /* Pihole.swift in Sources */,
315F5ED824BA2D0800FED38F /* PiholeDataProvider.swift in Sources */,
+ 317636A3252658F5005AED55 /* ViewUtils.swift in Sources */,
315F5EDA24BA2D0C00FED38F /* UserPreferences.swift in Sources */,
+ 3132CDCC252522F400542F6F /* IntentHandler.swift in Sources */,
+ 313599D42527224300A12AC9 /* Constants.swift in Sources */,
315F5EDB24BA2D2700FED38F /* KeyChainPasswordItem.swift in Sources */,
310647CC24BD10E900E2DA90 /* CircleBadgeStatus.swift in Sources */,
31AECCFE24C7A57300E381E0 /* PiholeTimelineProvider.swift in Sources */,
31AECCC524C7A17400E381E0 /* PiStatsWidgets.swift in Sources */,
+ 31986A9425252AC500261BF5 /* PiMonitorTimelineProvider.swift in Sources */,
+ 31E153102523AD69008DD6CD /* PiMonitorWidget.swift in Sources */,
3182E29124BB6DA100BF53D9 /* UserDefaultExtensions.swift in Sources */,
315F5ED924BA2D0C00FED38F /* APIToken.swift in Sources */,
310647D024BD120600E2DA90 /* PiStatsDisplayWidgetView.swift in Sources */,
315F5ED124BA1DF800FED38F /* StatsItemType.swift in Sources */,
+ 31E153072523AD61008DD6CD /* PiMonitorWidgetView.swift in Sources */,
315F5EC524BA162600FED38F /* ViewStatsWidget.swift in Sources */,
+ 317636AB25265AB7005AED55 /* PiMonitorView.swift in Sources */,
310647CE24BD115F00E2DA90 /* SmallStatsItem.swift in Sources */,
31AECCFC24C7A55A00E381E0 /* PiholeEntry.swift in Sources */,
+ 315825AE25275812001160EA /* PiholeIntents.intentdefinition in Sources */,
315F5ED424BA1E1500FED38F /* UIConstants.swift in Sources */,
310647D224BD13D700E2DA90 /* MediumStatsItem.swift in Sources */,
);
@@ -610,8 +756,10 @@
317D482224B3D2F000897EA9 /* CodeScannerView.swift in Sources */,
319B083024B53269008B3C2F /* APIToken.swift in Sources */,
31FC1DAA24B64BA900319E6F /* PiholeDataProviderListManager.swift in Sources */,
+ 313599D32527224300A12AC9 /* Constants.swift in Sources */,
3175EB5E24AE597900E7B5FF /* ContentView.swift in Sources */,
3102D30B24B7BC7900EC8120 /* SettingsView.swift in Sources */,
+ 315825AD25275812001160EA /* PiholeIntents.intentdefinition in Sources */,
311D0A7B24B0F613003F18DB /* StatsView.swift in Sources */,
311D0A7924B0F405003F18DB /* UIConstants.swift in Sources */,
319F965D24B265D200B680D3 /* PiholeStatsList.swift in Sources */,
@@ -622,6 +770,7 @@
31A4D54224B92D220033EF53 /* AppDelegate.swift in Sources */,
318087FE24BDB11F00EF5159 /* StatusHeaderView.swift in Sources */,
319B083124B53269008B3C2F /* UserPreferences.swift in Sources */,
+ 311A3E3425252544005464D2 /* IntentHandler.swift in Sources */,
31DB2A2A2517B1C200D67F9E /* CustomDurationsView.swift in Sources */,
31115BC824B29A1700C91212 /* PiholeSetupView.swift in Sources */,
319B082924B5321C008B3C2F /* PiholeDataProvider.swift in Sources */,
@@ -630,6 +779,7 @@
31A9298A2517CF4B00FA9F50 /* CountdownPickerViewRepresentable.swift in Sources */,
3182E29024BB6DA100BF53D9 /* UserDefaultExtensions.swift in Sources */,
31A9297F2517CF0000FA9F50 /* CountdownPickerView.swift in Sources */,
+ 317636A2252658F5005AED55 /* ViewUtils.swift in Sources */,
3175EB5C24AE597900E7B5FF /* PiStatsMobileApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -653,6 +803,11 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
+ 3132CDC2252522DA00542F6F /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 3132CDBB252522DA00542F6F /* IntentHandler */;
+ targetProxy = 3132CDC1252522DA00542F6F /* PBXContainerItemProxy */;
+ };
315F5ECA24BA162800FED38F /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 315F5EBC24BA162600FED38F /* PiStatsWidgetExtension */;
@@ -671,6 +826,16 @@
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
+ 315825B125275812001160EA /* PiholeIntents.intentdefinition */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 315825B025275812001160EA /* Base */,
+ 315825B825275832001160EA /* en */,
+ 3171772B25277BBA001B1CAB /* pt-BR */,
+ );
+ name = PiholeIntents.intentdefinition;
+ sourceTree = "";
+ };
317D481F24B3ADE200897EA9 /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
@@ -683,6 +848,50 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
+ 3132CDC5252522DA00542F6F /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_ENTITLEMENTS = IntentHandler/IntentHandler.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 13;
+ DEVELOPMENT_TEAM = B2RUA6XMHC;
+ INFOPLIST_FILE = IntentHandler/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ MARKETING_VERSION = 2.0.0;
+ PRODUCT_BUNDLE_IDENTIFIER = dev.bunn.PiStatsMobile.IntentHandler;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 3132CDC6252522DA00542F6F /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_ENTITLEMENTS = IntentHandler/IntentHandler.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 13;
+ DEVELOPMENT_TEAM = B2RUA6XMHC;
+ INFOPLIST_FILE = IntentHandler/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ MARKETING_VERSION = 2.0.0;
+ PRODUCT_BUNDLE_IDENTIFIER = dev.bunn.PiStatsMobile.IntentHandler;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
315F5ECD24BA162800FED38F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -690,7 +899,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = PiStatsWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 10;
+ CURRENT_PROJECT_VERSION = 13;
DEVELOPMENT_TEAM = B2RUA6XMHC;
INFOPLIST_FILE = PiStatsWidget/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -698,7 +907,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 1.2.1;
+ MARKETING_VERSION = 2.0.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.bunn.PiStatsMobile.PiStatsWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@@ -714,7 +923,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = PiStatsWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 10;
+ CURRENT_PROJECT_VERSION = 13;
DEVELOPMENT_TEAM = B2RUA6XMHC;
INFOPLIST_FILE = PiStatsWidget/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -722,7 +931,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 1.2.1;
+ MARKETING_VERSION = 2.0.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.bunn.PiStatsMobile.PiStatsWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@@ -855,7 +1064,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = PiStatsMobile/PiStatsMobile.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 10;
+ CURRENT_PROJECT_VERSION = 13;
DEVELOPMENT_ASSET_PATHS = "\"PiStatsMobile/Supporting Files/Preview Content\"";
DEVELOPMENT_TEAM = B2RUA6XMHC;
ENABLE_PREVIEWS = YES;
@@ -865,11 +1074,11 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.2.1;
+ MARKETING_VERSION = 2.0.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.bunn.PiStatsMobile;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = 1;
+ TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
@@ -881,7 +1090,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = PiStatsMobile/PiStatsMobile.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 10;
+ CURRENT_PROJECT_VERSION = 13;
DEVELOPMENT_ASSET_PATHS = "\"PiStatsMobile/Supporting Files/Preview Content\"";
DEVELOPMENT_TEAM = B2RUA6XMHC;
ENABLE_PREVIEWS = YES;
@@ -891,11 +1100,11 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.2.1;
+ MARKETING_VERSION = 2.0.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.bunn.PiStatsMobile;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = 1;
+ TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
@@ -986,6 +1195,15 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
+ 3132CDC4252522DA00542F6F /* Build configuration list for PBXNativeTarget "IntentHandler" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 3132CDC5252522DA00542F6F /* Debug */,
+ 3132CDC6252522DA00542F6F /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
315F5ECF24BA162800FED38F /* Build configuration list for PBXNativeTarget "PiStatsWidgetExtension" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -1053,6 +1271,16 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
+ 311A3E67252527D9005464D2 /* SwiftHole */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 315F5EDD24BA2E3900FED38F /* XCRemoteSwiftPackageReference "SwiftHole" */;
+ productName = SwiftHole;
+ };
+ 311A3E69252527DC005464D2 /* PiMonitor */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 3137D20B24D217CD0021ACD3 /* XCRemoteSwiftPackageReference "PiMonitor" */;
+ productName = PiMonitor;
+ };
3137D20C24D217CD0021ACD3 /* PiMonitor */ = {
isa = XCSwiftPackageProductDependency;
package = 3137D20B24D217CD0021ACD3 /* XCRemoteSwiftPackageReference "PiMonitor" */;
diff --git a/PiStatsMobile/PiStatsMobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/PiStatsMobile/PiStatsMobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 5ab032c..40d34dc 100644
--- a/PiStatsMobile/PiStatsMobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/PiStatsMobile/PiStatsMobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -6,8 +6,8 @@
"repositoryURL": "https://github.com/Bunn/PiMonitor",
"state": {
"branch": null,
- "revision": "a89879d3fb601871361dfc1f6b9d6cd7c4c42a11",
- "version": "0.0.4"
+ "revision": "c0dbc72b466e437de94041948c576dc318e9af8a",
+ "version": "0.0.5"
}
},
{
diff --git a/PiStatsMobile/PiStatsMobile/Base.lproj/PiholeIntents.intentdefinition b/PiStatsMobile/PiStatsMobile/Base.lproj/PiholeIntents.intentdefinition
new file mode 100644
index 0000000..c3c1d86
--- /dev/null
+++ b/PiStatsMobile/PiStatsMobile/Base.lproj/PiholeIntents.intentdefinition
@@ -0,0 +1,167 @@
+
+
+
+
+ INEnums
+
+ INIntentDefinitionModelVersion
+ 1.2
+ INIntentDefinitionNamespace
+ pwIO52
+ INIntentDefinitionSystemVersion
+ 19H2
+ INIntentDefinitionToolsBuildVersion
+ 12A7300
+ INIntentDefinitionToolsVersion
+ 12.0.1
+ INIntents
+
+
+ INIntentCategory
+ information
+ INIntentDescription
+ Select Pihole to display stats
+ INIntentDescriptionID
+ 9VVPSk
+ INIntentEligibleForWidgets
+
+ INIntentIneligibleForSuggestions
+
+ INIntentLastParameterTag
+ 2
+ INIntentName
+ SelectPihole
+ INIntentParameters
+
+
+ INIntentParameterConfigurable
+
+ INIntentParameterDisplayName
+ Pihole
+ INIntentParameterDisplayNameID
+ PiJeMO
+ INIntentParameterDisplayPriority
+ 1
+ INIntentParameterName
+ pihole
+ INIntentParameterObjectType
+ PiholeIntent
+ INIntentParameterObjectTypeNamespace
+ pwIO52
+ INIntentParameterPromptDialogs
+
+
+ INIntentParameterPromptDialogCustom
+
+ INIntentParameterPromptDialogType
+ Configuration
+
+
+ INIntentParameterPromptDialogCustom
+
+ INIntentParameterPromptDialogType
+ Primary
+
+
+ INIntentParameterSupportsDynamicEnumeration
+
+ INIntentParameterTag
+ 2
+ INIntentParameterType
+ Object
+
+
+ INIntentResponse
+
+ INIntentResponseCodes
+
+
+ INIntentResponseCodeName
+ success
+ INIntentResponseCodeSuccess
+
+
+
+ INIntentResponseCodeName
+ failure
+
+
+
+ INIntentTitle
+ Select Pihole
+ INIntentTitleID
+ XxlkUM
+ INIntentType
+ Custom
+ INIntentVerb
+ View
+
+
+ INTypes
+
+
+ INTypeDisplayName
+ Pihole Intent
+ INTypeDisplayNameID
+ YlcCql
+ INTypeLastPropertyTag
+ 100
+ INTypeName
+ PiholeIntent
+ INTypeProperties
+
+
+ INTypePropertyDefault
+
+ INTypePropertyDisplayPriority
+ 1
+ INTypePropertyName
+ identifier
+ INTypePropertyTag
+ 1
+ INTypePropertyType
+ String
+
+
+ INTypePropertyDefault
+
+ INTypePropertyDisplayPriority
+ 2
+ INTypePropertyName
+ displayString
+ INTypePropertyTag
+ 2
+ INTypePropertyType
+ String
+
+
+ INTypePropertyDefault
+
+ INTypePropertyDisplayPriority
+ 3
+ INTypePropertyName
+ pronunciationHint
+ INTypePropertyTag
+ 3
+ INTypePropertyType
+ String
+
+
+ INTypePropertyDefault
+
+ INTypePropertyDisplayPriority
+ 4
+ INTypePropertyName
+ alternativeSpeakableMatches
+ INTypePropertySupportsMultipleValues
+
+ INTypePropertyTag
+ 4
+ INTypePropertyType
+ SpeakableString
+
+
+
+
+
+
diff --git a/PiStatsMobile/PiStatsMobile/PiStatsMobileApp.swift b/PiStatsMobile/PiStatsMobile/PiStatsMobileApp.swift
index 6856f6d..390581c 100644
--- a/PiStatsMobile/PiStatsMobile/PiStatsMobileApp.swift
+++ b/PiStatsMobile/PiStatsMobile/PiStatsMobileApp.swift
@@ -12,16 +12,6 @@ final class DataModel: ObservableObject {
let piholeProviderListManager = PiholeDataProviderListManager()
let userPreferences = UserPreferences.shared
private var offlineBadgeCancellable: AnyCancellable?
-
- init() {
- setupCancellables()
- }
-
- private func setupCancellables() {
- offlineBadgeCancellable = userPreferences.$displayIconBadgeForOfflinePiholes.receive(on: DispatchQueue.main).sink { [weak self] value in
- self?.piholeProviderListManager.shouldUpdateIconBadgeWithOfflinePiholes = value
- }
- }
}
@main
@@ -35,6 +25,5 @@ struct PiStatsMobileApp: App {
.environmentObject(dataModel.piholeProviderListManager)
.environmentObject(dataModel.userPreferences)
}
-
}
}
diff --git a/PiStatsMobile/PiStatsMobile/Supporting Files/Info.plist b/PiStatsMobile/PiStatsMobile/Supporting Files/Info.plist
index 7896edc..fe3d2a3 100644
--- a/PiStatsMobile/PiStatsMobile/Supporting Files/Info.plist
+++ b/PiStatsMobile/PiStatsMobile/Supporting Files/Info.plist
@@ -35,6 +35,10 @@
NSCameraUsageDescription
Camera permission is necessary to read the API Token QRCode
+ NSUserActivityTypes
+
+ SelectPiholeIntent
+
UIApplicationSceneManifest
UIApplicationSupportsMultipleScenes
diff --git a/PiStatsMobile/PiStatsMobile/Utils/Constants.swift b/PiStatsMobile/PiStatsMobile/Utils/Constants.swift
new file mode 100644
index 0000000..97286c9
--- /dev/null
+++ b/PiStatsMobile/PiStatsMobile/Utils/Constants.swift
@@ -0,0 +1,12 @@
+//
+// Constants.swift
+// PiStatsMobile
+//
+// Created by Fernando Bunn on 02/10/2020.
+//
+
+import Foundation
+
+struct Constants {
+ static let appGroup = "group.dev.bunn.PiStatsMobile"
+}
diff --git a/PiStatsMobile/PiStatsMobile/Utils/DisableDurationManager.swift b/PiStatsMobile/PiStatsMobile/Utils/DisableDurationManager.swift
index 28f6b94..788403b 100644
--- a/PiStatsMobile/PiStatsMobile/Utils/DisableDurationManager.swift
+++ b/PiStatsMobile/PiStatsMobile/Utils/DisableDurationManager.swift
@@ -72,7 +72,7 @@ class DisableDurationManager: ObservableObject {
}
func addNewItem() {
- items.append(DisableTimeItem(timeInterval: 30))
+ items.append(DisableTimeItem(timeInterval: 0))
setupCancellables()
}
diff --git a/PiStatsMobile/PiStatsMobile/Utils/UserDefaultExtensions.swift b/PiStatsMobile/PiStatsMobile/Utils/UserDefaultExtensions.swift
index 2ba22cb..e891387 100644
--- a/PiStatsMobile/PiStatsMobile/Utils/UserDefaultExtensions.swift
+++ b/PiStatsMobile/PiStatsMobile/Utils/UserDefaultExtensions.swift
@@ -10,7 +10,6 @@ import Foundation
extension UserDefaults {
static func shared() -> UserDefaults {
- let appGroup = "group.dev.bunn.PiStatsMobile"
- return UserDefaults(suiteName: appGroup)!
+ return UserDefaults(suiteName: Constants.appGroup)!
}
}
diff --git a/PiStatsMobile/PiStatsMobile/Utils/ViewUtils.swift b/PiStatsMobile/PiStatsMobile/Utils/ViewUtils.swift
new file mode 100644
index 0000000..0edfc65
--- /dev/null
+++ b/PiStatsMobile/PiStatsMobile/Utils/ViewUtils.swift
@@ -0,0 +1,24 @@
+//
+// ViewUtils.swift
+// PiStatsMobile
+//
+// Created by Fernando Bunn on 01/10/2020.
+//
+
+import SwiftUI
+
+struct ViewUtils {
+
+ static func shieldStatusImageForDataProvider(_ dataProvider: PiholeDataProvider) -> some View {
+ if dataProvider.hasErrorMessages || dataProvider.status == .enabledAndDisabled {
+ return Image(systemName: UIConstants.SystemImages.piholeStatusWarning)
+ .foregroundColor(UIConstants.Colors.statusWarning)
+ } else if dataProvider.status == .allEnabled {
+ return Image(systemName: UIConstants.SystemImages.piholeStatusOnline)
+ .foregroundColor(UIConstants.Colors.statusOnline)
+ } else {
+ return Image(systemName: UIConstants.SystemImages.piholeStatusOffline)
+ .foregroundColor(UIConstants.Colors.statusOffline)
+ }
+ }
+}
diff --git a/PiStatsMobile/PiStatsMobile/Views/Piholes/MetricsView.swift b/PiStatsMobile/PiStatsMobile/Views/Piholes/MetricsView.swift
index f57706d..8372414 100644
--- a/PiStatsMobile/PiStatsMobile/Views/Piholes/MetricsView.swift
+++ b/PiStatsMobile/PiStatsMobile/Views/Piholes/MetricsView.swift
@@ -8,7 +8,7 @@
import SwiftUI
import PiMonitor
-struct MetricItem: Identifiable {
+fileprivate struct MetricItem: Identifiable {
let value: String
let systemName: String
let helpText: String
@@ -19,7 +19,7 @@ struct MetricsView: View {
@ObservedObject var dataProvider: PiholeDataProvider
private let imageSize: CGFloat = 15
- func getMetricItems() -> [MetricItem] {
+ private func getMetricItems() -> [MetricItem] {
return [
MetricItem(value: dataProvider.temperature, systemName: UIConstants.SystemImages.metricTemperature, helpText: "Raspberry Pi temperature"),
MetricItem(value: dataProvider.uptime, systemName: UIConstants.SystemImages.metricUptime, helpText: "Raspberry Pi uptime"),
diff --git a/PiStatsMobile/PiStatsMobile/Views/Piholes/PiholeStatsList.swift b/PiStatsMobile/PiStatsMobile/Views/Piholes/PiholeStatsList.swift
index 951597e..822f899 100644
--- a/PiStatsMobile/PiStatsMobile/Views/Piholes/PiholeStatsList.swift
+++ b/PiStatsMobile/PiStatsMobile/Views/Piholes/PiholeStatsList.swift
@@ -20,6 +20,8 @@ final class StatsListConfig: ObservableObject {
struct PiholeStatsList: View {
@StateObject private var viewModel = StatsListConfig()
+ @Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass?
+ @Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass?
@EnvironmentObject private var userPreferences: UserPreferences
@EnvironmentObject private var piholeProviderListManager: PiholeDataProviderListManager
@Environment(\.scenePhase) private var phase
@@ -27,37 +29,76 @@ struct PiholeStatsList: View {
I want to make this logic on the @App but it seems there's a bug on Beta2
More info here: https://twitter.com/fcbunn/status/1281905574695886848?s=21
*/
-
+
+ private let columns = [
+ GridItem(.flexible()),
+ GridItem(.flexible())
+ ]
+
+ private func regularSetup() -> some View {
+ Group {
+ LazyVGrid(columns: columns, alignment: .center, spacing: 10) {
+ ForEach(piholeProviderListManager.providerList, id: \.id) { provider in
+ StatsView(dataProvider: provider)
+ .onTapGesture() {
+ viewModel.openPiholeSetup(provider.piholes.first)
+ }
+ }
+ if piholeProviderListManager.isEmpty == false {
+ addPiholeButton()
+ }
+ }
+ if piholeProviderListManager.isEmpty == true {
+ addPiholeButton()
+ }
+ }
+ }
+
+ private func addPiholeButton() -> some View {
+ Button(action: {
+ viewModel.openPiholeSetup()
+ }, label: {
+ ZStack {
+ Circle()
+ .frame(width: UIConstants.Geometry.addPiholeButtonHeight, height: UIConstants.Geometry.addPiholeButtonHeight, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
+ Image(systemName: UIConstants.SystemImages.addPiholeButton)
+ .foregroundColor(.white)
+ .font(.largeTitle)
+ }
+ })
+ .shadow(radius: UIConstants.Geometry.shadowRadius)
+ .padding()
+ }
+
+ private func compactSetup() -> some View {
+ Group {
+ ForEach(piholeProviderListManager.providerList, id: \.id) { provider in
+ StatsView(dataProvider: provider)
+ .onTapGesture() {
+ viewModel.openPiholeSetup(provider.piholes.first)
+ }
+ }
+ addPiholeButton()
+ }
+ }
+
var body: some View {
ZStack {
Color(.systemGroupedBackground)
.edgesIgnoringSafeArea(.all)
ScrollView {
+
if userPreferences.displayAllPiholes {
StatsView(dataProvider: piholeProviderListManager.allPiholesProvider)
Divider()
}
- ForEach(piholeProviderListManager.providerList, id: \.id) { provider in
- StatsView(dataProvider: provider)
- .onTapGesture() {
- viewModel.openPiholeSetup(provider.piholes.first)
- }
+ if horizontalSizeClass == .regular && verticalSizeClass == .regular {
+ regularSetup()
+ } else {
+ compactSetup()
}
- Button(action: {
- viewModel.openPiholeSetup()
- }, label: {
- ZStack {
- Circle()
- .frame(width: UIConstants.Geometry.addPiholeButtonHeight, height: UIConstants.Geometry.addPiholeButtonHeight, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
- Image(systemName: UIConstants.SystemImages.addPiholeButton)
- .foregroundColor(.white)
- .font(.largeTitle)
- }
- })
- .shadow(radius: UIConstants.Geometry.shadowRadius)
- .padding()
if piholeProviderListManager.isEmpty {
Text(UIConstants.Strings.addFirstPiholeCaption)
}
@@ -76,7 +117,6 @@ struct PiholeStatsList: View {
case .background:
WidgetCenter.shared.reloadAllTimelines()
@unknown default: break
- // Fallback for future cases
}
}
}
diff --git a/PiStatsMobile/PiStatsMobile/en.lproj/PiholeIntents.strings b/PiStatsMobile/PiStatsMobile/en.lproj/PiholeIntents.strings
new file mode 100644
index 0000000..b9e8e72
--- /dev/null
+++ b/PiStatsMobile/PiStatsMobile/en.lproj/PiholeIntents.strings
@@ -0,0 +1,7 @@
+"PiJeMO" = "Pihole";
+
+"XxlkUM" = "Select Pihole";
+
+"YlcCql" = "Pihole Intent";
+
+"9VVPSk" = "Select Pihole to display stats";
diff --git a/PiStatsMobile/PiStatsMobile/pt-BR.lproj/PiholeIntents.strings b/PiStatsMobile/PiStatsMobile/pt-BR.lproj/PiholeIntents.strings
new file mode 100644
index 0000000..b9e8e72
--- /dev/null
+++ b/PiStatsMobile/PiStatsMobile/pt-BR.lproj/PiholeIntents.strings
@@ -0,0 +1,7 @@
+"PiJeMO" = "Pihole";
+
+"XxlkUM" = "Select Pihole";
+
+"YlcCql" = "Pihole Intent";
+
+"9VVPSk" = "Select Pihole to display stats";
diff --git a/PiStatsMobile/PiStatsWidget/Assets.xcassets/PiMonitorWidgetBackground.colorset/Contents.json b/PiStatsMobile/PiStatsWidget/Assets.xcassets/PiMonitorWidgetBackground.colorset/Contents.json
new file mode 100644
index 0000000..92ce3e6
--- /dev/null
+++ b/PiStatsMobile/PiStatsWidget/Assets.xcassets/PiMonitorWidgetBackground.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "1.000",
+ "green" : "1.000",
+ "red" : "1.000"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "display-p3",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.110",
+ "green" : "0.102",
+ "red" : "0.098"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/PiStatsMobile/PiStatsWidget/Core/PiMonitorTimelineProvider.swift b/PiStatsMobile/PiStatsWidget/Core/PiMonitorTimelineProvider.swift
new file mode 100644
index 0000000..5930185
--- /dev/null
+++ b/PiStatsMobile/PiStatsWidget/Core/PiMonitorTimelineProvider.swift
@@ -0,0 +1,65 @@
+//
+// PiMonitorTimelineProvider.swift
+// PiStatsMobile
+//
+// Created by Fernando Bunn on 30/09/2020.
+//
+
+
+import WidgetKit
+import Foundation
+import os.log
+
+struct PiMonitorTimelineProvider: IntentTimelineProvider {
+ typealias Intent = SelectPiholeIntent
+
+ typealias Entry = PiholeEntry
+ private static let fakePihole = PiholeDataProvider.previewData()
+ private let log = Logger().osLog(describing: PiMonitorTimelineProvider.self)
+
+ func placeholder(in context: Context) -> PiholeEntry {
+ PiholeEntry(piholeDataProvider: PiholeDataProvider.previewData(), date: Date(), widgetFamily: .systemSmall)
+ }
+
+ func getSnapshot(for configuration: Intent, in context: Context, completion: @escaping (PiholeEntry) -> Void) {
+ let entry = PiholeEntry(piholeDataProvider: PiMonitorTimelineProvider.fakePihole, date: Date(), widgetFamily: context.family)
+ completion(entry)
+ }
+
+
+ func getTimeline(for configuration: SelectPiholeIntent, in context: Context, completion: @escaping (Timeline) -> Void) {
+ let currentDate = Date()
+ let refreshDate = Calendar.current.date(byAdding: .minute, value: 5, to: currentDate)!
+
+ if let identifier = configuration.pihole?.identifier,
+ let piholeUUID = UUID(uuidString: identifier),
+ let pihole = Pihole.restore(piholeUUID) {
+ let provider = PiholeDataProvider(piholes: [pihole])
+ os_log("get timeline called")
+ let dispatchGroup = DispatchGroup()
+
+ dispatchGroup.enter()
+ provider.fetchSummaryData {
+ dispatchGroup.leave()
+ }
+
+ dispatchGroup.enter()
+ provider.fetchMetricsData {
+ dispatchGroup.leave()
+ }
+
+ dispatchGroup.notify(queue: DispatchQueue.main) {
+ let entry = PiholeEntry(piholeDataProvider: provider, date: Date(), widgetFamily: context.family)
+ let timeline = Timeline(entries: [entry], policy: .after(refreshDate))
+ completion(timeline)
+ }
+
+ } else {
+ os_log("No pihole found/selected")
+ let provider = PiholeDataProvider(piholes: [])
+ let entry = PiholeEntry(piholeDataProvider: provider, date: Date(), widgetFamily: context.family)
+ let timeline = Timeline(entries: [entry], policy: .after(refreshDate))
+ completion(timeline)
+ }
+ }
+}
diff --git a/PiStatsMobile/PiStatsWidget/PiMonitorWidget.swift b/PiStatsMobile/PiStatsWidget/PiMonitorWidget.swift
new file mode 100644
index 0000000..6388d8f
--- /dev/null
+++ b/PiStatsMobile/PiStatsWidget/PiMonitorWidget.swift
@@ -0,0 +1,49 @@
+//
+// PiMonitorWidget.swift
+// PiStatsMobile
+//
+// Created by Fernando Bunn on 29/09/2020.
+//
+
+import WidgetKit
+import SwiftUI
+
+private struct PlaceholderView : View {
+ var body: some View {
+
+ PiMonitorView(provider: PiholeDataProvider.previewData(), shouldDisplayStats: false ).redacted(reason: .placeholder)
+ }
+}
+
+struct PiMonitorWidget: Widget {
+ private let kind: String = "PiMonitorWidget"
+ public var body: some WidgetConfiguration {
+
+ IntentConfiguration(
+ kind: "dev.bunn.PiStatsMobile.SelectPiholeIntent",
+ intent: SelectPiholeIntent.self,
+ provider: PiMonitorTimelineProvider()
+ ) { entry in
+ PiMonitorWidgetView(entry: entry)
+ }
+ .configurationDisplayName("Pi Monitor")
+ .description("Display metrics for your Raspberry Pi")
+ .supportedFamilies([.systemSmall, .systemMedium])
+ }
+}
+
+
+struct PiMonitorWidget_Previews: PreviewProvider {
+ static var previews: some View {
+
+ PiMonitorWidgetView(entry: PiholeEntry(piholeDataProvider: PiholeDataProvider.previewData(), date: Date(), widgetFamily: .systemSmall))
+ .previewContext(WidgetPreviewContext(family: .systemSmall))
+
+ PiMonitorWidgetView(entry: PiholeEntry(piholeDataProvider: PiholeDataProvider.previewData(), date: Date(), widgetFamily: .systemMedium))
+ .previewContext(WidgetPreviewContext(family: .systemMedium))
+
+ PlaceholderView()
+ .previewContext(WidgetPreviewContext(family: .systemSmall))
+ }
+}
+
diff --git a/PiStatsMobile/PiStatsWidget/PiStatsWidgets.swift b/PiStatsMobile/PiStatsWidget/PiStatsWidgets.swift
index c55cb53..6a3d1b4 100644
--- a/PiStatsMobile/PiStatsWidget/PiStatsWidgets.swift
+++ b/PiStatsMobile/PiStatsWidget/PiStatsWidgets.swift
@@ -13,6 +13,6 @@ struct PiStatsWidgets: WidgetBundle {
@WidgetBundleBuilder
var body: some Widget {
ViewStatsWidget()
- //ToggleStatusWidget()
+ PiMonitorWidget()
}
}
diff --git a/PiStatsMobile/PiStatsWidget/ViewStatsWidget.swift b/PiStatsMobile/PiStatsWidget/ViewStatsWidget.swift
index 726280c..3a920a1 100644
--- a/PiStatsMobile/PiStatsWidget/ViewStatsWidget.swift
+++ b/PiStatsMobile/PiStatsWidget/ViewStatsWidget.swift
@@ -29,9 +29,7 @@ struct ViewStatsWidget: Widget {
public var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: PiholeTimelineProvider()) { entry in
PiStatsDisplayWidgetView(entry: entry)
-
}
-
.configurationDisplayName("Pi Stats")
.description("Display the status of your pi-holes")
.supportedFamilies([.systemSmall, .systemMedium])
diff --git a/PiStatsMobile/PiStatsWidget/Views/CircleBadgeStatus.swift b/PiStatsMobile/PiStatsWidget/Views/CircleBadgeStatus.swift
index e2a1e63..6819e7e 100644
--- a/PiStatsMobile/PiStatsWidget/Views/CircleBadgeStatus.swift
+++ b/PiStatsMobile/PiStatsWidget/Views/CircleBadgeStatus.swift
@@ -16,22 +16,9 @@ struct CircleBadgeStatus: View {
.foregroundColor(.white)
.frame(width: circleSize, height: circleSize)
- imageForDataProvider(dataProvider)
+ ViewUtils.shieldStatusImageForDataProvider(dataProvider)
.font(.title2)
}
-
- func imageForDataProvider(_ dataProvider: PiholeDataProvider) -> some View {
- if dataProvider.hasErrorMessages {
- return Image(systemName: UIConstants.SystemImages.piholeStatusWarning)
- .foregroundColor(UIConstants.Colors.statusWarning)
- } else if dataProvider.status == .allEnabled {
- return Image(systemName: UIConstants.SystemImages.piholeStatusOnline)
- .foregroundColor(UIConstants.Colors.statusOnline)
- } else {
- return Image(systemName: UIConstants.SystemImages.piholeStatusOffline)
- .foregroundColor(UIConstants.Colors.statusOffline)
- }
- }
}
struct CircleBadgeStatus_Previews: PreviewProvider {
diff --git a/PiStatsMobile/PiStatsWidget/Views/PiMonitor/PiMonitorStatusHeader.swift b/PiStatsMobile/PiStatsWidget/Views/PiMonitor/PiMonitorStatusHeader.swift
new file mode 100644
index 0000000..98e5a05
--- /dev/null
+++ b/PiStatsMobile/PiStatsWidget/Views/PiMonitor/PiMonitorStatusHeader.swift
@@ -0,0 +1,32 @@
+//
+// PiMonitorStatusHeader.swift
+// PiStatsWidgetExtension
+//
+// Created by Fernando Bunn on 01/10/2020.
+//
+
+import SwiftUI
+
+struct PiMonitorStatusHeader: View {
+ var provider: PiholeDataProvider
+
+ var body: some View {
+ VStack (alignment:.leading) {
+ Label(title: {
+ Text(provider.name)
+ }, icon: {
+ ViewUtils.shieldStatusImageForDataProvider(provider)
+ })
+ .minimumScaleFactor(0.75)
+ .font(Font.headline.weight(.bold))
+ Divider()
+ Spacer()
+ }
+ }
+}
+
+struct PiMonitorStatusHeader_Previews: PreviewProvider {
+ static var previews: some View {
+ PiMonitorStatusHeader(provider: PiholeDataProvider.previewData())
+ }
+}
diff --git a/PiStatsMobile/PiStatsWidget/Views/PiMonitor/PiMonitorView.swift b/PiStatsMobile/PiStatsWidget/Views/PiMonitor/PiMonitorView.swift
new file mode 100644
index 0000000..371eaaa
--- /dev/null
+++ b/PiStatsMobile/PiStatsWidget/Views/PiMonitor/PiMonitorView.swift
@@ -0,0 +1,104 @@
+//
+// PiMonitorView.swift
+// PiStatsMobile
+//
+// Created by Fernando Bunn on 01/10/2020.
+//
+
+import SwiftUI
+import WidgetKit
+
+fileprivate struct ListItem : Identifiable{
+ let id = UUID()
+ let text: String
+ let systemImage: String
+ let color: Color
+}
+struct PiMonitorStatsView: View {
+ let imageSize: CGFloat = 15
+ fileprivate let listItems: [ListItem]
+
+ var body: some View {
+ VStack (alignment:.leading, spacing: 6.0) {
+
+ ForEach(listItems) { item in
+ Label(title: {
+ Text(item.text)
+ }, icon: {
+ Image(systemName: item.systemImage)
+ .frame(width: imageSize, height: imageSize)
+ })
+ .font(Font.body.weight(.medium))
+ .minimumScaleFactor(0.80)
+ .foregroundColor(item.color)
+ }
+ }
+ }
+}
+
+struct PiMonitorView: View {
+ var provider: PiholeDataProvider
+ let imageSize: CGFloat = 15
+ let shouldDisplayStats: Bool
+
+ var body: some View {
+ VStack (alignment:.leading) {
+ PiMonitorStatusHeader(provider: provider)
+
+ HStack {
+ PiMonitorStatsView(listItems: getMetricListItems(provider))
+ .font(Font.body.weight(.semibold))
+ .minimumScaleFactor(0.89)
+
+ if shouldDisplayStats {
+ Spacer()
+
+ PiMonitorStatsView(listItems: getStatsListItems(provider))
+ }
+ }
+ }
+ .frame(
+ maxWidth: .infinity,
+ maxHeight: .infinity,
+ alignment: .topLeading
+ ).padding()
+ .font(.headline)
+ }
+
+ private func getMetricListItems(_ provider: PiholeDataProvider) -> [ListItem] {
+ return [
+
+ ListItem(text: provider.memoryUsage, systemImage: UIConstants.SystemImages.metricMemoryUsage, color: UIConstants.Colors.totalQueries),
+
+ ListItem(text: provider.uptime, systemImage: UIConstants.SystemImages.metricUptime, color: UIConstants.Colors.queriesBlocked),
+
+ ListItem(text: provider.temperature, systemImage: UIConstants.SystemImages.metricTemperature, color: UIConstants.Colors.domainsOnBlocklist),
+
+ ListItem(text: provider.loadAverage, systemImage: UIConstants.SystemImages.metricLoadAverage, color: UIConstants.Colors.percentBlocked),
+ ]
+ }
+
+ private func getStatsListItems(_ provider: PiholeDataProvider) -> [ListItem] {
+ return [
+ ListItem(text: provider.totalQueries, systemImage: UIConstants.SystemImages.totalQueries, color: UIConstants.Colors.totalQueries),
+
+ ListItem(text: provider.queriesBlocked, systemImage: UIConstants.SystemImages.queriesBlocked, color: UIConstants.Colors.queriesBlocked),
+
+ ListItem(text: provider.domainsOnBlocklist, systemImage: UIConstants.SystemImages.domainsOnBlockList, color: UIConstants.Colors.domainsOnBlocklist),
+
+ ListItem(text: provider.percentBlocked, systemImage: UIConstants.SystemImages.percentBlocked, color: UIConstants.Colors.percentBlocked),
+ ]
+ }
+}
+
+struct PiMonitorView_Previews: PreviewProvider {
+ static var previews: some View {
+ PiMonitorView(provider: PiholeDataProvider.previewData(), shouldDisplayStats: true)
+ .previewContext(WidgetPreviewContext(family: .systemMedium))
+
+ PiMonitorView(provider: PiholeDataProvider.previewData(), shouldDisplayStats: false)
+ .previewContext(WidgetPreviewContext(family: .systemSmall))
+
+
+ }
+}
diff --git a/PiStatsMobile/PiStatsWidget/Views/PiMonitor/PiMonitorWidgetView.swift b/PiStatsMobile/PiStatsWidget/Views/PiMonitor/PiMonitorWidgetView.swift
new file mode 100644
index 0000000..36d87ad
--- /dev/null
+++ b/PiStatsMobile/PiStatsWidget/Views/PiMonitor/PiMonitorWidgetView.swift
@@ -0,0 +1,49 @@
+//
+// PiMonitorWidgetView.swift
+// PiStatsMobile
+//
+// Created by Fernando Bunn on 29/09/2020.
+//
+
+import SwiftUI
+import WidgetKit
+
+struct PiMonitorWidgetView: View {
+ var entry: PiholeEntry
+ var shouldDisplayStats: Bool {
+ entry.widgetFamily == .systemMedium
+ }
+
+ var body: some View {
+ ZStack {
+ UIConstants.Colors.piMonitorWidgetBackground
+
+ if entry.piholeDataProvider.piholes.count == 0 {
+ PiMonitorView(provider: PiholeDataProvider.previewData() , shouldDisplayStats: shouldDisplayStats).redacted(reason: .placeholder)
+ } else if entry.piholeDataProvider.canDisplayMetrics == false {
+ VStack (spacing: 10) {
+ Image(systemName: UIConstants.SystemImages.piholeSetupMonitor)
+ .foregroundColor(UIConstants.Colors.domainsOnBlocklist)
+
+ Text("\(UIConstants.Strings.Widget.piholeNotEnabledOn) \(entry.piholeDataProvider.name)")
+ .multilineTextAlignment(.center)
+ }
+ .font(Font.headline.weight(.semibold))
+ .padding()
+ }
+ else {
+ PiMonitorView(provider: entry.piholeDataProvider, shouldDisplayStats: shouldDisplayStats)
+ }
+ }
+ }
+}
+
+struct PiMonitorWidgetView_Previews: PreviewProvider {
+ static var previews: some View {
+ PiMonitorWidgetView(entry: PiholeEntry.init(piholeDataProvider: PiholeDataProvider.previewData(), date: Date(), widgetFamily: .systemMedium))
+ .previewContext(WidgetPreviewContext(family: .systemMedium))
+
+ PiMonitorWidgetView(entry: PiholeEntry.init(piholeDataProvider: PiholeDataProvider.previewData(), date: Date(), widgetFamily: .systemMedium))
+ .previewContext(WidgetPreviewContext(family: .systemSmall))
+ }
+}
diff --git a/PiStatsMobile/Shared/Core/Pihole.swift b/PiStatsMobile/Shared/Core/Pihole.swift
index c80951f..610103b 100644
--- a/PiStatsMobile/Shared/Core/Pihole.swift
+++ b/PiStatsMobile/Shared/Core/Pihole.swift
@@ -17,7 +17,8 @@ class Pihole: Identifiable, ObservableObject {
private(set) var metrics: PiMetrics?
private(set) var active = false
private lazy var keychainToken = APIToken(accountName: self.id.uuidString)
-
+ private let servicesTimeout: TimeInterval = 10
+
var displayName: String?
var address: String
var piMonitorPort: Int?
@@ -56,12 +57,19 @@ class Pihole: Identifiable, ObservableObject {
address.components(separatedBy: ":").first ?? ""
}
+ var title: String {
+ if let name = displayName {
+ return name
+ }
+ return host
+ }
+
private var service: SwiftHole {
- SwiftHole(host: host, port: port, apiToken: apiToken, timeoutInterval: 10)
+ SwiftHole(host: host, port: port, apiToken: apiToken, timeoutInterval: servicesTimeout)
}
private var piMonitorService: PiMonitor {
- PiMonitor(host: host, port: piMonitorPort ?? 8088)
+ PiMonitor(host: host, port: piMonitorPort ?? 8088, timeoutInterval: servicesTimeout)
}
required init(from decoder: Decoder) throws {
@@ -92,7 +100,9 @@ class Pihole: Identifiable, ObservableObject {
}
static func previewData() -> Pihole {
- Pihole(address: "127.0.0.1")
+ let pihole = Pihole(address: "127.0.0.1")
+ pihole.hasPiMonitor = true
+ return pihole
}
private func getPort(_ address: String) -> Int? {
diff --git a/PiStatsMobile/Shared/Core/PiholeDataProvider.swift b/PiStatsMobile/Shared/Core/PiholeDataProvider.swift
index 63a6e94..43d2009 100644
--- a/PiStatsMobile/Shared/Core/PiholeDataProvider.swift
+++ b/PiStatsMobile/Shared/Core/PiholeDataProvider.swift
@@ -21,6 +21,11 @@ class PiholeDataProvider: ObservableObject, Identifiable {
provider.percentBlocked = "12,3%"
provider.domainsOnBlocklist = "12,345"
provider.status = .allEnabled
+
+ provider.temperature = "23 ºC"
+ provider.memoryUsage = "50%"
+ provider.loadAverage = "0.1, 0.3, 0.6"
+ provider.uptime = "23h 2m"
return provider
}
@@ -92,7 +97,7 @@ class PiholeDataProvider: ObservableObject, Identifiable {
if piholes.count > 1 {
self.name = UIConstants.Strings.allPiholesTitle
} else if let firstPihole = piholes.first {
- self.name = firstPihole.displayName ?? firstPihole.host
+ self.name = firstPihole.title
}
setupCancellables()
}
@@ -253,7 +258,6 @@ class PiholeDataProvider: ObservableObject, Identifiable {
completion?()
return
}
-
let dispatchGroup = DispatchGroup()
piholes.forEach { pihole in
@@ -262,6 +266,7 @@ class PiholeDataProvider: ObservableObject, Identifiable {
DispatchQueue.main.async {
self.updateMetrics(pihole.metrics)
}
+ dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: DispatchQueue.main) {
diff --git a/PiStatsMobile/Shared/Core/UserData/UserPreferences.swift b/PiStatsMobile/Shared/Core/UserData/UserPreferences.swift
index a5db347..865fb32 100644
--- a/PiStatsMobile/Shared/Core/UserData/UserPreferences.swift
+++ b/PiStatsMobile/Shared/Core/UserData/UserPreferences.swift
@@ -14,7 +14,6 @@ private enum Keys: String {
case displayStatsAsList
case displayStatsIcons
case displayAllPiholes
- case displayIconBadgeForOfflinePiholes
case disableTimes
case temperatureScale
}
@@ -34,65 +33,73 @@ class UserPreferences: ObservableObject {
return .celsius
}
}
+
+ init() {
+ migrateStandardUserDefaultToGroupIfNecessary()
+ }
+
+ private func migrateStandardUserDefaultToGroupIfNecessary() {
+ let keys = [
+ Keys.displayAllPiholes.rawValue,
+ Keys.disablePermanently.rawValue,
+ Keys.displayStatsAsList.rawValue,
+ Keys.displayStatsIcons.rawValue,
+ Keys.temperatureScale.rawValue,
+ Keys.disableTimes.rawValue
+ ]
+
+ keys.forEach { key in
+ if let value = UserDefaults.standard.object(forKey: key) {
+ UserDefaults.standard.removeObject(forKey: key)
+ /*
+ If I only set the disableTimes using the shared().setValue, it's getter returns nil and then returns the default set of intervals.
+ */
+ if key == Keys.disableTimes.rawValue {
+ if let times = value as? [TimeInterval] {
+ disableTimes = times
+ }
+ } else {
+ UserDefaults.shared().setValue(value, forKey: key)
+ }
+ }
+ }
+
+ UserDefaults.shared().synchronize()
+ }
- @AppStorage(Keys.displayAllPiholes.rawValue) var displayAllPiholes: Bool = false {
+ @AppStorage(Keys.displayAllPiholes.rawValue, store: UserDefaults(suiteName: Constants.appGroup)) var displayAllPiholes: Bool = false {
willSet {
objectWillChange.send()
}
}
- @AppStorage(Keys.disablePermanently.rawValue) var disablePermanently: Bool = false {
+ @AppStorage(Keys.disablePermanently.rawValue, store: UserDefaults(suiteName: Constants.appGroup)) var disablePermanently: Bool = false {
willSet {
objectWillChange.send()
}
}
- @AppStorage(Keys.displayStatsAsList.rawValue) var displayStatsAsList: Bool = false {
+ @AppStorage(Keys.displayStatsAsList.rawValue, store: UserDefaults(suiteName: Constants.appGroup)) var displayStatsAsList: Bool = false {
willSet {
objectWillChange.send()
}
}
- @AppStorage(Keys.displayStatsIcons.rawValue) var displayStatsIcons: Bool = true {
+ @AppStorage(Keys.displayStatsIcons.rawValue, store: UserDefaults(suiteName: Constants.appGroup)) var displayStatsIcons: Bool = true {
willSet {
objectWillChange.send()
}
}
- @Published var disableTimes: [TimeInterval] = UserDefaults.standard.object(forKey: Keys.disableTimes.rawValue) as? [TimeInterval] ?? [30, 60, 300] {
+ @Published var disableTimes: [TimeInterval] = UserDefaults.shared().object(forKey: Keys.disableTimes.rawValue) as? [TimeInterval] ?? [30, 60, 300] {
didSet {
- UserDefaults.standard.set(disableTimes, forKey: Keys.disableTimes.rawValue)
+ UserDefaults.shared().set(disableTimes, forKey: Keys.disableTimes.rawValue)
}
}
-
- @AppStorage(Keys.temperatureScale.rawValue) var temperatureScale: Int = 0 {
+ @AppStorage(Keys.temperatureScale.rawValue, store: UserDefaults(suiteName: Constants.appGroup)) var temperatureScale: Int = 0 {
willSet {
objectWillChange.send()
}
}
-
- /*
- TODO: Improve this:
- I'm not sure how to have an AppStorage + Published, so I used standard UserDefaults
- Also, the handling of the requestAuthorization and badge reset should be done by the caller since this
- class should only be responsible for data storage and not business logic
- */
- @Published var displayIconBadgeForOfflinePiholes: Bool = UserDefaults.standard.object(forKey: Keys.displayIconBadgeForOfflinePiholes.rawValue) as? Bool ?? false {
- willSet {
- if newValue {
- UNUserNotificationCenter.current().requestAuthorization(options: .badge) { (granted, error) in
- if granted == false {
- DispatchQueue.main.async {
- self.displayIconBadgeForOfflinePiholes = false
- }
- }
- }
- } else {
- // UIApplication.shared.applicationIconBadgeNumber = 0
- }
- } didSet {
- UserDefaults.standard.set(displayIconBadgeForOfflinePiholes, forKey: Keys.displayIconBadgeForOfflinePiholes.rawValue)
- }
- }
}
diff --git a/PiStatsMobile/Shared/StatsItemType.swift b/PiStatsMobile/Shared/StatsItemType.swift
index 58eb623..f6ee0af 100644
--- a/PiStatsMobile/Shared/StatsItemType.swift
+++ b/PiStatsMobile/Shared/StatsItemType.swift
@@ -16,13 +16,13 @@ enum StatsItemType {
var imageName: String {
switch self {
case .domainsOnBlockList:
- return "list.bullet"
+ return UIConstants.SystemImages.domainsOnBlockList
case .totalQueries:
- return "globe"
+ return UIConstants.SystemImages.totalQueries
case .queriesBlocked:
- return "hand.raised"
+ return UIConstants.SystemImages.queriesBlocked
case .percentBlocked:
- return "chart.pie"
+ return UIConstants.SystemImages.percentBlocked
}
}
diff --git a/PiStatsMobile/Shared/UIConstants.swift b/PiStatsMobile/Shared/UIConstants.swift
index de46573..aa166e6 100644
--- a/PiStatsMobile/Shared/UIConstants.swift
+++ b/PiStatsMobile/Shared/UIConstants.swift
@@ -29,6 +29,7 @@ struct UIConstants {
static let statusOnline = Color("StatusOnline")
static let statusWarning = Color("StatusWarning")
static let errorMessage = Color("StatusOffline")
+ static let piMonitorWidgetBackground = Color("PiMonitorWidgetBackground")
}
struct Strings {
@@ -58,7 +59,6 @@ struct UIConstants {
static let piMonitorExplanation = "Pi Monitor is a service that helps you to monitor your Raspberry Pi by showing you information like temperature, memory usage and more!\n\nIn order to use it you'll need to install it in your Raspberry Pi."
static let addFirstPiholeCaption = "Tap here to add your first pi-hole"
- static let displayIconBadgeForOfflinePiholes = "Display icon badge for offline pi-holes"
static let piholesNavigationTitle = "Pi-holes"
static let settingsNavigationTitle = "Settings"
static let disablePiholeOptionsTitle = "Disable Pi-hole"
@@ -70,6 +70,10 @@ struct UIConstants {
static let allPiholesTitle = "All Pi-holes"
static let temperatureScaleCelsius = "°C"
static let temperatureScaleFahrenheit = "°F"
+
+ struct Widget {
+ static let piholeNotEnabledOn = "Pi Monitor is not enabled on"
+ }
struct Preferences {
static let sectionInterface = "Interface"
@@ -135,5 +139,9 @@ struct UIConstants {
static let addNewCustomDisableTime = "plus"
static let piMonitorTemperature = "thermometer"
+ static let domainsOnBlockList = "list.bullet"
+ static let totalQueries = "globe"
+ static let queriesBlocked = "hand.raised"
+ static let percentBlocked = "chart.pie"
}
}