Compare commits

...

9 Commits

Author SHA1 Message Date
SternXD
a0d3c5ed4a fix(#703): Allow mobiledevicepair extension for pairing
Signed-off-by: Stern <stern@sidestore.io>
2026-05-09 15:47:00 -04:00
Mod4
0d8a2df802 fix(widget): blank app icons
Signed-off-by: Mod4 <omarelfarok24135@gmail.com>
Co-authored-by: mahee96 <47920326+mahee96@users.noreply.github.com>
2026-05-09 15:45:42 -04:00
project516
9223da751d Update actions (#1280) 2026-05-08 16:34:09 -07:00
mahee96
e744ed8b67 0.6.4 tag marker 2026-05-05 13:09:20 +05:30
mahee96
20714673b2 - Fix: corrected device IP in vpn configuration screen + corrected altsign package to use master-branch 2026-05-05 12:15:45 +05:30
mahee96
7f2be4cd58 Merge branch 'develop' 2026-05-05 12:02:25 +05:30
nythepegasus
f055f33bec chore: bump to 0.6.4 [noci]
Signed-off-by: nythepegasus <mobile@nythepegas.us>
2026-05-05 00:49:16 -04:00
Magesh K
2b71c36ace Merge pull request #944 from SideStore/develop
Merge develop into main for 0.6.1 release
2025-04-08 22:03:54 -07:00
Zero King
4aca1dfa43 fix: typo in hasUpdate comparison
Signed-off-by: Zero King <l2dy@icloud.com>
2025-03-08 22:43:20 +05:30
14 changed files with 281 additions and 80 deletions

View File

@@ -20,7 +20,7 @@ jobs:
UPSTREAM_CHANNEL: "nightly" UPSTREAM_CHANNEL: "nightly"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
fetch-depth: 0 fetch-depth: 0
@@ -54,13 +54,13 @@ jobs:
echo "MARKETING_VERSION=$NORMALIZED_VERSION" | tee -a $GITHUB_ENV echo "MARKETING_VERSION=$NORMALIZED_VERSION" | tee -a $GITHUB_ENV
- name: Setup Xcode - name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.6.0 uses: maxim-lobanov/setup-xcode@v1.7.0
with: with:
xcode-version: "26.2" xcode-version: "26.2"
- name: Restore Cache (exact) - name: Restore Cache (exact)
id: xcode-cache-exact id: xcode-cache-exact
uses: actions/cache/restore@v3 uses: actions/cache/restore@v5
with: with:
path: | path: |
~/Library/Developer/Xcode/DerivedData ~/Library/Developer/Xcode/DerivedData
@@ -70,7 +70,7 @@ jobs:
- name: Restore Cache (last) - name: Restore Cache (last)
if: steps.xcode-cache-exact.outputs.cache-hit != 'true' if: steps.xcode-cache-exact.outputs.cache-hit != 'true'
id: xcode-cache-fallback id: xcode-cache-fallback
uses: actions/cache/restore@v3 uses: actions/cache/restore@v5
with: with:
path: | path: |
~/Library/Developer/Xcode/DerivedData ~/Library/Developer/Xcode/DerivedData
@@ -119,7 +119,7 @@ jobs:
- name: Save Cache - name: Save Cache
if: ${{ steps.xcode-cache-fallback.outputs.cache-hit != 'true' }} if: ${{ steps.xcode-cache-fallback.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v3 uses: actions/cache/save@v5
with: with:
path: | path: |
~/Library/Developer/Xcode/DerivedData ~/Library/Developer/Xcode/DerivedData
@@ -141,12 +141,12 @@ jobs:
# -------------------------------------------------- # --------------------------------------------------
# artifacts # artifacts
# -------------------------------------------------- # --------------------------------------------------
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v7
with: with:
name: build-logs-${{ env.MARKETING_VERSION }}.zip name: build-logs-${{ env.MARKETING_VERSION }}.zip
path: build-logs.zip path: build-logs.zip
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v7
if: > if: >
vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_BUILD == '1' vars.ENABLE_TESTS_BUILD == '1'
@@ -154,7 +154,7 @@ jobs:
name: tests-build-logs-${{ env.SHORT_COMMIT }}.zip name: tests-build-logs-${{ env.SHORT_COMMIT }}.zip
path: tests-build-logs.zip path: tests-build-logs.zip
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v7
if: > if: >
vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_RUN == '1' vars.ENABLE_TESTS_RUN == '1'
@@ -166,12 +166,12 @@ jobs:
with: with:
name: SideStore-${{ env.MARKETING_VERSION }}.ipa name: SideStore-${{ env.MARKETING_VERSION }}.ipa
path: SideStore.ipa path: SideStore.ipa
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v7
with: with:
name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip
path: SideStore.dSYMs.zip path: SideStore.dSYMs.zip
- uses: actions/checkout@v4 - uses: actions/checkout@v7
if: env.DEPLOY_KEY != '' if: env.DEPLOY_KEY != ''
with: with:
repository: "SideStore/apps-v2.json" repository: "SideStore/apps-v2.json"

View File

@@ -24,7 +24,7 @@ jobs:
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/github-script@v6 - uses: actions/github-script@v9
with: with:
# This snippet is public-domain, taken from # This snippet is public-domain, taken from
# https://github.com/oprypin/nightly.link/blob/master/.github/workflows/pr-comment.yml # https://github.com/oprypin/nightly.link/blob/master/.github/workflows/pr-comment.yml

View File

@@ -22,7 +22,7 @@ jobs:
UPSTREAM_CHANNEL: "" UPSTREAM_CHANNEL: ""
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
fetch-depth: 0 fetch-depth: 0
@@ -78,14 +78,14 @@ jobs:
- name: Setup Xcode - name: Setup Xcode
if: steps.build_gate.outputs.should_skip != 'true' if: steps.build_gate.outputs.should_skip != 'true'
uses: maxim-lobanov/setup-xcode@v1.6.0 uses: maxim-lobanov/setup-xcode@v1.7.0
with: with:
xcode-version: "26.4" xcode-version: "26.4"
- name: Restore Cache (exact) - name: Restore Cache (exact)
if: steps.build_gate.outputs.should_skip != 'true' if: steps.build_gate.outputs.should_skip != 'true'
id: xcode-cache-exact id: xcode-cache-exact
uses: actions/cache/restore@v3 uses: actions/cache/restore@v5
with: with:
path: | path: |
~/Library/Developer/Xcode/DerivedData ~/Library/Developer/Xcode/DerivedData
@@ -97,7 +97,7 @@ jobs:
steps.build_gate.outputs.should_skip != 'true' && steps.build_gate.outputs.should_skip != 'true' &&
steps.xcode-cache-exact.outputs.cache-hit != 'true' steps.xcode-cache-exact.outputs.cache-hit != 'true'
id: xcode-cache-fallback id: xcode-cache-fallback
uses: actions/cache/restore@v3 uses: actions/cache/restore@v5
with: with:
path: | path: |
~/Library/Developer/Xcode/DerivedData ~/Library/Developer/Xcode/DerivedData
@@ -151,7 +151,7 @@ jobs:
if: > if: >
steps.build_gate.outputs.should_skip != 'true' && steps.build_gate.outputs.should_skip != 'true' &&
steps.xcode-cache-fallback.outputs.cache-hit != 'true' steps.xcode-cache-fallback.outputs.cache-hit != 'true'
uses: actions/cache/save@v3 uses: actions/cache/save@v5
with: with:
path: | path: |
~/Library/Developer/Xcode/DerivedData ~/Library/Developer/Xcode/DerivedData
@@ -174,13 +174,13 @@ jobs:
# -------------------------------------------------- # --------------------------------------------------
# artifacts # artifacts
# -------------------------------------------------- # --------------------------------------------------
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v7
if: steps.build_gate.outputs.should_skip != 'true' if: steps.build_gate.outputs.should_skip != 'true'
with: with:
name: build-logs-${{ env.MARKETING_VERSION }}.zip name: build-logs-${{ env.MARKETING_VERSION }}.zip
path: build-logs.zip path: build-logs.zip
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v7
if: > if: >
steps.build_gate.outputs.should_skip != 'true' && steps.build_gate.outputs.should_skip != 'true' &&
vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS == '1' &&
@@ -189,7 +189,7 @@ jobs:
name: tests-build-logs-${{ env.SHORT_COMMIT }}.zip name: tests-build-logs-${{ env.SHORT_COMMIT }}.zip
path: tests-build-logs.zip path: tests-build-logs.zip
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v7
if: > if: >
steps.build_gate.outputs.should_skip != 'true' && steps.build_gate.outputs.should_skip != 'true' &&
vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS == '1' &&
@@ -198,18 +198,18 @@ jobs:
name: tests-run-logs-${{ env.SHORT_COMMIT }}.zip name: tests-run-logs-${{ env.SHORT_COMMIT }}.zip
path: tests-run-logs.zip path: tests-run-logs.zip
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v7
if: steps.build_gate.outputs.should_skip != 'true' if: steps.build_gate.outputs.should_skip != 'true'
with: with:
name: SideStore-${{ env.MARKETING_VERSION }}.ipa name: SideStore-${{ env.MARKETING_VERSION }}.ipa
path: SideStore.ipa path: SideStore.ipa
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v7
if: steps.build_gate.outputs.should_skip != 'true' if: steps.build_gate.outputs.should_skip != 'true'
with: with:
name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip
path: SideStore.dSYMs.zip path: SideStore.dSYMs.zip
- uses: actions/checkout@v4 - uses: actions/checkout@v6
if: steps.build_gate.outputs.should_skip != 'true' && env.DEPLOY_KEY != '' if: steps.build_gate.outputs.should_skip != 'true' && env.DEPLOY_KEY != ''
with: with:
repository: "SideStore/apps-v2.json" repository: "SideStore/apps-v2.json"

View File

@@ -16,7 +16,7 @@ jobs:
runs-on: macos-26 runs-on: macos-26
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
fetch-depth: 1 # shallow clone just for PR fetch-depth: 1 # shallow clone just for PR
@@ -33,13 +33,13 @@ jobs:
echo "MARKETING_VERSION=$NORMALIZED_VERSION" | tee -a $GITHUB_ENV echo "MARKETING_VERSION=$NORMALIZED_VERSION" | tee -a $GITHUB_ENV
- name: Setup Xcode - name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.6.0 uses: maxim-lobanov/setup-xcode@v1.7.0
with: with:
xcode-version: "26.2" xcode-version: "26.2"
- name: Restore Cache (exact) - name: Restore Cache (exact)
id: xcode-cache-exact id: xcode-cache-exact
uses: actions/cache/restore@v3 uses: actions/cache/restore@v5
with: with:
path: | path: |
~/Library/Developer/Xcode/DerivedData ~/Library/Developer/Xcode/DerivedData
@@ -49,7 +49,7 @@ jobs:
- name: Restore Cache (last) - name: Restore Cache (last)
if: steps.xcode-cache-exact.outputs.cache-hit != 'true' if: steps.xcode-cache-exact.outputs.cache-hit != 'true'
id: xcode-cache-fallback id: xcode-cache-fallback
uses: actions/cache/restore@v3 uses: actions/cache/restore@v5
with: with:
path: | path: |
~/Library/Developer/Xcode/DerivedData ~/Library/Developer/Xcode/DerivedData
@@ -67,24 +67,24 @@ jobs:
- name: Save Cache - name: Save Cache
if: ${{ steps.xcode-cache-fallback.outputs.cache-hit != 'true' }} if: ${{ steps.xcode-cache-fallback.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v3 uses: actions/cache/save@v5
with: with:
path: | path: |
~/Library/Developer/Xcode/DerivedData ~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm ~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-${{ github.ref_name }}-${{ github.sha }} key: xcode-build-cache-${{ github.ref_name }}-${{ github.sha }}
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v7
with: with:
name: build-logs-${{ env.MARKETING_VERSION }}.zip name: build-logs-${{ env.MARKETING_VERSION }}.zip
path: build-logs.zip path: build-logs.zip
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v7
with: with:
name: SideStore-${{ env.MARKETING_VERSION }}.ipa name: SideStore-${{ env.MARKETING_VERSION }}.ipa
path: SideStore-${{ env.MARKETING_VERSION }}.ipa path: SideStore-${{ env.MARKETING_VERSION }}.ipa
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v7
with: with:
name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip
path: SideStore.dSYMs.zip path: SideStore.dSYMs.zip

View File

@@ -21,7 +21,7 @@ jobs:
UPSTREAM_CHANNEL: "" UPSTREAM_CHANNEL: ""
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
fetch-depth: 0 fetch-depth: 0
@@ -50,13 +50,13 @@ jobs:
echo "SHORT_COMMIT=$SHORT_COMMIT" | tee -a $GITHUB_ENV echo "SHORT_COMMIT=$SHORT_COMMIT" | tee -a $GITHUB_ENV
- name: Setup Xcode - name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.6.0 uses: maxim-lobanov/setup-xcode@v1.7.0
with: with:
xcode-version: "26.0" xcode-version: "26.4"
- name: Restore Cache (exact) - name: Restore Cache (exact)
id: xcode-cache-exact id: xcode-cache-exact
uses: actions/cache/restore@v3 uses: actions/cache/restore@v5
with: with:
path: | path: |
~/Library/Developer/Xcode/DerivedData ~/Library/Developer/Xcode/DerivedData
@@ -66,7 +66,7 @@ jobs:
- name: Restore Cache (last) - name: Restore Cache (last)
if: steps.xcode-cache-exact.outputs.cache-hit != 'true' if: steps.xcode-cache-exact.outputs.cache-hit != 'true'
id: xcode-cache-fallback id: xcode-cache-fallback
uses: actions/cache/restore@v3 uses: actions/cache/restore@v5
with: with:
path: | path: |
~/Library/Developer/Xcode/DerivedData ~/Library/Developer/Xcode/DerivedData
@@ -84,24 +84,24 @@ jobs:
- name: Save Cache - name: Save Cache
if: ${{ steps.xcode-cache-fallback.outputs.cache-hit != 'true' }} if: ${{ steps.xcode-cache-fallback.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v3 uses: actions/cache/save@v5
with: with:
path: | path: |
~/Library/Developer/Xcode/DerivedData ~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm ~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-stable-${{ github.sha }} key: xcode-build-cache-stable-${{ github.sha }}
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v7
with: with:
name: build-logs-${{ env.MARKETING_VERSION }}.zip name: build-logs-${{ env.MARKETING_VERSION }}.zip
path: build-logs.zip path: build-logs.zip
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v7
with: with:
name: SideStore-${{ env.MARKETING_VERSION }}.ipa name: SideStore-${{ env.MARKETING_VERSION }}.ipa
path: SideStore.ipa path: SideStore.ipa
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v7
with: with:
name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip
path: SideStore.dSYMs.zip path: SideStore.dSYMs.zip

View File

@@ -2427,8 +2427,8 @@
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/SideStore/AltSign"; repositoryURL = "https://github.com/SideStore/AltSign";
requirement = { requirement = {
kind = upToNextMajorVersion; branch = master;
minimumVersion = 0.1.0; kind = branch;
}; };
}; };
A82067C22D03E0DE00645C0D /* XCRemoteSwiftPackageReference "SemanticVersion" */ = { A82067C22D03E0DE00645C0D /* XCRemoteSwiftPackageReference "SemanticVersion" */ = {

View File

@@ -242,6 +242,7 @@
<key>public.filename-extension</key> <key>public.filename-extension</key>
<array> <array>
<string>mobiledevicepairing</string> <string>mobiledevicepairing</string>
<string>mobiledevicepair</string>
</array> </array>
</dict> </dict>
</dict> </dict>

View File

@@ -98,7 +98,7 @@ final class TunnelConfig: ObservableObject {
static let shared = TunnelConfig() static let shared = TunnelConfig()
private static let defaultOverrideIP: String = { private static let defaultOverrideIP: String = {
if #available(iOS 26.4, *) { return "192.168.1.50" } // if #available(iOS 26.4, *) { return "192.168.1.50" }
return "10.7.0.1" return "10.7.0.1"
}() }()

View File

@@ -7,6 +7,7 @@
// //
import SwiftUI import SwiftUI
import WidgetKit
extension View extension View
{ {
@@ -78,4 +79,59 @@ extension View
} }
} }
/// Opts this view into the widget accent group on iOS 16+, which lets the
/// system tint it with the user's chosen colour in tinted (accented) mode.
/// No-op on older OS versions where the API does not exist.
@ViewBuilder
func widgetAccentableIfAvailable() -> some View
{
if #available(iOSApplicationExtension 16, *)
{
self.widgetAccentable()
}
else
{
self
}
}
/// Applies `luminanceToAlpha()` only when the widget is rendering in
/// accented (tinted) mode on iOS 16+. This converts the view's pixel
/// brightness into opacity so the system can overlay the user's chosen
/// tint colour correctly without it, images appear as white rectangles
/// in tinted mode. No-op in fullColor/dark/light mode and on older OS.
@ViewBuilder
func luminanceToAlphaInAccentedMode() -> some View
{
if #available(iOSApplicationExtension 16, *)
{
LuminanceToAlphaWrapper(content: self)
}
else
{
self
}
}
}
/// Helper view that reads widgetRenderingMode (iOS 16+) and conditionally
/// applies luminanceToAlpha(). Kept separate so the environment read is
/// cleanly scoped behind the @available gate.
@available(iOSApplicationExtension 16, *)
private struct LuminanceToAlphaWrapper<Content: View>: View
{
let content: Content
@Environment(\.widgetRenderingMode) private var renderingMode
var body: some View {
if renderingMode == .accented
{
content.luminanceToAlpha()
}
else
{
content
}
}
} }

View File

@@ -0,0 +1,73 @@
//
// ViewAppIntent.swift
// AltWidgetExtension
//
// Replaces the legacy SiriKit ViewAppIntent (ViewApp.intentdefinition) with a
// modern AppIntents-based intent. Required because IntentConfiguration does not
// support containerBackground on iOS 17+, causing the blank-widget bug.
//
import AppIntents
import WidgetKit
import AltStoreCore
// Represents one installed app in the picker list.
@available(iOSApplicationExtension 17, *)
struct InstalledAppEntity: AppEntity
{
// Disambiguates from the AppEntity name used in AppIntents framework.
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Installed App"
static var defaultQuery = InstalledAppQuery()
var id: String // bundle identifier
var name: String
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "\(name)")
}
}
@available(iOSApplicationExtension 17, *)
struct InstalledAppQuery: EntityQuery
{
func entities(for identifiers: [String]) async throws -> [InstalledAppEntity]
{
try await DatabaseManager.shared.start()
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
return try await context.performAsync {
let fetchRequest = InstalledApp.fetchRequest()
fetchRequest.predicate = NSPredicate(
format: "%K IN %@",
#keyPath(InstalledApp.bundleIdentifier),
identifiers
)
fetchRequest.returnsObjectsAsFaults = false
let apps = try context.fetch(fetchRequest)
return apps.map { InstalledAppEntity(id: $0.bundleIdentifier, name: $0.name) }
}
}
func suggestedEntities() async throws -> [InstalledAppEntity]
{
try await DatabaseManager.shared.start()
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
return try await context.performAsync {
InstalledApp.all(in: context)
.map { InstalledAppEntity(id: $0.bundleIdentifier, name: $0.name) }
.sorted { $0.name < $1.name }
}
}
}
@available(iOSApplicationExtension 17, *)
struct SelectAppIntent: WidgetConfigurationIntent
{
static var title: LocalizedStringResource = "Select App"
static var description = IntentDescription("Choose which app to display.")
@Parameter(title: "App")
var app: InstalledAppEntity?
// WidgetConfigurationIntent requires perform() no-op for configuration intents.
func perform() async throws -> some IntentResult { .result() }
}

View File

@@ -221,3 +221,33 @@ class AppsTimelineProvider: AppsTimelineProviderBase<Intent>, IntentTimelineProv
} }
} }
} }
// Modern AppIntents-based provider for AppDetailWidget on iOS 17+.
// Replaces AppsTimelineProvider (IntentTimelineProvider) which uses the legacy
// SiriKit Intents framework that breaks containerBackground on iOS 17+.
@available(iOSApplicationExtension 17, *)
class SelectAppTimelineProvider: AppsTimelineProviderBase<SelectAppIntent>, AppIntentTimelineProvider
{
typealias Intent = SelectAppIntent
func snapshot(for intent: SelectAppIntent, in context: Context) async -> AppsEntry<SelectAppIntent>
{
let bundleID = await resolvedBundleID(for: intent)
return await self.snapshot(for: [bundleID], in: intent)
}
func timeline(for intent: SelectAppIntent, in context: Context) async -> Timeline<AppsEntry<SelectAppIntent>>
{
let bundleID = await resolvedBundleID(for: intent)
return await self.timeline(for: [bundleID], in: intent)
}
// If the user hasn't picked an app yet, fall back to the first active app
// rather than a hardcoded bundle ID that may not exist in the database.
private func resolvedBundleID(for intent: SelectAppIntent) async -> String
{
if let id = intent.app?.id { return id }
let activeIDs = await self.fetchActiveAppBundleIDs()
return activeIDs.first ?? StoreApp.altstoreAppID
}
}

