diff --git a/Vernissage/CoreData/AccountData+CoreDataClass.swift b/CoreData/AccountData+CoreDataClass.swift similarity index 100% rename from Vernissage/CoreData/AccountData+CoreDataClass.swift rename to CoreData/AccountData+CoreDataClass.swift diff --git a/Vernissage/CoreData/AccountData+CoreDataProperties.swift b/CoreData/AccountData+CoreDataProperties.swift similarity index 100% rename from Vernissage/CoreData/AccountData+CoreDataProperties.swift rename to CoreData/AccountData+CoreDataProperties.swift diff --git a/Vernissage/CoreData/AccountDataHandler.swift b/CoreData/AccountDataHandler.swift similarity index 86% rename from Vernissage/CoreData/AccountDataHandler.swift rename to CoreData/AccountDataHandler.swift index 6bc8bc2..bf130c9 100644 --- a/Vernissage/CoreData/AccountDataHandler.swift +++ b/CoreData/AccountDataHandler.swift @@ -17,7 +17,7 @@ class AccountDataHandler { do { return try context.fetch(fetchRequest) } catch { - ErrorService.shared.handle(error, message: "Accounts cannot be retrieved (getAccountsData).") + CoreDataError.shared.handle(error, message: "Accounts cannot be retrieved (getAccountsData).") return [] } } @@ -47,7 +47,7 @@ class AccountDataHandler { do { return try context.fetch(fetchRequest).first } catch { - ErrorService.shared.handle(error, message: "Error during fetching status (getAccountData).") + CoreDataError.shared.handle(error, message: "Error during fetching status (getAccountData).") return nil } } @@ -59,7 +59,7 @@ class AccountDataHandler { do { try context.save() } catch { - ErrorService.shared.handle(error, message: "Error during deleting account data (remove).") + CoreDataError.shared.handle(error, message: "Error during deleting account data (remove).") } } diff --git a/Vernissage/CoreData/ApplicationSettings+CoreDataClass.swift b/CoreData/ApplicationSettings+CoreDataClass.swift similarity index 100% rename from Vernissage/CoreData/ApplicationSettings+CoreDataClass.swift rename to CoreData/ApplicationSettings+CoreDataClass.swift diff --git a/Vernissage/CoreData/ApplicationSettings+CoreDataProperties.swift b/CoreData/ApplicationSettings+CoreDataProperties.swift similarity index 100% rename from Vernissage/CoreData/ApplicationSettings+CoreDataProperties.swift rename to CoreData/ApplicationSettings+CoreDataProperties.swift diff --git a/Vernissage/CoreData/ApplicationSettingsHandler.swift b/CoreData/ApplicationSettingsHandler.swift similarity index 97% rename from Vernissage/CoreData/ApplicationSettingsHandler.swift rename to CoreData/ApplicationSettingsHandler.swift index 0fc0fcb..fe83ea3 100644 --- a/Vernissage/CoreData/ApplicationSettingsHandler.swift +++ b/CoreData/ApplicationSettingsHandler.swift @@ -18,7 +18,7 @@ class ApplicationSettingsHandler { do { settingsList = try context.fetch(fetchRequest) } catch { - ErrorService.shared.handle(error, message: "Error during fetching application settings.") + CoreDataError.shared.handle(error, message: "Error during fetching application settings.") } if let settings = settingsList.first { @@ -58,11 +58,6 @@ class ApplicationSettingsHandler { CoreDataHandler.shared.save() } - private func createApplicationSettingsEntity() -> ApplicationSettings { - let context = CoreDataHandler.shared.container.viewContext - return ApplicationSettings(context: context) - } - func setHapticTabSelectionEnabled(value: Bool) { let defaultSettings = self.getDefaultSettings() defaultSettings.hapticTabSelectionEnabled = value @@ -104,4 +99,9 @@ class ApplicationSettingsHandler { defaultSettings.showPhotoDescription = value CoreDataHandler.shared.save() } + + private func createApplicationSettingsEntity() -> ApplicationSettings { + let context = CoreDataHandler.shared.container.viewContext + return ApplicationSettings(context: context) + } } diff --git a/Vernissage/CoreData/AttachmentData+Attachment.swift b/CoreData/AttachmentData+Attachment.swift similarity index 100% rename from Vernissage/CoreData/AttachmentData+Attachment.swift rename to CoreData/AttachmentData+Attachment.swift diff --git a/Vernissage/CoreData/AttachmentData+Comperable.swift b/CoreData/AttachmentData+Comperable.swift similarity index 100% rename from Vernissage/CoreData/AttachmentData+Comperable.swift rename to CoreData/AttachmentData+Comperable.swift diff --git a/Vernissage/CoreData/AttachmentData+CoreDataClass.swift b/CoreData/AttachmentData+CoreDataClass.swift similarity index 100% rename from Vernissage/CoreData/AttachmentData+CoreDataClass.swift rename to CoreData/AttachmentData+CoreDataClass.swift diff --git a/Vernissage/CoreData/AttachmentData+CoreDataProperties.swift b/CoreData/AttachmentData+CoreDataProperties.swift similarity index 100% rename from Vernissage/CoreData/AttachmentData+CoreDataProperties.swift rename to CoreData/AttachmentData+CoreDataProperties.swift diff --git a/Vernissage/CoreData/AttachmentData+HighestImage.swift b/CoreData/AttachmentData+HighestImage.swift similarity index 100% rename from Vernissage/CoreData/AttachmentData+HighestImage.swift rename to CoreData/AttachmentData+HighestImage.swift diff --git a/Vernissage/CoreData/AttachmentDataHandler.swift b/CoreData/AttachmentDataHandler.swift similarity index 100% rename from Vernissage/CoreData/AttachmentDataHandler.swift rename to CoreData/AttachmentDataHandler.swift diff --git a/CoreData/CoreDataError.swift b/CoreData/CoreDataError.swift new file mode 100644 index 0000000..89be7ea --- /dev/null +++ b/CoreData/CoreDataError.swift @@ -0,0 +1,16 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation + +public class CoreDataError { + public static let shared = CoreDataError() + private init() { } + + public func handle(_ error: Error, message: String) { + print("Error ['\(message)']: \(error.localizedDescription)") + } +} diff --git a/CoreData/CoreDataHandler.swift b/CoreData/CoreDataHandler.swift new file mode 100644 index 0000000..56a9d54 --- /dev/null +++ b/CoreData/CoreDataHandler.swift @@ -0,0 +1,93 @@ +// +// https://mczachurski.dev +// Copyright © 2022 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import CoreData + +public class CoreDataHandler { + public static let shared = CoreDataHandler() + + lazy var container: NSPersistentContainer = { + let container = NSPersistentContainer(name: AppConstants.coreDataPersistantContainerName) + let storeURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.dev.mczachurski.vernissage")! + .appendingPathComponent("Data.sqlite") + + var defaultURL: URL? + if let storeDescription = container.persistentStoreDescriptions.first, let url = storeDescription.url { + defaultURL = FileManager.default.fileExists(atPath: url.path) ? url : nil + } + + if defaultURL == nil { + container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: storeURL)] + } + + container.loadPersistentStores(completionHandler: { [unowned container] (storeDescription, error) in + if let error = error as NSError? { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + + /* + Typical reasons for an error here include: + * The parent directory does not exist, cannot be created, or disallows writing. + * The persistent store is not accessible, due to permissions or data protection when the device is locked. + * The device is out of space. + * The store could not be migrated to the current model version. + Check the error message to determine what the actual problem was. + */ + fatalError("Unresolved error \(error), \(error.userInfo)") + } + + // Migrate old store do current (shared between app and widget) + if let url = defaultURL, url.absoluteString != storeURL.absoluteString { + let coordinator = container.persistentStoreCoordinator + if let oldStore = coordinator.persistentStore(for: url) { + + // Migration process. + do { + try coordinator.migratePersistentStore(oldStore, to: storeURL, options: nil, withType: NSSQLiteStoreType) + } catch { + print(error.localizedDescription) + } + + // Delete old store. + let fileCoordinator = NSFileCoordinator(filePresenter: nil) + fileCoordinator.coordinate(writingItemAt: url, options: .forDeleting, error: nil, byAccessor: { url in + do { + try FileManager.default.removeItem(at: url) + } catch { + print(error.localizedDescription) + } + }) + } + } + }) + + return container + }() + + public func newBackgroundContext() -> NSManagedObjectContext { + self.container.newBackgroundContext() + } + + public func save() { + let context = self.container.viewContext + if context.hasChanges { + do { + try context.save() + } catch { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. + // You should not use this function in a shipping application, although it may be useful during development. + + #if DEBUG + let nserror = error as NSError + fatalError("Unresolved error \(nserror), \(nserror.userInfo)") + #else + CoreDataError.shared.handle(error, message: "An error occurred while writing the data.") + #endif + } + } + } +} diff --git a/Vernissage/CoreData/StatusData+Attachments.swift b/CoreData/StatusData+Attachments.swift similarity index 100% rename from Vernissage/CoreData/StatusData+Attachments.swift rename to CoreData/StatusData+Attachments.swift diff --git a/Vernissage/CoreData/StatusData+CoreDataClass.swift b/CoreData/StatusData+CoreDataClass.swift similarity index 100% rename from Vernissage/CoreData/StatusData+CoreDataClass.swift rename to CoreData/StatusData+CoreDataClass.swift diff --git a/Vernissage/CoreData/StatusData+CoreDataProperties.swift b/CoreData/StatusData+CoreDataProperties.swift similarity index 100% rename from Vernissage/CoreData/StatusData+CoreDataProperties.swift rename to CoreData/StatusData+CoreDataProperties.swift diff --git a/Vernissage/CoreData/StatusData+Status.swift b/CoreData/StatusData+Status.swift similarity index 100% rename from Vernissage/CoreData/StatusData+Status.swift rename to CoreData/StatusData+Status.swift diff --git a/Vernissage/CoreData/StatusDataHandler.swift b/CoreData/StatusDataHandler.swift similarity index 85% rename from Vernissage/CoreData/StatusDataHandler.swift rename to CoreData/StatusDataHandler.swift index b9b23a6..d1597b7 100644 --- a/Vernissage/CoreData/StatusDataHandler.swift +++ b/CoreData/StatusDataHandler.swift @@ -23,7 +23,7 @@ class StatusDataHandler { do { return try context.fetch(fetchRequest) } catch { - ErrorService.shared.handle(error, message: "Error during fetching status (getStatusData).") + CoreDataError.shared.handle(error, message: "Error during fetching status (getStatusData).") return [] } } @@ -41,7 +41,7 @@ class StatusDataHandler { do { return try context.fetch(fetchRequest).first } catch { - ErrorService.shared.handle(error, message: "Error during fetching status (getStatusData).") + CoreDataError.shared.handle(error, message: "Error during fetching status (getStatusData).") return nil } } @@ -60,7 +60,7 @@ class StatusDataHandler { let statuses = try context.fetch(fetchRequest) return statuses.first } catch { - ErrorService.shared.handle(error, message: "Error during fetching maximum status (getMaximumStatus).") + CoreDataError.shared.handle(error, message: "Error during fetching maximum status (getMaximumStatus).") return nil } } @@ -79,7 +79,7 @@ class StatusDataHandler { let statuses = try context.fetch(fetchRequest) return statuses.first } catch { - ErrorService.shared.handle(error, message: "Error during fetching minimum status (getMinimumtatus).") + CoreDataError.shared.handle(error, message: "Error during fetching minimum status (getMinimumtatus).") return nil } } @@ -96,7 +96,7 @@ class StatusDataHandler { do { try context.save() } catch { - ErrorService.shared.handle(error, message: "Error during deleting status (remove).") + CoreDataError.shared.handle(error, message: "Error during deleting status (remove).") } } @@ -110,7 +110,7 @@ class StatusDataHandler { do { try context.save() } catch { - ErrorService.shared.handle(error, message: "Error during deleting status (remove).") + CoreDataError.shared.handle(error, message: "Error during deleting status (remove).") } } diff --git a/Vernissage/Vernissage.xcdatamodeld/.xccurrentversion b/CoreData/Vernissage.xcdatamodeld/.xccurrentversion similarity index 100% rename from Vernissage/Vernissage.xcdatamodeld/.xccurrentversion rename to CoreData/Vernissage.xcdatamodeld/.xccurrentversion diff --git a/Vernissage/Vernissage.xcdatamodeld/Vernissage-001.xcdatamodel/contents b/CoreData/Vernissage.xcdatamodeld/Vernissage-001.xcdatamodel/contents similarity index 100% rename from Vernissage/Vernissage.xcdatamodeld/Vernissage-001.xcdatamodel/contents rename to CoreData/Vernissage.xcdatamodeld/Vernissage-001.xcdatamodel/contents diff --git a/Vernissage/Vernissage.xcdatamodeld/Vernissage-002.xcdatamodel/contents b/CoreData/Vernissage.xcdatamodeld/Vernissage-002.xcdatamodel/contents similarity index 100% rename from Vernissage/Vernissage.xcdatamodeld/Vernissage-002.xcdatamodel/contents rename to CoreData/Vernissage.xcdatamodeld/Vernissage-002.xcdatamodel/contents diff --git a/Vernissage/Vernissage.xcdatamodeld/Vernissage-003.xcdatamodel/contents b/CoreData/Vernissage.xcdatamodeld/Vernissage-003.xcdatamodel/contents similarity index 100% rename from Vernissage/Vernissage.xcdatamodeld/Vernissage-003.xcdatamodel/contents rename to CoreData/Vernissage.xcdatamodeld/Vernissage-003.xcdatamodel/contents diff --git a/Vernissage/Vernissage.xcdatamodeld/Vernissage-004.xcdatamodel/contents b/CoreData/Vernissage.xcdatamodeld/Vernissage-004.xcdatamodel/contents similarity index 100% rename from Vernissage/Vernissage.xcdatamodeld/Vernissage-004.xcdatamodel/contents rename to CoreData/Vernissage.xcdatamodeld/Vernissage-004.xcdatamodel/contents diff --git a/Vernissage/Vernissage.xcdatamodeld/Vernissage-005.xcdatamodel/contents b/CoreData/Vernissage.xcdatamodeld/Vernissage-005.xcdatamodel/contents similarity index 100% rename from Vernissage/Vernissage.xcdatamodeld/Vernissage-005.xcdatamodel/contents rename to CoreData/Vernissage.xcdatamodeld/Vernissage-005.xcdatamodel/contents diff --git a/Vernissage/Vernissage.xcdatamodeld/Vernissage.xcdatamodel/contents b/CoreData/Vernissage.xcdatamodeld/Vernissage.xcdatamodel/contents similarity index 100% rename from Vernissage/Vernissage.xcdatamodeld/Vernissage.xcdatamodel/contents rename to CoreData/Vernissage.xcdatamodeld/Vernissage.xcdatamodel/contents diff --git a/Vernissage/AppConstants.swift b/Models/AppConstants.swift similarity index 77% rename from Vernissage/AppConstants.swift rename to Models/AppConstants.swift index 53969c9..722cfbb 100644 --- a/Vernissage/AppConstants.swift +++ b/Models/AppConstants.swift @@ -12,7 +12,11 @@ public struct AppConstants { public static let oauthCallbackPart = "oauth-callback" public static let oauthRedirectUri = "\(AppConstants.oauthScheme)://\(oauthCallbackPart)/pixelfed" public static let oauthScopes = ["read", "write", "follow", "push"] - + + public static let statusScheme = "status-vernissage" + public static let statusCallbackPart = "statuses" + public static let statusUri = "\(AppConstants.statusScheme)://\(statusCallbackPart)" + public static let imagePipelineCacheName = "dev.mczachurski.Vernissage.DataCache" public static let coreDataPersistantContainerName = "Vernissage" } diff --git a/Models/AvatarShape.swift b/Models/AvatarShape.swift new file mode 100644 index 0000000..771d055 --- /dev/null +++ b/Models/AvatarShape.swift @@ -0,0 +1,13 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation +import SwiftUI + +public enum AvatarShape: Int { + case circle = 1 + case roundedRectangle = 2 +} diff --git a/Models/Theme.swift b/Models/Theme.swift new file mode 100644 index 0000000..63dc130 --- /dev/null +++ b/Models/Theme.swift @@ -0,0 +1,11 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import SwiftUI + +public enum Theme: Int { + case system, light, dark +} diff --git a/Models/TintColor.swift b/Models/TintColor.swift new file mode 100644 index 0000000..528a8fd --- /dev/null +++ b/Models/TintColor.swift @@ -0,0 +1,20 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import SwiftUI + +public enum TintColor: Int { + case accentColor1 = 1 + case accentColor2 = 2 + case accentColor3 = 3 + case accentColor4 = 4 + case accentColor5 = 5 + case accentColor6 = 6 + case accentColor7 = 7 + case accentColor8 = 8 + case accentColor9 = 9 + case accentColor10 = 10 +} diff --git a/PixelfedKit/Sources/PixelfedKit/Entities/Html.swift b/PixelfedKit/Sources/PixelfedKit/Entities/Html.swift index 5372932..7e110bd 100644 --- a/PixelfedKit/Sources/PixelfedKit/Entities/Html.swift +++ b/PixelfedKit/Sources/PixelfedKit/Entities/Html.swift @@ -38,7 +38,13 @@ public struct Html: Codable { } private func parseToMarkdown(html: String) throws -> String { - let dom = try HTMLParser().parse(html: html) + + // Fix issue: https://github.com/VernissageApp/Home/issues/11 + let mutatedHtml = html + .replacingOccurrences(of: "
\n", with: "
") + .replacingOccurrences(of: "
\n", with: "
") + + let dom = try HTMLParser().parse(html: mutatedHtml) return dom.toMarkdown() // Add space between hashtags and mentions that follow each other .replacingOccurrences(of: ")[", with: ") [") diff --git a/Vernissage.xcodeproj/project.pbxproj b/Vernissage.xcodeproj/project.pbxproj index 1f09ab5..de314a5 100644 --- a/Vernissage.xcodeproj/project.pbxproj +++ b/Vernissage.xcodeproj/project.pbxproj @@ -48,6 +48,39 @@ F85E132529741F05006A051D /* ActivityIndicatorView in Frameworks */ = {isa = PBXBuildFile; productRef = F85E132429741F05006A051D /* ActivityIndicatorView */; }; F86167C6297FE6CC004D1F67 /* AvatarShapesSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86167C5297FE6CC004D1F67 /* AvatarShapesSectionView.swift */; }; F86167C8297FE781004D1F67 /* AvatarShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86167C7297FE781004D1F67 /* AvatarShape.swift */; }; + F864F75F29BB91B400B13921 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F864F75E29BB91B400B13921 /* WidgetKit.framework */; }; + F864F76129BB91B400B13921 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F864F76029BB91B400B13921 /* SwiftUI.framework */; }; + F864F76429BB91B400B13921 /* VernissageWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F864F76329BB91B400B13921 /* VernissageWidgetBundle.swift */; }; + F864F76629BB91B400B13921 /* VernissageWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = F864F76529BB91B400B13921 /* VernissageWidget.swift */; }; + F864F76829BB91B600B13921 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F864F76729BB91B600B13921 /* Assets.xcassets */; }; + F864F76C29BB91B600B13921 /* VernissageWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = F864F75D29BB91B400B13921 /* VernissageWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + F864F77529BB92CE00B13921 /* Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F864F77329BB929A00B13921 /* Provider.swift */; }; + F864F77629BB92CE00B13921 /* VernissageWidgetEntryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F864F77129BB924D00B13921 /* VernissageWidgetEntryView.swift */; }; + F864F77829BB930000B13921 /* WidgetEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = F864F77729BB930000B13921 /* WidgetEntry.swift */; }; + F864F77A29BB94A800B13921 /* PixelfedKit in Frameworks */ = {isa = PBXBuildFile; productRef = F864F77929BB94A800B13921 /* PixelfedKit */; }; + F864F77C29BB982100B13921 /* ImageFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = F864F77B29BB982100B13921 /* ImageFetcher.swift */; }; + F864F77D29BB9A4600B13921 /* AttachmentData+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80047FF2961850500E6868A /* AttachmentData+CoreDataClass.swift */; }; + F864F77E29BB9A4900B13921 /* AttachmentData+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80048002961850500E6868A /* AttachmentData+CoreDataProperties.swift */; }; + F864F78229BB9A6500B13921 /* StatusData+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80048012961850500E6868A /* StatusData+CoreDataClass.swift */; }; + F864F78329BB9A6800B13921 /* StatusData+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80048022961850500E6868A /* StatusData+CoreDataProperties.swift */; }; + F864F78429BB9A6E00B13921 /* ApplicationSettings+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = F866F69E296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift */; }; + F864F78529BB9A7100B13921 /* ApplicationSettings+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F866F69F296040A8002E8F88 /* ApplicationSettings+CoreDataProperties.swift */; }; + F864F78629BB9A7400B13921 /* AccountData+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD28295F43B8009B20C9 /* AccountData+CoreDataClass.swift */; }; + F864F78729BB9A7700B13921 /* AccountData+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD29295F43B8009B20C9 /* AccountData+CoreDataProperties.swift */; }; + F864F78829BB9A7B00B13921 /* CoreDataHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88C2474295C37BB0006098B /* CoreDataHandler.swift */; }; + F864F78929BB9A7D00B13921 /* AccountDataHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F866F6A229604161002E8F88 /* AccountDataHandler.swift */; }; + F864F78A29BB9A8000B13921 /* ApplicationSettingsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F866F6A429604194002E8F88 /* ApplicationSettingsHandler.swift */; }; + F864F78B29BB9A8300B13921 /* StatusDataHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80048072961E6DE00E6868A /* StatusDataHandler.swift */; }; + F864F78C29BB9A8500B13921 /* AttachmentDataHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80048092961EA1900E6868A /* AttachmentDataHandler.swift */; }; + F864F78E29BB9B2F00B13921 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89D6C3E29716E41001DA3D4 /* Theme.swift */; }; + F864F78F29BB9B3100B13921 /* AvatarShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86167C7297FE781004D1F67 /* AvatarShape.swift */; }; + F864F79029BB9B3300B13921 /* TintColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CC95CD2970761D00C9C2AC /* TintColor.swift */; }; + F864F79D29BB9D3400B13921 /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = F87AEB932986C51B00434FB6 /* AppConstants.swift */; }; + F864F79F29BB9E6A00B13921 /* TintColor+Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = F864F79E29BB9E6A00B13921 /* TintColor+Color.swift */; }; + F864F7A129BB9E8F00B13921 /* AvatarShape+Shape.swift in Sources */ = {isa = PBXBuildFile; fileRef = F864F7A029BB9E8F00B13921 /* AvatarShape+Shape.swift */; }; + F864F7A329BB9EC700B13921 /* Theme+ColorScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = F864F7A229BB9EC700B13921 /* Theme+ColorScheme.swift */; }; + F864F7A529BBA01D00B13921 /* CoreDataError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F864F7A429BBA01D00B13921 /* CoreDataError.swift */; }; + F864F7A629BBA01D00B13921 /* CoreDataError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F864F7A429BBA01D00B13921 /* CoreDataError.swift */; }; F866F6A0296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = F866F69E296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift */; }; F866F6A1296040A8002E8F88 /* ApplicationSettings+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F866F69F296040A8002E8F88 /* ApplicationSettings+CoreDataProperties.swift */; }; F866F6A329604161002E8F88 /* AccountDataHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F866F6A229604161002E8F88 /* AccountDataHandler.swift */; }; @@ -159,6 +192,11 @@ F8CC95CE2970761D00C9C2AC /* TintColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CC95CD2970761D00C9C2AC /* TintColor.swift */; }; F8CEEDF829ABADDD00DBED66 /* UIImage+Size.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CEEDF729ABADDD00DBED66 /* UIImage+Size.swift */; }; F8CEEDFA29ABAFD200DBED66 /* ImageFileTranseferable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CEEDF929ABAFD200DBED66 /* ImageFileTranseferable.swift */; }; + F8F6E44229BC58F20004795E /* Vernissage.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = F88C2476295C37BB0006098B /* Vernissage.xcdatamodeld */; }; + F8F6E44C29BCC1F70004795E /* SmallWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F6E44629BCC0DC0004795E /* SmallWidgetView.swift */; }; + F8F6E44D29BCC1F90004795E /* MediumWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F6E44829BCC0F00004795E /* MediumWidgetView.swift */; }; + F8F6E44E29BCC1FB0004795E /* LargeWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F6E44A29BCC0FF0004795E /* LargeWidgetView.swift */; }; + F8F6E45129BCE9190004795E /* UIImage+Resize.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F6E45029BCE9190004795E /* UIImage+Resize.swift */; }; F8FA9917299F7DBD007AB130 /* Client+Media.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8FA9916299F7DBD007AB130 /* Client+Media.swift */; }; F8FA9919299FA35A007AB130 /* PhotoAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8FA9918299FA35A007AB130 /* PhotoAttachment.swift */; }; F8FA991C299FA8C2007AB130 /* ImageUploadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8FA991B299FA8C2007AB130 /* ImageUploadView.swift */; }; @@ -166,6 +204,30 @@ F8FA9920299FDDC3007AB130 /* TextInputField.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8FA991F299FDDC3007AB130 /* TextInputField.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + F864F76A29BB91B600B13921 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F88C2460295C37B80006098B /* Project object */; + proxyType = 1; + remoteGlobalIDString = F864F75C29BB91B400B13921; + remoteInfo = VernissageWidgetExtension; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + F864F76D29BB91B600B13921 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + F864F76C29BB91B600B13921 /* VernissageWidgetExtension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ F80047FF2961850500E6868A /* AttachmentData+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttachmentData+CoreDataClass.swift"; sourceTree = ""; }; F80048002961850500E6868A /* AttachmentData+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttachmentData+CoreDataProperties.swift"; sourceTree = ""; }; @@ -204,6 +266,21 @@ F85E131F297409CD006A051D /* ErrorsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorsService.swift; sourceTree = ""; }; F86167C5297FE6CC004D1F67 /* AvatarShapesSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarShapesSectionView.swift; sourceTree = ""; }; F86167C7297FE781004D1F67 /* AvatarShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarShape.swift; sourceTree = ""; }; + F864F75D29BB91B400B13921 /* VernissageWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = VernissageWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + F864F75E29BB91B400B13921 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; + F864F76029BB91B400B13921 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; + F864F76329BB91B400B13921 /* VernissageWidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VernissageWidgetBundle.swift; sourceTree = ""; }; + F864F76529BB91B400B13921 /* VernissageWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VernissageWidget.swift; sourceTree = ""; }; + F864F76729BB91B600B13921 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + F864F76929BB91B600B13921 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F864F77129BB924D00B13921 /* VernissageWidgetEntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VernissageWidgetEntryView.swift; sourceTree = ""; }; + F864F77329BB929A00B13921 /* Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Provider.swift; sourceTree = ""; }; + F864F77729BB930000B13921 /* WidgetEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetEntry.swift; sourceTree = ""; }; + F864F77B29BB982100B13921 /* ImageFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageFetcher.swift; sourceTree = ""; }; + F864F79E29BB9E6A00B13921 /* TintColor+Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TintColor+Color.swift"; sourceTree = ""; }; + F864F7A029BB9E8F00B13921 /* AvatarShape+Shape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AvatarShape+Shape.swift"; sourceTree = ""; }; + F864F7A229BB9EC700B13921 /* Theme+ColorScheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+ColorScheme.swift"; sourceTree = ""; }; + F864F7A429BBA01D00B13921 /* CoreDataError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataError.swift; sourceTree = ""; }; F866F69E296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ApplicationSettings+CoreDataClass.swift"; sourceTree = ""; }; F866F69F296040A8002E8F88 /* ApplicationSettings+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ApplicationSettings+CoreDataProperties.swift"; sourceTree = ""; }; F866F6A229604161002E8F88 /* AccountDataHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDataHandler.swift; sourceTree = ""; }; @@ -321,6 +398,12 @@ F8CC95CD2970761D00C9C2AC /* TintColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TintColor.swift; sourceTree = ""; }; F8CEEDF729ABADDD00DBED66 /* UIImage+Size.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Size.swift"; sourceTree = ""; }; F8CEEDF929ABAFD200DBED66 /* ImageFileTranseferable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageFileTranseferable.swift; sourceTree = ""; }; + F8F6E44329BC5CAA0004795E /* VernissageWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = VernissageWidgetExtension.entitlements; sourceTree = ""; }; + F8F6E44429BC5CC50004795E /* Vernissage.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Vernissage.entitlements; sourceTree = ""; }; + F8F6E44629BCC0DC0004795E /* SmallWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmallWidgetView.swift; sourceTree = ""; }; + F8F6E44829BCC0F00004795E /* MediumWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediumWidgetView.swift; sourceTree = ""; }; + F8F6E44A29BCC0FF0004795E /* LargeWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeWidgetView.swift; sourceTree = ""; }; + F8F6E45029BCE9190004795E /* UIImage+Resize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Resize.swift"; sourceTree = ""; }; F8FA9916299F7DBD007AB130 /* Client+Media.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Client+Media.swift"; sourceTree = ""; }; F8FA9918299FA35A007AB130 /* PhotoAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoAttachment.swift; sourceTree = ""; }; F8FA991B299FA8C2007AB130 /* ImageUploadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUploadView.swift; sourceTree = ""; }; @@ -329,6 +412,16 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + F864F75A29BB91B400B13921 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F864F76129BB91B400B13921 /* SwiftUI.framework in Frameworks */, + F864F77A29BB94A800B13921 /* PixelfedKit in Frameworks */, + F864F75F29BB91B400B13921 /* WidgetKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F88C2465295C37B80006098B /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -428,6 +521,9 @@ F8864CEE29ACE90B0020C534 /* UIFont+Font.swift */, F8864CF029ACFFB80020C534 /* View+Keyboard.swift */, F8CAE63F29B8E6E1001E0372 /* UIApplication+Window.swift */, + F864F79E29BB9E6A00B13921 /* TintColor+Color.swift */, + F864F7A029BB9E8F00B13921 /* AvatarShape+Shape.swift */, + F864F7A229BB9EC700B13921 /* Theme+ColorScheme.swift */, ); path = Extensions; sourceTree = ""; @@ -440,9 +536,6 @@ F898DE7129728CB2004B4A6A /* CommentModel.swift */, F8C5E55E2988E92600ADF6A7 /* AccountModel.swift */, F866F6AD29606367002E8F88 /* ApplicationViewMode.swift */, - F8CC95CD2970761D00C9C2AC /* TintColor.swift */, - F89D6C3E29716E41001DA3D4 /* Theme.swift */, - F86167C7297FE781004D1F67 /* AvatarShape.swift */, F8764186298ABB520057D362 /* ViewState.swift */, F8FA9918299FA35A007AB130 /* PhotoAttachment.swift */, F89AC00429A1F9B500F4159F /* AppMetadata.swift */, @@ -456,6 +549,7 @@ F8341F96295C6427009C8EE6 /* CoreData */ = { isa = PBXGroup; children = ( + F88C2476295C37BB0006098B /* Vernissage.xcdatamodeld */, F80047FF2961850500E6868A /* AttachmentData+CoreDataClass.swift */, F80048002961850500E6868A /* AttachmentData+CoreDataProperties.swift */, F85D498229642FAC00751DF7 /* AttachmentData+Comperable.swift */, @@ -474,6 +568,7 @@ F866F6A429604194002E8F88 /* ApplicationSettingsHandler.swift */, F80048072961E6DE00E6868A /* StatusDataHandler.swift */, F80048092961EA1900E6868A /* AttachmentDataHandler.swift */, + F864F7A429BBA01D00B13921 /* CoreDataError.swift */, ); path = CoreData; sourceTree = ""; @@ -508,6 +603,34 @@ path = Widgets; sourceTree = ""; }; + F864F76229BB91B400B13921 /* VernissageWidget */ = { + isa = PBXGroup; + children = ( + F8F6E44F29BCE9030004795E /* Extensions */, + F8F6E44529BCC0C90004795E /* Views */, + F864F77B29BB982100B13921 /* ImageFetcher.swift */, + F864F77729BB930000B13921 /* WidgetEntry.swift */, + F864F77329BB929A00B13921 /* Provider.swift */, + F864F76329BB91B400B13921 /* VernissageWidgetBundle.swift */, + F864F77129BB924D00B13921 /* VernissageWidgetEntryView.swift */, + F864F76529BB91B400B13921 /* VernissageWidget.swift */, + F864F76729BB91B600B13921 /* Assets.xcassets */, + F864F76929BB91B600B13921 /* Info.plist */, + ); + path = VernissageWidget; + sourceTree = ""; + }; + F864F79C29BB9D2400B13921 /* Models */ = { + isa = PBXGroup; + children = ( + F8CC95CD2970761D00C9C2AC /* TintColor.swift */, + F86167C7297FE781004D1F67 /* AvatarShape.swift */, + F89D6C3E29716E41001DA3D4 /* Theme.swift */, + F87AEB932986C51B00434FB6 /* AppConstants.swift */, + ); + path = Models; + sourceTree = ""; + }; F86B721F296C498B00EE59EC /* Styles */ = { isa = PBXGroup; children = ( @@ -595,9 +718,13 @@ F88C245F295C37B80006098B = { isa = PBXGroup; children = ( + F8F6E44329BC5CAA0004795E /* VernissageWidgetExtension.entitlements */, F837269429A221420098D3C4 /* PixelfedKit */, F88ABD9529687D4D004EF61E /* README.md */, + F864F79C29BB9D2400B13921 /* Models */, + F8341F96295C6427009C8EE6 /* CoreData */, F88C246A295C37B80006098B /* Vernissage */, + F864F76229BB91B400B13921 /* VernissageWidget */, F88C2469295C37B80006098B /* Products */, F89992C5296D3DF8005994BF /* Frameworks */, ); @@ -607,6 +734,7 @@ isa = PBXGroup; children = ( F88C2468295C37B80006098B /* Vernissage.app */, + F864F75D29BB91B400B13921 /* VernissageWidgetExtension.appex */, ); name = Products; sourceTree = ""; @@ -614,6 +742,7 @@ F88C246A295C37B80006098B /* Vernissage */ = { isa = PBXGroup; children = ( + F8F6E44429BC5CC50004795E /* Vernissage.entitlements */, F866F6A829604FFF002E8F88 /* Info.plist */, F802884D297AEEAA000BDD51 /* Errors */, F86B721F296C498B00EE59EC /* Styles */, @@ -623,18 +752,15 @@ F88FAD30295F5010009B20C9 /* Services */, F8C5E56029892C8A00ADF6A7 /* ViewModifiers */, F83901A2295D863B00456AE2 /* Widgets */, - F8341F96295C6427009C8EE6 /* CoreData */, F8341F95295C640C009C8EE6 /* Models */, F8B9B354298D4B88009CC69C /* EnvironmentObjects */, F8341F94295C63FE009C8EE6 /* Extensions */, F8341F93295C63E2009C8EE6 /* Views */, F88C246B295C37B80006098B /* VernissageApp.swift */, F88E4D55297EAD6E0057491A /* AppRouteur.swift */, - F87AEB932986C51B00434FB6 /* AppConstants.swift */, F86A4300299A97F500DF7645 /* ProductIdentifiers.swift */, F866F6A929605AFA002E8F88 /* SceneDelegate.swift */, F88C246F295C37BB0006098B /* Assets.xcassets */, - F88C2476295C37BB0006098B /* Vernissage.xcdatamodeld */, F88C2471295C37BB0006098B /* Preview Content */, F86A42FE299A8C5500DF7645 /* InAppPurchaseStoreKitConfiguration.storekit */, ); @@ -652,12 +778,12 @@ F88FAD30295F5010009B20C9 /* Services */ = { isa = PBXGroup; children = ( + F85E131F297409CD006A051D /* ErrorsService.swift */, + F8B1E6502973FB7E00EE0D10 /* ToastrService.swift */, F88FAD31295F5029009B20C9 /* RemoteFileService.swift */, F85D4970296402DC00751DF7 /* AuthorizationService.swift */, F87AEB912986C44E00434FB6 /* AuthorizationSession.swift */, F85D4974296407F100751DF7 /* HomeTimelineService.swift */, - F8B1E6502973FB7E00EE0D10 /* ToastrService.swift */, - F85E131F297409CD006A051D /* ErrorsService.swift */, F886F256297859E300879356 /* CacheImageService.swift */, F88E4D49297EA0490057491A /* RouterPath.swift */, F829193B2983012400367CE2 /* ImageSizeService.swift */, @@ -678,6 +804,8 @@ isa = PBXGroup; children = ( F86A42FC299A8B8E00DF7645 /* StoreKit.framework */, + F864F75E29BB91B400B13921 /* WidgetKit.framework */, + F864F76029BB91B400B13921 /* SwiftUI.framework */, ); name = Frameworks; sourceTree = ""; @@ -738,6 +866,24 @@ path = ViewModifiers; sourceTree = ""; }; + F8F6E44529BCC0C90004795E /* Views */ = { + isa = PBXGroup; + children = ( + F8F6E44629BCC0DC0004795E /* SmallWidgetView.swift */, + F8F6E44829BCC0F00004795E /* MediumWidgetView.swift */, + F8F6E44A29BCC0FF0004795E /* LargeWidgetView.swift */, + ); + path = Views; + sourceTree = ""; + }; + F8F6E44F29BCE9030004795E /* Extensions */ = { + isa = PBXGroup; + children = ( + F8F6E45029BCE9190004795E /* UIImage+Resize.swift */, + ); + path = Extensions; + sourceTree = ""; + }; F8FA991A299FA8A5007AB130 /* ComposeView */ = { isa = PBXGroup; children = ( @@ -750,6 +896,26 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + F864F75C29BB91B400B13921 /* VernissageWidgetExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = F864F77029BB91B600B13921 /* Build configuration list for PBXNativeTarget "VernissageWidgetExtension" */; + buildPhases = ( + F864F75929BB91B400B13921 /* Sources */, + F864F75A29BB91B400B13921 /* Frameworks */, + F864F75B29BB91B400B13921 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = VernissageWidgetExtension; + packageProductDependencies = ( + F864F77929BB94A800B13921 /* PixelfedKit */, + ); + productName = VernissageWidgetExtension; + productReference = F864F75D29BB91B400B13921 /* VernissageWidgetExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; F88C2467295C37B80006098B /* Vernissage */ = { isa = PBXNativeTarget; buildConfigurationList = F88C247B295C37BB0006098B /* Build configuration list for PBXNativeTarget "Vernissage" */; @@ -757,10 +923,12 @@ F88C2464295C37B80006098B /* Sources */, F88C2465295C37B80006098B /* Frameworks */, F88C2466295C37B80006098B /* Resources */, + F864F76D29BB91B600B13921 /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( + F864F76B29BB91B600B13921 /* PBXTargetDependency */, ); name = Vernissage; packageProductDependencies = ( @@ -786,6 +954,9 @@ LastSwiftUpdateCheck = 1420; LastUpgradeCheck = 1420; TargetAttributes = { + F864F75C29BB91B400B13921 = { + CreatedOnToolsVersion = 14.2; + }; F88C2467295C37B80006098B = { CreatedOnToolsVersion = 14.2; }; @@ -811,11 +982,20 @@ projectRoot = ""; targets = ( F88C2467295C37B80006098B /* Vernissage */, + F864F75C29BB91B400B13921 /* VernissageWidgetExtension */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + F864F75B29BB91B400B13921 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F864F76829BB91B600B13921 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F88C2466295C37B80006098B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -829,6 +1009,42 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + F864F75929BB91B400B13921 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F864F77829BB930000B13921 /* WidgetEntry.swift in Sources */, + F864F77529BB92CE00B13921 /* Provider.swift in Sources */, + F864F77629BB92CE00B13921 /* VernissageWidgetEntryView.swift in Sources */, + F864F79D29BB9D3400B13921 /* AppConstants.swift in Sources */, + F864F77C29BB982100B13921 /* ImageFetcher.swift in Sources */, + F8F6E44229BC58F20004795E /* Vernissage.xcdatamodeld in Sources */, + F8F6E44C29BCC1F70004795E /* SmallWidgetView.swift in Sources */, + F864F76629BB91B400B13921 /* VernissageWidget.swift in Sources */, + F8F6E44D29BCC1F90004795E /* MediumWidgetView.swift in Sources */, + F864F79029BB9B3300B13921 /* TintColor.swift in Sources */, + F8F6E44E29BCC1FB0004795E /* LargeWidgetView.swift in Sources */, + F864F76429BB91B400B13921 /* VernissageWidgetBundle.swift in Sources */, + F864F78F29BB9B3100B13921 /* AvatarShape.swift in Sources */, + F864F78E29BB9B2F00B13921 /* Theme.swift in Sources */, + F864F77D29BB9A4600B13921 /* AttachmentData+CoreDataClass.swift in Sources */, + F864F7A629BBA01D00B13921 /* CoreDataError.swift in Sources */, + F864F77E29BB9A4900B13921 /* AttachmentData+CoreDataProperties.swift in Sources */, + F864F78229BB9A6500B13921 /* StatusData+CoreDataClass.swift in Sources */, + F864F78329BB9A6800B13921 /* StatusData+CoreDataProperties.swift in Sources */, + F864F78429BB9A6E00B13921 /* ApplicationSettings+CoreDataClass.swift in Sources */, + F864F78629BB9A7400B13921 /* AccountData+CoreDataClass.swift in Sources */, + F864F78529BB9A7100B13921 /* ApplicationSettings+CoreDataProperties.swift in Sources */, + F864F78729BB9A7700B13921 /* AccountData+CoreDataProperties.swift in Sources */, + F864F78829BB9A7B00B13921 /* CoreDataHandler.swift in Sources */, + F8F6E45129BCE9190004795E /* UIImage+Resize.swift in Sources */, + F864F78929BB9A7D00B13921 /* AccountDataHandler.swift in Sources */, + F864F78A29BB9A8000B13921 /* ApplicationSettingsHandler.swift in Sources */, + F864F78C29BB9A8500B13921 /* AttachmentDataHandler.swift in Sources */, + F864F78B29BB9A8300B13921 /* StatusDataHandler.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F88C2464295C37B80006098B /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -843,6 +1059,7 @@ F80048082961E6DE00E6868A /* StatusDataHandler.swift in Sources */, F866F6A0296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift in Sources */, F8764189298ABEC80057D362 /* ErrorView.swift in Sources */, + F864F7A129BB9E8F00B13921 /* AvatarShape+Shape.swift in Sources */, F8210DEA2966E4F9001D9973 /* AnimatePlaceholderModifier.swift in Sources */, F886F257297859E300879356 /* CacheImageService.swift in Sources */, F8B08862299435C9002AB40A /* SupportView.swift in Sources */, @@ -907,6 +1124,7 @@ F89D6C4A297196FF001DA3D4 /* ImageViewer.swift in Sources */, F8A93D7E2965FD89001D8331 /* UserProfileView.swift in Sources */, F88C246E295C37B80006098B /* MainView.swift in Sources */, + F864F7A329BB9EC700B13921 /* Theme+ColorScheme.swift in Sources */, F89AC00729A208CC00F4159F /* PlaceSelectorView.swift in Sources */, F8AFF7C429B25EF40087D083 /* ImagesGrid.swift in Sources */, F86B721E296C458700EE59EC /* BlurredImage.swift in Sources */, @@ -933,6 +1151,7 @@ F85D49852964301800751DF7 /* StatusData+Attachments.swift in Sources */, F8B9B34D298D4AE4009CC69C /* Client+Notifications.swift in Sources */, F8764187298ABB520057D362 /* ViewState.swift in Sources */, + F864F79F29BB9E6A00B13921 /* TintColor+Color.swift in Sources */, F8210DE72966E1D1001D9973 /* Color+Assets.swift in Sources */, F88ABD9429687CA4004EF61E /* ComposeView.swift in Sources */, F89CEB802984198600A1376F /* AttachmentData+HighestImage.swift in Sources */, @@ -967,6 +1186,7 @@ F8FA9920299FDDC3007AB130 /* TextInputField.swift in Sources */, F86A4303299A9AF500DF7645 /* TipsStore.swift in Sources */, F8C5E56229892CC300ADF6A7 /* FirstAppear.swift in Sources */, + F864F7A529BBA01D00B13921 /* CoreDataError.swift in Sources */, F8864CEB29ACBAA80020C534 /* TextModel.swift in Sources */, F8C14394296AF21B001FE31D /* Double+Round.swift in Sources */, F83CBEFB298298A1002972C8 /* ImageCarouselPicture.swift in Sources */, @@ -984,7 +1204,72 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + F864F76B29BB91B600B13921 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F864F75C29BB91B400B13921 /* VernissageWidgetExtension */; + targetProxy = F864F76A29BB91B600B13921 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ + F864F76E29BB91B600B13921 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_ENTITLEMENTS = VernissageWidgetExtension.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 63; + DEVELOPMENT_TEAM = B2U9FEKYP8; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = VernissageWidget/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = VernissageWidget; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage.widget; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Debug; + }; + F864F76F29BB91B600B13921 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_ENTITLEMENTS = VernissageWidgetExtension.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 63; + DEVELOPMENT_TEAM = B2U9FEKYP8; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = VernissageWidget/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = VernissageWidget; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage.widget; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Release; + }; F88C2479295C37BB0006098B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1102,11 +1387,13 @@ F88C247C295C37BB0006098B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 61; + CURRENT_PROJECT_VERSION = 63; DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\""; DEVELOPMENT_TEAM = B2U9FEKYP8; ENABLE_PREVIEWS = YES; @@ -1114,7 +1401,6 @@ INFOPLIST_FILE = Vernissage/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Vernissage; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.photography"; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; @@ -1140,10 +1426,12 @@ F88C247D295C37BB0006098B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 61; + CURRENT_PROJECT_VERSION = 63; DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\""; DEVELOPMENT_TEAM = B2U9FEKYP8; ENABLE_PREVIEWS = YES; @@ -1151,7 +1439,6 @@ INFOPLIST_FILE = Vernissage/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Vernissage; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.photography"; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; @@ -1176,6 +1463,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + F864F77029BB91B600B13921 /* Build configuration list for PBXNativeTarget "VernissageWidgetExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F864F76E29BB91B600B13921 /* Debug */, + F864F76F29BB91B600B13921 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; F88C2463295C37B80006098B /* Build configuration list for PBXProject "Vernissage" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -1256,6 +1552,10 @@ package = F85E132329741F05006A051D /* XCRemoteSwiftPackageReference "ActivityIndicatorView" */; productName = ActivityIndicatorView; }; + F864F77929BB94A800B13921 /* PixelfedKit */ = { + isa = XCSwiftPackageProductDependency; + productName = PixelfedKit; + }; F88E4D4C297EA4290057491A /* EmojiText */ = { isa = XCSwiftPackageProductDependency; package = F88E4D4B297EA4290057491A /* XCRemoteSwiftPackageReference "EmojiText" */; diff --git a/Vernissage/CoreData/CoreDataHandler.swift b/Vernissage/CoreData/CoreDataHandler.swift deleted file mode 100644 index 0971d24..0000000 --- a/Vernissage/CoreData/CoreDataHandler.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// https://mczachurski.dev -// Copyright © 2022 Marcin Czachurski and the repository contributors. -// Licensed under the MIT License. -// - -import CoreData - -public class CoreDataHandler { - public static let shared = CoreDataHandler() - - public let container: NSPersistentContainer - - private init(inMemory: Bool = false) { - container = NSPersistentContainer(name: AppConstants.coreDataPersistantContainerName) - if inMemory { - container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") - } - - container.loadPersistentStores(completionHandler: { (storeDescription, error) in - if let error = error as NSError? { - // Replace this implementation with code to handle the error appropriately. - // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. - - /* - Typical reasons for an error here include: - * The parent directory does not exist, cannot be created, or disallows writing. - * The persistent store is not accessible, due to permissions or data protection when the device is locked. - * The device is out of space. - * The store could not be migrated to the current model version. - Check the error message to determine what the actual problem was. - */ - fatalError("Unresolved error \(error), \(error.userInfo)") - } - }) - container.viewContext.automaticallyMergesChangesFromParent = true - } - - public func newBackgroundContext() -> NSManagedObjectContext { - self.container.newBackgroundContext() - } - - public func save() { - let context = self.container.viewContext - if context.hasChanges { - do { - try context.save() - } catch { - // Replace this implementation with code to handle the error appropriately. - // fatalError() causes the application to generate a crash log and terminate. - // You should not use this function in a shipping application, although it may be useful during development. - - #if DEBUG - let nserror = error as NSError - fatalError("Unresolved error \(nserror), \(nserror.userInfo)") - #else - ErrorService.shared.handle(error, message: "An error occurred while writing the data.") - #endif - } - } - } -} diff --git a/Vernissage/EnvironmentObjects/ApplicationState.swift b/Vernissage/EnvironmentObjects/ApplicationState.swift index 2477ae0..42ab236 100644 --- a/Vernissage/EnvironmentObjects/ApplicationState.swift +++ b/Vernissage/EnvironmentObjects/ApplicationState.swift @@ -76,6 +76,9 @@ public class ApplicationState: ObservableObject { /// Should photo description for visually impaired be displayed. @Published var showPhotoDescription = false + /// Status which should be shown from URL. + @Published var showStatusId: String? = nil + public func changeApplicationState(accountModel: AccountModel, instance: Instance?, lastSeenStatusId: String?) { self.account = accountModel self.lastSeenStatusId = lastSeenStatusId diff --git a/Vernissage/Models/AvatarShape.swift b/Vernissage/Extensions/AvatarShape+Shape.swift similarity index 82% rename from Vernissage/Models/AvatarShape.swift rename to Vernissage/Extensions/AvatarShape+Shape.swift index 7d08182..f5a74b5 100644 --- a/Vernissage/Models/AvatarShape.swift +++ b/Vernissage/Extensions/AvatarShape+Shape.swift @@ -7,10 +7,7 @@ import Foundation import SwiftUI -public enum AvatarShape: Int { - case circle = 1 - case roundedRectangle = 2 - +extension AvatarShape { func shape() -> some Shape { switch self { case .circle: diff --git a/Vernissage/Models/Theme.swift b/Vernissage/Extensions/Theme+ColorScheme.swift similarity index 87% rename from Vernissage/Models/Theme.swift rename to Vernissage/Extensions/Theme+ColorScheme.swift index 9a60b24..db1ebae 100644 --- a/Vernissage/Models/Theme.swift +++ b/Vernissage/Extensions/Theme+ColorScheme.swift @@ -4,11 +4,10 @@ // Licensed under the MIT License. // +import Foundation import SwiftUI -public enum Theme: Int { - case system, light, dark - +extension Theme { public func colorScheme() -> ColorScheme? { switch self { case .system: diff --git a/Vernissage/Models/TintColor.swift b/Vernissage/Extensions/TintColor+Color.swift similarity index 77% rename from Vernissage/Models/TintColor.swift rename to Vernissage/Extensions/TintColor+Color.swift index 00b8f83..1407c36 100644 --- a/Vernissage/Models/TintColor.swift +++ b/Vernissage/Extensions/TintColor+Color.swift @@ -4,20 +4,10 @@ // Licensed under the MIT License. // +import Foundation import SwiftUI -public enum TintColor: Int { - case accentColor1 = 1 - case accentColor2 = 2 - case accentColor3 = 3 - case accentColor4 = 4 - case accentColor5 = 5 - case accentColor6 = 6 - case accentColor7 = 7 - case accentColor8 = 8 - case accentColor9 = 9 - case accentColor10 = 10 - +extension TintColor { public func color() -> Color { switch self { case .accentColor1: diff --git a/Vernissage/Info.plist b/Vernissage/Info.plist index 2ee4dde..b31dd73 100644 --- a/Vernissage/Info.plist +++ b/Vernissage/Info.plist @@ -2,11 +2,6 @@ - UIApplicationSceneManifest - - UISceneConfigurations - - CFBundleURLTypes @@ -17,6 +12,19 @@ oauth-vernissage + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + status-vernissage + + + UIApplicationSceneManifest + + UISceneConfigurations + + diff --git a/Vernissage/SceneDelegate.swift b/Vernissage/SceneDelegate.swift index a2b22ab..9895e16 100644 --- a/Vernissage/SceneDelegate.swift +++ b/Vernissage/SceneDelegate.swift @@ -16,6 +16,11 @@ class SceneDelegate: NSObject, UISceneDelegate { if url.host == AppConstants.oauthCallbackPart { OAuthSwift.handle(url: url) + } else if url.host == AppConstants.statusCallbackPart { + let statusId = url.string.replacingOccurrences(of: "\(AppConstants.statusUri)/", with: "") + if statusId.isEmpty == false { + ApplicationState.shared.showStatusId = statusId + } } } } diff --git a/Vernissage/Services/ToastrService.swift b/Vernissage/Services/ToastrService.swift index 544357d..e17ab81 100644 --- a/Vernissage/Services/ToastrService.swift +++ b/Vernissage/Services/ToastrService.swift @@ -17,7 +17,7 @@ public class ToastrService { title: title, subtitle: subtitle, subtitleNumberOfLines: 2, - icon: self.createImage(systemName: imageSystemName, color: ApplicationState.shared.tintColor.uiColor()), + icon: self.createImage(systemName: imageSystemName, color: UIColor(Color.accentColor)), action: .init { Drops.hideCurrent() }, @@ -34,7 +34,7 @@ public class ToastrService { title: title, subtitle: subtitle, subtitleNumberOfLines: 2, - icon: self.createImage(systemName: imageSystemName, color: Color.red.toUIColor()), + icon: self.createImage(systemName: imageSystemName, color: UIColor(Color.red)), action: .init { Drops.hideCurrent() }, diff --git a/Vernissage/Vernissage.entitlements b/Vernissage/Vernissage.entitlements new file mode 100644 index 0000000..bff12cc --- /dev/null +++ b/Vernissage/Vernissage.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.dev.mczachurski.vernissage + + + diff --git a/Vernissage/VernissageApp.swift b/Vernissage/VernissageApp.swift index 23b0b0e..18e90da 100644 --- a/Vernissage/VernissageApp.swift +++ b/Vernissage/VernissageApp.swift @@ -104,6 +104,12 @@ struct VernissageApp: App { self.applicationViewMode = .signIn } } + .onChange(of: applicationState.showStatusId) { newValue in + if let statusId = newValue { + self.routerPath.navigate(to: .status(id: statusId)) + self.applicationState.showStatusId = nil + } + } } } diff --git a/VernissageWidget/Assets.xcassets/AccentColor.colorset/Contents.json b/VernissageWidget/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/VernissageWidget/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/VernissageWidget/Assets.xcassets/AppIcon.appiconset/Contents.json b/VernissageWidget/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..13613e3 --- /dev/null +++ b/VernissageWidget/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/VernissageWidget/Assets.xcassets/Contents.json b/VernissageWidget/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/VernissageWidget/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/VernissageWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json b/VernissageWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/VernissageWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/VernissageWidget/Extensions/UIImage+Resize.swift b/VernissageWidget/Extensions/UIImage+Resize.swift new file mode 100644 index 0000000..6be2d61 --- /dev/null +++ b/VernissageWidget/Extensions/UIImage+Resize.swift @@ -0,0 +1,20 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import SwiftUI + +extension UIImage { + func resized(toWidth width: CGFloat, isOpaque: Bool = true) -> UIImage? { + let canvas = CGSize(width: width, height: CGFloat(ceil(width/size.width * size.height))) + + let format = imageRendererFormat + format.opaque = isOpaque + + return UIGraphicsImageRenderer(size: canvas, format: format).image { + _ in draw(in: CGRect(origin: .zero, size: canvas)) + } + } +} diff --git a/VernissageWidget/ImageFetcher.swift b/VernissageWidget/ImageFetcher.swift new file mode 100644 index 0000000..5bbaf47 --- /dev/null +++ b/VernissageWidget/ImageFetcher.swift @@ -0,0 +1,92 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation +import SwiftUI +import PixelfedKit + +public class ImageFetcher { + public static let shared = ImageFetcher() + private init() { } + + func fetchWidgetEntries(length: Int = 6) async throws -> [WidgetEntry] { + let defaultSettings = ApplicationSettingsHandler.shared.getDefaultSettings() + guard let accountId = defaultSettings.currentAccount else { + return [self.placeholder()] + } + + guard let account = AccountDataHandler.shared.getAccountData(accountId: accountId) else { + return [self.placeholder()] + } + + guard let accessToken = account.accessToken else { + return [self.placeholder()] + } + + let client = PixelfedClient(baseURL: account.serverUrl).getAuthenticated(token: accessToken) + let statuses = try await client.getHomeTimeline(limit: 10) + var widgetEntries: [WidgetEntry] = [] + + for status in statuses { + // When we have images for next hour we can skip. + if widgetEntries.count == length { + break + } + + // We have to skip sensitive (we cannot show them on iPhone home screen). + if status.sensitive { + continue + } + + guard let imageAttachment = status.mediaAttachments.first(where: { $0.type == .image }) else { + continue + } + + let uiImage = await self.getImage(url: imageAttachment.url) + let uiAvatar = await self.getImage(url: status.account.avatar) + + guard let uiImage, let uiAvatar else { + continue + } + + let displayDate = Calendar.current.date(byAdding: .minute, value: widgetEntries.count * 10, to: Date()) + + widgetEntries.append(WidgetEntry(date: displayDate ?? Date(), + image: uiImage, + avatar: uiAvatar, + displayName: status.account.displayNameWithoutEmojis, + statusId: status.id)) + } + + if widgetEntries.isEmpty { + widgetEntries.append(self.placeholder()) + } + + return widgetEntries + } + + func placeholder() -> WidgetEntry { + WidgetEntry(date: Date(), image: nil, avatar: nil, displayName: "John Misiakiewiczowicz", statusId: "123321") + } + + private func getImage(url: URL?) async -> UIImage? { + guard let url else { + return nil + } + + do { + let (data, response) = try await URLSession.shared.data(from: url) + + if (response as? HTTPURLResponse)?.status?.responseType == .success { + return UIImage(data: data)?.resized(toWidth: 1200) + } + + return nil + } catch { + return nil + } + } +} diff --git a/VernissageWidget/Info.plist b/VernissageWidget/Info.plist new file mode 100644 index 0000000..0f118fb --- /dev/null +++ b/VernissageWidget/Info.plist @@ -0,0 +1,11 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.widgetkit-extension + + + diff --git a/VernissageWidget/Provider.swift b/VernissageWidget/Provider.swift new file mode 100644 index 0000000..00b7629 --- /dev/null +++ b/VernissageWidget/Provider.swift @@ -0,0 +1,47 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import WidgetKit +import SwiftUI +import Intents + +struct Provider: TimelineProvider { + typealias Entry = WidgetEntry + + func placeholder(in context: Context) -> WidgetEntry { + ImageFetcher.shared.placeholder() + } + + func getSnapshot(in context: Context, completion: @escaping (WidgetEntry) -> ()) { + Task { + if let widgetEntry = await self.getWidgetEntries(length: 1).first { + completion(widgetEntry) + } else { + let entry = ImageFetcher.shared.placeholder() + completion(entry) + } + } + } + + func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) { + Task { + let currentDate = Date() + let widgetEntries = await self.getWidgetEntries() + + let nextUpdateDate = Calendar.current.date(byAdding: .hour, value: 1, to: currentDate)! + let timeline = Timeline(entries: widgetEntries, policy: .after(nextUpdateDate)) + completion(timeline) + } + } + + func getWidgetEntries(length: Int = 6) async -> [WidgetEntry] { + do { + return try await ImageFetcher.shared.fetchWidgetEntries(length: length) + } catch { + return [ImageFetcher.shared.placeholder()] + } + } +} diff --git a/VernissageWidget/VernissageWidget.swift b/VernissageWidget/VernissageWidget.swift new file mode 100644 index 0000000..283fce6 --- /dev/null +++ b/VernissageWidget/VernissageWidget.swift @@ -0,0 +1,21 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import WidgetKit +import SwiftUI + +struct VernissageWidget: Widget { + let kind: String = "VernissageWidget" + + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: Provider()) { entry in + VernissageWidgetEntryView(entry: entry) + } + .configurationDisplayName("Vernissage") + .description("Widget with photos from Pixelfed.") + .supportedFamilies([.systemSmall, .systemMedium, .systemLarge]) + } +} diff --git a/VernissageWidget/VernissageWidgetBundle.swift b/VernissageWidget/VernissageWidgetBundle.swift new file mode 100644 index 0000000..b470c92 --- /dev/null +++ b/VernissageWidget/VernissageWidgetBundle.swift @@ -0,0 +1,16 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + + +import WidgetKit +import SwiftUI + +@main +struct VernissageWidgetBundle: WidgetBundle { + var body: some Widget { + VernissageWidget() + } +} diff --git a/VernissageWidget/VernissageWidgetEntryView.swift b/VernissageWidget/VernissageWidgetEntryView.swift new file mode 100644 index 0000000..74dfb6f --- /dev/null +++ b/VernissageWidget/VernissageWidgetEntryView.swift @@ -0,0 +1,23 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import WidgetKit +import SwiftUI + +struct VernissageWidgetEntryView : View { + @Environment(\.widgetFamily) var family: WidgetFamily + + var entry: Provider.Entry + + var body: some View { + switch family { + case .systemSmall: SmallWidgetView(entry: entry) + case .systemMedium: MediumWidgetView(entry: entry) + case .systemLarge: LargeWidgetView(entry: entry) + default: Text("Not supported") + } + } +} diff --git a/VernissageWidget/Views/LargeWidgetView.swift b/VernissageWidget/Views/LargeWidgetView.swift new file mode 100644 index 0000000..ef834fd --- /dev/null +++ b/VernissageWidget/Views/LargeWidgetView.swift @@ -0,0 +1,53 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import SwiftUI +import WidgetKit + +struct LargeWidgetView: View { + var entry: Provider.Entry + + var body: some View { + if let uiImage = entry.image, let uiAvatar = entry.avatar { + VStack { + Spacer() + HStack { + Image(uiImage: uiAvatar) + .resizable() + .clipShape(Circle()) + .aspectRatio(contentMode: .fit) + .frame(width: 44, height: 44) + + Text(entry.displayName ?? "") + Spacer() + } + .padding(.leading, 8) + .padding(.bottom, 8) + } + .background { + Image(uiImage: uiImage) + .resizable() + .aspectRatio(contentMode: .fill) + .widgetURL(URL(string: "\(AppConstants.statusUri)/\(entry.statusId ?? "")")) + } + } else { + VStack { + Spacer() + HStack { + Circle() + .foregroundColor(Color(UIColor.placeholderText)) + .frame(width: 44, height: 44) + + Text(entry.displayName ?? "") + Spacer() + } + } + .padding(.leading, 8) + .padding(.bottom, 8) + } + } +} + diff --git a/VernissageWidget/Views/MediumWidgetView.swift b/VernissageWidget/Views/MediumWidgetView.swift new file mode 100644 index 0000000..592a291 --- /dev/null +++ b/VernissageWidget/Views/MediumWidgetView.swift @@ -0,0 +1,53 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import SwiftUI +import WidgetKit + +struct MediumWidgetView: View { + var entry: Provider.Entry + + var body: some View { + if let uiImage = entry.image, let uiAvatar = entry.avatar { + VStack { + Spacer() + HStack { + Image(uiImage: uiAvatar) + .resizable() + .clipShape(Circle()) + .aspectRatio(contentMode: .fit) + .frame(width: 32, height: 32) + + Text(entry.displayName ?? "") + Spacer() + } + .padding(.leading, 8) + .padding(.bottom, 8) + } + .background { + Image(uiImage: uiImage) + .resizable() + .aspectRatio(contentMode: .fill) + .widgetURL(URL(string: "\(AppConstants.statusUri)/\(entry.statusId ?? "")")) + } + } else { + VStack { + Spacer() + HStack { + Circle() + .foregroundColor(Color(UIColor.placeholderText)) + .frame(width: 32, height: 32) + + Text(entry.displayName ?? "") + Spacer() + } + } + .padding(.leading, 8) + .padding(.bottom, 8) + } + } +} + diff --git a/VernissageWidget/Views/SmallWidgetView.swift b/VernissageWidget/Views/SmallWidgetView.swift new file mode 100644 index 0000000..9ad0743 --- /dev/null +++ b/VernissageWidget/Views/SmallWidgetView.swift @@ -0,0 +1,38 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import SwiftUI +import WidgetKit + +struct SmallWidgetView: View { + var entry: Provider.Entry + + var body: some View { + if let uiImage = entry.image, let uiAvatar = entry.avatar { + VStack { + Spacer() + HStack { + Image(uiImage: uiAvatar) + .resizable() + .clipShape(Circle()) + .aspectRatio(contentMode: .fit) + .frame(width: 22, height: 22) + Spacer() + } + .padding(.leading, 8) + .padding(.bottom, 8) + } + .background { + Image(uiImage: uiImage) + .resizable() + .aspectRatio(contentMode: .fill) + .widgetURL(URL(string: "\(AppConstants.statusUri)/\(entry.statusId ?? "")")) + } + } else { + EmptyView() + } + } +} diff --git a/VernissageWidget/WidgetEntry.swift b/VernissageWidget/WidgetEntry.swift new file mode 100644 index 0000000..6c79c40 --- /dev/null +++ b/VernissageWidget/WidgetEntry.swift @@ -0,0 +1,16 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import WidgetKit +import SwiftUI + +struct WidgetEntry: TimelineEntry { + let date: Date + let image: UIImage? + let avatar: UIImage? + let displayName: String? + let statusId: String? +} diff --git a/VernissageWidgetExtension.entitlements b/VernissageWidgetExtension.entitlements new file mode 100644 index 0000000..bff12cc --- /dev/null +++ b/VernissageWidgetExtension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.dev.mczachurski.vernissage + + +