Files
SideStore/SideStoreApp/Sources/SideWidget/SideWidget.swift

243 lines
8.6 KiB
Swift
Raw Normal View History

2020-09-15 13:51:29 -07:00
//
2023-03-02 00:40:11 -05:00
// SideWidget.swift
// SideWidget
2020-09-15 13:51:29 -07:00
//
// Created by Riley Testut on 6/26/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
2023-03-01 00:48:36 -05:00
import CoreData
2020-09-15 13:51:29 -07:00
import SwiftUI
import UIKit
2023-03-01 00:48:36 -05:00
import WidgetKit
2020-09-15 13:51:29 -07:00
import AltSign
2023-03-01 00:48:36 -05:00
import SideStoreCore
import Roxas
import RoxasUIKit
2023-03-02 00:40:11 -05:00
import os.log
2023-03-01 00:48:36 -05:00
struct AppEntry: TimelineEntry {
2020-09-15 13:51:29 -07:00
var date: Date
var relevance: TimelineEntryRelevance?
2023-03-01 00:48:36 -05:00
2020-09-15 13:51:29 -07:00
var app: AppSnapshot?
var isPlaceholder: Bool = false
}
2023-03-01 00:48:36 -05:00
struct AppSnapshot {
2020-09-15 13:51:29 -07:00
var name: String
var bundleIdentifier: String
var expirationDate: Date
var refreshedDate: Date
2023-03-01 00:48:36 -05:00
2020-09-15 13:51:29 -07:00
var tintColor: UIColor?
var icon: UIImage?
}
2023-03-01 00:48:36 -05:00
extension AppSnapshot {
2020-09-15 13:51:29 -07:00
// Declared in extension so we retain synthesized initializer.
2023-03-01 00:48:36 -05:00
init(installedApp: InstalledApp) {
name = installedApp.name
bundleIdentifier = installedApp.bundleIdentifier
expirationDate = installedApp.expirationDate
refreshedDate = installedApp.refreshedDate
tintColor = installedApp.storeApp?.tintColor
2020-09-15 13:51:29 -07:00
let application = ALTApplication(fileURL: installedApp.fileURL)
2023-03-01 00:48:36 -05:00
icon = application?.icon?.resizing(toFill: CGSize(width: 180, height: 180))
2020-09-15 13:51:29 -07:00
}
}
2023-03-01 00:48:36 -05:00
struct Provider: IntentTimelineProvider {
2020-09-15 13:51:29 -07:00
typealias Intent = ViewAppIntent
typealias Entry = AppEntry
2023-03-01 00:48:36 -05:00
func placeholder(in _: Context) -> AppEntry {
AppEntry(date: Date(), app: nil, isPlaceholder: true)
2020-09-15 13:51:29 -07:00
}
2023-03-01 00:48:36 -05:00
func getSnapshot(for _: ViewAppIntent, in _: Context, completion: @escaping (AppEntry) -> Void) {
prepare { result in
do {
2020-09-15 13:51:29 -07:00
let context = try result.get()
let snapshot = InstalledApp.fetchAltStore(in: context).map(AppSnapshot.init)
let entry = AppEntry(date: Date(), app: snapshot)
completion(entry)
2023-03-01 00:48:36 -05:00
} catch {
2023-03-02 00:40:11 -05:00
os_log(" %@", type: .error , error.localizedDescription)
2023-03-01 00:48:36 -05:00
2020-09-15 13:51:29 -07:00
let entry = AppEntry(date: Date(), app: nil)
completion(entry)
}
}
}
2023-03-01 00:48:36 -05:00
func getTimeline(for configuration: ViewAppIntent, in _: Context, completion: @escaping (Timeline<AppEntry>) -> Void) {
prepare { result in
2020-09-15 13:51:29 -07:00
autoreleasepool {
2023-03-01 00:48:36 -05:00
do {
2020-09-15 13:51:29 -07:00
let context = try result.get()
2023-03-01 00:48:36 -05:00
2020-09-15 13:51:29 -07:00
let installedApp: InstalledApp?
2023-03-01 00:48:36 -05:00
if let identifier = configuration.app?.identifier {
2020-09-15 13:51:29 -07:00
let app = InstalledApp.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(InstalledApp.bundleIdentifier), identifier),
in: context)
installedApp = app
2023-03-01 00:48:36 -05:00
} else {
2020-09-15 13:51:29 -07:00
installedApp = InstalledApp.fetchAltStore(in: context)
}
2023-03-01 00:48:36 -05:00
guard let snapshot = installedApp.map(AppSnapshot.init) else { throw ALTError(.invalidApp) }
2023-03-01 00:48:36 -05:00
let currentDate = Calendar.current.startOfDay(for: Date())
let numberOfDays = snapshot.expirationDate.numberOfCalendarDays(since: currentDate)
2023-03-01 00:48:36 -05:00
2020-09-15 13:51:29 -07:00
// Generate a timeline consisting of one entry per day.
var entries: [AppEntry] = []
2023-03-01 00:48:36 -05:00
switch numberOfDays {
case ..<0:
let entry = AppEntry(date: currentDate, relevance: TimelineEntryRelevance(score: 0.0), app: snapshot)
entries.append(entry)
2023-03-01 00:48:36 -05:00
case 0:
let entry = AppEntry(date: currentDate, relevance: TimelineEntryRelevance(score: 1.0), app: snapshot)
entries.append(entry)
2023-03-01 00:48:36 -05:00
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
2023-03-01 00:48:36 -05:00
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)
2023-03-01 00:48:36 -05:00
let daysSinceRefresh = entryDate.numberOfCalendarDays(since: snapshot.refreshedDate)
let totalNumberOfDays = snapshot.expirationDate.numberOfCalendarDays(since: snapshot.refreshedDate)
2023-03-01 00:48:36 -05:00
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
}
2023-03-01 00:48:36 -05:00
entries.append(contentsOf: appEntries)
2020-09-15 13:51:29 -07:00
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
2023-03-01 00:48:36 -05:00
} catch {
2023-03-02 00:40:11 -05:00
os_log(" %@", type: .error , error.localizedDescription)
2023-03-01 00:48:36 -05:00
2020-09-15 13:51:29 -07:00
let entry = AppEntry(date: Date(), app: nil)
let timeline = Timeline(entries: [entry], policy: .atEnd)
completion(timeline)
2020-09-15 13:51:29 -07:00
}
}
}
}
2023-03-01 00:48:36 -05:00
private func prepare(completion: @escaping (Result<NSManagedObjectContext, Error>) -> Void) {
DatabaseManager.shared.start { error in
if let error = error {
2020-09-15 13:51:29 -07:00
completion(.failure(error))
2023-03-01 00:48:36 -05:00
} else {
2020-09-15 13:51:29 -07:00
DatabaseManager.shared.viewContext.perform {
completion(.success(DatabaseManager.shared.viewContext))
}
}
}
}
}
2023-03-01 00:48:36 -05:00
struct HomeScreenWidget: Widget {
2020-09-15 13:51:29 -07:00
private let kind: String = "AppDetail"
2023-03-01 00:48:36 -05:00
2020-09-15 13:51:29 -07:00
public var body: some WidgetConfiguration {
2023-03-01 00:48:36 -05:00
IntentConfiguration(kind: kind,
intent: ViewAppIntent.self,
provider: Provider()) { entry in
2020-09-15 13:51:29 -07:00
WidgetView(entry: entry)
}
.supportedFamilies([.systemSmall])
2023-03-02 00:40:11 -05:00
.configurationDisplayName("SideWidget")
2020-09-15 13:51:29 -07:00
.description("View remaining days until your sideloaded apps expire.")
}
}
2022-08-17 15:33:13 -05:00
2023-03-01 00:48:36 -05:00
struct TextLockScreenWidget: Widget {
private let kind: String = "TextLockAppDetail"
2023-03-01 00:48:36 -05:00
2022-08-17 15:33:13 -05:00
public var body: some WidgetConfiguration {
2023-03-01 00:48:36 -05:00
if #available(iOSApplicationExtension 16, *) {
2022-08-17 15:33:13 -05:00
return IntentConfiguration(kind: kind,
intent: ViewAppIntent.self,
2023-03-01 00:48:36 -05:00
provider: Provider()) { entry in
ComplicationView(entry: entry, style: .text)
2022-08-17 15:33:13 -05:00
}
.supportedFamilies([.accessoryCircular])
2023-03-02 00:40:11 -05:00
.configurationDisplayName("SideWidget (Text)")
.description("View remaining days until SideStore expires.")
2023-03-01 00:48:36 -05:00
} else {
2022-08-17 15:33:13 -05:00
return EmptyWidgetConfiguration()
}
}
}
2023-03-01 00:48:36 -05:00
struct IconLockScreenWidget: Widget {
private let kind: String = "IconLockAppDetail"
2023-03-01 00:48:36 -05:00
public var body: some WidgetConfiguration {
2023-03-01 00:48:36 -05:00
if #available(iOSApplicationExtension 16, *) {
return IntentConfiguration(kind: kind,
intent: ViewAppIntent.self,
2023-03-01 00:48:36 -05:00
provider: Provider()) { entry in
ComplicationView(entry: entry, style: .icon)
}
.supportedFamilies([.accessoryCircular])
2023-03-02 00:40:11 -05:00
.configurationDisplayName("SideWidget (Icon)")
.description("View remaining days until SideStore expires.")
2023-03-01 00:48:36 -05:00
} else {
return EmptyWidgetConfiguration()
}
}
}
2023-03-01 00:48:36 -05:00
//
2023-03-01 00:48:36 -05:00
// struct LockScreenWidget: Widget
// {
// private let kind: String = "LockAppDetail"
2023-03-01 00:48:36 -05:00
//
// 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])
2023-03-02 00:40:11 -05:00
// .configurationDisplayName("SideWidget")
// .description("View remaining days until SideStore expires.")
// }
// else
// {
// return EmptyWidgetConfiguration()
// }
// }
2023-03-01 00:48:36 -05:00
// }
2022-08-17 15:33:13 -05:00
@main
2023-03-02 00:40:11 -05:00
struct SideWidgets: WidgetBundle {
2022-08-17 15:33:13 -05:00
var body: some Widget {
HomeScreenWidget()
IconLockScreenWidget()
TextLockScreenWidget()
2022-08-17 15:33:13 -05:00
}
}