View File

@@ -74,6 +74,9 @@ private struct ActiveAppsWidgetView: View
@Environment(\.colorScheme) @Environment(\.colorScheme)
private var colorScheme private var colorScheme
@Environment(\.widgetRenderingMode)
private var renderingMode
var body: some View { var body: some View {
Group { Group {
@@ -92,6 +95,12 @@ private struct ActiveAppsWidgetView: View
{ {
LinearGradient(colors: [.altGradientDark, .altGradientExtraDark], startPoint: .top, endPoint: .bottom) LinearGradient(colors: [.altGradientDark, .altGradientExtraDark], startPoint: .top, endPoint: .bottom)
} }
else if renderingMode == .accented
{
// Plain dark background in tinted mode so the system's
// accent colour composites cleanly over it.
Color.black
}
else else
{ {
LinearGradient(colors: [.altGradientLight, .altGradientDark], startPoint: .top, endPoint: .bottom) LinearGradient(colors: [.altGradientLight, .altGradientDark], startPoint: .top, endPoint: .bottom)
@@ -128,11 +137,16 @@ private struct ActiveAppsWidgetView: View
let daysRemaining = app.expirationDate.numberOfCalendarDays(since: entry.date) let daysRemaining = app.expirationDate.numberOfCalendarDays(since: entry.date)
HStack(spacing: 10) { HStack(spacing: 10) {
// In tinted (accented) mode, luminanceToAlpha() converts the icon's
// brightness into opacity so the system can tint it with the user's
// chosen accent colour. widgetAccentable() opts the view into that
// accent group. In fullColor mode both are no-ops (via the helpers).
Image(uiImage: resizedIcon) Image(uiImage: resizedIcon)
.resizable() .resizable()
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.cornerRadius(cornerRadius) .luminanceToAlphaInAccentedMode()
.mask(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous))
.widgetAccentableIfAvailable()
VStack(alignment: .leading, spacing: 1) { VStack(alignment: .leading, spacing: 1) {
Text(app.name) Text(app.name)
@@ -151,6 +165,7 @@ private struct ActiveAppsWidgetView: View
.font(.system(size: 13, weight: .semibold, design: .rounded)) .font(.system(size: 13, weight: .semibold, design: .rounded))
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
.widgetAccentableIfAvailable()
Spacer() Spacer()
@@ -167,6 +182,7 @@ private struct ActiveAppsWidgetView: View
.activatesRefreshAllAppsIntent() .activatesRefreshAllAppsIntent()
// this modifier invalidates the view (disables user interaction and shows a blinking effect) // this modifier invalidates the view (disables user interaction and shows a blinking effect)
.invalidatableContent() .invalidatableContent()
.widgetAccentableIfAvailable()
} }
.frame(height: rowHeight) .frame(height: rowHeight)

View File

@@ -15,36 +15,52 @@ struct AppDetailWidget: Widget
private let kind: String = "AppDetail" private let kind: String = "AppDetail"
public var body: some WidgetConfiguration { public var body: some WidgetConfiguration {
let configuration = IntentConfiguration(kind: kind, // On iOS 16+ use AppIntentConfiguration it correctly supports
intent: ViewAppIntent.self, // containerBackground and contentMarginsDisabled(), unlike the legacy
provider: AppsTimelineProvider()) { (entry) in // IntentConfiguration which breaks on iOS 17+ with the
AppDetailWidgetView(entry: entry) // "Please adopt containerBackground" error.
} if #available(iOSApplicationExtension 17, *)
.supportedFamilies([.systemSmall])
.configurationDisplayName("App Status")
.description("View remaining days until your sideloaded apps expire. Tap the countdown timer to refresh them in the background.")
if #available(iOS 17, *)
{ {
return configuration return AppIntentConfiguration(
.contentMarginsDisabled() kind: kind,
intent: SelectAppIntent.self,
provider: SelectAppTimelineProvider()
) { entry in
AppDetailWidgetView(apps: entry.apps, date: entry.date, isPlaceholder: entry.isPlaceholder)
}
.supportedFamilies([.systemSmall])
.configurationDisplayName("App Status")
.description("View remaining days until your sideloaded apps expire. Tap the countdown timer to refresh them in the background.")
.contentMarginsDisabled()
} }
else else
{ {
return configuration // Legacy path for iOS 15.
return IntentConfiguration(
kind: kind,
intent: ViewAppIntent.self,
provider: AppsTimelineProvider()
) { entry in
AppDetailWidgetView(apps: entry.apps, date: entry.date, isPlaceholder: entry.isPlaceholder)
}
.supportedFamilies([.systemSmall])
.configurationDisplayName("App Status")
.description("View remaining days until your sideloaded apps expire. Tap the countdown timer to refresh them in the background.")
} }
} }
} }
private struct AppDetailWidgetView: View private struct AppDetailWidgetView: View
{ {
var entry: AppsEntry<Intent> let apps: [AppSnapshot]
let date: Date
let isPlaceholder: Bool
var body: some View { var body: some View {
Group { Group {
if let app = self.entry.apps.first if let app = apps.first
{ {
let daysRemaining = app.expirationDate.numberOfCalendarDays(since: self.entry.date) let daysRemaining = app.expirationDate.numberOfCalendarDays(since: date)
GeometryReader { (geometry) in GeometryReader { (geometry) in
Group { Group {
@@ -52,11 +68,7 @@ private struct AppDetailWidgetView: View
VStack(alignment: .leading, spacing: 5) { VStack(alignment: .leading, spacing: 5) {
let imageHeight = geometry.size.height * 0.4 let imageHeight = geometry.size.height * 0.4
Image(uiImage: app.icon ?? UIImage()) AppIconView(icon: app.icon, imageHeight: imageHeight)
.resizable()
.aspectRatio(CGSize(width: 1, height: 1), contentMode: .fit)
.frame(height: imageHeight)
.mask(RoundedRectangle(cornerRadius: imageHeight / 5.0, style: .continuous))
Text(app.name.uppercased()) Text(app.name.uppercased())
.font(.system(size: 12, weight: .semibold, design: .rounded)) .font(.system(size: 12, weight: .semibold, design: .rounded))
@@ -65,6 +77,7 @@ private struct AppDetailWidgetView: View
.minimumScaleFactor(0.5) .minimumScaleFactor(0.5)
} }
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.widgetAccentableIfAvailable()
Spacer(minLength: 0) Spacer(minLength: 0)
@@ -97,7 +110,7 @@ private struct AppDetailWidgetView: View
{ {
Countdown(startDate: app.refreshedDate, Countdown(startDate: app.refreshedDate,
endDate: app.expirationDate, endDate: app.expirationDate,
currentDate: self.entry.date) currentDate: date)
.font(.system(size: 20, weight: .semibold, design: .rounded)) .font(.system(size: 20, weight: .semibold, design: .rounded))
.foregroundColor(Color.white) .foregroundColor(Color.white)
.opacity(0.8) .opacity(0.8)
@@ -108,6 +121,7 @@ private struct AppDetailWidgetView: View
} }
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.activatesRefreshAllAppsIntent() .activatesRefreshAllAppsIntent()
.widgetAccentableIfAvailable()
} }
.padding() .padding()
} }
@@ -118,7 +132,7 @@ private struct AppDetailWidgetView: View
VStack { VStack {
// Put conditional inside VStack, or else an empty view will be returned // Put conditional inside VStack, or else an empty view will be returned
// if isPlaceholder == false, which messes up layout. // if isPlaceholder == false, which messes up layout.
if !entry.isPlaceholder if !isPlaceholder
{ {
Text("App Not Found") Text("App Not Found")
.font(.system(.body, design: .rounded)) .font(.system(.body, design: .rounded))
@@ -131,15 +145,12 @@ private struct AppDetailWidgetView: View
} }
.widgetBackground( .widgetBackground(
backgroundView( backgroundView(
icon: entry.apps.first?.icon, icon: apps.first?.icon,
tintColor: entry.apps.first?.tintColor tintColor: apps.first?.tintColor
) )
) )
} }
}
private extension AppDetailWidgetView
{
func backgroundView(icon: UIImage? = nil, tintColor: UIColor? = nil) -> some View func backgroundView(icon: UIImage? = nil, tintColor: UIColor? = nil) -> some View
{ {
let icon = icon ?? UIImage(named: "SideStore")! let icon = icon ?? UIImage(named: "SideStore")!
@@ -173,12 +184,6 @@ private extension AppDetailWidgetView
.saturation(saturation) .saturation(saturation)
.blur(radius: blurRadius, opaque: true) .blur(radius: blurRadius, opaque: true)
.scaleEffect(geometry.size.width / imageHeight, anchor: .center) .scaleEffect(geometry.size.width / imageHeight, anchor: .center)
// .onAppear {
// print("Geometry size: \(geometry.size)")
// print("Image height: \(imageHeight), Geometry width: \(geometry.size.width)")
// print("Icon size: \(icon.size)")
// }
Color(tintColor) Color(tintColor)
.opacity(tintOpacity) .opacity(tintOpacity)
@@ -193,6 +198,26 @@ private extension AppDetailWidgetView
} }
} }
// In tinted/clear mode: luminanceToAlpha converts pixel brightness opacity so
// the system can overlay the accent colour. Must come BEFORE the mask so the
// squircle corners are clipped after conversion (reverse order = corner bleed).
// widgetAccentable() opts the result into the accent group.
private struct AppIconView: View
{
let icon: UIImage?
let imageHeight: CGFloat
var body: some View {
Image(uiImage: icon ?? UIImage())
.resizable()
.aspectRatio(CGSize(width: 1, height: 1), contentMode: .fit)
.frame(height: imageHeight)
.luminanceToAlphaInAccentedMode()
.mask(RoundedRectangle(cornerRadius: imageHeight / 5.0, style: .continuous))
.widgetAccentableIfAvailable()
}
}
@available(iOS 17, *) @available(iOS 17, *)
#Preview(as: .systemSmall) { #Preview(as: .systemSmall) {
AppDetailWidget() AppDetailWidget()

View File

@@ -1,8 +1,8 @@
// Configuration settings file format documentation can be found at: // Configuration settings file format documentation can be found at:
// https://help.apple.com/xcode/#/dev745c5c974 // https://help.apple.com/xcode/#/dev745c5c974
MARKETING_VERSION = 0.6.3 MARKETING_VERSION = 0.6.4
CURRENT_PROJECT_VERSION = 0603 CURRENT_PROJECT_VERSION = 0604
// Vars to be overwritten by `CodeSigning.xcconfig` if exists // Vars to be overwritten by `CodeSigning.xcconfig` if exists
DEVELOPMENT_TEAM = S32Z3HMYVQ DEVELOPMENT_TEAM = S32Z3HMYVQ