mirror of
https://github.com/SideStore/SideStore.git
synced 2026-05-12 04:15:38 +02:00
fix(widget): blank app icons
Signed-off-by: Mod4 <omarelfarok24135@gmail.com> Co-authored-by: mahee96 <47920326+mahee96@users.noreply.github.com>
This commit is contained in:
@@ -74,6 +74,9 @@ private struct ActiveAppsWidgetView: View
|
||||
|
||||
@Environment(\.colorScheme)
|
||||
private var colorScheme
|
||||
|
||||
@Environment(\.widgetRenderingMode)
|
||||
private var renderingMode
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
@@ -92,6 +95,12 @@ private struct ActiveAppsWidgetView: View
|
||||
{
|
||||
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
|
||||
{
|
||||
LinearGradient(colors: [.altGradientLight, .altGradientDark], startPoint: .top, endPoint: .bottom)
|
||||
@@ -128,11 +137,16 @@ private struct ActiveAppsWidgetView: View
|
||||
let daysRemaining = app.expirationDate.numberOfCalendarDays(since: entry.date)
|
||||
|
||||
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)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.cornerRadius(cornerRadius)
|
||||
|
||||
.luminanceToAlphaInAccentedMode()
|
||||
.mask(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous))
|
||||
.widgetAccentableIfAvailable()
|
||||
|
||||
VStack(alignment: .leading, spacing: 1) {
|
||||
Text(app.name)
|
||||
@@ -151,6 +165,7 @@ private struct ActiveAppsWidgetView: View
|
||||
.font(.system(size: 13, weight: .semibold, design: .rounded))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.widgetAccentableIfAvailable()
|
||||
|
||||
Spacer()
|
||||
|
||||
@@ -167,6 +182,7 @@ private struct ActiveAppsWidgetView: View
|
||||
.activatesRefreshAllAppsIntent()
|
||||
// this modifier invalidates the view (disables user interaction and shows a blinking effect)
|
||||
.invalidatableContent()
|
||||
.widgetAccentableIfAvailable()
|
||||
|
||||
}
|
||||
.frame(height: rowHeight)
|
||||
|
||||
@@ -15,36 +15,52 @@ struct AppDetailWidget: Widget
|
||||
private let kind: String = "AppDetail"
|
||||
|
||||
public var body: some WidgetConfiguration {
|
||||
let configuration = IntentConfiguration(kind: kind,
|
||||
intent: ViewAppIntent.self,
|
||||
provider: AppsTimelineProvider()) { (entry) in
|
||||
AppDetailWidgetView(entry: entry)
|
||||
}
|
||||
.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, *)
|
||||
// On iOS 16+ use AppIntentConfiguration — it correctly supports
|
||||
// containerBackground and contentMarginsDisabled(), unlike the legacy
|
||||
// IntentConfiguration which breaks on iOS 17+ with the
|
||||
// "Please adopt containerBackground" error.
|
||||
if #available(iOSApplicationExtension 17, *)
|
||||
{
|
||||
return configuration
|
||||
.contentMarginsDisabled()
|
||||
return AppIntentConfiguration(
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
var entry: AppsEntry<Intent>
|
||||
let apps: [AppSnapshot]
|
||||
let date: Date
|
||||
let isPlaceholder: Bool
|
||||
|
||||
var body: some View {
|
||||
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
|
||||
Group {
|
||||
@@ -52,11 +68,7 @@ private struct AppDetailWidgetView: View
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
let imageHeight = geometry.size.height * 0.4
|
||||
|
||||
Image(uiImage: app.icon ?? UIImage())
|
||||
.resizable()
|
||||
.aspectRatio(CGSize(width: 1, height: 1), contentMode: .fit)
|
||||
.frame(height: imageHeight)
|
||||
.mask(RoundedRectangle(cornerRadius: imageHeight / 5.0, style: .continuous))
|
||||
AppIconView(icon: app.icon, imageHeight: imageHeight)
|
||||
|
||||
Text(app.name.uppercased())
|
||||
.font(.system(size: 12, weight: .semibold, design: .rounded))
|
||||
@@ -65,6 +77,7 @@ private struct AppDetailWidgetView: View
|
||||
.minimumScaleFactor(0.5)
|
||||
}
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.widgetAccentableIfAvailable()
|
||||
|
||||
Spacer(minLength: 0)
|
||||
|
||||
@@ -97,7 +110,7 @@ private struct AppDetailWidgetView: View
|
||||
{
|
||||
Countdown(startDate: app.refreshedDate,
|
||||
endDate: app.expirationDate,
|
||||
currentDate: self.entry.date)
|
||||
currentDate: date)
|
||||
.font(.system(size: 20, weight: .semibold, design: .rounded))
|
||||
.foregroundColor(Color.white)
|
||||
.opacity(0.8)
|
||||
@@ -108,6 +121,7 @@ private struct AppDetailWidgetView: View
|
||||
}
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.activatesRefreshAllAppsIntent()
|
||||
.widgetAccentableIfAvailable()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
@@ -118,7 +132,7 @@ private struct AppDetailWidgetView: View
|
||||
VStack {
|
||||
// Put conditional inside VStack, or else an empty view will be returned
|
||||
// if isPlaceholder == false, which messes up layout.
|
||||
if !entry.isPlaceholder
|
||||
if !isPlaceholder
|
||||
{
|
||||
Text("App Not Found")
|
||||
.font(.system(.body, design: .rounded))
|
||||
@@ -131,15 +145,12 @@ private struct AppDetailWidgetView: View
|
||||
}
|
||||
.widgetBackground(
|
||||
backgroundView(
|
||||
icon: entry.apps.first?.icon,
|
||||
tintColor: entry.apps.first?.tintColor
|
||||
icon: apps.first?.icon,
|
||||
tintColor: apps.first?.tintColor
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private extension AppDetailWidgetView
|
||||
{
|
||||
func backgroundView(icon: UIImage? = nil, tintColor: UIColor? = nil) -> some View
|
||||
{
|
||||
let icon = icon ?? UIImage(named: "SideStore")!
|
||||
@@ -173,12 +184,6 @@ private extension AppDetailWidgetView
|
||||
.saturation(saturation)
|
||||
.blur(radius: blurRadius, opaque: true)
|
||||
.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)
|
||||
.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, *)
|
||||
#Preview(as: .systemSmall) {
|
||||
AppDetailWidget()
|
||||
|
||||
Reference in New Issue
Block a user