[AltWidget] Refactors previous widgets to use AppsTimelineProvider

This commit is contained in:
Riley Testut
2023-09-01 13:30:19 -05:00
parent 21c2f8d5ce
commit ce3b032dc1
5 changed files with 46 additions and 169 deletions

View File

@@ -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<Void, Never> {
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<AppsEntry>) -> Void)
{
Task<Void, Never> {
let bundleIDs = [intent.identifier ?? StoreApp.altstoreAppID]
let timeline = await self.timeline(for: bundleIDs)
completion(timeline)
}
}
}

View File

@@ -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<AppEntry>) -> 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<NSManagedObjectContext, Error>) -> Void)
{
DatabaseManager.shared.start { (error) in
if let error = error
{
completion(.failure(error))
}
else
{
DatabaseManager.shared.viewContext.perform {
completion(.success(DatabaseManager.shared.viewContext))
}
}
}
}
}

View File

@@ -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))
}
}

View File

@@ -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))
}
}
}