diff --git a/EnvironmentKit/Sources/EnvironmentKit/ApplicationState.swift b/EnvironmentKit/Sources/EnvironmentKit/ApplicationState.swift index 92a4804..2f5bc16 100644 --- a/EnvironmentKit/Sources/EnvironmentKit/ApplicationState.swift +++ b/EnvironmentKit/Sources/EnvironmentKit/ApplicationState.swift @@ -83,6 +83,9 @@ public class ApplicationState: ObservableObject { /// Status which should be shown from URL. @Published public var showStatusId: String? + /// Account which should be shown from URL. + @Published public var showAccountId: String? + /// Updated user profile. @Published public var updatedProfile: Account? diff --git a/EnvironmentKit/Sources/EnvironmentKit/Models/AppConstants.swift b/EnvironmentKit/Sources/EnvironmentKit/Models/AppConstants.swift index 5e9145a..1801189 100644 --- a/EnvironmentKit/Sources/EnvironmentKit/Models/AppConstants.swift +++ b/EnvironmentKit/Sources/EnvironmentKit/Models/AppConstants.swift @@ -17,6 +17,10 @@ public struct AppConstants { public static let statusCallbackPart = "statuses" public static let statusUri = "\(AppConstants.statusScheme)://\(statusCallbackPart)" + public static let accountScheme = "account-vernissage" + public static let accountCallbackPart = "accounts" + public static let accountUri = "\(AppConstants.accountScheme)://\(accountCallbackPart)" + public static let imagePipelineCacheName = "dev.mczachurski.Vernissage.DataCache" public static let coreDataPersistantContainerName = "Vernissage" } diff --git a/Localization/en.lproj/Localizable.strings b/Localization/en.lproj/Localizable.strings index f104ea4..f350ef9 100644 --- a/Localization/en.lproj/Localizable.strings +++ b/Localization/en.lproj/Localizable.strings @@ -299,7 +299,8 @@ "thirdParty.navigationBar.title" = "Third party"; // Mark: Widget view. -"widget.title.description" = "Widget with photos from Pixelfed."; +"widget.title.photoDescription" = "Widget with photos from Pixelfed."; +"widget.title.qrCodeDescription" = "Widget with QR Code to your Pixelfed profile."; // Mark: In-app purchases. "purchase.donut.title" = "Donut"; diff --git a/Localization/eu.lproj/Localizable.strings b/Localization/eu.lproj/Localizable.strings index e0f3e7a..32a82f2 100644 --- a/Localization/eu.lproj/Localizable.strings +++ b/Localization/eu.lproj/Localizable.strings @@ -299,7 +299,8 @@ "thirdParty.navigationBar.title" = "Hirugarrenak"; // Mark: Widget view. -"widget.title.description" = "Widgeta Pixelfed-eko argazkiekin."; +"widget.title.photoDescription" = "Widgeta Pixelfed-eko argazkiekin."; +"widget.title.qrCodeDescription" = "Widget with QR Code to your Pixelfed profile."; // Mark: In-app purchases. "purchase.donut.title" = "Opila"; diff --git a/Localization/fr.lproj/Localizable.strings b/Localization/fr.lproj/Localizable.strings index 7f605df..525ef99 100644 --- a/Localization/fr.lproj/Localizable.strings +++ b/Localization/fr.lproj/Localizable.strings @@ -299,7 +299,8 @@ "thirdParty.navigationBar.title" = "Tiers"; // Mark: Widget view. -"widget.title.description" = "Widget avec des photos de Pixelfed."; +"widget.title.photoDescription" = "Widget avec des photos de Pixelfed."; +"widget.title.qrCodeDescription" = "Widget with QR Code to your Pixelfed profile."; // Mark: In-app purchases. "purchase.donut.title" = "Beignet"; diff --git a/Localization/pl.lproj/Localizable.strings b/Localization/pl.lproj/Localizable.strings index 9e59ec3..923bea6 100644 --- a/Localization/pl.lproj/Localizable.strings +++ b/Localization/pl.lproj/Localizable.strings @@ -299,7 +299,8 @@ "thirdParty.navigationBar.title" = "Zewnętrzne biblioteki"; // Mark: Widget view. -"widget.title.description" = "Widget ze zdjęciami z Pixelfed."; +"widget.title.photoDescription" = "Widget ze zdjęciami z Pixelfed."; +"widget.title.qrCodeDescription" = "Widget z QR kodem do profilu na Pixelfed."; // Mark: In-app purchases. "purchase.donut.title" = "Pączek"; diff --git a/Vernissage.xcodeproj/project.pbxproj b/Vernissage.xcodeproj/project.pbxproj index 97a3ce9..69a3e52 100644 --- a/Vernissage.xcodeproj/project.pbxproj +++ b/Vernissage.xcodeproj/project.pbxproj @@ -30,6 +30,13 @@ F835082329BEF9C400DE3247 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F835082629BEF9C400DE3247 /* Localizable.strings */; }; F835082429BEF9C400DE3247 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F835082629BEF9C400DE3247 /* Localizable.strings */; }; F83CBEFB298298A1002972C8 /* ImageCarouselPicture.swift in Sources */ = {isa = PBXBuildFile; fileRef = F83CBEFA298298A1002972C8 /* ImageCarouselPicture.swift */; }; + F84625E929FE2788002D3AF4 /* QRCodeWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = F84625E829FE2788002D3AF4 /* QRCodeWidget.swift */; }; + F84625EB29FE28D4002D3AF4 /* QRCodeWidgetEntryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F84625EA29FE28D4002D3AF4 /* QRCodeWidgetEntryView.swift */; }; + F84625ED29FE295B002D3AF4 /* QRCodeLargeWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F84625EC29FE295B002D3AF4 /* QRCodeLargeWidgetView.swift */; }; + F84625F229FE2B6B002D3AF4 /* QRCodeWidgetEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = F84625F129FE2B6B002D3AF4 /* QRCodeWidgetEntry.swift */; }; + F84625F429FE2BF9002D3AF4 /* QRCodeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F84625F329FE2BF9002D3AF4 /* QRCodeProvider.swift */; }; + F84625F829FE2C2F002D3AF4 /* AccountFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = F84625F729FE2C2F002D3AF4 /* AccountFetcher.swift */; }; + F84625FB29FE393B002D3AF4 /* QRCode in Frameworks */ = {isa = PBXBuildFile; productRef = F84625FA29FE393B002D3AF4 /* QRCode */; }; F858906B29E1CC7A00D4BDED /* UIApplication+Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = F858906A29E1CC7A00D4BDED /* UIApplication+Window.swift */; }; F85D4971296402DC00751DF7 /* AuthorizationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85D4970296402DC00751DF7 /* AuthorizationService.swift */; }; F85D4975296407F100751DF7 /* HomeTimelineService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85D4974296407F100751DF7 /* HomeTimelineService.swift */; }; @@ -46,14 +53,14 @@ 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 */; }; + F864F76629BB91B400B13921 /* PhotoWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = F864F76529BB91B400B13921 /* PhotoWidget.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 */; }; + F864F77529BB92CE00B13921 /* PhotoProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F864F77329BB929A00B13921 /* PhotoProvider.swift */; }; + F864F77629BB92CE00B13921 /* PhotoWidgetEntryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F864F77129BB924D00B13921 /* PhotoWidgetEntryView.swift */; }; + F864F77829BB930000B13921 /* PhotoWidgetEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = F864F77729BB930000B13921 /* PhotoWidgetEntry.swift */; }; F864F77A29BB94A800B13921 /* PixelfedKit in Frameworks */ = {isa = PBXBuildFile; productRef = F864F77929BB94A800B13921 /* PixelfedKit */; }; - F864F77C29BB982100B13921 /* ImageFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = F864F77B29BB982100B13921 /* ImageFetcher.swift */; }; + F864F77C29BB982100B13921 /* StatusFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = F864F77B29BB982100B13921 /* StatusFetcher.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 */; }; @@ -87,6 +94,10 @@ F86B7221296C49A300EE59EC /* EmptyButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86B7220296C49A300EE59EC /* EmptyButtonStyle.swift */; }; F86BC9E929EBBB67009415EC /* ImageSaver.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86BC9E829EBBB66009415EC /* ImageSaver.swift */; }; F86BC9EB29EBDA2E009415EC /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86BC9EA29EBDA2E009415EC /* ActivityView.swift */; }; + F8705A7729FF7ABD00DA818A /* QRCodeSmallWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8705A7629FF7ABD00DA818A /* QRCodeSmallWidgetView.swift */; }; + F8705A7929FF7CCB00DA818A /* QRCodeMediumWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8705A7829FF7CCB00DA818A /* QRCodeMediumWidgetView.swift */; }; + F8705A7B29FF872F00DA818A /* QRCodeGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8705A7A29FF872F00DA818A /* QRCodeGenerator.swift */; }; + F8705A7E29FF880600DA818A /* FileFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8705A7D29FF880600DA818A /* FileFetcher.swift */; }; F870EE5229F1645C00A2D43B /* MainNavigationOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F870EE5129F1645C00A2D43B /* MainNavigationOptions.swift */; }; F871F21D29EF0D7000A351EF /* NavigationMenuItemDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = F871F21C29EF0D7000A351EF /* NavigationMenuItemDetails.swift */; }; F8742FC429990AFB00E9642B /* ClientError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8742FC329990AFB00E9642B /* ClientError.swift */; }; @@ -178,9 +189,9 @@ F8DF38E629DDB98A0047F1AA /* SocialsSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8DF38E529DDB98A0047F1AA /* SocialsSectionView.swift */; }; F8E6D03329CDD52500416CCA /* EditProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E6D03229CDD52500416CCA /* EditProfileView.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 */; }; + F8F6E44C29BCC1F70004795E /* PhotoSmallWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F6E44629BCC0DC0004795E /* PhotoSmallWidgetView.swift */; }; + F8F6E44D29BCC1F90004795E /* PhotoMediumWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F6E44829BCC0F00004795E /* PhotoMediumWidgetView.swift */; }; + F8F6E44E29BCC1FB0004795E /* PhotoLargeWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F6E44A29BCC0FF0004795E /* PhotoLargeWidgetView.swift */; }; F8F6E45129BCE9190004795E /* UIImage+Resize.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F6E45029BCE9190004795E /* UIImage+Resize.swift */; }; F8FB8ABA29EB2ED400342C04 /* NavigationMenuButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8FB8AB929EB2ED400342C04 /* NavigationMenuButtons.swift */; }; /* End PBXBuildFile section */ @@ -240,6 +251,12 @@ F837269429A221420098D3C4 /* PixelfedKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = PixelfedKit; sourceTree = ""; }; F83CBEFA298298A1002972C8 /* ImageCarouselPicture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCarouselPicture.swift; sourceTree = ""; }; F844F42429D2DC39000DD896 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + F84625E829FE2788002D3AF4 /* QRCodeWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeWidget.swift; sourceTree = ""; }; + F84625EA29FE28D4002D3AF4 /* QRCodeWidgetEntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeWidgetEntryView.swift; sourceTree = ""; }; + F84625EC29FE295B002D3AF4 /* QRCodeLargeWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeLargeWidgetView.swift; sourceTree = ""; }; + F84625F129FE2B6B002D3AF4 /* QRCodeWidgetEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeWidgetEntry.swift; sourceTree = ""; }; + F84625F329FE2BF9002D3AF4 /* QRCodeProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeProvider.swift; sourceTree = ""; }; + F84625F729FE2C2F002D3AF4 /* AccountFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFetcher.swift; sourceTree = ""; }; F858906A29E1CC7A00D4BDED /* UIApplication+Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIApplication+Window.swift"; sourceTree = ""; }; F85B586C29ED169B00A16D12 /* Vernissage-010.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-010.xcdatamodel"; sourceTree = ""; }; F85D4970296402DC00751DF7 /* AuthorizationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationService.swift; sourceTree = ""; }; @@ -258,13 +275,13 @@ 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 = ""; }; + F864F76529BB91B400B13921 /* PhotoWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoWidget.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 = ""; }; + F864F77129BB924D00B13921 /* PhotoWidgetEntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoWidgetEntryView.swift; sourceTree = ""; }; + F864F77329BB929A00B13921 /* PhotoProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoProvider.swift; sourceTree = ""; }; + F864F77729BB930000B13921 /* PhotoWidgetEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoWidgetEntry.swift; sourceTree = ""; }; + F864F77B29BB982100B13921 /* StatusFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusFetcher.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 = ""; }; @@ -285,6 +302,10 @@ F86B7220296C49A300EE59EC /* EmptyButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyButtonStyle.swift; sourceTree = ""; }; F86BC9E829EBBB66009415EC /* ImageSaver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageSaver.swift; sourceTree = ""; }; F86BC9EA29EBDA2E009415EC /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = ""; }; + F8705A7629FF7ABD00DA818A /* QRCodeSmallWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeSmallWidgetView.swift; sourceTree = ""; }; + F8705A7829FF7CCB00DA818A /* QRCodeMediumWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeMediumWidgetView.swift; sourceTree = ""; }; + F8705A7A29FF872F00DA818A /* QRCodeGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeGenerator.swift; sourceTree = ""; }; + F8705A7D29FF880600DA818A /* FileFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileFetcher.swift; sourceTree = ""; }; F870EE5129F1645C00A2D43B /* MainNavigationOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainNavigationOptions.swift; sourceTree = ""; }; F871F21C29EF0D7000A351EF /* NavigationMenuItemDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationMenuItemDetails.swift; sourceTree = ""; }; F871F21F29EF0FEC00A351EF /* Vernissage-011.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-011.xcdatamodel"; sourceTree = ""; }; @@ -370,9 +391,9 @@ F8EF3C8B29FC3A5F00CBFF7C /* Vernissage-012.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-012.xcdatamodel"; 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 = ""; }; + F8F6E44629BCC0DC0004795E /* PhotoSmallWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoSmallWidgetView.swift; sourceTree = ""; }; + F8F6E44829BCC0F00004795E /* PhotoMediumWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoMediumWidgetView.swift; sourceTree = ""; }; + F8F6E44A29BCC0FF0004795E /* PhotoLargeWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoLargeWidgetView.swift; sourceTree = ""; }; F8F6E45029BCE9190004795E /* UIImage+Resize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Resize.swift"; sourceTree = ""; }; F8FB8AB929EB2ED400342C04 /* NavigationMenuButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationMenuButtons.swift; sourceTree = ""; }; F8FFBD4929E99BEE0047EE80 /* Vernissage-009.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-009.xcdatamodel"; sourceTree = ""; }; @@ -383,6 +404,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + F84625FB29FE393B002D3AF4 /* QRCode in Frameworks */, F864F76129BB91B400B13921 /* SwiftUI.framework in Frameworks */, F864F77A29BB94A800B13921 /* PixelfedKit in Frameworks */, F864F75F29BB91B400B13921 /* WidgetKit.framework in Frameworks */, @@ -557,6 +579,59 @@ path = Widgets; sourceTree = ""; }; + F84625EE29FE2ABA002D3AF4 /* PhotoWidget */ = { + isa = PBXGroup; + children = ( + F84625F629FE2C18002D3AF4 /* Service */, + F84625EF29FE2AC5002D3AF4 /* Views */, + F864F77729BB930000B13921 /* PhotoWidgetEntry.swift */, + F864F77329BB929A00B13921 /* PhotoProvider.swift */, + F864F77129BB924D00B13921 /* PhotoWidgetEntryView.swift */, + F864F76529BB91B400B13921 /* PhotoWidget.swift */, + ); + path = PhotoWidget; + sourceTree = ""; + }; + F84625EF29FE2AC5002D3AF4 /* Views */ = { + isa = PBXGroup; + children = ( + F8F6E44629BCC0DC0004795E /* PhotoSmallWidgetView.swift */, + F8F6E44829BCC0F00004795E /* PhotoMediumWidgetView.swift */, + F8F6E44A29BCC0FF0004795E /* PhotoLargeWidgetView.swift */, + ); + path = Views; + sourceTree = ""; + }; + F84625F029FE2B40002D3AF4 /* QRCodeWidget */ = { + isa = PBXGroup; + children = ( + F84625F529FE2C0D002D3AF4 /* Service */, + F8F6E44529BCC0C90004795E /* Views */, + F84625E829FE2788002D3AF4 /* QRCodeWidget.swift */, + F84625EA29FE28D4002D3AF4 /* QRCodeWidgetEntryView.swift */, + F84625F129FE2B6B002D3AF4 /* QRCodeWidgetEntry.swift */, + F84625F329FE2BF9002D3AF4 /* QRCodeProvider.swift */, + ); + path = QRCodeWidget; + sourceTree = ""; + }; + F84625F529FE2C0D002D3AF4 /* Service */ = { + isa = PBXGroup; + children = ( + F84625F729FE2C2F002D3AF4 /* AccountFetcher.swift */, + F8705A7A29FF872F00DA818A /* QRCodeGenerator.swift */, + ); + path = Service; + sourceTree = ""; + }; + F84625F629FE2C18002D3AF4 /* Service */ = { + isa = PBXGroup; + children = ( + F864F77B29BB982100B13921 /* StatusFetcher.swift */, + ); + path = Service; + sourceTree = ""; + }; F858906729E1CC2900D4BDED /* Extensions */ = { isa = PBXGroup; children = ( @@ -570,14 +645,11 @@ children = ( F8F6E44329BC5CAA0004795E /* VernissageWidgetExtension.entitlements */, F864F76929BB91B600B13921 /* Info.plist */, + F8705A7C29FF87EE00DA818A /* SharedServices */, + F84625F029FE2B40002D3AF4 /* QRCodeWidget */, + F84625EE29FE2ABA002D3AF4 /* PhotoWidget */, 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 */, ); path = VernissageWidget; @@ -591,6 +663,14 @@ path = Styles; sourceTree = ""; }; + F8705A7C29FF87EE00DA818A /* SharedServices */ = { + isa = PBXGroup; + children = ( + F8705A7D29FF880600DA818A /* FileFetcher.swift */, + ); + path = SharedServices; + sourceTree = ""; + }; F878841B29A49493003CFAD2 /* Subviews */ = { isa = PBXGroup; children = ( @@ -802,9 +882,9 @@ F8F6E44529BCC0C90004795E /* Views */ = { isa = PBXGroup; children = ( - F8F6E44629BCC0DC0004795E /* SmallWidgetView.swift */, - F8F6E44829BCC0F00004795E /* MediumWidgetView.swift */, - F8F6E44A29BCC0FF0004795E /* LargeWidgetView.swift */, + F84625EC29FE295B002D3AF4 /* QRCodeLargeWidgetView.swift */, + F8705A7629FF7ABD00DA818A /* QRCodeSmallWidgetView.swift */, + F8705A7829FF7CCB00DA818A /* QRCodeMediumWidgetView.swift */, ); path = Views; sourceTree = ""; @@ -837,6 +917,7 @@ packageProductDependencies = ( F864F77929BB94A800B13921 /* PixelfedKit */, F88BC52E29E04C5F00CE6141 /* EnvironmentKit */, + F84625FA29FE393B002D3AF4 /* QRCode */, ); productName = VernissageWidgetExtension; productReference = F864F75D29BB91B400B13921 /* VernissageWidgetExtension.appex */; @@ -934,6 +1015,7 @@ F8210DD32966BB7E001D9973 /* XCRemoteSwiftPackageReference "Nuke" */, F88E4D4B297EA4290057491A /* XCRemoteSwiftPackageReference "EmojiText" */, F89B5CBE29D019B600549F2F /* XCRemoteSwiftPackageReference "HTMLString" */, + F84625F929FE393B002D3AF4 /* XCRemoteSwiftPackageReference "QRCode" */, ); productRefGroup = F88C2469295C37B80006098B /* Products */; projectDirPath = ""; @@ -1005,16 +1087,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F864F77829BB930000B13921 /* WidgetEntry.swift in Sources */, - F864F77529BB92CE00B13921 /* Provider.swift in Sources */, - F864F77629BB92CE00B13921 /* VernissageWidgetEntryView.swift in Sources */, - F864F77C29BB982100B13921 /* ImageFetcher.swift in Sources */, + F864F77829BB930000B13921 /* PhotoWidgetEntry.swift in Sources */, + F864F77529BB92CE00B13921 /* PhotoProvider.swift in Sources */, + F864F77629BB92CE00B13921 /* PhotoWidgetEntryView.swift in Sources */, + F8705A7729FF7ABD00DA818A /* QRCodeSmallWidgetView.swift in Sources */, + F864F77C29BB982100B13921 /* StatusFetcher.swift in Sources */, + F84625ED29FE295B002D3AF4 /* QRCodeLargeWidgetView.swift in Sources */, F8F6E44229BC58F20004795E /* Vernissage.xcdatamodeld in Sources */, - F8F6E44C29BCC1F70004795E /* SmallWidgetView.swift in Sources */, - F864F76629BB91B400B13921 /* VernissageWidget.swift in Sources */, - F8F6E44D29BCC1F90004795E /* MediumWidgetView.swift in Sources */, + F8F6E44C29BCC1F70004795E /* PhotoSmallWidgetView.swift in Sources */, + F864F76629BB91B400B13921 /* PhotoWidget.swift in Sources */, + F8F6E44D29BCC1F90004795E /* PhotoMediumWidgetView.swift in Sources */, F815F60C29E49CF20044566B /* Avatar.swift in Sources */, - F8F6E44E29BCC1FB0004795E /* LargeWidgetView.swift in Sources */, + F8F6E44E29BCC1FB0004795E /* PhotoLargeWidgetView.swift in Sources */, F864F76429BB91B400B13921 /* VernissageWidgetBundle.swift in Sources */, F864F77D29BB9A4600B13921 /* AttachmentData+CoreDataClass.swift in Sources */, F864F7A629BBA01D00B13921 /* CoreDataError.swift in Sources */, @@ -1023,14 +1107,22 @@ F864F78329BB9A6800B13921 /* StatusData+CoreDataProperties.swift in Sources */, F864F78429BB9A6E00B13921 /* ApplicationSettings+CoreDataClass.swift in Sources */, F864F78629BB9A7400B13921 /* AccountData+CoreDataClass.swift in Sources */, + F8705A7B29FF872F00DA818A /* QRCodeGenerator.swift in Sources */, + F8705A7E29FF880600DA818A /* FileFetcher.swift in Sources */, F864F78529BB9A7100B13921 /* ApplicationSettings+CoreDataProperties.swift in Sources */, + F84625E929FE2788002D3AF4 /* QRCodeWidget.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 */, + F84625F229FE2B6B002D3AF4 /* QRCodeWidgetEntry.swift in Sources */, + F84625EB29FE28D4002D3AF4 /* QRCodeWidgetEntryView.swift in Sources */, F864F78C29BB9A8500B13921 /* AttachmentDataHandler.swift in Sources */, + F84625F829FE2C2F002D3AF4 /* AccountFetcher.swift in Sources */, + F84625F429FE2BF9002D3AF4 /* QRCodeProvider.swift in Sources */, F864F78B29BB9A8300B13921 /* StatusDataHandler.swift in Sources */, + F8705A7929FF7CCB00DA818A /* QRCodeMediumWidgetView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1561,6 +1653,14 @@ minimumVersion = 12.0.0; }; }; + F84625F929FE393B002D3AF4 /* XCRemoteSwiftPackageReference "QRCode" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/dmrschmidt/QRCode"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; F88E4D4B297EA4290057491A /* XCRemoteSwiftPackageReference "EmojiText" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/divadretlaw/EmojiText"; @@ -1595,6 +1695,11 @@ package = F8210DD32966BB7E001D9973 /* XCRemoteSwiftPackageReference "Nuke" */; productName = NukeUI; }; + F84625FA29FE393B002D3AF4 /* QRCode */ = { + isa = XCSwiftPackageProductDependency; + package = F84625F929FE393B002D3AF4 /* XCRemoteSwiftPackageReference "QRCode" */; + productName = QRCode; + }; F864F77929BB94A800B13921 /* PixelfedKit */ = { isa = XCSwiftPackageProductDependency; productName = PixelfedKit; diff --git a/Vernissage/SceneDelegate.swift b/Vernissage/SceneDelegate.swift index d933456..d1b7c55 100644 --- a/Vernissage/SceneDelegate.swift +++ b/Vernissage/SceneDelegate.swift @@ -22,6 +22,11 @@ class SceneDelegate: NSObject, UISceneDelegate { if statusId.isEmpty == false { ApplicationState.shared.showStatusId = statusId } + } else if url.host == AppConstants.accountCallbackPart { + let accountId = url.string.replacingOccurrences(of: "\(AppConstants.accountUri)/", with: "") + if accountId.isEmpty == false { + ApplicationState.shared.showAccountId = accountId + } } } } diff --git a/Vernissage/VernissageApp.swift b/Vernissage/VernissageApp.swift index eee05a4..d4e63c8 100644 --- a/Vernissage/VernissageApp.swift +++ b/Vernissage/VernissageApp.swift @@ -95,6 +95,12 @@ struct VernissageApp: App { self.applicationState.showStatusId = nil } } + .onChange(of: applicationState.showAccountId) { newValue in + if let accountId = newValue { + self.routerPath.navigate(to: .userProfile(accountId: accountId, accountDisplayName: nil, accountUserName: "")) + self.applicationState.showAccountId = nil + } + } } } diff --git a/Vernissage/Views/ThirdPartyView.swift b/Vernissage/Views/ThirdPartyView.swift index 197bcb1..390278f 100644 --- a/Vernissage/Views/ThirdPartyView.swift +++ b/Vernissage/Views/ThirdPartyView.swift @@ -90,6 +90,16 @@ struct ThirdPartyView: View { } .font(.footnote) } + + Section("QR codes") { + VStack(alignment: .leading) { + Link("https://github.com/dmrschmidt/QRCode", + destination: URL(string: "https://github.com/dmrschmidt/QRCode")!) + .padding(.bottom, 4) + Text("A simple QR code image generator to use in your apps, written in Swift 5.") + } + .font(.footnote) + } } .navigationTitle("thirdParty.navigationBar.title") .navigationBarTitleDisplayMode(.inline) diff --git a/VernissageWidget/Assets.xcassets/Pixelfed.imageset/Contents.json b/VernissageWidget/Assets.xcassets/Pixelfed.imageset/Contents.json new file mode 100644 index 0000000..5d40392 --- /dev/null +++ b/VernissageWidget/Assets.xcassets/Pixelfed.imageset/Contents.json @@ -0,0 +1,56 @@ +{ + "images" : [ + { + "filename" : "PixelfedBlack.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "PixelfedWhite.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "PixelfedBlack@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "PixelfedWhite@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "PixelfedBlack@3x.png", + "idiom" : "universal", + "scale" : "3x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "PixelfedWhite@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/VernissageWidget/Assets.xcassets/Pixelfed.imageset/PixelfedBlack.png b/VernissageWidget/Assets.xcassets/Pixelfed.imageset/PixelfedBlack.png new file mode 100644 index 0000000..7df72ad Binary files /dev/null and b/VernissageWidget/Assets.xcassets/Pixelfed.imageset/PixelfedBlack.png differ diff --git a/VernissageWidget/Assets.xcassets/Pixelfed.imageset/PixelfedBlack@2x.png b/VernissageWidget/Assets.xcassets/Pixelfed.imageset/PixelfedBlack@2x.png new file mode 100644 index 0000000..9bf1736 Binary files /dev/null and b/VernissageWidget/Assets.xcassets/Pixelfed.imageset/PixelfedBlack@2x.png differ diff --git a/VernissageWidget/Assets.xcassets/Pixelfed.imageset/PixelfedBlack@3x.png b/VernissageWidget/Assets.xcassets/Pixelfed.imageset/PixelfedBlack@3x.png new file mode 100644 index 0000000..e0886c5 Binary files /dev/null and b/VernissageWidget/Assets.xcassets/Pixelfed.imageset/PixelfedBlack@3x.png differ diff --git a/VernissageWidget/Assets.xcassets/Pixelfed.imageset/PixelfedWhite.png b/VernissageWidget/Assets.xcassets/Pixelfed.imageset/PixelfedWhite.png new file mode 100644 index 0000000..40efd4e Binary files /dev/null and b/VernissageWidget/Assets.xcassets/Pixelfed.imageset/PixelfedWhite.png differ diff --git a/VernissageWidget/Assets.xcassets/Pixelfed.imageset/PixelfedWhite@2x.png b/VernissageWidget/Assets.xcassets/Pixelfed.imageset/PixelfedWhite@2x.png new file mode 100644 index 0000000..94223b8 Binary files /dev/null and b/VernissageWidget/Assets.xcassets/Pixelfed.imageset/PixelfedWhite@2x.png differ diff --git a/VernissageWidget/Assets.xcassets/Pixelfed.imageset/PixelfedWhite@3x.png b/VernissageWidget/Assets.xcassets/Pixelfed.imageset/PixelfedWhite@3x.png new file mode 100644 index 0000000..2920d60 Binary files /dev/null and b/VernissageWidget/Assets.xcassets/Pixelfed.imageset/PixelfedWhite@3x.png differ diff --git a/VernissageWidget/Assets.xcassets/QRCode.imageset/Contents.json b/VernissageWidget/Assets.xcassets/QRCode.imageset/Contents.json new file mode 100644 index 0000000..7b8826d --- /dev/null +++ b/VernissageWidget/Assets.xcassets/QRCode.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "QRCode.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "QRCode@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "QRCode@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/VernissageWidget/Assets.xcassets/QRCode.imageset/QRCode.png b/VernissageWidget/Assets.xcassets/QRCode.imageset/QRCode.png new file mode 100644 index 0000000..e83955e Binary files /dev/null and b/VernissageWidget/Assets.xcassets/QRCode.imageset/QRCode.png differ diff --git a/VernissageWidget/Assets.xcassets/QRCode.imageset/QRCode@2x.png b/VernissageWidget/Assets.xcassets/QRCode.imageset/QRCode@2x.png new file mode 100644 index 0000000..11ead5f Binary files /dev/null and b/VernissageWidget/Assets.xcassets/QRCode.imageset/QRCode@2x.png differ diff --git a/VernissageWidget/Assets.xcassets/QRCode.imageset/QRCode@3x.png b/VernissageWidget/Assets.xcassets/QRCode.imageset/QRCode@3x.png new file mode 100644 index 0000000..1c417d1 Binary files /dev/null and b/VernissageWidget/Assets.xcassets/QRCode.imageset/QRCode@3x.png differ diff --git a/VernissageWidget/ImageFetcher.swift b/VernissageWidget/ImageFetcher.swift deleted file mode 100644 index 787d12c..0000000 --- a/VernissageWidget/ImageFetcher.swift +++ /dev/null @@ -1,106 +0,0 @@ -// -// https://mczachurski.dev -// Copyright © 2023 Marcin Czachurski and the repository contributors. -// Licensed under the Apache License 2.0. -// - -import Foundation -import SwiftUI -import PixelfedKit - -public class ImageFetcher { - public static let shared = ImageFetcher() - private init() { } - - private let maxImageSize = 1000.0 - - func fetchWidgetEntries(length: Int = 8) async throws -> [WidgetEntry] { - let defaultSettings = ApplicationSettingsHandler.shared.get() - 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: 20) - 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 * 20, 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.shuffled() - } - - func placeholder() -> WidgetEntry { - WidgetEntry(date: Date(), image: nil, avatar: nil, displayName: "Caroline Rick", statusId: "") - } - - private func getImage(url: URL?) async -> UIImage? { - guard let url else { - return nil - } - - do { - let (data, response) = try await URLSession.shared.data(from: url) - - guard (response as? HTTPURLResponse)?.status?.responseType == .success else { - return nil - } - - guard let uiImage = UIImage(data: data) else { - return nil - } - - if uiImage.size.width < self.maxImageSize && uiImage.size.height < self.maxImageSize { - return uiImage - } - - if uiImage.size.width > uiImage.size.height { - return uiImage.resized(toWidth: self.maxImageSize) - } else { - return uiImage.resized(toHeight: self.maxImageSize) - } - } catch { - return nil - } - } -} diff --git a/VernissageWidget/Provider.swift b/VernissageWidget/PhotoWidget/PhotoProvider.swift similarity index 66% rename from VernissageWidget/Provider.swift rename to VernissageWidget/PhotoWidget/PhotoProvider.swift index b90ed14..e284c30 100644 --- a/VernissageWidget/Provider.swift +++ b/VernissageWidget/PhotoWidget/PhotoProvider.swift @@ -8,19 +8,19 @@ import WidgetKit import SwiftUI import Intents -struct Provider: TimelineProvider { - typealias Entry = WidgetEntry +struct PhotoProvider: TimelineProvider { + typealias Entry = PhotoWidgetEntry - func placeholder(in context: Context) -> WidgetEntry { - ImageFetcher.shared.placeholder() + func placeholder(in context: Context) -> PhotoWidgetEntry { + StatusFetcher.shared.placeholder() } - func getSnapshot(in context: Context, completion: @escaping (WidgetEntry) -> Void) { + func getSnapshot(in context: Context, completion: @escaping (PhotoWidgetEntry) -> Void) { Task { if let widgetEntry = await self.getWidgetEntries(length: 1).first { completion(widgetEntry) } else { - let entry = ImageFetcher.shared.placeholder() + let entry = StatusFetcher.shared.placeholder() completion(entry) } } @@ -37,11 +37,11 @@ struct Provider: TimelineProvider { } } - func getWidgetEntries(length: Int = 3) async -> [WidgetEntry] { + func getWidgetEntries(length: Int = 3) async -> [PhotoWidgetEntry] { do { - return try await ImageFetcher.shared.fetchWidgetEntries(length: length) + return try await StatusFetcher.shared.fetchWidgetEntries(length: length) } catch { - return [ImageFetcher.shared.placeholder()] + return [StatusFetcher.shared.placeholder()] } } } diff --git a/VernissageWidget/VernissageWidget.swift b/VernissageWidget/PhotoWidget/PhotoWidget.swift similarity index 66% rename from VernissageWidget/VernissageWidget.swift rename to VernissageWidget/PhotoWidget/PhotoWidget.swift index 4dba6ef..310ddea 100644 --- a/VernissageWidget/VernissageWidget.swift +++ b/VernissageWidget/PhotoWidget/PhotoWidget.swift @@ -7,15 +7,15 @@ import WidgetKit import SwiftUI -struct VernissageWidget: Widget { +struct PhotoWidget: Widget { let kind: String = "VernissageWidget" var body: some WidgetConfiguration { - StaticConfiguration(kind: kind, provider: Provider()) { entry in - VernissageWidgetEntryView(entry: entry) + StaticConfiguration(kind: kind, provider: PhotoProvider()) { entry in + PhotoWidgetEntryView(entry: entry) } .configurationDisplayName("Vernissage") - .description("widget.title.description") + .description("widget.title.photoDescription") .supportedFamilies([.systemSmall, .systemMedium, .systemLarge]) } } diff --git a/VernissageWidget/WidgetEntry.swift b/VernissageWidget/PhotoWidget/PhotoWidgetEntry.swift similarity index 88% rename from VernissageWidget/WidgetEntry.swift rename to VernissageWidget/PhotoWidget/PhotoWidgetEntry.swift index fdb42dc..eb5fdb4 100644 --- a/VernissageWidget/WidgetEntry.swift +++ b/VernissageWidget/PhotoWidget/PhotoWidgetEntry.swift @@ -7,7 +7,7 @@ import WidgetKit import SwiftUI -struct WidgetEntry: TimelineEntry { +struct PhotoWidgetEntry: TimelineEntry { let date: Date let image: UIImage? let avatar: UIImage? diff --git a/VernissageWidget/VernissageWidgetEntryView.swift b/VernissageWidget/PhotoWidget/PhotoWidgetEntryView.swift similarity index 57% rename from VernissageWidget/VernissageWidgetEntryView.swift rename to VernissageWidget/PhotoWidget/PhotoWidgetEntryView.swift index a505082..512986e 100644 --- a/VernissageWidget/VernissageWidgetEntryView.swift +++ b/VernissageWidget/PhotoWidget/PhotoWidgetEntryView.swift @@ -7,16 +7,16 @@ import WidgetKit import SwiftUI -struct VernissageWidgetEntryView: View { +struct PhotoWidgetEntryView: View { @Environment(\.widgetFamily) var family: WidgetFamily - var entry: Provider.Entry + var entry: PhotoProvider.Entry var body: some View { switch family { - case .systemSmall: SmallWidgetView(entry: entry) - case .systemMedium: MediumWidgetView(entry: entry) - case .systemLarge: LargeWidgetView(entry: entry) + case .systemSmall: PhotoSmallWidgetView(entry: entry) + case .systemMedium: PhotoMediumWidgetView(entry: entry) + case .systemLarge: PhotoLargeWidgetView(entry: entry) default: Text("Not supported") } } diff --git a/VernissageWidget/PhotoWidget/Service/StatusFetcher.swift b/VernissageWidget/PhotoWidget/Service/StatusFetcher.swift new file mode 100644 index 0000000..b42057c --- /dev/null +++ b/VernissageWidget/PhotoWidget/Service/StatusFetcher.swift @@ -0,0 +1,74 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the Apache License 2.0. +// + +import Foundation +import SwiftUI +import PixelfedKit + +public class StatusFetcher { + public static let shared = StatusFetcher() + private init() { } + + func fetchWidgetEntries(length: Int = 8) async throws -> [PhotoWidgetEntry] { + let defaultSettings = ApplicationSettingsHandler.shared.get() + 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: 20) + var widgetEntries: [PhotoWidgetEntry] = [] + + 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 FileFetcher.shared.getImage(url: imageAttachment.url) + let uiAvatar = await FileFetcher.shared.getImage(url: status.account.avatar) + + guard let uiImage, let uiAvatar else { + continue + } + + let displayDate = Calendar.current.date(byAdding: .minute, value: widgetEntries.count * 20, to: Date()) + + widgetEntries.append(PhotoWidgetEntry(date: displayDate ?? Date(), + image: uiImage, + avatar: uiAvatar, + displayName: status.account.displayNameWithoutEmojis, + statusId: status.id)) + } + + 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/Views/LargeWidgetView.swift b/VernissageWidget/PhotoWidget/Views/PhotoLargeWidgetView.swift similarity index 95% rename from VernissageWidget/Views/LargeWidgetView.swift rename to VernissageWidget/PhotoWidget/Views/PhotoLargeWidgetView.swift index a58c89d..c45a2f6 100644 --- a/VernissageWidget/Views/LargeWidgetView.swift +++ b/VernissageWidget/PhotoWidget/Views/PhotoLargeWidgetView.swift @@ -8,8 +8,8 @@ import SwiftUI import WidgetKit import EnvironmentKit -struct LargeWidgetView: View { - var entry: Provider.Entry +struct PhotoLargeWidgetView: View { + var entry: PhotoProvider.Entry var body: some View { if let uiImage = entry.image, let uiAvatar = entry.avatar { diff --git a/VernissageWidget/Views/MediumWidgetView.swift b/VernissageWidget/PhotoWidget/Views/PhotoMediumWidgetView.swift similarity index 94% rename from VernissageWidget/Views/MediumWidgetView.swift rename to VernissageWidget/PhotoWidget/Views/PhotoMediumWidgetView.swift index 1eefeb6..e498839 100644 --- a/VernissageWidget/Views/MediumWidgetView.swift +++ b/VernissageWidget/PhotoWidget/Views/PhotoMediumWidgetView.swift @@ -8,8 +8,8 @@ import SwiftUI import WidgetKit import EnvironmentKit -struct MediumWidgetView: View { - var entry: Provider.Entry +struct PhotoMediumWidgetView: View { + var entry: PhotoProvider.Entry var body: some View { if let uiImage = entry.image, let uiAvatar = entry.avatar { diff --git a/VernissageWidget/Views/SmallWidgetView.swift b/VernissageWidget/PhotoWidget/Views/PhotoSmallWidgetView.swift similarity index 94% rename from VernissageWidget/Views/SmallWidgetView.swift rename to VernissageWidget/PhotoWidget/Views/PhotoSmallWidgetView.swift index be1dd83..2cb195e 100644 --- a/VernissageWidget/Views/SmallWidgetView.swift +++ b/VernissageWidget/PhotoWidget/Views/PhotoSmallWidgetView.swift @@ -8,8 +8,8 @@ import SwiftUI import WidgetKit import EnvironmentKit -struct SmallWidgetView: View { - var entry: Provider.Entry +struct PhotoSmallWidgetView: View { + var entry: PhotoProvider.Entry var body: some View { if let uiImage = entry.image, let uiAvatar = entry.avatar { diff --git a/VernissageWidget/QRCodeWidget/QRCodeProvider.swift b/VernissageWidget/QRCodeWidget/QRCodeProvider.swift new file mode 100644 index 0000000..98dfdd2 --- /dev/null +++ b/VernissageWidget/QRCodeWidget/QRCodeProvider.swift @@ -0,0 +1,47 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the Apache License 2.0. +// + +import WidgetKit +import SwiftUI +import Intents + +struct QRCodeProvider: TimelineProvider { + typealias Entry = QRCodeWidgetEntry + + func placeholder(in context: Context) -> QRCodeWidgetEntry { + AccountFetcher.shared.placeholder() + } + + func getSnapshot(in context: Context, completion: @escaping (QRCodeWidgetEntry) -> Void) { + Task { + if let widgetEntry = await self.getWidgetEntry().first { + completion(widgetEntry) + } else { + let entry = AccountFetcher.shared.placeholder() + completion(entry) + } + } + } + + func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { + Task { + let currentDate = Date() + let widgetEntries = await self.getWidgetEntry() + + let nextUpdateDate = Calendar.current.date(byAdding: .day, value: 1, to: currentDate)! + let timeline = Timeline(entries: widgetEntries, policy: .after(nextUpdateDate)) + completion(timeline) + } + } + + func getWidgetEntry() async -> [QRCodeWidgetEntry] { + do { + return try await AccountFetcher.shared.fetchWidgetEntry() + } catch { + return [AccountFetcher.shared.placeholder()] + } + } +} diff --git a/VernissageWidget/QRCodeWidget/QRCodeWidget.swift b/VernissageWidget/QRCodeWidget/QRCodeWidget.swift new file mode 100644 index 0000000..ec6cb02 --- /dev/null +++ b/VernissageWidget/QRCodeWidget/QRCodeWidget.swift @@ -0,0 +1,21 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the Apache License 2.0. +// + +import WidgetKit +import SwiftUI + +struct QRCodeWidget: Widget { + let kind: String = "QRCodeWidget" + + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: QRCodeProvider()) { entry in + QRCodeWidgetEntryView(entry: entry) + } + .configurationDisplayName("Vernissage") + .description("widget.title.qrCodeDescription") + .supportedFamilies([.systemSmall, .systemMedium, .systemLarge]) + } +} diff --git a/VernissageWidget/QRCodeWidget/QRCodeWidgetEntry.swift b/VernissageWidget/QRCodeWidget/QRCodeWidgetEntry.swift new file mode 100644 index 0000000..3fed223 --- /dev/null +++ b/VernissageWidget/QRCodeWidget/QRCodeWidgetEntry.swift @@ -0,0 +1,18 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the Apache License 2.0. +// + +import WidgetKit +import SwiftUI + +struct QRCodeWidgetEntry: TimelineEntry { + let date: Date + let accountId: String + let avatar: UIImage? + let displayName: String? + let profileUrl: URL? + let avatarUrl: URL? + let portfolioUrl: URL? +} diff --git a/VernissageWidget/QRCodeWidget/QRCodeWidgetEntryView.swift b/VernissageWidget/QRCodeWidget/QRCodeWidgetEntryView.swift new file mode 100644 index 0000000..ffaf457 --- /dev/null +++ b/VernissageWidget/QRCodeWidget/QRCodeWidgetEntryView.swift @@ -0,0 +1,23 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the Apache License 2.0. +// + +import WidgetKit +import SwiftUI + +struct QRCodeWidgetEntryView: View { + @Environment(\.widgetFamily) var family: WidgetFamily + + var entry: QRCodeProvider.Entry + + var body: some View { + switch family { + case .systemSmall: QRCodeSmallWidgetView(entry: entry) + case .systemMedium: QRCodeMediumWidgetView(entry: entry) + case .systemLarge: QRCodeLargeWidgetView(entry: entry) + default: Text("Not supported") + } + } +} diff --git a/VernissageWidget/QRCodeWidget/Service/AccountFetcher.swift b/VernissageWidget/QRCodeWidget/Service/AccountFetcher.swift new file mode 100644 index 0000000..05e9849 --- /dev/null +++ b/VernissageWidget/QRCodeWidget/Service/AccountFetcher.swift @@ -0,0 +1,47 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the Apache License 2.0. +// + +import Foundation +import SwiftUI +import PixelfedKit + +public class AccountFetcher { + public static let shared = AccountFetcher() + private init() { } + + func fetchWidgetEntry() async throws -> [QRCodeWidgetEntry] { + let defaultSettings = ApplicationSettingsHandler.shared.get() + guard let accountId = defaultSettings.currentAccount else { + return [self.placeholder()] + } + + guard let account = AccountDataHandler.shared.getAccountData(accountId: accountId) else { + return [self.placeholder()] + } + + let uiAvatar = await FileFetcher.shared.getImage(url: account.avatar) + + return [ + QRCodeWidgetEntry(date: Date(), + accountId: accountId, + avatar: uiAvatar, + displayName: account.displayName, + profileUrl: account.url, + avatarUrl: account.avatar, + portfolioUrl: nil) + ] + } + + func placeholder() -> QRCodeWidgetEntry { + QRCodeWidgetEntry(date: Date(), + accountId: "", + avatar: nil, + displayName: "Caroline Rick", + profileUrl: URL(string: "https://pixelfed.org"), + avatarUrl: nil, + portfolioUrl: nil) + } +} diff --git a/VernissageWidget/QRCodeWidget/Service/QRCodeGenerator.swift b/VernissageWidget/QRCodeWidget/Service/QRCodeGenerator.swift new file mode 100644 index 0000000..bbfb27b --- /dev/null +++ b/VernissageWidget/QRCodeWidget/Service/QRCodeGenerator.swift @@ -0,0 +1,24 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the Apache License 2.0. +// + +import Foundation +import SwiftUI +import QRCode + +public class QRCodeGenerator { + public static let shared = QRCodeGenerator() + private init() { } + + func generateQRCode(from string: String, scheme: ColorScheme) -> UIImage? { + let qrCode = QRCode(string: string, + color: scheme == .light ? Color.black.toUIColor() : Color.white.toUIColor(), + backgroundColor: scheme == .light ? Color.white.toUIColor() : Color.black.toUIColor(), + size: CGSize(width: 150, height: 150), + scale: 4.0, + inputCorrection: .medium) + return try? qrCode?.image() + } +} diff --git a/VernissageWidget/QRCodeWidget/Views/QRCodeLargeWidgetView.swift b/VernissageWidget/QRCodeWidget/Views/QRCodeLargeWidgetView.swift new file mode 100644 index 0000000..5e0d890 --- /dev/null +++ b/VernissageWidget/QRCodeWidget/Views/QRCodeLargeWidgetView.swift @@ -0,0 +1,87 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the Apache License 2.0. +// + +import SwiftUI +import WidgetKit +import EnvironmentKit + +struct QRCodeLargeWidgetView: View { + @Environment(\.colorScheme) var colorScheme + + var entry: QRCodeProvider.Entry + + private let qrCodeLightImage: UIImage? + private let qrCodeDarkImage: UIImage? + + init(entry: QRCodeProvider.Entry) { + self.entry = entry + + if let profileUrl = entry.profileUrl { + self.qrCodeLightImage = QRCodeGenerator.shared.generateQRCode(from: profileUrl.absoluteString, scheme: .light) + self.qrCodeDarkImage = QRCodeGenerator.shared.generateQRCode(from: profileUrl.absoluteString, scheme: .dark) + } else { + self.qrCodeLightImage = QRCodeGenerator.shared.generateQRCode(from: "https://pixelfed.org", scheme: .light) + self.qrCodeDarkImage = QRCodeGenerator.shared.generateQRCode(from: "https://pixelfed.org", scheme: .dark) + } + } + + var body: some View { + if let uiAvatar = entry.avatar, let qrCodeImage { + self.getWidgetBody(uiAvatar: Image(uiImage: uiAvatar), uiQRCode: Image(uiImage: qrCodeImage)) + } else { + self.getWidgetBody(uiAvatar: Image("Avatar"), uiQRCode: Image("QRCode")) + .unredacted() + } + } + + var qrCodeImage: UIImage? { + colorScheme == .dark ? qrCodeDarkImage : qrCodeLightImage + } + + @ViewBuilder + private func getWidgetBody(uiAvatar: Image, uiQRCode: Image) -> some View { + VStack(spacing: 0) { + HStack(alignment: .center) { + uiAvatar + .avatar(size: 32) + + Text(entry.displayName ?? "") + .font(.system(size: 18)) + .foregroundColor(Color.primary) + .fontWeight(.semibold) + + Spacer() + } + .padding(.leading, 6) + .padding(.bottom, 4) + + uiQRCode + .resizable() + .widgetURL(URL(string: "\(AppConstants.accountUri)/\(entry.accountId)")) + + if let profileUrl = entry.profileUrl { + HStack { + Text(profileUrl.absoluteString) + .font(.system(size: 10)) + .foregroundColor(Color.primary.opacity(0.6)) + Spacer() + } + .offset(y: -2) + .padding(.leading, 8) + } + + HStack { + Spacer() + Image("Pixelfed") + .resizable() + .scaledToFit() + .frame(height: 32) + .offset(y: -8) + } + } + .padding([.leading, .trailing, .top], 24) + } +} diff --git a/VernissageWidget/QRCodeWidget/Views/QRCodeMediumWidgetView.swift b/VernissageWidget/QRCodeWidget/Views/QRCodeMediumWidgetView.swift new file mode 100644 index 0000000..c70ecfd --- /dev/null +++ b/VernissageWidget/QRCodeWidget/Views/QRCodeMediumWidgetView.swift @@ -0,0 +1,72 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the Apache License 2.0. +// + +import SwiftUI +import WidgetKit +import EnvironmentKit + +struct QRCodeMediumWidgetView: View { + @Environment(\.colorScheme) var colorScheme + + var entry: QRCodeProvider.Entry + + private let qrCodeLightImage: UIImage? + private let qrCodeDarkImage: UIImage? + + init(entry: QRCodeProvider.Entry) { + self.entry = entry + + if let profileUrl = entry.profileUrl { + self.qrCodeLightImage = QRCodeGenerator.shared.generateQRCode(from: profileUrl.absoluteString, scheme: .light) + self.qrCodeDarkImage = QRCodeGenerator.shared.generateQRCode(from: profileUrl.absoluteString, scheme: .dark) + } else { + self.qrCodeLightImage = QRCodeGenerator.shared.generateQRCode(from: "https://pixelfed.org", scheme: .light) + self.qrCodeDarkImage = QRCodeGenerator.shared.generateQRCode(from: "https://pixelfed.org", scheme: .dark) + } + } + + var body: some View { + if let uiAvatar = entry.avatar, let qrCodeImage { + self.getWidgetBody(uiAvatar: Image(uiImage: uiAvatar), uiQRCode: Image(uiImage: qrCodeImage)) + } else { + self.getWidgetBody(uiAvatar: Image("Avatar"), uiQRCode: Image("QRCode")) + .unredacted() + } + } + + var qrCodeImage: UIImage? { + colorScheme == .dark ? qrCodeDarkImage : qrCodeLightImage + } + + @ViewBuilder + private func getWidgetBody(uiAvatar: Image, uiQRCode: Image) -> some View { + VStack(spacing: 0) { + uiQRCode + .resizable() + .widgetURL(URL(string: "\(AppConstants.accountUri)/\(entry.accountId)")) + + HStack { + uiAvatar + .avatar(size: 24) + .padding(.leading, 8) + + Text(entry.displayName ?? "") + .font(.system(size: 16)) + .foregroundColor(Color.primary) + .fontWeight(.semibold) + + Spacer() + + Image("Pixelfed") + .resizable() + .scaledToFit() + .frame(height: 32) + } + .padding(.top, 4) + } + .padding(8) + } +} diff --git a/VernissageWidget/QRCodeWidget/Views/QRCodeSmallWidgetView.swift b/VernissageWidget/QRCodeWidget/Views/QRCodeSmallWidgetView.swift new file mode 100644 index 0000000..8616c5f --- /dev/null +++ b/VernissageWidget/QRCodeWidget/Views/QRCodeSmallWidgetView.swift @@ -0,0 +1,66 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the Apache License 2.0. +// + +import SwiftUI +import WidgetKit +import EnvironmentKit + +struct QRCodeSmallWidgetView: View { + @Environment(\.colorScheme) var colorScheme + + var entry: QRCodeProvider.Entry + + private let qrCodeLightImage: UIImage? + private let qrCodeDarkImage: UIImage? + + init(entry: QRCodeProvider.Entry) { + self.entry = entry + + if let profileUrl = entry.profileUrl { + self.qrCodeLightImage = QRCodeGenerator.shared.generateQRCode(from: profileUrl.absoluteString, scheme: .light) + self.qrCodeDarkImage = QRCodeGenerator.shared.generateQRCode(from: profileUrl.absoluteString, scheme: .dark) + } else { + self.qrCodeLightImage = QRCodeGenerator.shared.generateQRCode(from: "https://pixelfed.org", scheme: .light) + self.qrCodeDarkImage = QRCodeGenerator.shared.generateQRCode(from: "https://pixelfed.org", scheme: .dark) + } + } + + var body: some View { + if let uiAvatar = entry.avatar, let qrCodeImage { + self.getWidgetBody(uiAvatar: Image(uiImage: uiAvatar), uiQRCode: Image(uiImage: qrCodeImage)) + } else { + self.getWidgetBody(uiAvatar: Image("Avatar"), uiQRCode: Image("QRCode")) + .unredacted() + } + } + + var qrCodeImage: UIImage? { + colorScheme == .dark ? qrCodeDarkImage : qrCodeLightImage + } + + @ViewBuilder + private func getWidgetBody(uiAvatar: Image, uiQRCode: Image) -> some View { + VStack(spacing: 0) { + uiQRCode + .resizable() + .widgetURL(URL(string: "\(AppConstants.accountUri)/\(entry.accountId)")) + + HStack { + uiAvatar + .avatar(size: 16) + .padding(.leading, 6) + + Spacer() + + Image("Pixelfed") + .resizable() + .scaledToFit() + .frame(height: 24) + } + } + .padding(8) + } +} diff --git a/VernissageWidget/SharedServices/FileFetcher.swift b/VernissageWidget/SharedServices/FileFetcher.swift new file mode 100644 index 0000000..8fc5f54 --- /dev/null +++ b/VernissageWidget/SharedServices/FileFetcher.swift @@ -0,0 +1,45 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the Apache License 2.0. +// + +import Foundation +import UIKit + +public class FileFetcher { + public static let shared = FileFetcher() + private init() { } + + private let maxImageSize = 1000.0 + + public func getImage(url: URL?) async -> UIImage? { + guard let url else { + return nil + } + + do { + let (data, response) = try await URLSession.shared.data(from: url) + + guard (response as? HTTPURLResponse)?.status?.responseType == .success else { + return nil + } + + guard let uiImage = UIImage(data: data) else { + return nil + } + + if uiImage.size.width < self.maxImageSize && uiImage.size.height < self.maxImageSize { + return uiImage + } + + if uiImage.size.width > uiImage.size.height { + return uiImage.resized(toWidth: self.maxImageSize) + } else { + return uiImage.resized(toHeight: self.maxImageSize) + } + } catch { + return nil + } + } +} diff --git a/VernissageWidget/VernissageWidgetBundle.swift b/VernissageWidget/VernissageWidgetBundle.swift index 0e9be9c..e4db3ff 100644 --- a/VernissageWidget/VernissageWidgetBundle.swift +++ b/VernissageWidget/VernissageWidgetBundle.swift @@ -9,7 +9,10 @@ import SwiftUI @main struct VernissageWidgetBundle: WidgetBundle { + + @WidgetBundleBuilder var body: some Widget { - VernissageWidget() + PhotoWidget() + QRCodeWidget() } }