mirror of
https://github.com/immich-app/immich.git
synced 2025-06-23 15:30:51 -04:00
* feat: working widgets * chore/feat: cleaned up API, added album picker to random widget * album filtering for requests * check album and throw if not found * fix app IDs and project configuration * switch to repository/service model for updating widgets * fix: remove home widget import * revert info.plist formatting changes * ran swift-format on widget code * more formatting changes (this time run from xcode) * show memory on widget picker snapshot * fix: dart changes from code review * fix: swift code review changes (not including task groups) * fix: use task groups to run image retrievals concurrently, get rid of do catch in favor of if let * chore: cleanup widget service in dart app * chore: format swift * fix: remove comma why does xcode not freak out over this >:( * switch to preview size for thumbnail * chore: cropped large image * fix: properly resize widgets so we dont OOM * fix: set app group on logout happens on first install * fix: stupid app ids * fix: revert back to thumbnail we are hitting OOM exceptions due to resizing, once we have on-the-fly resizing on server this can be upgraded * fix: more memory efficient resizing method, remove extraneous resize commands from API call * fix: random widget use 12 entries instead of 24 to save memory * fix: modify duration of entries to 20 minutes and only generate 10 at a time to avoid OOM * feat: toggle to show album name on random widget * Podfile lock --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
171 lines
4.6 KiB
Swift
171 lines
4.6 KiB
Swift
import AppIntents
|
|
import SwiftUI
|
|
import WidgetKit
|
|
|
|
// MARK: Widget Configuration
|
|
|
|
extension Album: @unchecked Sendable, AppEntity, Identifiable {
|
|
|
|
struct AlbumQuery: EntityQuery {
|
|
func entities(for identifiers: [Album.ID]) async throws -> [Album] {
|
|
// use cached albums to search
|
|
var albums = (try? await AlbumCache.shared.getAlbums()) ?? []
|
|
albums.insert(NO_ALBUM, at: 0)
|
|
|
|
return albums.filter {
|
|
identifiers.contains($0.id)
|
|
}
|
|
}
|
|
|
|
func suggestedEntities() async throws -> [Album] {
|
|
var albums = (try? await AlbumCache.shared.getAlbums(refresh: true)) ?? []
|
|
albums.insert(NO_ALBUM, at: 0)
|
|
|
|
return albums
|
|
}
|
|
}
|
|
|
|
static var defaultQuery = AlbumQuery()
|
|
static var typeDisplayRepresentation = TypeDisplayRepresentation(
|
|
name: "Album"
|
|
)
|
|
|
|
var displayRepresentation: DisplayRepresentation {
|
|
DisplayRepresentation(title: "\(albumName)")
|
|
}
|
|
}
|
|
|
|
let NO_ALBUM = Album(id: "NONE", albumName: "None")
|
|
|
|
struct RandomConfigurationAppIntent: WidgetConfigurationIntent {
|
|
static var title: LocalizedStringResource { "Select Album" }
|
|
static var description: IntentDescription {
|
|
"Choose an album to show images from"
|
|
}
|
|
|
|
@Parameter(title: "Album", default: NO_ALBUM)
|
|
var album: Album?
|
|
|
|
@Parameter(title: "Show Album Name", default: false)
|
|
var showAlbumName: Bool
|
|
}
|
|
|
|
// MARK: Provider
|
|
|
|
struct ImmichRandomProvider: AppIntentTimelineProvider {
|
|
func placeholder(in context: Context) -> ImageEntry {
|
|
ImageEntry(date: Date(), image: nil)
|
|
}
|
|
|
|
func snapshot(
|
|
for configuration: RandomConfigurationAppIntent,
|
|
in context: Context
|
|
) async
|
|
-> ImageEntry
|
|
{
|
|
guard let api = try? await ImmichAPI() else {
|
|
return ImageEntry(date: Date(), image: nil, error: .noLogin)
|
|
}
|
|
|
|
guard
|
|
let randomImage = try? await api.fetchSearchResults(
|
|
with: SearchFilters(size: 1)
|
|
).first
|
|
else {
|
|
return ImageEntry(date: Date(), image: nil, error: .fetchFailed)
|
|
}
|
|
|
|
guard
|
|
var entry = try? await buildEntry(
|
|
api: api,
|
|
asset: randomImage,
|
|
dateOffset: 0
|
|
)
|
|
else {
|
|
return ImageEntry(date: Date(), image: nil, error: .fetchFailed)
|
|
}
|
|
|
|
entry.resize()
|
|
|
|
return entry
|
|
}
|
|
|
|
func timeline(
|
|
for configuration: RandomConfigurationAppIntent,
|
|
in context: Context
|
|
) async
|
|
-> Timeline<ImageEntry>
|
|
{
|
|
var entries: [ImageEntry] = []
|
|
let now = Date()
|
|
|
|
// If we don't have a server config, return an entry with an error
|
|
guard let api = try? await ImmichAPI() else {
|
|
entries.append(ImageEntry(date: now, image: nil, error: .noLogin))
|
|
return Timeline(entries: entries, policy: .atEnd)
|
|
}
|
|
|
|
// nil if album is NONE or nil
|
|
let albumId =
|
|
configuration.album?.id != "NONE" ? configuration.album?.id : nil
|
|
var albumName: String? = albumId != nil ? configuration.album?.albumName : nil
|
|
|
|
if albumId != nil {
|
|
// make sure the album exists on server, otherwise show error
|
|
guard let albums = try? await api.fetchAlbums() else {
|
|
entries.append(ImageEntry(date: now, image: nil, error: .fetchFailed))
|
|
return Timeline(entries: entries, policy: .atEnd)
|
|
}
|
|
|
|
if !albums.contains(where: { $0.id == albumId }) {
|
|
entries.append(ImageEntry(date: now, image: nil, error: .albumNotFound))
|
|
return Timeline(entries: entries, policy: .atEnd)
|
|
}
|
|
}
|
|
|
|
entries.append(
|
|
contentsOf: (try? await generateRandomEntries(
|
|
api: api,
|
|
now: now,
|
|
count: 12,
|
|
albumId: albumId,
|
|
subtitle: configuration.showAlbumName ? albumName : nil
|
|
))
|
|
?? []
|
|
)
|
|
|
|
// If we fail to fetch images, we still want to add an entry with a nil image and an error
|
|
if entries.count == 0 {
|
|
entries.append(ImageEntry(date: now, image: nil, error: .fetchFailed))
|
|
}
|
|
|
|
// Resize all images to something that can be stored by iOS
|
|
for i in entries.indices {
|
|
entries[i].resize()
|
|
}
|
|
|
|
return Timeline(entries: entries, policy: .atEnd)
|
|
}
|
|
}
|
|
|
|
struct ImmichRandomWidget: Widget {
|
|
let kind: String = "com.immich.widget.random"
|
|
|
|
var body: some WidgetConfiguration {
|
|
AppIntentConfiguration(
|
|
kind: kind,
|
|
intent: RandomConfigurationAppIntent.self,
|
|
provider: ImmichRandomProvider()
|
|
) { entry in
|
|
ImmichWidgetView(entry: entry)
|
|
.containerBackground(.regularMaterial, for: .widget)
|
|
}
|
|
// allow image to take up entire widget
|
|
.contentMarginsDisabled()
|
|
|
|
// widget picker info
|
|
.configurationDisplayName("Random")
|
|
.description("View a random image from your library or a specific album.")
|
|
}
|
|
}
|