mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-05-31 22:58:20 +00:00
Compare commits
81 commits
Author | SHA1 | Date | |
---|---|---|---|
f40aeb9cac | |||
1578896b3e | |||
ba3d8b1882 | |||
04af087c4b | |||
a9398c25af | |||
13d721912b | |||
e3d4e693d2 | |||
86c053344b | |||
a996aace80 | |||
18a1d17230 | |||
69cb9a20f9 | |||
bab2b4be9c | |||
bb005386df | |||
c77bb992b4 | |||
7caf00d07d | |||
6ed760a775 | |||
ecd149b3d2 | |||
9aaf0b2350 | |||
2d6cce6b01 | |||
48faddebea | |||
a8039df22d | |||
e21ec0bd1f | |||
9c42a3d7cc | |||
54a16b2c9a | |||
a6f3068728 | |||
f04258ec04 | |||
8468e51c17 | |||
e9a2d3e151 | |||
1f56fa1b9b | |||
ccad00a094 | |||
51fecb01f5 | |||
c29de44d8c | |||
1d79832544 | |||
a37316c56f | |||
189e10f2b4 | |||
24d5ecd119 | |||
ee6f003073 | |||
7328c00006 | |||
a6fd8d1137 | |||
ea31cda3c2 | |||
8ab7b5ac69 | |||
7aebe530dd | |||
a2afd4f58f | |||
88218cd6ec | |||
c4dee39efe | |||
73651cb7f1 | |||
dd1615f0e3 | |||
6bd14e0f8d | |||
1ca4a74ff0 | |||
c3edabb183 | |||
ba4cc899f8 | |||
5a93184c6d | |||
66754ecc7c | |||
e857439a02 | |||
ed620e86ca | |||
936bc96ff7 | |||
37b441a43d | |||
07af494dcb | |||
49a5c6a56a | |||
4e4d903c44 | |||
abcd4cc321 | |||
6a7df1065d | |||
c0b855ea55 | |||
4c3047b0b9 | |||
899b92e390 | |||
e71c55b488 | |||
361b5f1d84 | |||
ad61600328 | |||
5f1f71068c | |||
3782300b27 | |||
7d47834903 | |||
65a83fa636 | |||
8038e8e6af | |||
eb82a67671 | |||
bc5bb8272a | |||
d2ead5b6d1 | |||
d22370959c | |||
2c9b841f30 | |||
2e3cf4aace | |||
7de563a6eb | |||
3aae2e6623 |
|
@ -26,8 +26,6 @@
|
||||||
9F24EEB829360C330042359D /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9F24EEB729360C330042359D /* Preview Assets.xcassets */; };
|
9F24EEB829360C330042359D /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9F24EEB729360C330042359D /* Preview Assets.xcassets */; };
|
||||||
9F295540292B6C3400E0E81B /* Timeline in Frameworks */ = {isa = PBXBuildFile; productRef = 9F29553F292B6C3400E0E81B /* Timeline */; };
|
9F295540292B6C3400E0E81B /* Timeline in Frameworks */ = {isa = PBXBuildFile; productRef = 9F29553F292B6C3400E0E81B /* Timeline */; };
|
||||||
9F2A540729699698009B2D7C /* SupportAppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2A540629699698009B2D7C /* SupportAppView.swift */; };
|
9F2A540729699698009B2D7C /* SupportAppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2A540629699698009B2D7C /* SupportAppView.swift */; };
|
||||||
9F2A540A29699705009B2D7C /* ReceiptParser in Frameworks */ = {isa = PBXBuildFile; productRef = 9F2A540929699705009B2D7C /* ReceiptParser */; };
|
|
||||||
9F2A540C29699705009B2D7C /* RevenueCat in Frameworks */ = {isa = PBXBuildFile; productRef = 9F2A540B29699705009B2D7C /* RevenueCat */; };
|
|
||||||
9F2A540E2969A0B0009B2D7C /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F2A540D2969A0B0009B2D7C /* StoreKit.framework */; };
|
9F2A540E2969A0B0009B2D7C /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F2A540D2969A0B0009B2D7C /* StoreKit.framework */; };
|
||||||
9F2A5411296A1429009B2D7C /* PushNotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2A5410296A1429009B2D7C /* PushNotificationsView.swift */; };
|
9F2A5411296A1429009B2D7C /* PushNotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2A5410296A1429009B2D7C /* PushNotificationsView.swift */; };
|
||||||
9F2A5419296AB631009B2D7C /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2A5418296AB631009B2D7C /* NotificationService.swift */; };
|
9F2A5419296AB631009B2D7C /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2A5418296AB631009B2D7C /* NotificationService.swift */; };
|
||||||
|
@ -44,6 +42,11 @@
|
||||||
9F35DB4729506F6600B3281A /* NotificationTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F35DB4629506F6600B3281A /* NotificationTab.swift */; };
|
9F35DB4729506F6600B3281A /* NotificationTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F35DB4629506F6600B3281A /* NotificationTab.swift */; };
|
||||||
9F35DB4A29506FA100B3281A /* Notifications in Frameworks */ = {isa = PBXBuildFile; productRef = 9F35DB4929506FA100B3281A /* Notifications */; };
|
9F35DB4A29506FA100B3281A /* Notifications in Frameworks */ = {isa = PBXBuildFile; productRef = 9F35DB4929506FA100B3281A /* Notifications */; };
|
||||||
9F35DB4C2952005C00B3281A /* MessagesTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F35DB4B2952005C00B3281A /* MessagesTab.swift */; };
|
9F35DB4C2952005C00B3281A /* MessagesTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F35DB4B2952005C00B3281A /* MessagesTab.swift */; };
|
||||||
|
9F37BDDB2BE36E22007F28AD /* PostIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F37BDDA2BE36E22007F28AD /* PostIntent.swift */; };
|
||||||
|
9F37BDDD2BE37193007F28AD /* AppIntentService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F37BDDC2BE37193007F28AD /* AppIntentService.swift */; };
|
||||||
|
9F37BDDF2BE37C35007F28AD /* TabIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F37BDDE2BE37C35007F28AD /* TabIntent.swift */; };
|
||||||
|
9F37BDE12BE38646007F28AD /* PostImageIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F37BDE02BE38646007F28AD /* PostImageIntent.swift */; };
|
||||||
|
9F37BDE32BE393A7007F28AD /* AppShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F37BDE22BE393A7007F28AD /* AppShortcuts.swift */; };
|
||||||
9F38A7332ACEA26100DBCD66 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9F38A7322ACEA26100DBCD66 /* Localizable.xcstrings */; };
|
9F38A7332ACEA26100DBCD66 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9F38A7322ACEA26100DBCD66 /* Localizable.xcstrings */; };
|
||||||
9F38A7342ACEA26100DBCD66 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9F38A7322ACEA26100DBCD66 /* Localizable.xcstrings */; };
|
9F38A7342ACEA26100DBCD66 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9F38A7322ACEA26100DBCD66 /* Localizable.xcstrings */; };
|
||||||
9F38A7352ACEA26100DBCD66 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9F38A7322ACEA26100DBCD66 /* Localizable.xcstrings */; };
|
9F38A7352ACEA26100DBCD66 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9F38A7322ACEA26100DBCD66 /* Localizable.xcstrings */; };
|
||||||
|
@ -54,6 +57,9 @@
|
||||||
9F4A48192976B21900A1A038 /* ProfileTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4A48182976B21900A1A038 /* ProfileTab.swift */; };
|
9F4A48192976B21900A1A038 /* ProfileTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4A48182976B21900A1A038 /* ProfileTab.swift */; };
|
||||||
9F55C68D2955968700F94077 /* ExploreTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F55C68C2955968700F94077 /* ExploreTab.swift */; };
|
9F55C68D2955968700F94077 /* ExploreTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F55C68C2955968700F94077 /* ExploreTab.swift */; };
|
||||||
9F55C6902955993C00F94077 /* Explore in Frameworks */ = {isa = PBXBuildFile; productRef = 9F55C68F2955993C00F94077 /* Explore */; };
|
9F55C6902955993C00F94077 /* Explore in Frameworks */ = {isa = PBXBuildFile; productRef = 9F55C68F2955993C00F94077 /* Explore */; };
|
||||||
|
9F5BE6272BF492CF0074387E /* ListEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5BE6252BF48FE10074387E /* ListEntity.swift */; };
|
||||||
|
9F5BE6282BF492D10074387E /* ListsWidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5BE6232BF48FC40074387E /* ListsWidgetConfiguration.swift */; };
|
||||||
|
9F5BE6292BF492D40074387E /* ListsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5BE6212BF48FBA0074387E /* ListsWidget.swift */; };
|
||||||
9F5E581929545BE700A53960 /* Env in Frameworks */ = {isa = PBXBuildFile; productRef = 9F5E581829545BE700A53960 /* Env */; };
|
9F5E581929545BE700A53960 /* Env in Frameworks */ = {isa = PBXBuildFile; productRef = 9F5E581829545BE700A53960 /* Env */; };
|
||||||
9F6028562B3F36AE00476078 /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F6028552B3F36AE00476078 /* AppView.swift */; };
|
9F6028562B3F36AE00476078 /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F6028552B3F36AE00476078 /* AppView.swift */; };
|
||||||
9F6028582B3F3B7600476078 /* ToolbarTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F6028572B3F3B7600476078 /* ToolbarTab.swift */; };
|
9F6028582B3F3B7600476078 /* ToolbarTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F6028572B3F3B7600476078 /* ToolbarTab.swift */; };
|
||||||
|
@ -63,10 +69,30 @@
|
||||||
9F7335EF29674F7100AFF0BA /* QuickLook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F7335EE29674F7100AFF0BA /* QuickLook.framework */; };
|
9F7335EF29674F7100AFF0BA /* QuickLook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F7335EE29674F7100AFF0BA /* QuickLook.framework */; };
|
||||||
9F7335F22967608F00AFF0BA /* AddRemoteTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F12967608F00AFF0BA /* AddRemoteTimelineView.swift */; };
|
9F7335F22967608F00AFF0BA /* AddRemoteTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F12967608F00AFF0BA /* AddRemoteTimelineView.swift */; };
|
||||||
9F7335F92968576500AFF0BA /* DisplaySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */; };
|
9F7335F92968576500AFF0BA /* DisplaySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */; };
|
||||||
|
9F7788C02BE63935004E6BEF /* InlinePostIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7788BF2BE63935004E6BEF /* InlinePostIntent.swift */; };
|
||||||
|
9F7788C72BE652B1004E6BEF /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F7788C62BE652B1004E6BEF /* WidgetKit.framework */; };
|
||||||
|
9F7788C92BE652B1004E6BEF /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F7788C82BE652B1004E6BEF /* SwiftUI.framework */; };
|
||||||
|
9F7788CC2BE652B1004E6BEF /* IceCubesAppWidgetsExtensionBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7788CB2BE652B1004E6BEF /* IceCubesAppWidgetsExtensionBundle.swift */; };
|
||||||
|
9F7788CE2BE652B1004E6BEF /* LatestPostsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7788CD2BE652B1004E6BEF /* LatestPostsWidget.swift */; };
|
||||||
|
9F7788D02BE652B1004E6BEF /* LatestPostsWidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7788CF2BE652B1004E6BEF /* LatestPostsWidgetConfiguration.swift */; };
|
||||||
|
9F7788D22BE652B2004E6BEF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9F7788D12BE652B2004E6BEF /* Assets.xcassets */; };
|
||||||
|
9F7788D62BE652B2004E6BEF /* IceCubesAppWidgetsExtensionExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9F7788C52BE652B1004E6BEF /* IceCubesAppWidgetsExtensionExtension.appex */; platformFilters = (ios, maccatalyst, ); settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
9F7788DE2BE6543D004E6BEF /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 9F7788DD2BE6543D004E6BEF /* Account */; };
|
||||||
|
9F7788E02BE6543D004E6BEF /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9F7788DF2BE6543D004E6BEF /* AppAccount */; };
|
||||||
|
9F7788E22BE6543D004E6BEF /* Env in Frameworks */ = {isa = PBXBuildFile; productRef = 9F7788E12BE6543D004E6BEF /* Env */; };
|
||||||
|
9F7788E42BE6543D004E6BEF /* Models in Frameworks */ = {isa = PBXBuildFile; productRef = 9F7788E32BE6543D004E6BEF /* Models */; };
|
||||||
|
9F7788E62BE6543D004E6BEF /* Network in Frameworks */ = {isa = PBXBuildFile; productRef = 9F7788E52BE6543D004E6BEF /* Network */; };
|
||||||
|
9F7788E82BE65533004E6BEF /* AppAccountEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7788E72BE65533004E6BEF /* AppAccountEntity.swift */; };
|
||||||
|
9F7788EA2BE65585004E6BEF /* AppAccountEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7788E72BE65533004E6BEF /* AppAccountEntity.swift */; };
|
||||||
|
9F7788ED2BE78D75004E6BEF /* TimelineFilterEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7788EC2BE78D75004E6BEF /* TimelineFilterEntity.swift */; };
|
||||||
|
9F7788EE2BE78D7B004E6BEF /* TimelineFilterEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7788EC2BE78D75004E6BEF /* TimelineFilterEntity.swift */; };
|
||||||
|
9F7788F02BE78E77004E6BEF /* Timeline in Frameworks */ = {isa = PBXBuildFile; productRef = 9F7788EF2BE78E77004E6BEF /* Timeline */; };
|
||||||
9F7D93942980063100EE6B7A /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9F7D93932980063100EE6B7A /* AppAccount */; };
|
9F7D93942980063100EE6B7A /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9F7D93932980063100EE6B7A /* AppAccount */; };
|
||||||
9F7D939A29805DBD00EE6B7A /* AccountSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7D939929805DBD00EE6B7A /* AccountSettingView.swift */; };
|
9F7D939A29805DBD00EE6B7A /* AccountSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7D939929805DBD00EE6B7A /* AccountSettingView.swift */; };
|
||||||
|
9F8B92122BF77DBE003D37A2 /* AccountWidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F8B920E2BF77DB4003D37A2 /* AccountWidgetConfiguration.swift */; };
|
||||||
|
9F8B92132BF77DBE003D37A2 /* AccountWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F8B92102BF77DBB003D37A2 /* AccountWidget.swift */; };
|
||||||
|
9F8B92162BF77F0B003D37A2 /* AccountWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F8B92142BF77F05003D37A2 /* AccountWidgetView.swift */; };
|
||||||
9FA6FD6229C04A8800E2312C /* TranslationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA6FD6129C04A8800E2312C /* TranslationSettingsView.swift */; };
|
9FA6FD6229C04A8800E2312C /* TranslationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA6FD6129C04A8800E2312C /* TranslationSettingsView.swift */; };
|
||||||
9FAD85832971BF7200496AB1 /* Secret.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9FAD85822971BF7200496AB1 /* Secret.plist */; };
|
|
||||||
9FAD858B29743F7400496AB1 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAD858A29743F7400496AB1 /* ShareViewController.swift */; };
|
9FAD858B29743F7400496AB1 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAD858A29743F7400496AB1 /* ShareViewController.swift */; };
|
||||||
9FAD858E29743F7400496AB1 /* (null) in Resources */ = {isa = PBXBuildFile; };
|
9FAD858E29743F7400496AB1 /* (null) in Resources */ = {isa = PBXBuildFile; };
|
||||||
9FAD859229743F7400496AB1 /* IceCubesShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */; platformFilters = (ios, maccatalyst, ); settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
9FAD859229743F7400496AB1 /* IceCubesShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */; platformFilters = (ios, maccatalyst, ); settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
@ -98,6 +124,13 @@
|
||||||
9FE3DB57296FEFCA00628CB0 /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9FE3DB56296FEFCA00628CB0 /* AppAccount */; };
|
9FE3DB57296FEFCA00628CB0 /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9FE3DB56296FEFCA00628CB0 /* AppAccount */; };
|
||||||
9FE4CCAB2B4C848A00DA5F13 /* GiphyUISDK in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, maccatalyst, ); productRef = 9FE4CCAA2B4C848A00DA5F13 /* GiphyUISDK */; };
|
9FE4CCAB2B4C848A00DA5F13 /* GiphyUISDK in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, maccatalyst, ); productRef = 9FE4CCAA2B4C848A00DA5F13 /* GiphyUISDK */; };
|
||||||
9FE4CCAD2B4C849F00DA5F13 /* GiphyUISDK in Frameworks */ = {isa = PBXBuildFile; productRef = 9FE4CCAC2B4C849F00DA5F13 /* GiphyUISDK */; };
|
9FE4CCAD2B4C849F00DA5F13 /* GiphyUISDK in Frameworks */ = {isa = PBXBuildFile; productRef = 9FE4CCAC2B4C849F00DA5F13 /* GiphyUISDK */; };
|
||||||
|
9FE6A42E2BD043A90055D388 /* RevenueCat in Frameworks */ = {isa = PBXBuildFile; productRef = 9FE6A42D2BD043A90055D388 /* RevenueCat */; };
|
||||||
|
9FF2FB622BE7F5D5001560CE /* HashtagPostsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF2FB602BE7F5A7001560CE /* HashtagPostsWidget.swift */; };
|
||||||
|
9FF2FB632BE7F5D9001560CE /* HashtagPostsWidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF2FB5E2BE7F56F001560CE /* HashtagPostsWidgetConfiguration.swift */; };
|
||||||
|
9FF2FB672BE7F816001560CE /* PostsWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF2FB652BE7F805001560CE /* PostsWidgetView.swift */; };
|
||||||
|
9FF2FB6A2BE7F84E001560CE /* SharedUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF2FB682BE7F842001560CE /* SharedUtils.swift */; };
|
||||||
|
9FF2FB702BE8AE9D001560CE /* MentionWidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF2FB6E2BE8AE9B001560CE /* MentionWidgetConfiguration.swift */; };
|
||||||
|
9FF2FB712BE8AEA0001560CE /* MentionWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF2FB6C2BE8AE90001560CE /* MentionWidget.swift */; };
|
||||||
9FFF677C299B7B2C00FE700A /* Notifications in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF677B299B7B2C00FE700A /* Notifications */; };
|
9FFF677C299B7B2C00FE700A /* Notifications in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF677B299B7B2C00FE700A /* Notifications */; };
|
||||||
9FFF6780299B7D2B00FE700A /* DesignSystem in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF677F299B7D2B00FE700A /* DesignSystem */; };
|
9FFF6780299B7D2B00FE700A /* DesignSystem in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF677F299B7D2B00FE700A /* DesignSystem */; };
|
||||||
9FFF6782299B7D3A00FE700A /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF6781299B7D3A00FE700A /* Account */; };
|
9FFF6782299B7D3A00FE700A /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF6781299B7D3A00FE700A /* Account */; };
|
||||||
|
@ -123,6 +156,13 @@
|
||||||
remoteGlobalIDString = 9F2A5415296AB631009B2D7C;
|
remoteGlobalIDString = 9F2A5415296AB631009B2D7C;
|
||||||
remoteInfo = IceCubesNotifications;
|
remoteInfo = IceCubesNotifications;
|
||||||
};
|
};
|
||||||
|
9F7788D42BE652B2004E6BEF /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 9FBFE631292A715500C250E9 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 9F7788C42BE652B1004E6BEF;
|
||||||
|
remoteInfo = IceCubesAppWidgetsExtensionExtension;
|
||||||
|
};
|
||||||
9FAD859029743F7400496AB1 /* PBXContainerItemProxy */ = {
|
9FAD859029743F7400496AB1 /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = 9FBFE631292A715500C250E9 /* Project object */;
|
containerPortal = 9FBFE631292A715500C250E9 /* Project object */;
|
||||||
|
@ -149,6 +189,7 @@
|
||||||
E9DF420729830FEC0003AAD2 /* IceCubesActionExtension.appex in Embed Foundation Extensions */,
|
E9DF420729830FEC0003AAD2 /* IceCubesActionExtension.appex in Embed Foundation Extensions */,
|
||||||
9F2A541D296AB631009B2D7C /* IceCubesNotifications.appex in Embed Foundation Extensions */,
|
9F2A541D296AB631009B2D7C /* IceCubesNotifications.appex in Embed Foundation Extensions */,
|
||||||
9FAD859229743F7400496AB1 /* IceCubesShareExtension.appex in Embed Foundation Extensions */,
|
9FAD859229743F7400496AB1 /* IceCubesShareExtension.appex in Embed Foundation Extensions */,
|
||||||
|
9F7788D62BE652B2004E6BEF /* IceCubesAppWidgetsExtensionExtension.appex in Embed Foundation Extensions */,
|
||||||
);
|
);
|
||||||
name = "Embed Foundation Extensions";
|
name = "Embed Foundation Extensions";
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -197,6 +238,11 @@
|
||||||
9F35DB4629506F6600B3281A /* NotificationTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationTab.swift; sourceTree = "<group>"; };
|
9F35DB4629506F6600B3281A /* NotificationTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationTab.swift; sourceTree = "<group>"; };
|
||||||
9F35DB4829506F7F00B3281A /* Notifications */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Notifications; path = Packages/Notifications; sourceTree = "<group>"; };
|
9F35DB4829506F7F00B3281A /* Notifications */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Notifications; path = Packages/Notifications; sourceTree = "<group>"; };
|
||||||
9F35DB4B2952005C00B3281A /* MessagesTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesTab.swift; sourceTree = "<group>"; };
|
9F35DB4B2952005C00B3281A /* MessagesTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesTab.swift; sourceTree = "<group>"; };
|
||||||
|
9F37BDDA2BE36E22007F28AD /* PostIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostIntent.swift; sourceTree = "<group>"; };
|
||||||
|
9F37BDDC2BE37193007F28AD /* AppIntentService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntentService.swift; sourceTree = "<group>"; };
|
||||||
|
9F37BDDE2BE37C35007F28AD /* TabIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabIntent.swift; sourceTree = "<group>"; };
|
||||||
|
9F37BDE02BE38646007F28AD /* PostImageIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostImageIntent.swift; sourceTree = "<group>"; };
|
||||||
|
9F37BDE22BE393A7007F28AD /* AppShortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppShortcuts.swift; sourceTree = "<group>"; };
|
||||||
9F38A7322ACEA26100DBCD66 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
|
9F38A7322ACEA26100DBCD66 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
|
||||||
9F398AA32935F90100A889F2 /* Models */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Models; path = Packages/Models; sourceTree = "<group>"; };
|
9F398AA32935F90100A889F2 /* Models */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Models; path = Packages/Models; sourceTree = "<group>"; };
|
||||||
9F398AA52935FE8A00A889F2 /* AppRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRegistry.swift; sourceTree = "<group>"; };
|
9F398AA52935FE8A00A889F2 /* AppRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRegistry.swift; sourceTree = "<group>"; };
|
||||||
|
@ -205,6 +251,9 @@
|
||||||
9F4A48182976B21900A1A038 /* ProfileTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTab.swift; sourceTree = "<group>"; };
|
9F4A48182976B21900A1A038 /* ProfileTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTab.swift; sourceTree = "<group>"; };
|
||||||
9F55C68C2955968700F94077 /* ExploreTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreTab.swift; sourceTree = "<group>"; };
|
9F55C68C2955968700F94077 /* ExploreTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreTab.swift; sourceTree = "<group>"; };
|
||||||
9F55C68E295598F900F94077 /* Explore */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Explore; path = Packages/Explore; sourceTree = "<group>"; };
|
9F55C68E295598F900F94077 /* Explore */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Explore; path = Packages/Explore; sourceTree = "<group>"; };
|
||||||
|
9F5BE6212BF48FBA0074387E /* ListsWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListsWidget.swift; sourceTree = "<group>"; };
|
||||||
|
9F5BE6232BF48FC40074387E /* ListsWidgetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListsWidgetConfiguration.swift; sourceTree = "<group>"; };
|
||||||
|
9F5BE6252BF48FE10074387E /* ListEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListEntity.swift; sourceTree = "<group>"; };
|
||||||
9F5E581729545B5500A53960 /* Env */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Env; path = Packages/Env; sourceTree = "<group>"; };
|
9F5E581729545B5500A53960 /* Env */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Env; path = Packages/Env; sourceTree = "<group>"; };
|
||||||
9F6028552B3F36AE00476078 /* AppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppView.swift; sourceTree = "<group>"; };
|
9F6028552B3F36AE00476078 /* AppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppView.swift; sourceTree = "<group>"; };
|
||||||
9F6028572B3F3B7600476078 /* ToolbarTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarTab.swift; sourceTree = "<group>"; };
|
9F6028572B3F3B7600476078 /* ToolbarTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarTab.swift; sourceTree = "<group>"; };
|
||||||
|
@ -215,10 +264,24 @@
|
||||||
9F7335EE29674F7100AFF0BA /* QuickLook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLook.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.2.sdk/System/Library/Frameworks/QuickLook.framework; sourceTree = DEVELOPER_DIR; };
|
9F7335EE29674F7100AFF0BA /* QuickLook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLook.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.2.sdk/System/Library/Frameworks/QuickLook.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
9F7335F12967608F00AFF0BA /* AddRemoteTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRemoteTimelineView.swift; sourceTree = "<group>"; };
|
9F7335F12967608F00AFF0BA /* AddRemoteTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRemoteTimelineView.swift; sourceTree = "<group>"; };
|
||||||
9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplaySettingsView.swift; sourceTree = "<group>"; };
|
9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplaySettingsView.swift; sourceTree = "<group>"; };
|
||||||
|
9F7788BF2BE63935004E6BEF /* InlinePostIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlinePostIntent.swift; sourceTree = "<group>"; };
|
||||||
|
9F7788C52BE652B1004E6BEF /* IceCubesAppWidgetsExtensionExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = IceCubesAppWidgetsExtensionExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
9F7788C62BE652B1004E6BEF /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
|
||||||
|
9F7788C82BE652B1004E6BEF /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
|
||||||
|
9F7788CB2BE652B1004E6BEF /* IceCubesAppWidgetsExtensionBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IceCubesAppWidgetsExtensionBundle.swift; sourceTree = "<group>"; };
|
||||||
|
9F7788CD2BE652B1004E6BEF /* LatestPostsWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestPostsWidget.swift; sourceTree = "<group>"; };
|
||||||
|
9F7788CF2BE652B1004E6BEF /* LatestPostsWidgetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestPostsWidgetConfiguration.swift; sourceTree = "<group>"; };
|
||||||
|
9F7788D12BE652B2004E6BEF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
9F7788D32BE652B2004E6BEF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
9F7788D72BE652B2004E6BEF /* IceCubesAppWidgetsExtensionExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = IceCubesAppWidgetsExtensionExtension.entitlements; sourceTree = "<group>"; };
|
||||||
|
9F7788E72BE65533004E6BEF /* AppAccountEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAccountEntity.swift; sourceTree = "<group>"; };
|
||||||
|
9F7788EC2BE78D75004E6BEF /* TimelineFilterEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineFilterEntity.swift; sourceTree = "<group>"; };
|
||||||
9F7D939529800B0300EE6B7A /* IceCubesApp-release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "IceCubesApp-release.xcconfig"; sourceTree = "<group>"; };
|
9F7D939529800B0300EE6B7A /* IceCubesApp-release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "IceCubesApp-release.xcconfig"; sourceTree = "<group>"; };
|
||||||
9F7D939929805DBD00EE6B7A /* AccountSettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSettingView.swift; sourceTree = "<group>"; };
|
9F7D939929805DBD00EE6B7A /* AccountSettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSettingView.swift; sourceTree = "<group>"; };
|
||||||
|
9F8B920E2BF77DB4003D37A2 /* AccountWidgetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountWidgetConfiguration.swift; sourceTree = "<group>"; };
|
||||||
|
9F8B92102BF77DBB003D37A2 /* AccountWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountWidget.swift; sourceTree = "<group>"; };
|
||||||
|
9F8B92142BF77F05003D37A2 /* AccountWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountWidgetView.swift; sourceTree = "<group>"; };
|
||||||
9FA6FD6129C04A8800E2312C /* TranslationSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationSettingsView.swift; sourceTree = "<group>"; };
|
9FA6FD6129C04A8800E2312C /* TranslationSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationSettingsView.swift; sourceTree = "<group>"; };
|
||||||
9FAD85822971BF7200496AB1 /* Secret.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Secret.plist; sourceTree = "<group>"; };
|
|
||||||
9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = IceCubesShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = IceCubesShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
9FAD858A29743F7400496AB1 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
9FAD858A29743F7400496AB1 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
||||||
9FAD858F29743F7400496AB1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
9FAD858F29743F7400496AB1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
@ -243,6 +306,12 @@
|
||||||
9FE0346A2ADD59AC00529EA8 /* MediaUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = MediaUI; path = Packages/MediaUI; sourceTree = "<group>"; };
|
9FE0346A2ADD59AC00529EA8 /* MediaUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = MediaUI; path = Packages/MediaUI; sourceTree = "<group>"; };
|
||||||
9FE151A5293C90F900E9683D /* IconSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconSelectorView.swift; sourceTree = "<group>"; };
|
9FE151A5293C90F900E9683D /* IconSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconSelectorView.swift; sourceTree = "<group>"; };
|
||||||
9FE3DB55296FEF5800628CB0 /* AppAccount */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = AppAccount; path = Packages/AppAccount; sourceTree = "<group>"; };
|
9FE3DB55296FEF5800628CB0 /* AppAccount */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = AppAccount; path = Packages/AppAccount; sourceTree = "<group>"; };
|
||||||
|
9FF2FB5E2BE7F56F001560CE /* HashtagPostsWidgetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagPostsWidgetConfiguration.swift; sourceTree = "<group>"; };
|
||||||
|
9FF2FB602BE7F5A7001560CE /* HashtagPostsWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagPostsWidget.swift; sourceTree = "<group>"; };
|
||||||
|
9FF2FB652BE7F805001560CE /* PostsWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostsWidgetView.swift; sourceTree = "<group>"; };
|
||||||
|
9FF2FB682BE7F842001560CE /* SharedUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedUtils.swift; sourceTree = "<group>"; };
|
||||||
|
9FF2FB6C2BE8AE90001560CE /* MentionWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionWidget.swift; sourceTree = "<group>"; };
|
||||||
|
9FF2FB6E2BE8AE9B001560CE /* MentionWidgetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionWidgetConfiguration.swift; sourceTree = "<group>"; };
|
||||||
B0BAB49E29B3D7A9008F54D7 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
B0BAB49E29B3D7A9008F54D7 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||||
C4CBB90B298A0DA3007E1707 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
C4CBB90B298A0DA3007E1707 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||||
C4FBCF6F298FD88A0015DF22 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
C4FBCF6F298FD88A0015DF22 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||||
|
@ -278,6 +347,21 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
9F7788C22BE652B1004E6BEF /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
9F7788E42BE6543D004E6BEF /* Models in Frameworks */,
|
||||||
|
9F7788E62BE6543D004E6BEF /* Network in Frameworks */,
|
||||||
|
9F7788E02BE6543D004E6BEF /* AppAccount in Frameworks */,
|
||||||
|
9F7788DE2BE6543D004E6BEF /* Account in Frameworks */,
|
||||||
|
9F7788E22BE6543D004E6BEF /* Env in Frameworks */,
|
||||||
|
9F7788C92BE652B1004E6BEF /* SwiftUI.framework in Frameworks */,
|
||||||
|
9F7788C72BE652B1004E6BEF /* WidgetKit.framework in Frameworks */,
|
||||||
|
9F7788F02BE78E77004E6BEF /* Timeline in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
9FAD858529743F7400496AB1 /* Frameworks */ = {
|
9FAD858529743F7400496AB1 /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
@ -300,7 +384,6 @@
|
||||||
9F7335EF29674F7100AFF0BA /* QuickLook.framework in Frameworks */,
|
9F7335EF29674F7100AFF0BA /* QuickLook.framework in Frameworks */,
|
||||||
9FE4CCAB2B4C848A00DA5F13 /* GiphyUISDK in Frameworks */,
|
9FE4CCAB2B4C848A00DA5F13 /* GiphyUISDK in Frameworks */,
|
||||||
9F7335ED2967463400AFF0BA /* AVKit.framework in Frameworks */,
|
9F7335ED2967463400AFF0BA /* AVKit.framework in Frameworks */,
|
||||||
9F2A540C29699705009B2D7C /* RevenueCat in Frameworks */,
|
|
||||||
9F2A540E2969A0B0009B2D7C /* StoreKit.framework in Frameworks */,
|
9F2A540E2969A0B0009B2D7C /* StoreKit.framework in Frameworks */,
|
||||||
9F55C6902955993C00F94077 /* Explore in Frameworks */,
|
9F55C6902955993C00F94077 /* Explore in Frameworks */,
|
||||||
9FAE4ACE29379A5A00772766 /* KeychainSwift in Frameworks */,
|
9FAE4ACE29379A5A00772766 /* KeychainSwift in Frameworks */,
|
||||||
|
@ -311,8 +394,8 @@
|
||||||
9FD542E72962D2FF0045321A /* Lists in Frameworks */,
|
9FD542E72962D2FF0045321A /* Lists in Frameworks */,
|
||||||
9F398AAB2935FFDB00A889F2 /* Models in Frameworks */,
|
9F398AAB2935FFDB00A889F2 /* Models in Frameworks */,
|
||||||
9F5E581929545BE700A53960 /* Env in Frameworks */,
|
9F5E581929545BE700A53960 /* Env in Frameworks */,
|
||||||
9F2A540A29699705009B2D7C /* ReceiptParser in Frameworks */,
|
|
||||||
DA0B24FB2A6876D50045BDD7 /* SFSafeSymbols in Frameworks */,
|
DA0B24FB2A6876D50045BDD7 /* SFSafeSymbols in Frameworks */,
|
||||||
|
9FE6A42E2BD043A90055D388 /* RevenueCat in Frameworks */,
|
||||||
9F295540292B6C3400E0E81B /* Timeline in Frameworks */,
|
9F295540292B6C3400E0E81B /* Timeline in Frameworks */,
|
||||||
9F35DB4A29506FA100B3281A /* Notifications in Frameworks */,
|
9F35DB4A29506FA100B3281A /* Notifications in Frameworks */,
|
||||||
9FC2A38B2B49D19A00DFD1C1 /* StatusKit in Frameworks */,
|
9FC2A38B2B49D19A00DFD1C1 /* StatusKit in Frameworks */,
|
||||||
|
@ -351,6 +434,22 @@
|
||||||
path = IceCubesNotifications;
|
path = IceCubesNotifications;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
9F37BDD92BE36E08007F28AD /* IceCubesAppIntents */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9F7788E72BE65533004E6BEF /* AppAccountEntity.swift */,
|
||||||
|
9F37BDDC2BE37193007F28AD /* AppIntentService.swift */,
|
||||||
|
9F37BDE22BE393A7007F28AD /* AppShortcuts.swift */,
|
||||||
|
9F7788BF2BE63935004E6BEF /* InlinePostIntent.swift */,
|
||||||
|
9F37BDE02BE38646007F28AD /* PostImageIntent.swift */,
|
||||||
|
9F37BDDA2BE36E22007F28AD /* PostIntent.swift */,
|
||||||
|
9F37BDDE2BE37C35007F28AD /* TabIntent.swift */,
|
||||||
|
9F7788EC2BE78D75004E6BEF /* TimelineFilterEntity.swift */,
|
||||||
|
9F5BE6252BF48FE10074387E /* ListEntity.swift */,
|
||||||
|
);
|
||||||
|
path = IceCubesAppIntents;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
9F398AB429360A5800A889F2 /* App */ = {
|
9F398AB429360A5800A889F2 /* App */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -375,6 +474,15 @@
|
||||||
path = Resources;
|
path = Resources;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
9F5BE6202BF48FB20074387E /* ListsWidget */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9F5BE6212BF48FBA0074387E /* ListsWidget.swift */,
|
||||||
|
9F5BE6232BF48FC40074387E /* ListsWidgetConfiguration.swift */,
|
||||||
|
);
|
||||||
|
path = ListsWidget;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
9F654BF0299AC46200D27FA5 /* Report */ = {
|
9F654BF0299AC46200D27FA5 /* Report */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -392,6 +500,33 @@
|
||||||
path = Timeline;
|
path = Timeline;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
9F7788CA2BE652B1004E6BEF /* IceCubesAppWidgetsExtension */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9F8B920D2BF77DA8003D37A2 /* AccountWidget */,
|
||||||
|
9F5BE6202BF48FB20074387E /* ListsWidget */,
|
||||||
|
9FF2FB6B2BE8AE78001560CE /* MentionWidget */,
|
||||||
|
9FF2FB642BE7F7FA001560CE /* Shared */,
|
||||||
|
9FF2FB5D2BE7F559001560CE /* HashtagPostsWidget */,
|
||||||
|
9FF2FB5C2BE7F549001560CE /* LatestPostsWidget */,
|
||||||
|
9F7788D72BE652B2004E6BEF /* IceCubesAppWidgetsExtensionExtension.entitlements */,
|
||||||
|
9F7788CB2BE652B1004E6BEF /* IceCubesAppWidgetsExtensionBundle.swift */,
|
||||||
|
9F7788D12BE652B2004E6BEF /* Assets.xcassets */,
|
||||||
|
9F7788D32BE652B2004E6BEF /* Info.plist */,
|
||||||
|
);
|
||||||
|
path = IceCubesAppWidgetsExtension;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9F8B920D2BF77DA8003D37A2 /* AccountWidget */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9F8B920E2BF77DB4003D37A2 /* AccountWidgetConfiguration.swift */,
|
||||||
|
9F8B92102BF77DBB003D37A2 /* AccountWidget.swift */,
|
||||||
|
9F8B92142BF77F05003D37A2 /* AccountWidgetView.swift */,
|
||||||
|
);
|
||||||
|
path = AccountWidget;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
9FA0D2AC29921C1F008A143B /* Embeds */ = {
|
9FA0D2AC29921C1F008A143B /* Embeds */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -456,9 +591,11 @@
|
||||||
DD31E2E5297FB68B00A4BE29 /* IceCubesApp.xcconfig */,
|
DD31E2E5297FB68B00A4BE29 /* IceCubesApp.xcconfig */,
|
||||||
9F7D939529800B0300EE6B7A /* IceCubesApp-release.xcconfig */,
|
9F7D939529800B0300EE6B7A /* IceCubesApp-release.xcconfig */,
|
||||||
9FBFE63B292A715500C250E9 /* IceCubesApp */,
|
9FBFE63B292A715500C250E9 /* IceCubesApp */,
|
||||||
|
9F37BDD92BE36E08007F28AD /* IceCubesAppIntents */,
|
||||||
E9DF41FD29830FEC0003AAD2 /* IceCubesActionExtension */,
|
E9DF41FD29830FEC0003AAD2 /* IceCubesActionExtension */,
|
||||||
9F2A5417296AB631009B2D7C /* IceCubesNotifications */,
|
9F2A5417296AB631009B2D7C /* IceCubesNotifications */,
|
||||||
9FAD858929743F7400496AB1 /* IceCubesShareExtension */,
|
9FAD858929743F7400496AB1 /* IceCubesShareExtension */,
|
||||||
|
9F7788CA2BE652B1004E6BEF /* IceCubesAppWidgetsExtension */,
|
||||||
9FBFE63A292A715500C250E9 /* Products */,
|
9FBFE63A292A715500C250E9 /* Products */,
|
||||||
9FBFE64C292A72BD00C250E9 /* Frameworks */,
|
9FBFE64C292A72BD00C250E9 /* Frameworks */,
|
||||||
9FE3DB55296FEF5800628CB0 /* AppAccount */,
|
9FE3DB55296FEF5800628CB0 /* AppAccount */,
|
||||||
|
@ -484,6 +621,7 @@
|
||||||
9F2A5416296AB631009B2D7C /* IceCubesNotifications.appex */,
|
9F2A5416296AB631009B2D7C /* IceCubesNotifications.appex */,
|
||||||
9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */,
|
9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */,
|
||||||
E9DF41FA29830FEC0003AAD2 /* IceCubesActionExtension.appex */,
|
E9DF41FA29830FEC0003AAD2 /* IceCubesActionExtension.appex */,
|
||||||
|
9F7788C52BE652B1004E6BEF /* IceCubesAppWidgetsExtensionExtension.appex */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -496,7 +634,6 @@
|
||||||
9FAE4AC8293774FF00772766 /* Info.plist */,
|
9FAE4AC8293774FF00772766 /* Info.plist */,
|
||||||
9F398AB429360A5800A889F2 /* App */,
|
9F398AB429360A5800A889F2 /* App */,
|
||||||
9F398AB529360A6100A889F2 /* Resources */,
|
9F398AB529360A6100A889F2 /* Resources */,
|
||||||
9FAD85822971BF7200496AB1 /* Secret.plist */,
|
|
||||||
);
|
);
|
||||||
path = IceCubesApp;
|
path = IceCubesApp;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -509,6 +646,8 @@
|
||||||
9F7335EE29674F7100AFF0BA /* QuickLook.framework */,
|
9F7335EE29674F7100AFF0BA /* QuickLook.framework */,
|
||||||
9F7335EB2967461B00AFF0BA /* AVKit.framework */,
|
9F7335EB2967461B00AFF0BA /* AVKit.framework */,
|
||||||
E9DF41FB29830FEC0003AAD2 /* UniformTypeIdentifiers.framework */,
|
E9DF41FB29830FEC0003AAD2 /* UniformTypeIdentifiers.framework */,
|
||||||
|
9F7788C62BE652B1004E6BEF /* WidgetKit.framework */,
|
||||||
|
9F7788C82BE652B1004E6BEF /* SwiftUI.framework */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -538,6 +677,42 @@
|
||||||
path = Settings;
|
path = Settings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
9FF2FB5C2BE7F549001560CE /* LatestPostsWidget */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9F7788CD2BE652B1004E6BEF /* LatestPostsWidget.swift */,
|
||||||
|
9F7788CF2BE652B1004E6BEF /* LatestPostsWidgetConfiguration.swift */,
|
||||||
|
);
|
||||||
|
path = LatestPostsWidget;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9FF2FB5D2BE7F559001560CE /* HashtagPostsWidget */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9FF2FB5E2BE7F56F001560CE /* HashtagPostsWidgetConfiguration.swift */,
|
||||||
|
9FF2FB602BE7F5A7001560CE /* HashtagPostsWidget.swift */,
|
||||||
|
);
|
||||||
|
path = HashtagPostsWidget;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9FF2FB642BE7F7FA001560CE /* Shared */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9FF2FB652BE7F805001560CE /* PostsWidgetView.swift */,
|
||||||
|
9FF2FB682BE7F842001560CE /* SharedUtils.swift */,
|
||||||
|
);
|
||||||
|
path = Shared;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9FF2FB6B2BE8AE78001560CE /* MentionWidget */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9FF2FB6C2BE8AE90001560CE /* MentionWidget.swift */,
|
||||||
|
9FF2FB6E2BE8AE9B001560CE /* MentionWidgetConfiguration.swift */,
|
||||||
|
);
|
||||||
|
path = MentionWidget;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
E9B576C029743F2A00BCE646 /* Localization */ = {
|
E9B576C029743F2A00BCE646 /* Localization */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -589,6 +764,31 @@
|
||||||
productReference = 9F2A5416296AB631009B2D7C /* IceCubesNotifications.appex */;
|
productReference = 9F2A5416296AB631009B2D7C /* IceCubesNotifications.appex */;
|
||||||
productType = "com.apple.product-type.app-extension";
|
productType = "com.apple.product-type.app-extension";
|
||||||
};
|
};
|
||||||
|
9F7788C42BE652B1004E6BEF /* IceCubesAppWidgetsExtensionExtension */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 9F7788D82BE652B2004E6BEF /* Build configuration list for PBXNativeTarget "IceCubesAppWidgetsExtensionExtension" */;
|
||||||
|
buildPhases = (
|
||||||
|
9F7788C12BE652B1004E6BEF /* Sources */,
|
||||||
|
9F7788C22BE652B1004E6BEF /* Frameworks */,
|
||||||
|
9F7788C32BE652B1004E6BEF /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = IceCubesAppWidgetsExtensionExtension;
|
||||||
|
packageProductDependencies = (
|
||||||
|
9F7788DD2BE6543D004E6BEF /* Account */,
|
||||||
|
9F7788DF2BE6543D004E6BEF /* AppAccount */,
|
||||||
|
9F7788E12BE6543D004E6BEF /* Env */,
|
||||||
|
9F7788E32BE6543D004E6BEF /* Models */,
|
||||||
|
9F7788E52BE6543D004E6BEF /* Network */,
|
||||||
|
9F7788EF2BE78E77004E6BEF /* Timeline */,
|
||||||
|
);
|
||||||
|
productName = IceCubesAppWidgetsExtensionExtension;
|
||||||
|
productReference = 9F7788C52BE652B1004E6BEF /* IceCubesAppWidgetsExtensionExtension.appex */;
|
||||||
|
productType = "com.apple.product-type.app-extension";
|
||||||
|
};
|
||||||
9FAD858729743F7400496AB1 /* IceCubesShareExtension */ = {
|
9FAD858729743F7400496AB1 /* IceCubesShareExtension */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 9FAD859329743F7400496AB1 /* Build configuration list for PBXNativeTarget "IceCubesShareExtension" */;
|
buildConfigurationList = 9FAD859329743F7400496AB1 /* Build configuration list for PBXNativeTarget "IceCubesShareExtension" */;
|
||||||
|
@ -631,6 +831,7 @@
|
||||||
9F2A541C296AB631009B2D7C /* PBXTargetDependency */,
|
9F2A541C296AB631009B2D7C /* PBXTargetDependency */,
|
||||||
9FAD859129743F7400496AB1 /* PBXTargetDependency */,
|
9FAD859129743F7400496AB1 /* PBXTargetDependency */,
|
||||||
E9DF420629830FEC0003AAD2 /* PBXTargetDependency */,
|
E9DF420629830FEC0003AAD2 /* PBXTargetDependency */,
|
||||||
|
9F7788D52BE652B2004E6BEF /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = IceCubesApp;
|
name = IceCubesApp;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
|
@ -644,12 +845,11 @@
|
||||||
9F55C68F2955993C00F94077 /* Explore */,
|
9F55C68F2955993C00F94077 /* Explore */,
|
||||||
9FD542E62962D2FF0045321A /* Lists */,
|
9FD542E62962D2FF0045321A /* Lists */,
|
||||||
9F7335E92966B3F800AFF0BA /* Conversations */,
|
9F7335E92966B3F800AFF0BA /* Conversations */,
|
||||||
9F2A540929699705009B2D7C /* ReceiptParser */,
|
|
||||||
9F2A540B29699705009B2D7C /* RevenueCat */,
|
|
||||||
9FE3DB56296FEFCA00628CB0 /* AppAccount */,
|
9FE3DB56296FEFCA00628CB0 /* AppAccount */,
|
||||||
DA0B24FA2A6876D50045BDD7 /* SFSafeSymbols */,
|
DA0B24FA2A6876D50045BDD7 /* SFSafeSymbols */,
|
||||||
9FC2A38A2B49D19A00DFD1C1 /* StatusKit */,
|
9FC2A38A2B49D19A00DFD1C1 /* StatusKit */,
|
||||||
9FE4CCAA2B4C848A00DA5F13 /* GiphyUISDK */,
|
9FE4CCAA2B4C848A00DA5F13 /* GiphyUISDK */,
|
||||||
|
9FE6A42D2BD043A90055D388 /* RevenueCat */,
|
||||||
);
|
);
|
||||||
productName = IceCubesApp;
|
productName = IceCubesApp;
|
||||||
productReference = 9FBFE639292A715500C250E9 /* Ice Cubes.app */;
|
productReference = 9FBFE639292A715500C250E9 /* Ice Cubes.app */;
|
||||||
|
@ -683,12 +883,15 @@
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = 1;
|
BuildIndependentTargetsInParallel = 1;
|
||||||
LastSwiftUpdateCheck = 1420;
|
LastSwiftUpdateCheck = 1530;
|
||||||
LastUpgradeCheck = 1500;
|
LastUpgradeCheck = 1500;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
9F2A5415296AB631009B2D7C = {
|
9F2A5415296AB631009B2D7C = {
|
||||||
CreatedOnToolsVersion = 14.2;
|
CreatedOnToolsVersion = 14.2;
|
||||||
};
|
};
|
||||||
|
9F7788C42BE652B1004E6BEF = {
|
||||||
|
CreatedOnToolsVersion = 15.3;
|
||||||
|
};
|
||||||
9FAD858729743F7400496AB1 = {
|
9FAD858729743F7400496AB1 = {
|
||||||
CreatedOnToolsVersion = 14.2;
|
CreatedOnToolsVersion = 14.2;
|
||||||
};
|
};
|
||||||
|
@ -724,13 +927,14 @@
|
||||||
be,
|
be,
|
||||||
uk,
|
uk,
|
||||||
"zh-Hant",
|
"zh-Hant",
|
||||||
|
Base,
|
||||||
);
|
);
|
||||||
mainGroup = 9FBFE630292A715500C250E9;
|
mainGroup = 9FBFE630292A715500C250E9;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
9FAE4ACC29379A5A00772766 /* XCRemoteSwiftPackageReference "keychain-swift" */,
|
9FAE4ACC29379A5A00772766 /* XCRemoteSwiftPackageReference "keychain-swift" */,
|
||||||
9F2A540829699705009B2D7C /* XCRemoteSwiftPackageReference "purchases-ios" */,
|
|
||||||
DA0B24F92A6876D40045BDD7 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */,
|
DA0B24F92A6876D40045BDD7 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */,
|
||||||
9FE4CCA92B4C848A00DA5F13 /* XCRemoteSwiftPackageReference "giphy-ios-sdk" */,
|
9FE4CCA92B4C848A00DA5F13 /* XCRemoteSwiftPackageReference "giphy-ios-sdk" */,
|
||||||
|
9FE6A42C2BD043A80055D388 /* XCRemoteSwiftPackageReference "purchases-ios" */,
|
||||||
);
|
);
|
||||||
productRefGroup = 9FBFE63A292A715500C250E9 /* Products */;
|
productRefGroup = 9FBFE63A292A715500C250E9 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
|
@ -740,6 +944,7 @@
|
||||||
E9DF41F929830FEC0003AAD2 /* IceCubesActionExtension */,
|
E9DF41F929830FEC0003AAD2 /* IceCubesActionExtension */,
|
||||||
9F2A5415296AB631009B2D7C /* IceCubesNotifications */,
|
9F2A5415296AB631009B2D7C /* IceCubesNotifications */,
|
||||||
9FAD858729743F7400496AB1 /* IceCubesShareExtension */,
|
9FAD858729743F7400496AB1 /* IceCubesShareExtension */,
|
||||||
|
9F7788C42BE652B1004E6BEF /* IceCubesAppWidgetsExtensionExtension */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
|
@ -755,6 +960,14 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
9F7788C32BE652B1004E6BEF /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
9F7788D22BE652B2004E6BEF /* Assets.xcassets in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
9FAD858629743F7400496AB1 /* Resources */ = {
|
9FAD858629743F7400496AB1 /* Resources */ = {
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
@ -778,7 +991,6 @@
|
||||||
9F18801829AE477F00D85459 /* favorite.wav in Resources */,
|
9F18801829AE477F00D85459 /* favorite.wav in Resources */,
|
||||||
9F24EEB829360C330042359D /* Preview Assets.xcassets in Resources */,
|
9F24EEB829360C330042359D /* Preview Assets.xcassets in Resources */,
|
||||||
069709A8298C87B5006E4CB5 /* OpenDyslexic-Regular.otf in Resources */,
|
069709A8298C87B5006E4CB5 /* OpenDyslexic-Regular.otf in Resources */,
|
||||||
9FAD85832971BF7200496AB1 /* Secret.plist in Resources */,
|
|
||||||
9F18801229AE477F00D85459 /* tabSelection.wav in Resources */,
|
9F18801229AE477F00D85459 /* tabSelection.wav in Resources */,
|
||||||
9F18801429AE477F00D85459 /* bookmark.wav in Resources */,
|
9F18801429AE477F00D85459 /* bookmark.wav in Resources */,
|
||||||
9F18801629AE477F00D85459 /* refresh.wav in Resources */,
|
9F18801629AE477F00D85459 /* refresh.wav in Resources */,
|
||||||
|
@ -809,6 +1021,30 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
9F7788C12BE652B1004E6BEF /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
9F5BE6272BF492CF0074387E /* ListEntity.swift in Sources */,
|
||||||
|
9FF2FB622BE7F5D5001560CE /* HashtagPostsWidget.swift in Sources */,
|
||||||
|
9FF2FB712BE8AEA0001560CE /* MentionWidget.swift in Sources */,
|
||||||
|
9F8B92162BF77F0B003D37A2 /* AccountWidgetView.swift in Sources */,
|
||||||
|
9F8B92122BF77DBE003D37A2 /* AccountWidgetConfiguration.swift in Sources */,
|
||||||
|
9F8B92132BF77DBE003D37A2 /* AccountWidget.swift in Sources */,
|
||||||
|
9F7788EA2BE65585004E6BEF /* AppAccountEntity.swift in Sources */,
|
||||||
|
9FF2FB6A2BE7F84E001560CE /* SharedUtils.swift in Sources */,
|
||||||
|
9F7788CE2BE652B1004E6BEF /* LatestPostsWidget.swift in Sources */,
|
||||||
|
9F7788EE2BE78D7B004E6BEF /* TimelineFilterEntity.swift in Sources */,
|
||||||
|
9F5BE6292BF492D40074387E /* ListsWidget.swift in Sources */,
|
||||||
|
9F7788CC2BE652B1004E6BEF /* IceCubesAppWidgetsExtensionBundle.swift in Sources */,
|
||||||
|
9FF2FB702BE8AE9D001560CE /* MentionWidgetConfiguration.swift in Sources */,
|
||||||
|
9F7788D02BE652B1004E6BEF /* LatestPostsWidgetConfiguration.swift in Sources */,
|
||||||
|
9FF2FB672BE7F816001560CE /* PostsWidgetView.swift in Sources */,
|
||||||
|
9FF2FB632BE7F5D9001560CE /* HashtagPostsWidgetConfiguration.swift in Sources */,
|
||||||
|
9F5BE6282BF492D10074387E /* ListsWidgetConfiguration.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
9FAD858429743F7400496AB1 /* Sources */ = {
|
9FAD858429743F7400496AB1 /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
@ -830,6 +1066,11 @@
|
||||||
9F15D6042B3DC2180008C220 /* NavigationSheet.swift in Sources */,
|
9F15D6042B3DC2180008C220 /* NavigationSheet.swift in Sources */,
|
||||||
9FA6FD6229C04A8800E2312C /* TranslationSettingsView.swift in Sources */,
|
9FA6FD6229C04A8800E2312C /* TranslationSettingsView.swift in Sources */,
|
||||||
9F35DB4C2952005C00B3281A /* MessagesTab.swift in Sources */,
|
9F35DB4C2952005C00B3281A /* MessagesTab.swift in Sources */,
|
||||||
|
9F37BDDB2BE36E22007F28AD /* PostIntent.swift in Sources */,
|
||||||
|
9F37BDDD2BE37193007F28AD /* AppIntentService.swift in Sources */,
|
||||||
|
9F37BDE12BE38646007F28AD /* PostImageIntent.swift in Sources */,
|
||||||
|
9F37BDDF2BE37C35007F28AD /* TabIntent.swift in Sources */,
|
||||||
|
9F7788C02BE63935004E6BEF /* InlinePostIntent.swift in Sources */,
|
||||||
9FAD85CF2975B68900496AB1 /* SideBarView.swift in Sources */,
|
9FAD85CF2975B68900496AB1 /* SideBarView.swift in Sources */,
|
||||||
9FAE4ACB293783B000772766 /* SettingsTab.swift in Sources */,
|
9FAE4ACB293783B000772766 /* SettingsTab.swift in Sources */,
|
||||||
9FC14EF42B494D940006CEE1 /* RemoteTimelinesSettingView.swift in Sources */,
|
9FC14EF42B494D940006CEE1 /* RemoteTimelinesSettingView.swift in Sources */,
|
||||||
|
@ -848,9 +1089,12 @@
|
||||||
9F2B92FA295DA7D700DE16D0 /* AddAccountsView.swift in Sources */,
|
9F2B92FA295DA7D700DE16D0 /* AddAccountsView.swift in Sources */,
|
||||||
639CDF9C296AC82F00C35E58 /* SafariRouter.swift in Sources */,
|
639CDF9C296AC82F00C35E58 /* SafariRouter.swift in Sources */,
|
||||||
9F35DB4729506F6600B3281A /* NotificationTab.swift in Sources */,
|
9F35DB4729506F6600B3281A /* NotificationTab.swift in Sources */,
|
||||||
|
9F7788ED2BE78D75004E6BEF /* TimelineFilterEntity.swift in Sources */,
|
||||||
|
9F37BDE32BE393A7007F28AD /* AppShortcuts.swift in Sources */,
|
||||||
9F654BEF299AC45B00D27FA5 /* ReportView.swift in Sources */,
|
9F654BEF299AC45B00D27FA5 /* ReportView.swift in Sources */,
|
||||||
D08A9C3529956CFA00204A4A /* SwipeActionsSettingsView.swift in Sources */,
|
D08A9C3529956CFA00204A4A /* SwipeActionsSettingsView.swift in Sources */,
|
||||||
9F7335F22967608F00AFF0BA /* AddRemoteTimelineView.swift in Sources */,
|
9F7335F22967608F00AFF0BA /* AddRemoteTimelineView.swift in Sources */,
|
||||||
|
9F7788E82BE65533004E6BEF /* AppAccountEntity.swift in Sources */,
|
||||||
9FC14EF62B494DFF0006CEE1 /* RecenTagsSettingView.swift in Sources */,
|
9FC14EF62B494DFF0006CEE1 /* RecenTagsSettingView.swift in Sources */,
|
||||||
9F6028562B3F36AE00476078 /* AppView.swift in Sources */,
|
9F6028562B3F36AE00476078 /* AppView.swift in Sources */,
|
||||||
9F55C68D2955968700F94077 /* ExploreTab.swift in Sources */,
|
9F55C68D2955968700F94077 /* ExploreTab.swift in Sources */,
|
||||||
|
@ -877,6 +1121,15 @@
|
||||||
target = 9F2A5415296AB631009B2D7C /* IceCubesNotifications */;
|
target = 9F2A5415296AB631009B2D7C /* IceCubesNotifications */;
|
||||||
targetProxy = 9F2A541B296AB631009B2D7C /* PBXContainerItemProxy */;
|
targetProxy = 9F2A541B296AB631009B2D7C /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
|
9F7788D52BE652B2004E6BEF /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
platformFilters = (
|
||||||
|
ios,
|
||||||
|
maccatalyst,
|
||||||
|
);
|
||||||
|
target = 9F7788C42BE652B1004E6BEF /* IceCubesAppWidgetsExtensionExtension */;
|
||||||
|
targetProxy = 9F7788D42BE652B2004E6BEF /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
9FAD859129743F7400496AB1 /* PBXTargetDependency */ = {
|
9FAD859129743F7400496AB1 /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
platformFilters = (
|
platformFilters = (
|
||||||
|
@ -940,7 +1193,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.10.33;
|
MARKETING_VERSION = 1.10.41;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesNotifications";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesNotifications";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
@ -975,7 +1228,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.10.33;
|
MARKETING_VERSION = 1.10.41;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesNotifications";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesNotifications";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
@ -991,6 +1244,78 @@
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
9F7788D92BE652B2004E6BEF /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = IceCubesAppWidgetsExtension/IceCubesAppWidgetsExtensionExtension.entitlements;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = IceCubesAppWidgetsExtension/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = IceCubesAppWidgetsExtension;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 17.4;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesAppWidgetsExtension";
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SUPPORTS_MACCATALYST = YES;
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
9F7788DA2BE652B2004E6BEF /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = IceCubesAppWidgetsExtension/IceCubesAppWidgetsExtensionExtension.entitlements;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = IceCubesAppWidgetsExtension/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = IceCubesAppWidgetsExtension;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 17.4;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesAppWidgetsExtension";
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SUPPORTS_MACCATALYST = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
9FAD859429743F7400496AB1 /* Debug */ = {
|
9FAD859429743F7400496AB1 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
@ -1011,7 +1336,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.10.33;
|
MARKETING_VERSION = 1.10.41;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesShareExtension";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesShareExtension";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
@ -1045,7 +1370,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.10.33;
|
MARKETING_VERSION = 1.10.41;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesShareExtension";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesShareExtension";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
@ -1193,6 +1518,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||||
|
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = NO;
|
||||||
CODE_SIGN_ENTITLEMENTS = IceCubesApp/App/IceCubesApp.entitlements;
|
CODE_SIGN_ENTITLEMENTS = IceCubesApp/App/IceCubesApp.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "-";
|
CODE_SIGN_IDENTITY = "-";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||||
|
@ -1209,6 +1535,7 @@
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "Ice Cubes";
|
INFOPLIST_KEY_CFBundleDisplayName = "Ice Cubes";
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
INFOPLIST_KEY_NSCameraUsageDescription = "Upload photos & videos to attach to your Mastodon posts.";
|
INFOPLIST_KEY_NSCameraUsageDescription = "Upload photos & videos to attach to your Mastodon posts.";
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "© 2024 Thomas Ricouard";
|
||||||
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Upload photos & videos to Mastodon";
|
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Upload photos & videos to Mastodon";
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
||||||
|
@ -1225,7 +1552,7 @@
|
||||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 1.10.33;
|
MARKETING_VERSION = 1.10.41;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp";
|
||||||
PRODUCT_NAME = "Ice Cubes";
|
PRODUCT_NAME = "Ice Cubes";
|
||||||
SDKROOT = auto;
|
SDKROOT = auto;
|
||||||
|
@ -1247,6 +1574,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||||
|
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = NO;
|
||||||
CODE_SIGN_ENTITLEMENTS = "IceCubesApp/App/IceCubesApp-release.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "IceCubesApp/App/IceCubesApp-release.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "-";
|
CODE_SIGN_IDENTITY = "-";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||||
|
@ -1263,6 +1591,7 @@
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "Ice Cubes";
|
INFOPLIST_KEY_CFBundleDisplayName = "Ice Cubes";
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
INFOPLIST_KEY_NSCameraUsageDescription = "Upload photos & videos to attach to your Mastodon posts.";
|
INFOPLIST_KEY_NSCameraUsageDescription = "Upload photos & videos to attach to your Mastodon posts.";
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "© 2024 Thomas Ricouard";
|
||||||
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Upload photos & videos to Mastodon";
|
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Upload photos & videos to Mastodon";
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
||||||
|
@ -1279,7 +1608,7 @@
|
||||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 1.10.33;
|
MARKETING_VERSION = 1.10.41;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp";
|
||||||
PRODUCT_NAME = "Ice Cubes";
|
PRODUCT_NAME = "Ice Cubes";
|
||||||
SDKROOT = auto;
|
SDKROOT = auto;
|
||||||
|
@ -1314,7 +1643,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.10.33;
|
MARKETING_VERSION = 1.10.41;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesActionExtension";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesActionExtension";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
@ -1349,7 +1678,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.10.33;
|
MARKETING_VERSION = 1.10.41;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesActionExtension";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesActionExtension";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
@ -1376,6 +1705,15 @@
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
|
9F7788D82BE652B2004E6BEF /* Build configuration list for PBXNativeTarget "IceCubesAppWidgetsExtensionExtension" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
9F7788D92BE652B2004E6BEF /* Debug */,
|
||||||
|
9F7788DA2BE652B2004E6BEF /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
9FAD859329743F7400496AB1 /* Build configuration list for PBXNativeTarget "IceCubesShareExtension" */ = {
|
9FAD859329743F7400496AB1 /* Build configuration list for PBXNativeTarget "IceCubesShareExtension" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
|
@ -1415,14 +1753,6 @@
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference section */
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
9F2A540829699705009B2D7C /* XCRemoteSwiftPackageReference "purchases-ios" */ = {
|
|
||||||
isa = XCRemoteSwiftPackageReference;
|
|
||||||
repositoryURL = "https://github.com/RevenueCat/purchases-ios.git";
|
|
||||||
requirement = {
|
|
||||||
kind = upToNextMajorVersion;
|
|
||||||
minimumVersion = 4.0.0;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
9FAE4ACC29379A5A00772766 /* XCRemoteSwiftPackageReference "keychain-swift" */ = {
|
9FAE4ACC29379A5A00772766 /* XCRemoteSwiftPackageReference "keychain-swift" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/evgenyneu/keychain-swift";
|
repositoryURL = "https://github.com/evgenyneu/keychain-swift";
|
||||||
|
@ -1436,7 +1766,15 @@
|
||||||
repositoryURL = "https://github.com/Giphy/giphy-ios-sdk";
|
repositoryURL = "https://github.com/Giphy/giphy-ios-sdk";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = upToNextMajorVersion;
|
kind = upToNextMajorVersion;
|
||||||
minimumVersion = 2.2.7;
|
minimumVersion = 2.2.8;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
9FE6A42C2BD043A80055D388 /* XCRemoteSwiftPackageReference "purchases-ios" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/RevenueCat/purchases-ios";
|
||||||
|
requirement = {
|
||||||
|
kind = upToNextMajorVersion;
|
||||||
|
minimumVersion = 4.40.1;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
DA0B24F92A6876D40045BDD7 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */ = {
|
DA0B24F92A6876D40045BDD7 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */ = {
|
||||||
|
@ -1454,16 +1792,6 @@
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = Timeline;
|
productName = Timeline;
|
||||||
};
|
};
|
||||||
9F2A540929699705009B2D7C /* ReceiptParser */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
package = 9F2A540829699705009B2D7C /* XCRemoteSwiftPackageReference "purchases-ios" */;
|
|
||||||
productName = ReceiptParser;
|
|
||||||
};
|
|
||||||
9F2A540B29699705009B2D7C /* RevenueCat */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
package = 9F2A540829699705009B2D7C /* XCRemoteSwiftPackageReference "purchases-ios" */;
|
|
||||||
productName = RevenueCat;
|
|
||||||
};
|
|
||||||
9F2A5423296AB67A009B2D7C /* Env */ = {
|
9F2A5423296AB67A009B2D7C /* Env */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = Env;
|
productName = Env;
|
||||||
|
@ -1501,6 +1829,30 @@
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = Conversations;
|
productName = Conversations;
|
||||||
};
|
};
|
||||||
|
9F7788DD2BE6543D004E6BEF /* Account */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = Account;
|
||||||
|
};
|
||||||
|
9F7788DF2BE6543D004E6BEF /* AppAccount */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = AppAccount;
|
||||||
|
};
|
||||||
|
9F7788E12BE6543D004E6BEF /* Env */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = Env;
|
||||||
|
};
|
||||||
|
9F7788E32BE6543D004E6BEF /* Models */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = Models;
|
||||||
|
};
|
||||||
|
9F7788E52BE6543D004E6BEF /* Network */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = Network;
|
||||||
|
};
|
||||||
|
9F7788EF2BE78E77004E6BEF /* Timeline */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = Timeline;
|
||||||
|
};
|
||||||
9F7D93932980063100EE6B7A /* AppAccount */ = {
|
9F7D93932980063100EE6B7A /* AppAccount */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = AppAccount;
|
productName = AppAccount;
|
||||||
|
@ -1569,6 +1921,11 @@
|
||||||
package = 9FE4CCA92B4C848A00DA5F13 /* XCRemoteSwiftPackageReference "giphy-ios-sdk" */;
|
package = 9FE4CCA92B4C848A00DA5F13 /* XCRemoteSwiftPackageReference "giphy-ios-sdk" */;
|
||||||
productName = GiphyUISDK;
|
productName = GiphyUISDK;
|
||||||
};
|
};
|
||||||
|
9FE6A42D2BD043A90055D388 /* RevenueCat */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 9FE6A42C2BD043A80055D388 /* XCRemoteSwiftPackageReference "purchases-ios" */;
|
||||||
|
productName = RevenueCat;
|
||||||
|
};
|
||||||
9FFF677B299B7B2C00FE700A /* Notifications */ = {
|
9FFF677B299B7B2C00FE700A /* Notifications */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = Notifications;
|
productName = Notifications;
|
||||||
|
|
|
@ -75,10 +75,10 @@
|
||||||
{
|
{
|
||||||
"identity" : "purchases-ios",
|
"identity" : "purchases-ios",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/RevenueCat/purchases-ios.git",
|
"location" : "https://github.com/RevenueCat/purchases-ios",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "b3597e0aa5e1193b04d9e1ff04eaf5cab3b8737d",
|
"revision" : "a9763ca482d52ea3d59aa2dfd2fc23427b02dada",
|
||||||
"version" : "4.34.0"
|
"version" : "4.40.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1530"
|
||||||
|
wasCreatedForAppExtension = "YES"
|
||||||
|
version = "2.0">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES"
|
||||||
|
buildArchitectures = "Automatic">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "9F7788C42BE652B1004E6BEF"
|
||||||
|
BuildableName = "IceCubesAppWidgetsExtensionExtension.appex"
|
||||||
|
BlueprintName = "IceCubesAppWidgetsExtensionExtension"
|
||||||
|
ReferencedContainer = "container:IceCubesApp.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "9FBFE638292A715500C250E9"
|
||||||
|
BuildableName = "Ice Cubes.app"
|
||||||
|
BlueprintName = "IceCubesApp"
|
||||||
|
ReferencedContainer = "container:IceCubesApp.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
shouldAutocreateTestPlan = "YES">
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = ""
|
||||||
|
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||||
|
launchStyle = "0"
|
||||||
|
askForAppToLaunch = "Yes"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES"
|
||||||
|
launchAutomaticallySubstyle = "2">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "9FBFE638292A715500C250E9"
|
||||||
|
BuildableName = "Ice Cubes.app"
|
||||||
|
BlueprintName = "IceCubesApp"
|
||||||
|
ReferencedContainer = "container:IceCubesApp.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
<EnvironmentVariables>
|
||||||
|
<EnvironmentVariable
|
||||||
|
key = "_XCWidgetKind"
|
||||||
|
value = ""
|
||||||
|
isEnabled = "YES">
|
||||||
|
</EnvironmentVariable>
|
||||||
|
<EnvironmentVariable
|
||||||
|
key = "_XCWidgetDefaultView"
|
||||||
|
value = "timeline"
|
||||||
|
isEnabled = "YES">
|
||||||
|
</EnvironmentVariable>
|
||||||
|
<EnvironmentVariable
|
||||||
|
key = "_XCWidgetFamily"
|
||||||
|
value = "systemMedium"
|
||||||
|
isEnabled = "YES">
|
||||||
|
</EnvironmentVariable>
|
||||||
|
</EnvironmentVariables>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
askForAppToLaunch = "Yes"
|
||||||
|
launchAutomaticallySubstyle = "2">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "9FBFE638292A715500C250E9"
|
||||||
|
BuildableName = "Ice Cubes.app"
|
||||||
|
BlueprintName = "IceCubesApp"
|
||||||
|
ReferencedContainer = "container:IceCubesApp.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
|
@ -8,10 +8,10 @@ import LinkPresentation
|
||||||
import Lists
|
import Lists
|
||||||
import MediaUI
|
import MediaUI
|
||||||
import Models
|
import Models
|
||||||
|
import Notifications
|
||||||
import StatusKit
|
import StatusKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Timeline
|
import Timeline
|
||||||
import Notifications
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
extension View {
|
extension View {
|
||||||
|
@ -67,9 +67,13 @@ extension View {
|
||||||
case .notificationsRequests:
|
case .notificationsRequests:
|
||||||
NotificationsRequestsListView()
|
NotificationsRequestsListView()
|
||||||
case let .notificationForAccount(accountId):
|
case let .notificationForAccount(accountId):
|
||||||
NotificationsListView(lockedType: nil ,
|
NotificationsListView(lockedType: nil,
|
||||||
lockedAccountId: accountId,
|
lockedAccountId: accountId,
|
||||||
scrollToTopSignal: .constant(0))
|
scrollToTopSignal: .constant(0))
|
||||||
|
case .blockedAccounts:
|
||||||
|
AccountsListView(mode: .blocked)
|
||||||
|
case .mutedAccounts:
|
||||||
|
AccountsListView(mode: .muted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +85,13 @@ extension View {
|
||||||
StatusEditor.MainView(mode: .replyTo(status: status))
|
StatusEditor.MainView(mode: .replyTo(status: status))
|
||||||
.withEnvironments()
|
.withEnvironments()
|
||||||
case let .newStatusEditor(visibility):
|
case let .newStatusEditor(visibility):
|
||||||
StatusEditor.MainView(mode: .new(visibility: visibility))
|
StatusEditor.MainView(mode: .new(text: nil, visibility: visibility))
|
||||||
|
.withEnvironments()
|
||||||
|
case let .prefilledStatusEditor(text, visibility):
|
||||||
|
StatusEditor.MainView(mode: .new(text: text, visibility: visibility))
|
||||||
|
.withEnvironments()
|
||||||
|
case let .imageURL(urls, visibility):
|
||||||
|
StatusEditor.MainView(mode: .imageURL(urls: urls, visibility: visibility))
|
||||||
.withEnvironments()
|
.withEnvironments()
|
||||||
case let .editStatusEditor(status):
|
case let .editStatusEditor(status):
|
||||||
StatusEditor.MainView(mode: .edit(status: status))
|
StatusEditor.MainView(mode: .edit(status: status))
|
||||||
|
|
|
@ -54,5 +54,11 @@ extension IceCubesApp {
|
||||||
}
|
}
|
||||||
.keyboardShortcut("l", modifiers: .shift)
|
.keyboardShortcut("l", modifiers: .shift)
|
||||||
}
|
}
|
||||||
|
CommandGroup(replacing: .help) {
|
||||||
|
Button("menu.help.github") {
|
||||||
|
let url = URL(string: "https://github.com/Dimillian/IceCubesApp/issues")!
|
||||||
|
UIApplication.shared.open(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import AppIntents
|
||||||
import Env
|
import Env
|
||||||
import MediaUI
|
import MediaUI
|
||||||
import StatusKit
|
import StatusKit
|
||||||
|
@ -22,6 +23,7 @@ extension IceCubesApp {
|
||||||
.environment(theme)
|
.environment(theme)
|
||||||
.environment(watcher)
|
.environment(watcher)
|
||||||
.environment(pushNotificationsService)
|
.environment(pushNotificationsService)
|
||||||
|
.environment(appIntentService)
|
||||||
.environment(\.isSupporter, isSupporter)
|
.environment(\.isSupporter, isSupporter)
|
||||||
.sheet(item: $quickLook.selectedMediaAttachment) { selectedMediaAttachment in
|
.sheet(item: $quickLook.selectedMediaAttachment) { selectedMediaAttachment in
|
||||||
MediaUIView(selectedAttachment: selectedMediaAttachment,
|
MediaUIView(selectedAttachment: selectedMediaAttachment,
|
||||||
|
@ -47,6 +49,12 @@ extension IceCubesApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onChange(of: appIntentService.handledIntent) { _, _ in
|
||||||
|
if let intent = appIntentService.handledIntent?.intent {
|
||||||
|
handleIntent(intent)
|
||||||
|
appIntentService.handledIntent = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
.withModelContainer()
|
.withModelContainer()
|
||||||
}
|
}
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
|
@ -74,7 +82,9 @@ extension IceCubesApp {
|
||||||
Group {
|
Group {
|
||||||
switch destination.wrappedValue {
|
switch destination.wrappedValue {
|
||||||
case let .newStatusEditor(visibility):
|
case let .newStatusEditor(visibility):
|
||||||
StatusEditor.MainView(mode: .new(visibility: visibility))
|
StatusEditor.MainView(mode: .new(text: nil, visibility: visibility))
|
||||||
|
case let .prefilledStatusEditor(text, visibility):
|
||||||
|
StatusEditor.MainView(mode: .new(text: text, visibility: visibility))
|
||||||
case let .editStatusEditor(status):
|
case let .editStatusEditor(status):
|
||||||
StatusEditor.MainView(mode: .edit(status: status))
|
StatusEditor.MainView(mode: .edit(status: status))
|
||||||
case let .quoteStatusEditor(status):
|
case let .quoteStatusEditor(status):
|
||||||
|
@ -90,6 +100,7 @@ extension IceCubesApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.withEnvironments()
|
.withEnvironments()
|
||||||
|
.environment(RouterPath())
|
||||||
.withModelContainer()
|
.withModelContainer()
|
||||||
.applyTheme(theme)
|
.applyTheme(theme)
|
||||||
.frame(minWidth: 300, minHeight: 400)
|
.frame(minWidth: 300, minHeight: 400)
|
||||||
|
@ -115,4 +126,23 @@ extension IceCubesApp {
|
||||||
.defaultSize(width: 1200, height: 1000)
|
.defaultSize(width: 1200, height: 1000)
|
||||||
.windowResizability(.contentMinSize)
|
.windowResizability(.contentMinSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func handleIntent(_: any AppIntent) {
|
||||||
|
if let postIntent = appIntentService.handledIntent?.intent as? PostIntent {
|
||||||
|
#if os(visionOS) || os(macOS)
|
||||||
|
openWindow(value: WindowDestinationEditor.prefilledStatusEditor(text: postIntent.content ?? "",
|
||||||
|
visibility: userPreferences.postVisibility))
|
||||||
|
#else
|
||||||
|
appRouterPath.presentedSheet = .prefilledStatusEditor(text: postIntent.content ?? "",
|
||||||
|
visibility: userPreferences.postVisibility)
|
||||||
|
#endif
|
||||||
|
} else if let tabIntent = appIntentService.handledIntent?.intent as? TabIntent {
|
||||||
|
selectedTab = tabIntent.tab.toAppTab
|
||||||
|
} else if let imageIntent = appIntentService.handledIntent?.intent as? PostImageIntent,
|
||||||
|
let urls = imageIntent.images?.compactMap({ $0.fileURL })
|
||||||
|
{
|
||||||
|
appRouterPath.presentedSheet = .imageURL(urls: urls,
|
||||||
|
visibility: userPreferences.postVisibility)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ struct IceCubesApp: App {
|
||||||
@State var currentAccount = CurrentAccount.shared
|
@State var currentAccount = CurrentAccount.shared
|
||||||
@State var userPreferences = UserPreferences.shared
|
@State var userPreferences = UserPreferences.shared
|
||||||
@State var pushNotificationsService = PushNotificationsService.shared
|
@State var pushNotificationsService = PushNotificationsService.shared
|
||||||
|
@State var appIntentService = AppIntentService.shared
|
||||||
@State var watcher = StreamWatcher.shared
|
@State var watcher = StreamWatcher.shared
|
||||||
@State var quickLook = QuickLook.shared
|
@State var quickLook = QuickLook.shared
|
||||||
@State var theme = Theme.shared
|
@State var theme = Theme.shared
|
||||||
|
@ -79,7 +80,7 @@ struct IceCubesApp: App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppDelegate: NSObject, UIApplicationDelegate {
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
func application(_: UIApplication,
|
func application(_: UIApplication,
|
||||||
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool
|
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool
|
||||||
{
|
{
|
||||||
|
@ -113,4 +114,11 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
||||||
}
|
}
|
||||||
return configuration
|
return configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func buildMenu(with builder: UIMenuBuilder) {
|
||||||
|
super.buildMenu(with: builder)
|
||||||
|
builder.remove(menu: .document)
|
||||||
|
builder.remove(menu: .toolbar)
|
||||||
|
builder.remove(menu: .sidebar)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ extension View {
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
private struct SafariRouter: ViewModifier {
|
private struct SafariRouter: ViewModifier {
|
||||||
|
@Environment(\.isSecondaryColumn) private var isSecondaryColumn: Bool
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
@Environment(UserPreferences.self) private var preferences
|
@Environment(UserPreferences.self) private var preferences
|
||||||
@Environment(RouterPath.self) private var routerPath
|
@Environment(RouterPath.self) private var routerPath
|
||||||
|
@ -25,13 +26,15 @@ private struct SafariRouter: ViewModifier {
|
||||||
content
|
content
|
||||||
.environment(\.openURL, OpenURLAction { url in
|
.environment(\.openURL, OpenURLAction { url in
|
||||||
// Open internal URL.
|
// Open internal URL.
|
||||||
routerPath.handle(url: url)
|
guard !isSecondaryColumn else { return .discarded }
|
||||||
|
return routerPath.handle(url: url)
|
||||||
})
|
})
|
||||||
.onOpenURL { url in
|
.onOpenURL { url in
|
||||||
// Open external URL (from icecubesapp://)
|
// Open external URL (from icecubesapp://)
|
||||||
|
guard !isSecondaryColumn else { return }
|
||||||
let urlString = url.absoluteString.replacingOccurrences(of: AppInfo.scheme, with: "https://")
|
let urlString = url.absoluteString.replacingOccurrences(of: AppInfo.scheme, with: "https://")
|
||||||
guard let url = URL(string: urlString), url.host != nil else { return }
|
guard let url = URL(string: urlString), url.host != nil else { return }
|
||||||
_ = routerPath.handle(url: url)
|
_ = routerPath.handleDeepLink(url: url)
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
routerPath.urlHandler = { url in
|
routerPath.urlHandler = { url in
|
||||||
|
|
|
@ -35,15 +35,23 @@ struct SideBarView<Content: View>: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeIconForTab(tab: Tab) -> some View {
|
private func makeIconForTab(tab: Tab) -> some View {
|
||||||
ZStack(alignment: .topTrailing) {
|
HStack {
|
||||||
SideBarIcon(systemIconName: tab.iconName,
|
ZStack(alignment: .topTrailing) {
|
||||||
isSelected: tab == selectedTab)
|
SideBarIcon(systemIconName: tab.iconName,
|
||||||
let badge = badgeFor(tab: tab)
|
isSelected: tab == selectedTab)
|
||||||
if badge > 0 {
|
let badge = badgeFor(tab: tab)
|
||||||
makeBadgeView(count: badge)
|
if badge > 0 {
|
||||||
|
makeBadgeView(count: badge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if userPreferences.isSidebarExpanded {
|
||||||
|
Text(tab.title)
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundColor(tab == selectedTab ? theme.tintColor : theme.labelColor)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(width: .sidebarWidth - 24, height: 50)
|
.frame(width: (userPreferences.isSidebarExpanded ? .sidebarWidthExpanded : .sidebarWidth) - 24, height: 50)
|
||||||
.background(tab == selectedTab ? theme.secondaryBackgroundColor : .clear,
|
.background(tab == selectedTab ? theme.secondaryBackgroundColor : .clear,
|
||||||
in: RoundedRectangle(cornerRadius: 8))
|
in: RoundedRectangle(cornerRadius: 8))
|
||||||
}
|
}
|
||||||
|
@ -75,6 +83,7 @@ struct SideBarView<Content: View>: View {
|
||||||
.offset(x: 2, y: -2)
|
.offset(x: 2, y: -2)
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(.borderedProminent)
|
||||||
|
.help(Tab.post.title)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeAccountButton(account: AppAccount, showBadge: Bool) -> some View {
|
private func makeAccountButton(account: AppAccount, showBadge: Bool) -> some View {
|
||||||
|
@ -91,9 +100,19 @@ struct SideBarView<Content: View>: View {
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
ZStack(alignment: .topTrailing) {
|
ZStack(alignment: .topTrailing) {
|
||||||
AppAccountView(viewModel: .init(appAccount: account, isCompact: true),
|
if userPreferences.isSidebarExpanded {
|
||||||
isParentPresented: .constant(false))
|
AppAccountView(viewModel: .init(appAccount: account,
|
||||||
if showBadge,
|
isCompact: false,
|
||||||
|
isInSettings: false),
|
||||||
|
isParentPresented: .constant(false))
|
||||||
|
} else {
|
||||||
|
AppAccountView(viewModel: .init(appAccount: account,
|
||||||
|
isCompact: true,
|
||||||
|
isInSettings: false),
|
||||||
|
isParentPresented: .constant(false))
|
||||||
|
}
|
||||||
|
if !userPreferences.isSidebarExpanded,
|
||||||
|
showBadge,
|
||||||
let token = account.oauthToken,
|
let token = account.oauthToken,
|
||||||
let notificationsCount = userPreferences.notificationsCount[token],
|
let notificationsCount = userPreferences.notificationsCount[token],
|
||||||
notificationsCount > 0
|
notificationsCount > 0
|
||||||
|
@ -101,13 +120,23 @@ struct SideBarView<Content: View>: View {
|
||||||
makeBadgeView(count: notificationsCount)
|
makeBadgeView(count: notificationsCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.padding(.leading, userPreferences.isSidebarExpanded ? 16 : 0)
|
||||||
}
|
}
|
||||||
.frame(width: .sidebarWidth, height: 50)
|
.help(accountButtonTitle(accountName: account.accountName))
|
||||||
|
.frame(width: userPreferences.isSidebarExpanded ? .sidebarWidthExpanded : .sidebarWidth, height: 50)
|
||||||
.padding(.vertical, 8)
|
.padding(.vertical, 8)
|
||||||
.background(selectedTab == .profile && account.id == appAccounts.currentAccount.id ?
|
.background(selectedTab == .profile && account.id == appAccounts.currentAccount.id ?
|
||||||
theme.secondaryBackgroundColor : .clear)
|
theme.secondaryBackgroundColor : .clear)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func accountButtonTitle(accountName: String?) -> LocalizedStringKey {
|
||||||
|
if let accountName {
|
||||||
|
"tab.profile-account-\(accountName)"
|
||||||
|
} else {
|
||||||
|
Tab.profile.title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var tabsView: some View {
|
private var tabsView: some View {
|
||||||
ForEach(tabs) { tab in
|
ForEach(tabs) { tab in
|
||||||
if tab != .profile && sidebarTabs.isEnabled(tab) {
|
if tab != .profile && sidebarTabs.isEnabled(tab) {
|
||||||
|
@ -132,6 +161,7 @@ struct SideBarView<Content: View>: View {
|
||||||
} label: {
|
} label: {
|
||||||
makeIconForTab(tab: tab)
|
makeIconForTab(tab: tab)
|
||||||
}
|
}
|
||||||
|
.help(tab.title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,15 +185,22 @@ struct SideBarView<Content: View>: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(width: .sidebarWidth)
|
.frame(width: userPreferences.isSidebarExpanded ? .sidebarWidthExpanded : .sidebarWidth)
|
||||||
.scrollContentBackground(.hidden)
|
.scrollContentBackground(.hidden)
|
||||||
.background(.thinMaterial)
|
.background(.thinMaterial)
|
||||||
.safeAreaInset(edge: .bottom, content: {
|
.safeAreaInset(edge: .bottom, content: {
|
||||||
HStack {
|
HStack(spacing: 16) {
|
||||||
postButton
|
postButton
|
||||||
.padding(.vertical, 24)
|
.padding(.vertical, 24)
|
||||||
|
.padding(.leading, userPreferences.isSidebarExpanded ? 18 : 0)
|
||||||
|
if userPreferences.isSidebarExpanded {
|
||||||
|
Text("menu.new-post")
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(theme.labelColor)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.frame(width: .sidebarWidth)
|
.frame(width: userPreferences.isSidebarExpanded ? .sidebarWidthExpanded : .sidebarWidth)
|
||||||
.background(.thinMaterial)
|
.background(.thinMaterial)
|
||||||
})
|
})
|
||||||
Divider().edgesIgnoringSafeArea(.all)
|
Divider().edgesIgnoringSafeArea(.all)
|
||||||
|
@ -196,6 +233,7 @@ private struct SideBarIcon: View {
|
||||||
self.isHovered = isHovered
|
self.isHovered = isHovered
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.frame(width: 50, height: 40)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ struct AboutView: View {
|
||||||
Label("settings.support.terms-of-use", systemImage: "checkmark.shield")
|
Label("settings.support.terms-of-use", systemImage: "checkmark.shield")
|
||||||
}
|
}
|
||||||
} footer: {
|
} footer: {
|
||||||
Text("\(versionNumber)©2023 Thomas Ricouard")
|
Text("\(versionNumber)© 2024 Thomas Ricouard")
|
||||||
}
|
}
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
|
|
@ -29,7 +29,7 @@ struct AccountSettingsView: View {
|
||||||
Form {
|
Form {
|
||||||
Section {
|
Section {
|
||||||
Button {
|
Button {
|
||||||
routerPath.presentedSheet = .accountFiltersList
|
routerPath.presentedSheet = .accountEditInfo
|
||||||
} label: {
|
} label: {
|
||||||
Label("account.action.edit-info", systemImage: "pencil")
|
Label("account.action.edit-info", systemImage: "pencil")
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
|
@ -31,7 +31,9 @@ struct SettingsTabs: View {
|
||||||
@Binding var popToRootTab: Tab
|
@Binding var popToRootTab: Tab
|
||||||
|
|
||||||
let isModal: Bool
|
let isModal: Bool
|
||||||
|
|
||||||
|
@State private var startingPoint: SettingsStartingPoint? = nil
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack(path: $routerPath.path) {
|
NavigationStack(path: $routerPath.path) {
|
||||||
Form {
|
Form {
|
||||||
|
@ -64,6 +66,32 @@ struct SettingsTabs: View {
|
||||||
}
|
}
|
||||||
.withAppRouter()
|
.withAppRouter()
|
||||||
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
|
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
|
||||||
|
.onAppear {
|
||||||
|
startingPoint = RouterPath.settingsStartingPoint
|
||||||
|
RouterPath.settingsStartingPoint = nil
|
||||||
|
}
|
||||||
|
.navigationDestination(item: $startingPoint) { targetView in
|
||||||
|
switch targetView {
|
||||||
|
case .display:
|
||||||
|
DisplaySettingsView()
|
||||||
|
case .haptic:
|
||||||
|
HapticSettingsView()
|
||||||
|
case .remoteTimelines:
|
||||||
|
RemoteTimelinesSettingView()
|
||||||
|
case .tagGroups:
|
||||||
|
TagsGroupSettingView()
|
||||||
|
case .recentTags:
|
||||||
|
RecenTagsSettingView()
|
||||||
|
case .content:
|
||||||
|
ContentSettingsView()
|
||||||
|
case .swipeActions:
|
||||||
|
SwipeActionsSettingsView()
|
||||||
|
case .tabAndSidebarEntries:
|
||||||
|
EmptyView()
|
||||||
|
case .translation:
|
||||||
|
TranslationSettingsView()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
routerPath.client = client
|
routerPath.client = client
|
||||||
|
|
|
@ -11,16 +11,13 @@ struct TranslationSettingsView: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
deepLToggle
|
translationSelector
|
||||||
if preferences.alwaysUseDeepl {
|
if preferences.preferredTranslationType == .useDeepl {
|
||||||
Section("settings.translation.user-api-key") {
|
Section("settings.translation.user-api-key") {
|
||||||
deepLPicker
|
deepLPicker
|
||||||
SecureField("settings.translation.user-api-key", text: $apiKey)
|
SecureField("settings.translation.user-api-key", text: $apiKey)
|
||||||
.textContentType(.password)
|
.textContentType(.password)
|
||||||
}
|
}
|
||||||
.onAppear {
|
|
||||||
readValue()
|
|
||||||
}
|
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
#endif
|
#endif
|
||||||
|
@ -37,6 +34,7 @@ struct TranslationSettingsView: View {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
backgroundAPIKey
|
||||||
autoDetectSection
|
autoDetectSection
|
||||||
}
|
}
|
||||||
.navigationTitle("settings.translation.navigation-title")
|
.navigationTitle("settings.translation.navigation-title")
|
||||||
|
@ -48,19 +46,39 @@ struct TranslationSettingsView: View {
|
||||||
writeNewValue()
|
writeNewValue()
|
||||||
}
|
}
|
||||||
.onAppear(perform: updatePrefs)
|
.onAppear(perform: updatePrefs)
|
||||||
|
.onAppear(perform: readValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var deepLToggle: some View {
|
private var translationSelector: some View {
|
||||||
@Bindable var preferences = preferences
|
@Bindable var preferences = preferences
|
||||||
Toggle(isOn: $preferences.alwaysUseDeepl) {
|
Picker("Translation Service", selection: $preferences.preferredTranslationType) {
|
||||||
Text("settings.translation.always-deepl")
|
ForEach(allTTCases, id: \.self) { type in
|
||||||
|
Text(type.description).tag(type)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var allTTCases: [TranslationType] {
|
||||||
|
TranslationType.allCases.filter { type in
|
||||||
|
if type != .useApple {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
#if canImport(_Translation_SwiftUI)
|
||||||
|
if #available(iOS 17.4, *) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
return false
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var deepLPicker: some View {
|
private var deepLPicker: some View {
|
||||||
@Bindable var preferences = preferences
|
@Bindable var preferences = preferences
|
||||||
|
@ -80,6 +98,34 @@ struct TranslationSettingsView: View {
|
||||||
} footer: {
|
} footer: {
|
||||||
Text("settings.translation.auto-detect-post-language-footer")
|
Text("settings.translation.auto-detect-post-language-footer")
|
||||||
}
|
}
|
||||||
|
#if !os(visionOS)
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var backgroundAPIKey: some View {
|
||||||
|
if preferences.preferredTranslationType != .useDeepl,
|
||||||
|
!apiKey.isEmpty
|
||||||
|
{
|
||||||
|
Section {
|
||||||
|
Text("The DeepL API Key is still stored!")
|
||||||
|
if preferences.preferredTranslationType == .useServerIfPossible {
|
||||||
|
Text("It can however still be used as a fallback for your instance's translation service.")
|
||||||
|
}
|
||||||
|
Button(role: .destructive) {
|
||||||
|
withAnimation {
|
||||||
|
writeNewValue(value: "")
|
||||||
|
readValue()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Text("action.delete")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if !os(visionOS)
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func writeNewValue() {
|
private func writeNewValue() {
|
||||||
|
@ -91,11 +137,7 @@ struct TranslationSettingsView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func readValue() {
|
private func readValue() {
|
||||||
if let apiKey = DeepLUserAPIHandler.readIfAllowed() {
|
apiKey = DeepLUserAPIHandler.readKey()
|
||||||
self.apiKey = apiKey
|
|
||||||
} else {
|
|
||||||
apiKey = ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updatePrefs() {
|
private func updatePrefs() {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import Account
|
import Account
|
||||||
|
import AppIntents
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Explore
|
import Explore
|
||||||
import Foundation
|
import Foundation
|
||||||
|
@ -79,41 +80,47 @@ enum Tab: Int, Identifiable, Hashable, CaseIterable, Codable {
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var label: some View {
|
var label: some View {
|
||||||
|
if self != .other {
|
||||||
|
Label(title, systemImage: iconName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var title: LocalizedStringKey {
|
||||||
switch self {
|
switch self {
|
||||||
case .timeline:
|
case .timeline:
|
||||||
Label("tab.timeline", systemImage: iconName)
|
"tab.timeline"
|
||||||
case .trending:
|
case .trending:
|
||||||
Label("tab.trending", systemImage: iconName)
|
"tab.trending"
|
||||||
case .local:
|
case .local:
|
||||||
Label("tab.local", systemImage: iconName)
|
"tab.local"
|
||||||
case .federated:
|
case .federated:
|
||||||
Label("tab.federated", systemImage: iconName)
|
"tab.federated"
|
||||||
case .notifications:
|
case .notifications:
|
||||||
Label("tab.notifications", systemImage: iconName)
|
"tab.notifications"
|
||||||
case .mentions:
|
case .mentions:
|
||||||
Label("tab.mentions", systemImage: iconName)
|
"tab.mentions"
|
||||||
case .explore:
|
case .explore:
|
||||||
Label("tab.explore", systemImage: iconName)
|
"tab.explore"
|
||||||
case .messages:
|
case .messages:
|
||||||
Label("tab.messages", systemImage: iconName)
|
"tab.messages"
|
||||||
case .settings:
|
case .settings:
|
||||||
Label("tab.settings", systemImage: iconName)
|
"tab.settings"
|
||||||
case .profile:
|
case .profile:
|
||||||
Label("tab.profile", systemImage: iconName)
|
"tab.profile"
|
||||||
case .bookmarks:
|
case .bookmarks:
|
||||||
Label("accessibility.tabs.profile.picker.bookmarks", systemImage: iconName)
|
"accessibility.tabs.profile.picker.bookmarks"
|
||||||
case .favorites:
|
case .favorites:
|
||||||
Label("accessibility.tabs.profile.picker.favorites", systemImage: iconName)
|
"accessibility.tabs.profile.picker.favorites"
|
||||||
case .post:
|
case .post:
|
||||||
Label("menu.new-post", systemImage: iconName)
|
"menu.new-post"
|
||||||
case .followedTags:
|
case .followedTags:
|
||||||
Label("timeline.filter.tags", systemImage: iconName)
|
"timeline.filter.tags"
|
||||||
case .lists:
|
case .lists:
|
||||||
Label("timeline.filter.lists", systemImage: iconName)
|
"timeline.filter.lists"
|
||||||
case .links:
|
case .links:
|
||||||
Label("explore.section.trending.links", systemImage: iconName)
|
"explore.section.trending.links"
|
||||||
case .other:
|
case .other:
|
||||||
EmptyView()
|
""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,21 @@ struct ToolbarTab: ToolbarContent {
|
||||||
|
|
||||||
var body: some ToolbarContent {
|
var body: some ToolbarContent {
|
||||||
if !isSecondaryColumn {
|
if !isSecondaryColumn {
|
||||||
|
ToolbarItem(placement: .topBarLeading) {
|
||||||
|
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||||
|
Button {
|
||||||
|
withAnimation {
|
||||||
|
userPreferences.isSidebarExpanded.toggle()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
if userPreferences.isSidebarExpanded {
|
||||||
|
Image(systemName: "sidebar.squares.left")
|
||||||
|
} else {
|
||||||
|
Image(systemName: "sidebar.left")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
statusEditorToolbarItem(routerPath: routerPath,
|
statusEditorToolbarItem(routerPath: routerPath,
|
||||||
visibility: userPreferences.postVisibility)
|
visibility: userPreferences.postVisibility)
|
||||||
if UIDevice.current.userInterfaceIdiom != .pad ||
|
if UIDevice.current.userInterfaceIdiom != .pad ||
|
||||||
|
|
File diff suppressed because it is too large
Load diff
43
IceCubesAppIntents/AppAccountEntity.swift
Normal file
43
IceCubesAppIntents/AppAccountEntity.swift
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import AppAccount
|
||||||
|
import AppIntents
|
||||||
|
import Env
|
||||||
|
import Foundation
|
||||||
|
import Models
|
||||||
|
import Network
|
||||||
|
|
||||||
|
extension IntentDescription: @unchecked Sendable {}
|
||||||
|
extension TypeDisplayRepresentation: @unchecked Sendable {}
|
||||||
|
|
||||||
|
public struct AppAccountEntity: Identifiable, AppEntity {
|
||||||
|
public var id: String { account.id }
|
||||||
|
|
||||||
|
public let account: AppAccount
|
||||||
|
|
||||||
|
public static let defaultQuery = DefaultAppAccountEntityQuery()
|
||||||
|
|
||||||
|
public static let typeDisplayRepresentation: TypeDisplayRepresentation = "AppAccount"
|
||||||
|
|
||||||
|
public var displayRepresentation: DisplayRepresentation {
|
||||||
|
DisplayRepresentation(title: "\(account.accountName ?? account.server)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct DefaultAppAccountEntityQuery: EntityQuery {
|
||||||
|
public init() {}
|
||||||
|
|
||||||
|
public func entities(for identifiers: [AppAccountEntity.ID]) async throws -> [AppAccountEntity] {
|
||||||
|
return await AppAccountsManager.shared.availableAccounts.filter { account in
|
||||||
|
identifiers.contains { id in
|
||||||
|
id == account.id
|
||||||
|
}
|
||||||
|
}.map { AppAccountEntity(account: $0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
public func suggestedEntities() async throws -> [AppAccountEntity] {
|
||||||
|
await AppAccountsManager.shared.availableAccounts.map { .init(account: $0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
public func defaultResult() async -> AppAccountEntity? {
|
||||||
|
await .init(account: AppAccountsManager.shared.currentAccount)
|
||||||
|
}
|
||||||
|
}
|
25
IceCubesAppIntents/AppIntentService.swift
Normal file
25
IceCubesAppIntents/AppIntentService.swift
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import AppIntents
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@Observable
|
||||||
|
public class AppIntentService: @unchecked Sendable {
|
||||||
|
struct HandledIntent: Equatable {
|
||||||
|
static func == (lhs: AppIntentService.HandledIntent, rhs: AppIntentService.HandledIntent) -> Bool {
|
||||||
|
lhs.id == rhs.id
|
||||||
|
}
|
||||||
|
|
||||||
|
let id: String
|
||||||
|
let intent: any AppIntent
|
||||||
|
|
||||||
|
init(intent: any AppIntent) {
|
||||||
|
id = UUID().uuidString
|
||||||
|
self.intent = intent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static let shared = AppIntentService()
|
||||||
|
|
||||||
|
var handledIntent: HandledIntent?
|
||||||
|
|
||||||
|
private init() {}
|
||||||
|
}
|
42
IceCubesAppIntents/AppShortcuts.swift
Normal file
42
IceCubesAppIntents/AppShortcuts.swift
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import AppIntents
|
||||||
|
|
||||||
|
struct AppShortcuts: AppShortcutsProvider {
|
||||||
|
static var appShortcuts: [AppShortcut] {
|
||||||
|
AppShortcut(
|
||||||
|
intent: PostIntent(),
|
||||||
|
phrases: [
|
||||||
|
"Post \(\.$content) in \(.applicationName)",
|
||||||
|
"Post a status on Mastodon with \(.applicationName)",
|
||||||
|
],
|
||||||
|
shortTitle: "Compose a post",
|
||||||
|
systemImageName: "square.and.pencil"
|
||||||
|
)
|
||||||
|
AppShortcut(
|
||||||
|
intent: InlinePostIntent(),
|
||||||
|
phrases: [
|
||||||
|
"Write a post with \(.applicationName)",
|
||||||
|
"Send on post on Mastodon with \(.applicationName)",
|
||||||
|
],
|
||||||
|
shortTitle: "Send a post",
|
||||||
|
systemImageName: "square.and.pencil"
|
||||||
|
)
|
||||||
|
AppShortcut(
|
||||||
|
intent: TabIntent(),
|
||||||
|
phrases: [
|
||||||
|
"Open \(\.$tab) in \(.applicationName)",
|
||||||
|
"Open \(.applicationName)",
|
||||||
|
],
|
||||||
|
shortTitle: "Open Ice Cubes",
|
||||||
|
systemImageName: "cube"
|
||||||
|
)
|
||||||
|
AppShortcut(
|
||||||
|
intent: PostImageIntent(),
|
||||||
|
phrases: [
|
||||||
|
"Post images \(\.$images) in \(.applicationName)",
|
||||||
|
"Send photos \(\.$images) with \(.applicationName)",
|
||||||
|
],
|
||||||
|
shortTitle: "Post a status with an image",
|
||||||
|
systemImageName: "photo"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
61
IceCubesAppIntents/InlinePostIntent.swift
Normal file
61
IceCubesAppIntents/InlinePostIntent.swift
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import AppAccount
|
||||||
|
import AppIntents
|
||||||
|
import Env
|
||||||
|
import Foundation
|
||||||
|
import Models
|
||||||
|
import Network
|
||||||
|
|
||||||
|
enum PostVisibility: String, AppEnum {
|
||||||
|
case direct, priv, unlisted, pub
|
||||||
|
|
||||||
|
public static var caseDisplayRepresentations: [PostVisibility: DisplayRepresentation] {
|
||||||
|
[.direct: "Private",
|
||||||
|
.priv: "Followers Only",
|
||||||
|
.unlisted: "Quiet Public",
|
||||||
|
.pub: "Public"]
|
||||||
|
}
|
||||||
|
|
||||||
|
static var typeDisplayName: LocalizedStringResource { "Visibility" }
|
||||||
|
|
||||||
|
public static let typeDisplayRepresentation: TypeDisplayRepresentation = "Visibility"
|
||||||
|
|
||||||
|
var toAppVisibility: Models.Visibility {
|
||||||
|
switch self {
|
||||||
|
case .direct:
|
||||||
|
.direct
|
||||||
|
case .priv:
|
||||||
|
.priv
|
||||||
|
case .unlisted:
|
||||||
|
.unlisted
|
||||||
|
case .pub:
|
||||||
|
.pub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InlinePostIntent: AppIntent {
|
||||||
|
static let title: LocalizedStringResource = "Send post to Mastodon"
|
||||||
|
static let description: IntentDescription = "Send a text post to Mastodon with Ice Cubes"
|
||||||
|
static let openAppWhenRun: Bool = false
|
||||||
|
|
||||||
|
@Parameter(title: "Account", requestValueDialog: IntentDialog("Account"))
|
||||||
|
var account: AppAccountEntity
|
||||||
|
|
||||||
|
@Parameter(title: "Post visibility", requestValueDialog: IntentDialog("Visibility of your post"))
|
||||||
|
var visibility: PostVisibility
|
||||||
|
|
||||||
|
@Parameter(title: "Post content", requestValueDialog: IntentDialog("Content of the post to be sent to Mastodon"))
|
||||||
|
var content: String
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
func perform() async throws -> some IntentResult & ProvidesDialog & ShowsSnippetView {
|
||||||
|
let client = Client(server: account.account.server, version: .v1, oauthToken: account.account.oauthToken)
|
||||||
|
let status = StatusData(status: content, visibility: visibility.toAppVisibility)
|
||||||
|
do {
|
||||||
|
let status: Status = try await client.post(endpoint: Statuses.postStatus(json: status))
|
||||||
|
return .result(dialog: "\(status.content.asRawText) was posted on Mastodon")
|
||||||
|
} catch {
|
||||||
|
return .result(dialog: "An error occured while posting to Mastodon, please try again.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
IceCubesAppIntents/ListEntity.swift
Normal file
55
IceCubesAppIntents/ListEntity.swift
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import AppAccount
|
||||||
|
import AppIntents
|
||||||
|
import Env
|
||||||
|
import Foundation
|
||||||
|
import Models
|
||||||
|
import Network
|
||||||
|
import Timeline
|
||||||
|
|
||||||
|
public struct ListEntity: Identifiable, AppEntity {
|
||||||
|
public var id: String { list.id }
|
||||||
|
|
||||||
|
public let list: Models.List
|
||||||
|
|
||||||
|
public static let defaultQuery = DefaultListEntityQuery()
|
||||||
|
|
||||||
|
public static let typeDisplayRepresentation: TypeDisplayRepresentation = "List"
|
||||||
|
|
||||||
|
public var displayRepresentation: DisplayRepresentation {
|
||||||
|
DisplayRepresentation(title: "\(list.title)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct DefaultListEntityQuery: EntityQuery {
|
||||||
|
public init() {}
|
||||||
|
|
||||||
|
@IntentParameterDependency<ListsWidgetConfiguration>(
|
||||||
|
\.$account
|
||||||
|
)
|
||||||
|
var account
|
||||||
|
|
||||||
|
public func entities(for _: [ListEntity.ID]) async throws -> [ListEntity] {
|
||||||
|
await fetchLists().map{ .init(list: $0 )}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func suggestedEntities() async throws -> [ListEntity] {
|
||||||
|
await fetchLists().map{ .init(list: $0 )}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func defaultResult() async -> ListEntity? {
|
||||||
|
nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func fetchLists() async -> [Models.List] {
|
||||||
|
guard let account = account?.account.account else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let client = Client(server: account.server, oauthToken: account.oauthToken)
|
||||||
|
do {
|
||||||
|
let lists: [Models.List] = try await client.get(endpoint: Lists.lists)
|
||||||
|
return lists
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
IceCubesAppIntents/PostImageIntent.swift
Normal file
19
IceCubesAppIntents/PostImageIntent.swift
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import AppIntents
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct PostImageIntent: AppIntent {
|
||||||
|
static let title: LocalizedStringResource = "Post an image to Mastodon"
|
||||||
|
static let description: IntentDescription = "Use Ice Cubes to compose a post with an image to Mastodon"
|
||||||
|
static let openAppWhenRun: Bool = true
|
||||||
|
|
||||||
|
@Parameter(title: "Image",
|
||||||
|
description: "Image to post on Mastodon",
|
||||||
|
supportedTypeIdentifiers: ["public.image"],
|
||||||
|
inputConnectionBehavior: .connectToPreviousIntentResult)
|
||||||
|
var images: [IntentFile]?
|
||||||
|
|
||||||
|
func perform() async throws -> some IntentResult {
|
||||||
|
AppIntentService.shared.handledIntent = .init(intent: self)
|
||||||
|
return .result()
|
||||||
|
}
|
||||||
|
}
|
16
IceCubesAppIntents/PostIntent.swift
Normal file
16
IceCubesAppIntents/PostIntent.swift
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import AppIntents
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct PostIntent: AppIntent {
|
||||||
|
static let title: LocalizedStringResource = "Compose a post to Mastodon"
|
||||||
|
static let description: IntentDescription = "Use Ice Cubes to compose a post for Mastodon"
|
||||||
|
static let openAppWhenRun: Bool = true
|
||||||
|
|
||||||
|
@Parameter(title: "Post content", inputConnectionBehavior: .connectToPreviousIntentResult)
|
||||||
|
var content: String?
|
||||||
|
|
||||||
|
func perform() async throws -> some IntentResult {
|
||||||
|
AppIntentService.shared.handledIntent = .init(intent: self)
|
||||||
|
return .result()
|
||||||
|
}
|
||||||
|
}
|
89
IceCubesAppIntents/TabIntent.swift
Normal file
89
IceCubesAppIntents/TabIntent.swift
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import AppIntents
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum TabEnum: String, AppEnum, Sendable {
|
||||||
|
case timeline, notifications, mentions, explore, messages, settings
|
||||||
|
case trending, federated, local
|
||||||
|
case profile
|
||||||
|
case bookmarks
|
||||||
|
case favorites
|
||||||
|
case post
|
||||||
|
case followedTags
|
||||||
|
case lists
|
||||||
|
case links
|
||||||
|
|
||||||
|
static var typeDisplayName: LocalizedStringResource { "Tab" }
|
||||||
|
|
||||||
|
static let typeDisplayRepresentation: TypeDisplayRepresentation = "Tab"
|
||||||
|
|
||||||
|
nonisolated static var caseDisplayRepresentations: [TabEnum: DisplayRepresentation] {
|
||||||
|
[.timeline: .init(title: "Home Timeline"),
|
||||||
|
.trending: .init(title: "Trending Timeline"),
|
||||||
|
.federated: .init(title: "Federated Timeline"),
|
||||||
|
.local: .init(title: "Local Timeline"),
|
||||||
|
.notifications: .init(title: "Notifications"),
|
||||||
|
.mentions: .init(title: "Mentions"),
|
||||||
|
.explore: .init(title: "Explore & Trending"),
|
||||||
|
.messages: .init(title: "Private Messages"),
|
||||||
|
.settings: .init(title: "Settings"),
|
||||||
|
.profile: .init(title: "Profile"),
|
||||||
|
.bookmarks: .init(title: "Bookmarks"),
|
||||||
|
.favorites: .init(title: "Favorites"),
|
||||||
|
.followedTags: .init(title: "Followed Tags"),
|
||||||
|
.lists: .init(title: "Lists"),
|
||||||
|
.links: .init(title: "Trending Links"),
|
||||||
|
.post: .init(title: "New post")]
|
||||||
|
}
|
||||||
|
|
||||||
|
var toAppTab: Tab {
|
||||||
|
switch self {
|
||||||
|
case .timeline:
|
||||||
|
.timeline
|
||||||
|
case .notifications:
|
||||||
|
.notifications
|
||||||
|
case .mentions:
|
||||||
|
.mentions
|
||||||
|
case .explore:
|
||||||
|
.explore
|
||||||
|
case .messages:
|
||||||
|
.messages
|
||||||
|
case .settings:
|
||||||
|
.settings
|
||||||
|
case .trending:
|
||||||
|
.trending
|
||||||
|
case .federated:
|
||||||
|
.federated
|
||||||
|
case .local:
|
||||||
|
.local
|
||||||
|
case .profile:
|
||||||
|
.profile
|
||||||
|
case .bookmarks:
|
||||||
|
.bookmarks
|
||||||
|
case .favorites:
|
||||||
|
.favorites
|
||||||
|
case .post:
|
||||||
|
.post
|
||||||
|
case .followedTags:
|
||||||
|
.followedTags
|
||||||
|
case .lists:
|
||||||
|
.lists
|
||||||
|
case .links:
|
||||||
|
.links
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TabIntent: AppIntent {
|
||||||
|
static let title: LocalizedStringResource = "Open on a tab"
|
||||||
|
static let description: IntentDescription = "Open the app on a specific tab"
|
||||||
|
static let openAppWhenRun: Bool = true
|
||||||
|
|
||||||
|
@Parameter(title: "Selected tab")
|
||||||
|
var tab: TabEnum
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
func perform() async throws -> some IntentResult {
|
||||||
|
AppIntentService.shared.handledIntent = .init(intent: self)
|
||||||
|
return .result()
|
||||||
|
}
|
||||||
|
}
|
37
IceCubesAppIntents/TimelineFilterEntity.swift
Normal file
37
IceCubesAppIntents/TimelineFilterEntity.swift
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import AppAccount
|
||||||
|
import AppIntents
|
||||||
|
import Env
|
||||||
|
import Foundation
|
||||||
|
import Models
|
||||||
|
import Network
|
||||||
|
import Timeline
|
||||||
|
|
||||||
|
public struct TimelineFilterEntity: Identifiable, AppEntity {
|
||||||
|
public var id: String { timeline.id }
|
||||||
|
|
||||||
|
public let timeline: TimelineFilter
|
||||||
|
|
||||||
|
public static let defaultQuery = DefaultTimelineEntityQuery()
|
||||||
|
|
||||||
|
public static let typeDisplayRepresentation: TypeDisplayRepresentation = "TimelineFilter"
|
||||||
|
|
||||||
|
public var displayRepresentation: DisplayRepresentation {
|
||||||
|
DisplayRepresentation(title: "\(timeline.title)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct DefaultTimelineEntityQuery: EntityQuery {
|
||||||
|
public init() {}
|
||||||
|
|
||||||
|
public func entities(for _: [TimelineFilter.ID]) async throws -> [TimelineFilterEntity] {
|
||||||
|
[.home, .trending, .federated, .local].map { .init(timeline: $0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
public func suggestedEntities() async throws -> [TimelineFilterEntity] {
|
||||||
|
[.home, .trending, .federated, .local].map { .init(timeline: $0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
public func defaultResult() async -> TimelineFilterEntity? {
|
||||||
|
.init(timeline: .home)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
import DesignSystem
|
||||||
|
import Models
|
||||||
|
import Network
|
||||||
|
import SwiftUI
|
||||||
|
import Timeline
|
||||||
|
import WidgetKit
|
||||||
|
|
||||||
|
struct AccountWidgetProvider: AppIntentTimelineProvider {
|
||||||
|
func placeholder(in _: Context) -> AccountWidgetEntry {
|
||||||
|
.init(date: Date(), account: .placeholder(), avatar: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func snapshot(for configuration: AccountWidgetConfiguration, in context: Context) async -> AccountWidgetEntry {
|
||||||
|
let account = await fetchAccount(configuration: configuration)
|
||||||
|
return .init(date: Date(), account: account, avatar: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeline(for configuration: AccountWidgetConfiguration, in context: Context) async -> Timeline<AccountWidgetEntry> {
|
||||||
|
let account = await fetchAccount(configuration: configuration)
|
||||||
|
let images = try? await loadImages(urls: [account.avatar])
|
||||||
|
return .init(entries: [.init(date: Date(), account: account, avatar: images?.first?.value)],
|
||||||
|
policy: .atEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func fetchAccount(configuration: AccountWidgetConfiguration) async -> Account {
|
||||||
|
let client = Client(server: configuration.account.account.server,
|
||||||
|
oauthToken: configuration.account.account.oauthToken)
|
||||||
|
do {
|
||||||
|
let account: Account = try await client.get(endpoint: Accounts.verifyCredentials)
|
||||||
|
return account
|
||||||
|
} catch {
|
||||||
|
return .placeholder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AccountWidgetEntry: TimelineEntry {
|
||||||
|
let date: Date
|
||||||
|
let account: Account
|
||||||
|
let avatar: UIImage?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AccountWidget: Widget {
|
||||||
|
let kind: String = "AccountWidget"
|
||||||
|
|
||||||
|
var body: some WidgetConfiguration {
|
||||||
|
AppIntentConfiguration(kind: kind,
|
||||||
|
intent: AccountWidgetConfiguration.self,
|
||||||
|
provider: AccountWidgetProvider())
|
||||||
|
{ entry in
|
||||||
|
AccountWidgetView(entry: entry)
|
||||||
|
.containerBackground(Color("WidgetBackground").gradient, for: .widget)
|
||||||
|
}
|
||||||
|
.configurationDisplayName("Account")
|
||||||
|
.description("Show information about your Mastodon account")
|
||||||
|
.supportedFamilies([.systemSmall])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview(as: .systemSmall) {
|
||||||
|
AccountWidget()
|
||||||
|
} timeline: {
|
||||||
|
AccountWidgetEntry(date: Date(), account: .placeholder(), avatar: nil)
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import AppIntents
|
||||||
|
import WidgetKit
|
||||||
|
|
||||||
|
struct AccountWidgetConfiguration: WidgetConfigurationIntent {
|
||||||
|
static let title: LocalizedStringResource = "Configuration"
|
||||||
|
static let description = IntentDescription("Choose the account for this widget")
|
||||||
|
|
||||||
|
@Parameter(title: "Account")
|
||||||
|
var account: AppAccountEntity
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AccountWidgetConfiguration {
|
||||||
|
static var previewAccount: AccountWidgetConfiguration {
|
||||||
|
let intent = AccountWidgetConfiguration()
|
||||||
|
intent.account = .init(account: .init(server: "Test", accountName: "Test account"))
|
||||||
|
return intent
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
import DesignSystem
|
||||||
|
import Models
|
||||||
|
import Network
|
||||||
|
import SwiftUI
|
||||||
|
import Timeline
|
||||||
|
import WidgetKit
|
||||||
|
|
||||||
|
struct AccountWidgetView: View {
|
||||||
|
var entry: AccountWidgetProvider.Entry
|
||||||
|
|
||||||
|
@Environment(\.widgetFamily) var family
|
||||||
|
@Environment(\.redactionReasons) var redacted
|
||||||
|
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .center, spacing: 4) {
|
||||||
|
if let avatar = entry.avatar {
|
||||||
|
Image(uiImage: avatar)
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 64, height: 64)
|
||||||
|
.clipShape(Circle())
|
||||||
|
Text("\(entry.account.followersCount ?? 0)")
|
||||||
|
.font(.title)
|
||||||
|
.fontDesign(.rounded)
|
||||||
|
.fontWeight(.bold)
|
||||||
|
.monospacedDigit()
|
||||||
|
Text("Followers")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "display-p3",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.859",
|
||||||
|
"green" : "0.267",
|
||||||
|
"red" : "0.675"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "display-p3",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.949",
|
||||||
|
"green" : "0.945",
|
||||||
|
"red" : "0.941"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "display-p3",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.133",
|
||||||
|
"green" : "0.082",
|
||||||
|
"red" : "0.067"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
import DesignSystem
|
||||||
|
import Models
|
||||||
|
import Network
|
||||||
|
import SwiftUI
|
||||||
|
import Timeline
|
||||||
|
import WidgetKit
|
||||||
|
|
||||||
|
struct HashtagPostsWidgetProvider: AppIntentTimelineProvider {
|
||||||
|
func placeholder(in _: Context) -> PostsWidgetEntry {
|
||||||
|
.init(date: Date(),
|
||||||
|
title: "#Mastodon",
|
||||||
|
statuses: [.placeholder()],
|
||||||
|
images: [:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func snapshot(for configuration: HashtagPostsWidgetConfiguration, in context: Context) async -> PostsWidgetEntry {
|
||||||
|
if let entry = await timeline(for: configuration, context: context).entries.first {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
return .init(date: Date(),
|
||||||
|
title: "#Mastodon",
|
||||||
|
statuses: [],
|
||||||
|
images: [:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeline(for configuration: HashtagPostsWidgetConfiguration, in context: Context) async -> Timeline<PostsWidgetEntry> {
|
||||||
|
await timeline(for: configuration, context: context)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func timeline(for configuration: HashtagPostsWidgetConfiguration, context: Context) async -> Timeline<PostsWidgetEntry> {
|
||||||
|
do {
|
||||||
|
let timeline: TimelineFilter = .hashtag(tag: configuration.hashgtag, accountId: nil)
|
||||||
|
let statuses = await loadStatuses(for: timeline,
|
||||||
|
account: configuration.account,
|
||||||
|
widgetFamily: context.family)
|
||||||
|
let images = try await loadImages(urls: statuses.map { $0.account.avatar })
|
||||||
|
return Timeline(entries: [.init(date: Date(),
|
||||||
|
title: timeline.title,
|
||||||
|
statuses: statuses,
|
||||||
|
images: images)], policy: .atEnd)
|
||||||
|
} catch {
|
||||||
|
return Timeline(entries: [.init(date: Date(),
|
||||||
|
title: "#Mastodon",
|
||||||
|
statuses: [],
|
||||||
|
images: [:])],
|
||||||
|
policy: .atEnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HashtagPostsWidget: Widget {
|
||||||
|
let kind: String = "HashtagPostsWidget"
|
||||||
|
|
||||||
|
var body: some WidgetConfiguration {
|
||||||
|
AppIntentConfiguration(kind: kind,
|
||||||
|
intent: HashtagPostsWidgetConfiguration.self,
|
||||||
|
provider: HashtagPostsWidgetProvider())
|
||||||
|
{ entry in
|
||||||
|
PostsWidgetView(entry: entry)
|
||||||
|
.containerBackground(Color("WidgetBackground").gradient, for: .widget)
|
||||||
|
}
|
||||||
|
.configurationDisplayName("Hashtag timeline")
|
||||||
|
.description("Show the latest post for the selected hashtag")
|
||||||
|
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge, .systemExtraLarge])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview(as: .systemMedium) {
|
||||||
|
HashtagPostsWidget()
|
||||||
|
} timeline: {
|
||||||
|
PostsWidgetEntry(date: .now,
|
||||||
|
title: "#Mastodon",
|
||||||
|
statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()],
|
||||||
|
images: [:])
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
import AppIntents
|
||||||
|
import WidgetKit
|
||||||
|
|
||||||
|
struct HashtagPostsWidgetConfiguration: WidgetConfigurationIntent {
|
||||||
|
static let title: LocalizedStringResource = "Configuration"
|
||||||
|
static let description = IntentDescription("Choose the account and hashtag for this widget")
|
||||||
|
|
||||||
|
@Parameter(title: "Account")
|
||||||
|
var account: AppAccountEntity
|
||||||
|
|
||||||
|
@Parameter(title: "Hashtag")
|
||||||
|
var hashgtag: String
|
||||||
|
}
|
||||||
|
|
||||||
|
extension HashtagPostsWidgetConfiguration {
|
||||||
|
static var previewAccount: HashtagPostsWidgetConfiguration {
|
||||||
|
let intent = HashtagPostsWidgetConfiguration()
|
||||||
|
intent.account = .init(account: .init(server: "Test", accountName: "Test account"))
|
||||||
|
intent.hashgtag = "Mastodon"
|
||||||
|
return intent
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import SwiftUI
|
||||||
|
import WidgetKit
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct IceCubesAppWidgetsExtensionBundle: WidgetBundle {
|
||||||
|
var body: some Widget {
|
||||||
|
LatestPostsWidget()
|
||||||
|
HashtagPostsWidget()
|
||||||
|
ListsPostWidget()
|
||||||
|
MentionsWidget()
|
||||||
|
AccountWidget()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>keychain-access-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>$(AppIdentifierPrefix)$(BUNDLE_ID_PREFIX).IceCubesApp</string>
|
||||||
|
</array>
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.$(BUNDLE_ID_PREFIX).IceCubesApp</string>
|
||||||
|
</array>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -2,7 +2,10 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>DEEPL_SECRET</key>
|
<key>NSExtension</key>
|
||||||
<string>NICE_TRY_AGAIN</string>
|
<dict>
|
||||||
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
<string>com.apple.widgetkit-extension</string>
|
||||||
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
|
@ -0,0 +1,93 @@
|
||||||
|
import DesignSystem
|
||||||
|
import Models
|
||||||
|
import Network
|
||||||
|
import SwiftUI
|
||||||
|
import Timeline
|
||||||
|
import WidgetKit
|
||||||
|
|
||||||
|
struct LatestPostsWidgetProvider: AppIntentTimelineProvider {
|
||||||
|
func placeholder(in _: Context) -> PostsWidgetEntry {
|
||||||
|
.init(date: Date(),
|
||||||
|
title: "Home",
|
||||||
|
statuses: [.placeholder()],
|
||||||
|
images: [:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func snapshot(for configuration: LatestPostsWidgetConfiguration, in context: Context) async -> PostsWidgetEntry {
|
||||||
|
if let entry = await timeline(for: configuration, context: context).entries.first {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
return .init(date: Date(),
|
||||||
|
title: configuration.timeline.timeline.title,
|
||||||
|
statuses: [],
|
||||||
|
images: [:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeline(for configuration: LatestPostsWidgetConfiguration, in context: Context) async -> Timeline<PostsWidgetEntry> {
|
||||||
|
await timeline(for: configuration, context: context)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func timeline(for configuration: LatestPostsWidgetConfiguration, context: Context) async -> Timeline<PostsWidgetEntry> {
|
||||||
|
do {
|
||||||
|
let statuses = await loadStatuses(for: configuration.timeline.timeline,
|
||||||
|
account: configuration.account,
|
||||||
|
widgetFamily: context.family)
|
||||||
|
let images = try await loadImages(urls: statuses.map { $0.account.avatar })
|
||||||
|
return Timeline(entries: [.init(date: Date(),
|
||||||
|
title: configuration.timeline.timeline.title,
|
||||||
|
statuses: statuses,
|
||||||
|
images: images)], policy: .atEnd)
|
||||||
|
} catch {
|
||||||
|
return Timeline(entries: [.init(date: Date(),
|
||||||
|
title: configuration.timeline.timeline.title,
|
||||||
|
statuses: [],
|
||||||
|
images: [:])],
|
||||||
|
policy: .atEnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadImages(urls: [URL]) async throws -> [URL: UIImage] {
|
||||||
|
try await withThrowingTaskGroup(of: (URL, UIImage?).self) { group in
|
||||||
|
for url in urls {
|
||||||
|
group.addTask {
|
||||||
|
let response = try await URLSession.shared.data(from: url)
|
||||||
|
return (url, UIImage(data: response.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var images: [URL: UIImage] = [:]
|
||||||
|
|
||||||
|
for try await (url, image) in group {
|
||||||
|
images[url] = image
|
||||||
|
}
|
||||||
|
|
||||||
|
return images
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LatestPostsWidget: Widget {
|
||||||
|
let kind: String = "LatestPostsWidget"
|
||||||
|
|
||||||
|
var body: some WidgetConfiguration {
|
||||||
|
AppIntentConfiguration(kind: kind,
|
||||||
|
intent: LatestPostsWidgetConfiguration.self,
|
||||||
|
provider: LatestPostsWidgetProvider())
|
||||||
|
{ entry in
|
||||||
|
PostsWidgetView(entry: entry)
|
||||||
|
.containerBackground(Color("WidgetBackground").gradient, for: .widget)
|
||||||
|
}
|
||||||
|
.configurationDisplayName("Latest posts")
|
||||||
|
.description("Show the latest post for the selected timeline")
|
||||||
|
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge, .systemExtraLarge])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview(as: .systemMedium) {
|
||||||
|
LatestPostsWidget()
|
||||||
|
} timeline: {
|
||||||
|
PostsWidgetEntry(date: .now,
|
||||||
|
title: "Mastodon",
|
||||||
|
statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()],
|
||||||
|
images: [:])
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
import AppIntents
|
||||||
|
import WidgetKit
|
||||||
|
|
||||||
|
struct LatestPostsWidgetConfiguration: WidgetConfigurationIntent {
|
||||||
|
static let title: LocalizedStringResource = "Configuration"
|
||||||
|
static let description = IntentDescription("Choose the account and timeline for this widget")
|
||||||
|
|
||||||
|
@Parameter(title: "Account")
|
||||||
|
var account: AppAccountEntity
|
||||||
|
|
||||||
|
@Parameter(title: "Timeline")
|
||||||
|
var timeline: TimelineFilterEntity
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LatestPostsWidgetConfiguration {
|
||||||
|
static var previewAccount: LatestPostsWidgetConfiguration {
|
||||||
|
let intent = LatestPostsWidgetConfiguration()
|
||||||
|
intent.account = .init(account: .init(server: "Test", accountName: "Test account"))
|
||||||
|
intent.timeline = .init(timeline: .home)
|
||||||
|
return intent
|
||||||
|
}
|
||||||
|
}
|
75
IceCubesAppWidgetsExtension/ListsWidget/ListsWidget.swift
Normal file
75
IceCubesAppWidgetsExtension/ListsWidget/ListsWidget.swift
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import DesignSystem
|
||||||
|
import Models
|
||||||
|
import Network
|
||||||
|
import SwiftUI
|
||||||
|
import Timeline
|
||||||
|
import WidgetKit
|
||||||
|
|
||||||
|
struct ListsWidgetProvider: AppIntentTimelineProvider {
|
||||||
|
func placeholder(in _: Context) -> PostsWidgetEntry {
|
||||||
|
.init(date: Date(),
|
||||||
|
title: "List name",
|
||||||
|
statuses: [.placeholder()],
|
||||||
|
images: [:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func snapshot(for configuration: ListsWidgetConfiguration, in context: Context) async -> PostsWidgetEntry {
|
||||||
|
if let entry = await timeline(for: configuration, context: context).entries.first {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
return .init(date: Date(),
|
||||||
|
title: "List name",
|
||||||
|
statuses: [],
|
||||||
|
images: [:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeline(for configuration: ListsWidgetConfiguration, in context: Context) async -> Timeline<PostsWidgetEntry> {
|
||||||
|
await timeline(for: configuration, context: context)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func timeline(for configuration: ListsWidgetConfiguration, context: Context) async -> Timeline<PostsWidgetEntry> {
|
||||||
|
do {
|
||||||
|
let timeline: TimelineFilter = .list(list: configuration.timeline.list)
|
||||||
|
let statuses = await loadStatuses(for: timeline,
|
||||||
|
account: configuration.account,
|
||||||
|
widgetFamily: context.family)
|
||||||
|
let images = try await loadImages(urls: statuses.map { $0.account.avatar })
|
||||||
|
return Timeline(entries: [.init(date: Date(),
|
||||||
|
title: timeline.title,
|
||||||
|
statuses: statuses,
|
||||||
|
images: images)], policy: .atEnd)
|
||||||
|
} catch {
|
||||||
|
return Timeline(entries: [.init(date: Date(),
|
||||||
|
title: "List name",
|
||||||
|
statuses: [],
|
||||||
|
images: [:])],
|
||||||
|
policy: .atEnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ListsPostWidget: Widget {
|
||||||
|
let kind: String = "ListsPostWidget"
|
||||||
|
|
||||||
|
var body: some WidgetConfiguration {
|
||||||
|
AppIntentConfiguration(kind: kind,
|
||||||
|
intent: ListsWidgetConfiguration.self,
|
||||||
|
provider: ListsWidgetProvider())
|
||||||
|
{ entry in
|
||||||
|
PostsWidgetView(entry: entry)
|
||||||
|
.containerBackground(Color("WidgetBackground").gradient, for: .widget)
|
||||||
|
}
|
||||||
|
.configurationDisplayName("List timeline")
|
||||||
|
.description("Show the latest post for the selected list")
|
||||||
|
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge, .systemExtraLarge])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview(as: .systemMedium) {
|
||||||
|
ListsPostWidget()
|
||||||
|
} timeline: {
|
||||||
|
PostsWidgetEntry(date: .now,
|
||||||
|
title: "List name",
|
||||||
|
statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()],
|
||||||
|
images: [:])
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import AppIntents
|
||||||
|
import WidgetKit
|
||||||
|
|
||||||
|
struct ListsWidgetConfiguration: WidgetConfigurationIntent {
|
||||||
|
static let title: LocalizedStringResource = "Configuration"
|
||||||
|
static let description = IntentDescription("Choose the account and list for this widget")
|
||||||
|
|
||||||
|
@Parameter(title: "Account")
|
||||||
|
var account: AppAccountEntity
|
||||||
|
|
||||||
|
@Parameter(title: "List")
|
||||||
|
var timeline: ListEntity
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ListsWidgetConfiguration {
|
||||||
|
static var previewAccount: LatestPostsWidgetConfiguration {
|
||||||
|
let intent = LatestPostsWidgetConfiguration()
|
||||||
|
intent.account = .init(account: .init(server: "Test", accountName: "Test account"))
|
||||||
|
return intent
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
import DesignSystem
|
||||||
|
import Models
|
||||||
|
import Network
|
||||||
|
import SwiftUI
|
||||||
|
import Timeline
|
||||||
|
import WidgetKit
|
||||||
|
|
||||||
|
struct MentionsWidgetProvider: AppIntentTimelineProvider {
|
||||||
|
func placeholder(in _: Context) -> PostsWidgetEntry {
|
||||||
|
.init(date: Date(),
|
||||||
|
title: "Mentions",
|
||||||
|
statuses: [.placeholder()],
|
||||||
|
images: [:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func snapshot(for configuration: MentionsWidgetConfiguration, in context: Context) async -> PostsWidgetEntry {
|
||||||
|
if let entry = await timeline(for: configuration, context: context).entries.first {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
return .init(date: Date(),
|
||||||
|
title: "Mentions",
|
||||||
|
statuses: [],
|
||||||
|
images: [:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeline(for configuration: MentionsWidgetConfiguration, in context: Context) async -> Timeline<PostsWidgetEntry> {
|
||||||
|
await timeline(for: configuration, context: context)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func timeline(for configuration: MentionsWidgetConfiguration, context _: Context) async -> Timeline<PostsWidgetEntry> {
|
||||||
|
do {
|
||||||
|
let client = Client(server: configuration.account.account.server,
|
||||||
|
oauthToken: configuration.account.account.oauthToken)
|
||||||
|
var excludedTypes = Models.Notification.NotificationType.allCases
|
||||||
|
excludedTypes.removeAll(where: { $0 == .mention })
|
||||||
|
let notifications: [Models.Notification] =
|
||||||
|
try await client.get(endpoint: Notifications.notifications(minId: nil,
|
||||||
|
maxId: nil,
|
||||||
|
types: excludedTypes.map(\.rawValue),
|
||||||
|
limit: 5))
|
||||||
|
let statuses = notifications.compactMap { $0.status }
|
||||||
|
let images = try await loadImages(urls: statuses.map { $0.account.avatar })
|
||||||
|
return Timeline(entries: [.init(date: Date(),
|
||||||
|
title: "Mentions",
|
||||||
|
statuses: statuses,
|
||||||
|
images: images)], policy: .atEnd)
|
||||||
|
} catch {
|
||||||
|
return Timeline(entries: [.init(date: Date(),
|
||||||
|
title: "Mentions",
|
||||||
|
statuses: [],
|
||||||
|
images: [:])],
|
||||||
|
policy: .atEnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MentionsWidget: Widget {
|
||||||
|
let kind: String = "MentionsWidget"
|
||||||
|
|
||||||
|
var body: some WidgetConfiguration {
|
||||||
|
AppIntentConfiguration(kind: kind,
|
||||||
|
intent: MentionsWidgetConfiguration.self,
|
||||||
|
provider: MentionsWidgetProvider())
|
||||||
|
{ entry in
|
||||||
|
PostsWidgetView(entry: entry)
|
||||||
|
.containerBackground(Color("WidgetBackground").gradient, for: .widget)
|
||||||
|
}
|
||||||
|
.configurationDisplayName("Mentions")
|
||||||
|
.description("Show the latest mentions for the selected account.")
|
||||||
|
.supportedFamilies([.systemLarge, .systemExtraLarge])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview(as: .systemMedium) {
|
||||||
|
MentionsWidget()
|
||||||
|
} timeline: {
|
||||||
|
PostsWidgetEntry(date: .now,
|
||||||
|
title: "Mentions",
|
||||||
|
statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()],
|
||||||
|
images: [:])
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import AppIntents
|
||||||
|
import WidgetKit
|
||||||
|
|
||||||
|
struct MentionsWidgetConfiguration: WidgetConfigurationIntent {
|
||||||
|
static let title: LocalizedStringResource = "Configuration"
|
||||||
|
static let description = IntentDescription("Choose the account for this widget")
|
||||||
|
|
||||||
|
@Parameter(title: "Account")
|
||||||
|
var account: AppAccountEntity
|
||||||
|
}
|
||||||
|
|
||||||
|
extension MentionsWidgetConfiguration {
|
||||||
|
static var previewAccount: MentionsWidgetConfiguration {
|
||||||
|
let intent = MentionsWidgetConfiguration()
|
||||||
|
intent.account = .init(account: .init(server: "Test", accountName: "Test account"))
|
||||||
|
return intent
|
||||||
|
}
|
||||||
|
}
|
96
IceCubesAppWidgetsExtension/Shared/PostsWidgetView.swift
Normal file
96
IceCubesAppWidgetsExtension/Shared/PostsWidgetView.swift
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
import DesignSystem
|
||||||
|
import Models
|
||||||
|
import Network
|
||||||
|
import SwiftUI
|
||||||
|
import Timeline
|
||||||
|
import WidgetKit
|
||||||
|
|
||||||
|
struct PostsWidgetEntry: TimelineEntry {
|
||||||
|
let date: Date
|
||||||
|
let title: String
|
||||||
|
let statuses: [Status]
|
||||||
|
let images: [URL: UIImage]
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PostsWidgetView: View {
|
||||||
|
var entry: LatestPostsWidgetProvider.Entry
|
||||||
|
|
||||||
|
@Environment(\.widgetFamily) var family
|
||||||
|
@Environment(\.redactionReasons) var redacted
|
||||||
|
|
||||||
|
var contentLineLimit: Int {
|
||||||
|
switch family {
|
||||||
|
case .systemSmall, .systemMedium:
|
||||||
|
return 5
|
||||||
|
default:
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
headerView
|
||||||
|
ForEach(entry.statuses) { status in
|
||||||
|
makeStatusView(status)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var headerView: some View {
|
||||||
|
HStack {
|
||||||
|
Text(entry.title)
|
||||||
|
Spacer()
|
||||||
|
Image(systemName: "cube")
|
||||||
|
}
|
||||||
|
.font(.subheadline)
|
||||||
|
.fontWeight(.bold)
|
||||||
|
.foregroundStyle(Color("AccentColor"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func makeStatusView(_ status: Status) -> some View {
|
||||||
|
if let url = URL(string: status.url ?? "") {
|
||||||
|
Link(destination: url, label: {
|
||||||
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
makeStatusHeaderView(status)
|
||||||
|
Text(status.content.asSafeMarkdownAttributedString)
|
||||||
|
.font(.footnote)
|
||||||
|
.lineLimit(contentLineLimit)
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
.padding(.leading, 20)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func makeStatusHeaderView(_ status: Status) -> some View {
|
||||||
|
HStack(alignment: .center, spacing: 4) {
|
||||||
|
if let image = entry.images[status.account.avatar] {
|
||||||
|
Image(uiImage: image)
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 16, height: 16)
|
||||||
|
.clipShape(Circle())
|
||||||
|
} else {
|
||||||
|
Circle()
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.frame(width: 16, height: 16)
|
||||||
|
}
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Text(status.account.safeDisplayName)
|
||||||
|
.foregroundStyle(.primary)
|
||||||
|
if family != .systemSmall {
|
||||||
|
Text(" @")
|
||||||
|
.foregroundStyle(.tertiary)
|
||||||
|
Text(status.account.username)
|
||||||
|
.foregroundStyle(.tertiary)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.font(.footnote)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
IceCubesAppWidgetsExtension/Shared/SharedUtils.swift
Normal file
56
IceCubesAppWidgetsExtension/Shared/SharedUtils.swift
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import AppAccount
|
||||||
|
import Foundation
|
||||||
|
import Models
|
||||||
|
import Network
|
||||||
|
import StatusKit
|
||||||
|
import Timeline
|
||||||
|
import UIKit
|
||||||
|
import WidgetKit
|
||||||
|
|
||||||
|
func loadStatuses(for timeline: TimelineFilter,
|
||||||
|
account: AppAccountEntity,
|
||||||
|
widgetFamily: WidgetFamily) async -> [Status]
|
||||||
|
{
|
||||||
|
let client = Client(server: account.account.server, oauthToken: account.account.oauthToken)
|
||||||
|
do {
|
||||||
|
var statuses: [Status] = try await client.get(endpoint: timeline.endpoint(sinceId: nil,
|
||||||
|
maxId: nil,
|
||||||
|
minId: nil,
|
||||||
|
offset: nil))
|
||||||
|
statuses = statuses.filter { $0.reblog == nil && !$0.content.asRawText.isEmpty }
|
||||||
|
switch widgetFamily {
|
||||||
|
case .systemSmall, .systemMedium:
|
||||||
|
if statuses.count >= 1 {
|
||||||
|
statuses = statuses.prefix(upTo: 1).map { $0 }
|
||||||
|
}
|
||||||
|
case .systemLarge, .systemExtraLarge:
|
||||||
|
if statuses.count >= 5 {
|
||||||
|
statuses = statuses.prefix(upTo: 5).map { $0 }
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return statuses
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadImages(urls: [URL]) async throws -> [URL: UIImage] {
|
||||||
|
try await withThrowingTaskGroup(of: (URL, UIImage?).self) { group in
|
||||||
|
for url in urls {
|
||||||
|
group.addTask {
|
||||||
|
let response = try await URLSession.shared.data(from: url)
|
||||||
|
return (url, UIImage(data: response.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var images: [URL: UIImage] = [:]
|
||||||
|
|
||||||
|
for try await (url, image) in group {
|
||||||
|
images[url] = image
|
||||||
|
}
|
||||||
|
|
||||||
|
return images
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ public struct AccountDetailContextMenu: View {
|
||||||
@Environment(UserPreferences.self) private var preferences
|
@Environment(UserPreferences.self) private var preferences
|
||||||
|
|
||||||
@Binding var showBlockConfirmation: Bool
|
@Binding var showBlockConfirmation: Bool
|
||||||
|
@Binding var showTranslateView: Bool
|
||||||
|
|
||||||
var viewModel: AccountDetailViewModel
|
var viewModel: AccountDetailViewModel
|
||||||
|
|
||||||
|
@ -136,15 +137,15 @@ public struct AccountDetailContextMenu: View {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if let lang = preferences.serverPreferences?.postLanguage ?? Locale.current.language.languageCode?.identifier {
|
#if canImport(_Translation_SwiftUI)
|
||||||
Button {
|
if #available(iOS 17.4, *) {
|
||||||
Task {
|
Button {
|
||||||
await viewModel.translate(userLang: lang)
|
showTranslateView = true
|
||||||
|
} label: {
|
||||||
|
Label("status.action.translate", systemImage: "captions.bubble")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} label: {
|
#endif
|
||||||
Label("status.action.translate", systemImage: "captions.bubble")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if viewModel.relationship?.following == true {
|
if viewModel.relationship?.following == true {
|
||||||
Button {
|
Button {
|
||||||
|
|
|
@ -24,6 +24,7 @@ public struct AccountDetailView: View {
|
||||||
@State private var isCurrentUser: Bool = false
|
@State private var isCurrentUser: Bool = false
|
||||||
@State private var showBlockConfirmation: Bool = false
|
@State private var showBlockConfirmation: Bool = false
|
||||||
@State private var isEditingRelationshipNote: Bool = false
|
@State private var isEditingRelationshipNote: Bool = false
|
||||||
|
@State private var showTranslateView: Bool = false
|
||||||
|
|
||||||
@State private var displayTitle: Bool = false
|
@State private var displayTitle: Bool = false
|
||||||
|
|
||||||
|
@ -285,7 +286,9 @@ public struct AccountDetailView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu {
|
Menu {
|
||||||
AccountDetailContextMenu(showBlockConfirmation: $showBlockConfirmation, viewModel: viewModel)
|
AccountDetailContextMenu(showBlockConfirmation: $showBlockConfirmation,
|
||||||
|
showTranslateView: $showTranslateView,
|
||||||
|
viewModel: viewModel)
|
||||||
|
|
||||||
if !viewModel.isCurrentUser {
|
if !viewModel.isCurrentUser {
|
||||||
Button {
|
Button {
|
||||||
|
@ -327,6 +330,20 @@ public struct AccountDetailView: View {
|
||||||
if let account = viewModel.account {
|
if let account = viewModel.account {
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
|
Button {
|
||||||
|
routerPath.navigate(to: .blockedAccounts)
|
||||||
|
} label: {
|
||||||
|
Label("account.blocked", systemImage: "person.crop.circle.badge.xmark")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
routerPath.navigate(to: .mutedAccounts)
|
||||||
|
} label: {
|
||||||
|
Label("account.muted", systemImage: "person.crop.circle.badge.moon")
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
if let url = URL(string: "https://mastometrics.com/auth/login?username=\(account.acct)@\(client.server)&instance=\(client.server)&auto=true") {
|
if let url = URL(string: "https://mastometrics.com/auth/login?username=\(account.acct)@\(client.server)&instance=\(client.server)&auto=true") {
|
||||||
openURL(url)
|
openURL(url)
|
||||||
|
@ -366,6 +383,9 @@ public struct AccountDetailView: View {
|
||||||
} message: {
|
} message: {
|
||||||
Text("account.action.block-user-confirmation")
|
Text("account.action.block-user-confirmation")
|
||||||
}
|
}
|
||||||
|
#if canImport(_Translation_SwiftUI)
|
||||||
|
.addTranslateView(isPresented: $showTranslateView, text: viewModel.account?.note.asRawText ?? "")
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -273,22 +273,4 @@ import SwiftUI
|
||||||
func statusDidAppear(status _: Models.Status) {}
|
func statusDidAppear(status _: Models.Status) {}
|
||||||
|
|
||||||
func statusDidDisappear(status _: Status) {}
|
func statusDidDisappear(status _: Status) {}
|
||||||
|
|
||||||
func translate(userLang: String) async {
|
|
||||||
guard let account else { return }
|
|
||||||
withAnimation {
|
|
||||||
isLoadingTranslation = true
|
|
||||||
}
|
|
||||||
|
|
||||||
let userAPIKey = DeepLUserAPIHandler.readIfAllowed()
|
|
||||||
let userAPIFree = UserPreferences.shared.userDeeplAPIFree
|
|
||||||
let deeplClient = DeepLClient(userAPIKey: userAPIKey, userAPIFree: userAPIFree)
|
|
||||||
|
|
||||||
let translation = try? await deeplClient.request(target: userLang, text: account.note.asRawText)
|
|
||||||
|
|
||||||
withAnimation {
|
|
||||||
self.translation = translation
|
|
||||||
isLoadingTranslation = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ public struct AccountsListRow: View {
|
||||||
|
|
||||||
@State private var isEditingRelationshipNote: Bool = false
|
@State private var isEditingRelationshipNote: Bool = false
|
||||||
@State private var showBlockConfirmation: Bool = false
|
@State private var showBlockConfirmation: Bool = false
|
||||||
|
@State private var showTranslateView: Bool = false
|
||||||
|
|
||||||
let isFollowRequest: Bool
|
let isFollowRequest: Bool
|
||||||
let requestUpdated: (() -> Void)?
|
let requestUpdated: (() -> Void)?
|
||||||
|
@ -108,8 +109,13 @@ public struct AccountsListRow: View {
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
routerPath.navigate(to: .accountDetailWithAccount(account: viewModel.account))
|
routerPath.navigate(to: .accountDetailWithAccount(account: viewModel.account))
|
||||||
}
|
}
|
||||||
|
#if canImport(_Translation_SwiftUI)
|
||||||
|
.addTranslateView(isPresented: $showTranslateView, text: viewModel.account.note.asRawText)
|
||||||
|
#endif
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
AccountDetailContextMenu(showBlockConfirmation: $showBlockConfirmation, viewModel: .init(account: viewModel.account))
|
AccountDetailContextMenu(showBlockConfirmation: $showBlockConfirmation,
|
||||||
|
showTranslateView: $showTranslateView,
|
||||||
|
viewModel: .init(account: viewModel.account))
|
||||||
} preview: {
|
} preview: {
|
||||||
List {
|
List {
|
||||||
AccountDetailHeaderView(viewModel: .init(account: viewModel.account),
|
AccountDetailHeaderView(viewModel: .init(account: viewModel.account),
|
||||||
|
|
|
@ -52,6 +52,9 @@ public struct AccountsListView: View {
|
||||||
searchableList
|
searchableList
|
||||||
} else {
|
} else {
|
||||||
standardList
|
standardList
|
||||||
|
.refreshable {
|
||||||
|
await viewModel.fetch()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,16 +124,23 @@ public struct AccountsListView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Section {
|
Section {
|
||||||
ForEach(accounts) { account in
|
if accounts.isEmpty {
|
||||||
if let relationship = relationships.first(where: { $0.id == account.id }) {
|
PlaceholderView(iconName: "person.icloud",
|
||||||
AccountsListRow(viewModel: .init(account: account,
|
title: "No accounts found",
|
||||||
relationShip: relationship))
|
message: "This list of accounts is empty")
|
||||||
#if !os(visionOS)
|
.listRowSeparator(.hidden)
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
} else {
|
||||||
#endif
|
ForEach(accounts) { account in
|
||||||
|
if let relationship = relationships.first(where: { $0.id == account.id }) {
|
||||||
|
AccountsListRow(viewModel: .init(account: account,
|
||||||
|
relationShip: relationship))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if !os(visionOS)
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
#endif
|
||||||
|
|
||||||
switch nextPageState {
|
switch nextPageState {
|
||||||
case .hasNextPage:
|
case .hasNextPage:
|
||||||
|
|
|
@ -8,6 +8,7 @@ public enum AccountsListMode {
|
||||||
case following(accountId: String), followers(accountId: String)
|
case following(accountId: String), followers(accountId: String)
|
||||||
case favoritedBy(statusId: String), rebloggedBy(statusId: String)
|
case favoritedBy(statusId: String), rebloggedBy(statusId: String)
|
||||||
case accountsList(accounts: [Account])
|
case accountsList(accounts: [Account])
|
||||||
|
case blocked, muted
|
||||||
|
|
||||||
var title: LocalizedStringKey {
|
var title: LocalizedStringKey {
|
||||||
switch self {
|
switch self {
|
||||||
|
@ -21,6 +22,10 @@ public enum AccountsListMode {
|
||||||
"account.boosted-by"
|
"account.boosted-by"
|
||||||
case .accountsList:
|
case .accountsList:
|
||||||
""
|
""
|
||||||
|
case .blocked:
|
||||||
|
"account.blocked"
|
||||||
|
case .muted:
|
||||||
|
"account.muted"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,6 +89,12 @@ public enum AccountsListMode {
|
||||||
case let .accountsList(accounts):
|
case let .accountsList(accounts):
|
||||||
self.accounts = accounts
|
self.accounts = accounts
|
||||||
link = nil
|
link = nil
|
||||||
|
|
||||||
|
case .blocked:
|
||||||
|
(accounts, link) = try await client.getWithLink(endpoint: Accounts.blockList)
|
||||||
|
|
||||||
|
case .muted:
|
||||||
|
(accounts, link) = try await client.getWithLink(endpoint: Accounts.muteList)
|
||||||
}
|
}
|
||||||
nextPageId = link?.maxId
|
nextPageId = link?.maxId
|
||||||
relationships = try await client.get(endpoint:
|
relationships = try await client.get(endpoint:
|
||||||
|
@ -114,7 +125,14 @@ public enum AccountsListMode {
|
||||||
case .accountsList:
|
case .accountsList:
|
||||||
newAccounts = []
|
newAccounts = []
|
||||||
link = nil
|
link = nil
|
||||||
|
|
||||||
|
case .blocked:
|
||||||
|
(newAccounts, link) = try await client.getWithLink(endpoint: Accounts.blockList)
|
||||||
|
|
||||||
|
case .muted:
|
||||||
|
(newAccounts, link) = try await client.getWithLink(endpoint: Accounts.muteList)
|
||||||
}
|
}
|
||||||
|
|
||||||
accounts.append(contentsOf: newAccounts)
|
accounts.append(contentsOf: newAccounts)
|
||||||
let newRelationships: [Relationship] =
|
let newRelationships: [Relationship] =
|
||||||
try await client.get(endpoint: Accounts.relationships(ids: newAccounts.map(\.id)))
|
try await client.get(endpoint: Accounts.relationships(ids: newAccounts.map(\.id)))
|
||||||
|
|
|
@ -50,7 +50,7 @@ public struct AppAccountsSelectorView: View {
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $isPresented, content: {
|
.sheet(isPresented: $isPresented, content: {
|
||||||
accountsView.presentationDetents([.height(preferredHeight), .large])
|
accountsView.presentationDetents([.height(preferredHeight), .large])
|
||||||
.presentationBackground(.thinMaterial)
|
.presentationBackground(.ultraThinMaterial)
|
||||||
.presentationCornerRadius(16)
|
.presentationCornerRadius(16)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
refreshAccounts()
|
refreshAccounts()
|
||||||
|
@ -101,7 +101,7 @@ public struct AppAccountsSelectorView: View {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor.opacity(0.4))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if accountCreationEnabled {
|
if accountCreationEnabled {
|
||||||
|
@ -113,7 +113,7 @@ public struct AppAccountsSelectorView: View {
|
||||||
#if os(visionOS)
|
#if os(visionOS)
|
||||||
.foregroundStyle(theme.labelColor)
|
.foregroundStyle(theme.labelColor)
|
||||||
#else
|
#else
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor.opacity(0.4))
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,5 +9,6 @@ public extension CGFloat {
|
||||||
static let statusComponentSpacing: CGFloat = 6
|
static let statusComponentSpacing: CGFloat = 6
|
||||||
static let secondaryColumnWidth: CGFloat = 400
|
static let secondaryColumnWidth: CGFloat = 400
|
||||||
static let sidebarWidth: CGFloat = 90
|
static let sidebarWidth: CGFloat = 90
|
||||||
|
static let sidebarWidthExpanded: CGFloat = 220
|
||||||
static let pollBarHeight: CGFloat = 30
|
static let pollBarHeight: CGFloat = 30
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ public struct CloseToolbarItem: ToolbarContent {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
dismiss()
|
dismiss()
|
||||||
}, label: {
|
}, label: {
|
||||||
Image(systemName: "xmark.circle")
|
Image(systemName: "xmark.circle")
|
||||||
})
|
})
|
||||||
.keyboardShortcut(.cancelAction)
|
.keyboardShortcut(.cancelAction)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@ public struct ErrorView: View {
|
||||||
public let title: LocalizedStringKey
|
public let title: LocalizedStringKey
|
||||||
public let message: LocalizedStringKey
|
public let message: LocalizedStringKey
|
||||||
public let buttonTitle: LocalizedStringKey
|
public let buttonTitle: LocalizedStringKey
|
||||||
public let onButtonPress: (() async -> Void)
|
public let onButtonPress: () async -> Void
|
||||||
|
|
||||||
public init(title: LocalizedStringKey, message: LocalizedStringKey, buttonTitle: LocalizedStringKey, onButtonPress: @escaping (() async -> Void) ) {
|
public init(title: LocalizedStringKey, message: LocalizedStringKey, buttonTitle: LocalizedStringKey, onButtonPress: @escaping (() async -> Void)) {
|
||||||
self.title = title
|
self.title = title
|
||||||
self.message = message
|
self.message = message
|
||||||
self.buttonTitle = buttonTitle
|
self.buttonTitle = buttonTitle
|
||||||
|
|
|
@ -34,7 +34,7 @@ import Observation
|
||||||
public var isEditAltTextSupported: Bool {
|
public var isEditAltTextSupported: Bool {
|
||||||
version >= 4.1
|
version >= 4.1
|
||||||
}
|
}
|
||||||
|
|
||||||
public var isNotificationsFilterSupported: Bool {
|
public var isNotificationsFilterSupported: Bool {
|
||||||
version >= 4.3
|
version >= 4.3
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,22 +23,30 @@ public enum DeepLUserAPIHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func readIfAllowed() -> String? {
|
public static func readKeyIfAllowed() -> String? {
|
||||||
guard UserPreferences.shared.alwaysUseDeepl else { return nil }
|
guard UserPreferences.shared.preferredTranslationType == .useDeepl else { return nil }
|
||||||
|
|
||||||
return readValue()
|
return readKeyInternal()
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func readValue() -> String? {
|
public static func readKey() -> String {
|
||||||
|
return readKeyInternal() ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func readKeyInternal() -> String? {
|
||||||
keychain.synchronizable = true
|
keychain.synchronizable = true
|
||||||
return keychain.get(key)
|
return keychain.get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func deactivateToggleIfNoKey() {
|
public static func deactivateToggleIfNoKey() {
|
||||||
UserPreferences.shared.alwaysUseDeepl = shouldAlwaysUseDeepl
|
if UserPreferences.shared.preferredTranslationType == .useDeepl {
|
||||||
|
if readKeyInternal() == nil {
|
||||||
|
UserPreferences.shared.preferredTranslationType = .useServerIfPossible
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var shouldAlwaysUseDeepl: Bool {
|
public static var shouldAlwaysUseDeepl: Bool {
|
||||||
readIfAllowed() != nil
|
readKeyIfAllowed() != nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
15
Packages/Env/Sources/Env/Ext/TranslationView.swift
Normal file
15
Packages/Env/Sources/Env/Ext/TranslationView.swift
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
#if canImport(_Translation_SwiftUI)
|
||||||
|
import Translation
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
public func addTranslateView(isPresented: Binding<Bool>, text: String) -> some View {
|
||||||
|
if #available(iOS 17.4, *) {
|
||||||
|
return self.translationPresentation(isPresented: isPresented, text: text)
|
||||||
|
} else {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -25,10 +25,13 @@ public enum RouterDestination: Hashable {
|
||||||
case tagsList(tags: [Tag])
|
case tagsList(tags: [Tag])
|
||||||
case notificationsRequests
|
case notificationsRequests
|
||||||
case notificationForAccount(accountId: String)
|
case notificationForAccount(accountId: String)
|
||||||
|
case blockedAccounts
|
||||||
|
case mutedAccounts
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum WindowDestinationEditor: Hashable, Codable {
|
public enum WindowDestinationEditor: Hashable, Codable {
|
||||||
case newStatusEditor(visibility: Models.Visibility)
|
case newStatusEditor(visibility: Models.Visibility)
|
||||||
|
case prefilledStatusEditor(text: String, visibility: Models.Visibility)
|
||||||
case editStatusEditor(status: Status)
|
case editStatusEditor(status: Status)
|
||||||
case replyToStatusEditor(status: Status)
|
case replyToStatusEditor(status: Status)
|
||||||
case quoteStatusEditor(status: Status)
|
case quoteStatusEditor(status: Status)
|
||||||
|
@ -50,6 +53,8 @@ public enum SheetDestination: Identifiable, Hashable {
|
||||||
}
|
}
|
||||||
|
|
||||||
case newStatusEditor(visibility: Models.Visibility)
|
case newStatusEditor(visibility: Models.Visibility)
|
||||||
|
case prefilledStatusEditor(text: String, visibility: Models.Visibility)
|
||||||
|
case imageURL(urls: [URL], visibility: Models.Visibility)
|
||||||
case editStatusEditor(status: Status)
|
case editStatusEditor(status: Status)
|
||||||
case replyToStatusEditor(status: Status)
|
case replyToStatusEditor(status: Status)
|
||||||
case quoteStatusEditor(status: Status)
|
case quoteStatusEditor(status: Status)
|
||||||
|
@ -76,7 +81,7 @@ public enum SheetDestination: Identifiable, Hashable {
|
||||||
public var id: String {
|
public var id: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .editStatusEditor, .newStatusEditor, .replyToStatusEditor, .quoteStatusEditor,
|
case .editStatusEditor, .newStatusEditor, .replyToStatusEditor, .quoteStatusEditor,
|
||||||
.mentionStatusEditor, .quoteLinkStatusEditor:
|
.mentionStatusEditor, .quoteLinkStatusEditor, .prefilledStatusEditor, .imageURL:
|
||||||
"statusEditor"
|
"statusEditor"
|
||||||
case .listCreate:
|
case .listCreate:
|
||||||
"listCreate"
|
"listCreate"
|
||||||
|
@ -110,6 +115,18 @@ public enum SheetDestination: Identifiable, Hashable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum SettingsStartingPoint {
|
||||||
|
case display
|
||||||
|
case haptic
|
||||||
|
case remoteTimelines
|
||||||
|
case tagGroups
|
||||||
|
case recentTags
|
||||||
|
case content
|
||||||
|
case swipeActions
|
||||||
|
case tabAndSidebarEntries
|
||||||
|
case translation
|
||||||
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@Observable public class RouterPath {
|
@Observable public class RouterPath {
|
||||||
public var client: Client?
|
public var client: Client?
|
||||||
|
@ -118,6 +135,8 @@ public enum SheetDestination: Identifiable, Hashable {
|
||||||
public var path: [RouterDestination] = []
|
public var path: [RouterDestination] = []
|
||||||
public var presentedSheet: SheetDestination?
|
public var presentedSheet: SheetDestination?
|
||||||
|
|
||||||
|
public static var settingsStartingPoint: SettingsStartingPoint? = nil
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
public func navigate(to: RouterDestination) {
|
public func navigate(to: RouterDestination) {
|
||||||
|
@ -186,6 +205,57 @@ public enum SheetDestination: Identifiable, Hashable {
|
||||||
return urlHandler?(url) ?? .systemAction
|
return urlHandler?(url) ?? .systemAction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func handleDeepLink(url: URL) -> OpenURLAction.Result {
|
||||||
|
guard let client,
|
||||||
|
client.isAuth,
|
||||||
|
let id = Int(url.lastPathComponent)
|
||||||
|
else {
|
||||||
|
return urlHandler?(url) ?? .systemAction
|
||||||
|
}
|
||||||
|
// First check whether we already know that the client's server federates with the server this post is on
|
||||||
|
if client.hasConnection(with: url) {
|
||||||
|
navigateToStatus(url: url, id: id)
|
||||||
|
return .handled
|
||||||
|
}
|
||||||
|
Task {
|
||||||
|
// Client does not currently report a federation relationship, but that doesn't mean none exists
|
||||||
|
// Ensure client is aware of all peers its server federates with so it can give a meaningful answer to hasConnection(with:)
|
||||||
|
do {
|
||||||
|
let connections: [String] = try await client.get(endpoint: Instances.peers)
|
||||||
|
client.addConnections(connections)
|
||||||
|
} catch {
|
||||||
|
handlerOrDefault(url: url)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard client.hasConnection(with: url) else {
|
||||||
|
handlerOrDefault(url: url)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateToStatus(url: url, id: id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return .handled
|
||||||
|
}
|
||||||
|
|
||||||
|
private func navigateToStatus(url: URL, id: Int) {
|
||||||
|
guard let client else { return }
|
||||||
|
if url.absoluteString.contains(client.server) {
|
||||||
|
navigate(to: .statusDetail(id: String(id)))
|
||||||
|
} else {
|
||||||
|
navigate(to: .remoteStatusDetail(url: url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handlerOrDefault(url: URL) {
|
||||||
|
if let urlHandler {
|
||||||
|
_ = urlHandler(url)
|
||||||
|
} else {
|
||||||
|
UIApplication.shared.open(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func navigateToAccountFrom(acct: String, url: URL) async {
|
public func navigateToAccountFrom(acct: String, url: URL) async {
|
||||||
guard let client else { return }
|
guard let client else { return }
|
||||||
let results: SearchResults? = try? await client.get(endpoint: Search.search(query: acct,
|
let results: SearchResults? = try? await client.get(endpoint: Search.search(query: acct,
|
||||||
|
|
18
Packages/Env/Sources/Env/TranslationType.swift
Normal file
18
Packages/Env/Sources/Env/TranslationType.swift
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
public enum TranslationType: String, CaseIterable {
|
||||||
|
case useServerIfPossible
|
||||||
|
case useDeepl
|
||||||
|
case useApple
|
||||||
|
|
||||||
|
public var description: LocalizedStringKey {
|
||||||
|
switch self {
|
||||||
|
case .useServerIfPossible:
|
||||||
|
"Instance"
|
||||||
|
case .useDeepl:
|
||||||
|
"DeepL"
|
||||||
|
case .useApple:
|
||||||
|
"Apple Translate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ import SwiftUI
|
||||||
@AppStorage("app_require_alt_text") public var appRequireAltText = false
|
@AppStorage("app_require_alt_text") public var appRequireAltText = false
|
||||||
@AppStorage("autoplay_video") public var autoPlayVideo = true
|
@AppStorage("autoplay_video") public var autoPlayVideo = true
|
||||||
@AppStorage("mute_video") public var muteVideo = true
|
@AppStorage("mute_video") public var muteVideo = true
|
||||||
@AppStorage("always_use_deepl") public var alwaysUseDeepl = false
|
@AppStorage("preferred_translation_type") public var preferredTranslationType = TranslationType.useServerIfPossible
|
||||||
@AppStorage("user_deepl_api_free") public var userDeeplAPIFree = true
|
@AppStorage("user_deepl_api_free") public var userDeeplAPIFree = true
|
||||||
@AppStorage("auto_detect_post_language") public var autoDetectPostLanguage = true
|
@AppStorage("auto_detect_post_language") public var autoDetectPostLanguage = true
|
||||||
|
|
||||||
|
@ -61,7 +61,32 @@ import SwiftUI
|
||||||
|
|
||||||
@AppStorage("show_account_popover") public var showAccountPopover: Bool = true
|
@AppStorage("show_account_popover") public var showAccountPopover: Bool = true
|
||||||
|
|
||||||
init() {}
|
@AppStorage("sidebar_expanded") public var isSidebarExpanded: Bool = false
|
||||||
|
|
||||||
|
init() {
|
||||||
|
prepareTranslationType()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func prepareTranslationType() {
|
||||||
|
let sharedDefault = UserDefaults.standard
|
||||||
|
if let alwaysUseDeepl = (sharedDefault.object(forKey: "always_use_deepl") as? Bool) {
|
||||||
|
if alwaysUseDeepl {
|
||||||
|
preferredTranslationType = .useDeepl
|
||||||
|
}
|
||||||
|
sharedDefault.removeObject(forKey: "always_use_deepl")
|
||||||
|
}
|
||||||
|
#if canImport(_Translation_SwiftUI)
|
||||||
|
if #unavailable(iOS 17.4),
|
||||||
|
preferredTranslationType == .useApple
|
||||||
|
{
|
||||||
|
preferredTranslationType = .useServerIfPossible
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if preferredTranslationType == .useApple {
|
||||||
|
preferredTranslationType = .useServerIfPossible
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static let sharedDefault = UserDefaults(suiteName: "group.com.thomasricouard.IceCubesApp")
|
public static let sharedDefault = UserDefaults(suiteName: "group.com.thomasricouard.IceCubesApp")
|
||||||
|
@ -183,9 +208,9 @@ import SwiftUI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var alwaysUseDeepl: Bool {
|
public var preferredTranslationType: TranslationType {
|
||||||
didSet {
|
didSet {
|
||||||
storage.alwaysUseDeepl = alwaysUseDeepl
|
storage.preferredTranslationType = preferredTranslationType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,6 +352,12 @@ import SwiftUI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var isSidebarExpanded: Bool {
|
||||||
|
didSet {
|
||||||
|
storage.isSidebarExpanded = isSidebarExpanded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func getRealMaxIndent() -> UInt {
|
public func getRealMaxIndent() -> UInt {
|
||||||
showReplyIndentation ? maxReplyIndentation : 0
|
showReplyIndentation ? maxReplyIndentation : 0
|
||||||
}
|
}
|
||||||
|
@ -474,7 +505,7 @@ import SwiftUI
|
||||||
appDefaultPostsSensitive = storage.appDefaultPostsSensitive
|
appDefaultPostsSensitive = storage.appDefaultPostsSensitive
|
||||||
appRequireAltText = storage.appRequireAltText
|
appRequireAltText = storage.appRequireAltText
|
||||||
autoPlayVideo = storage.autoPlayVideo
|
autoPlayVideo = storage.autoPlayVideo
|
||||||
alwaysUseDeepl = storage.alwaysUseDeepl
|
preferredTranslationType = storage.preferredTranslationType
|
||||||
userDeeplAPIFree = storage.userDeeplAPIFree
|
userDeeplAPIFree = storage.userDeeplAPIFree
|
||||||
autoDetectPostLanguage = storage.autoDetectPostLanguage
|
autoDetectPostLanguage = storage.autoDetectPostLanguage
|
||||||
inAppBrowserReaderView = storage.inAppBrowserReaderView
|
inAppBrowserReaderView = storage.inAppBrowserReaderView
|
||||||
|
@ -501,6 +532,7 @@ import SwiftUI
|
||||||
showReplyIndentation = storage.showReplyIndentation
|
showReplyIndentation = storage.showReplyIndentation
|
||||||
showAccountPopover = storage.showAccountPopover
|
showAccountPopover = storage.showAccountPopover
|
||||||
muteVideo = storage.muteVideo
|
muteVideo = storage.muteVideo
|
||||||
|
isSidebarExpanded = storage.isSidebarExpanded
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ public struct ListCreateView: View {
|
||||||
.scrollContentBackground(.hidden)
|
.scrollContentBackground(.hidden)
|
||||||
.background(theme.secondaryBackgroundColor)
|
.background(theme.secondaryBackgroundColor)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
CancelToolbarItem()
|
||||||
ToolbarItem {
|
ToolbarItem {
|
||||||
Button {
|
Button {
|
||||||
Task {
|
Task {
|
||||||
|
|
|
@ -61,7 +61,7 @@ public struct HTMLString: Codable, Equatable, Hashable, @unchecked Sendable {
|
||||||
_ = text.removeLast()
|
_ = text.removeLast()
|
||||||
_ = text.removeLast()
|
_ = text.removeLast()
|
||||||
}
|
}
|
||||||
asRawText = text
|
asRawText = (try? Entities.unescape(text)) ?? text
|
||||||
|
|
||||||
if asMarkdown.hasPrefix("\n") {
|
if asMarkdown.hasPrefix("\n") {
|
||||||
_ = asMarkdown.removeFirst()
|
_ = asMarkdown.removeFirst()
|
||||||
|
@ -175,7 +175,9 @@ public struct HTMLString: Codable, Equatable, Hashable, @unchecked Sendable {
|
||||||
return
|
return
|
||||||
} else if node.nodeName() == "#text" {
|
} else if node.nodeName() == "#text" {
|
||||||
var txt = node.description
|
var txt = node.description
|
||||||
|
|
||||||
|
txt = (try? Entities.unescape(txt)) ?? txt
|
||||||
|
|
||||||
if let underscore_regex, let main_regex {
|
if let underscore_regex, let main_regex {
|
||||||
// This is the markdown escaper
|
// This is the markdown escaper
|
||||||
txt = main_regex.stringByReplacingMatches(in: txt, options: [], range: NSRange(location: 0, length: txt.count), withTemplate: "\\\\$1")
|
txt = main_regex.stringByReplacingMatches(in: txt, options: [], range: NSRange(location: 0, length: txt.count), withTemplate: "\\\\$1")
|
||||||
|
|
|
@ -6,7 +6,7 @@ public struct NotificationsPolicy: Codable, Sendable {
|
||||||
public var filterNewAccounts: Bool
|
public var filterNewAccounts: Bool
|
||||||
public var filterPrivateMentions: Bool
|
public var filterPrivateMentions: Bool
|
||||||
public let summary: Summary
|
public let summary: Summary
|
||||||
|
|
||||||
public struct Summary: Codable, Sendable {
|
public struct Summary: Codable, Sendable {
|
||||||
public let pendingRequestsCount: String
|
public let pendingRequestsCount: String
|
||||||
public let pendingNotificationsCount: String
|
public let pendingNotificationsCount: String
|
||||||
|
|
|
@ -12,20 +12,8 @@ public struct DeepLClient: Sendable {
|
||||||
"https://api\(deeplUserAPIFree && (deeplUserAPIKey != nil) ? "-free" : "").deepl.com/v2/translate"
|
"https://api\(deeplUserAPIFree && (deeplUserAPIKey != nil) ? "-free" : "").deepl.com/v2/translate"
|
||||||
}
|
}
|
||||||
|
|
||||||
private var APIKey: String {
|
|
||||||
if let deeplUserAPIKey {
|
|
||||||
return deeplUserAPIKey
|
|
||||||
}
|
|
||||||
|
|
||||||
if let path = Bundle.main.path(forResource: "Secret", ofType: "plist") {
|
|
||||||
let secret = NSDictionary(contentsOfFile: path)
|
|
||||||
return secret?["DEEPL_SECRET"] as? String ?? ""
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
private var authorizationHeaderValue: String {
|
private var authorizationHeaderValue: String {
|
||||||
"DeepL-Auth-Key \(APIKey)"
|
"DeepL-Auth-Key \(deeplUserAPIKey ?? "")"
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Response: Decodable {
|
public struct Response: Decodable {
|
||||||
|
@ -49,26 +37,22 @@ public struct DeepLClient: Sendable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func request(target: String, text: String) async throws -> Translation {
|
public func request(target: String, text: String) async throws -> Translation {
|
||||||
do {
|
var components = URLComponents(string: endpoint)!
|
||||||
var components = URLComponents(string: endpoint)!
|
var queryItems: [URLQueryItem] = []
|
||||||
var queryItems: [URLQueryItem] = []
|
queryItems.append(.init(name: "text", value: text))
|
||||||
queryItems.append(.init(name: "text", value: text))
|
queryItems.append(.init(name: "target_lang", value: target.uppercased()))
|
||||||
queryItems.append(.init(name: "target_lang", value: target.uppercased()))
|
components.queryItems = queryItems
|
||||||
components.queryItems = queryItems
|
var request = URLRequest(url: components.url!)
|
||||||
var request = URLRequest(url: components.url!)
|
request.httpMethod = "POST"
|
||||||
request.httpMethod = "POST"
|
request.setValue(authorizationHeaderValue, forHTTPHeaderField: "Authorization")
|
||||||
request.setValue(authorizationHeaderValue, forHTTPHeaderField: "Authorization")
|
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
||||||
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
let (result, _) = try await URLSession.shared.data(for: request)
|
||||||
let (result, _) = try await URLSession.shared.data(for: request)
|
let response = try decoder.decode(Response.self, from: result)
|
||||||
let response = try decoder.decode(Response.self, from: result)
|
if let translation = response.translations.first {
|
||||||
if let translation = response.translations.first {
|
return .init(content: translation.text.removingPercentEncoding ?? "",
|
||||||
return .init(content: translation.text.removingPercentEncoding ?? "",
|
detectedSourceLanguage: translation.detectedSourceLanguage,
|
||||||
detectedSourceLanguage: translation.detectedSourceLanguage,
|
provider: "DeepL.com")
|
||||||
provider: "DeepL.com")
|
|
||||||
}
|
|
||||||
throw DeepLError.notFound
|
|
||||||
} catch {
|
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
|
throw DeepLError.notFound
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@ public enum Accounts: Endpoint {
|
||||||
case mute(id: String, json: MuteData)
|
case mute(id: String, json: MuteData)
|
||||||
case unmute(id: String)
|
case unmute(id: String)
|
||||||
case relationshipNote(id: String, json: RelationshipNoteData)
|
case relationshipNote(id: String, json: RelationshipNoteData)
|
||||||
|
case blockList
|
||||||
|
case muteList
|
||||||
|
|
||||||
public func path() -> String {
|
public func path() -> String {
|
||||||
switch self {
|
switch self {
|
||||||
|
@ -81,6 +83,10 @@ public enum Accounts: Endpoint {
|
||||||
"accounts/\(id)/unmute"
|
"accounts/\(id)/unmute"
|
||||||
case let .relationshipNote(id, _):
|
case let .relationshipNote(id, _):
|
||||||
"accounts/\(id)/note"
|
"accounts/\(id)/note"
|
||||||
|
case .blockList:
|
||||||
|
"blocks"
|
||||||
|
case .muteList:
|
||||||
|
"mutes"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ public enum Notifications: Endpoint {
|
||||||
"notifications/clear"
|
"notifications/clear"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var jsonValue: (any Encodable)? {
|
public var jsonValue: (any Encodable)? {
|
||||||
switch self {
|
switch self {
|
||||||
case let .putPolicy(policy):
|
case let .putPolicy(policy):
|
||||||
|
|
|
@ -25,7 +25,7 @@ public struct OpenAIClient {
|
||||||
public let content: String
|
public let content: String
|
||||||
}
|
}
|
||||||
|
|
||||||
let model = "gpt-3.5-turbo"
|
let model = "gpt-4o"
|
||||||
let messages: [Message]
|
let messages: [Message]
|
||||||
|
|
||||||
let temperature: CGFloat
|
let temperature: CGFloat
|
||||||
|
@ -52,7 +52,7 @@ public struct OpenAIClient {
|
||||||
public let content: [MessageContent]
|
public let content: [MessageContent]
|
||||||
}
|
}
|
||||||
|
|
||||||
let model = "gpt-4-vision-preview"
|
let model = "gpt-4o"
|
||||||
let messages: [Message]
|
let messages: [Message]
|
||||||
let maxTokens = 50
|
let maxTokens = 50
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import SwiftUI
|
|
||||||
import Models
|
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Env
|
import Env
|
||||||
|
import Models
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct NotificationsHeaderFilteredView: View {
|
struct NotificationsHeaderFilteredView: View {
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
@Environment(RouterPath.self) private var routerPath
|
@Environment(RouterPath.self) private var routerPath
|
||||||
|
|
||||||
let filteredNotifications: NotificationsPolicy.Summary
|
let filteredNotifications: NotificationsPolicy.Summary
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if let count = Int(filteredNotifications.pendingNotificationsCount), count > 0 {
|
if let count = Int(filteredNotifications.pendingNotificationsCount), count > 0 {
|
||||||
HStack {
|
HStack {
|
||||||
|
|
|
@ -7,14 +7,14 @@ import SwiftUI
|
||||||
@MainActor
|
@MainActor
|
||||||
public struct NotificationsListView: View {
|
public struct NotificationsListView: View {
|
||||||
@Environment(\.scenePhase) private var scenePhase
|
@Environment(\.scenePhase) private var scenePhase
|
||||||
|
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
@Environment(StreamWatcher.self) private var watcher
|
@Environment(StreamWatcher.self) private var watcher
|
||||||
@Environment(Client.self) private var client
|
@Environment(Client.self) private var client
|
||||||
@Environment(RouterPath.self) private var routerPath
|
@Environment(RouterPath.self) private var routerPath
|
||||||
@Environment(CurrentAccount.self) private var account
|
@Environment(CurrentAccount.self) private var account
|
||||||
@Environment(CurrentInstance.self) private var currentInstance
|
@Environment(CurrentInstance.self) private var currentInstance
|
||||||
|
|
||||||
@State private var viewModel = NotificationsViewModel()
|
@State private var viewModel = NotificationsViewModel()
|
||||||
@State private var isNotificationsPolicyPresented: Bool = false
|
@State private var isNotificationsPolicyPresented: Bool = false
|
||||||
@Binding var scrollToTopSignal: Int
|
@Binding var scrollToTopSignal: Int
|
||||||
|
@ -24,7 +24,8 @@ public struct NotificationsListView: View {
|
||||||
|
|
||||||
public init(lockedType: Models.Notification.NotificationType? = nil,
|
public init(lockedType: Models.Notification.NotificationType? = nil,
|
||||||
lockedAccountId: String? = nil,
|
lockedAccountId: String? = nil,
|
||||||
scrollToTopSignal: Binding<Int>) {
|
scrollToTopSignal: Binding<Int>)
|
||||||
|
{
|
||||||
self.lockedType = lockedType
|
self.lockedType = lockedType
|
||||||
self.lockedAccountId = lockedAccountId
|
self.lockedAccountId = lockedAccountId
|
||||||
_scrollToTopSignal = scrollToTopSignal
|
_scrollToTopSignal = scrollToTopSignal
|
||||||
|
@ -113,7 +114,7 @@ public struct NotificationsListView: View {
|
||||||
.scrollContentBackground(.hidden)
|
.scrollContentBackground(.hidden)
|
||||||
.background(theme.primaryBackgroundColor)
|
.background(theme.primaryBackgroundColor)
|
||||||
#endif
|
#endif
|
||||||
.onAppear {
|
.onAppear {
|
||||||
viewModel.client = client
|
viewModel.client = client
|
||||||
viewModel.currentAccount = account
|
viewModel.currentAccount = account
|
||||||
if let lockedType {
|
if let lockedType {
|
||||||
|
|
|
@ -1,53 +1,53 @@
|
||||||
import SwiftUI
|
|
||||||
import Network
|
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Models
|
import Models
|
||||||
|
import Network
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
struct NotificationsPolicyView: View {
|
struct NotificationsPolicyView: View {
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
@Environment(Client.self) private var client
|
@Environment(Client.self) private var client
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
|
|
||||||
@State private var policy: NotificationsPolicy?
|
@State private var policy: NotificationsPolicy?
|
||||||
@State private var isUpdating: Bool = false
|
@State private var isUpdating: Bool = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
Form {
|
Form {
|
||||||
Section("notifications.content-filter.title-inline") {
|
Section("notifications.content-filter.title-inline") {
|
||||||
Toggle(isOn: .init(get: { policy?.filterNotFollowing == true },
|
Toggle(isOn: .init(get: { policy?.filterNotFollowing == true },
|
||||||
set: { newValue in
|
set: { newValue in
|
||||||
policy?.filterNotFollowing = newValue
|
policy?.filterNotFollowing = newValue
|
||||||
Task { await updatePolicy() }
|
Task { await updatePolicy() }
|
||||||
}), label: {
|
}), label: {
|
||||||
Text("notifications.content-filter.peopleYouDontFollow")
|
Text("notifications.content-filter.peopleYouDontFollow")
|
||||||
})
|
})
|
||||||
Toggle(isOn: .init(get: { policy?.filterNotFollowers == true },
|
Toggle(isOn: .init(get: { policy?.filterNotFollowers == true },
|
||||||
set: { newValue in
|
set: { newValue in
|
||||||
policy?.filterNotFollowers = newValue
|
policy?.filterNotFollowers = newValue
|
||||||
Task { await updatePolicy() }
|
Task { await updatePolicy() }
|
||||||
}), label: {
|
}), label: {
|
||||||
Text("notifications.content-filter.peopleNotFollowingYou")
|
Text("notifications.content-filter.peopleNotFollowingYou")
|
||||||
})
|
})
|
||||||
Toggle(isOn: .init(get: { policy?.filterNewAccounts == true },
|
Toggle(isOn: .init(get: { policy?.filterNewAccounts == true },
|
||||||
set: { newValue in
|
set: { newValue in
|
||||||
policy?.filterNewAccounts = newValue
|
policy?.filterNewAccounts = newValue
|
||||||
Task { await updatePolicy() }
|
Task { await updatePolicy() }
|
||||||
}), label: {
|
}), label: {
|
||||||
Text("notifications.content-filter.newAccounts")
|
Text("notifications.content-filter.newAccounts")
|
||||||
})
|
})
|
||||||
Toggle(isOn: .init(get: { policy?.filterPrivateMentions == true },
|
Toggle(isOn: .init(get: { policy?.filterPrivateMentions == true },
|
||||||
set: { newValue in
|
set: { newValue in
|
||||||
policy?.filterPrivateMentions = newValue
|
policy?.filterPrivateMentions = newValue
|
||||||
Task { await updatePolicy() }
|
Task { await updatePolicy() }
|
||||||
}), label: {
|
}), label: {
|
||||||
Text("notifications.content-filter.privateMentions")
|
Text("notifications.content-filter.privateMentions")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor.opacity(0.3))
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
.formStyle(.grouped)
|
.formStyle(.grouped)
|
||||||
|
@ -63,7 +63,7 @@ struct NotificationsPolicyView: View {
|
||||||
.presentationDetents([.medium])
|
.presentationDetents([.medium])
|
||||||
.presentationBackground(.thinMaterial)
|
.presentationBackground(.thinMaterial)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getPolicy() async {
|
private func getPolicy() async {
|
||||||
defer {
|
defer {
|
||||||
isUpdating = false
|
isUpdating = false
|
||||||
|
@ -75,7 +75,7 @@ struct NotificationsPolicyView: View {
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updatePolicy() async {
|
private func updatePolicy() async {
|
||||||
if let policy {
|
if let policy {
|
||||||
defer {
|
defer {
|
||||||
|
@ -84,7 +84,7 @@ struct NotificationsPolicyView: View {
|
||||||
do {
|
do {
|
||||||
isUpdating = true
|
isUpdating = true
|
||||||
self.policy = try await client.put(endpoint: Notifications.putPolicy(policy: policy))
|
self.policy = try await client.put(endpoint: Notifications.putPolicy(policy: policy))
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ import SwiftUI
|
||||||
private let filterKey = "notification-filter"
|
private let filterKey = "notification-filter"
|
||||||
var state: State = .loading
|
var state: State = .loading
|
||||||
var isLockedType: Bool = false
|
var isLockedType: Bool = false
|
||||||
var lockedAccountId: String? = nil
|
var lockedAccountId: String?
|
||||||
var policy: Models.NotificationsPolicy?
|
var policy: Models.NotificationsPolicy?
|
||||||
var selectedType: Models.Notification.NotificationType? {
|
var selectedType: Models.Notification.NotificationType? {
|
||||||
didSet {
|
didSet {
|
||||||
|
@ -156,7 +156,7 @@ import SwiftUI
|
||||||
let newNotifications: [Models.Notification]
|
let newNotifications: [Models.Notification]
|
||||||
if let lockedAccountId {
|
if let lockedAccountId {
|
||||||
newNotifications =
|
newNotifications =
|
||||||
try await client.get(endpoint: Notifications.notificationsForAccount(accountId: lockedAccountId, maxId: lastId))
|
try await client.get(endpoint: Notifications.notificationsForAccount(accountId: lockedAccountId, maxId: lastId))
|
||||||
} else {
|
} else {
|
||||||
newNotifications =
|
newNotifications =
|
||||||
try await client.get(endpoint: Notifications.notifications(minId: nil,
|
try await client.get(endpoint: Notifications.notifications(minId: nil,
|
||||||
|
@ -180,7 +180,7 @@ import SwiftUI
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchPolicy() async {
|
func fetchPolicy() async {
|
||||||
policy = try? await client?.get(endpoint: Notifications.policy)
|
policy = try? await client?.get(endpoint: Notifications.policy)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,31 @@
|
||||||
import SwiftUI
|
|
||||||
import Network
|
|
||||||
import Models
|
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
|
import Models
|
||||||
|
import Network
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public struct NotificationsRequestsListView: View {
|
public struct NotificationsRequestsListView: View {
|
||||||
@Environment(Client.self) private var client
|
@Environment(Client.self) private var client
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
|
|
||||||
enum ViewState {
|
enum ViewState {
|
||||||
case loading
|
case loading
|
||||||
case error
|
case error
|
||||||
case requests(_ data: [NotificationsRequest])
|
case requests(_ data: [NotificationsRequest])
|
||||||
}
|
}
|
||||||
|
|
||||||
@State private var viewState: ViewState = .loading
|
@State private var viewState: ViewState = .loading
|
||||||
|
|
||||||
public init() { }
|
public init() {}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
List {
|
List {
|
||||||
switch viewState {
|
switch viewState {
|
||||||
case .loading:
|
case .loading:
|
||||||
ProgressView()
|
ProgressView()
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
#endif
|
#endif
|
||||||
.listSectionSeparator(.hidden)
|
.listSectionSeparator(.hidden)
|
||||||
case .error:
|
case .error:
|
||||||
ErrorView(title: "notifications.error.title",
|
ErrorView(title: "notifications.error.title",
|
||||||
|
@ -33,10 +34,10 @@ public struct NotificationsRequestsListView: View {
|
||||||
{
|
{
|
||||||
await fetchRequests()
|
await fetchRequests()
|
||||||
}
|
}
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
#endif
|
#endif
|
||||||
.listSectionSeparator(.hidden)
|
.listSectionSeparator(.hidden)
|
||||||
case let .requests(data):
|
case let .requests(data):
|
||||||
ForEach(data) { request in
|
ForEach(data) { request in
|
||||||
NotificationsRequestsRowView(request: request)
|
NotificationsRequestsRowView(request: request)
|
||||||
|
@ -46,7 +47,7 @@ public struct NotificationsRequestsListView: View {
|
||||||
} label: {
|
} label: {
|
||||||
Label("account.follow-request.accept", systemImage: "checkmark")
|
Label("account.follow-request.accept", systemImage: "checkmark")
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
Task { await dismissRequest(request) }
|
Task { await dismissRequest(request) }
|
||||||
} label: {
|
} label: {
|
||||||
|
@ -59,32 +60,32 @@ public struct NotificationsRequestsListView: View {
|
||||||
}
|
}
|
||||||
.listStyle(.plain)
|
.listStyle(.plain)
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
.scrollContentBackground(.hidden)
|
.scrollContentBackground(.hidden)
|
||||||
.background(theme.primaryBackgroundColor)
|
.background(theme.primaryBackgroundColor)
|
||||||
#endif
|
#endif
|
||||||
.navigationTitle("notifications.content-filter.requests.title")
|
.navigationTitle("notifications.content-filter.requests.title")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.task {
|
.task {
|
||||||
await fetchRequests()
|
await fetchRequests()
|
||||||
}
|
}
|
||||||
.refreshable {
|
.refreshable {
|
||||||
await fetchRequests()
|
await fetchRequests()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func fetchRequests() async {
|
private func fetchRequests() async {
|
||||||
do {
|
do {
|
||||||
viewState = .requests(try await client.get(endpoint: Notifications.requests))
|
viewState = try .requests(await client.get(endpoint: Notifications.requests))
|
||||||
} catch {
|
} catch {
|
||||||
viewState = .error
|
viewState = .error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func acceptRequest(_ request: NotificationsRequest) async {
|
private func acceptRequest(_ request: NotificationsRequest) async {
|
||||||
_ = try? await client.post(endpoint: Notifications.acceptRequest(id: request.id))
|
_ = try? await client.post(endpoint: Notifications.acceptRequest(id: request.id))
|
||||||
await fetchRequests()
|
await fetchRequests()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func dismissRequest(_ request: NotificationsRequest) async {
|
private func dismissRequest(_ request: NotificationsRequest) async {
|
||||||
_ = try? await client.post(endpoint: Notifications.dismissRequest(id: request.id))
|
_ = try? await client.post(endpoint: Notifications.dismissRequest(id: request.id))
|
||||||
await fetchRequests()
|
await fetchRequests()
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import SwiftUI
|
|
||||||
import Models
|
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Env
|
import Env
|
||||||
|
import Models
|
||||||
import Network
|
import Network
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct NotificationsRequestsRowView: View {
|
struct NotificationsRequestsRowView: View {
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
@Environment(RouterPath.self) private var routerPath
|
@Environment(RouterPath.self) private var routerPath
|
||||||
@Environment(Client.self) private var client
|
@Environment(Client.self) private var client
|
||||||
|
|
||||||
let request: NotificationsRequest
|
let request: NotificationsRequest
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .center, spacing: 8) {
|
HStack(alignment: .center, spacing: 8) {
|
||||||
AvatarView(request.account.avatar, config: .embed)
|
AvatarView(request.account.avatar, config: .embed)
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
EmojiTextApp(request.account.cachedDisplayName, emojis: request.account.emojis)
|
EmojiTextApp(request.account.cachedDisplayName, emojis: request.account.emojis)
|
||||||
.font(.scaledBody)
|
.font(.scaledBody)
|
||||||
|
@ -35,14 +35,14 @@ struct NotificationsRequestsRowView: View {
|
||||||
.padding(8)
|
.padding(8)
|
||||||
.background(.secondary)
|
.background(.secondary)
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
|
|
||||||
Image(systemName: "chevron.right")
|
Image(systemName: "chevron.right")
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
routerPath.navigate(to: .notificationForAccount(accountId: request.account.id))
|
routerPath.navigate(to: .notificationForAccount(accountId: request.account.id))
|
||||||
}
|
}
|
||||||
.listRowInsets(.init(top: 12,
|
.listRowInsets(.init(top: 12,
|
||||||
leading: .layoutPadding,
|
leading: .layoutPadding,
|
||||||
bottom: 12,
|
bottom: 12,
|
||||||
trailing: .layoutPadding))
|
trailing: .layoutPadding))
|
||||||
|
@ -50,7 +50,7 @@ struct NotificationsRequestsRowView: View {
|
||||||
.listRowBackground(RoundedRectangle(cornerRadius: 8)
|
.listRowBackground(RoundedRectangle(cornerRadius: 8)
|
||||||
.foregroundStyle(.background))
|
.foregroundStyle(.background))
|
||||||
#else
|
#else
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,7 +161,7 @@ extension StatusEditor {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
// all SEVM have the same visibility value
|
// all SEVM have the same visibility value
|
||||||
followUpSEVMs.append(ViewModel(mode: .new(visibility: focusedSEVM.visibility)))
|
followUpSEVMs.append(ViewModel(mode: .new(text: nil, visibility: focusedSEVM.visibility)))
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "arrowshape.turn.up.left.circle.fill")
|
Image(systemName: "arrowshape.turn.up.left.circle.fill")
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@ extension StatusEditor {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
ForEach(viewModel.customEmojiContainer) { container in
|
LazyVGrid(columns: [GridItem(.adaptive(minimum: 40, maximum: 40))], spacing: 9) {
|
||||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 40, maximum: 40))], spacing: 9) {
|
ForEach(viewModel.customEmojiContainer) { container in
|
||||||
Section {
|
Section {
|
||||||
ForEach(container.emojis) { emoji in
|
ForEach(container.emojis) { emoji in
|
||||||
LazyImage(url: emoji.url) { state in
|
LazyImage(url: emoji.url) { state in
|
||||||
|
@ -39,15 +39,16 @@ extension StatusEditor {
|
||||||
viewModel.insertStatusText(text: " :\(emoji.shortcode): ")
|
viewModel.insertStatusText(text: " :\(emoji.shortcode): ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.padding(.horizontal, 16)
|
||||||
} header: {
|
} header: {
|
||||||
HStack {
|
Text(container.categoryName)
|
||||||
Text(container.categoryName)
|
.font(.scaledHeadline)
|
||||||
.font(.scaledFootnote)
|
.bold()
|
||||||
Spacer()
|
.foregroundStyle(Color.secondary)
|
||||||
}
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.padding(.horizontal, 16)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 8)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
|
|
@ -23,7 +23,7 @@ extension StatusEditor {
|
||||||
@State private var didAppear: Bool = false
|
@State private var didAppear: Bool = false
|
||||||
@State private var isGeneratingDescription: Bool = false
|
@State private var isGeneratingDescription: Bool = false
|
||||||
|
|
||||||
@State private var showTranslateButton: Bool = false
|
@State private var showTranslateView: Bool = false
|
||||||
@State private var isTranslating: Bool = false
|
@State private var isTranslating: Bool = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
@ -34,8 +34,14 @@ extension StatusEditor {
|
||||||
text: $imageDescription,
|
text: $imageDescription,
|
||||||
axis: .vertical)
|
axis: .vertical)
|
||||||
.focused($isFieldFocused)
|
.focused($isFieldFocused)
|
||||||
generateButton
|
if imageDescription.isEmpty {
|
||||||
translateButton
|
generateButton
|
||||||
|
}
|
||||||
|
#if canImport(_Translation_SwiftUI)
|
||||||
|
if #available(iOS 17.4, *), !imageDescription.isEmpty {
|
||||||
|
translateButton
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
Section {
|
Section {
|
||||||
|
@ -111,12 +117,6 @@ extension StatusEditor {
|
||||||
Task {
|
Task {
|
||||||
if let description = await generateDescription(url: url) {
|
if let description = await generateDescription(url: url) {
|
||||||
imageDescription = description
|
imageDescription = description
|
||||||
let lang = preferences.serverPreferences?.postLanguage ?? Locale.current.language.languageCode?.identifier
|
|
||||||
if lang != nil, lang != "en" {
|
|
||||||
withAnimation {
|
|
||||||
showTranslateButton = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
|
@ -131,24 +131,18 @@ extension StatusEditor {
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var translateButton: some View {
|
private var translateButton: some View {
|
||||||
if showTranslateButton {
|
Button {
|
||||||
Button {
|
showTranslateView = true
|
||||||
Task {
|
} label: {
|
||||||
if let description = await translateDescription() {
|
if isTranslating {
|
||||||
imageDescription = description
|
ProgressView()
|
||||||
withAnimation {
|
} else {
|
||||||
showTranslateButton = false
|
Text("status.action.translate")
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
if isTranslating {
|
|
||||||
ProgressView()
|
|
||||||
} else {
|
|
||||||
Text("status.action.translate")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if canImport(_Translation_SwiftUI)
|
||||||
|
.addTranslateView(isPresented: $showTranslateView, text: imageDescription)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private func generateDescription(url: URL) async -> String? {
|
private func generateDescription(url: URL) async -> String? {
|
||||||
|
@ -158,17 +152,5 @@ extension StatusEditor {
|
||||||
isGeneratingDescription = false
|
isGeneratingDescription = false
|
||||||
return response?.trimmedText
|
return response?.trimmedText
|
||||||
}
|
}
|
||||||
|
|
||||||
private func translateDescription() async -> String? {
|
|
||||||
isTranslating = true
|
|
||||||
let userAPIKey = DeepLUserAPIHandler.readIfAllowed()
|
|
||||||
let userAPIFree = UserPreferences.shared.userDeeplAPIFree
|
|
||||||
let deeplClient = DeepLClient(userAPIKey: userAPIKey, userAPIFree: userAPIFree)
|
|
||||||
let lang = preferences.serverPreferences?.postLanguage ?? Locale.current.language.languageCode?.identifier
|
|
||||||
guard let lang else { return nil }
|
|
||||||
let translation = try? await deeplClient.request(target: lang, text: imageDescription)
|
|
||||||
isTranslating = false
|
|
||||||
return translation?.content.asRawText
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,108 +41,110 @@ public extension StatusEditor {
|
||||||
@Bindable var focusedSEVM = focusedSEVM
|
@Bindable var focusedSEVM = focusedSEVM
|
||||||
|
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
ScrollView {
|
ZStack(alignment: .top) {
|
||||||
VStackLayout(spacing: 0) {
|
ScrollView {
|
||||||
if mainSEVM.isPosting {
|
VStackLayout(spacing: 0) {
|
||||||
ProgressView(value: mainSEVM.postingProgress, total: 100.0)
|
|
||||||
}
|
|
||||||
EditorView(
|
|
||||||
viewModel: mainSEVM,
|
|
||||||
followUpSEVMs: $followUpSEVMs,
|
|
||||||
editingMediaContainer: $editingMediaContainer,
|
|
||||||
editorFocusState: $editorFocusState,
|
|
||||||
assignedFocusState: .main,
|
|
||||||
isMain: true
|
|
||||||
)
|
|
||||||
.id(mainSEVM.id)
|
|
||||||
|
|
||||||
ForEach(followUpSEVMs) { sevm in
|
|
||||||
@Bindable var sevm: ViewModel = sevm
|
|
||||||
|
|
||||||
EditorView(
|
EditorView(
|
||||||
viewModel: sevm,
|
viewModel: mainSEVM,
|
||||||
followUpSEVMs: $followUpSEVMs,
|
followUpSEVMs: $followUpSEVMs,
|
||||||
editingMediaContainer: $editingMediaContainer,
|
editingMediaContainer: $editingMediaContainer,
|
||||||
editorFocusState: $editorFocusState,
|
editorFocusState: $editorFocusState,
|
||||||
assignedFocusState: .followUp(index: sevm.id),
|
assignedFocusState: .main,
|
||||||
isMain: false
|
isMain: true
|
||||||
)
|
)
|
||||||
.id(sevm.id)
|
.id(mainSEVM.id)
|
||||||
}
|
|
||||||
}
|
ForEach(followUpSEVMs) { sevm in
|
||||||
.scrollTargetLayout()
|
@Bindable var sevm: ViewModel = sevm
|
||||||
}
|
|
||||||
.scrollPosition(id: $scrollID, anchor: .top)
|
EditorView(
|
||||||
.animation(.bouncy(duration: 0.3), value: editorFocusState)
|
viewModel: sevm,
|
||||||
.animation(.bouncy(duration: 0.3), value: followUpSEVMs)
|
followUpSEVMs: $followUpSEVMs,
|
||||||
#if !os(visionOS)
|
editingMediaContainer: $editingMediaContainer,
|
||||||
.background(theme.primaryBackgroundColor)
|
editorFocusState: $editorFocusState,
|
||||||
#endif
|
assignedFocusState: .followUp(index: sevm.id),
|
||||||
.safeAreaInset(edge: .bottom) {
|
isMain: false
|
||||||
AutoCompleteView(viewModel: focusedSEVM)
|
)
|
||||||
}
|
.id(sevm.id)
|
||||||
#if os(visionOS)
|
|
||||||
.ornament(attachmentAnchor: .scene(.leading)) {
|
|
||||||
AccessoryView(focusedSEVM: focusedSEVM,
|
|
||||||
followUpSEVMs: $followUpSEVMs)
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
.safeAreaInset(edge: .bottom) {
|
|
||||||
if presentationDetent == .large || presentationDetent == .medium {
|
|
||||||
AccessoryView(focusedSEVM: focusedSEVM,
|
|
||||||
followUpSEVMs: $followUpSEVMs)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
.scrollTargetLayout()
|
||||||
.accessibilitySortPriority(1) // Ensure that all elements inside the `ScrollView` occur earlier than the accessory views
|
}
|
||||||
.navigationTitle(focusedSEVM.mode.title)
|
.scrollPosition(id: $scrollID, anchor: .top)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.animation(.bouncy(duration: 0.3), value: editorFocusState)
|
||||||
.toolbar { ToolbarItems(mainSEVM: mainSEVM,
|
.animation(.bouncy(duration: 0.3), value: followUpSEVMs)
|
||||||
focusedSEVM: focusedSEVM,
|
#if !os(visionOS)
|
||||||
followUpSEVMs: followUpSEVMs) }
|
.background(theme.primaryBackgroundColor)
|
||||||
.toolbarBackground(.visible, for: .navigationBar)
|
#endif
|
||||||
.alert(
|
.safeAreaInset(edge: .bottom) {
|
||||||
"status.error.posting.title",
|
AutoCompleteView(viewModel: focusedSEVM)
|
||||||
isPresented: $focusedSEVM.showPostingErrorAlert,
|
}
|
||||||
actions: {
|
#if os(visionOS)
|
||||||
Button("OK") {}
|
.ornament(attachmentAnchor: .scene(.leading)) {
|
||||||
}, message: {
|
AccessoryView(focusedSEVM: focusedSEVM,
|
||||||
Text(mainSEVM.postingError ?? "")
|
followUpSEVMs: $followUpSEVMs)
|
||||||
}
|
}
|
||||||
)
|
#else
|
||||||
.interactiveDismissDisabled(mainSEVM.shouldDisplayDismissWarning)
|
.safeAreaInset(edge: .bottom) {
|
||||||
.onChange(of: appAccounts.currentClient) { _, newValue in
|
if presentationDetent == .large || presentationDetent == .medium {
|
||||||
if mainSEVM.mode.isInShareExtension {
|
AccessoryView(focusedSEVM: focusedSEVM,
|
||||||
currentAccount.setClient(client: newValue)
|
followUpSEVMs: $followUpSEVMs)
|
||||||
mainSEVM.client = newValue
|
|
||||||
for post in followUpSEVMs {
|
|
||||||
post.client = newValue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
#endif
|
||||||
.onDrop(of: [.image, .video, .gif, .mpeg4Movie, .quickTimeMovie, .movie],
|
.accessibilitySortPriority(1) // Ensure that all elements inside the `ScrollView` occur earlier than the accessory views
|
||||||
delegate: focusedSEVM)
|
.navigationTitle(focusedSEVM.mode.title)
|
||||||
.onChange(of: currentAccount.account?.id) {
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
mainSEVM.currentAccount = currentAccount.account
|
.toolbar { ToolbarItems(mainSEVM: mainSEVM,
|
||||||
for p in followUpSEVMs {
|
focusedSEVM: focusedSEVM,
|
||||||
p.currentAccount = mainSEVM.currentAccount
|
followUpSEVMs: followUpSEVMs) }
|
||||||
}
|
.toolbarBackground(.visible, for: .navigationBar)
|
||||||
}
|
.alert(
|
||||||
.onChange(of: mainSEVM.visibility) {
|
"status.error.posting.title",
|
||||||
for p in followUpSEVMs {
|
isPresented: $focusedSEVM.showPostingErrorAlert,
|
||||||
p.visibility = mainSEVM.visibility
|
actions: {
|
||||||
}
|
Button("OK") {}
|
||||||
}
|
}, message: {
|
||||||
.onChange(of: followUpSEVMs.count) { oldValue, newValue in
|
Text(mainSEVM.postingError ?? "")
|
||||||
if oldValue < newValue {
|
}
|
||||||
Task {
|
)
|
||||||
try? await Task.sleep(for: .seconds(0.1))
|
.interactiveDismissDisabled(mainSEVM.shouldDisplayDismissWarning)
|
||||||
withAnimation(.bouncy(duration: 0.5)) {
|
.onChange(of: appAccounts.currentClient) { _, newValue in
|
||||||
scrollID = followUpSEVMs.last?.id
|
if mainSEVM.mode.isInShareExtension {
|
||||||
|
currentAccount.setClient(client: newValue)
|
||||||
|
mainSEVM.client = newValue
|
||||||
|
for post in followUpSEVMs {
|
||||||
|
post.client = newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
.onDrop(of: [.image, .video, .gif, .mpeg4Movie, .quickTimeMovie, .movie],
|
||||||
|
delegate: focusedSEVM)
|
||||||
|
.onChange(of: currentAccount.account?.id) {
|
||||||
|
mainSEVM.currentAccount = currentAccount.account
|
||||||
|
for p in followUpSEVMs {
|
||||||
|
p.currentAccount = mainSEVM.currentAccount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: mainSEVM.visibility) {
|
||||||
|
for p in followUpSEVMs {
|
||||||
|
p.visibility = mainSEVM.visibility
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: followUpSEVMs.count) { oldValue, newValue in
|
||||||
|
if oldValue < newValue {
|
||||||
|
Task {
|
||||||
|
try? await Task.sleep(for: .seconds(0.1))
|
||||||
|
withAnimation(.bouncy(duration: 0.5)) {
|
||||||
|
scrollID = followUpSEVMs.last?.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mainSEVM.isPosting {
|
||||||
|
ProgressView(value: mainSEVM.postingProgress, total: 100.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.sheet(item: $editingMediaContainer) { container in
|
.sheet(item: $editingMediaContainer) { container in
|
||||||
StatusEditor.MediaEditView(viewModel: focusedSEVM, container: container)
|
StatusEditor.MediaEditView(viewModel: focusedSEVM, container: container)
|
||||||
|
|
|
@ -45,9 +45,7 @@ extension TextView.Representable {
|
||||||
textView.allowsEditingTextAttributes = false
|
textView.allowsEditingTextAttributes = false
|
||||||
textView.returnKeyType = .default
|
textView.returnKeyType = .default
|
||||||
textView.allowsEditingTextAttributes = true
|
textView.allowsEditingTextAttributes = true
|
||||||
#if targetEnvironment(macCatalyst)
|
textView.inlinePredictionType = .no
|
||||||
textView.inlinePredictionType = .no
|
|
||||||
#endif
|
|
||||||
|
|
||||||
self.getTextView?(textView)
|
self.getTextView?(textView)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,13 @@ import UIKit
|
||||||
public extension StatusEditor.ViewModel {
|
public extension StatusEditor.ViewModel {
|
||||||
enum Mode {
|
enum Mode {
|
||||||
case replyTo(status: Status)
|
case replyTo(status: Status)
|
||||||
case new(visibility: Models.Visibility)
|
case new(text: String?, visibility: Models.Visibility)
|
||||||
case edit(status: Status)
|
case edit(status: Status)
|
||||||
case quote(status: Status)
|
case quote(status: Status)
|
||||||
case quoteLink(link: URL)
|
case quoteLink(link: URL)
|
||||||
case mention(account: Account, visibility: Models.Visibility)
|
case mention(account: Account, visibility: Models.Visibility)
|
||||||
case shareExtension(items: [NSItemProvider])
|
case shareExtension(items: [NSItemProvider])
|
||||||
|
case imageURL(urls: [URL], visibility: Models.Visibility)
|
||||||
|
|
||||||
var isInShareExtension: Bool {
|
var isInShareExtension: Bool {
|
||||||
switch self {
|
switch self {
|
||||||
|
@ -41,7 +42,7 @@ public extension StatusEditor.ViewModel {
|
||||||
|
|
||||||
var title: LocalizedStringKey {
|
var title: LocalizedStringKey {
|
||||||
switch self {
|
switch self {
|
||||||
case .new, .mention, .shareExtension, .quoteLink:
|
case .new, .mention, .shareExtension, .quoteLink, .imageURL:
|
||||||
"status.editor.mode.new"
|
"status.editor.mode.new"
|
||||||
case .edit:
|
case .edit:
|
||||||
"status.editor.mode.edit"
|
"status.editor.mode.edit"
|
||||||
|
|
|
@ -234,7 +234,7 @@ public extension StatusEditor {
|
||||||
language: selectedLanguage,
|
language: selectedLanguage,
|
||||||
mediaAttributes: mediaAttributes)
|
mediaAttributes: mediaAttributes)
|
||||||
switch mode {
|
switch mode {
|
||||||
case .new, .replyTo, .quote, .mention, .shareExtension, .quoteLink:
|
case .new, .replyTo, .quote, .mention, .shareExtension, .quoteLink, .imageURL:
|
||||||
postStatus = try await client.post(endpoint: Statuses.postStatus(json: data))
|
postStatus = try await client.post(endpoint: Statuses.postStatus(json: data))
|
||||||
if let postStatus {
|
if let postStatus {
|
||||||
StreamWatcher.shared.emmitPostEvent(for: postStatus)
|
StreamWatcher.shared.emmitPostEvent(for: postStatus)
|
||||||
|
@ -301,12 +301,23 @@ public extension StatusEditor {
|
||||||
|
|
||||||
func prepareStatusText() {
|
func prepareStatusText() {
|
||||||
switch mode {
|
switch mode {
|
||||||
case let .new(visibility):
|
case let .new(text, visibility):
|
||||||
|
if let text {
|
||||||
|
statusText = .init(string: text)
|
||||||
|
selectedRange = .init(location: text.utf16.count, length: 0)
|
||||||
|
}
|
||||||
self.visibility = visibility
|
self.visibility = visibility
|
||||||
case let .shareExtension(items):
|
case let .shareExtension(items):
|
||||||
itemsProvider = items
|
itemsProvider = items
|
||||||
visibility = .pub
|
visibility = .pub
|
||||||
processItemsProvider(items: items)
|
processItemsProvider(items: items)
|
||||||
|
case let .imageURL(urls, visibility):
|
||||||
|
Task {
|
||||||
|
for container in await Self.makeImageContainer(from: urls) {
|
||||||
|
prepareToPost(for: container)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.visibility = visibility
|
||||||
case let .replyTo(status):
|
case let .replyTo(status):
|
||||||
var mentionString = ""
|
var mentionString = ""
|
||||||
if (status.reblog?.account.acct ?? status.account.acct) != currentAccount?.acct {
|
if (status.reblog?.account.acct ?? status.account.acct) != currentAccount?.acct {
|
||||||
|
@ -557,7 +568,7 @@ public extension StatusEditor {
|
||||||
!statusText.string.contains(url.absoluteString)
|
!statusText.string.contains(url.absoluteString)
|
||||||
{
|
{
|
||||||
embeddedStatus = nil
|
embeddedStatus = nil
|
||||||
mode = .new(visibility: visibility)
|
mode = .new(text: nil, visibility: visibility)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -736,6 +747,31 @@ public extension StatusEditor {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func makeImageContainer(from urls: [URL]) async -> [MediaContainer] {
|
||||||
|
var containers: [MediaContainer] = []
|
||||||
|
|
||||||
|
for url in urls {
|
||||||
|
let compressor = Compressor()
|
||||||
|
_ = url.startAccessingSecurityScopedResource()
|
||||||
|
if let compressedData = await compressor.compressImageFrom(url: url),
|
||||||
|
let image = UIImage(data: compressedData)
|
||||||
|
{
|
||||||
|
containers.append(MediaContainer(
|
||||||
|
id: UUID().uuidString,
|
||||||
|
image: image,
|
||||||
|
movieTransferable: nil,
|
||||||
|
gifTransferable: nil,
|
||||||
|
mediaAttachment: nil,
|
||||||
|
error: nil
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
url.stopAccessingSecurityScopedResource()
|
||||||
|
}
|
||||||
|
|
||||||
|
return containers
|
||||||
|
}
|
||||||
|
|
||||||
func upload(container: MediaContainer) async {
|
func upload(container: MediaContainer) async {
|
||||||
if let index = indexOf(container: container) {
|
if let index = indexOf(container: container) {
|
||||||
let originalContainer = mediaContainers[index]
|
let originalContainer = mediaContainers[index]
|
||||||
|
|
|
@ -15,6 +15,7 @@ public struct StatusRowView: View {
|
||||||
@Environment(\.accessibilityVoiceOverEnabled) private var accessibilityVoiceOverEnabled
|
@Environment(\.accessibilityVoiceOverEnabled) private var accessibilityVoiceOverEnabled
|
||||||
@Environment(\.isStatusFocused) private var isFocused
|
@Environment(\.isStatusFocused) private var isFocused
|
||||||
@Environment(\.indentationLevel) private var indentationLevel
|
@Environment(\.indentationLevel) private var indentationLevel
|
||||||
|
@Environment(RouterPath.self) private var routerPath: RouterPath
|
||||||
|
|
||||||
@Environment(QuickLook.self) private var quickLook
|
@Environment(QuickLook.self) private var quickLook
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
|
@ -219,6 +220,23 @@ public struct StatusRowView: View {
|
||||||
StatusDataControllerProvider.shared.dataController(for: viewModel.finalStatus,
|
StatusDataControllerProvider.shared.dataController(for: viewModel.finalStatus,
|
||||||
client: viewModel.client)
|
client: viewModel.client)
|
||||||
)
|
)
|
||||||
|
.alert("DeepL couldn't be reached!\nIs the API Key correct?", isPresented: $viewModel.deeplTranslationError) {
|
||||||
|
Button("alert.button.ok", role: .cancel) {}
|
||||||
|
Button("settings.general.translate") {
|
||||||
|
RouterPath.settingsStartingPoint = .translation
|
||||||
|
routerPath.presentedSheet = .settings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.alert("The Translation Service of your Instance couldn't be reached!", isPresented: $viewModel.instanceTranslationError) {
|
||||||
|
Button("alert.button.ok", role: .cancel) {}
|
||||||
|
Button("settings.general.translate") {
|
||||||
|
RouterPath.settingsStartingPoint = .translation
|
||||||
|
routerPath.presentedSheet = .settings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if canImport(_Translation_SwiftUI)
|
||||||
|
.addTranslateView(isPresented: $viewModel.showAppleTranslation, text: viewModel.finalStatus.content.asRawText)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|
|
@ -31,6 +31,18 @@ import SwiftUI
|
||||||
var translation: Translation?
|
var translation: Translation?
|
||||||
var isLoadingTranslation: Bool = false
|
var isLoadingTranslation: Bool = false
|
||||||
var showDeleteAlert: Bool = false
|
var showDeleteAlert: Bool = false
|
||||||
|
var showAppleTranslation = false
|
||||||
|
var preferredTranslationType = TranslationType.useServerIfPossible {
|
||||||
|
didSet {
|
||||||
|
if oldValue != preferredTranslationType {
|
||||||
|
translation = nil
|
||||||
|
showAppleTranslation = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var deeplTranslationError = false
|
||||||
|
var instanceTranslationError = false
|
||||||
|
|
||||||
private(set) var actionsAccountsFetched: Bool = false
|
private(set) var actionsAccountsFetched: Bool = false
|
||||||
var favoriters: [Account] = []
|
var favoriters: [Account] = []
|
||||||
|
@ -297,25 +309,43 @@ import SwiftUI
|
||||||
}
|
}
|
||||||
|
|
||||||
func translate(userLang: String) async {
|
func translate(userLang: String) async {
|
||||||
withAnimation {
|
updatePreferredTranslation()
|
||||||
isLoadingTranslation = true
|
if preferredTranslationType == .useApple {
|
||||||
}
|
showAppleTranslation = true
|
||||||
if !alwaysTranslateWithDeepl {
|
return
|
||||||
do {
|
|
||||||
// We first use instance translation API if available.
|
|
||||||
let translation: Translation = try await client.post(endpoint: Statuses.translate(id: finalStatus.id,
|
|
||||||
lang: userLang))
|
|
||||||
withAnimation {
|
|
||||||
self.translation = translation
|
|
||||||
isLoadingTranslation = false
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
} catch {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not or fail we use Ice Cubes own DeepL client.
|
if preferredTranslationType != .useDeepl {
|
||||||
await translateWithDeepL(userLang: userLang)
|
await translateWithInstance(userLang: userLang)
|
||||||
|
|
||||||
|
if translation == nil {
|
||||||
|
await translateWithDeepL(userLang: userLang)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await translateWithDeepL(userLang: userLang)
|
||||||
|
|
||||||
|
if translation == nil {
|
||||||
|
await translateWithInstance(userLang: userLang)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasShown = false
|
||||||
|
#if canImport(_Translation_SwiftUI)
|
||||||
|
if translation == nil,
|
||||||
|
#available(iOS 17.4, *) {
|
||||||
|
showAppleTranslation = true
|
||||||
|
hasShown = true
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if !hasShown,
|
||||||
|
translation == nil {
|
||||||
|
if preferredTranslationType == .useDeepl {
|
||||||
|
deeplTranslationError = true
|
||||||
|
} else {
|
||||||
|
instanceTranslationError = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func translateWithDeepL(userLang: String) async {
|
func translateWithDeepL(userLang: String) async {
|
||||||
|
@ -331,6 +361,20 @@ import SwiftUI
|
||||||
isLoadingTranslation = false
|
isLoadingTranslation = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func translateWithInstance(userLang: String) async {
|
||||||
|
withAnimation {
|
||||||
|
isLoadingTranslation = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let translation: Translation? = try? await client.post(endpoint: Statuses.translate(id: finalStatus.id,
|
||||||
|
lang: userLang))
|
||||||
|
|
||||||
|
withAnimation {
|
||||||
|
self.translation = translation
|
||||||
|
isLoadingTranslation = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func getDeepLClient() -> DeepLClient {
|
private func getDeepLClient() -> DeepLClient {
|
||||||
let userAPIfree = UserPreferences.shared.userDeeplAPIFree
|
let userAPIfree = UserPreferences.shared.userDeeplAPIFree
|
||||||
|
@ -339,11 +383,17 @@ import SwiftUI
|
||||||
}
|
}
|
||||||
|
|
||||||
private var userAPIKey: String? {
|
private var userAPIKey: String? {
|
||||||
DeepLUserAPIHandler.readIfAllowed()
|
DeepLUserAPIHandler.readKey()
|
||||||
}
|
}
|
||||||
|
|
||||||
var alwaysTranslateWithDeepl: Bool {
|
func updatePreferredTranslation() {
|
||||||
DeepLUserAPIHandler.shouldAlwaysUseDeepl
|
if DeepLUserAPIHandler.shouldAlwaysUseDeepl {
|
||||||
|
preferredTranslationType = .useDeepl
|
||||||
|
} else if UserPreferences.shared.preferredTranslationType == .useApple {
|
||||||
|
preferredTranslationType = .useApple
|
||||||
|
} else {
|
||||||
|
preferredTranslationType = .useServerIfPossible
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchRemoteStatus() async -> Bool {
|
func fetchRemoteStatus() async -> Bool {
|
||||||
|
|
|
@ -31,7 +31,7 @@ struct StatusRowContextMenu: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
if statusDataController.isReblogged {
|
if statusDataController.isReblogged {
|
||||||
return Label("status.action.unboost", image: "Rocket.fill")
|
return Label("status.action.unboost", image: "Rocket.Fill")
|
||||||
}
|
}
|
||||||
return Label("status.action.boost", image: "Rocket")
|
return Label("status.action.boost", image: "Rocket")
|
||||||
}
|
}
|
||||||
|
|
|
@ -258,6 +258,7 @@ struct AltTextButton: View {
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
|
|
||||||
@State private var isDisplayingAlert = false
|
@State private var isDisplayingAlert = false
|
||||||
|
@State private var isDisplayingTranslation = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if !isInCaptureMode,
|
if !isInCaptureMode,
|
||||||
|
@ -278,6 +279,9 @@ struct AltTextButton: View {
|
||||||
.buttonStyle(.borderless)
|
.buttonStyle(.borderless)
|
||||||
.padding(EdgeInsets(top: 5, leading: 7, bottom: 5, trailing: 7))
|
.padding(EdgeInsets(top: 5, leading: 7, bottom: 5, trailing: 7))
|
||||||
.background(.thinMaterial)
|
.background(.thinMaterial)
|
||||||
|
#if canImport(_Translation_SwiftUI)
|
||||||
|
.addTranslateView(isPresented: $isDisplayingTranslation, text: text)
|
||||||
|
#endif
|
||||||
#if os(visionOS)
|
#if os(visionOS)
|
||||||
.clipShape(Capsule())
|
.clipShape(Capsule())
|
||||||
#endif
|
#endif
|
||||||
|
@ -288,6 +292,12 @@ struct AltTextButton: View {
|
||||||
isPresented: $isDisplayingAlert
|
isPresented: $isDisplayingAlert
|
||||||
) {
|
) {
|
||||||
Button("alert.button.ok", action: {})
|
Button("alert.button.ok", action: {})
|
||||||
|
Button("status.action.copy-text", action: { UIPasteboard.general.string = text })
|
||||||
|
#if canImport(_Translation_SwiftUI)
|
||||||
|
if #available(iOS 17.4, *) {
|
||||||
|
Button("status.action.translate", action: { isDisplayingTranslation = true })
|
||||||
|
}
|
||||||
|
#endif
|
||||||
} message: {
|
} message: {
|
||||||
Text(text)
|
Text(text)
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,8 @@ struct StatusRowTranslateView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
@ViewBuilder
|
||||||
|
var translateButton: some View {
|
||||||
if !isInCaptureMode,
|
if !isInCaptureMode,
|
||||||
!isCompact,
|
!isCompact,
|
||||||
let userLang = preferences.serverPreferences?.postLanguage,
|
let userLang = preferences.serverPreferences?.postLanguage,
|
||||||
|
@ -54,8 +55,22 @@ struct StatusRowTranslateView: View {
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderless)
|
.buttonStyle(.borderless)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let translation = viewModel.translation, !viewModel.isLoadingTranslation {
|
@ViewBuilder
|
||||||
|
var generalTranslateButton: some View {
|
||||||
|
translateButton
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
generalTranslateButton
|
||||||
|
.onChange(of: preferences.preferredTranslationType) { _, _ in
|
||||||
|
withAnimation {
|
||||||
|
_ = viewModel.updatePreferredTranslation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let translation = viewModel.translation, !viewModel.isLoadingTranslation, preferences.preferredTranslationType != .useApple {
|
||||||
GroupBox {
|
GroupBox {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text(translation.content.asSafeMarkdownAttributedString)
|
Text(translation.content.asSafeMarkdownAttributedString)
|
||||||
|
|
|
@ -3,7 +3,7 @@ import Models
|
||||||
import Network
|
import Network
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
public enum RemoteTimelineFilter: String, CaseIterable, Hashable, Equatable {
|
public enum RemoteTimelineFilter: String, CaseIterable, Hashable, Equatable, Sendable {
|
||||||
case local, federated, trending
|
case local, federated, trending
|
||||||
|
|
||||||
public func localizedTitle() -> LocalizedStringKey {
|
public func localizedTitle() -> LocalizedStringKey {
|
||||||
|
@ -29,7 +29,7 @@ public enum RemoteTimelineFilter: String, CaseIterable, Hashable, Equatable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum TimelineFilter: Hashable, Equatable, Identifiable {
|
public enum TimelineFilter: Hashable, Equatable, Identifiable, Sendable {
|
||||||
case home, local, federated, trending
|
case home, local, federated, trending
|
||||||
case hashtag(tag: String, accountId: String?)
|
case hashtag(tag: String, accountId: String?)
|
||||||
case tagGroup(title: String, tags: [String], symbolName: String?)
|
case tagGroup(title: String, tags: [String], symbolName: String?)
|
||||||
|
|
|
@ -30,7 +30,7 @@ public struct TimelineContentFilterView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor.opacity(0.3))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
|
@ -46,7 +46,7 @@ public struct TimelineContentFilterView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor.opacity(0.3))
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
.navigationTitle("timeline.content-filter.title")
|
.navigationTitle("timeline.content-filter.title")
|
||||||
|
|
Loading…
Reference in a new issue