diff --git a/CoreData/AttachmentDataHandler.swift b/CoreData/AttachmentDataHandler.swift index f81d838..d2df044 100644 --- a/CoreData/AttachmentDataHandler.swift +++ b/CoreData/AttachmentDataHandler.swift @@ -15,4 +15,24 @@ class AttachmentDataHandler { let context = viewContext ?? CoreDataHandler.shared.container.viewContext return AttachmentData(context: context) } + + func getDownloadedAttachmentData(accountId: String, length: Int, viewContext: NSManagedObjectContext? = nil) -> [AttachmentData] { + let context = viewContext ?? CoreDataHandler.shared.container.viewContext + let fetchRequest = AttachmentData.fetchRequest() + fetchRequest.fetchLimit = length + + let sortDescriptor = NSSortDescriptor(key: "statusRelation.id", ascending: true) + fetchRequest.sortDescriptors = [sortDescriptor] + + let predicate1 = NSPredicate(format: "statusRelation.pixelfedAccount.id = %@", accountId) + let predicate2 = NSPredicate(format: "data != nil") + fetchRequest.predicate = NSCompoundPredicate.init(type: .and, subpredicates: [predicate1, predicate2]) + + do { + return try context.fetch(fetchRequest) + } catch { + CoreDataError.shared.handle(error, message: "Error during fetching attachment data (getDownloadedAttachmentData).") + return [] + } + } } diff --git a/VernissageWidget/PhotoWidget/PhotoProvider.swift b/VernissageWidget/PhotoWidget/PhotoProvider.swift index e284c30..eae19ba 100644 --- a/VernissageWidget/PhotoWidget/PhotoProvider.swift +++ b/VernissageWidget/PhotoWidget/PhotoProvider.swift @@ -17,31 +17,54 @@ struct PhotoProvider: TimelineProvider { func getSnapshot(in context: Context, completion: @escaping (PhotoWidgetEntry) -> Void) { Task { - if let widgetEntry = await self.getWidgetEntries(length: 1).first { - completion(widgetEntry) - } else { - let entry = StatusFetcher.shared.placeholder() - completion(entry) - } + let widgetEntry = await self.getWidgetEntriesForSnapshot() + completion(widgetEntry) } } func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { Task { let currentDate = Date() - let widgetEntries = await self.getWidgetEntries() + let widgetEntries = await self.getWidgetEntriesForTimeline() let nextUpdateDate = Calendar.current.date(byAdding: .hour, value: 1, to: currentDate)! let timeline = Timeline(entries: widgetEntries, policy: .after(nextUpdateDate)) completion(timeline) } } + + func getWidgetEntriesForSnapshot() async -> PhotoWidgetEntry { + let entriesFromDatabase = await self.getWidgetEntriesFromDatabase(length: 1) + if let firstEntry = entriesFromDatabase.first { + return firstEntry + } + + return StatusFetcher.shared.placeholder() + } + + func getWidgetEntriesForTimeline() async -> [PhotoWidgetEntry] { + let entriesFromServer = await self.getWidgetEntriesFromServer(length: 3) + if entriesFromServer.isEmpty == false { + return entriesFromServer + } + + let entriesFromDatabase = await self.getWidgetEntriesFromDatabase(length: 3) + if entriesFromDatabase.isEmpty == false { + return entriesFromDatabase + } + + return [StatusFetcher.shared.placeholder()] + } - func getWidgetEntries(length: Int = 3) async -> [PhotoWidgetEntry] { + func getWidgetEntriesFromServer(length: Int) async -> [PhotoWidgetEntry] { do { - return try await StatusFetcher.shared.fetchWidgetEntries(length: length) + return try await StatusFetcher.shared.fetchWidgetEntriesFromServer(length: length) } catch { - return [StatusFetcher.shared.placeholder()] + return [] } } + + func getWidgetEntriesFromDatabase(length: Int) async -> [PhotoWidgetEntry] { + return await StatusFetcher.shared.fetchWidgetEntriesFromDatabase(length: length) + } } diff --git a/VernissageWidget/PhotoWidget/Service/StatusFetcher.swift b/VernissageWidget/PhotoWidget/Service/StatusFetcher.swift index bc12481..0648088 100644 --- a/VernissageWidget/PhotoWidget/Service/StatusFetcher.swift +++ b/VernissageWidget/PhotoWidget/Service/StatusFetcher.swift @@ -12,7 +12,7 @@ public class StatusFetcher { public static let shared = StatusFetcher() private init() { } - func fetchWidgetEntries(length: Int = 8) async throws -> [PhotoWidgetEntry] { + func fetchWidgetEntriesFromServer(length: Int) async throws -> [PhotoWidgetEntry] { let defaultSettings = ApplicationSettingsHandler.shared.get() guard let accountId = defaultSettings.currentAccount else { return [self.placeholder()] @@ -67,6 +67,37 @@ public class StatusFetcher { return widgetEntries.shuffled() } + + func fetchWidgetEntriesFromDatabase(length: Int) async -> [PhotoWidgetEntry] { + let defaultSettings = ApplicationSettingsHandler.shared.get() + guard let accountId = defaultSettings.currentAccount else { + return [self.placeholder()] + } + + let attachmentDatas = AttachmentDataHandler.shared.getDownloadedAttachmentData(accountId: accountId, length: length) + + var widgetEntries: [PhotoWidgetEntry] = [] + for attachmentData in attachmentDatas { + guard let imageData = attachmentData.data, let uiImage = UIImage(data: imageData) else { + continue + } + + let uiAvatar = await FileFetcher.shared.getImage(url: attachmentData.statusRelation?.accountAvatar) + let displayDate = Calendar.current.date(byAdding: .minute, value: widgetEntries.count * 20, to: Date()) + + widgetEntries.append(PhotoWidgetEntry(date: displayDate ?? Date(), + image: uiImage, + avatar: uiAvatar, + displayName: attachmentData.statusRelation?.accountDisplayName, + statusId: attachmentData.statusId)) + } + + if widgetEntries.isEmpty { + widgetEntries.append(self.placeholder()) + } + + return widgetEntries.shuffled() + } func placeholder() -> PhotoWidgetEntry { PhotoWidgetEntry(date: Date(), image: nil, avatar: nil, displayName: "Caroline Rick", statusId: "") diff --git a/VernissageWidget/PhotoWidget/Views/PhotoLargeWidgetView.swift b/VernissageWidget/PhotoWidget/Views/PhotoLargeWidgetView.swift index 983ef16..3cb82f1 100644 --- a/VernissageWidget/PhotoWidget/Views/PhotoLargeWidgetView.swift +++ b/VernissageWidget/PhotoWidget/Views/PhotoLargeWidgetView.swift @@ -12,21 +12,23 @@ struct PhotoLargeWidgetView: View { var entry: PhotoProvider.Entry var body: some View { - if let uiImage = entry.image, let uiAvatar = entry.avatar { - self.getWidgetBody(uiImage: Image(uiImage: uiImage), uiAvatar: Image(uiImage: uiAvatar)) + if let uiImage = entry.image { + self.getWidgetBody(uiImage: Image(uiImage: uiImage), uiAvatarImage: entry.avatar) } else { - self.getWidgetBody(uiImage: Image("Placeholder"), uiAvatar: Image("Avatar")) + self.getWidgetBody(uiImage: Image("Placeholder"), uiAvatarImage: UIImage(named: "Avatar")) .unredacted() } } @ViewBuilder - private func getWidgetBody(uiImage: Image, uiAvatar: Image) -> some View { + private func getWidgetBody(uiImage: Image, uiAvatarImage: UIImage?) -> some View { VStack { Spacer() HStack { - uiAvatar - .avatar(size: 24) + if let uiAvatar = uiAvatarImage { + Image(uiImage: uiAvatar) + .avatar(size: 24) + } Text(entry.displayName ?? "") .font(.system(size: 15)) diff --git a/VernissageWidget/PhotoWidget/Views/PhotoMediumWidgetView.swift b/VernissageWidget/PhotoWidget/Views/PhotoMediumWidgetView.swift index fc9f75e..c6c70f5 100644 --- a/VernissageWidget/PhotoWidget/Views/PhotoMediumWidgetView.swift +++ b/VernissageWidget/PhotoWidget/Views/PhotoMediumWidgetView.swift @@ -12,21 +12,23 @@ struct PhotoMediumWidgetView: View { var entry: PhotoProvider.Entry var body: some View { - if let uiImage = entry.image, let uiAvatar = entry.avatar { - self.getWidgetBody(uiImage: Image(uiImage: uiImage), uiAvatar: Image(uiImage: uiAvatar)) + if let uiImage = entry.image { + self.getWidgetBody(uiImage: Image(uiImage: uiImage), uiAvatarImage: entry.avatar) } else { - self.getWidgetBody(uiImage: Image("Placeholder"), uiAvatar: Image("Avatar")) + self.getWidgetBody(uiImage: Image("Placeholder"), uiAvatarImage: UIImage(named: "Avatar")) .unredacted() } } @ViewBuilder - private func getWidgetBody(uiImage: Image, uiAvatar: Image) -> some View { + private func getWidgetBody(uiImage: Image, uiAvatarImage: UIImage?) -> some View { VStack { Spacer() HStack { - uiAvatar - .avatar(size: 24) + if let uiAvatar = uiAvatarImage { + Image(uiImage: uiAvatar) + .avatar(size: 24) + } Text(entry.displayName ?? "") .font(.system(size: 15)) diff --git a/VernissageWidget/PhotoWidget/Views/PhotoSmallWidgetView.swift b/VernissageWidget/PhotoWidget/Views/PhotoSmallWidgetView.swift index a4cf584..78a5a2d 100644 --- a/VernissageWidget/PhotoWidget/Views/PhotoSmallWidgetView.swift +++ b/VernissageWidget/PhotoWidget/Views/PhotoSmallWidgetView.swift @@ -12,21 +12,23 @@ struct PhotoSmallWidgetView: View { var entry: PhotoProvider.Entry var body: some View { - if let uiImage = entry.image, let uiAvatar = entry.avatar { - self.getWidgetBody(uiImage: Image(uiImage: uiImage), uiAvatar: Image(uiImage: uiAvatar)) + if let uiImage = entry.image { + self.getWidgetBody(uiImage: Image(uiImage: uiImage), uiAvatarImage: entry.avatar) } else { - self.getWidgetBody(uiImage: Image("Placeholder"), uiAvatar: Image("Avatar")) + self.getWidgetBody(uiImage: Image("Placeholder"), uiAvatarImage: UIImage(named: "Avatar")) .unredacted() } } @ViewBuilder - private func getWidgetBody(uiImage: Image, uiAvatar: Image) -> some View { + private func getWidgetBody(uiImage: Image, uiAvatarImage: UIImage?) -> some View { VStack { Spacer() HStack { - uiAvatar - .avatar(size: 16) + if let uiAvatar = uiAvatarImage { + Image(uiImage: uiAvatar) + .avatar(size: 16) + } Spacer() }