2020-09-15 13:51:29 -07:00
|
|
|
//
|
2023-08-31 13:50:27 -05:00
|
|
|
// Provider.swift
|
|
|
|
|
// AltStore
|
2020-09-15 13:51:29 -07:00
|
|
|
//
|
|
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-12 12:10:45 -07:00
|
|
|
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)
|
2021-10-06 12:18:08 -07:00
|
|
|
|
2020-09-15 13:51:29 -07:00
|
|
|
// Generate a timeline consisting of one entry per day.
|
2021-10-12 12:10:45 -07:00
|
|
|
var entries: [AppEntry] = []
|
|
|
|
|
|
|
|
|
|
switch numberOfDays
|
2020-09-15 13:51:29 -07:00
|
|
|
{
|
2021-10-12 12:10:45 -07:00
|
|
|
case ..<0:
|
|
|
|
|
let entry = AppEntry(date: currentDate, relevance: TimelineEntryRelevance(score: 0.0), app: snapshot)
|
|
|
|
|
entries.append(entry)
|
2021-10-04 18:41:50 -07:00
|
|
|
|
2021-10-12 12:10:45 -07:00
|
|
|
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
|
2021-10-06 12:18:08 -07:00
|
|
|
}
|
2021-10-12 12:10:45 -07:00
|
|
|
|
|
|
|
|
entries.append(contentsOf: appEntries)
|
2020-09-15 13:51:29 -07:00
|
|
|
}
|
2021-10-06 12:18:08 -07:00
|
|
|
|
|
|
|
|
let timeline = Timeline(entries: entries, policy: .atEnd)
|
|
|
|
|
completion(timeline)
|
2020-09-15 13:51:29 -07:00
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
print("Error preparing widget timeline:", error)
|
|
|
|
|
|
|
|
|
|
let entry = AppEntry(date: Date(), app: nil)
|
2021-10-06 12:18:08 -07:00
|
|
|
let timeline = Timeline(entries: [entry], policy: .atEnd)
|
|
|
|
|
completion(timeline)
|
2020-09-15 13:51:29 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-17 15:33:13 -05:00
|
|
|
struct HomeScreenWidget: Widget
|
2020-09-15 13:51:29 -07:00
|
|
|
{
|
|
|
|
|
private let kind: String = "AppDetail"
|
|
|
|
|
|
|
|
|
|
public var body: some WidgetConfiguration {
|
2023-08-18 18:37:52 -05:00
|
|
|
let configuration = IntentConfiguration(kind: kind,
|
2020-09-15 13:51:29 -07:00
|
|
|
intent: ViewAppIntent.self,
|
|
|
|
|
provider: Provider()) { (entry) in
|
|
|
|
|
WidgetView(entry: entry)
|
|
|
|
|
}
|
|
|
|
|
.supportedFamilies([.systemSmall])
|
|
|
|
|
.configurationDisplayName("AltWidget")
|
|
|
|
|
.description("View remaining days until your sideloaded apps expire.")
|
2023-08-18 18:37:52 -05:00
|
|
|
|
|
|
|
|
if #available(iOS 17, *)
|
|
|
|
|
{
|
|
|
|
|
return configuration
|
|
|
|
|
.contentMarginsDisabled()
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return configuration
|
|
|
|
|
}
|
2020-09-15 13:51:29 -07:00
|
|
|
}
|
|
|
|
|
}
|
2022-08-17 15:33:13 -05:00
|
|
|
|
2022-09-14 19:04:36 -07:00
|
|
|
struct TextLockScreenWidget: Widget
|
2022-08-17 15:33:13 -05:00
|
|
|
{
|
2022-09-14 19:04:36 -07:00
|
|
|
private let kind: String = "TextLockAppDetail"
|
2022-08-17 15:33:13 -05:00
|
|
|
|
|
|
|
|
public var body: some WidgetConfiguration {
|
|
|
|
|
if #available(iOSApplicationExtension 16, *)
|
|
|
|
|
{
|
|
|
|
|
return IntentConfiguration(kind: kind,
|
|
|
|
|
intent: ViewAppIntent.self,
|
|
|
|
|
provider: Provider()) { (entry) in
|
2022-09-14 19:04:36 -07:00
|
|
|
ComplicationView(entry: entry, style: .text)
|
2022-08-17 15:33:13 -05:00
|
|
|
}
|
|
|
|
|
.supportedFamilies([.accessoryCircular])
|
2022-09-14 19:04:36 -07:00
|
|
|
.configurationDisplayName("AltWidget (Text)")
|
2022-11-05 23:50:07 -07:00
|
|
|
.description("View remaining days until SideStore expires.")
|
2022-08-17 15:33:13 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return EmptyWidgetConfiguration()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-14 19:04:36 -07:00
|
|
|
struct IconLockScreenWidget: Widget
|
|
|
|
|
{
|
|
|
|
|
private let kind: String = "IconLockAppDetail"
|
|
|
|
|
|
|
|
|
|
public var body: some WidgetConfiguration {
|
|
|
|
|
if #available(iOSApplicationExtension 16, *)
|
|
|
|
|
{
|
|
|
|
|
return IntentConfiguration(kind: kind,
|
|
|
|
|
intent: ViewAppIntent.self,
|
|
|
|
|
provider: Provider()) { (entry) in
|
|
|
|
|
ComplicationView(entry: entry, style: .icon)
|
|
|
|
|
}
|
|
|
|
|
.supportedFamilies([.accessoryCircular])
|
|
|
|
|
.configurationDisplayName("AltWidget (Icon)")
|
|
|
|
|
.description("View remaining days until SideStore expires.")
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return EmptyWidgetConfiguration()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//
|
|
|
|
|
//struct LockScreenWidget: Widget
|
|
|
|
|
//{
|
|
|
|
|
// private let kind: String = "LockAppDetail"
|
|
|
|
|
//
|
|
|
|
|
// public var body: some WidgetConfiguration {
|
|
|
|
|
// if #available(iOSApplicationExtension 16, *)
|
|
|
|
|
// {
|
|
|
|
|
// return IntentConfiguration(kind: kind,
|
|
|
|
|
// intent: ViewAppIntent.self,
|
|
|
|
|
// provider: Provider()) { (entry) in
|
|
|
|
|
// ComplicationView(entry: entry, style: .icon)
|
|
|
|
|
// }
|
|
|
|
|
// .supportedFamilies([.accessoryCircular])
|
|
|
|
|
// .configurationDisplayName("AltWidget")
|
|
|
|
|
// .description("View remaining days until SideStore expires.")
|
|
|
|
|
// }
|
|
|
|
|
// else
|
|
|
|
|
// {
|
|
|
|
|
// return EmptyWidgetConfiguration()
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//}
|
|
|
|
|
|
2022-08-17 15:33:13 -05:00
|
|
|
@main
|
|
|
|
|
struct AltWidgets: WidgetBundle
|
|
|
|
|
{
|
|
|
|
|
var body: some Widget {
|
|
|
|
|
HomeScreenWidget()
|
2022-09-14 19:04:36 -07:00
|
|
|
IconLockScreenWidget()
|
|
|
|
|
TextLockScreenWidget()
|
2022-08-17 15:33:13 -05:00
|
|
|
}
|
|
|
|
|
}
|