diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index 051b6d4f..82b8d1b6 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -382,7 +382,6 @@ D57F2C9126E0070200B9FA39 /* EnableJITOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57F2C9026E0070200B9FA39 /* EnableJITOperation.swift */; }; D57F2C9426E01BC700B9FA39 /* UIDevice+Vibration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57F2C9326E01BC700B9FA39 /* UIDevice+Vibration.swift */; }; D57FE84428C7DB7100216002 /* ErrorLogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57FE84328C7DB7100216002 /* ErrorLogViewController.swift */; }; - D586222F2AA1161700A493E1 /* Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D586222D2AA115D000A493E1 /* Provider.swift */; }; D586D39B28EF58B0000E101F /* AltTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D586D39A28EF58B0000E101F /* AltTests.swift */; }; D58916FE28C7C55C00E39C8B /* LoggedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58916FD28C7C55C00E39C8B /* LoggedError.swift */; }; D5893F802A1419E800E767CD /* NSManagedObjectContext+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5893F7E2A14183200E767CD /* NSManagedObjectContext+Conveniences.swift */; }; @@ -920,7 +919,6 @@ D57F2C9326E01BC700B9FA39 /* UIDevice+Vibration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIDevice+Vibration.swift"; sourceTree = ""; }; D57FE84328C7DB7100216002 /* ErrorLogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorLogViewController.swift; sourceTree = ""; }; D581822C2A218A140087965B /* AltStore 13.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 13.xcdatamodel"; sourceTree = ""; }; - D586222D2AA115D000A493E1 /* Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Provider.swift; sourceTree = ""; }; D586D39828EF58B0000E101F /* AltTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AltTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D586D39A28EF58B0000E101F /* AltTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AltTests.swift; sourceTree = ""; }; D58916FD28C7C55C00E39C8B /* LoggedError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggedError.swift; sourceTree = ""; }; @@ -1551,7 +1549,6 @@ children = ( BF8B17F0250AC62400F8157F /* AltWidgetExtension.entitlements */, D5FD4EC42A952EAD0097BEE8 /* AltWidgetBundle.swift */, - D586222D2AA115D000A493E1 /* Provider.swift */, D577AB7A2A967DF5007FE952 /* AppsTimelineProvider.swift */, D50C29F22A8ECD71009AB488 /* Widgets */, D51AF9752A97D29100471312 /* Model */, @@ -2867,7 +2864,6 @@ BF98917E250AAC4F002ACF50 /* Countdown.swift in Sources */, D5151BE22A90363300C96F28 /* RefreshAllAppsWidgetIntent.swift in Sources */, D5FD4EC92A9530C00097BEE8 /* AppSnapshot.swift in Sources */, - D586222F2AA1161700A493E1 /* Provider.swift in Sources */, D5151BE72A90395400C96F28 /* View+AltWidget.swift in Sources */, BF98917F250AAC4F002ACF50 /* LockScreenWidget.swift in Sources */, D5FD4EC52A952EAD0097BEE8 /* AltWidgetBundle.swift in Sources */, diff --git a/AltWidget/AppsTimelineProvider.swift b/AltWidget/AppsTimelineProvider.swift index 398182b7..b14c42a8 100644 --- a/AltWidget/AppsTimelineProvider.swift +++ b/AltWidget/AppsTimelineProvider.swift @@ -200,3 +200,28 @@ extension AppsTimelineProvider: TimelineProvider } } } + +extension AppsTimelineProvider: IntentTimelineProvider +{ + typealias Intent = ViewAppIntent + + func getSnapshot(for intent: Intent, in context: Context, completion: @escaping (AppsEntry) -> Void) + { + Task { + let bundleIDs = [intent.identifier ?? StoreApp.altstoreAppID] + + let snapshot = await self.snapshot(for: bundleIDs) + completion(snapshot) + } + } + + func getTimeline(for intent: Intent, in context: Context, completion: @escaping (Timeline) -> Void) + { + Task { + let bundleIDs = [intent.identifier ?? StoreApp.altstoreAppID] + + let timeline = await self.timeline(for: bundleIDs) + completion(timeline) + } + } +} diff --git a/AltWidget/Provider.swift b/AltWidget/Provider.swift deleted file mode 100644 index ea65bf4a..00000000 --- a/AltWidget/Provider.swift +++ /dev/null @@ -1,144 +0,0 @@ -// -// Provider.swift -// AltStore -// -// Created by Riley Testut on 6/26/20. -// Copyright © 2020 Riley Testut. All rights reserved. -// - -import WidgetKit -import CoreData - -import AltStoreCore -import AltSign - -struct AppEntry: TimelineEntry -{ - var date: Date - var relevance: TimelineEntryRelevance? - - var app: AppSnapshot? - var isPlaceholder: Bool = false -} - -struct Provider: IntentTimelineProvider -{ - typealias Intent = ViewAppIntent - typealias Entry = AppEntry - - func placeholder(in context: Context) -> AppEntry - { - return AppEntry(date: Date(), app: nil, isPlaceholder: true) - } - - func getSnapshot(for configuration: ViewAppIntent, in context: Context, completion: @escaping (AppEntry) -> Void) - { - self.prepare { (result) in - do - { - let context = try result.get() - let snapshot = InstalledApp.fetchAltStore(in: context).map(AppSnapshot.init) - - let entry = AppEntry(date: Date(), app: snapshot) - completion(entry) - } - catch - { - print("Error preparing widget snapshot:", error) - - let entry = AppEntry(date: Date(), app: nil) - completion(entry) - } - } - } - - func getTimeline(for configuration: ViewAppIntent, in context: Context, completion: @escaping (Timeline) -> Void) { - self.prepare { (result) in - autoreleasepool { - do - { - let context = try result.get() - - let installedApp: InstalledApp? - - if let identifier = configuration.app?.identifier - { - let app = InstalledApp.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(InstalledApp.bundleIdentifier), identifier), - in: context) - installedApp = app - } - else - { - installedApp = InstalledApp.fetchAltStore(in: context) - } - - guard let snapshot = installedApp.map(AppSnapshot.init) else { throw ALTError(.invalidApp) } - - let currentDate = Calendar.current.startOfDay(for: Date()) - let numberOfDays = snapshot.expirationDate.numberOfCalendarDays(since: currentDate) - - // Generate a timeline consisting of one entry per day. - var entries: [AppEntry] = [] - - switch numberOfDays - { - case ..<0: - let entry = AppEntry(date: currentDate, relevance: TimelineEntryRelevance(score: 0.0), app: snapshot) - entries.append(entry) - - case 0: - let entry = AppEntry(date: currentDate, relevance: TimelineEntryRelevance(score: 1.0), app: snapshot) - entries.append(entry) - - default: - // To reduce memory consumption, we only generate entries for the next week. This includes: - // * 1 for each day the app is valid (up to 7) - // * 1 "0 days remaining" - // * 1 "Expired" - let numberOfEntries = min(numberOfDays, 7) + 2 - - let appEntries = (0 ..< numberOfEntries).map { (dayOffset) -> AppEntry in - let entryDate = Calendar.current.date(byAdding: .day, value: dayOffset, to: currentDate) ?? currentDate.addingTimeInterval(Double(dayOffset) * 60 * 60 * 24) - - let daysSinceRefresh = entryDate.numberOfCalendarDays(since: snapshot.refreshedDate) - let totalNumberOfDays = snapshot.expirationDate.numberOfCalendarDays(since: snapshot.refreshedDate) - - let score = (entryDate <= snapshot.expirationDate) ? Float(daysSinceRefresh + 1) / Float(totalNumberOfDays + 1) : 0 // Expired apps have a score of 0. - let entry = AppEntry(date: entryDate, relevance: TimelineEntryRelevance(score: score), app: snapshot) - return entry - } - - entries.append(contentsOf: appEntries) - } - - let timeline = Timeline(entries: entries, policy: .atEnd) - completion(timeline) - } - catch - { - print("Error preparing widget timeline:", error) - - let entry = AppEntry(date: Date(), app: nil) - let timeline = Timeline(entries: [entry], policy: .atEnd) - completion(timeline) - } - } - } - } - - private func prepare(completion: @escaping (Result) -> Void) - { - DatabaseManager.shared.start { (error) in - if let error = error - { - completion(.failure(error)) - } - else - { - DatabaseManager.shared.viewContext.perform { - completion(.success(DatabaseManager.shared.viewContext)) - } - } - } - } -} diff --git a/AltWidget/Widgets/AppDetailWidget.swift b/AltWidget/Widgets/AppDetailWidget.swift index 960c6f4c..dea97e8f 100644 --- a/AltWidget/Widgets/AppDetailWidget.swift +++ b/AltWidget/Widgets/AppDetailWidget.swift @@ -18,7 +18,7 @@ struct AppDetailWidget: Widget public var body: some WidgetConfiguration { let configuration = IntentConfiguration(kind: kind, intent: ViewAppIntent.self, - provider: Provider()) { (entry) in + provider: AppsTimelineProvider()) { (entry) in AppDetailWidgetView(entry: entry) } .supportedFamilies([.systemSmall]) @@ -39,11 +39,11 @@ struct AppDetailWidget: Widget private struct AppDetailWidgetView: View { - var entry: AppEntry + var entry: AppsEntry var body: some View { Group { - if let app = self.entry.app + if let app = self.entry.apps.first { let daysRemaining = app.expirationDate.numberOfCalendarDays(since: self.entry.date) @@ -130,7 +130,7 @@ private struct AppDetailWidgetView: View .frame(maxWidth: .infinity, maxHeight: .infinity) } } - .widgetBackground(backgroundView(icon: entry.app?.icon, tintColor: entry.app?.tintColor)) + .widgetBackground(backgroundView(icon: entry.apps.first?.icon, tintColor: entry.apps.first?.tintColor)) } } @@ -202,19 +202,19 @@ struct WidgetView_Previews: PreviewProvider { icon: UIImage(named: "Delta")) return Group { - AppDetailWidgetView(entry: AppEntry(date: Date(), app: altstore)) + AppDetailWidgetView(entry: AppsEntry(date: Date(), apps: [altstore])) .previewContext(WidgetPreviewContext(family: .systemSmall)) - AppDetailWidgetView(entry: AppEntry(date: Date(), app: delta)) + AppDetailWidgetView(entry: AppsEntry(date: Date(), apps: [delta])) .previewContext(WidgetPreviewContext(family: .systemSmall)) - AppDetailWidgetView(entry: AppEntry(date: Date(), app: expiredDelta)) + AppDetailWidgetView(entry: AppsEntry(date: Date(), apps: [expiredDelta])) .previewContext(WidgetPreviewContext(family: .systemSmall)) - AppDetailWidgetView(entry: AppEntry(date: Date(), app: nil)) + AppDetailWidgetView(entry: AppsEntry(date: Date(), apps: [])) .previewContext(WidgetPreviewContext(family: .systemSmall)) - AppDetailWidgetView(entry: AppEntry(date: Date(), app: nil, isPlaceholder: true)) + AppDetailWidgetView(entry: AppsEntry(date: Date(), apps: [], isPlaceholder: true)) .previewContext(WidgetPreviewContext(family: .systemSmall)) } } diff --git a/AltWidget/Widgets/LockScreenWidget.swift b/AltWidget/Widgets/LockScreenWidget.swift index a27044ac..d19aa9f4 100644 --- a/AltWidget/Widgets/LockScreenWidget.swift +++ b/AltWidget/Widgets/LockScreenWidget.swift @@ -20,7 +20,7 @@ struct TextLockScreenWidget: Widget { return IntentConfiguration(kind: kind, intent: ViewAppIntent.self, - provider: Provider()) { (entry) in + provider: AppsTimelineProvider()) { (entry) in ComplicationView(entry: entry, style: .text) } .supportedFamilies([.accessoryCircular]) @@ -43,7 +43,7 @@ struct IconLockScreenWidget: Widget { return IntentConfiguration(kind: kind, intent: ViewAppIntent.self, - provider: Provider()) { (entry) in + provider: AppsTimelineProvider()) { (entry) in ComplicationView(entry: entry, style: .icon) } .supportedFamilies([.accessoryCircular]) @@ -70,12 +70,12 @@ extension ComplicationView @available(iOS 16, *) private struct ComplicationView: View { - let entry: AppEntry + let entry: AppsEntry let style: Style var body: some View { - let refreshedDate = self.entry.app?.refreshedDate ?? .now - let expirationDate = self.entry.app?.expirationDate ?? .now + let refreshedDate = self.entry.apps.first?.refreshedDate ?? .now + let expirationDate = self.entry.apps.first?.expirationDate ?? .now let totalDays = expirationDate.numberOfCalendarDays(since: refreshedDate) let daysRemaining = expirationDate.numberOfCalendarDays(since: self.entry.date) @@ -159,23 +159,23 @@ struct ComplicationView_Previews: PreviewProvider { icon: UIImage(named: "AltStore")) return Group { - ComplicationView(entry: AppEntry(date: Date(), app: weekAltstore), style: .icon) + ComplicationView(entry: AppsEntry(date: Date(), apps: [weekAltstore]), style: .icon) .previewContext(WidgetPreviewContext(family: .accessoryCircular)) - ComplicationView(entry: AppEntry(date: expiredDate, app: weekAltstore), style: .icon) + ComplicationView(entry: AppsEntry(date: expiredDate, apps: [weekAltstore]), style: .icon) .previewContext(WidgetPreviewContext(family: .accessoryCircular)) - ComplicationView(entry: AppEntry(date: longRefreshedDate, app: yearAltstore), style: .icon) + ComplicationView(entry: AppsEntry(date: longRefreshedDate, apps: [yearAltstore]), style: .icon) .previewContext(WidgetPreviewContext(family: .accessoryCircular)) - ComplicationView(entry: AppEntry(date: Date(), app: weekAltstore), style: .text) + ComplicationView(entry: AppsEntry(date: Date(), apps: [weekAltstore]), style: .text) .previewContext(WidgetPreviewContext(family: .accessoryCircular)) - ComplicationView(entry: AppEntry(date: expiredDate, app: weekAltstore), style: .text) + ComplicationView(entry: AppsEntry(date: expiredDate, apps: [weekAltstore]), style: .text) .previewContext(WidgetPreviewContext(family: .accessoryCircular)) - ComplicationView(entry: AppEntry(date: longRefreshedDate, app: yearAltstore), style: .text) - .previewContext(WidgetPreviewContext(family: .accessoryCircular)) + ComplicationView(entry: AppsEntry(date: longRefreshedDate, apps: [yearAltstore]), style: .text) + .previewContext(WidgetPreviewContext(family: .accessoryCircular)) } } }