mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-08 22:33:26 +01:00
[AltWidget] Adds interactive Active Apps widget to view + refresh all active apps (iOS 17+)
This commit is contained in:
@@ -365,12 +365,14 @@
|
|||||||
D54DED1428CBC44B008B27A0 /* ErrorLogTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54DED1328CBC44B008B27A0 /* ErrorLogTableViewCell.swift */; };
|
D54DED1428CBC44B008B27A0 /* ErrorLogTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54DED1328CBC44B008B27A0 /* ErrorLogTableViewCell.swift */; };
|
||||||
D552B1D82A042A740066216F /* AppPermissionsCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = D552B1D72A042A740066216F /* AppPermissionsCard.swift */; };
|
D552B1D82A042A740066216F /* AppPermissionsCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = D552B1D72A042A740066216F /* AppPermissionsCard.swift */; };
|
||||||
D55467B82A8D5E2600F4CE90 /* AppShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55467B12A8D5E2600F4CE90 /* AppShortcuts.swift */; };
|
D55467B82A8D5E2600F4CE90 /* AppShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55467B12A8D5E2600F4CE90 /* AppShortcuts.swift */; };
|
||||||
|
D55467C52A8D72C300F4CE90 /* ActiveAppsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55467C42A8D72C300F4CE90 /* ActiveAppsWidget.swift */; };
|
||||||
D561B2EB28EF5A4F006752E4 /* AltSign-Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = D561B2EA28EF5A4F006752E4 /* AltSign-Dynamic */; };
|
D561B2EB28EF5A4F006752E4 /* AltSign-Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = D561B2EA28EF5A4F006752E4 /* AltSign-Dynamic */; };
|
||||||
D561B2ED28EF5A4F006752E4 /* AltSign-Dynamic in Embed Frameworks */ = {isa = PBXBuildFile; productRef = D561B2EA28EF5A4F006752E4 /* AltSign-Dynamic */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
D561B2ED28EF5A4F006752E4 /* AltSign-Dynamic in Embed Frameworks */ = {isa = PBXBuildFile; productRef = D561B2EA28EF5A4F006752E4 /* AltSign-Dynamic */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||||
D5708417292448DA00D42D34 /* OperatingSystemVersion+Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5708416292448DA00D42D34 /* OperatingSystemVersion+Comparable.swift */; };
|
D5708417292448DA00D42D34 /* OperatingSystemVersion+Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5708416292448DA00D42D34 /* OperatingSystemVersion+Comparable.swift */; };
|
||||||
D570841A2924680D00D42D34 /* OperatingSystemVersion+Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5708416292448DA00D42D34 /* OperatingSystemVersion+Comparable.swift */; };
|
D570841A2924680D00D42D34 /* OperatingSystemVersion+Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5708416292448DA00D42D34 /* OperatingSystemVersion+Comparable.swift */; };
|
||||||
D571ADD02A02FC7200B24B63 /* ALTAppPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = D571ADCF2A02FC7200B24B63 /* ALTAppPermission.swift */; };
|
D571ADD02A02FC7200B24B63 /* ALTAppPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = D571ADCF2A02FC7200B24B63 /* ALTAppPermission.swift */; };
|
||||||
D5728CA72A0D79D30014E73C /* OptionalProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5728CA62A0D79D30014E73C /* OptionalProtocol.swift */; };
|
D5728CA72A0D79D30014E73C /* OptionalProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5728CA62A0D79D30014E73C /* OptionalProtocol.swift */; };
|
||||||
|
D577AB7B2A967DF5007FE952 /* AppsTimelineProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D577AB7A2A967DF5007FE952 /* AppsTimelineProvider.swift */; };
|
||||||
D577AB7F2A96878A007FE952 /* AppDetailWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = D577AB7E2A96878A007FE952 /* AppDetailWidget.swift */; };
|
D577AB7F2A96878A007FE952 /* AppDetailWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = D577AB7E2A96878A007FE952 /* AppDetailWidget.swift */; };
|
||||||
D57968CB29CB99EF00539069 /* VibrantButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57968CA29CB99EF00539069 /* VibrantButton.swift */; };
|
D57968CB29CB99EF00539069 /* VibrantButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57968CA29CB99EF00539069 /* VibrantButton.swift */; };
|
||||||
D57DF638271E32F000677701 /* PatchApp.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D57DF637271E32F000677701 /* PatchApp.storyboard */; };
|
D57DF638271E32F000677701 /* PatchApp.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D57DF637271E32F000677701 /* PatchApp.storyboard */; };
|
||||||
@@ -412,8 +414,10 @@
|
|||||||
D5F5AF7D28ECEA990067C736 /* ErrorDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F5AF7C28ECEA990067C736 /* ErrorDetailsViewController.swift */; };
|
D5F5AF7D28ECEA990067C736 /* ErrorDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F5AF7C28ECEA990067C736 /* ErrorDetailsViewController.swift */; };
|
||||||
D5F99A1828D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = D5F99A1728D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel */; };
|
D5F99A1828D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = D5F99A1728D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel */; };
|
||||||
D5F99A1A28D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F99A1928D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift */; };
|
D5F99A1A28D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F99A1928D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift */; };
|
||||||
|
D5FB7A0E2AA25A4E00EF863D /* Previews.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D5FB7A0D2AA25A4E00EF863D /* Previews.xcassets */; };
|
||||||
D5FD4EC52A952EAD0097BEE8 /* AltWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FD4EC42A952EAD0097BEE8 /* AltWidgetBundle.swift */; };
|
D5FD4EC52A952EAD0097BEE8 /* AltWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FD4EC42A952EAD0097BEE8 /* AltWidgetBundle.swift */; };
|
||||||
D5FD4EC92A9530C00097BEE8 /* AppSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FD4EC82A9530C00097BEE8 /* AppSnapshot.swift */; };
|
D5FD4EC92A9530C00097BEE8 /* AppSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FD4EC82A9530C00097BEE8 /* AppSnapshot.swift */; };
|
||||||
|
D5FD4ECB2A9532960097BEE8 /* DatabaseManager+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FD4ECA2A9532960097BEE8 /* DatabaseManager+Async.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -939,10 +943,12 @@
|
|||||||
D54DED1328CBC44B008B27A0 /* ErrorLogTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorLogTableViewCell.swift; sourceTree = "<group>"; };
|
D54DED1328CBC44B008B27A0 /* ErrorLogTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorLogTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D552B1D72A042A740066216F /* AppPermissionsCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPermissionsCard.swift; sourceTree = "<group>"; };
|
D552B1D72A042A740066216F /* AppPermissionsCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPermissionsCard.swift; sourceTree = "<group>"; };
|
||||||
D55467B12A8D5E2600F4CE90 /* AppShortcuts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppShortcuts.swift; sourceTree = "<group>"; };
|
D55467B12A8D5E2600F4CE90 /* AppShortcuts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppShortcuts.swift; sourceTree = "<group>"; };
|
||||||
|
D55467C42A8D72C300F4CE90 /* ActiveAppsWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveAppsWidget.swift; sourceTree = "<group>"; };
|
||||||
D5708416292448DA00D42D34 /* OperatingSystemVersion+Comparable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OperatingSystemVersion+Comparable.swift"; sourceTree = "<group>"; };
|
D5708416292448DA00D42D34 /* OperatingSystemVersion+Comparable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OperatingSystemVersion+Comparable.swift"; sourceTree = "<group>"; };
|
||||||
D571ADCD2A02FA7400B24B63 /* SourceError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceError.swift; sourceTree = "<group>"; };
|
D571ADCD2A02FA7400B24B63 /* SourceError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceError.swift; sourceTree = "<group>"; };
|
||||||
D571ADCF2A02FC7200B24B63 /* ALTAppPermission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ALTAppPermission.swift; sourceTree = "<group>"; };
|
D571ADCF2A02FC7200B24B63 /* ALTAppPermission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ALTAppPermission.swift; sourceTree = "<group>"; };
|
||||||
D5728CA62A0D79D30014E73C /* OptionalProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionalProtocol.swift; sourceTree = "<group>"; };
|
D5728CA62A0D79D30014E73C /* OptionalProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionalProtocol.swift; sourceTree = "<group>"; };
|
||||||
|
D577AB7A2A967DF5007FE952 /* AppsTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppsTimelineProvider.swift; sourceTree = "<group>"; };
|
||||||
D577AB7E2A96878A007FE952 /* AppDetailWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDetailWidget.swift; sourceTree = "<group>"; };
|
D577AB7E2A96878A007FE952 /* AppDetailWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDetailWidget.swift; sourceTree = "<group>"; };
|
||||||
D57968CA29CB99EF00539069 /* VibrantButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrantButton.swift; sourceTree = "<group>"; };
|
D57968CA29CB99EF00539069 /* VibrantButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrantButton.swift; sourceTree = "<group>"; };
|
||||||
D57DF637271E32F000677701 /* PatchApp.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = PatchApp.storyboard; sourceTree = "<group>"; };
|
D57DF637271E32F000677701 /* PatchApp.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = PatchApp.storyboard; sourceTree = "<group>"; };
|
||||||
@@ -986,8 +992,10 @@
|
|||||||
D5F5AF7C28ECEA990067C736 /* ErrorDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorDetailsViewController.swift; sourceTree = "<group>"; };
|
D5F5AF7C28ECEA990067C736 /* ErrorDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorDetailsViewController.swift; sourceTree = "<group>"; };
|
||||||
D5F99A1728D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore10ToAltStore11.xcmappingmodel; sourceTree = "<group>"; };
|
D5F99A1728D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore10ToAltStore11.xcmappingmodel; sourceTree = "<group>"; };
|
||||||
D5F99A1928D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreApp10ToStoreApp11Policy.swift; sourceTree = "<group>"; };
|
D5F99A1928D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreApp10ToStoreApp11Policy.swift; sourceTree = "<group>"; };
|
||||||
|
D5FB7A0D2AA25A4E00EF863D /* Previews.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Previews.xcassets; sourceTree = "<group>"; };
|
||||||
D5FD4EC42A952EAD0097BEE8 /* AltWidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AltWidgetBundle.swift; sourceTree = "<group>"; };
|
D5FD4EC42A952EAD0097BEE8 /* AltWidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AltWidgetBundle.swift; sourceTree = "<group>"; };
|
||||||
D5FD4EC82A9530C00097BEE8 /* AppSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSnapshot.swift; sourceTree = "<group>"; };
|
D5FD4EC82A9530C00097BEE8 /* AppSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSnapshot.swift; sourceTree = "<group>"; };
|
||||||
|
D5FD4ECA2A9532960097BEE8 /* DatabaseManager+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DatabaseManager+Async.swift"; sourceTree = "<group>"; };
|
||||||
EA79A60285C6AF5848AA16E9 /* Pods-AltStore.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AltStore.debug.xcconfig"; path = "Target Support Files/Pods-AltStore/Pods-AltStore.debug.xcconfig"; sourceTree = "<group>"; };
|
EA79A60285C6AF5848AA16E9 /* Pods-AltStore.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AltStore.debug.xcconfig"; path = "Target Support Files/Pods-AltStore/Pods-AltStore.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
FC3822AB1C4CF1D4CDF7445D /* Pods_AltServer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AltServer.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
FC3822AB1C4CF1D4CDF7445D /* Pods_AltServer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AltServer.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
@@ -1507,6 +1515,7 @@
|
|||||||
BF66EEC62501AECA007EE018 /* AppPermission.swift */,
|
BF66EEC62501AECA007EE018 /* AppPermission.swift */,
|
||||||
D52C08ED28AEC37A006C4AE5 /* AppVersion.swift */,
|
D52C08ED28AEC37A006C4AE5 /* AppVersion.swift */,
|
||||||
BF66EECA2501AECA007EE018 /* DatabaseManager.swift */,
|
BF66EECA2501AECA007EE018 /* DatabaseManager.swift */,
|
||||||
|
D5FD4ECA2A9532960097BEE8 /* DatabaseManager+Async.swift */,
|
||||||
BF66EEC02501AECA007EE018 /* InstalledApp.swift */,
|
BF66EEC02501AECA007EE018 /* InstalledApp.swift */,
|
||||||
BF66EECB2501AECA007EE018 /* InstalledExtension.swift */,
|
BF66EECB2501AECA007EE018 /* InstalledExtension.swift */,
|
||||||
D58916FD28C7C55C00E39C8B /* LoggedError.swift */,
|
D58916FD28C7C55C00E39C8B /* LoggedError.swift */,
|
||||||
@@ -1618,11 +1627,13 @@
|
|||||||
BF8B17F0250AC62400F8157F /* AltWidgetExtension.entitlements */,
|
BF8B17F0250AC62400F8157F /* AltWidgetExtension.entitlements */,
|
||||||
D5FD4EC42A952EAD0097BEE8 /* AltWidgetBundle.swift */,
|
D5FD4EC42A952EAD0097BEE8 /* AltWidgetBundle.swift */,
|
||||||
D586222D2AA115D000A493E1 /* Provider.swift */,
|
D586222D2AA115D000A493E1 /* Provider.swift */,
|
||||||
|
D577AB7A2A967DF5007FE952 /* AppsTimelineProvider.swift */,
|
||||||
D50C29F22A8ECD71009AB488 /* Widgets */,
|
D50C29F22A8ECD71009AB488 /* Widgets */,
|
||||||
D51AF9752A97D29100471312 /* Model */,
|
D51AF9752A97D29100471312 /* Model */,
|
||||||
D577AB802A968B7E007FE952 /* Components */,
|
D577AB802A968B7E007FE952 /* Components */,
|
||||||
D5151BE42A9038FA00C96F28 /* Extensions */,
|
D5151BE42A9038FA00C96F28 /* Extensions */,
|
||||||
BF989170250AABF4002ACF50 /* Assets.xcassets */,
|
BF989170250AABF4002ACF50 /* Assets.xcassets */,
|
||||||
|
D5FB7A0D2AA25A4E00EF863D /* Previews.xcassets */,
|
||||||
BF989172250AABF4002ACF50 /* Info.plist */,
|
BF989172250AABF4002ACF50 /* Info.plist */,
|
||||||
);
|
);
|
||||||
path = AltWidget;
|
path = AltWidget;
|
||||||
@@ -1971,6 +1982,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D577AB7E2A96878A007FE952 /* AppDetailWidget.swift */,
|
D577AB7E2A96878A007FE952 /* AppDetailWidget.swift */,
|
||||||
|
D55467C42A8D72C300F4CE90 /* ActiveAppsWidget.swift */,
|
||||||
BF98917D250AAC4F002ACF50 /* LockScreenWidget.swift */,
|
BF98917D250AAC4F002ACF50 /* LockScreenWidget.swift */,
|
||||||
);
|
);
|
||||||
path = Widgets;
|
path = Widgets;
|
||||||
@@ -2487,6 +2499,7 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
D5FB7A0E2AA25A4E00EF863D /* Previews.xcassets in Resources */,
|
||||||
BF989171250AABF4002ACF50 /* Assets.xcassets in Resources */,
|
BF989171250AABF4002ACF50 /* Assets.xcassets in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -2748,6 +2761,7 @@
|
|||||||
D58916FE28C7C55C00E39C8B /* LoggedError.swift in Sources */,
|
D58916FE28C7C55C00E39C8B /* LoggedError.swift in Sources */,
|
||||||
BFBF331B2526762200B7B8C9 /* AltStore8ToAltStore9.xcmappingmodel in Sources */,
|
BFBF331B2526762200B7B8C9 /* AltStore8ToAltStore9.xcmappingmodel in Sources */,
|
||||||
0EE7FDC72BE8CF4100D1E390 /* ALTWrappedError.m in Sources */,
|
0EE7FDC72BE8CF4100D1E390 /* ALTWrappedError.m in Sources */,
|
||||||
|
D5FD4ECB2A9532960097BEE8 /* DatabaseManager+Async.swift in Sources */,
|
||||||
D5893F802A1419E800E767CD /* NSManagedObjectContext+Conveniences.swift in Sources */,
|
D5893F802A1419E800E767CD /* NSManagedObjectContext+Conveniences.swift in Sources */,
|
||||||
D5CA0C4E280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel in Sources */,
|
D5CA0C4E280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel in Sources */,
|
||||||
D51AD27F29356B7B00967AAA /* ALTWrappedError.m in Sources */,
|
D51AD27F29356B7B00967AAA /* ALTWrappedError.m in Sources */,
|
||||||
@@ -2800,6 +2814,8 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
D55467C52A8D72C300F4CE90 /* ActiveAppsWidget.swift in Sources */,
|
||||||
|
D577AB7B2A967DF5007FE952 /* AppsTimelineProvider.swift in Sources */,
|
||||||
D577AB7F2A96878A007FE952 /* AppDetailWidget.swift in Sources */,
|
D577AB7F2A96878A007FE952 /* AppDetailWidget.swift in Sources */,
|
||||||
BF98917E250AAC4F002ACF50 /* Countdown.swift in Sources */,
|
BF98917E250AAC4F002ACF50 /* Countdown.swift in Sources */,
|
||||||
D5151BE22A90363300C96F28 /* RefreshAllAppsWidgetIntent.swift in Sources */,
|
D5151BE22A90363300C96F28 /* RefreshAllAppsWidgetIntent.swift in Sources */,
|
||||||
@@ -3408,6 +3424,8 @@
|
|||||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||||
ENABLE_DEBUG_DYLIB = NO;
|
ENABLE_DEBUG_DYLIB = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
|
CURRENT_PROJECT_VERSION = 6;
|
||||||
|
DEVELOPMENT_ASSET_PATHS = AltWidget/Previews.xcassets;
|
||||||
INFOPLIST_FILE = AltWidget/Info.plist;
|
INFOPLIST_FILE = AltWidget/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@@ -3439,6 +3457,8 @@
|
|||||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||||
ENABLE_DEBUG_DYLIB = NO;
|
ENABLE_DEBUG_DYLIB = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
|
CURRENT_PROJECT_VERSION = 6;
|
||||||
|
DEVELOPMENT_ASSET_PATHS = AltWidget/Previews.xcassets;
|
||||||
INFOPLIST_FILE = AltWidget/Info.plist;
|
INFOPLIST_FILE = AltWidget/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ public extension UIColor
|
|||||||
|
|
||||||
static let altPrimary = UIColor(named: "Primary", in: colorBundle, compatibleWith: nil)!
|
static let altPrimary = UIColor(named: "Primary", in: colorBundle, compatibleWith: nil)!
|
||||||
static let deltaPrimary = UIColor(named: "DeltaPrimary", in: colorBundle, compatibleWith: nil)
|
static let deltaPrimary = UIColor(named: "DeltaPrimary", in: colorBundle, compatibleWith: nil)
|
||||||
|
static let clipPrimary = UIColor(named: "ClipPrimary", in: colorBundle, compatibleWith: nil)
|
||||||
static let altPink = UIColor(named: "Pink", in: colorBundle, compatibleWith: nil)!
|
|
||||||
|
|
||||||
static let refreshRed = UIColor(named: "RefreshRed", in: colorBundle, compatibleWith: nil)!
|
static let refreshRed = UIColor(named: "RefreshRed", in: colorBundle, compatibleWith: nil)!
|
||||||
static let refreshOrange = UIColor(named: "RefreshOrange", in: colorBundle, compatibleWith: nil)!
|
static let refreshOrange = UIColor(named: "RefreshOrange", in: colorBundle, compatibleWith: nil)!
|
||||||
|
|||||||
28
AltStoreCore/Model/DatabaseManager+Async.swift
Normal file
28
AltStoreCore/Model/DatabaseManager+Async.swift
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
//
|
||||||
|
// DatabaseManager+Async.swift
|
||||||
|
// AltStoreCore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 8/22/23.
|
||||||
|
// Copyright © 2023 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public extension DatabaseManager
|
||||||
|
{
|
||||||
|
func start() async throws
|
||||||
|
{
|
||||||
|
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
|
||||||
|
self.start { error in
|
||||||
|
if let error
|
||||||
|
{
|
||||||
|
continuation.resume(throwing: error)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
continuation.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,9 +5,9 @@
|
|||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "0.698",
|
"blue" : "140",
|
||||||
"green" : "0.255",
|
"green" : "0",
|
||||||
"red" : "0.925"
|
"red" : "236"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
@@ -17,5 +17,10 @@ struct AltWidgetBundle: WidgetBundle
|
|||||||
|
|
||||||
IconLockScreenWidget()
|
IconLockScreenWidget()
|
||||||
TextLockScreenWidget()
|
TextLockScreenWidget()
|
||||||
|
|
||||||
|
if #available(iOS 17, *)
|
||||||
|
{
|
||||||
|
ActiveAppsWidget()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
202
AltWidget/AppsTimelineProvider.swift
Normal file
202
AltWidget/AppsTimelineProvider.swift
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
//
|
||||||
|
// AppsTimelineProvider.swift
|
||||||
|
// AltWidgetExtension
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 8/23/23.
|
||||||
|
// Copyright © 2023 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import WidgetKit
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
|
|
||||||
|
struct AppsEntry: TimelineEntry
|
||||||
|
{
|
||||||
|
var date: Date
|
||||||
|
var relevance: TimelineEntryRelevance?
|
||||||
|
|
||||||
|
var apps: [AppSnapshot]
|
||||||
|
var isPlaceholder: Bool = false
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AppsTimelineProvider
|
||||||
|
{
|
||||||
|
typealias Entry = AppsEntry
|
||||||
|
|
||||||
|
func placeholder(in context: TimelineProviderContext) -> AppsEntry
|
||||||
|
{
|
||||||
|
return AppsEntry(date: Date(), apps: [], isPlaceholder: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func snapshot(for appBundleIDs: [String]) async -> AppsEntry
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
try await self.prepare()
|
||||||
|
|
||||||
|
let apps = try await self.fetchApps(withBundleIDs: appBundleIDs)
|
||||||
|
|
||||||
|
let entry = AppsEntry(date: Date(), apps: apps)
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("Failed to prepare widget snapshot:", error)
|
||||||
|
|
||||||
|
let entry = AppsEntry(date: Date(), apps: [])
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeline(for appBundleIDs: [String]) async -> Timeline<AppsEntry>
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
try await self.prepare()
|
||||||
|
|
||||||
|
let apps = try await self.fetchApps(withBundleIDs: appBundleIDs)
|
||||||
|
|
||||||
|
let entries = self.makeEntries(for: apps)
|
||||||
|
let timeline = Timeline(entries: entries, policy: .atEnd)
|
||||||
|
return timeline
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("Failed to prepare widget timeline:", error)
|
||||||
|
|
||||||
|
let entry = AppsEntry(date: Date(), apps: [])
|
||||||
|
let timeline = Timeline(entries: [entry], policy: .atEnd)
|
||||||
|
return timeline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension AppsTimelineProvider
|
||||||
|
{
|
||||||
|
func prepare() async throws
|
||||||
|
{
|
||||||
|
try await DatabaseManager.shared.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchApps(withBundleIDs bundleIDs: [String]) async throws -> [AppSnapshot]
|
||||||
|
{
|
||||||
|
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||||
|
let apps = try await context.performAsync {
|
||||||
|
let fetchRequest = InstalledApp.fetchRequest()
|
||||||
|
fetchRequest.predicate = NSPredicate(format: "%K IN %@", #keyPath(InstalledApp.bundleIdentifier), bundleIDs)
|
||||||
|
fetchRequest.returnsObjectsAsFaults = false
|
||||||
|
|
||||||
|
let installedApps = try context.fetch(fetchRequest)
|
||||||
|
|
||||||
|
let apps = installedApps.map { AppSnapshot(installedApp: $0) }
|
||||||
|
|
||||||
|
// Always list apps in alphabetical order.
|
||||||
|
let sortedApps = apps.sorted { $0.name < $1.name }
|
||||||
|
return sortedApps
|
||||||
|
}
|
||||||
|
|
||||||
|
return apps
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeEntries(for snapshots: [AppSnapshot]) -> [AppsEntry]
|
||||||
|
{
|
||||||
|
let sortedAppsByExpirationDate = snapshots.sorted { $0.expirationDate < $1.expirationDate }
|
||||||
|
guard let firstExpiringApp = sortedAppsByExpirationDate.first, let lastExpiringApp = sortedAppsByExpirationDate.last else { return [] }
|
||||||
|
|
||||||
|
let currentDate = Calendar.current.startOfDay(for: Date())
|
||||||
|
let numberOfDays = lastExpiringApp.expirationDate.numberOfCalendarDays(since: currentDate)
|
||||||
|
|
||||||
|
// Generate a timeline consisting of one entry per day.
|
||||||
|
var entries: [AppsEntry] = []
|
||||||
|
|
||||||
|
switch numberOfDays
|
||||||
|
{
|
||||||
|
case ..<0:
|
||||||
|
let entry = AppsEntry(date: currentDate, relevance: TimelineEntryRelevance(score: 0.0), apps: snapshots)
|
||||||
|
entries.append(entry)
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
let entry = AppsEntry(date: currentDate, relevance: TimelineEntryRelevance(score: 1.0), apps: snapshots)
|
||||||
|
entries.append(entry)
|
||||||
|
|
||||||
|
default:
|
||||||
|
// To reduce memory consumption, we only generate entries for the next week. This includes:
|
||||||
|
// * 1 for each day the "least expired" app is valid (up to 7)
|
||||||
|
// * 1 "0 days remaining"
|
||||||
|
// * 1 "Expired"
|
||||||
|
|
||||||
|
let numberOfEntries = min(numberOfDays, 7) + 2
|
||||||
|
|
||||||
|
let appEntries = (0 ..< numberOfEntries).map { (dayOffset) -> AppsEntry in
|
||||||
|
let entryDate = Calendar.current.date(byAdding: .day, value: dayOffset, to: currentDate) ?? currentDate.addingTimeInterval(Double(dayOffset) * 60 * 60 * 24)
|
||||||
|
|
||||||
|
let daysSinceRefresh = entryDate.numberOfCalendarDays(since: firstExpiringApp.refreshedDate)
|
||||||
|
let totalNumberOfDays = firstExpiringApp.expirationDate.numberOfCalendarDays(since: firstExpiringApp.refreshedDate)
|
||||||
|
|
||||||
|
var score = (entryDate <= firstExpiringApp.expirationDate) ? Float(daysSinceRefresh + 1) / Float(totalNumberOfDays + 1) : 1 // Expired apps have a score of 1.
|
||||||
|
if snapshots.allSatisfy({ $0.expirationDate > currentDate })
|
||||||
|
{
|
||||||
|
// Unless ALL apps are expired, in which case relevance is 0.
|
||||||
|
score = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry = AppsEntry(date: entryDate, relevance: TimelineEntryRelevance(score: score), apps: snapshots)
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.append(contentsOf: appEntries)
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AppsTimelineProvider: TimelineProvider
|
||||||
|
{
|
||||||
|
func getSnapshot(in context: Context, completion: @escaping (AppsEntry) -> Void)
|
||||||
|
{
|
||||||
|
Task<Void, Never> {
|
||||||
|
let bundleIDs = await self.fetchActiveAppBundleIDs()
|
||||||
|
|
||||||
|
let snapshot = await self.snapshot(for: bundleIDs)
|
||||||
|
completion(snapshot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTimeline(in context: Context, completion: @escaping (Timeline<AppsEntry>) -> Void)
|
||||||
|
{
|
||||||
|
Task<Void, Never> {
|
||||||
|
let bundleIDs = await self.fetchActiveAppBundleIDs()
|
||||||
|
|
||||||
|
let timeline = await self.timeline(for: bundleIDs)
|
||||||
|
completion(timeline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func fetchActiveAppBundleIDs() async -> [String]
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
try await self.prepare()
|
||||||
|
|
||||||
|
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||||
|
let bundleIDs = try await context.performAsync {
|
||||||
|
let fetchRequest = InstalledApp.activeAppsFetchRequest() as! NSFetchRequest<NSDictionary>
|
||||||
|
fetchRequest.resultType = .dictionaryResultType
|
||||||
|
fetchRequest.propertiesToFetch = [#keyPath(InstalledApp.bundleIdentifier)]
|
||||||
|
|
||||||
|
let bundleIDs = try context.fetch(fetchRequest).compactMap { $0[#keyPath(InstalledApp.bundleIdentifier)] as? String }
|
||||||
|
return bundleIDs
|
||||||
|
}
|
||||||
|
|
||||||
|
return bundleIDs
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("Failed to fetch active bundle IDs, falling back to AltStore bundle ID.", error)
|
||||||
|
|
||||||
|
return [StoreApp.altstoreAppID]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,13 +15,15 @@ struct Countdown: View
|
|||||||
var endDate: Date?
|
var endDate: Date?
|
||||||
var currentDate: Date = Date()
|
var currentDate: Date = Date()
|
||||||
|
|
||||||
|
var strokeWidth: Double = 4.0
|
||||||
|
|
||||||
@Environment(\.font) private var font
|
@Environment(\.font) private var font
|
||||||
|
|
||||||
private var numberOfDays: Int {
|
private var numberOfDays: Int {
|
||||||
guard let date = self.endDate else { return 0 }
|
guard let date = self.endDate else { return 0 }
|
||||||
|
|
||||||
let numberOfDays = date.numberOfCalendarDays(since: self.currentDate)
|
let numberOfDays = date.numberOfCalendarDays(since: self.currentDate)
|
||||||
return numberOfDays
|
return max(numberOfDays, 0) // Never show negative values.
|
||||||
}
|
}
|
||||||
|
|
||||||
private var fractionComplete: CGFloat {
|
private var fractionComplete: CGFloat {
|
||||||
@@ -35,7 +37,7 @@ struct Countdown: View
|
|||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func overlay(progress: CGFloat) -> some View
|
private func overlay(progress: CGFloat) -> some View
|
||||||
{
|
{
|
||||||
let strokeStyle = StrokeStyle(lineWidth: 4.0, lineCap: .round, lineJoin: .round)
|
let strokeStyle = StrokeStyle(lineWidth: self.strokeWidth, lineCap: .round, lineJoin: .round)
|
||||||
|
|
||||||
if self.numberOfDays > 9 || self.numberOfDays < 0 {
|
if self.numberOfDays > 9 || self.numberOfDays < 0 {
|
||||||
Capsule(style: .continuous)
|
Capsule(style: .continuous)
|
||||||
|
|||||||
@@ -39,3 +39,51 @@ extension AppSnapshot
|
|||||||
self.icon = application?.icon?.resizing(toFill: CGSize(width: 180, height: 180))
|
self.icon = application?.icon?.resizing(toFill: CGSize(width: 180, height: 180))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension AppSnapshot
|
||||||
|
{
|
||||||
|
static func makePreviewSnapshots() -> (altstore: AppSnapshot, delta: AppSnapshot, clip: AppSnapshot, longAltStore: AppSnapshot, longDelta: AppSnapshot, longClip: AppSnapshot)
|
||||||
|
{
|
||||||
|
let shortRefreshedDate = Calendar.current.date(byAdding: .day, value: -2, to: Date()) ?? Date()
|
||||||
|
let shortExpirationDate = Calendar.current.date(byAdding: .day, value: 7, to: shortRefreshedDate) ?? Date()
|
||||||
|
|
||||||
|
let longRefreshedDate = Calendar.current.date(byAdding: .day, value: -100, to: Date()) ?? Date()
|
||||||
|
let longExpirationDate = Calendar.current.date(byAdding: .day, value: 365, to: longRefreshedDate) ?? Date()
|
||||||
|
|
||||||
|
let altstore = AppSnapshot(name: "AltStore",
|
||||||
|
bundleIdentifier: "com.rileytestut.AltStore",
|
||||||
|
expirationDate: shortExpirationDate,
|
||||||
|
refreshedDate: shortRefreshedDate,
|
||||||
|
tintColor: .altPrimary,
|
||||||
|
icon: UIImage(named: "AltStore"))
|
||||||
|
|
||||||
|
let delta = AppSnapshot(name: "Delta",
|
||||||
|
bundleIdentifier: "com.rileytestut.Delta",
|
||||||
|
expirationDate: shortExpirationDate,
|
||||||
|
refreshedDate: shortRefreshedDate,
|
||||||
|
tintColor: .deltaPrimary,
|
||||||
|
icon: UIImage(named: "Delta"))
|
||||||
|
|
||||||
|
let clip = AppSnapshot(name: "Clip",
|
||||||
|
bundleIdentifier: "com.rileytestut.Clip",
|
||||||
|
expirationDate: shortExpirationDate,
|
||||||
|
refreshedDate: shortRefreshedDate,
|
||||||
|
tintColor: .clipPrimary,
|
||||||
|
icon: UIImage(named: "Clip"))
|
||||||
|
|
||||||
|
let longAltStore = altstore.with(refreshedDate: longRefreshedDate, expirationDate: longExpirationDate)
|
||||||
|
let longDelta = delta.with(refreshedDate: longRefreshedDate, expirationDate: longExpirationDate)
|
||||||
|
let longClip = clip.with(refreshedDate: longRefreshedDate, expirationDate: longExpirationDate)
|
||||||
|
|
||||||
|
return (altstore, delta, clip, longAltStore, longDelta, longClip)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func with(refreshedDate: Date, expirationDate: Date) -> AppSnapshot
|
||||||
|
{
|
||||||
|
var app = self
|
||||||
|
app.refreshedDate = refreshedDate
|
||||||
|
app.expirationDate = expirationDate
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
BIN
AltWidget/Previews.xcassets/Clip.imageset/ClipIcon@2x.png
vendored
Normal file
BIN
AltWidget/Previews.xcassets/Clip.imageset/ClipIcon@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
BIN
AltWidget/Previews.xcassets/Clip.imageset/ClipIcon@3x.png
vendored
Normal file
BIN
AltWidget/Previews.xcassets/Clip.imageset/ClipIcon@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
22
AltWidget/Previews.xcassets/Clip.imageset/Contents.json
vendored
Normal file
22
AltWidget/Previews.xcassets/Clip.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "ClipIcon@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "ClipIcon@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
6
AltWidget/Previews.xcassets/Contents.json
Normal file
6
AltWidget/Previews.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
158
AltWidget/Widgets/ActiveAppsWidget.swift
Normal file
158
AltWidget/Widgets/ActiveAppsWidget.swift
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
//
|
||||||
|
// HomeScreenWidget.swift
|
||||||
|
// AltWidgetExtension
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 8/16/23.
|
||||||
|
// Copyright © 2023 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import WidgetKit
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
|
import AltSign
|
||||||
|
|
||||||
|
private extension Color
|
||||||
|
{
|
||||||
|
static let altGradientLight = Color.init(.displayP3, red: 123.0/255.0, green: 200.0/255.0, blue: 176.0/255.0)
|
||||||
|
static let altGradientDark = Color.init(.displayP3, red: 0.0/255.0, green: 128.0/255.0, blue: 132.0/255.0)
|
||||||
|
|
||||||
|
static let altGradientExtraDark = Color.init(.displayP3, red: 2.0/255.0, green: 82.0/255.0, blue: 103.0/255.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 17, *)
|
||||||
|
struct ActiveAppsWidget: Widget
|
||||||
|
{
|
||||||
|
private let kind: String = "ActiveApps"
|
||||||
|
|
||||||
|
public var body: some WidgetConfiguration {
|
||||||
|
StaticConfiguration(kind: kind, provider: AppsTimelineProvider()) { entry in
|
||||||
|
ActiveAppsWidgetView(entry: entry)
|
||||||
|
}
|
||||||
|
.supportedFamilies([.systemMedium])
|
||||||
|
.configurationDisplayName("AltWidget")
|
||||||
|
.description("View remaining days until your active apps expire.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 17, *)
|
||||||
|
private struct ActiveAppsWidgetView: View
|
||||||
|
{
|
||||||
|
var entry: AppsEntry
|
||||||
|
|
||||||
|
@Environment(\.colorScheme)
|
||||||
|
private var colorScheme
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
if entry.apps.isEmpty
|
||||||
|
{
|
||||||
|
placeholder
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
.containerBackground(for: .widget) {
|
||||||
|
if colorScheme == .dark
|
||||||
|
{
|
||||||
|
LinearGradient(colors: [.altGradientDark, .altGradientExtraDark], startPoint: .top, endPoint: .bottom)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LinearGradient(colors: [.altGradientLight, .altGradientDark], startPoint: .top, endPoint: .bottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var content: some View {
|
||||||
|
GeometryReader { (geometry) in
|
||||||
|
|
||||||
|
let numberOfApps = max(entry.apps.count, 1) // Ensure we don't divide by 0
|
||||||
|
let preferredRowHeight = (geometry.size.height / Double(numberOfApps)) - 8
|
||||||
|
let rowHeight = min(preferredRowHeight, geometry.size.height / 2)
|
||||||
|
|
||||||
|
ZStack(alignment: .center) {
|
||||||
|
VStack(spacing: 12) {
|
||||||
|
ForEach(entry.apps, id: \.bundleIdentifier) { app in
|
||||||
|
|
||||||
|
let daysRemaining = app.expirationDate.numberOfCalendarDays(since: entry.date)
|
||||||
|
let cornerRadius = rowHeight / 5.0
|
||||||
|
|
||||||
|
HStack(spacing: 10) {
|
||||||
|
Image(uiImage: app.icon ?? UIImage(named: "AltStore")!)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.cornerRadius(cornerRadius)
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 1) {
|
||||||
|
Text(app.name)
|
||||||
|
.font(.system(size: 15, weight: .semibold, design: .rounded))
|
||||||
|
|
||||||
|
let text = if entry.date > app.expirationDate
|
||||||
|
{
|
||||||
|
Text("Expired")
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Text("Expires in \(daysRemaining) ") + (daysRemaining == 1 ? Text("day") : Text("days"))
|
||||||
|
}
|
||||||
|
|
||||||
|
text
|
||||||
|
.font(.system(size: 13, weight: .semibold, design: .rounded))
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Countdown(startDate: app.refreshedDate,
|
||||||
|
endDate: app.expirationDate,
|
||||||
|
currentDate: entry.date,
|
||||||
|
strokeWidth: 3.0) // Slightly thinner circle stroke width
|
||||||
|
.background {
|
||||||
|
Color.black.opacity(0.1)
|
||||||
|
.mask(Capsule())
|
||||||
|
.padding(.all, -5)
|
||||||
|
}
|
||||||
|
.font(.system(size: 16, weight: .semibold, design: .rounded))
|
||||||
|
.invalidatableContent()
|
||||||
|
}
|
||||||
|
.activatesRefreshAllAppsIntent()
|
||||||
|
}
|
||||||
|
.frame(height: rowHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var placeholder: some View {
|
||||||
|
Text("App Not Found")
|
||||||
|
.font(.system(.body, design: .rounded))
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundColor(Color.white.opacity(0.4))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview(as: .systemMedium) {
|
||||||
|
ActiveAppsWidget()
|
||||||
|
} timeline: {
|
||||||
|
let expiredDate = Date().addingTimeInterval(1 * 60 * 60 * 24 * 7)
|
||||||
|
let (altstore, delta, clip, longAltStore, longDelta, longClip) = AppSnapshot.makePreviewSnapshots()
|
||||||
|
|
||||||
|
AppsEntry(date: Date(), apps: [altstore, delta, clip])
|
||||||
|
AppsEntry(date: Date(), apps: [longAltStore, longDelta, longClip])
|
||||||
|
|
||||||
|
AppsEntry(date: expiredDate, apps: [altstore, delta, clip])
|
||||||
|
|
||||||
|
AppsEntry(date: Date(), apps: [altstore, delta])
|
||||||
|
AppsEntry(date: Date(), apps: [altstore])
|
||||||
|
|
||||||
|
AppsEntry(date: Date(), apps: [])
|
||||||
|
AppsEntry(date: Date(), apps: [], isPlaceholder: true)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user