diff --git a/mobile/ios/WidgetExtension/ImageWidgetView.swift b/mobile/ios/WidgetExtension/ImageWidgetView.swift index 619147c4b4..17088d43b8 100644 --- a/mobile/ios/WidgetExtension/ImageWidgetView.swift +++ b/mobile/ios/WidgetExtension/ImageWidgetView.swift @@ -17,50 +17,67 @@ struct ImmichWidgetView: View { var entry: ImageEntry var body: some View { - if entry.image == nil { - Image("LaunchImage") - .tintedWidgetImageModifier() - .overlay(alignment: .bottom) { - if let error = entry.metadata.error?.errorDescription { - Text(error) - .minimumScaleFactor(0.25) - .multilineTextAlignment(.center) - .foregroundStyle(.secondary) - .fixedSize() - .alignmentGuide(.bottom) { dimensions in - // Place the text below the bottom of the image - dimensions[.top] - 8 - } - } - } + if let image = entry.image { + ImmichWidgetContentView(image: image, subtitle: entry.metadata.subtitle, deepLink: entry.metadata.deepLink) } else { - ZStack(alignment: .leading) { - Color.clear.overlay( - Image(uiImage: entry.image!) - .resizable() - .tintedWidgetImageModifier() - .scaledToFill() - - ) - VStack { - Spacer() - if let subtitle = entry.metadata.subtitle { - Text(subtitle) - .foregroundColor(.white) - .padding(8) - .background(Color.black.opacity(0.6)) - .cornerRadius(8) - .font(.system(size: 16)) - } - } - .padding(16) - } - .widgetURL(entry.metadata.deepLink) + ImmichWidgetLoadingView(message: entry.metadata.error?.errorDescription) } } } +private struct ImmichWidgetLoadingView: View { + let message: String? + + var body: some View { + let messageText = Text(message ?? "") + .minimumScaleFactor(0.25) + .multilineTextAlignment(.center) + .foregroundStyle(.secondary) + + VStack(spacing: 8) { + // This is used as a nicer way to center the image, rather than using offsets + messageText.hidden() + + Image("LaunchImage") + .tintedWidgetImageModifier() + + messageText + } + } +} + +private struct ImmichWidgetContentView: View { + let image: UIImage + let subtitle: String? + let deepLink: URL? + + var body: some View { + ZStack(alignment: .leading) { + Color.clear.overlay( + Image(uiImage: image) + .resizable() + .tintedWidgetImageModifier() + .scaledToFill() + ) + + VStack { + Spacer() + if let subtitle { + Text(subtitle) + .foregroundColor(.white) + .padding(6) + .background(ContainerRelativeShape().fill(Color.black.opacity(0.6))) + .font(.system(size: 16)) + } + } + .padding(16) + } + .widgetURL(deepLink) + } +} + #Preview( + "Medium", as: .systemMedium, widget: { ImmichRandomWidget() @@ -69,10 +86,93 @@ struct ImmichWidgetView: View { let date = Date() ImageEntry( date: date, - image: UIImage(named: "ImmichLogo"), + image: UIImage(named: "LaunchImage"), metadata: EntryMetadata( subtitle: "1 year ago" ) ) } ) + +#Preview( + "Medium No Data", + as: .systemMedium, + widget: { + ImmichRandomWidget() + }, + timeline: { + let date = Date() + ImageEntry( + date: date, + image: nil + ) + } +) + +#Preview( + "Medium No Data Error", + as: .systemMedium, + widget: { + ImmichRandomWidget() + }, + timeline: { + let date = Date() + ImageEntry( + date: date, + image: nil, + metadata: EntryMetadata(error: WidgetError.fetchFailed) + ) + } +) + +#Preview( + "Small", + as: .systemSmall, + widget: { + ImmichRandomWidget() + }, + timeline: { + let date = Date() + ImageEntry( + date: date, + image: UIImage(named: "LaunchImage"), + metadata: EntryMetadata( + subtitle: "Yesterday" + ) + ) + } +) + +#Preview( + "Small No Data Error", + as: .systemSmall, + widget: { + ImmichRandomWidget() + }, + timeline: { + let date = Date() + ImageEntry( + date: date, + image: nil, + metadata: EntryMetadata(error: WidgetError.fetchFailed) + ) + } +) + +#Preview( + "Large", + as: .systemLarge, + widget: { + ImmichRandomWidget() + }, + timeline: { + let date = Date() + ImageEntry( + date: date, + image: UIImage(named: "LaunchImage"), + metadata: EntryMetadata( + subtitle: "2000 seconds ago" + ) + ) + } +) diff --git a/mobile/ios/WidgetExtension/ImmichAPI.swift b/mobile/ios/WidgetExtension/ImmichAPI.swift index f5ee578dad..7e9dcd904a 100644 --- a/mobile/ios/WidgetExtension/ImmichAPI.swift +++ b/mobile/ios/WidgetExtension/ImmichAPI.swift @@ -25,7 +25,7 @@ extension WidgetError: LocalizedError { return "Login to Immich" case .fetchFailed: - return "Unable to connect to your Immich instance" + return "Unable to connect to Immich" case .albumNotFound: return "Album not found"