diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index a3cf6c68..e86bbd7f 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -348,9 +348,11 @@ BFF7C90F257844C900E55F36 /* AltXPC.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = BFF7C904257844C900E55F36 /* AltXPC.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; BFF7C920257844FA00E55F36 /* ALTPluginService.m in Sources */ = {isa = PBXBuildFile; fileRef = BF5C5FCE237DF69100EDD0C6 /* ALTPluginService.m */; }; BFF7C9342578492100E55F36 /* ALTAnisetteData.m in Sources */ = {isa = PBXBuildFile; fileRef = BFB49AA823834CF900D542D9 /* ALTAnisetteData.m */; }; + D504F42628AD72C50014BB5D /* ProgressRing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D504F42528AD72C50014BB5D /* ProgressRing.swift */; }; D533E8B72727841800A9B5DD /* libAppleArchive.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D533E8B62727841800A9B5DD /* libAppleArchive.tbd */; settings = {ATTRIBUTES = (Weak, ); }; }; D533E8BC2727BBEE00A9B5DD /* libfragmentzip.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D533E8BB2727BBEE00A9B5DD /* libfragmentzip.a */; }; D533E8BE2727BBF800A9B5DD /* libcurl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D533E8BD2727BBF800A9B5DD /* libcurl.a */; }; + D55E163728776CB700A627A1 /* ComplicationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55E163528776CB000A627A1 /* ComplicationView.swift */; }; D57DF638271E32F000677701 /* PatchApp.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D57DF637271E32F000677701 /* PatchApp.storyboard */; }; D57DF63F271E51E400677701 /* ALTAppPatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = D57DF63E271E51E400677701 /* ALTAppPatcher.m */; }; D57F2C9126E0070200B9FA39 /* EnableJITOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57F2C9026E0070200B9FA39 /* EnableJITOperation.swift */; }; @@ -811,10 +813,12 @@ BFF7EC4C25081E9300BDE521 /* AltStore 8.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 8.xcdatamodel"; sourceTree = ""; }; BFFCFA45248835530077BFCE /* AltDaemon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AltDaemon.entitlements; sourceTree = ""; }; C9EEAA842DA87A88A870053B /* Pods_AltStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AltStore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D504F42528AD72C50014BB5D /* ProgressRing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressRing.swift; sourceTree = ""; }; D533E8B62727841800A9B5DD /* libAppleArchive.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libAppleArchive.tbd; path = usr/lib/libAppleArchive.tbd; sourceTree = SDKROOT; }; D533E8B82727B61400A9B5DD /* fragmentzip.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = fragmentzip.h; sourceTree = ""; }; D533E8BB2727BBEE00A9B5DD /* libfragmentzip.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libfragmentzip.a; path = Dependencies/fragmentzip/libfragmentzip.a; sourceTree = SOURCE_ROOT; }; D533E8BD2727BBF800A9B5DD /* libcurl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libcurl.a; path = Dependencies/libcurl/libcurl.a; sourceTree = SOURCE_ROOT; }; + D55E163528776CB000A627A1 /* ComplicationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComplicationView.swift; sourceTree = ""; }; D57DF637271E32F000677701 /* PatchApp.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = PatchApp.storyboard; sourceTree = ""; }; D57DF63D271E51E400677701 /* ALTAppPatcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTAppPatcher.h; sourceTree = ""; }; D57DF63E271E51E400677701 /* ALTAppPatcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTAppPatcher.m; sourceTree = ""; }; @@ -1398,6 +1402,8 @@ BF98917D250AAC4F002ACF50 /* AltWidget.swift */, BF42345825101C1D006D1EB2 /* WidgetView.swift */, BF98917C250AAC4F002ACF50 /* Countdown.swift */, + D55E163528776CB000A627A1 /* ComplicationView.swift */, + D504F42528AD72C50014BB5D /* ProgressRing.swift */, BF989170250AABF4002ACF50 /* Assets.xcassets */, BF989172250AABF4002ACF50 /* Info.plist */, ); @@ -2543,7 +2549,9 @@ files = ( BF98917E250AAC4F002ACF50 /* Countdown.swift in Sources */, BF42345A25101C35006D1EB2 /* WidgetView.swift in Sources */, + D55E163728776CB700A627A1 /* ComplicationView.swift in Sources */, BF98917F250AAC4F002ACF50 /* AltWidget.swift in Sources */, + D504F42628AD72C50014BB5D /* ProgressRing.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/AltWidget/AltWidget.swift b/AltWidget/AltWidget.swift index ca64beaa..8dbd8f7c 100644 --- a/AltWidget/AltWidget.swift +++ b/AltWidget/AltWidget.swift @@ -173,8 +173,7 @@ struct Provider: IntentTimelineProvider } } -@main -struct AltWidget: Widget +struct HomeScreenWidget: Widget { private let kind: String = "AppDetail" @@ -189,3 +188,35 @@ struct AltWidget: Widget .description("View remaining days until your sideloaded apps expire.") } } + +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) + } + .supportedFamilies([.accessoryCircular]) + .configurationDisplayName("AltWidget") + .description("View remaining days until AltStore expires.") + } + else + { + return EmptyWidgetConfiguration() + } + } +} + +@main +struct AltWidgets: WidgetBundle +{ + var body: some Widget { + HomeScreenWidget() + LockScreenWidget() + } +} diff --git a/AltWidget/ComplicationView.swift b/AltWidget/ComplicationView.swift new file mode 100644 index 00000000..19222926 --- /dev/null +++ b/AltWidget/ComplicationView.swift @@ -0,0 +1,86 @@ +// +// ComplicationView.swift +// AltStore +// +// Created by Riley Testut on 7/7/22. +// Copyright © 2022 Riley Testut. All rights reserved. +// + +import SwiftUI +import WidgetKit + +@available(iOS 16, *) +struct ComplicationView: View +{ + let entry: AppEntry + + var body: some View { + let refreshedDate = self.entry.app?.refreshedDate ?? .now + let expirationDate = self.entry.app?.expirationDate ?? .now + + let totalDays = expirationDate.numberOfCalendarDays(since: refreshedDate) + let daysRemaining = expirationDate.numberOfCalendarDays(since: self.entry.date) + + let progress = Double(daysRemaining) / Double(totalDays) + + ZStack(alignment: .center) { + ProgressRing(progress: progress) { + if daysRemaining < 0 + { + Text("Expired") + .font(.system(size: 10, weight: .bold)) + } + else + { + VStack(spacing: -1) { + Text("\(daysRemaining)") + .font(.system(size: 20, weight: .bold, design: .rounded)) + + Text(daysRemaining == 1 ? "DAY" : "DAYS") + .font(.caption) + } + .offset(y: -1) + } + } + } + .unredacted() + } +} + +@available(iOS 16, *) +struct ComplicationView_Previews: PreviewProvider { + static var previews: some View { + 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 expiredDate = shortExpirationDate.addingTimeInterval(1 * 60 * 60 * 24) + + let weekAltstore = AppSnapshot(name: "AltStore", + bundleIdentifier: "com.rileytestut.AltStore", + expirationDate: shortExpirationDate, + refreshedDate: shortRefreshedDate, + tintColor: .altPrimary, + icon: UIImage(named: "AltStore")) + + let yearAltstore = AppSnapshot(name: "AltStore", + bundleIdentifier: "com.rileytestut.AltStore", + expirationDate: longExpirationDate, + refreshedDate: longRefreshedDate, + tintColor: .altPrimary, + icon: UIImage(named: "AltStore")) + + return Group { + ComplicationView(entry: AppEntry(date: Date(), app: weekAltstore)) + .previewContext(WidgetPreviewContext(family: .accessoryCircular)) + + ComplicationView(entry: AppEntry(date: expiredDate, app: weekAltstore)) + .previewContext(WidgetPreviewContext(family: .accessoryCircular)) + + ComplicationView(entry: AppEntry(date: longRefreshedDate, app: yearAltstore)) + .previewContext(WidgetPreviewContext(family: .accessoryCircular)) + } + } +} diff --git a/AltWidget/ProgressRing.swift b/AltWidget/ProgressRing.swift new file mode 100644 index 00000000..4464e20f --- /dev/null +++ b/AltWidget/ProgressRing.swift @@ -0,0 +1,54 @@ +// +// ProgressRing.swift +// AltWidgetExtension +// +// Created by Riley Testut on 8/17/22. +// Copyright © 2022 Riley Testut. All rights reserved. +// + +import SwiftUI +import WidgetKit + +struct ProgressRing: View +{ + let progress: Double + + private let content: Content + + init(progress: Double, @ViewBuilder content: () -> Content) + { + self.progress = progress + self.content = content() + } + + var body: some View { + ZStack(alignment: .center) { + ring(progress: 1.0) + .opacity(0.3) + + ring(progress: self.progress) + + content + } + } + + @ViewBuilder + private func ring(progress: Double) -> some View { + let strokeStyle = StrokeStyle(lineWidth: 4.0, lineCap: .round, lineJoin: .round) + + Circle() + .inset(by: 2.0) + .trim(from: 0.0, to: progress) + .rotation(Angle(degrees: -90), anchor: .center) + .stroke(style: strokeStyle) + } +} + +struct ProgressRing_Previews: PreviewProvider { + static var previews: some View { + ProgressRing(progress: 0.5) { + EmptyView() + } + .previewContext(WidgetPreviewContext(family: .systemSmall)) + } +}