#62 QR code widgets

This commit is contained in:
Marcin Czachurski 2023-05-01 07:50:24 +02:00
parent e9e9bf2008
commit 9f26f77b45
41 changed files with 806 additions and 169 deletions

View File

@ -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?

View File

@ -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"
}

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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 = "<group>"; };
F83CBEFA298298A1002972C8 /* ImageCarouselPicture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCarouselPicture.swift; sourceTree = "<group>"; };
F844F42429D2DC39000DD896 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
F84625E829FE2788002D3AF4 /* QRCodeWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeWidget.swift; sourceTree = "<group>"; };
F84625EA29FE28D4002D3AF4 /* QRCodeWidgetEntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeWidgetEntryView.swift; sourceTree = "<group>"; };
F84625EC29FE295B002D3AF4 /* QRCodeLargeWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeLargeWidgetView.swift; sourceTree = "<group>"; };
F84625F129FE2B6B002D3AF4 /* QRCodeWidgetEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeWidgetEntry.swift; sourceTree = "<group>"; };
F84625F329FE2BF9002D3AF4 /* QRCodeProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeProvider.swift; sourceTree = "<group>"; };
F84625F729FE2C2F002D3AF4 /* AccountFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFetcher.swift; sourceTree = "<group>"; };
F858906A29E1CC7A00D4BDED /* UIApplication+Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIApplication+Window.swift"; sourceTree = "<group>"; };
F85B586C29ED169B00A16D12 /* Vernissage-010.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-010.xcdatamodel"; sourceTree = "<group>"; };
F85D4970296402DC00751DF7 /* AuthorizationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationService.swift; sourceTree = "<group>"; };
@ -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 = "<group>"; };
F864F76529BB91B400B13921 /* VernissageWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VernissageWidget.swift; sourceTree = "<group>"; };
F864F76529BB91B400B13921 /* PhotoWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoWidget.swift; sourceTree = "<group>"; };
F864F76729BB91B600B13921 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
F864F76929BB91B600B13921 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
F864F77129BB924D00B13921 /* VernissageWidgetEntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VernissageWidgetEntryView.swift; sourceTree = "<group>"; };
F864F77329BB929A00B13921 /* Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Provider.swift; sourceTree = "<group>"; };
F864F77729BB930000B13921 /* WidgetEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetEntry.swift; sourceTree = "<group>"; };
F864F77B29BB982100B13921 /* ImageFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageFetcher.swift; sourceTree = "<group>"; };
F864F77129BB924D00B13921 /* PhotoWidgetEntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoWidgetEntryView.swift; sourceTree = "<group>"; };
F864F77329BB929A00B13921 /* PhotoProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoProvider.swift; sourceTree = "<group>"; };
F864F77729BB930000B13921 /* PhotoWidgetEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoWidgetEntry.swift; sourceTree = "<group>"; };
F864F77B29BB982100B13921 /* StatusFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusFetcher.swift; sourceTree = "<group>"; };
F864F7A429BBA01D00B13921 /* CoreDataError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataError.swift; sourceTree = "<group>"; };
F866F69E296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ApplicationSettings+CoreDataClass.swift"; sourceTree = "<group>"; };
F866F69F296040A8002E8F88 /* ApplicationSettings+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ApplicationSettings+CoreDataProperties.swift"; sourceTree = "<group>"; };
@ -285,6 +302,10 @@
F86B7220296C49A300EE59EC /* EmptyButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyButtonStyle.swift; sourceTree = "<group>"; };
F86BC9E829EBBB66009415EC /* ImageSaver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageSaver.swift; sourceTree = "<group>"; };
F86BC9EA29EBDA2E009415EC /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = "<group>"; };
F8705A7629FF7ABD00DA818A /* QRCodeSmallWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeSmallWidgetView.swift; sourceTree = "<group>"; };
F8705A7829FF7CCB00DA818A /* QRCodeMediumWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeMediumWidgetView.swift; sourceTree = "<group>"; };
F8705A7A29FF872F00DA818A /* QRCodeGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeGenerator.swift; sourceTree = "<group>"; };
F8705A7D29FF880600DA818A /* FileFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileFetcher.swift; sourceTree = "<group>"; };
F870EE5129F1645C00A2D43B /* MainNavigationOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainNavigationOptions.swift; sourceTree = "<group>"; };
F871F21C29EF0D7000A351EF /* NavigationMenuItemDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationMenuItemDetails.swift; sourceTree = "<group>"; };
F871F21F29EF0FEC00A351EF /* Vernissage-011.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-011.xcdatamodel"; sourceTree = "<group>"; };
@ -370,9 +391,9 @@
F8EF3C8B29FC3A5F00CBFF7C /* Vernissage-012.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-012.xcdatamodel"; sourceTree = "<group>"; };
F8F6E44329BC5CAA0004795E /* VernissageWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = VernissageWidgetExtension.entitlements; sourceTree = "<group>"; };
F8F6E44429BC5CC50004795E /* Vernissage.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Vernissage.entitlements; sourceTree = "<group>"; };
F8F6E44629BCC0DC0004795E /* SmallWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmallWidgetView.swift; sourceTree = "<group>"; };
F8F6E44829BCC0F00004795E /* MediumWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediumWidgetView.swift; sourceTree = "<group>"; };
F8F6E44A29BCC0FF0004795E /* LargeWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeWidgetView.swift; sourceTree = "<group>"; };
F8F6E44629BCC0DC0004795E /* PhotoSmallWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoSmallWidgetView.swift; sourceTree = "<group>"; };
F8F6E44829BCC0F00004795E /* PhotoMediumWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoMediumWidgetView.swift; sourceTree = "<group>"; };
F8F6E44A29BCC0FF0004795E /* PhotoLargeWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoLargeWidgetView.swift; sourceTree = "<group>"; };
F8F6E45029BCE9190004795E /* UIImage+Resize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Resize.swift"; sourceTree = "<group>"; };
F8FB8AB929EB2ED400342C04 /* NavigationMenuButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationMenuButtons.swift; sourceTree = "<group>"; };
F8FFBD4929E99BEE0047EE80 /* Vernissage-009.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-009.xcdatamodel"; sourceTree = "<group>"; };
@ -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 = "<group>";
};
F84625EE29FE2ABA002D3AF4 /* PhotoWidget */ = {
isa = PBXGroup;
children = (
F84625F629FE2C18002D3AF4 /* Service */,
F84625EF29FE2AC5002D3AF4 /* Views */,
F864F77729BB930000B13921 /* PhotoWidgetEntry.swift */,
F864F77329BB929A00B13921 /* PhotoProvider.swift */,
F864F77129BB924D00B13921 /* PhotoWidgetEntryView.swift */,
F864F76529BB91B400B13921 /* PhotoWidget.swift */,
);
path = PhotoWidget;
sourceTree = "<group>";
};
F84625EF29FE2AC5002D3AF4 /* Views */ = {
isa = PBXGroup;
children = (
F8F6E44629BCC0DC0004795E /* PhotoSmallWidgetView.swift */,
F8F6E44829BCC0F00004795E /* PhotoMediumWidgetView.swift */,
F8F6E44A29BCC0FF0004795E /* PhotoLargeWidgetView.swift */,
);
path = Views;
sourceTree = "<group>";
};
F84625F029FE2B40002D3AF4 /* QRCodeWidget */ = {
isa = PBXGroup;
children = (
F84625F529FE2C0D002D3AF4 /* Service */,
F8F6E44529BCC0C90004795E /* Views */,
F84625E829FE2788002D3AF4 /* QRCodeWidget.swift */,
F84625EA29FE28D4002D3AF4 /* QRCodeWidgetEntryView.swift */,
F84625F129FE2B6B002D3AF4 /* QRCodeWidgetEntry.swift */,
F84625F329FE2BF9002D3AF4 /* QRCodeProvider.swift */,
);
path = QRCodeWidget;
sourceTree = "<group>";
};
F84625F529FE2C0D002D3AF4 /* Service */ = {
isa = PBXGroup;
children = (
F84625F729FE2C2F002D3AF4 /* AccountFetcher.swift */,
F8705A7A29FF872F00DA818A /* QRCodeGenerator.swift */,
);
path = Service;
sourceTree = "<group>";
};
F84625F629FE2C18002D3AF4 /* Service */ = {
isa = PBXGroup;
children = (
F864F77B29BB982100B13921 /* StatusFetcher.swift */,
);
path = Service;
sourceTree = "<group>";
};
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 = "<group>";
};
F8705A7C29FF87EE00DA818A /* SharedServices */ = {
isa = PBXGroup;
children = (
F8705A7D29FF880600DA818A /* FileFetcher.swift */,
);
path = SharedServices;
sourceTree = "<group>";
};
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 = "<group>";
@ -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;

View File

@ -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
}
}
}
}

View File

@ -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
}
}
}
}

View File

@ -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)

View File

@ -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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -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
}
}
}

View File

@ -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()]
}
}
}

View File

@ -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])
}
}

View File

@ -7,7 +7,7 @@
import WidgetKit
import SwiftUI
struct WidgetEntry: TimelineEntry {
struct PhotoWidgetEntry: TimelineEntry {
let date: Date
let image: UIImage?
let avatar: UIImage?

View File

@ -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")
}
}

View File

@ -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: "")
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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<Entry>) -> 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()]
}
}
}

View File

@ -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])
}
}

View File

@ -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?
}

View File

@ -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")
}
}
}

View File

@ -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)
}
}

View File

@ -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()
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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
}
}
}

View File

@ -9,7 +9,10 @@ import SwiftUI
@main
struct VernissageWidgetBundle: WidgetBundle {
@WidgetBundleBuilder
var body: some Widget {
VernissageWidget()
PhotoWidget()
QRCodeWidget()
}
}