Compare commits
242 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 | |||
5c32c24ae5 | |||
bb56047ee2 | |||
924ada6606 | |||
e6f96d1899 | |||
058500f91e | |||
fd3d9fc2bc | |||
9a7e6b7cb0 | |||
bc2a09891a | |||
7c343eb4e9 | |||
15d7d1dabd | |||
f4ec69a37f | |||
732a253c7a | |||
9c67af8451 | |||
b56da94a7c | |||
e612fbdf7c | |||
f46a0cee17 | |||
4a90d979e3 | |||
9e4323f317 | |||
24ce872849 | |||
1f858414d8 | |||
2d988d48c1 | |||
21d9fd7b59 | |||
cca6472a32 | |||
c769e80bb6 | |||
2986d2b177 | |||
29312d1be2 | |||
9ddf0e65fc | |||
bc74a50a6a | |||
d55d6a0371 | |||
773fdc318b | |||
7423aba92a | |||
77aa50ef19 | |||
dfc213a19a | |||
20900f573f | |||
046a41e8ef | |||
fcd56ab7a0 | |||
923927cddd | |||
219703ecc7 | |||
0739264005 | |||
6f8bec4737 | |||
d8e6e6cfb1 | |||
e7bc857231 | |||
35d249f7c9 | |||
7b7e65bf31 | |||
9542002534 | |||
3020d831e4 | |||
a0e022b8de | |||
b9b3d0e727 | |||
4bf476daea | |||
d1fd97794a | |||
f14ca6e529 | |||
75bb4f43dd | |||
cfd6eed159 | |||
d10adf1fd9 | |||
3b07d56b1d | |||
b4dbda8722 | |||
827765f251 | |||
e7702e1ad0 | |||
70f58aa08d | |||
cf81054366 | |||
f67163e4b0 | |||
9bd967cddf | |||
551e6b1412 | |||
1c76d50bde | |||
2a6afb4092 | |||
b348f37f1a | |||
de757c58f8 | |||
7268b5a38e | |||
b8cf446406 | |||
6e497fae5b | |||
586e4f525e | |||
7f689bbb9c | |||
9dfd9c27c7 | |||
9cf16b2f30 | |||
1299202bba | |||
f16f0d514b | |||
096996c242 | |||
c7bd5a1d94 | |||
20f4eb9c71 | |||
74590542bc | |||
49b1b0e96c | |||
3eec5c0eec | |||
016e4d5d57 | |||
ba071eb4c8 | |||
d2014d3aec | |||
621f0d0864 | |||
eeff60bf98 | |||
245d35db82 | |||
62eeba5334 | |||
46b8fbde29 | |||
9a8568d3fa | |||
a6ccdc029b | |||
ed9a4a598d | |||
13af2d7e3f | |||
2b446833da | |||
0b96b76641 | |||
78eee1e855 | |||
b7937e3580 | |||
9320b2f114 | |||
ad7bc999d3 | |||
b41fd2d6ce | |||
a79a181d6f | |||
fb944f9c48 | |||
3c82af0273 | |||
3577254f08 | |||
abff6218a4 | |||
1be9a7b941 | |||
18381e22e2 | |||
e2273c436a | |||
2296dd4658 | |||
92662665b9 | |||
17387626b8 | |||
e00fd49d89 | |||
97798b2c35 | |||
90a2a19bb1 | |||
21d54cc546 | |||
76638911ee | |||
0b7fed2e9a | |||
328ee2d090 | |||
ebdd5b9feb | |||
f79117eff1 | |||
709dd79e25 | |||
bf7cdc3712 | |||
f12d0600f7 | |||
b6f11e4e08 | |||
76a8f45478 | |||
e03747aa45 | |||
8568d6cc59 | |||
1a0b52d268 | |||
0dea624060 | |||
a4927fd30c | |||
b8be6b79af | |||
ddaf4f9fde | |||
d9f115ba67 | |||
a8f8933e11 | |||
35f6dc8d14 | |||
70eea46aef | |||
788fab930b | |||
7c8ea23ae9 | |||
54dd7521c0 | |||
5eccb5c294 | |||
254962c5c0 | |||
a737c61d15 | |||
27da37e9ec | |||
6d12f2528d | |||
231b622f4e | |||
73a02db57f | |||
ab69a0683b | |||
34d8d8bb46 | |||
79d062f168 | |||
49e47f3dfd | |||
c55259efbb | |||
99c8ea6f99 | |||
a11b6ac2a5 | |||
8c68aa711d | |||
68b7d469f5 | |||
fab23cbafc | |||
2f5307bfc7 | |||
801b6c5682 | |||
802eae750d | |||
afa31411e1 |
35
.github/workflows/validate_translations.yml
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
name: Validate Translations
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [synchronize, opened, reopened, labeled, unlabeled, edited]
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Validate Translations
|
||||
runs-on: macOS-latest
|
||||
steps:
|
||||
- name: git checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: ruby versions
|
||||
run: |
|
||||
ruby --version
|
||||
gem --version
|
||||
bundler --version
|
||||
|
||||
- name: ruby setup
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.3
|
||||
bundler-cache: true
|
||||
|
||||
# additional steps here, if needed
|
||||
|
||||
- name: Clone SwiftPolyglot
|
||||
run: git clone https://github.com/appdecostudio/SwiftPolyglot.git --branch 0.2.0
|
||||
|
||||
- name: Build and Run SwiftPolyglot
|
||||
run: |
|
||||
swift build --package-path ./SwiftPolyglot --configuration release
|
||||
swift run --package-path ./SwiftPolyglot swiftpolyglot "en,eu,be,ca,zh-Hans,zh-Hant,nl,en-GB,fr,de,it,ja,ko,nb,pl,pt-BR,es,tr,uk"
|
|
@ -26,8 +26,6 @@
|
|||
9F24EEB829360C330042359D /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9F24EEB729360C330042359D /* Preview Assets.xcassets */; };
|
||||
9F295540292B6C3400E0E81B /* Timeline in Frameworks */ = {isa = PBXBuildFile; productRef = 9F29553F292B6C3400E0E81B /* Timeline */; };
|
||||
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 */; };
|
||||
9F2A5411296A1429009B2D7C /* PushNotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2A5410296A1429009B2D7C /* PushNotificationsView.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 */; };
|
||||
9F35DB4A29506FA100B3281A /* Notifications in Frameworks */ = {isa = PBXBuildFile; productRef = 9F35DB4929506FA100B3281A /* Notifications */; };
|
||||
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 */; };
|
||||
9F38A7342ACEA26100DBCD66 /* 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 */; };
|
||||
9F55C68D2955968700F94077 /* ExploreTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F55C68C2955968700F94077 /* ExploreTab.swift */; };
|
||||
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 */; };
|
||||
9F6028562B3F36AE00476078 /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F6028552B3F36AE00476078 /* AppView.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 */; };
|
||||
9F7335F22967608F00AFF0BA /* AddRemoteTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F12967608F00AFF0BA /* AddRemoteTimelineView.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 */; };
|
||||
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 */; };
|
||||
9FAD85832971BF7200496AB1 /* Secret.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9FAD85822971BF7200496AB1 /* Secret.plist */; };
|
||||
9FAD858B29743F7400496AB1 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAD858A29743F7400496AB1 /* ShareViewController.swift */; };
|
||||
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, ); }; };
|
||||
|
@ -98,6 +124,13 @@
|
|||
9FE3DB57296FEFCA00628CB0 /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9FE3DB56296FEFCA00628CB0 /* AppAccount */; };
|
||||
9FE4CCAB2B4C848A00DA5F13 /* GiphyUISDK in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, maccatalyst, ); productRef = 9FE4CCAA2B4C848A00DA5F13 /* 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 */; };
|
||||
9FFF6780299B7D2B00FE700A /* DesignSystem in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF677F299B7D2B00FE700A /* DesignSystem */; };
|
||||
9FFF6782299B7D3A00FE700A /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF6781299B7D3A00FE700A /* Account */; };
|
||||
|
@ -123,6 +156,13 @@
|
|||
remoteGlobalIDString = 9F2A5415296AB631009B2D7C;
|
||||
remoteInfo = IceCubesNotifications;
|
||||
};
|
||||
9F7788D42BE652B2004E6BEF /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 9FBFE631292A715500C250E9 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 9F7788C42BE652B1004E6BEF;
|
||||
remoteInfo = IceCubesAppWidgetsExtensionExtension;
|
||||
};
|
||||
9FAD859029743F7400496AB1 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 9FBFE631292A715500C250E9 /* Project object */;
|
||||
|
@ -149,6 +189,7 @@
|
|||
E9DF420729830FEC0003AAD2 /* IceCubesActionExtension.appex in Embed Foundation Extensions */,
|
||||
9F2A541D296AB631009B2D7C /* IceCubesNotifications.appex in Embed Foundation Extensions */,
|
||||
9FAD859229743F7400496AB1 /* IceCubesShareExtension.appex in Embed Foundation Extensions */,
|
||||
9F7788D62BE652B2004E6BEF /* IceCubesAppWidgetsExtensionExtension.appex in Embed Foundation Extensions */,
|
||||
);
|
||||
name = "Embed Foundation Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -197,6 +238,11 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -205,6 +251,9 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -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; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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; };
|
||||
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>"; };
|
||||
|
@ -243,6 +306,12 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -278,6 +347,21 @@
|
|||
);
|
||||
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 */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -300,7 +384,6 @@
|
|||
9F7335EF29674F7100AFF0BA /* QuickLook.framework in Frameworks */,
|
||||
9FE4CCAB2B4C848A00DA5F13 /* GiphyUISDK in Frameworks */,
|
||||
9F7335ED2967463400AFF0BA /* AVKit.framework in Frameworks */,
|
||||
9F2A540C29699705009B2D7C /* RevenueCat in Frameworks */,
|
||||
9F2A540E2969A0B0009B2D7C /* StoreKit.framework in Frameworks */,
|
||||
9F55C6902955993C00F94077 /* Explore in Frameworks */,
|
||||
9FAE4ACE29379A5A00772766 /* KeychainSwift in Frameworks */,
|
||||
|
@ -311,8 +394,8 @@
|
|||
9FD542E72962D2FF0045321A /* Lists in Frameworks */,
|
||||
9F398AAB2935FFDB00A889F2 /* Models in Frameworks */,
|
||||
9F5E581929545BE700A53960 /* Env in Frameworks */,
|
||||
9F2A540A29699705009B2D7C /* ReceiptParser in Frameworks */,
|
||||
DA0B24FB2A6876D50045BDD7 /* SFSafeSymbols in Frameworks */,
|
||||
9FE6A42E2BD043A90055D388 /* RevenueCat in Frameworks */,
|
||||
9F295540292B6C3400E0E81B /* Timeline in Frameworks */,
|
||||
9F35DB4A29506FA100B3281A /* Notifications in Frameworks */,
|
||||
9FC2A38B2B49D19A00DFD1C1 /* StatusKit in Frameworks */,
|
||||
|
@ -351,6 +434,22 @@
|
|||
path = IceCubesNotifications;
|
||||
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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -375,6 +474,15 @@
|
|||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9F5BE6202BF48FB20074387E /* ListsWidget */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9F5BE6212BF48FBA0074387E /* ListsWidget.swift */,
|
||||
9F5BE6232BF48FC40074387E /* ListsWidgetConfiguration.swift */,
|
||||
);
|
||||
path = ListsWidget;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9F654BF0299AC46200D27FA5 /* Report */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -392,6 +500,33 @@
|
|||
path = Timeline;
|
||||
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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -456,9 +591,11 @@
|
|||
DD31E2E5297FB68B00A4BE29 /* IceCubesApp.xcconfig */,
|
||||
9F7D939529800B0300EE6B7A /* IceCubesApp-release.xcconfig */,
|
||||
9FBFE63B292A715500C250E9 /* IceCubesApp */,
|
||||
9F37BDD92BE36E08007F28AD /* IceCubesAppIntents */,
|
||||
E9DF41FD29830FEC0003AAD2 /* IceCubesActionExtension */,
|
||||
9F2A5417296AB631009B2D7C /* IceCubesNotifications */,
|
||||
9FAD858929743F7400496AB1 /* IceCubesShareExtension */,
|
||||
9F7788CA2BE652B1004E6BEF /* IceCubesAppWidgetsExtension */,
|
||||
9FBFE63A292A715500C250E9 /* Products */,
|
||||
9FBFE64C292A72BD00C250E9 /* Frameworks */,
|
||||
9FE3DB55296FEF5800628CB0 /* AppAccount */,
|
||||
|
@ -484,6 +621,7 @@
|
|||
9F2A5416296AB631009B2D7C /* IceCubesNotifications.appex */,
|
||||
9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */,
|
||||
E9DF41FA29830FEC0003AAD2 /* IceCubesActionExtension.appex */,
|
||||
9F7788C52BE652B1004E6BEF /* IceCubesAppWidgetsExtensionExtension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
|
@ -496,7 +634,6 @@
|
|||
9FAE4AC8293774FF00772766 /* Info.plist */,
|
||||
9F398AB429360A5800A889F2 /* App */,
|
||||
9F398AB529360A6100A889F2 /* Resources */,
|
||||
9FAD85822971BF7200496AB1 /* Secret.plist */,
|
||||
);
|
||||
path = IceCubesApp;
|
||||
sourceTree = "<group>";
|
||||
|
@ -509,6 +646,8 @@
|
|||
9F7335EE29674F7100AFF0BA /* QuickLook.framework */,
|
||||
9F7335EB2967461B00AFF0BA /* AVKit.framework */,
|
||||
E9DF41FB29830FEC0003AAD2 /* UniformTypeIdentifiers.framework */,
|
||||
9F7788C62BE652B1004E6BEF /* WidgetKit.framework */,
|
||||
9F7788C82BE652B1004E6BEF /* SwiftUI.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
|
@ -538,6 +677,42 @@
|
|||
path = Settings;
|
||||
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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -589,6 +764,31 @@
|
|||
productReference = 9F2A5416296AB631009B2D7C /* IceCubesNotifications.appex */;
|
||||
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 */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 9FAD859329743F7400496AB1 /* Build configuration list for PBXNativeTarget "IceCubesShareExtension" */;
|
||||
|
@ -631,6 +831,7 @@
|
|||
9F2A541C296AB631009B2D7C /* PBXTargetDependency */,
|
||||
9FAD859129743F7400496AB1 /* PBXTargetDependency */,
|
||||
E9DF420629830FEC0003AAD2 /* PBXTargetDependency */,
|
||||
9F7788D52BE652B2004E6BEF /* PBXTargetDependency */,
|
||||
);
|
||||
name = IceCubesApp;
|
||||
packageProductDependencies = (
|
||||
|
@ -644,12 +845,11 @@
|
|||
9F55C68F2955993C00F94077 /* Explore */,
|
||||
9FD542E62962D2FF0045321A /* Lists */,
|
||||
9F7335E92966B3F800AFF0BA /* Conversations */,
|
||||
9F2A540929699705009B2D7C /* ReceiptParser */,
|
||||
9F2A540B29699705009B2D7C /* RevenueCat */,
|
||||
9FE3DB56296FEFCA00628CB0 /* AppAccount */,
|
||||
DA0B24FA2A6876D50045BDD7 /* SFSafeSymbols */,
|
||||
9FC2A38A2B49D19A00DFD1C1 /* StatusKit */,
|
||||
9FE4CCAA2B4C848A00DA5F13 /* GiphyUISDK */,
|
||||
9FE6A42D2BD043A90055D388 /* RevenueCat */,
|
||||
);
|
||||
productName = IceCubesApp;
|
||||
productReference = 9FBFE639292A715500C250E9 /* Ice Cubes.app */;
|
||||
|
@ -683,12 +883,15 @@
|
|||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1420;
|
||||
LastSwiftUpdateCheck = 1530;
|
||||
LastUpgradeCheck = 1500;
|
||||
TargetAttributes = {
|
||||
9F2A5415296AB631009B2D7C = {
|
||||
CreatedOnToolsVersion = 14.2;
|
||||
};
|
||||
9F7788C42BE652B1004E6BEF = {
|
||||
CreatedOnToolsVersion = 15.3;
|
||||
};
|
||||
9FAD858729743F7400496AB1 = {
|
||||
CreatedOnToolsVersion = 14.2;
|
||||
};
|
||||
|
@ -724,13 +927,14 @@
|
|||
be,
|
||||
uk,
|
||||
"zh-Hant",
|
||||
Base,
|
||||
);
|
||||
mainGroup = 9FBFE630292A715500C250E9;
|
||||
packageReferences = (
|
||||
9FAE4ACC29379A5A00772766 /* XCRemoteSwiftPackageReference "keychain-swift" */,
|
||||
9F2A540829699705009B2D7C /* XCRemoteSwiftPackageReference "purchases-ios" */,
|
||||
DA0B24F92A6876D40045BDD7 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */,
|
||||
9FE4CCA92B4C848A00DA5F13 /* XCRemoteSwiftPackageReference "giphy-ios-sdk" */,
|
||||
9FE6A42C2BD043A80055D388 /* XCRemoteSwiftPackageReference "purchases-ios" */,
|
||||
);
|
||||
productRefGroup = 9FBFE63A292A715500C250E9 /* Products */;
|
||||
projectDirPath = "";
|
||||
|
@ -740,6 +944,7 @@
|
|||
E9DF41F929830FEC0003AAD2 /* IceCubesActionExtension */,
|
||||
9F2A5415296AB631009B2D7C /* IceCubesNotifications */,
|
||||
9FAD858729743F7400496AB1 /* IceCubesShareExtension */,
|
||||
9F7788C42BE652B1004E6BEF /* IceCubesAppWidgetsExtensionExtension */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
@ -755,6 +960,14 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
9F7788C32BE652B1004E6BEF /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9F7788D22BE652B2004E6BEF /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
9FAD858629743F7400496AB1 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -778,7 +991,6 @@
|
|||
9F18801829AE477F00D85459 /* favorite.wav in Resources */,
|
||||
9F24EEB829360C330042359D /* Preview Assets.xcassets in Resources */,
|
||||
069709A8298C87B5006E4CB5 /* OpenDyslexic-Regular.otf in Resources */,
|
||||
9FAD85832971BF7200496AB1 /* Secret.plist in Resources */,
|
||||
9F18801229AE477F00D85459 /* tabSelection.wav in Resources */,
|
||||
9F18801429AE477F00D85459 /* bookmark.wav in Resources */,
|
||||
9F18801629AE477F00D85459 /* refresh.wav in Resources */,
|
||||
|
@ -809,6 +1021,30 @@
|
|||
);
|
||||
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 */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -830,6 +1066,11 @@
|
|||
9F15D6042B3DC2180008C220 /* NavigationSheet.swift in Sources */,
|
||||
9FA6FD6229C04A8800E2312C /* TranslationSettingsView.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 */,
|
||||
9FAE4ACB293783B000772766 /* SettingsTab.swift in Sources */,
|
||||
9FC14EF42B494D940006CEE1 /* RemoteTimelinesSettingView.swift in Sources */,
|
||||
|
@ -848,9 +1089,12 @@
|
|||
9F2B92FA295DA7D700DE16D0 /* AddAccountsView.swift in Sources */,
|
||||
639CDF9C296AC82F00C35E58 /* SafariRouter.swift in Sources */,
|
||||
9F35DB4729506F6600B3281A /* NotificationTab.swift in Sources */,
|
||||
9F7788ED2BE78D75004E6BEF /* TimelineFilterEntity.swift in Sources */,
|
||||
9F37BDE32BE393A7007F28AD /* AppShortcuts.swift in Sources */,
|
||||
9F654BEF299AC45B00D27FA5 /* ReportView.swift in Sources */,
|
||||
D08A9C3529956CFA00204A4A /* SwipeActionsSettingsView.swift in Sources */,
|
||||
9F7335F22967608F00AFF0BA /* AddRemoteTimelineView.swift in Sources */,
|
||||
9F7788E82BE65533004E6BEF /* AppAccountEntity.swift in Sources */,
|
||||
9FC14EF62B494DFF0006CEE1 /* RecenTagsSettingView.swift in Sources */,
|
||||
9F6028562B3F36AE00476078 /* AppView.swift in Sources */,
|
||||
9F55C68D2955968700F94077 /* ExploreTab.swift in Sources */,
|
||||
|
@ -877,6 +1121,15 @@
|
|||
target = 9F2A5415296AB631009B2D7C /* IceCubesNotifications */;
|
||||
targetProxy = 9F2A541B296AB631009B2D7C /* PBXContainerItemProxy */;
|
||||
};
|
||||
9F7788D52BE652B2004E6BEF /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
platformFilters = (
|
||||
ios,
|
||||
maccatalyst,
|
||||
);
|
||||
target = 9F7788C42BE652B1004E6BEF /* IceCubesAppWidgetsExtensionExtension */;
|
||||
targetProxy = 9F7788D42BE652B2004E6BEF /* PBXContainerItemProxy */;
|
||||
};
|
||||
9FAD859129743F7400496AB1 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
platformFilters = (
|
||||
|
@ -940,7 +1193,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.20;
|
||||
MARKETING_VERSION = 1.10.41;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesNotifications";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
|
@ -975,7 +1228,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.20;
|
||||
MARKETING_VERSION = 1.10.41;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesNotifications";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
|
@ -991,6 +1244,78 @@
|
|||
};
|
||||
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 */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
|
@ -1011,7 +1336,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.20;
|
||||
MARKETING_VERSION = 1.10.41;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesShareExtension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
|
@ -1045,7 +1370,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.20;
|
||||
MARKETING_VERSION = 1.10.41;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesShareExtension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
|
@ -1189,10 +1514,11 @@
|
|||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIconAlternate0 AppIconAlternate6 AppIconAlternate7 AppIconAlternate8 AppIconAlternate10 AppIconAlternate11 AppIconAlternate12 AppIconAlternate13 AppIconAlternate14 AppIconAlternate15 AppIconAlternate16 AppIconAlternate17 AppIconAlternate19 AppIconAlternate18 AppIconAlternate20 AppIconAlternate21 AppIconAlternate22 AppIconAlternate23 AppIconAlternate24 AppIconAlternate25 AppIconAlternate26 AppIconAlternate27 AppIconAlternate28 AppIconAlternate29 AppIconAlternate30 AppIconAlternate31 AppIconAlternate32 AppIconAlternate33 AppIconAlternate34 AppIconAlternate35 AppIconAlternate36 AppIconAlternate37 AppIconAlternate38 AppIconAlternate39 AppIconAlternate40 AppIconAlternate42 AppIconAlternate2 AppIconAlternate41 AppIconAlternate45 AppIconAlternate44 AppIconAlternate1 AppIconAlternate4 AppIconAlternate3 AppIconAlternate5 AppIconAlternate9 AppIconAlternate43";
|
||||
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIconAlternate0 AppIconAlternate6 AppIconAlternate7 AppIconAlternate8 AppIconAlternate10 AppIconAlternate11 AppIconAlternate12 AppIconAlternate13 AppIconAlternate14 AppIconAlternate15 AppIconAlternate16 AppIconAlternate17 AppIconAlternate19 AppIconAlternate18 AppIconAlternate20 AppIconAlternate21 AppIconAlternate22 AppIconAlternate23 AppIconAlternate24 AppIconAlternate25 AppIconAlternate26 AppIconAlternate27 AppIconAlternate28 AppIconAlternate29 AppIconAlternate30 AppIconAlternate31 AppIconAlternate32 AppIconAlternate33 AppIconAlternate34 AppIconAlternate35 AppIconAlternate36 AppIconAlternate37 AppIconAlternate38 AppIconAlternate39 AppIconAlternate40 AppIconAlternate42 AppIconAlternate2 AppIconAlternate41 AppIconAlternate45 AppIconAlternate44 AppIconAlternate1 AppIconAlternate4 AppIconAlternate3 AppIconAlternate5 AppIconAlternate46 AppIconAlternate9 AppIconAlternate43";
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = NO;
|
||||
CODE_SIGN_ENTITLEMENTS = IceCubesApp/App/IceCubesApp.entitlements;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
|
@ -1208,7 +1534,8 @@
|
|||
INFOPLIST_FILE = IceCubesApp/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Ice Cubes";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
INFOPLIST_KEY_NSCameraUsageDescription = "Upload photos & videos to Mastodon";
|
||||
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_UIApplicationSceneManifest_Generation = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
||||
|
@ -1225,7 +1552,7 @@
|
|||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.10.20;
|
||||
MARKETING_VERSION = 1.10.41;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp";
|
||||
PRODUCT_NAME = "Ice Cubes";
|
||||
SDKROOT = auto;
|
||||
|
@ -1243,10 +1570,11 @@
|
|||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIconAlternate0 AppIconAlternate6 AppIconAlternate7 AppIconAlternate8 AppIconAlternate10 AppIconAlternate11 AppIconAlternate12 AppIconAlternate13 AppIconAlternate14 AppIconAlternate15 AppIconAlternate16 AppIconAlternate17 AppIconAlternate19 AppIconAlternate18 AppIconAlternate20 AppIconAlternate21 AppIconAlternate22 AppIconAlternate23 AppIconAlternate24 AppIconAlternate25 AppIconAlternate26 AppIconAlternate27 AppIconAlternate28 AppIconAlternate29 AppIconAlternate30 AppIconAlternate31 AppIconAlternate32 AppIconAlternate33 AppIconAlternate34 AppIconAlternate35 AppIconAlternate36 AppIconAlternate37 AppIconAlternate38 AppIconAlternate39 AppIconAlternate40 AppIconAlternate42 AppIconAlternate2 AppIconAlternate41 AppIconAlternate45 AppIconAlternate44 AppIconAlternate1 AppIconAlternate4 AppIconAlternate3 AppIconAlternate5 AppIconAlternate9 AppIconAlternate43";
|
||||
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIconAlternate0 AppIconAlternate6 AppIconAlternate7 AppIconAlternate8 AppIconAlternate10 AppIconAlternate11 AppIconAlternate12 AppIconAlternate13 AppIconAlternate14 AppIconAlternate15 AppIconAlternate16 AppIconAlternate17 AppIconAlternate19 AppIconAlternate18 AppIconAlternate20 AppIconAlternate21 AppIconAlternate22 AppIconAlternate23 AppIconAlternate24 AppIconAlternate25 AppIconAlternate26 AppIconAlternate27 AppIconAlternate28 AppIconAlternate29 AppIconAlternate30 AppIconAlternate31 AppIconAlternate32 AppIconAlternate33 AppIconAlternate34 AppIconAlternate35 AppIconAlternate36 AppIconAlternate37 AppIconAlternate38 AppIconAlternate39 AppIconAlternate40 AppIconAlternate42 AppIconAlternate2 AppIconAlternate41 AppIconAlternate45 AppIconAlternate44 AppIconAlternate1 AppIconAlternate4 AppIconAlternate3 AppIconAlternate5 AppIconAlternate46 AppIconAlternate9 AppIconAlternate43";
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = NO;
|
||||
CODE_SIGN_ENTITLEMENTS = "IceCubesApp/App/IceCubesApp-release.entitlements";
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
|
@ -1262,7 +1590,8 @@
|
|||
INFOPLIST_FILE = IceCubesApp/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Ice Cubes";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
INFOPLIST_KEY_NSCameraUsageDescription = "Upload photos & videos to Mastodon";
|
||||
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_UIApplicationSceneManifest_Generation = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
||||
|
@ -1279,7 +1608,7 @@
|
|||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.10.20;
|
||||
MARKETING_VERSION = 1.10.41;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp";
|
||||
PRODUCT_NAME = "Ice Cubes";
|
||||
SDKROOT = auto;
|
||||
|
@ -1314,7 +1643,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.20;
|
||||
MARKETING_VERSION = 1.10.41;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesActionExtension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
|
@ -1349,7 +1678,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.20;
|
||||
MARKETING_VERSION = 1.10.41;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesActionExtension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
|
@ -1376,6 +1705,15 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
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" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
@ -1415,14 +1753,6 @@
|
|||
/* End XCConfigurationList 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" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/evgenyneu/keychain-swift";
|
||||
|
@ -1436,7 +1766,15 @@
|
|||
repositoryURL = "https://github.com/Giphy/giphy-ios-sdk";
|
||||
requirement = {
|
||||
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" */ = {
|
||||
|
@ -1454,16 +1792,6 @@
|
|||
isa = XCSwiftPackageProductDependency;
|
||||
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 */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Env;
|
||||
|
@ -1501,6 +1829,30 @@
|
|||
isa = XCSwiftPackageProductDependency;
|
||||
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 */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = AppAccount;
|
||||
|
@ -1569,6 +1921,11 @@
|
|||
package = 9FE4CCA92B4C848A00DA5F13 /* XCRemoteSwiftPackageReference "giphy-ios-sdk" */;
|
||||
productName = GiphyUISDK;
|
||||
};
|
||||
9FE6A42D2BD043A90055D388 /* RevenueCat */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 9FE6A42C2BD043A80055D388 /* XCRemoteSwiftPackageReference "purchases-ios" */;
|
||||
productName = RevenueCat;
|
||||
};
|
||||
9FFF677B299B7B2C00FE700A /* Notifications */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Notifications;
|
||||
|
|
|
@ -9,13 +9,22 @@
|
|||
"version" : "2.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "buttonkit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Dean151/ButtonKit",
|
||||
"state" : {
|
||||
"revision" : "377f5bab4ed047704316d531e0826d4de5ebf6a4",
|
||||
"version" : "0.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "emojitext",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/divadretlaw/EmojiText",
|
||||
"state" : {
|
||||
"revision" : "d0664390e3236ff6241ea0586d80f4e92702973b",
|
||||
"version" : "3.2.1"
|
||||
"revision" : "c54000aa9ccc048619054a5a2da2ce0576ffea18",
|
||||
"version" : "4.0.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -23,8 +32,8 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Giphy/giphy-ios-sdk",
|
||||
"state" : {
|
||||
"revision" : "95c32b862185e76107841b49bfff8ff7230f3b68",
|
||||
"version" : "2.2.7"
|
||||
"revision" : "9c58a350a3381f1641f5a31cdcd162a406274892",
|
||||
"version" : "2.2.8"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -59,17 +68,17 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/kean/Nuke",
|
||||
"state" : {
|
||||
"revision" : "1694798e876113d44f6ec6ead965d7286695981d",
|
||||
"version" : "12.2.0"
|
||||
"revision" : "8ecbfc886da39bccb01c34abef5f2ff4073ad633",
|
||||
"version" : "12.4.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "purchases-ios",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/RevenueCat/purchases-ios.git",
|
||||
"location" : "https://github.com/RevenueCat/purchases-ios",
|
||||
"state" : {
|
||||
"revision" : "b8d20ba1c8e13cc73d72e37cf98607d01fd357b6",
|
||||
"version" : "4.31.2"
|
||||
"revision" : "a9763ca482d52ea3d59aa2dfd2fc23427b02dada",
|
||||
"version" : "4.40.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -90,6 +99,24 @@
|
|||
"version" : "0.14.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-cmark",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-cmark.git",
|
||||
"state" : {
|
||||
"revision" : "f218e5d7691f78b55bfa39b367763f4612486c35",
|
||||
"version" : "0.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-markdown",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-markdown",
|
||||
"state" : {
|
||||
"revision" : "e4f95e2dc23097a1a9a1dfdfe3fe3ee44de77378",
|
||||
"version" : "0.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftsoup",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
@ -107,15 +134,6 @@
|
|||
"revision" : "9e1cc02a65b22e09a8251261cccbccce02731fc5",
|
||||
"version" : "1.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftui-shimmer",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/markiv/SwiftUI-Shimmer",
|
||||
"state" : {
|
||||
"revision" : "965a7cbcbf094cbcf22b9251a2323bdc3432e171",
|
||||
"version" : "1.1.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
|
|
|
@ -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,6 +8,7 @@ import LinkPresentation
|
|||
import Lists
|
||||
import MediaUI
|
||||
import Models
|
||||
import Notifications
|
||||
import StatusKit
|
||||
import SwiftUI
|
||||
import Timeline
|
||||
|
@ -60,63 +61,109 @@ extension View {
|
|||
scrollToTopSignal: .constant(0),
|
||||
canFilterTimeline: false)
|
||||
case let .trendingLinks(cards):
|
||||
CardsListView(cards: cards)
|
||||
TrendingLinksListView(cards: cards)
|
||||
case let .tagsList(tags):
|
||||
TagsListView(tags: tags)
|
||||
case .notificationsRequests:
|
||||
NotificationsRequestsListView()
|
||||
case let .notificationForAccount(accountId):
|
||||
NotificationsListView(lockedType: nil,
|
||||
lockedAccountId: accountId,
|
||||
scrollToTopSignal: .constant(0))
|
||||
case .blockedAccounts:
|
||||
AccountsListView(mode: .blocked)
|
||||
case .mutedAccounts:
|
||||
AccountsListView(mode: .muted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func withSheetDestinations(sheetDestinations: Binding<SheetDestination?>) -> some View {
|
||||
sheet(item: sheetDestinations) { destination in
|
||||
Group {
|
||||
switch destination {
|
||||
case let .replyToStatusEditor(status):
|
||||
StatusEditor.MainView(mode: .replyTo(status: status))
|
||||
case let .newStatusEditor(visibility):
|
||||
StatusEditor.MainView(mode: .new(visibility: visibility))
|
||||
case let .editStatusEditor(status):
|
||||
StatusEditor.MainView(mode: .edit(status: status))
|
||||
case let .quoteStatusEditor(status):
|
||||
StatusEditor.MainView(mode: .quote(status: status))
|
||||
case let .mentionStatusEditor(account, visibility):
|
||||
StatusEditor.MainView(mode: .mention(account: account, visibility: visibility))
|
||||
case .listCreate:
|
||||
ListCreateView()
|
||||
case let .listEdit(list):
|
||||
ListEditView(list: list)
|
||||
case let .listAddAccount(account):
|
||||
ListAddAccountView(account: account)
|
||||
case .addAccount:
|
||||
AddAccountView()
|
||||
case .addRemoteLocalTimeline:
|
||||
AddRemoteTimelineView()
|
||||
case .addTagGroup:
|
||||
EditTagGroupView()
|
||||
case let .statusEditHistory(status):
|
||||
StatusEditHistoryView(statusId: status)
|
||||
case .settings:
|
||||
SettingsTabs(popToRootTab: .constant(.settings), isModal: true)
|
||||
.preferredColorScheme(Theme.shared.selectedScheme == .dark ? .dark : .light)
|
||||
case .accountPushNotficationsSettings:
|
||||
if let subscription = PushNotificationsService.shared.subscriptions.first(where: { $0.account.token == AppAccountsManager.shared.currentAccount.oauthToken }) {
|
||||
NavigationSheet { PushNotificationsView(subscription: subscription) }
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
case .about:
|
||||
NavigationSheet { AboutView() }
|
||||
case .support:
|
||||
NavigationSheet { SupportAppView() }
|
||||
case let .report(status):
|
||||
ReportView(status: status)
|
||||
case let .shareImage(image, status):
|
||||
ActivityView(image: image, status: status)
|
||||
case let .editTagGroup(tagGroup, onSaved):
|
||||
EditTagGroupView(tagGroup: tagGroup, onSaved: onSaved)
|
||||
switch destination {
|
||||
case let .replyToStatusEditor(status):
|
||||
StatusEditor.MainView(mode: .replyTo(status: status))
|
||||
.withEnvironments()
|
||||
case let .newStatusEditor(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()
|
||||
case let .editStatusEditor(status):
|
||||
StatusEditor.MainView(mode: .edit(status: status))
|
||||
.withEnvironments()
|
||||
case let .quoteStatusEditor(status):
|
||||
StatusEditor.MainView(mode: .quote(status: status))
|
||||
.withEnvironments()
|
||||
case let .quoteLinkStatusEditor(link):
|
||||
StatusEditor.MainView(mode: .quoteLink(link: link))
|
||||
.withEnvironments()
|
||||
case let .mentionStatusEditor(account, visibility):
|
||||
StatusEditor.MainView(mode: .mention(account: account, visibility: visibility))
|
||||
.withEnvironments()
|
||||
case .listCreate:
|
||||
ListCreateView()
|
||||
.withEnvironments()
|
||||
case let .listEdit(list):
|
||||
ListEditView(list: list)
|
||||
.withEnvironments()
|
||||
case let .listAddAccount(account):
|
||||
ListAddAccountView(account: account)
|
||||
.withEnvironments()
|
||||
case .addAccount:
|
||||
AddAccountView()
|
||||
.withEnvironments()
|
||||
case .addRemoteLocalTimeline:
|
||||
AddRemoteTimelineView()
|
||||
.withEnvironments()
|
||||
case .addTagGroup:
|
||||
EditTagGroupView()
|
||||
.withEnvironments()
|
||||
case let .statusEditHistory(status):
|
||||
StatusEditHistoryView(statusId: status)
|
||||
.withEnvironments()
|
||||
case .settings:
|
||||
SettingsTabs(popToRootTab: .constant(.settings), isModal: true)
|
||||
.withEnvironments()
|
||||
.preferredColorScheme(Theme.shared.selectedScheme == .dark ? .dark : .light)
|
||||
case .accountPushNotficationsSettings:
|
||||
if let subscription = PushNotificationsService.shared.subscriptions.first(where: { $0.account.token == AppAccountsManager.shared.currentAccount.oauthToken }) {
|
||||
NavigationSheet { PushNotificationsView(subscription: subscription) }
|
||||
.withEnvironments()
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
case .about:
|
||||
NavigationSheet { AboutView() }
|
||||
.withEnvironments()
|
||||
case .support:
|
||||
NavigationSheet { SupportAppView() }
|
||||
.withEnvironments()
|
||||
case let .report(status):
|
||||
ReportView(status: status)
|
||||
.withEnvironments()
|
||||
case let .shareImage(image, status):
|
||||
ActivityView(image: image, status: status)
|
||||
.withEnvironments()
|
||||
case let .editTagGroup(tagGroup, onSaved):
|
||||
EditTagGroupView(tagGroup: tagGroup, onSaved: onSaved)
|
||||
.withEnvironments()
|
||||
case .timelineContentFilter:
|
||||
NavigationSheet { TimelineContentFilterView() }
|
||||
.presentationDetents([.medium])
|
||||
.presentationBackground(.thinMaterial)
|
||||
.withEnvironments()
|
||||
case .accountEditInfo:
|
||||
EditAccountView()
|
||||
.withEnvironments()
|
||||
case .accountFiltersList:
|
||||
FiltersListView()
|
||||
.withEnvironments()
|
||||
}
|
||||
.withEnvironments()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,29 +17,29 @@ struct AppView: View {
|
|||
@Environment(UserPreferences.self) private var userPreferences
|
||||
@Environment(Theme.self) private var theme
|
||||
@Environment(StreamWatcher.self) private var watcher
|
||||
|
||||
|
||||
@Environment(\.openWindow) var openWindow
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
|
||||
|
||||
@Binding var selectedTab: Tab
|
||||
@Binding var appRouterPath: RouterPath
|
||||
|
||||
|
||||
@State var popToRootTab: Tab = .other
|
||||
@State var iosTabs = iOSTabs.shared
|
||||
@State var sidebarTabs = SidebarTabs.shared
|
||||
|
||||
|
||||
var body: some View {
|
||||
#if os(visionOS)
|
||||
tabBarView
|
||||
#else
|
||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||
sidebarView
|
||||
} else {
|
||||
tabBarView
|
||||
}
|
||||
#else
|
||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||
sidebarView
|
||||
} else {
|
||||
tabBarView
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
var availableTabs: [Tab] {
|
||||
guard appAccountsManager.currentClient.isAuth else {
|
||||
return Tab.loggedOutTab()
|
||||
|
@ -49,7 +49,7 @@ struct AppView: View {
|
|||
} else if UIDevice.current.userInterfaceIdiom == .vision {
|
||||
return Tab.visionOSTab()
|
||||
}
|
||||
return sidebarTabs.tabs.map{ $0.tab }
|
||||
return sidebarTabs.tabs.map { $0.tab }
|
||||
}
|
||||
|
||||
var tabBarView: some View {
|
||||
|
@ -57,7 +57,11 @@ struct AppView: View {
|
|||
selectedTab
|
||||
}, set: { newTab in
|
||||
if newTab == .post {
|
||||
appRouterPath.presentedSheet = .newStatusEditor(visibility: userPreferences.postVisibility)
|
||||
#if os(visionOS)
|
||||
openWindow(value: WindowDestinationEditor.newStatusEditor(visibility: userPreferences.postVisibility))
|
||||
#else
|
||||
appRouterPath.presentedSheet = .newStatusEditor(visibility: userPreferences.postVisibility)
|
||||
#endif
|
||||
return
|
||||
}
|
||||
if newTab == selectedTab {
|
||||
|
@ -85,7 +89,7 @@ struct AppView: View {
|
|||
}
|
||||
.tag(tab)
|
||||
.badge(badgeFor(tab: tab))
|
||||
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.50), for: .tabBar)
|
||||
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.30), for: .tabBar)
|
||||
}
|
||||
}
|
||||
.id(appAccountsManager.currentClient.id)
|
||||
|
@ -100,40 +104,40 @@ struct AppView: View {
|
|||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
#if !os(visionOS)
|
||||
var sidebarView: some View {
|
||||
SideBarView(selectedTab: $selectedTab,
|
||||
popToRootTab: $popToRootTab,
|
||||
tabs: availableTabs)
|
||||
{
|
||||
HStack(spacing: 0) {
|
||||
TabView(selection: $selectedTab) {
|
||||
ForEach(availableTabs) { tab in
|
||||
tab
|
||||
.makeContentView(selectedTab: $selectedTab, popToRootTab: $popToRootTab)
|
||||
.tabItem {
|
||||
tab.label
|
||||
}
|
||||
.tag(tab)
|
||||
var sidebarView: some View {
|
||||
SideBarView(selectedTab: $selectedTab,
|
||||
popToRootTab: $popToRootTab,
|
||||
tabs: availableTabs)
|
||||
{
|
||||
HStack(spacing: 0) {
|
||||
TabView(selection: $selectedTab) {
|
||||
ForEach(availableTabs) { tab in
|
||||
tab
|
||||
.makeContentView(selectedTab: $selectedTab, popToRootTab: $popToRootTab)
|
||||
.tabItem {
|
||||
tab.label
|
||||
}
|
||||
.tag(tab)
|
||||
}
|
||||
}
|
||||
.introspect(.tabView, on: .iOS(.v17)) { (tabview: UITabBarController) in
|
||||
tabview.tabBar.isHidden = horizontalSizeClass == .regular
|
||||
tabview.customizableViewControllers = []
|
||||
tabview.moreNavigationController.isNavigationBarHidden = true
|
||||
}
|
||||
if horizontalSizeClass == .regular,
|
||||
appAccountsManager.currentClient.isAuth,
|
||||
userPreferences.showiPadSecondaryColumn
|
||||
{
|
||||
Divider().edgesIgnoringSafeArea(.all)
|
||||
notificationsSecondaryColumn
|
||||
}
|
||||
}
|
||||
.introspect(.tabView, on: .iOS(.v17)) { (tabview: UITabBarController) in
|
||||
tabview.tabBar.isHidden = horizontalSizeClass == .regular
|
||||
tabview.customizableViewControllers = []
|
||||
tabview.moreNavigationController.isNavigationBarHidden = true
|
||||
}
|
||||
if horizontalSizeClass == .regular,
|
||||
appAccountsManager.currentClient.isAuth,
|
||||
userPreferences.showiPadSecondaryColumn
|
||||
{
|
||||
Divider().edgesIgnoringSafeArea(.all)
|
||||
notificationsSecondaryColumn
|
||||
}
|
||||
}
|
||||
.environment(appRouterPath)
|
||||
}
|
||||
.environment(appRouterPath)
|
||||
}
|
||||
#endif
|
||||
|
||||
var notificationsSecondaryColumn: some View {
|
||||
|
|
|
@ -54,5 +54,11 @@ extension IceCubesApp {
|
|||
}
|
||||
.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 MediaUI
|
||||
import StatusKit
|
||||
|
@ -22,6 +23,7 @@ extension IceCubesApp {
|
|||
.environment(theme)
|
||||
.environment(watcher)
|
||||
.environment(pushNotificationsService)
|
||||
.environment(appIntentService)
|
||||
.environment(\.isSupporter, isSupporter)
|
||||
.sheet(item: $quickLook.selectedMediaAttachment) { selectedMediaAttachment in
|
||||
MediaUIView(selectedAttachment: selectedMediaAttachment,
|
||||
|
@ -47,8 +49,19 @@ extension IceCubesApp {
|
|||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: appIntentService.handledIntent) { _, _ in
|
||||
if let intent = appIntentService.handledIntent?.intent {
|
||||
handleIntent(intent)
|
||||
appIntentService.handledIntent = nil
|
||||
}
|
||||
}
|
||||
.withModelContainer()
|
||||
}
|
||||
#if targetEnvironment(macCatalyst)
|
||||
.defaultSize(width: userPreferences.showiPadSecondaryColumn ? 1100 : 800, height: 1400)
|
||||
#elseif os(visionOS)
|
||||
.defaultSize(width: 800, height: 1200)
|
||||
#endif
|
||||
.commands {
|
||||
appMenu
|
||||
}
|
||||
|
@ -69,7 +82,9 @@ extension IceCubesApp {
|
|||
Group {
|
||||
switch destination.wrappedValue {
|
||||
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):
|
||||
StatusEditor.MainView(mode: .edit(status: status))
|
||||
case let .quoteStatusEditor(status):
|
||||
|
@ -78,11 +93,14 @@ extension IceCubesApp {
|
|||
StatusEditor.MainView(mode: .replyTo(status: status))
|
||||
case let .mentionStatusEditor(account, visibility):
|
||||
StatusEditor.MainView(mode: .mention(account: account, visibility: visibility))
|
||||
case let .quoteLinkStatusEditor(link):
|
||||
StatusEditor.MainView(mode: .quoteLink(link: link))
|
||||
case .none:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
.withEnvironments()
|
||||
.environment(RouterPath())
|
||||
.withModelContainer()
|
||||
.applyTheme(theme)
|
||||
.frame(minWidth: 300, minHeight: 400)
|
||||
|
@ -108,4 +126,23 @@ extension IceCubesApp {
|
|||
.defaultSize(width: 1200, height: 1000)
|
||||
.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,13 +23,14 @@ struct IceCubesApp: App {
|
|||
@State var currentAccount = CurrentAccount.shared
|
||||
@State var userPreferences = UserPreferences.shared
|
||||
@State var pushNotificationsService = PushNotificationsService.shared
|
||||
@State var appIntentService = AppIntentService.shared
|
||||
@State var watcher = StreamWatcher.shared
|
||||
@State var quickLook = QuickLook.shared
|
||||
@State var theme = Theme.shared
|
||||
|
||||
|
||||
@State var selectedTab: Tab = .timeline
|
||||
@State var appRouterPath = RouterPath()
|
||||
|
||||
|
||||
@State var isSupporter: Bool = false
|
||||
|
||||
var body: some Scene {
|
||||
|
@ -79,7 +80,7 @@ struct IceCubesApp: App {
|
|||
}
|
||||
}
|
||||
|
||||
class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
func application(_: UIApplication,
|
||||
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool
|
||||
{
|
||||
|
@ -113,4 +114,11 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
|||
}
|
||||
return configuration
|
||||
}
|
||||
|
||||
override func buildMenu(with builder: UIMenuBuilder) {
|
||||
super.buildMenu(with: builder)
|
||||
builder.remove(menu: .document)
|
||||
builder.remove(menu: .toolbar)
|
||||
builder.remove(menu: .sidebar)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,43 +36,37 @@ public struct ReportView: View {
|
|||
.navigationTitle("report.title")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
#if !os(visionOS)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
#endif
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
isSendingReport = true
|
||||
Task {
|
||||
do {
|
||||
let _: ReportSent =
|
||||
try await client.post(endpoint: Statuses.report(accountId: status.account.id,
|
||||
statusId: status.id,
|
||||
comment: commentText))
|
||||
dismiss()
|
||||
isSendingReport = false
|
||||
} catch {
|
||||
isSendingReport = false
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
isSendingReport = true
|
||||
Task {
|
||||
do {
|
||||
let _: ReportSent =
|
||||
try await client.post(endpoint: Statuses.report(accountId: status.account.id,
|
||||
statusId: status.id,
|
||||
comment: commentText))
|
||||
dismiss()
|
||||
isSendingReport = false
|
||||
} catch {
|
||||
isSendingReport = false
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
if isSendingReport {
|
||||
ProgressView()
|
||||
} else {
|
||||
Text("report.action.send")
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
if isSendingReport {
|
||||
ProgressView()
|
||||
} else {
|
||||
Text("report.action.send")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Text("action.cancel")
|
||||
}
|
||||
CancelToolbarItem()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,25 +13,28 @@ extension View {
|
|||
|
||||
@MainActor
|
||||
private struct SafariRouter: ViewModifier {
|
||||
@Environment(\.isSecondaryColumn) private var isSecondaryColumn: Bool
|
||||
@Environment(Theme.self) private var theme
|
||||
@Environment(UserPreferences.self) private var preferences
|
||||
@Environment(RouterPath.self) private var routerPath
|
||||
|
||||
#if !os(visionOS)
|
||||
@State private var safariManager = InAppSafariManager()
|
||||
@State private var safariManager = InAppSafariManager()
|
||||
#endif
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.environment(\.openURL, OpenURLAction { url in
|
||||
// Open internal URL.
|
||||
routerPath.handle(url: url)
|
||||
guard !isSecondaryColumn else { return .discarded }
|
||||
return routerPath.handle(url: url)
|
||||
})
|
||||
.onOpenURL { url in
|
||||
// Open external URL (from icecubesapp://)
|
||||
guard !isSecondaryColumn else { return }
|
||||
let urlString = url.absoluteString.replacingOccurrences(of: AppInfo.scheme, with: "https://")
|
||||
guard let url = URL(string: urlString), url.host != nil else { return }
|
||||
_ = routerPath.handle(url: url)
|
||||
_ = routerPath.handleDeepLink(url: url)
|
||||
}
|
||||
.onAppear {
|
||||
routerPath.urlHandler = { url in
|
||||
|
@ -52,78 +55,78 @@ private struct SafariRouter: ViewModifier {
|
|||
return .systemAction
|
||||
}
|
||||
#if os(visionOS)
|
||||
return .systemAction
|
||||
return .systemAction
|
||||
#else
|
||||
return safariManager.open(url)
|
||||
return safariManager.open(url)
|
||||
#endif
|
||||
#else
|
||||
return .systemAction
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#if !os(visionOS)
|
||||
#if !os(visionOS)
|
||||
.background {
|
||||
WindowReader { window in
|
||||
safariManager.windowScene = window.windowScene
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if !os(visionOS)
|
||||
@MainActor
|
||||
@Observable private class InAppSafariManager: NSObject, SFSafariViewControllerDelegate {
|
||||
var windowScene: UIWindowScene?
|
||||
let viewController: UIViewController = .init()
|
||||
var window: UIWindow?
|
||||
|
||||
@MainActor
|
||||
func open(_ url: URL) -> OpenURLAction.Result {
|
||||
guard let windowScene else { return .systemAction }
|
||||
@Observable private class InAppSafariManager: NSObject, SFSafariViewControllerDelegate {
|
||||
var windowScene: UIWindowScene?
|
||||
let viewController: UIViewController = .init()
|
||||
var window: UIWindow?
|
||||
|
||||
window = setupWindow(windowScene: windowScene)
|
||||
@MainActor
|
||||
func open(_ url: URL) -> OpenURLAction.Result {
|
||||
guard let windowScene else { return .systemAction }
|
||||
|
||||
let configuration = SFSafariViewController.Configuration()
|
||||
configuration.entersReaderIfAvailable = UserPreferences.shared.inAppBrowserReaderView
|
||||
window = setupWindow(windowScene: windowScene)
|
||||
|
||||
let safari = SFSafariViewController(url: url, configuration: configuration)
|
||||
safari.preferredBarTintColor = UIColor(Theme.shared.primaryBackgroundColor)
|
||||
safari.preferredControlTintColor = UIColor(Theme.shared.tintColor)
|
||||
safari.delegate = self
|
||||
let configuration = SFSafariViewController.Configuration()
|
||||
configuration.entersReaderIfAvailable = UserPreferences.shared.inAppBrowserReaderView
|
||||
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.viewController.present(safari, animated: true)
|
||||
let safari = SFSafariViewController(url: url, configuration: configuration)
|
||||
safari.preferredBarTintColor = UIColor(Theme.shared.primaryBackgroundColor)
|
||||
safari.preferredControlTintColor = UIColor(Theme.shared.tintColor)
|
||||
safari.delegate = self
|
||||
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.viewController.present(safari, animated: true)
|
||||
}
|
||||
|
||||
return .handled
|
||||
}
|
||||
|
||||
return .handled
|
||||
}
|
||||
func setupWindow(windowScene: UIWindowScene) -> UIWindow {
|
||||
let window = window ?? UIWindow(windowScene: windowScene)
|
||||
|
||||
func setupWindow(windowScene: UIWindowScene) -> UIWindow {
|
||||
let window = window ?? UIWindow(windowScene: windowScene)
|
||||
window.rootViewController = viewController
|
||||
window.makeKeyAndVisible()
|
||||
|
||||
window.rootViewController = viewController
|
||||
window.makeKeyAndVisible()
|
||||
switch Theme.shared.selectedScheme {
|
||||
case .dark:
|
||||
window.overrideUserInterfaceStyle = .dark
|
||||
case .light:
|
||||
window.overrideUserInterfaceStyle = .light
|
||||
}
|
||||
|
||||
switch Theme.shared.selectedScheme {
|
||||
case .dark:
|
||||
window.overrideUserInterfaceStyle = .dark
|
||||
case .light:
|
||||
window.overrideUserInterfaceStyle = .light
|
||||
self.window = window
|
||||
return window
|
||||
}
|
||||
|
||||
self.window = window
|
||||
return window
|
||||
}
|
||||
|
||||
nonisolated func safariViewControllerDidFinish(_: SFSafariViewController) {
|
||||
Task { @MainActor in
|
||||
window?.resignKey()
|
||||
window?.isHidden = false
|
||||
window = nil
|
||||
nonisolated func safariViewControllerDidFinish(_: SFSafariViewController) {
|
||||
Task { @MainActor in
|
||||
window?.resignKey()
|
||||
window?.isHidden = false
|
||||
window = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private struct WindowReader: UIViewRepresentable {
|
||||
|
|
|
@ -17,12 +17,12 @@ struct SideBarView<Content: View>: View {
|
|||
@Environment(StreamWatcher.self) private var watcher
|
||||
@Environment(UserPreferences.self) private var userPreferences
|
||||
@Environment(RouterPath.self) private var routerPath
|
||||
|
||||
|
||||
@Binding var selectedTab: Tab
|
||||
@Binding var popToRootTab: Tab
|
||||
var tabs: [Tab]
|
||||
@ViewBuilder var content: () -> Content
|
||||
|
||||
|
||||
@State private var sidebarTabs = SidebarTabs.shared
|
||||
|
||||
private func badgeFor(tab: Tab) -> Int {
|
||||
|
@ -35,16 +35,25 @@ struct SideBarView<Content: View>: View {
|
|||
}
|
||||
|
||||
private func makeIconForTab(tab: Tab) -> some View {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
SideBarIcon(systemIconName: tab.iconName,
|
||||
isSelected: tab == selectedTab)
|
||||
let badge = badgeFor(tab: tab)
|
||||
if badge > 0 {
|
||||
makeBadgeView(count: badge)
|
||||
HStack {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
SideBarIcon(systemIconName: tab.iconName,
|
||||
isSelected: tab == selectedTab)
|
||||
let badge = badgeFor(tab: tab)
|
||||
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)
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.frame(width: .sidebarWidth, height: 50)
|
||||
.frame(width: (userPreferences.isSidebarExpanded ? .sidebarWidthExpanded : .sidebarWidth) - 24, height: 50)
|
||||
.background(tab == selectedTab ? theme.secondaryBackgroundColor : .clear,
|
||||
in: RoundedRectangle(cornerRadius: 8))
|
||||
}
|
||||
|
||||
private func makeBadgeView(count: Int) -> some View {
|
||||
|
@ -74,6 +83,7 @@ struct SideBarView<Content: View>: View {
|
|||
.offset(x: 2, y: -2)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.help(Tab.post.title)
|
||||
}
|
||||
|
||||
private func makeAccountButton(account: AppAccount, showBadge: Bool) -> some View {
|
||||
|
@ -90,9 +100,19 @@ struct SideBarView<Content: View>: View {
|
|||
}
|
||||
} label: {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
AppAccountView(viewModel: .init(appAccount: account, isCompact: true),
|
||||
isParentPresented: .constant(false))
|
||||
if showBadge,
|
||||
if userPreferences.isSidebarExpanded {
|
||||
AppAccountView(viewModel: .init(appAccount: account,
|
||||
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 notificationsCount = userPreferences.notificationsCount[token],
|
||||
notificationsCount > 0
|
||||
|
@ -100,13 +120,23 @@ struct SideBarView<Content: View>: View {
|
|||
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)
|
||||
.background(selectedTab == .profile && account.id == appAccounts.currentAccount.id ?
|
||||
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 {
|
||||
ForEach(tabs) { tab in
|
||||
if tab != .profile && sidebarTabs.isEnabled(tab) {
|
||||
|
@ -131,7 +161,7 @@ struct SideBarView<Content: View>: View {
|
|||
} label: {
|
||||
makeIconForTab(tab: tab)
|
||||
}
|
||||
.background(tab == selectedTab ? theme.secondaryBackgroundColor : .clear)
|
||||
.help(tab.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -153,15 +183,27 @@ struct SideBarView<Content: View>: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
postButton
|
||||
.padding(.top, 12)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.frame(width: .sidebarWidth)
|
||||
.frame(width: userPreferences.isSidebarExpanded ? .sidebarWidthExpanded : .sidebarWidth)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(.thinMaterial)
|
||||
Divider().edgesIgnoringSafeArea(.all)
|
||||
.safeAreaInset(edge: .bottom, content: {
|
||||
HStack(spacing: 16) {
|
||||
postButton
|
||||
.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: userPreferences.isSidebarExpanded ? .sidebarWidthExpanded : .sidebarWidth)
|
||||
.background(.thinMaterial)
|
||||
})
|
||||
Divider().edgesIgnoringSafeArea(.all)
|
||||
}
|
||||
content()
|
||||
}
|
||||
|
@ -191,6 +233,7 @@ private struct SideBarIcon: View {
|
|||
self.isHovered = isHovered
|
||||
}
|
||||
}
|
||||
.frame(width: 50, height: 40)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import Env
|
|||
import Explore
|
||||
import Models
|
||||
import Network
|
||||
import Shimmer
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
|
@ -22,7 +21,7 @@ struct ExploreTab: View {
|
|||
ExploreView(scrollToTopSignal: $scrollToTopSignal)
|
||||
.withAppRouter()
|
||||
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
|
||||
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.50), for: .navigationBar)
|
||||
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.30), for: .navigationBar)
|
||||
.toolbar {
|
||||
ToolbarTab(routerPath: $routerPath)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import DesignSystem
|
|||
import Env
|
||||
import Models
|
||||
import Network
|
||||
import Shimmer
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
|
@ -27,7 +26,7 @@ struct MessagesTab: View {
|
|||
.toolbar {
|
||||
ToolbarTab(routerPath: $routerPath)
|
||||
}
|
||||
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.50), for: .navigationBar)
|
||||
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.30), for: .navigationBar)
|
||||
.id(client.id)
|
||||
}
|
||||
.onChange(of: $popToRootTab.wrappedValue) { _, newValue in
|
||||
|
|
|
@ -1,29 +1,23 @@
|
|||
import SwiftUI
|
||||
import Env
|
||||
import AppAccount
|
||||
import DesignSystem
|
||||
import Env
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
struct NavigationSheet<Content: View>: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
|
||||
var content: () -> Content
|
||||
|
||||
|
||||
init(@ViewBuilder content: @escaping () -> Content) {
|
||||
self.content = content
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
content()
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Image(systemName: "xmark.circle")
|
||||
}
|
||||
}
|
||||
CloseToolbarItem()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
import SwiftUI
|
||||
import Env
|
||||
import AppAccount
|
||||
import DesignSystem
|
||||
import Env
|
||||
import Network
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
struct NavigationTab<Content: View>: View {
|
||||
@Environment(\.isSecondaryColumn) private var isSecondaryColumn: Bool
|
||||
|
||||
|
||||
@Environment(AppAccountsManager.self) private var appAccount
|
||||
@Environment(CurrentAccount.self) private var currentAccount
|
||||
@Environment(UserPreferences.self) private var userPreferences
|
||||
@Environment(Theme.self) private var theme
|
||||
@Environment(Client.self) private var client
|
||||
|
||||
|
||||
var content: () -> Content
|
||||
|
||||
|
||||
@State private var routerPath = RouterPath()
|
||||
|
||||
|
||||
init(@ViewBuilder content: @escaping () -> Content) {
|
||||
self.content = content
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
NavigationStack(path: $routerPath.path) {
|
||||
content()
|
||||
|
@ -32,7 +32,7 @@ struct NavigationTab<Content: View>: View {
|
|||
.toolbar {
|
||||
ToolbarTab(routerPath: $routerPath)
|
||||
}
|
||||
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.50), for: .navigationBar)
|
||||
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.30), for: .navigationBar)
|
||||
.onChange(of: client.id) {
|
||||
routerPath.path = []
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ struct NotificationsTab: View {
|
|||
@Environment(PushNotificationsService.self) private var pushNotificationsService
|
||||
@State private var routerPath = RouterPath()
|
||||
@State private var scrollToTopSignal: Int = 0
|
||||
|
||||
|
||||
@Binding var selectedTab: Tab
|
||||
@Binding var popToRootTab: Tab
|
||||
|
||||
|
@ -42,7 +42,7 @@ struct NotificationsTab: View {
|
|||
}
|
||||
ToolbarTab(routerPath: $routerPath)
|
||||
}
|
||||
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.50), for: .navigationBar)
|
||||
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.30), for: .navigationBar)
|
||||
.id(client.id)
|
||||
}
|
||||
.onAppear {
|
||||
|
@ -60,9 +60,9 @@ struct NotificationsTab: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: selectedTab, { _, newValue in
|
||||
.onChange(of: selectedTab) { _, _ in
|
||||
clearNotifications()
|
||||
})
|
||||
}
|
||||
.onChange(of: pushNotificationsService.handledNotification) { _, newValue in
|
||||
if let newValue, let type = newValue.notification.supportedType {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||
|
|
|
@ -5,7 +5,6 @@ import DesignSystem
|
|||
import Env
|
||||
import Models
|
||||
import Network
|
||||
import Shimmer
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
|
@ -24,7 +23,7 @@ struct ProfileTab: View {
|
|||
AccountDetailView(account: account, scrollToTopSignal: $scrollToTopSignal)
|
||||
.withAppRouter()
|
||||
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
|
||||
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.50), for: .navigationBar)
|
||||
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.30), for: .navigationBar)
|
||||
.id(account.id)
|
||||
} else {
|
||||
AccountDetailView(account: .placeholder(), scrollToTopSignal: $scrollToTopSignal)
|
||||
|
|
|
@ -27,27 +27,27 @@ struct AboutView: View {
|
|||
var body: some View {
|
||||
List {
|
||||
Section {
|
||||
#if !targetEnvironment(macCatalyst)
|
||||
HStack {
|
||||
Spacer()
|
||||
Image(uiImage: .init(named: "AppIconAlternate0")!)
|
||||
.resizable()
|
||||
.frame(width: 50, height: 50)
|
||||
.cornerRadius(4)
|
||||
Image(uiImage: .init(named: "AppIconAlternate4")!)
|
||||
.resizable()
|
||||
.frame(width: 50, height: 50)
|
||||
.cornerRadius(4)
|
||||
Image(uiImage: .init(named: "AppIconAlternate17")!)
|
||||
.resizable()
|
||||
.frame(width: 50, height: 50)
|
||||
.cornerRadius(4)
|
||||
Image(uiImage: .init(named: "AppIconAlternate23")!)
|
||||
.resizable()
|
||||
.frame(width: 50, height: 50)
|
||||
.cornerRadius(4)
|
||||
Spacer()
|
||||
}
|
||||
#if !targetEnvironment(macCatalyst) && !os(visionOS)
|
||||
HStack {
|
||||
Spacer()
|
||||
Image(uiImage: .init(named: "AppIconAlternate0")!)
|
||||
.resizable()
|
||||
.frame(width: 50, height: 50)
|
||||
.cornerRadius(4)
|
||||
Image(uiImage: .init(named: "AppIconAlternate4")!)
|
||||
.resizable()
|
||||
.frame(width: 50, height: 50)
|
||||
.cornerRadius(4)
|
||||
Image(uiImage: .init(named: "AppIconAlternate17")!)
|
||||
.resizable()
|
||||
.frame(width: 50, height: 50)
|
||||
.cornerRadius(4)
|
||||
Image(uiImage: .init(named: "AppIconAlternate23")!)
|
||||
.resizable()
|
||||
.frame(width: 50, height: 50)
|
||||
.cornerRadius(4)
|
||||
Spacer()
|
||||
}
|
||||
#endif
|
||||
Link(destination: URL(string: "https://github.com/Dimillian/IceCubesApp/blob/main/PRIVACY.MD")!) {
|
||||
Label("settings.support.privacy-policy", systemImage: "lock")
|
||||
|
@ -57,7 +57,7 @@ struct AboutView: View {
|
|||
Label("settings.support.terms-of-use", systemImage: "checkmark.shield")
|
||||
}
|
||||
} footer: {
|
||||
Text("\(versionNumber)©2023 Thomas Ricouard")
|
||||
Text("\(versionNumber)© 2024 Thomas Ricouard")
|
||||
}
|
||||
#if !os(visionOS)
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
|
@ -107,14 +107,14 @@ struct AboutView: View {
|
|||
}
|
||||
.listStyle(.insetGrouped)
|
||||
#if !os(visionOS)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
#endif
|
||||
.navigationTitle(Text("settings.about.title"))
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
.environment(\.openURL, OpenURLAction { url in
|
||||
routerPath.handle(url: url)
|
||||
})
|
||||
.navigationTitle(Text("settings.about.title"))
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
.environment(\.openURL, OpenURLAction { url in
|
||||
routerPath.handle(url: url)
|
||||
})
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
|
|
|
@ -17,9 +17,8 @@ struct AccountSettingsView: View {
|
|||
@Environment(Theme.self) private var theme
|
||||
@Environment(AppAccountsManager.self) private var appAccountsManager
|
||||
@Environment(Client.self) private var client
|
||||
@Environment(RouterPath.self) private var routerPath
|
||||
|
||||
@State private var isEditingAccount: Bool = false
|
||||
@State private var isEditingFilters: Bool = false
|
||||
@State private var cachedPostsCount: Int = 0
|
||||
@State private var timelineCache = TimelineCache()
|
||||
|
||||
|
@ -30,7 +29,7 @@ struct AccountSettingsView: View {
|
|||
Form {
|
||||
Section {
|
||||
Button {
|
||||
isEditingAccount = true
|
||||
routerPath.presentedSheet = .accountEditInfo
|
||||
} label: {
|
||||
Label("account.action.edit-info", systemImage: "pencil")
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
@ -40,7 +39,7 @@ struct AccountSettingsView: View {
|
|||
|
||||
if currentInstance.isFiltersSupported {
|
||||
Button {
|
||||
isEditingFilters = true
|
||||
routerPath.presentedSheet = .accountFiltersList
|
||||
} label: {
|
||||
Label("account.action.edit-filters", systemImage: "line.3.horizontal.decrease.circle")
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
@ -96,12 +95,6 @@ struct AccountSettingsView: View {
|
|||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
}
|
||||
.sheet(isPresented: $isEditingAccount, content: {
|
||||
EditAccountView()
|
||||
})
|
||||
.sheet(isPresented: $isEditingFilters, content: {
|
||||
FiltersListView()
|
||||
})
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .principal) {
|
||||
HStack {
|
||||
|
@ -116,8 +109,8 @@ struct AccountSettingsView: View {
|
|||
}
|
||||
.navigationTitle(account.safeDisplayName)
|
||||
#if !os(visionOS)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import Models
|
|||
import Network
|
||||
import NukeUI
|
||||
import SafariServices
|
||||
import Shimmer
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
|
@ -83,77 +82,75 @@ struct AddAccountView: View {
|
|||
.navigationTitle("account.add.navigation-title")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
#if !os(visionOS)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
#endif
|
||||
.toolbar {
|
||||
if !appAccountsManager.availableAccounts.isEmpty {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("action.cancel", action: { dismiss() })
|
||||
.toolbar {
|
||||
if !appAccountsManager.availableAccounts.isEmpty {
|
||||
CancelToolbarItem()
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
isInstanceURLFieldFocused = true
|
||||
Task {
|
||||
let instances = await instanceSocialClient.fetchInstances(keyword: instanceName)
|
||||
withAnimation {
|
||||
self.instances = instances
|
||||
}
|
||||
}
|
||||
isSigninIn = false
|
||||
}
|
||||
.onChange(of: instanceName) {
|
||||
searchingTask.cancel()
|
||||
searchingTask = Task {
|
||||
try? await Task.sleep(for: .seconds(0.1))
|
||||
guard !Task.isCancelled else { return }
|
||||
|
||||
let instances = await instanceSocialClient.fetchInstances(keyword: instanceName)
|
||||
withAnimation {
|
||||
self.instances = instances
|
||||
}
|
||||
}
|
||||
|
||||
getInstanceDetailTask.cancel()
|
||||
getInstanceDetailTask = Task {
|
||||
try? await Task.sleep(for: .seconds(0.1))
|
||||
guard !Task.isCancelled else { return }
|
||||
|
||||
do {
|
||||
// bare bones preflight for domain validity
|
||||
let instanceDetailClient = Client(server: sanitizedName)
|
||||
if
|
||||
instanceDetailClient.server.contains("."),
|
||||
instanceDetailClient.server.last != "."
|
||||
{
|
||||
let instance: Instance = try await instanceDetailClient.get(endpoint: Instances.instance)
|
||||
withAnimation {
|
||||
self.instance = instance
|
||||
instanceName = sanitizedName // clean up the text box, principally to chop off the username if present so it's clear that you might not wind up siging in as the thing in the box
|
||||
}
|
||||
instanceFetchError = nil
|
||||
} else {
|
||||
instance = nil
|
||||
instanceFetchError = nil
|
||||
.onAppear {
|
||||
isInstanceURLFieldFocused = true
|
||||
Task {
|
||||
let instances = await instanceSocialClient.fetchInstances(keyword: instanceName)
|
||||
withAnimation {
|
||||
self.instances = instances
|
||||
}
|
||||
}
|
||||
isSigninIn = false
|
||||
}
|
||||
.onChange(of: instanceName) {
|
||||
searchingTask.cancel()
|
||||
searchingTask = Task {
|
||||
try? await Task.sleep(for: .seconds(0.1))
|
||||
guard !Task.isCancelled else { return }
|
||||
|
||||
let instances = await instanceSocialClient.fetchInstances(keyword: instanceName)
|
||||
withAnimation {
|
||||
self.instances = instances
|
||||
}
|
||||
}
|
||||
|
||||
getInstanceDetailTask.cancel()
|
||||
getInstanceDetailTask = Task {
|
||||
try? await Task.sleep(for: .seconds(0.1))
|
||||
guard !Task.isCancelled else { return }
|
||||
|
||||
do {
|
||||
// bare bones preflight for domain validity
|
||||
let instanceDetailClient = Client(server: sanitizedName)
|
||||
if
|
||||
instanceDetailClient.server.contains("."),
|
||||
instanceDetailClient.server.last != "."
|
||||
{
|
||||
let instance: Instance = try await instanceDetailClient.get(endpoint: Instances.instance)
|
||||
withAnimation {
|
||||
self.instance = instance
|
||||
instanceName = sanitizedName // clean up the text box, principally to chop off the username if present so it's clear that you might not wind up siging in as the thing in the box
|
||||
}
|
||||
instanceFetchError = nil
|
||||
} else {
|
||||
instance = nil
|
||||
instanceFetchError = nil
|
||||
}
|
||||
} catch _ as DecodingError {
|
||||
instance = nil
|
||||
instanceFetchError = "account.add.error.instance-not-supported"
|
||||
} catch {
|
||||
instance = nil
|
||||
}
|
||||
} catch _ as DecodingError {
|
||||
instance = nil
|
||||
instanceFetchError = "account.add.error.instance-not-supported"
|
||||
} catch {
|
||||
instance = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: scenePhase) { _, newValue in
|
||||
switch newValue {
|
||||
case .active:
|
||||
isSigninIn = false
|
||||
default:
|
||||
break
|
||||
.onChange(of: scenePhase) { _, newValue in
|
||||
switch newValue {
|
||||
case .active:
|
||||
isSigninIn = false
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -217,9 +214,9 @@ struct AddAccountView: View {
|
|||
.foregroundColor(.primary)
|
||||
Spacer()
|
||||
(Text("instance.list.users-\(formatAsNumber(instance.users))")
|
||||
+ Text(" ⸱ ")
|
||||
+ Text("instance.list.posts-\(formatAsNumber(instance.statuses))"))
|
||||
.foregroundStyle(theme.tintColor)
|
||||
+ Text(" ⸱ ")
|
||||
+ Text("instance.list.posts-\(formatAsNumber(instance.statuses))"))
|
||||
.foregroundStyle(theme.tintColor)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
Text(instance.info?.shortDescription?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "")
|
||||
|
@ -265,9 +262,8 @@ struct AddAccountView: View {
|
|||
}
|
||||
.redacted(reason: .placeholder)
|
||||
.allowsHitTesting(false)
|
||||
.shimmering()
|
||||
#if !os(visionOS)
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -306,13 +302,3 @@ struct AddAccountView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SafariView: UIViewControllerRepresentable {
|
||||
let url: URL
|
||||
|
||||
func makeUIViewController(context _: UIViewControllerRepresentableContext<SafariView>) -> SFSafariViewController {
|
||||
SFSafariViewController(url: url)
|
||||
}
|
||||
|
||||
func updateUIViewController(_: SFSafariViewController, context _: UIViewControllerRepresentableContext<SafariView>) {}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import Models
|
|||
import Network
|
||||
import NukeUI
|
||||
import SwiftUI
|
||||
import Timeline
|
||||
import UserNotifications
|
||||
|
||||
@MainActor
|
||||
|
@ -12,18 +13,11 @@ struct ContentSettingsView: View {
|
|||
@Environment(UserPreferences.self) private var userPreferences
|
||||
@Environment(Theme.self) private var theme
|
||||
|
||||
@State private var contentFilter = TimelineContentFilter.shared
|
||||
|
||||
var body: some View {
|
||||
@Bindable var userPreferences = userPreferences
|
||||
Form {
|
||||
Section("settings.content.boosts") {
|
||||
Toggle(isOn: $userPreferences.suppressDupeReblogs) {
|
||||
Text("settings.content.hide-repeated-boosts")
|
||||
}
|
||||
}
|
||||
#if !os(visionOS)
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
#endif
|
||||
|
||||
Section("settings.content.media") {
|
||||
Toggle(isOn: $userPreferences.autoPlayVideo) {
|
||||
Text("settings.other.autoplay-video")
|
||||
|
@ -47,7 +41,7 @@ struct ContentSettingsView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
#if !os(visionOS)
|
||||
#if !os(visionOS)
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
#endif
|
||||
|
||||
|
@ -65,6 +59,7 @@ struct ContentSettingsView: View {
|
|||
userPreferences.appAutoExpandMedia = userPreferences.autoExpandMedia
|
||||
userPreferences.appDefaultPostsSensitive = userPreferences.postIsSensitive
|
||||
userPreferences.appDefaultPostVisibility = userPreferences.postVisibility
|
||||
userPreferences.appRequireAltText = userPreferences.appRequireAltText
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,7 +84,7 @@ struct ContentSettingsView: View {
|
|||
} footer: {
|
||||
Text("settings.content.collapse-long-posts-hint")
|
||||
}
|
||||
#if !os(visionOS)
|
||||
#if !os(visionOS)
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
#endif
|
||||
|
||||
|
@ -118,6 +113,28 @@ struct ContentSettingsView: View {
|
|||
Text("settings.content.default-sensitive")
|
||||
}
|
||||
.disabled(userPreferences.useInstanceContentSettings)
|
||||
|
||||
Toggle(isOn: $userPreferences.appRequireAltText) {
|
||||
Text("settings.content.require-alt-text")
|
||||
}
|
||||
}
|
||||
#if !os(visionOS)
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
#endif
|
||||
|
||||
Section("timeline.content-filter.title") {
|
||||
Toggle(isOn: $contentFilter.showBoosts) {
|
||||
Label("timeline.filter.show-boosts", image: "Rocket")
|
||||
}
|
||||
Toggle(isOn: $contentFilter.showReplies) {
|
||||
Label("timeline.filter.show-replies", systemImage: "bubble.left.and.bubble.right")
|
||||
}
|
||||
Toggle(isOn: $contentFilter.showThreads) {
|
||||
Label("timeline.filter.show-threads", systemImage: "bubble.left.and.text.bubble.right")
|
||||
}
|
||||
Toggle(isOn: $contentFilter.showQuotePosts) {
|
||||
Label("timeline.filter.show-quote", systemImage: "quote.bubble")
|
||||
}
|
||||
}
|
||||
#if !os(visionOS)
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
|
@ -125,8 +142,8 @@ struct ContentSettingsView: View {
|
|||
}
|
||||
.navigationTitle("settings.content.navigation-title")
|
||||
#if !os(visionOS)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import Observation
|
|||
import StatusKit
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
@Observable class DisplaySettingsLocalValues {
|
||||
var tintColor = Theme.shared.tintColor
|
||||
var primaryBackgroundColor = Theme.shared.primaryBackgroundColor
|
||||
|
@ -35,11 +36,13 @@ struct DisplaySettingsView: View {
|
|||
var body: some View {
|
||||
ZStack(alignment: .top) {
|
||||
Form {
|
||||
StatusRowView(viewModel: previewStatusViewModel)
|
||||
.allowsHitTesting(false)
|
||||
.opacity(0)
|
||||
.hidden()
|
||||
themeSection
|
||||
#if !os(visionOS)
|
||||
StatusRowView(viewModel: previewStatusViewModel)
|
||||
.allowsHitTesting(false)
|
||||
.opacity(0)
|
||||
.hidden()
|
||||
themeSection
|
||||
#endif
|
||||
fontSection
|
||||
layoutSection
|
||||
platformsSection
|
||||
|
@ -47,34 +50,36 @@ struct DisplaySettingsView: View {
|
|||
}
|
||||
.navigationTitle("settings.display.navigation-title")
|
||||
#if !os(visionOS)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
#endif
|
||||
.task(id: localValues.tintColor) {
|
||||
do { try await Task.sleep(for: .microseconds(500)) } catch {}
|
||||
theme.tintColor = localValues.tintColor
|
||||
}
|
||||
.task(id: localValues.primaryBackgroundColor) {
|
||||
do { try await Task.sleep(for: .microseconds(500)) } catch {}
|
||||
theme.primaryBackgroundColor = localValues.primaryBackgroundColor
|
||||
}
|
||||
.task(id: localValues.secondaryBackgroundColor) {
|
||||
do { try await Task.sleep(for: .microseconds(500)) } catch {}
|
||||
theme.secondaryBackgroundColor = localValues.secondaryBackgroundColor
|
||||
}
|
||||
.task(id: localValues.labelColor) {
|
||||
do { try await Task.sleep(for: .microseconds(500)) } catch {}
|
||||
theme.labelColor = localValues.labelColor
|
||||
}
|
||||
.task(id: localValues.lineSpacing) {
|
||||
do { try await Task.sleep(for: .microseconds(500)) } catch {}
|
||||
theme.lineSpacing = localValues.lineSpacing
|
||||
}
|
||||
.task(id: localValues.fontSizeScale) {
|
||||
do { try await Task.sleep(for: .microseconds(500)) } catch {}
|
||||
theme.fontSizeScale = localValues.fontSizeScale
|
||||
}
|
||||
#if !os(visionOS)
|
||||
examplePost
|
||||
#endif
|
||||
.task(id: localValues.tintColor) {
|
||||
do { try await Task.sleep(for: .microseconds(500)) } catch {}
|
||||
theme.tintColor = localValues.tintColor
|
||||
}
|
||||
.task(id: localValues.primaryBackgroundColor) {
|
||||
do { try await Task.sleep(for: .microseconds(500)) } catch {}
|
||||
theme.primaryBackgroundColor = localValues.primaryBackgroundColor
|
||||
}
|
||||
.task(id: localValues.secondaryBackgroundColor) {
|
||||
do { try await Task.sleep(for: .microseconds(500)) } catch {}
|
||||
theme.secondaryBackgroundColor = localValues.secondaryBackgroundColor
|
||||
}
|
||||
.task(id: localValues.labelColor) {
|
||||
do { try await Task.sleep(for: .microseconds(500)) } catch {}
|
||||
theme.labelColor = localValues.labelColor
|
||||
}
|
||||
.task(id: localValues.lineSpacing) {
|
||||
do { try await Task.sleep(for: .microseconds(500)) } catch {}
|
||||
theme.lineSpacing = localValues.lineSpacing
|
||||
}
|
||||
.task(id: localValues.fontSizeScale) {
|
||||
do { try await Task.sleep(for: .microseconds(500)) } catch {}
|
||||
theme.fontSizeScale = localValues.fontSizeScale
|
||||
}
|
||||
examplePost
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -243,7 +248,7 @@ struct DisplaySettingsView: View {
|
|||
@Bindable var userPreferences = userPreferences
|
||||
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
Section("iPad") {
|
||||
Section("settings.display.section.platform") {
|
||||
Toggle("settings.display.show-ipad-column", isOn: $userPreferences.showiPadSecondaryColumn)
|
||||
}
|
||||
#if !os(visionOS)
|
||||
|
@ -255,21 +260,7 @@ struct DisplaySettingsView: View {
|
|||
private var resetSection: some View {
|
||||
Section {
|
||||
Button {
|
||||
theme.followSystemColorScheme = true
|
||||
theme.applySet(set: colorScheme == .dark ? .iceCubeDark : .iceCubeLight)
|
||||
theme.avatarShape = .circle
|
||||
theme.avatarPosition = .leading
|
||||
theme.statusActionsDisplay = .full
|
||||
theme.displayFullUsername = false
|
||||
theme.statusDisplayStyle = .large
|
||||
theme.lineSpacing = 1.2
|
||||
theme.fontSizeScale = 1.0
|
||||
|
||||
localValues.tintColor = theme.tintColor
|
||||
localValues.primaryBackgroundColor = theme.primaryBackgroundColor
|
||||
localValues.secondaryBackgroundColor = theme.secondaryBackgroundColor
|
||||
localValues.labelColor = theme.labelColor
|
||||
|
||||
theme.restoreDefault()
|
||||
} label: {
|
||||
Text("settings.display.restore")
|
||||
}
|
||||
|
|
|
@ -23,8 +23,8 @@ struct HapticSettingsView: View {
|
|||
}
|
||||
.navigationTitle("settings.haptic.navigation-title")
|
||||
#if !os(visionOS)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ struct IconSelectorView: View {
|
|||
case alt38
|
||||
case alt39, alt40, alt41, alt42, alt43
|
||||
case alt44, alt45
|
||||
case alt46
|
||||
|
||||
var appIconName: String {
|
||||
return "AppIconAlternate\(rawValue)"
|
||||
|
@ -39,7 +40,7 @@ struct IconSelectorView: View {
|
|||
|
||||
static let items = [
|
||||
IconSelector(title: "settings.app.icon.official".localized, icons: [
|
||||
.primary, .alt1, .alt2, .alt3, .alt4,
|
||||
.primary, .alt46, .alt1, .alt2, .alt3, .alt4,
|
||||
.alt5, .alt6, .alt7, .alt8,
|
||||
.alt9, .alt10, .alt11, .alt12, .alt13, .alt14, .alt15,
|
||||
.alt16, .alt17, .alt18, .alt19, .alt20, .alt21]),
|
||||
|
|
|
@ -14,8 +14,8 @@ struct InstanceInfoView: View {
|
|||
}
|
||||
.navigationTitle("instance.info.navigation-title")
|
||||
#if !os(visionOS)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,12 +111,12 @@ struct PushNotificationsView: View {
|
|||
}
|
||||
.navigationTitle("settings.push.navigation-title")
|
||||
#if !os(visionOS)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
#endif
|
||||
.task {
|
||||
await subscription.fetchSubscription()
|
||||
}
|
||||
.task {
|
||||
await subscription.fetchSubscription()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateSubscription() {
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import SwiftUI
|
||||
import SwiftData
|
||||
import Models
|
||||
import Env
|
||||
import DesignSystem
|
||||
import Env
|
||||
import Models
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
|
||||
struct RecenTagsSettingView: View {
|
||||
@Environment(\.modelContext) private var context
|
||||
|
||||
|
||||
@Environment(RouterPath.self) private var routerPath
|
||||
@Environment(Theme.self) private var theme
|
||||
|
||||
|
||||
@Query(sort: \RecentTag.lastUse, order: .reverse) var tags: [RecentTag]
|
||||
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
ForEach(tags) { tag in
|
||||
|
@ -35,10 +35,10 @@ struct RecenTagsSettingView: View {
|
|||
.navigationTitle("settings.general.recent-tags")
|
||||
.scrollContentBackground(.hidden)
|
||||
#if !os(visionOS)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
#endif
|
||||
.toolbar {
|
||||
EditButton()
|
||||
}
|
||||
.toolbar {
|
||||
EditButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import SwiftUI
|
||||
import SwiftData
|
||||
import Models
|
||||
import Env
|
||||
import DesignSystem
|
||||
import Env
|
||||
import Models
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
|
||||
struct RemoteTimelinesSettingView: View {
|
||||
@Environment(\.modelContext) private var context
|
||||
|
||||
|
||||
@Environment(RouterPath.self) private var routerPath
|
||||
@Environment(Theme.self) private var theme
|
||||
|
||||
|
||||
@Query(sort: \LocalTimeline.creationDate, order: .reverse) var localTimelines: [LocalTimeline]
|
||||
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
ForEach(localTimelines) { timeline in
|
||||
|
@ -36,10 +36,10 @@ struct RemoteTimelinesSettingView: View {
|
|||
.navigationTitle("settings.general.remote-timelines")
|
||||
.scrollContentBackground(.hidden)
|
||||
#if !os(visionOS)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
#endif
|
||||
.toolbar {
|
||||
EditButton()
|
||||
}
|
||||
.toolbar {
|
||||
EditButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,9 @@ struct SettingsTabs: View {
|
|||
@Binding var popToRootTab: Tab
|
||||
|
||||
let isModal: Bool
|
||||
|
||||
|
||||
@State private var startingPoint: SettingsStartingPoint? = nil
|
||||
|
||||
var body: some View {
|
||||
NavigationStack(path: $routerPath.path) {
|
||||
Form {
|
||||
|
@ -43,27 +45,53 @@ struct SettingsTabs: View {
|
|||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
#if !os(visionOS)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
#endif
|
||||
.navigationTitle(Text("settings.title"))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.50), for: .navigationBar)
|
||||
.toolbar {
|
||||
if isModal {
|
||||
ToolbarItem {
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Text("action.done").bold()
|
||||
.navigationTitle(Text("settings.title"))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.30), for: .navigationBar)
|
||||
.toolbar {
|
||||
if isModal {
|
||||
ToolbarItem {
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Text("action.done").bold()
|
||||
}
|
||||
}
|
||||
}
|
||||
if UIDevice.current.userInterfaceIdiom == .pad, !preferences.showiPadSecondaryColumn, !isModal {
|
||||
SecondaryColumnToolbarItem()
|
||||
}
|
||||
}
|
||||
if UIDevice.current.userInterfaceIdiom == .pad, !preferences.showiPadSecondaryColumn, !isModal {
|
||||
SecondaryColumnToolbarItem()
|
||||
.withAppRouter()
|
||||
.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
.withAppRouter()
|
||||
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
|
||||
}
|
||||
.onAppear {
|
||||
routerPath.client = client
|
||||
|
|
|
@ -2,10 +2,11 @@ import DesignSystem
|
|||
import Env
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
struct SidebarEntriesSettingsView: View {
|
||||
@Environment(Theme.self) private var theme
|
||||
@Environment(UserPreferences.self) private var userPreferences
|
||||
|
||||
|
||||
@State private var sidebarTabs = SidebarTabs.shared
|
||||
|
||||
var body: some View {
|
||||
|
@ -28,11 +29,11 @@ struct SidebarEntriesSettingsView: View {
|
|||
.environment(\.editMode, .constant(.active))
|
||||
.navigationTitle("settings.general.sidebarEntries")
|
||||
#if !os(visionOS)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
func move(from source: IndexSet, to destination: Int) {
|
||||
sidebarTabs.tabs.move(fromOffsets: source, toOffset: destination)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import DesignSystem
|
||||
import Env
|
||||
import RevenueCat
|
||||
import Shimmer
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
|
@ -70,24 +69,24 @@ struct SupportAppView: View {
|
|||
}
|
||||
.navigationTitle("settings.support.navigation-title")
|
||||
#if !os(visionOS)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
#endif
|
||||
.alert("settings.support.alert.title", isPresented: $purchaseSuccessDisplayed, actions: {
|
||||
Button { purchaseSuccessDisplayed = false } label: { Text("alert.button.ok") }
|
||||
}, message: {
|
||||
Text("settings.support.alert.message")
|
||||
})
|
||||
.alert("alert.error", isPresented: $purchaseErrorDisplayed, actions: {
|
||||
Button { purchaseErrorDisplayed = false } label: { Text("alert.button.ok") }
|
||||
}, message: {
|
||||
Text("settings.support.alert.error.message")
|
||||
})
|
||||
.onAppear {
|
||||
loadingProducts = true
|
||||
fetchStoreProducts()
|
||||
refreshUserInfo()
|
||||
}
|
||||
.alert("settings.support.alert.title", isPresented: $purchaseSuccessDisplayed, actions: {
|
||||
Button { purchaseSuccessDisplayed = false } label: { Text("alert.button.ok") }
|
||||
}, message: {
|
||||
Text("settings.support.alert.message")
|
||||
})
|
||||
.alert("alert.error", isPresented: $purchaseErrorDisplayed, actions: {
|
||||
Button { purchaseErrorDisplayed = false } label: { Text("alert.button.ok") }
|
||||
}, message: {
|
||||
Text("settings.support.alert.error.message")
|
||||
})
|
||||
.onAppear {
|
||||
loadingProducts = true
|
||||
fetchStoreProducts()
|
||||
refreshUserInfo()
|
||||
}
|
||||
}
|
||||
|
||||
private func purchase(product: StoreProduct) async {
|
||||
|
@ -280,6 +279,5 @@ struct SupportAppView: View {
|
|||
}
|
||||
.redacted(reason: .placeholder)
|
||||
.allowsHitTesting(false)
|
||||
.shimmering()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ struct SwipeActionsSettingsView: View {
|
|||
#if !os(visionOS)
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
#endif
|
||||
|
||||
|
||||
Section {
|
||||
Picker(selection: $userPreferences.swipeActionsIconStyle, label: Text("settings.swipeactions.icon-style")) {
|
||||
ForEach(UserPreferences.SwipeActionsIconStyle.allCases, id: \.rawValue) { style in
|
||||
|
@ -70,8 +70,8 @@ struct SwipeActionsSettingsView: View {
|
|||
}
|
||||
.navigationTitle("settings.swipeactions.navigation-title")
|
||||
#if !os(visionOS)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import SwiftUI
|
|||
struct TabbarEntriesSettingsView: View {
|
||||
@Environment(Theme.self) private var theme
|
||||
@Environment(UserPreferences.self) private var userPreferences
|
||||
|
||||
|
||||
@State private var tabs = iOSTabs.shared
|
||||
|
||||
var body: some View {
|
||||
|
@ -42,7 +42,7 @@ struct TabbarEntriesSettingsView: View {
|
|||
#if !os(visionOS)
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
#endif
|
||||
|
||||
|
||||
Section {
|
||||
Toggle("settings.display.show-tab-label", isOn: $userPreferences.showiPhoneTabLabel)
|
||||
}
|
||||
|
@ -52,8 +52,8 @@ struct TabbarEntriesSettingsView: View {
|
|||
}
|
||||
.navigationTitle("settings.general.tabbarEntries")
|
||||
#if !os(visionOS)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import SwiftUI
|
||||
import SwiftData
|
||||
import Models
|
||||
import Env
|
||||
import DesignSystem
|
||||
import Env
|
||||
import Models
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
|
||||
struct TagsGroupSettingView: View {
|
||||
@Environment(\.modelContext) private var context
|
||||
|
||||
|
||||
@Environment(RouterPath.self) private var routerPath
|
||||
@Environment(Theme.self) private var theme
|
||||
|
||||
|
||||
@Query(sort: \TagGroup.creationDate, order: .reverse) var tagGroups: [TagGroup]
|
||||
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
ForEach(tagGroups) { group in
|
||||
|
@ -41,10 +41,10 @@ struct TagsGroupSettingView: View {
|
|||
.navigationTitle("timeline.filter.tag-groups")
|
||||
.scrollContentBackground(.hidden)
|
||||
#if !os(visionOS)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
#endif
|
||||
.toolbar {
|
||||
EditButton()
|
||||
}
|
||||
.toolbar {
|
||||
EditButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,20 +11,17 @@ struct TranslationSettingsView: View {
|
|||
|
||||
var body: some View {
|
||||
Form {
|
||||
deepLToggle
|
||||
if preferences.alwaysUseDeepl {
|
||||
translationSelector
|
||||
if preferences.preferredTranslationType == .useDeepl {
|
||||
Section("settings.translation.user-api-key") {
|
||||
deepLPicker
|
||||
SecureField("settings.translation.user-api-key", text: $apiKey)
|
||||
.textContentType(.password)
|
||||
}
|
||||
.onAppear {
|
||||
readValue()
|
||||
}
|
||||
#if !os(visionOS)
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
#endif
|
||||
|
||||
|
||||
if apiKey.isEmpty {
|
||||
Section {
|
||||
Link(destination: URL(string: "https://www.deepl.com/pro-api")!) {
|
||||
|
@ -37,30 +34,51 @@ struct TranslationSettingsView: View {
|
|||
#endif
|
||||
}
|
||||
}
|
||||
backgroundAPIKey
|
||||
autoDetectSection
|
||||
}
|
||||
.navigationTitle("settings.translation.navigation-title")
|
||||
#if !os(visionOS)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
#endif
|
||||
.onChange(of: apiKey) {
|
||||
writeNewValue()
|
||||
}
|
||||
.onAppear(perform: updatePrefs)
|
||||
.onChange(of: apiKey) {
|
||||
writeNewValue()
|
||||
}
|
||||
.onAppear(perform: updatePrefs)
|
||||
.onAppear(perform: readValue)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var deepLToggle: some View {
|
||||
private var translationSelector: some View {
|
||||
@Bindable var preferences = preferences
|
||||
Toggle(isOn: $preferences.alwaysUseDeepl) {
|
||||
Text("settings.translation.always-deepl")
|
||||
Picker("Translation Service", selection: $preferences.preferredTranslationType) {
|
||||
ForEach(allTTCases, id: \.self) { type in
|
||||
Text(type.description).tag(type)
|
||||
}
|
||||
}
|
||||
#if !os(visionOS)
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
#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
|
||||
private var deepLPicker: some View {
|
||||
@Bindable var preferences = preferences
|
||||
|
@ -80,6 +98,34 @@ struct TranslationSettingsView: View {
|
|||
} 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() {
|
||||
|
@ -91,11 +137,7 @@ struct TranslationSettingsView: View {
|
|||
}
|
||||
|
||||
private func readValue() {
|
||||
if let apiKey = DeepLUserAPIHandler.readIfAllowed() {
|
||||
self.apiKey = apiKey
|
||||
} else {
|
||||
apiKey = ""
|
||||
}
|
||||
apiKey = DeepLUserAPIHandler.readKey()
|
||||
}
|
||||
|
||||
private func updatePrefs() {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Account
|
||||
import AppIntents
|
||||
import DesignSystem
|
||||
import Explore
|
||||
import Foundation
|
||||
|
@ -15,6 +16,7 @@ enum Tab: Int, Identifiable, Hashable, CaseIterable, Codable {
|
|||
case post
|
||||
case followedTags
|
||||
case lists
|
||||
case links
|
||||
|
||||
nonisolated var id: Int {
|
||||
rawValue
|
||||
|
@ -23,9 +25,9 @@ enum Tab: Int, Identifiable, Hashable, CaseIterable, Codable {
|
|||
static func loggedOutTab() -> [Tab] {
|
||||
[.timeline, .settings]
|
||||
}
|
||||
|
||||
|
||||
static func visionOSTab() -> [Tab] {
|
||||
[.profile, .timeline, .notifications, .mentions, .explore, .messages, .settings]
|
||||
[.profile, .timeline, .notifications, .mentions, .explore, .post, .settings]
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
|
@ -67,8 +69,10 @@ enum Tab: Int, Identifiable, Hashable, CaseIterable, Codable {
|
|||
NavigationTab {
|
||||
ListsListView()
|
||||
}
|
||||
case .links:
|
||||
NavigationTab { TrendingLinksListView(cards: []) }
|
||||
case .post:
|
||||
VStack { }
|
||||
VStack {}
|
||||
case .other:
|
||||
EmptyView()
|
||||
}
|
||||
|
@ -76,40 +80,47 @@ enum Tab: Int, Identifiable, Hashable, CaseIterable, Codable {
|
|||
|
||||
@ViewBuilder
|
||||
var label: some View {
|
||||
if self != .other {
|
||||
Label(title, systemImage: iconName)
|
||||
}
|
||||
}
|
||||
|
||||
var title: LocalizedStringKey {
|
||||
switch self {
|
||||
case .timeline:
|
||||
Label("tab.timeline", systemImage: iconName)
|
||||
"tab.timeline"
|
||||
case .trending:
|
||||
Label("tab.trending", systemImage: iconName)
|
||||
"tab.trending"
|
||||
case .local:
|
||||
Label("tab.local", systemImage: iconName)
|
||||
"tab.local"
|
||||
case .federated:
|
||||
Label("tab.federated", systemImage: iconName)
|
||||
"tab.federated"
|
||||
case .notifications:
|
||||
Label("tab.notifications", systemImage: iconName)
|
||||
"tab.notifications"
|
||||
case .mentions:
|
||||
Label("tab.mentions", systemImage: iconName)
|
||||
"tab.mentions"
|
||||
case .explore:
|
||||
Label("tab.explore", systemImage: iconName)
|
||||
"tab.explore"
|
||||
case .messages:
|
||||
Label("tab.messages", systemImage: iconName)
|
||||
"tab.messages"
|
||||
case .settings:
|
||||
Label("tab.settings", systemImage: iconName)
|
||||
"tab.settings"
|
||||
case .profile:
|
||||
Label("tab.profile", systemImage: iconName)
|
||||
"tab.profile"
|
||||
case .bookmarks:
|
||||
Label("accessibility.tabs.profile.picker.bookmarks", systemImage: iconName)
|
||||
"accessibility.tabs.profile.picker.bookmarks"
|
||||
case .favorites:
|
||||
Label("accessibility.tabs.profile.picker.favorites", systemImage: iconName)
|
||||
"accessibility.tabs.profile.picker.favorites"
|
||||
case .post:
|
||||
Label("menu.new-post", systemImage: iconName)
|
||||
"menu.new-post"
|
||||
case .followedTags:
|
||||
Label("timeline.filter.tags", systemImage: iconName)
|
||||
"timeline.filter.tags"
|
||||
case .lists:
|
||||
Label("timeline.filter.lists", systemImage: iconName)
|
||||
"timeline.filter.lists"
|
||||
case .links:
|
||||
"explore.section.trending.links"
|
||||
case .other:
|
||||
EmptyView()
|
||||
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,19 +156,22 @@ enum Tab: Int, Identifiable, Hashable, CaseIterable, Codable {
|
|||
"tag"
|
||||
case .lists:
|
||||
"list.bullet"
|
||||
case .links:
|
||||
"newspaper"
|
||||
case .other:
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
class SidebarTabs {
|
||||
struct SidedebarTab: Hashable, Codable {
|
||||
let tab: Tab
|
||||
var enabled: Bool
|
||||
}
|
||||
|
||||
|
||||
class Storage {
|
||||
@AppStorage("sidebar_tabs") var tabs: [SidedebarTab] = [
|
||||
.init(tab: .timeline, enabled: true),
|
||||
|
@ -172,36 +186,37 @@ class SidebarTabs {
|
|||
.init(tab: .favorites, enabled: true),
|
||||
.init(tab: .followedTags, enabled: true),
|
||||
.init(tab: .lists, enabled: true),
|
||||
|
||||
|
||||
.init(tab: .settings, enabled: true),
|
||||
.init(tab: .profile, enabled: true),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
private let storage = Storage()
|
||||
public static let shared = SidebarTabs()
|
||||
|
||||
|
||||
var tabs: [SidedebarTab] {
|
||||
didSet {
|
||||
storage.tabs = tabs
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func isEnabled(_ tab: Tab) -> Bool {
|
||||
tabs.first(where: { $0.tab.id == tab.id })?.enabled == true
|
||||
}
|
||||
|
||||
|
||||
private init() {
|
||||
tabs = storage.tabs
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
class iOSTabs {
|
||||
enum TabEntries: String {
|
||||
case first, second, third, fourth, fifth
|
||||
}
|
||||
|
||||
|
||||
class Storage {
|
||||
@AppStorage(TabEntries.first.rawValue) var firstTab = Tab.timeline
|
||||
@AppStorage(TabEntries.second.rawValue) var secondTab = Tab.notifications
|
||||
|
@ -209,44 +224,44 @@ class iOSTabs {
|
|||
@AppStorage(TabEntries.fourth.rawValue) var fourthTab = Tab.messages
|
||||
@AppStorage(TabEntries.fifth.rawValue) var fifthTab = Tab.profile
|
||||
}
|
||||
|
||||
|
||||
private let storage = Storage()
|
||||
public static let shared = iOSTabs()
|
||||
|
||||
|
||||
var tabs: [Tab] {
|
||||
[firstTab, secondTab, thirdTab, fourthTab, fifthTab]
|
||||
}
|
||||
|
||||
|
||||
var firstTab: Tab {
|
||||
didSet {
|
||||
storage.firstTab = firstTab
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var secondTab: Tab {
|
||||
didSet {
|
||||
storage.secondTab = secondTab
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var thirdTab: Tab {
|
||||
didSet {
|
||||
storage.thirdTab = thirdTab
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var fourthTab: Tab {
|
||||
didSet {
|
||||
storage.fourthTab = fourthTab
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var fifthTab: Tab {
|
||||
didSet {
|
||||
storage.fifthTab = fifthTab
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private init() {
|
||||
firstTab = storage.firstTab
|
||||
secondTab = storage.secondTab
|
||||
|
|
|
@ -5,7 +5,6 @@ import Models
|
|||
import Network
|
||||
import NukeUI
|
||||
import SFSafeSymbols
|
||||
import Shimmer
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
|
||||
|
@ -39,7 +38,9 @@ struct EditTagGroupView: View {
|
|||
focusedField: $focusedField
|
||||
)
|
||||
}
|
||||
#if !os(visionOS)
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
#endif
|
||||
|
||||
Section("add-tag-groups.edit.tags") {
|
||||
TagsInputView(
|
||||
|
@ -48,7 +49,9 @@ struct EditTagGroupView: View {
|
|||
focusedField: $focusedField
|
||||
)
|
||||
}
|
||||
#if !os(visionOS)
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
#endif
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
.navigationTitle(
|
||||
|
@ -58,22 +61,20 @@ struct EditTagGroupView: View {
|
|||
)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
#if !os(visionOS)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
#endif
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("action.cancel", action: { dismiss() })
|
||||
.toolbar {
|
||||
CancelToolbarItem()
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("action.save", action: { save() })
|
||||
.disabled(!tagGroup.isValid)
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("action.save", action: { save() })
|
||||
.disabled(!tagGroup.isValid)
|
||||
.onAppear {
|
||||
focusedField = .title
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
focusedField = .title
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import Env
|
|||
import Models
|
||||
import Network
|
||||
import NukeUI
|
||||
import Shimmer
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
|
@ -53,31 +52,29 @@ struct AddRemoteTimelineView: View {
|
|||
.navigationTitle("timeline.add-remote.title")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
#if !os(visionOS)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
#endif
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("action.cancel", action: { dismiss() })
|
||||
.toolbar {
|
||||
CancelToolbarItem()
|
||||
}
|
||||
}
|
||||
.onChange(of: instanceName) { _, newValue in
|
||||
instanceNamePublisher.send(newValue)
|
||||
}
|
||||
.onReceive(instanceNamePublisher.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)) { newValue in
|
||||
Task {
|
||||
let client = Client(server: newValue)
|
||||
instance = try? await client.get(endpoint: Instances.instance)
|
||||
.onChange(of: instanceName) { _, newValue in
|
||||
instanceNamePublisher.send(newValue)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
isInstanceURLFieldFocused = true
|
||||
let client = InstanceSocialClient()
|
||||
Task {
|
||||
instances = await client.fetchInstances(keyword: instanceName)
|
||||
.onReceive(instanceNamePublisher.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)) { newValue in
|
||||
Task {
|
||||
let client = Client(server: newValue)
|
||||
instance = try? await client.get(endpoint: Instances.instance)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
isInstanceURLFieldFocused = true
|
||||
let client = InstanceSocialClient()
|
||||
Task {
|
||||
instances = await client.fetchInstances(keyword: instanceName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ struct TimelineTab: View {
|
|||
.toolbar {
|
||||
toolbarView
|
||||
}
|
||||
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.50), for: .navigationBar)
|
||||
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.30), for: .navigationBar)
|
||||
.id(client.id)
|
||||
}
|
||||
.onAppear {
|
||||
|
@ -95,7 +95,7 @@ struct TimelineTab: View {
|
|||
}
|
||||
switch newValue {
|
||||
case let .tagGroup(title, _, _):
|
||||
if let group = tagGroups.first(where: { $0.title == title}) {
|
||||
if let group = tagGroups.first(where: { $0.title == title }) {
|
||||
selectedTagGroup = group
|
||||
}
|
||||
default:
|
||||
|
@ -123,13 +123,14 @@ struct TimelineTab: View {
|
|||
|
||||
@ViewBuilder
|
||||
private var timelineFilterButton: some View {
|
||||
latestOrResumeButtons
|
||||
pinMenuButton
|
||||
headerGroup
|
||||
timelineFiltersButtons
|
||||
listsFiltersButons
|
||||
tagsFiltersButtons
|
||||
localTimelinesFiltersButtons
|
||||
tagGroupsFiltersButtons
|
||||
Divider()
|
||||
contentFilterButton
|
||||
}
|
||||
|
||||
private var addAccountButton: some View {
|
||||
|
@ -184,14 +185,16 @@ struct TimelineTab: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ViewBuilder
|
||||
private var latestOrResumeButtons: some View {
|
||||
if timeline.supportNewestPagination {
|
||||
Button {
|
||||
timeline = .latest
|
||||
} label: {
|
||||
Label(TimelineFilter.latest.localizedTitle(), systemImage: TimelineFilter.latest.iconName())
|
||||
private var headerGroup: some View {
|
||||
ControlGroup {
|
||||
if timeline.supportNewestPagination {
|
||||
Button {
|
||||
timeline = .latest
|
||||
} label: {
|
||||
Label(TimelineFilter.latest.localizedTitle(), systemImage: TimelineFilter.latest.iconName())
|
||||
}
|
||||
}
|
||||
if timeline == .home {
|
||||
Button {
|
||||
|
@ -203,13 +206,13 @@ struct TimelineTab: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
pinButton
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ViewBuilder
|
||||
private var pinMenuButton: some View {
|
||||
let index = pinnedFilters.firstIndex(where: { $0.id == timeline.id})
|
||||
private var pinButton: some View {
|
||||
let index = pinnedFilters.firstIndex(where: { $0.id == timeline.id })
|
||||
Button {
|
||||
withAnimation {
|
||||
if let index {
|
||||
|
@ -219,16 +222,14 @@ struct TimelineTab: View {
|
|||
}
|
||||
}
|
||||
} label: {
|
||||
if index != nil {
|
||||
if index != nil {
|
||||
Label("status.action.unpin", systemImage: "pin.slash")
|
||||
} else {
|
||||
Label("status.action.pin", systemImage: "pin")
|
||||
}
|
||||
}
|
||||
|
||||
Divider()
|
||||
}
|
||||
|
||||
|
||||
private var timelineFiltersButtons: some View {
|
||||
ForEach(TimelineFilter.availableTimeline(client: client), id: \.self) { timeline in
|
||||
Button {
|
||||
|
@ -238,7 +239,7 @@ struct TimelineTab: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ViewBuilder
|
||||
private var listsFiltersButons: some View {
|
||||
Menu("timeline.filter.lists") {
|
||||
|
@ -256,7 +257,7 @@ struct TimelineTab: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ViewBuilder
|
||||
private var tagsFiltersButtons: some View {
|
||||
if !currentAccount.tags.isEmpty {
|
||||
|
@ -271,7 +272,7 @@ struct TimelineTab: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var localTimelinesFiltersButtons: some View {
|
||||
Menu("timeline.filter.local") {
|
||||
ForEach(localTimelines) { remoteLocal in
|
||||
|
@ -290,7 +291,7 @@ struct TimelineTab: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var tagGroupsFiltersButtons: some View {
|
||||
Menu("timeline.filter.tag-groups") {
|
||||
ForEach(tagGroups) { group in
|
||||
|
@ -312,6 +313,14 @@ struct TimelineTab: View {
|
|||
}
|
||||
}
|
||||
|
||||
private var contentFilterButton: some View {
|
||||
Button(action: {
|
||||
routerPath.presentedSheet = .timelineContentFilter
|
||||
}, label: {
|
||||
Label("timeline.content-filter.title", systemSymbol: .line3HorizontalDecrease)
|
||||
})
|
||||
}
|
||||
|
||||
private func resetTimelineFilter() {
|
||||
if client.isAuth, canFilterTimeline {
|
||||
timeline = lastTimelineFilter
|
||||
|
|
|
@ -1,23 +1,39 @@
|
|||
import SwiftUI
|
||||
import Env
|
||||
import AppAccount
|
||||
import DesignSystem
|
||||
import Env
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
struct ToolbarTab: ToolbarContent {
|
||||
@Environment(\.isSecondaryColumn) private var isSecondaryColumn: Bool
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
|
||||
|
||||
@Environment(UserPreferences.self) private var userPreferences
|
||||
|
||||
|
||||
@Binding var routerPath: RouterPath
|
||||
|
||||
|
||||
var body: some ToolbarContent {
|
||||
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,
|
||||
visibility: userPreferences.postVisibility)
|
||||
if UIDevice.current.userInterfaceIdiom != .pad ||
|
||||
(UIDevice.current.userInterfaceIdiom == .pad && horizontalSizeClass == .compact) {
|
||||
(UIDevice.current.userInterfaceIdiom == .pad && horizontalSizeClass == .compact)
|
||||
{
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
AppAccountsSelectorView(routerPath: routerPath)
|
||||
}
|
||||
|
|
BIN
IceCubesApp/Assets.xcassets/AppIcon.appiconset/1024.png
Normal file
After Width: | Height: | Size: 991 KiB |
Before Width: | Height: | Size: 156 KiB |
|
@ -1,67 +1,67 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AppIcon.png",
|
||||
"filename" : "1024.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-16.png",
|
||||
"filename" : "macos16.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-32.png",
|
||||
"filename" : "macos32 1.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-32 1.png",
|
||||
"filename" : "macos32.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-64.png",
|
||||
"filename" : "macos64.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-128.png",
|
||||
"filename" : "macos128.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-256 1.png",
|
||||
"filename" : "mac256 1.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-256.png",
|
||||
"filename" : "mac256.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-512 1.png",
|
||||
"filename" : "macOS512 1.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-512.png",
|
||||
"filename" : "macOS512.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-1024.png",
|
||||
"filename" : "macOS1024.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "512x512"
|
||||
|
|
Before Width: | Height: | Size: 501 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 734 B |
Before Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 147 KiB |
Before Width: | Height: | Size: 147 KiB |
Before Width: | Height: | Size: 5.5 KiB |
BIN
IceCubesApp/Assets.xcassets/AppIcon.appiconset/mac256 1.png
Normal file
After Width: | Height: | Size: 79 KiB |
BIN
IceCubesApp/Assets.xcassets/AppIcon.appiconset/mac256.png
Normal file
After Width: | Height: | Size: 79 KiB |
BIN
IceCubesApp/Assets.xcassets/AppIcon.appiconset/macOS1024.png
Normal file
After Width: | Height: | Size: 973 KiB |
BIN
IceCubesApp/Assets.xcassets/AppIcon.appiconset/macOS512 1.png
Normal file
After Width: | Height: | Size: 262 KiB |
BIN
IceCubesApp/Assets.xcassets/AppIcon.appiconset/macOS512.png
Normal file
After Width: | Height: | Size: 262 KiB |
BIN
IceCubesApp/Assets.xcassets/AppIcon.appiconset/macos128.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
IceCubesApp/Assets.xcassets/AppIcon.appiconset/macos16.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
IceCubesApp/Assets.xcassets/AppIcon.appiconset/macos32 1.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
IceCubesApp/Assets.xcassets/AppIcon.appiconset/macos32.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
IceCubesApp/Assets.xcassets/AppIcon.appiconset/macos64.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 764 KiB |
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Back.png",
|
||||
"filename" : "Background.png",
|
||||
"idiom" : "vision",
|
||||
"scale" : "2x"
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Front.png",
|
||||
"filename" : "Layer 1.png",
|
||||
"idiom" : "vision",
|
||||
"scale" : "2x"
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 543 KiB |
After Width: | Height: | Size: 549 KiB |
|
@ -1,7 +1,6 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Middle.png",
|
||||
"idiom" : "vision",
|
||||
"scale" : "2x"
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 991 KiB |
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AppIcon-fs8.png",
|
||||
"filename" : "1024.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
|
|
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 156 KiB |
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AppIcon-fs8.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>DEEPL_SECRET</key>
|
||||
<string>NICE_TRY_AGAIN</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.widgetkit-extension</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</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
|
||||
}
|
||||
}
|