Revamped Navigation system and more

This commit is contained in:
Lumaa 2024-08-11 16:29:26 +02:00 committed by GitHub
commit f2da07ab52
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
43 changed files with 1132 additions and 1098 deletions

View File

@ -43,6 +43,9 @@
B98BC7492B46CEDA00595441 /* AppearenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98BC7482B46CEDA00595441 /* AppearenceView.swift */; };
B98BC74B2B46CF0400595441 /* ThreadedStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98BC74A2B46CF0400595441 /* ThreadedStyle.swift */; };
B98BC74D2B46CFCE00595441 /* UserPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98BC74C2B46CFCE00595441 /* UserPreferences.swift */; };
B98DD8D62C681FDA009F40DD /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B9FB948F2B2E2B0E00D81C07 /* Localizable.xcstrings */; };
B98DD8D82C6821F7009F40DD /* CreatePostControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98DD8D72C6821F7009F40DD /* CreatePostControl.swift */; };
B98DD8D92C6821F7009F40DD /* CreatePostControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98DD8D72C6821F7009F40DD /* CreatePostControl.swift */; };
B98F47962B645DF40092000F /* EmojiSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98F47952B645DF40092000F /* EmojiSelector.swift */; };
B98F47982B64670F0092000F /* ShopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98F47972B64670F0092000F /* ShopView.swift */; };
B98F479A2B653CAE0092000F /* Compressor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98F47992B653CAE0092000F /* Compressor.swift */; };
@ -50,24 +53,100 @@
B999DE5E2B76F9D100509868 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = B999DE5D2B76F9D100509868 /* Message.swift */; };
B999DE602B76FB3E00509868 /* ContactRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B999DE5F2B76FB3E00509868 /* ContactRow.swift */; };
B9A80DDA2C66DE1000DE3D88 /* ReportStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A80DD92C66DE1000DE3D88 /* ReportStatusView.swift */; };
B9A80DDE2C67BFF800DE3D88 /* CreatePostWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A80DDD2C67BFF800DE3D88 /* CreatePostWidget.swift */; };
B9A80DDF2C67C27B00DE3D88 /* AppIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C20D132B921C78004DC9B3 /* AppIntent.swift */; };
B9A80DE02C67C2D000DE3D88 /* Navigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB946F2B2DF3CD00D81C07 /* Navigator.swift */; };
B9A80DE22C67C38E00DE3D88 /* URLNavigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A80DE12C67C38E00DE3D88 /* URLNavigator.swift */; };
B9A80DE72C67C3FC00DE3D88 /* Account+Elms.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB94852B2E211200D81C07 /* Account+Elms.swift */; };
B9A80DE82C67C3FC00DE3D88 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB947E2B2E1D5F00D81C07 /* Account.swift */; };
B9A80DE92C67C3FC00DE3D88 /* LoggedAccounts.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98627302B86F23500844245 /* LoggedAccounts.swift */; };
B9A80DEA2C67C3FC00DE3D88 /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB947C2B2E19E300D81C07 /* AccountManager.swift */; };
B9A80DEB2C67C3FC00DE3D88 /* AccountsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9842C172B2F36F500D9F3C1 /* AccountsList.swift */; };
B9A80DF02C67C40900DE3D88 /* SearchResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B63B262B449CDC00BBC82D /* SearchResults.swift */; };
B9A80DF12C67C40900DE3D88 /* ContentFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B93126E92C29C63000BF16E9 /* ContentFilter.swift */; };
B9A80DF22C67C40900DE3D88 /* FetchTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9842C132B2F310C00D9F3C1 /* FetchTimeline.swift */; };
B9A80DF32C67C40900DE3D88 /* HTMLString.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB94802B2E1FEF00D81C07 /* HTMLString.swift */; };
B9A80DF42C67C40900DE3D88 /* TimelineFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9842C152B2F363600D9F3C1 /* TimelineFilter.swift */; };
B9A80DF52C67C40900DE3D88 /* Tenor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9BCC3172B90B3BC00211976 /* Tenor.swift */; };
B9A80DF62C67C40900DE3D88 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB94BB2B2F035500D81C07 /* Tag.swift */; };
B9A80DF72C67C40900DE3D88 /* MediaTransferables.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB948D2B2E28E800D81C07 /* MediaTransferables.swift */; };
B9A80DF82C67C40900DE3D88 /* Compressor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98F47992B653CAE0092000F /* Compressor.swift */; };
B9A80DF92C67C40900DE3D88 /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9842C0F2B2F228C00D9F3C1 /* Status.swift */; };
B9A80DFA2C67C40E00DE3D88 /* URLNavigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A80DE12C67C38E00DE3D88 /* URLNavigator.swift */; };
B9A80DFB2C67C40E00DE3D88 /* AltClients.swift in Sources */ = {isa = PBXBuildFile; fileRef = B93757102B7FB8D400652F91 /* AltClients.swift */; };
B9A80DFC2C67C40E00DE3D88 /* MastodonRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB949E2B2EF0F200D81C07 /* MastodonRequest.swift */; };
B9A80DFD2C67C40E00DE3D88 /* UserPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98BC74C2B46CFCE00595441 /* UserPreferences.swift */; };
B9A80DFF2C67C40E00DE3D88 /* HapticManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B97BCE232B3DD8400044756D /* HapticManager.swift */; };
B9A80E002C67C40E00DE3D88 /* Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB949C2B2EF0D600D81C07 /* Instance.swift */; };
B9A80E0A2C67C48D00DE3D88 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EBE8572B474FD600FB594D /* AppDelegate.swift */; };
B9A80E0B2C67C4A600DE3D88 /* AccountRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FA6E762B82788A00D63E30 /* AccountRow.swift */; };
B9A80E0C2C67C4A600DE3D88 /* ComingSoonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B469DA2B9B2EDB00AD5585 /* ComingSoonView.swift */; };
B9A80E0D2C67C4A600DE3D88 /* OnlineImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB948B2B2E232300D81C07 /* OnlineImage.swift */; };
B9A80E0E2C67C4A600DE3D88 /* DynamicTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B63B202B442D1500BBC82D /* DynamicTextEditor.swift */; };
B9A80E0F2C67C4A600DE3D88 /* ContactRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B999DE5F2B76FB3E00509868 /* ContactRow.swift */; };
B9A80E102C67C4A600DE3D88 /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C7F46B2C387D3B009C36DC /* WarningView.swift */; };
B9A80E112C67C4A600DE3D88 /* ThreadedStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98BC74A2B46CF0400595441 /* ThreadedStyle.swift */; };
B9A80E122C67C4A600DE3D88 /* NotificationRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D9C6C42B6A587700C26A41 /* NotificationRow.swift */; };
B9A80E132C67C4A600DE3D88 /* TextEmoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = B93B67772B42E8F0000892E9 /* TextEmoji.swift */; };
B9A80E142C67C4A600DE3D88 /* SearchResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9DC69282B78D9A500E625B9 /* SearchResultView.swift */; };
B9A80E152C67C4A600DE3D88 /* SymbolWidth.swift in Sources */ = {isa = PBXBuildFile; fileRef = B97491E22B6E96700098BC48 /* SymbolWidth.swift */; };
B9A80E162C67C4A600DE3D88 /* TabsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB94752B2E023D00D81C07 /* TabsView.swift */; };
B9A80E172C67C4A600DE3D88 /* ShareSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9BED5192B5D662D00C9B715 /* ShareSheetController.swift */; };
B9A80E182C67C4A600DE3D88 /* ButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB94732B2DF6A100D81C07 /* ButtonStyles.swift */; };
B9A80E192C67C4A600DE3D88 /* ProfilePicture.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D9C6C62B6A590F00C26A41 /* ProfilePicture.swift */; };
B9A80E1A2C67C4A600DE3D88 /* MailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D365602B79A1BE004C1255 /* MailView.swift */; };
B9A80E1B2C67C4AE00DE3D88 /* PostCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B63B222B447B8000BBC82D /* PostCardView.swift */; };
B9A80E1C2C67C4AE00DE3D88 /* CompactPostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9842C0D2B2F21B700D9F3C1 /* CompactPostView.swift */; };
B9A80E1D2C67C4AE00DE3D88 /* PostMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9BED5172B5D649C00C9B715 /* PostMenu.swift */; };
B9A80E1E2C67C4AE00DE3D88 /* PostInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9BED5152B5D5E6500C9B715 /* PostInteractor.swift */; };
B9A80E1F2C67C4AE00DE3D88 /* EmojiSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98F47952B645DF40092000F /* EmojiSelector.swift */; };
B9A80E202C67C4AE00DE3D88 /* PostPoll.swift in Sources */ = {isa = PBXBuildFile; fileRef = B964F8052B9B78F4005C193D /* PostPoll.swift */; };
B9A80E212C67C4AE00DE3D88 /* PostAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EBE8552B47256900FB594D /* PostAttachment.swift */; };
B9A80E222C67C4AE00DE3D88 /* QuotePostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B63B242B44997400BBC82D /* QuotePostView.swift */; };
B9A80E232C67C4B700DE3D88 /* AttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FA6E742B82367B00D63E30 /* AttachmentView.swift */; };
B9A80E242C67C4B700DE3D88 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB94912B2E35D000D81C07 /* SettingsView.swift */; };
B9A80E252C67C4B700DE3D88 /* ConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB94712B2DF49700D81C07 /* ConnectView.swift */; };
B9A80E262C67C4B700DE3D88 /* PostingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B93B677B2B433A6E000892E9 /* PostingView.swift */; };
B9A80E272C67C4B700DE3D88 /* PostDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98BC7462B46CE6300595441 /* PostDetailsView.swift */; };
B9A80E282C67C4B700DE3D88 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FD18992C57DE9200A74A71 /* IconView.swift */; };
B9A80E292C67C4B700DE3D88 /* UpdateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B97798882B853E6600DC869F /* UpdateView.swift */; };
B9A80E2A2C67C4B700DE3D88 /* ReportStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A80DD92C66DE1000DE3D88 /* ReportStatusView.swift */; };
B9A80E2B2C67C4B700DE3D88 /* PrivacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B469B12B9A6E8300AD5585 /* PrivacyView.swift */; };
B9A80E2C2C67C4B700DE3D88 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9F8FA152B5D3AC30044DAB4 /* SafariView.swift */; };
B9A80E2D2C67C4B700DE3D88 /* FilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B93126EF2C2AEB8300BF16E9 /* FilterView.swift */; };
B9A80E2E2C67C4B700DE3D88 /* AppearenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98BC7482B46CEDA00595441 /* AppearenceView.swift */; };
B9A80E2F2C67C4B700DE3D88 /* ContactsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B999DE5B2B76F8CB00509868 /* ContactsView.swift */; };
B9A80E302C67C4B700DE3D88 /* PostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A8DAB92BB7364300A890CC /* PostsView.swift */; };
B9A80E312C67C4B700DE3D88 /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D9C6C22B6A576C00C26A41 /* NotificationsView.swift */; };
B9A80E322C67C4B700DE3D88 /* EditProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FD18972C55108F00A74A71 /* EditProfileView.swift */; };
B9A80E332C67C4B700DE3D88 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB945C2B2DEECE00D81C07 /* ContentView.swift */; };
B9A80E342C67C4B700DE3D88 /* DiscoveryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B93ADFCA2B7625CD00FF9172 /* DiscoveryView.swift */; };
B9A80E352C67C4B700DE3D88 /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B97BCE252B3DE5A10044756D /* AccountView.swift */; };
B9A80E362C67C4B700DE3D88 /* SupportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB94962B2EDABF00D81C07 /* SupportView.swift */; };
B9A80E372C67C4B700DE3D88 /* AddInstanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB94982B2EEB9400D81C07 /* AddInstanceView.swift */; };
B9A80E382C67C4B700DE3D88 /* ShopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98F47972B64670F0092000F /* ShopView.swift */; };
B9A80E392C67C4B700DE3D88 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9CC45B72B40A2D6001E4FA5 /* AboutView.swift */; };
B9A80E3A2C67C4B700DE3D88 /* RestrictedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B934EA232BAB5E7F001F4345 /* RestrictedView.swift */; };
B9A80E3B2C67C4B700DE3D88 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9842C112B2F2A5800D9F3C1 /* TimelineView.swift */; };
B9A80E3C2C67C4B700DE3D88 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B915C4412B6F908C00042DDB /* ProfileView.swift */; };
B9A80E6D2C67C61E00DE3D88 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D9C6C02B6A56E000C26A41 /* Notification.swift */; };
B9A80E6F2C67C62800DE3D88 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = B999DE5D2B76F9D100509868 /* Message.swift */; };
B9A80E852C67C7C300DE3D88 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = B9A80E842C67C7C300DE3D88 /* SwiftSoup */; };
B9A80E872C67C7C300DE3D88 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = B9A80E862C67C7C300DE3D88 /* Nuke */; };
B9A80E892C67C7C300DE3D88 /* NukeExtensions in Frameworks */ = {isa = PBXBuildFile; productRef = B9A80E882C67C7C300DE3D88 /* NukeExtensions */; };
B9A80E8B2C67C7C300DE3D88 /* NukeUI in Frameworks */ = {isa = PBXBuildFile; productRef = B9A80E8A2C67C7C300DE3D88 /* NukeUI */; };
B9A80E8D2C67C7C300DE3D88 /* NukeVideo in Frameworks */ = {isa = PBXBuildFile; productRef = B9A80E8C2C67C7C300DE3D88 /* NukeVideo */; };
B9A80E8F2C67C7C300DE3D88 /* EmojiText in Frameworks */ = {isa = PBXBuildFile; productRef = B9A80E8E2C67C7C300DE3D88 /* EmojiText */; };
B9A80E912C67C7C300DE3D88 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = B9A80E902C67C7C300DE3D88 /* KeychainSwift */; };
B9A80E932C67C7C300DE3D88 /* ReceiptParser in Frameworks */ = {isa = PBXBuildFile; productRef = B9A80E922C67C7C300DE3D88 /* ReceiptParser */; };
B9A80E952C67C7C300DE3D88 /* RevenueCat in Frameworks */ = {isa = PBXBuildFile; productRef = B9A80E942C67C7C300DE3D88 /* RevenueCat */; };
B9A80E972C67C7C300DE3D88 /* RevenueCatUI in Frameworks */ = {isa = PBXBuildFile; productRef = B9A80E962C67C7C300DE3D88 /* RevenueCatUI */; };
B9A80E9A2C67D56900DE3D88 /* FollowCountWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C20D112B921C78004DC9B3 /* FollowCountWidget.swift */; };
B9A80E9B2C67D56900DE3D88 /* FollowGoalWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B469AF2B9A275F00AD5585 /* FollowGoalWidget.swift */; };
B9A80E9C2C67D56900DE3D88 /* CreatePostWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A80DDD2C67BFF800DE3D88 /* CreatePostWidget.swift */; };
B9A8DABA2BB7364300A890CC /* PostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A8DAB92BB7364300A890CC /* PostsView.swift */; };
B9B469B02B9A275F00AD5585 /* FollowGoalWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B469AF2B9A275F00AD5585 /* FollowGoalWidget.swift */; };
B9B469B22B9A6E8300AD5585 /* PrivacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B469B12B9A6E8300AD5585 /* PrivacyView.swift */; };
B9B469BA2B9A7E6800AD5585 /* ThreadedWatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B469B92B9A7E6800AD5585 /* ThreadedWatchApp.swift */; };
B9B469BC2B9A7E6800AD5585 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B469BB2B9A7E6800AD5585 /* ContentView.swift */; };
B9B469BE2B9A7E6B00AD5585 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B9B469BD2B9A7E6B00AD5585 /* Assets.xcassets */; };
B9B469C12B9A7E6B00AD5585 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B9B469C02B9A7E6B00AD5585 /* Preview Assets.xcassets */; };
B9B469C42B9A7E6B00AD5585 /* Threaded.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = B9B469B72B9A7E6800AD5585 /* Threaded.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
B9B469C92B9A804800AD5585 /* Redeclarations.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C20D602B949AD7004DC9B3 /* Redeclarations.swift */; };
B9B469CA2B9A811200AD5585 /* AppInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB94A12B2EF24A00D81C07 /* AppInfo.swift */; };
B9B469CB2B9A816D00AD5585 /* LoggedAccounts.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98627302B86F23500844245 /* LoggedAccounts.swift */; };
B9B469CD2B9A823600AD5585 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B9B469CC2B9A823600AD5585 /* Localizable.xcstrings */; };
B9B469CF2B9A82ED00AD5585 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = B9B469CE2B9A82ED00AD5585 /* KeychainSwift */; };
B9B469D42B9A871C00AD5585 /* WatchConnectivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B469D32B9A871C00AD5585 /* WatchConnectivity.swift */; };
B9B469D52B9A871C00AD5585 /* WatchConnectivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B469D32B9A871C00AD5585 /* WatchConnectivity.swift */; };
B9B469D72B9A8B0700AD5585 /* GivenAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B469D62B9A8B0700AD5585 /* GivenAccount.swift */; };
B9B469D82B9A8B1600AD5585 /* GivenAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B469D62B9A8B0700AD5585 /* GivenAccount.swift */; };
B9B469D92B9AA4DF00AD5585 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB949A2B2EF09A00D81C07 /* Client.swift */; };
B9B469DB2B9B2EDB00AD5585 /* ComingSoonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B469DA2B9B2EDB00AD5585 /* ComingSoonView.swift */; };
B9B63B212B442D1500BBC82D /* DynamicTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B63B202B442D1500BBC82D /* DynamicTextEditor.swift */; };
B9B63B232B447B8000BBC82D /* PostCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B63B222B447B8000BBC82D /* PostCardView.swift */; };
@ -85,13 +164,9 @@
B9C20D142B921C78004DC9B3 /* AppIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C20D132B921C78004DC9B3 /* AppIntent.swift */; };
B9C20D162B921C7B004DC9B3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B9C20D152B921C7B004DC9B3 /* Assets.xcassets */; };
B9C20D1A2B921C7B004DC9B3 /* ThreadedWidgetsExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = B9C20D092B921C78004DC9B3 /* ThreadedWidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
B9C20D1F2B921E81004DC9B3 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B9C20D1E2B921E81004DC9B3 /* Localizable.xcstrings */; };
B9C20D282B9229DF004DC9B3 /* LoggedAccounts.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98627302B86F23500844245 /* LoggedAccounts.swift */; };
B9C20D342B9229EC004DC9B3 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB949A2B2EF09A00D81C07 /* Client.swift */; };
B9C20D372B9229EC004DC9B3 /* AppInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB94A12B2EF24A00D81C07 /* AppInfo.swift */; };
B9C20D3D2B9229EC004DC9B3 /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB94872B2E223E00D81C07 /* Emoji.swift */; };
B9C20D562B922DAC004DC9B3 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = B9C20D552B922DAC004DC9B3 /* KeychainSwift */; };
B9C20D612B949AD7004DC9B3 /* Redeclarations.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C20D602B949AD7004DC9B3 /* Redeclarations.swift */; };
B9C7F46C2C387D3B009C36DC /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C7F46B2C387D3B009C36DC /* WarningView.swift */; };
B9CC45B82B40A2D6001E4FA5 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9CC45B72B40A2D6001E4FA5 /* AboutView.swift */; };
B9CFC43B2B4F08C9004CFCB7 /* LaunchStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B9CFC43A2B4F08C9004CFCB7 /* LaunchStoryboard.storyboard */; };
@ -142,13 +217,6 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
B9B469C22B9A7E6B00AD5585 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = B9FB944F2B2DEECE00D81C07 /* Project object */;
proxyType = 1;
remoteGlobalIDString = B9B469B62B9A7E6800AD5585;
remoteInfo = "ThreadedWatch Watch App";
};
B9C20D182B921C7B004DC9B3 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = B9FB944F2B2DEECE00D81C07 /* Project object */;
@ -172,7 +240,6 @@
dstPath = "$(CONTENTS_FOLDER_PATH)/Watch";
dstSubfolderSpec = 16;
files = (
B9B469C42B9A7E6B00AD5585 /* Threaded.app in Embed Watch Content */,
);
name = "Embed Watch Content";
runOnlyForDeploymentPostprocessing = 0;
@ -221,6 +288,7 @@
B98BC7482B46CEDA00595441 /* AppearenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearenceView.swift; sourceTree = "<group>"; };
B98BC74A2B46CF0400595441 /* ThreadedStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadedStyle.swift; sourceTree = "<group>"; };
B98BC74C2B46CFCE00595441 /* UserPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPreferences.swift; sourceTree = "<group>"; };
B98DD8D72C6821F7009F40DD /* CreatePostControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePostControl.swift; sourceTree = "<group>"; };
B98F47952B645DF40092000F /* EmojiSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiSelector.swift; sourceTree = "<group>"; };
B98F47972B64670F0092000F /* ShopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopView.swift; sourceTree = "<group>"; };
B98F47992B653CAE0092000F /* Compressor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Compressor.swift; sourceTree = "<group>"; };
@ -228,18 +296,11 @@
B999DE5D2B76F9D100509868 /* Message.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = "<group>"; };
B999DE5F2B76FB3E00509868 /* ContactRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRow.swift; sourceTree = "<group>"; };
B9A80DD92C66DE1000DE3D88 /* ReportStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportStatusView.swift; sourceTree = "<group>"; };
B9A80DDD2C67BFF800DE3D88 /* CreatePostWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePostWidget.swift; sourceTree = "<group>"; };
B9A80DE12C67C38E00DE3D88 /* URLNavigator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLNavigator.swift; sourceTree = "<group>"; };
B9A8DAB92BB7364300A890CC /* PostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostsView.swift; sourceTree = "<group>"; };
B9B469AF2B9A275F00AD5585 /* FollowGoalWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowGoalWidget.swift; sourceTree = "<group>"; };
B9B469B12B9A6E8300AD5585 /* PrivacyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyView.swift; sourceTree = "<group>"; };
B9B469B72B9A7E6800AD5585 /* Threaded.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Threaded.app; sourceTree = BUILT_PRODUCTS_DIR; };
B9B469B92B9A7E6800AD5585 /* ThreadedWatchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadedWatchApp.swift; sourceTree = "<group>"; };
B9B469BB2B9A7E6800AD5585 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
B9B469BD2B9A7E6B00AD5585 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
B9B469C02B9A7E6B00AD5585 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
B9B469CC2B9A823600AD5585 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
B9B469D22B9A838500AD5585 /* ThreadedWatch Watch App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "ThreadedWatch Watch App.entitlements"; sourceTree = "<group>"; };
B9B469D32B9A871C00AD5585 /* WatchConnectivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchConnectivity.swift; sourceTree = "<group>"; };
B9B469D62B9A8B0700AD5585 /* GivenAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GivenAccount.swift; sourceTree = "<group>"; };
B9B469DA2B9B2EDB00AD5585 /* ComingSoonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComingSoonView.swift; sourceTree = "<group>"; };
B9B63B202B442D1500BBC82D /* DynamicTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicTextEditor.swift; sourceTree = "<group>"; };
B9B63B222B447B8000BBC82D /* PostCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCardView.swift; sourceTree = "<group>"; };
@ -257,10 +318,8 @@
B9C20D132B921C78004DC9B3 /* AppIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntent.swift; sourceTree = "<group>"; };
B9C20D152B921C7B004DC9B3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
B9C20D172B921C7B004DC9B3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B9C20D1E2B921E81004DC9B3 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
B9C20D582B923CDD004DC9B3 /* ThreadedWidgetsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ThreadedWidgetsExtension.entitlements; sourceTree = "<group>"; };
B9C20D592B923D53004DC9B3 /* Threaded.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Threaded.entitlements; sourceTree = "<group>"; };
B9C20D602B949AD7004DC9B3 /* Redeclarations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Redeclarations.swift; sourceTree = "<group>"; };
B9C7F46B2C387D3B009C36DC /* WarningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WarningView.swift; sourceTree = "<group>"; };
B9CC45B72B40A2D6001E4FA5 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
B9CC45B92B40AA1E001E4FA5 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
@ -314,21 +373,22 @@
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
B9B469B42B9A7E6800AD5585 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
B9B469CF2B9A82ED00AD5585 /* KeychainSwift in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
B9C20D062B921C78004DC9B3 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
B9A80E912C67C7C300DE3D88 /* KeychainSwift in Frameworks */,
B9C20D0D2B921C78004DC9B3 /* SwiftUI.framework in Frameworks */,
B9C20D0B2B921C78004DC9B3 /* WidgetKit.framework in Frameworks */,
B9C20D562B922DAC004DC9B3 /* KeychainSwift in Frameworks */,
B9A80E8F2C67C7C300DE3D88 /* EmojiText in Frameworks */,
B9A80E952C67C7C300DE3D88 /* RevenueCat in Frameworks */,
B9A80E932C67C7C300DE3D88 /* ReceiptParser in Frameworks */,
B9A80E8D2C67C7C300DE3D88 /* NukeVideo in Frameworks */,
B9A80E852C67C7C300DE3D88 /* SwiftSoup in Frameworks */,
B9A80E972C67C7C300DE3D88 /* RevenueCatUI in Frameworks */,
B9A80E8B2C67C7C300DE3D88 /* NukeUI in Frameworks */,
B9A80E872C67C7C300DE3D88 /* Nuke in Frameworks */,
B9A80E892C67C7C300DE3D88 /* NukeExtensions in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -377,29 +437,6 @@
path = Content;
sourceTree = "<group>";
};
B9B469B82B9A7E6800AD5585 /* ThreadedWatch */ = {
isa = PBXGroup;
children = (
B9B469B92B9A7E6800AD5585 /* ThreadedWatchApp.swift */,
B9B469BB2B9A7E6800AD5585 /* ContentView.swift */,
B9B469D32B9A871C00AD5585 /* WatchConnectivity.swift */,
B9B469D62B9A8B0700AD5585 /* GivenAccount.swift */,
B9B469BD2B9A7E6B00AD5585 /* Assets.xcassets */,
B9B469D22B9A838500AD5585 /* ThreadedWatch Watch App.entitlements */,
B9B469BF2B9A7E6B00AD5585 /* Preview Content */,
B9B469CC2B9A823600AD5585 /* Localizable.xcstrings */,
);
path = ThreadedWatch;
sourceTree = "<group>";
};
B9B469BF2B9A7E6B00AD5585 /* Preview Content */ = {
isa = PBXGroup;
children = (
B9B469C02B9A7E6B00AD5585 /* Preview Assets.xcassets */,
);
path = "Preview Content";
sourceTree = "<group>";
};
B9BED5142B5D5CCD00C9B715 /* Post */ = {
isa = PBXGroup;
children = (
@ -422,10 +459,10 @@
B9C20D0F2B921C78004DC9B3 /* ThreadedWidgetsBundle.swift */,
B9C20D112B921C78004DC9B3 /* FollowCountWidget.swift */,
B9B469AF2B9A275F00AD5585 /* FollowGoalWidget.swift */,
B9A80DDD2C67BFF800DE3D88 /* CreatePostWidget.swift */,
B98DD8D72C6821F7009F40DD /* CreatePostControl.swift */,
B9C20D132B921C78004DC9B3 /* AppIntent.swift */,
B9C20D602B949AD7004DC9B3 /* Redeclarations.swift */,
B9C20D152B921C7B004DC9B3 /* Assets.xcassets */,
B9C20D1E2B921E81004DC9B3 /* Localizable.xcstrings */,
B9C20D172B921C7B004DC9B3 /* Info.plist */,
);
path = ThreadedWidgets;
@ -454,7 +491,6 @@
children = (
B9CC45B92B40AA1E001E4FA5 /* README.md */,
B9FB94592B2DEECE00D81C07 /* Threaded */,
B9B469B82B9A7E6800AD5585 /* ThreadedWatch */,
B9C20D0E2B921C78004DC9B3 /* ThreadedWidgets */,
B9FB94AB2B2F009F00D81C07 /* AuthService */,
B9FB94A82B2F009F00D81C07 /* Frameworks */,
@ -470,7 +506,6 @@
B9FB94572B2DEECE00D81C07 /* Threaded.app */,
B9FB94A72B2F009F00D81C07 /* ThreadedAuthService.appex */,
B9C20D092B921C78004DC9B3 /* ThreadedWidgetsExtension.appex */,
B9B469B72B9A7E6800AD5585 /* Threaded.app */,
);
name = Products;
sourceTree = "<group>";
@ -510,6 +545,7 @@
B93BCC3F2B5E38E5008EEA19 /* Content */,
B9D9C6BF2B6A56D500C26A41 /* Notifications */,
B9FB946F2B2DF3CD00D81C07 /* Navigator.swift */,
B9A80DE12C67C38E00DE3D88 /* URLNavigator.swift */,
B9FB94A12B2EF24A00D81C07 /* AppInfo.swift */,
B9029FC32B8125CE00AA9B68 /* HuggingFace.swift */,
B98BC74C2B46CFCE00595441 /* UserPreferences.swift */,
@ -624,26 +660,6 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
B9B469B62B9A7E6800AD5585 /* ThreadedWatch Watch App */ = {
isa = PBXNativeTarget;
buildConfigurationList = B9B469C52B9A7E6B00AD5585 /* Build configuration list for PBXNativeTarget "ThreadedWatch Watch App" */;
buildPhases = (
B9B469B32B9A7E6800AD5585 /* Sources */,
B9B469B42B9A7E6800AD5585 /* Frameworks */,
B9B469B52B9A7E6800AD5585 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "ThreadedWatch Watch App";
packageProductDependencies = (
B9B469CE2B9A82ED00AD5585 /* KeychainSwift */,
);
productName = "ThreadedWatch Watch App";
productReference = B9B469B72B9A7E6800AD5585 /* Threaded.app */;
productType = "com.apple.product-type.application";
};
B9C20D082B921C78004DC9B3 /* ThreadedWidgetsExtension */ = {
isa = PBXNativeTarget;
buildConfigurationList = B9C20D1D2B921C7B004DC9B3 /* Build configuration list for PBXNativeTarget "ThreadedWidgetsExtension" */;
@ -658,7 +674,16 @@
);
name = ThreadedWidgetsExtension;
packageProductDependencies = (
B9C20D552B922DAC004DC9B3 /* KeychainSwift */,
B9A80E842C67C7C300DE3D88 /* SwiftSoup */,
B9A80E862C67C7C300DE3D88 /* Nuke */,
B9A80E882C67C7C300DE3D88 /* NukeExtensions */,
B9A80E8A2C67C7C300DE3D88 /* NukeUI */,
B9A80E8C2C67C7C300DE3D88 /* NukeVideo */,
B9A80E8E2C67C7C300DE3D88 /* EmojiText */,
B9A80E902C67C7C300DE3D88 /* KeychainSwift */,
B9A80E922C67C7C300DE3D88 /* ReceiptParser */,
B9A80E942C67C7C300DE3D88 /* RevenueCat */,
B9A80E962C67C7C300DE3D88 /* RevenueCatUI */,
);
productName = ThreadedWidgetsExtension;
productReference = B9C20D092B921C78004DC9B3 /* ThreadedWidgetsExtension.appex */;
@ -679,7 +704,6 @@
dependencies = (
B9FB94B32B2F009F00D81C07 /* PBXTargetDependency */,
B9C20D192B921C7B004DC9B3 /* PBXTargetDependency */,
B9B469C32B9A7E6B00AD5585 /* PBXTargetDependency */,
);
name = Threaded;
packageProductDependencies = (
@ -724,9 +748,6 @@
LastSwiftUpdateCheck = 1600;
LastUpgradeCheck = 1510;
TargetAttributes = {
B9B469B62B9A7E6800AD5585 = {
CreatedOnToolsVersion = 15.3;
};
B9C20D082B921C78004DC9B3 = {
CreatedOnToolsVersion = 15.2;
};
@ -760,7 +781,6 @@
projectRoot = "";
targets = (
B9FB94562B2DEECE00D81C07 /* Threaded */,
B9B469B62B9A7E6800AD5585 /* ThreadedWatch Watch App */,
B9C20D082B921C78004DC9B3 /* ThreadedWidgetsExtension */,
B9FB94A62B2F009F00D81C07 /* ThreadedAuthService */,
);
@ -768,21 +788,11 @@
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
B9B469B52B9A7E6800AD5585 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B9B469C12B9A7E6B00AD5585 /* Preview Assets.xcassets in Resources */,
B9B469BE2B9A7E6B00AD5585 /* Assets.xcassets in Resources */,
B9B469CD2B9A823600AD5585 /* Localizable.xcstrings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
B9C20D072B921C78004DC9B3 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B9C20D1F2B921E81004DC9B3 /* Localizable.xcstrings in Resources */,
B98DD8D62C681FDA009F40DD /* Localizable.xcstrings in Resources */,
B9C20D162B921C7B004DC9B3 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -814,34 +824,94 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
B9B469B32B9A7E6800AD5585 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B9B469D72B9A8B0700AD5585 /* GivenAccount.swift in Sources */,
B9B469D52B9A871C00AD5585 /* WatchConnectivity.swift in Sources */,
B9B469C92B9A804800AD5585 /* Redeclarations.swift in Sources */,
B9B469CB2B9A816D00AD5585 /* LoggedAccounts.swift in Sources */,
B9B469CA2B9A811200AD5585 /* AppInfo.swift in Sources */,
B9B469BC2B9A7E6800AD5585 /* ContentView.swift in Sources */,
B9B469D92B9AA4DF00AD5585 /* Client.swift in Sources */,
B9B469BA2B9A7E6800AD5585 /* ThreadedWatchApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
B9C20D052B921C78004DC9B3 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B9C20D282B9229DF004DC9B3 /* LoggedAccounts.swift in Sources */,
B9C20D612B949AD7004DC9B3 /* Redeclarations.swift in Sources */,
B9C20D122B921C78004DC9B3 /* FollowCountWidget.swift in Sources */,
B9A80DDE2C67BFF800DE3D88 /* CreatePostWidget.swift in Sources */,
B9C20D102B921C78004DC9B3 /* ThreadedWidgetsBundle.swift in Sources */,
B9A80DE02C67C2D000DE3D88 /* Navigator.swift in Sources */,
B9C20D142B921C78004DC9B3 /* AppIntent.swift in Sources */,
B9C20D372B9229EC004DC9B3 /* AppInfo.swift in Sources */,
B9A80DF02C67C40900DE3D88 /* SearchResults.swift in Sources */,
B9A80DF12C67C40900DE3D88 /* ContentFilter.swift in Sources */,
B9A80E232C67C4B700DE3D88 /* AttachmentView.swift in Sources */,
B9A80E242C67C4B700DE3D88 /* SettingsView.swift in Sources */,
B9A80E252C67C4B700DE3D88 /* ConnectView.swift in Sources */,
B9A80E262C67C4B700DE3D88 /* PostingView.swift in Sources */,
B9A80E272C67C4B700DE3D88 /* PostDetailsView.swift in Sources */,
B9A80E282C67C4B700DE3D88 /* IconView.swift in Sources */,
B9A80E292C67C4B700DE3D88 /* UpdateView.swift in Sources */,
B9A80E2A2C67C4B700DE3D88 /* ReportStatusView.swift in Sources */,
B9A80E2B2C67C4B700DE3D88 /* PrivacyView.swift in Sources */,
B9A80E2C2C67C4B700DE3D88 /* SafariView.swift in Sources */,
B9A80E2D2C67C4B700DE3D88 /* FilterView.swift in Sources */,
B9A80E2E2C67C4B700DE3D88 /* AppearenceView.swift in Sources */,
B9A80E2F2C67C4B700DE3D88 /* ContactsView.swift in Sources */,
B9A80E302C67C4B700DE3D88 /* PostsView.swift in Sources */,
B9A80E312C67C4B700DE3D88 /* NotificationsView.swift in Sources */,
B9A80E322C67C4B700DE3D88 /* EditProfileView.swift in Sources */,
B9A80E332C67C4B700DE3D88 /* ContentView.swift in Sources */,
B9A80E342C67C4B700DE3D88 /* DiscoveryView.swift in Sources */,
B9A80E352C67C4B700DE3D88 /* AccountView.swift in Sources */,
B9A80E362C67C4B700DE3D88 /* SupportView.swift in Sources */,
B9A80E372C67C4B700DE3D88 /* AddInstanceView.swift in Sources */,
B9A80E382C67C4B700DE3D88 /* ShopView.swift in Sources */,
B9A80E392C67C4B700DE3D88 /* AboutView.swift in Sources */,
B9A80E3A2C67C4B700DE3D88 /* RestrictedView.swift in Sources */,
B9A80E3B2C67C4B700DE3D88 /* TimelineView.swift in Sources */,
B9A80E3C2C67C4B700DE3D88 /* ProfileView.swift in Sources */,
B9A80E0A2C67C48D00DE3D88 /* AppDelegate.swift in Sources */,
B9A80DF22C67C40900DE3D88 /* FetchTimeline.swift in Sources */,
B9A80DF32C67C40900DE3D88 /* HTMLString.swift in Sources */,
B9A80DF42C67C40900DE3D88 /* TimelineFilter.swift in Sources */,
B9A80DF52C67C40900DE3D88 /* Tenor.swift in Sources */,
B9A80DF62C67C40900DE3D88 /* Tag.swift in Sources */,
B9A80DF72C67C40900DE3D88 /* MediaTransferables.swift in Sources */,
B9A80DF82C67C40900DE3D88 /* Compressor.swift in Sources */,
B9A80DF92C67C40900DE3D88 /* Status.swift in Sources */,
B9C20D3D2B9229EC004DC9B3 /* Emoji.swift in Sources */,
B9B469B02B9A275F00AD5585 /* FollowGoalWidget.swift in Sources */,
B9C20D342B9229EC004DC9B3 /* Client.swift in Sources */,
B9A80DFA2C67C40E00DE3D88 /* URLNavigator.swift in Sources */,
B9A80DFB2C67C40E00DE3D88 /* AltClients.swift in Sources */,
B9A80DFC2C67C40E00DE3D88 /* MastodonRequest.swift in Sources */,
B9A80DFD2C67C40E00DE3D88 /* UserPreferences.swift in Sources */,
B9A80E0B2C67C4A600DE3D88 /* AccountRow.swift in Sources */,
B9A80E0C2C67C4A600DE3D88 /* ComingSoonView.swift in Sources */,
B9A80E0D2C67C4A600DE3D88 /* OnlineImage.swift in Sources */,
B98DD8D92C6821F7009F40DD /* CreatePostControl.swift in Sources */,
B9A80E0E2C67C4A600DE3D88 /* DynamicTextEditor.swift in Sources */,
B9A80E6F2C67C62800DE3D88 /* Message.swift in Sources */,
B9A80E0F2C67C4A600DE3D88 /* ContactRow.swift in Sources */,
B9A80E1B2C67C4AE00DE3D88 /* PostCardView.swift in Sources */,
B9A80E1C2C67C4AE00DE3D88 /* CompactPostView.swift in Sources */,
B9A80E1D2C67C4AE00DE3D88 /* PostMenu.swift in Sources */,
B9A80E1E2C67C4AE00DE3D88 /* PostInteractor.swift in Sources */,
B9A80E1F2C67C4AE00DE3D88 /* EmojiSelector.swift in Sources */,
B9A80E202C67C4AE00DE3D88 /* PostPoll.swift in Sources */,
B9A80E212C67C4AE00DE3D88 /* PostAttachment.swift in Sources */,
B9A80E222C67C4AE00DE3D88 /* QuotePostView.swift in Sources */,
B9A80E102C67C4A600DE3D88 /* WarningView.swift in Sources */,
B9A80E112C67C4A600DE3D88 /* ThreadedStyle.swift in Sources */,
B9A80E122C67C4A600DE3D88 /* NotificationRow.swift in Sources */,
B9A80E132C67C4A600DE3D88 /* TextEmoji.swift in Sources */,
B9A80E142C67C4A600DE3D88 /* SearchResultView.swift in Sources */,
B9A80E152C67C4A600DE3D88 /* SymbolWidth.swift in Sources */,
B9A80E162C67C4A600DE3D88 /* TabsView.swift in Sources */,
B9A80E172C67C4A600DE3D88 /* ShareSheetController.swift in Sources */,
B9A80E182C67C4A600DE3D88 /* ButtonStyles.swift in Sources */,
B9A80E192C67C4A600DE3D88 /* ProfilePicture.swift in Sources */,
B9A80E1A2C67C4A600DE3D88 /* MailView.swift in Sources */,
B9A80E6D2C67C61E00DE3D88 /* Notification.swift in Sources */,
B9A80DFF2C67C40E00DE3D88 /* HapticManager.swift in Sources */,
B9A80E002C67C40E00DE3D88 /* Instance.swift in Sources */,
B9A80DE72C67C3FC00DE3D88 /* Account+Elms.swift in Sources */,
B9A80DE82C67C3FC00DE3D88 /* Account.swift in Sources */,
B9A80DE92C67C3FC00DE3D88 /* LoggedAccounts.swift in Sources */,
B9A80DEA2C67C3FC00DE3D88 /* AccountManager.swift in Sources */,
B9A80DEB2C67C3FC00DE3D88 /* AccountsList.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -856,6 +926,7 @@
B98F47962B645DF40092000F /* EmojiSelector.swift in Sources */,
B9B469B22B9A6E8300AD5585 /* PrivacyView.swift in Sources */,
B9FB94882B2E223E00D81C07 /* Emoji.swift in Sources */,
B9A80DE22C67C38E00DE3D88 /* URLNavigator.swift in Sources */,
B93B67782B42E8F0000892E9 /* TextEmoji.swift in Sources */,
B9FB94762B2E023D00D81C07 /* TabsView.swift in Sources */,
B9FB947D2B2E19E300D81C07 /* AccountManager.swift in Sources */,
@ -875,7 +946,6 @@
B9FB94972B2EDABF00D81C07 /* SupportView.swift in Sources */,
B9F8FA162B5D3AC30044DAB4 /* SafariView.swift in Sources */,
B97798892B853E6600DC869F /* UpdateView.swift in Sources */,
B9B469D82B9A8B1600AD5585 /* GivenAccount.swift in Sources */,
B9842C142B2F310C00D9F3C1 /* FetchTimeline.swift in Sources */,
B9842C162B2F363600D9F3C1 /* TimelineFilter.swift in Sources */,
B9B63B232B447B8000BBC82D /* PostCardView.swift in Sources */,
@ -884,12 +954,14 @@
B93ADFCB2B7625CD00FF9172 /* DiscoveryView.swift in Sources */,
B9D9C6C72B6A590F00C26A41 /* ProfilePicture.swift in Sources */,
B9842C102B2F228C00D9F3C1 /* Status.swift in Sources */,
B98DD8D82C6821F7009F40DD /* CreatePostControl.swift in Sources */,
B9D9C6C32B6A576C00C26A41 /* NotificationsView.swift in Sources */,
B9FA6E772B82788A00D63E30 /* AccountRow.swift in Sources */,
B934EA242BAB5E7F001F4345 /* RestrictedView.swift in Sources */,
B9FB94722B2DF49700D81C07 /* ConnectView.swift in Sources */,
B9FB945B2B2DEECE00D81C07 /* ThreadedApp.swift in Sources */,
B9FB94862B2E211200D81C07 /* Account+Elms.swift in Sources */,
B9A80DDF2C67C27B00DE3D88 /* AppIntent.swift in Sources */,
B97491E32B6E96700098BC48 /* SymbolWidth.swift in Sources */,
B9FB94BC2B2F035500D81C07 /* Tag.swift in Sources */,
B9029FC42B8125CE00AA9B68 /* HuggingFace.swift in Sources */,
@ -902,13 +974,15 @@
B9FB94812B2E1FEF00D81C07 /* HTMLString.swift in Sources */,
B9FB947F2B2E1D5F00D81C07 /* Account.swift in Sources */,
B9842C122B2F2A5800D9F3C1 /* TimelineView.swift in Sources */,
B9B469D42B9A871C00AD5585 /* WatchConnectivity.swift in Sources */,
B9FB948C2B2E232300D81C07 /* OnlineImage.swift in Sources */,
B9BED5182B5D649C00C9B715 /* PostMenu.swift in Sources */,
B9FB94742B2DF6A100D81C07 /* ButtonStyles.swift in Sources */,
B999DE5E2B76F9D100509868 /* Message.swift in Sources */,
B915C4422B6F908C00042DDB /* ProfileView.swift in Sources */,
B9FB94702B2DF3CD00D81C07 /* Navigator.swift in Sources */,
B9A80E9A2C67D56900DE3D88 /* FollowCountWidget.swift in Sources */,
B9A80E9B2C67D56900DE3D88 /* FollowGoalWidget.swift in Sources */,
B9A80E9C2C67D56900DE3D88 /* CreatePostWidget.swift in Sources */,
B9C7F46C2C387D3B009C36DC /* WarningView.swift in Sources */,
B9EBE8562B47256900FB594D /* PostAttachment.swift in Sources */,
B9EBE8582B474FD600FB594D /* AppDelegate.swift in Sources */,
@ -944,11 +1018,6 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
B9B469C32B9A7E6B00AD5585 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = B9B469B62B9A7E6800AD5585 /* ThreadedWatch Watch App */;
targetProxy = B9B469C22B9A7E6B00AD5585 /* PBXContainerItemProxy */;
};
B9C20D192B921C7B004DC9B3 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = B9C20D082B921C78004DC9B3 /* ThreadedWidgetsExtension */;
@ -973,68 +1042,6 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
B9B469C62B9A7E6B00AD5585 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "ThreadedWatch/ThreadedWatch Watch App.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"ThreadedWatch/Preview Content\"";
DEVELOPMENT_TEAM = HB5P3BML86;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = Threaded;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = fr.lumaa.Threaded;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2.0;
PRODUCT_BUNDLE_IDENTIFIER = fr.lumaa.Threaded.watchkitapp;
PRODUCT_NAME = Threaded;
SDKROOT = watchos;
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 10.0;
};
name = Debug;
};
B9B469C72B9A7E6B00AD5585 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "ThreadedWatch/ThreadedWatch Watch App.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"ThreadedWatch/Preview Content\"";
DEVELOPMENT_TEAM = HB5P3BML86;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = Threaded;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = fr.lumaa.Threaded;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2.0;
PRODUCT_BUNDLE_IDENTIFIER = fr.lumaa.Threaded.watchkitapp;
PRODUCT_NAME = Threaded;
SDKROOT = watchos;
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 10.0;
};
name = Release;
};
B9C20D1B2B921C7B004DC9B3 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -1058,13 +1065,13 @@
PRODUCT_BUNDLE_IDENTIFIER = fr.lumaa.Threaded.ThreadedWidgets;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator watchos watchsimulator";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,4";
TARGETED_DEVICE_FAMILY = 1;
WATCHOS_DEPLOYMENT_TARGET = 10.0;
};
name = Debug;
@ -1092,13 +1099,13 @@
PRODUCT_BUNDLE_IDENTIFIER = fr.lumaa.Threaded.ThreadedWidgets;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator watchos watchsimulator";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,4";
TARGETED_DEVICE_FAMILY = 1;
WATCHOS_DEPLOYMENT_TARGET = 10.0;
};
name = Release;
@ -1163,6 +1170,7 @@
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
WATCHOS_DEPLOYMENT_TARGET = 10.0;
};
@ -1221,6 +1229,7 @@
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES;
VALIDATE_PRODUCT = YES;
WATCHOS_DEPLOYMENT_TARGET = 10.0;
};
@ -1373,15 +1382,6 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
B9B469C52B9A7E6B00AD5585 /* Build configuration list for PBXNativeTarget "ThreadedWatch Watch App" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B9B469C62B9A7E6B00AD5585 /* Debug */,
B9B469C72B9A7E6B00AD5585 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
B9C20D1D2B921C7B004DC9B3 /* Build configuration list for PBXNativeTarget "ThreadedWidgetsExtension" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@ -1499,21 +1499,61 @@
package = B95ED2312B8707D60055F5BD /* XCRemoteSwiftPackageReference "purchases-ios" */;
productName = RevenueCatUI;
};
B9B469CE2B9A82ED00AD5585 /* KeychainSwift */ = {
B9A80E842C67C7C300DE3D88 /* SwiftSoup */ = {
isa = XCSwiftPackageProductDependency;
package = B9FB94822B2E20AF00D81C07 /* XCRemoteSwiftPackageReference "SwiftSoup" */;
productName = SwiftSoup;
};
B9A80E862C67C7C300DE3D88 /* Nuke */ = {
isa = XCSwiftPackageProductDependency;
package = B93B676B2B42C94F000892E9 /* XCRemoteSwiftPackageReference "Nuke" */;
productName = Nuke;
};
B9A80E882C67C7C300DE3D88 /* NukeExtensions */ = {
isa = XCSwiftPackageProductDependency;
package = B93B676B2B42C94F000892E9 /* XCRemoteSwiftPackageReference "Nuke" */;
productName = NukeExtensions;
};
B9A80E8A2C67C7C300DE3D88 /* NukeUI */ = {
isa = XCSwiftPackageProductDependency;
package = B93B676B2B42C94F000892E9 /* XCRemoteSwiftPackageReference "Nuke" */;
productName = NukeUI;
};
B9A80E8C2C67C7C300DE3D88 /* NukeVideo */ = {
isa = XCSwiftPackageProductDependency;
package = B93B676B2B42C94F000892E9 /* XCRemoteSwiftPackageReference "Nuke" */;
productName = NukeVideo;
};
B9A80E8E2C67C7C300DE3D88 /* EmojiText */ = {
isa = XCSwiftPackageProductDependency;
package = B93B67742B42E8AB000892E9 /* XCRemoteSwiftPackageReference "EmojiText" */;
productName = EmojiText;
};
B9A80E902C67C7C300DE3D88 /* KeychainSwift */ = {
isa = XCSwiftPackageProductDependency;
package = B9BF54052B6B6823004B24E7 /* XCRemoteSwiftPackageReference "keychain-swift" */;
productName = KeychainSwift;
};
B9A80E922C67C7C300DE3D88 /* ReceiptParser */ = {
isa = XCSwiftPackageProductDependency;
package = B95ED2312B8707D60055F5BD /* XCRemoteSwiftPackageReference "purchases-ios" */;
productName = ReceiptParser;
};
B9A80E942C67C7C300DE3D88 /* RevenueCat */ = {
isa = XCSwiftPackageProductDependency;
package = B95ED2312B8707D60055F5BD /* XCRemoteSwiftPackageReference "purchases-ios" */;
productName = RevenueCat;
};
B9A80E962C67C7C300DE3D88 /* RevenueCatUI */ = {
isa = XCSwiftPackageProductDependency;
package = B95ED2312B8707D60055F5BD /* XCRemoteSwiftPackageReference "purchases-ios" */;
productName = RevenueCatUI;
};
B9BF54062B6B6823004B24E7 /* KeychainSwift */ = {
isa = XCSwiftPackageProductDependency;
package = B9BF54052B6B6823004B24E7 /* XCRemoteSwiftPackageReference "keychain-swift" */;
productName = KeychainSwift;
};
B9C20D552B922DAC004DC9B3 /* KeychainSwift */ = {
isa = XCSwiftPackageProductDependency;
package = B9BF54052B6B6823004B24E7 /* XCRemoteSwiftPackageReference "keychain-swift" */;
productName = KeychainSwift;
};
B9FB94832B2E20AF00D81C07 /* SwiftSoup */ = {
isa = XCSwiftPackageProductDependency;
package = B9FB94822B2E20AF00D81C07 /* XCRemoteSwiftPackageReference "SwiftSoup" */;

View File

@ -85,6 +85,15 @@
ReferencedContainer = "container:Threaded.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B9B469B62B9A7E6800AD5585"
BuildableName = "Threaded.app"
BlueprintName = "ThreadedWatch Watch App"
ReferencedContainer = "container:Threaded.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">

View File

@ -34,5 +34,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"localizable" : true
}
}

View File

@ -48,8 +48,8 @@ struct PostCardView: View {
.stroke(.gray.opacity(0.3), lineWidth: 1)
)
.onTapGesture {
if UIApplication.shared.canOpenURL(URL(string: card.url)!) {
openURL(URL(string: card.url)!)
if let url = URL(string: card.url) {
openURL(url)
}
}
}

View File

@ -3,15 +3,19 @@
import SwiftUI
struct TabsView: View {
@Binding var selectedTab: TabDestination
@State var selectedTab: TabDestination = Navigator.shared.selectedTab
var canTap: Binding<Bool> = .constant(true)
var postButton: () -> Void = {}
var tapAction: () -> Void = {}
var retapAction: () -> Void = {}
var body: some View {
HStack(alignment: .center) {
Button {
guard canTap.wrappedValue else { return }
if selectedTab == .timeline {
retapAction()
} else {
@ -25,11 +29,14 @@ struct TabsView: View {
Tabs.timeline.image
}
}
.disabled(!canTap.wrappedValue)
.buttonStyle(NoTapAnimationStyle())
Spacer()
Button {
guard canTap.wrappedValue else { return }
if selectedTab == .search {
retapAction()
} else {
@ -43,20 +50,26 @@ struct TabsView: View {
Tabs.search.image
}
}
.disabled(!canTap.wrappedValue)
.buttonStyle(NoTapAnimationStyle())
Spacer()
Button {
guard canTap.wrappedValue else { return }
postButton()
} label: {
Tabs.post.image
}
.disabled(!canTap.wrappedValue)
.buttonStyle(NoTapAnimationStyle())
Spacer()
Button {
guard canTap.wrappedValue else { return }
if selectedTab == .activity {
retapAction()
} else {
@ -70,11 +83,14 @@ struct TabsView: View {
Tabs.activity.image
}
}
.disabled(!canTap.wrappedValue)
.buttonStyle(NoTapAnimationStyle())
Spacer()
Button {
guard canTap.wrappedValue else { return }
if selectedTab == .profile {
retapAction()
} else {
@ -88,10 +104,14 @@ struct TabsView: View {
Tabs.profile.image
}
}
.disabled(!canTap.wrappedValue)
.buttonStyle(NoTapAnimationStyle())
}
.padding(.horizontal, 30)
.background(Color.appBackground)
.onChange(of: selectedTab) { _, newValue in
Navigator.shared.selectedTab = newValue
}
}
}

View File

@ -2,6 +2,7 @@
import Foundation
/// Mastodon visibility strings
public enum Visibility: String, Codable, CaseIterable, Hashable, Equatable, Sendable {
case pub = "public"
case unlisted

View File

@ -9,3 +9,7 @@ public enum AppInfo {
public static let defaultServer = "mastodon.social"
public static let website = "https://apps.lumaa.fr/app/threaded"
}
extension AppInfo {
static var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
}

View File

@ -275,3 +275,9 @@ public final class Client: Equatable, Identifiable, Hashable {
}
extension Client: Sendable {}
extension CGFloat {
static func getFontSize(from font: UIFont.TextStyle) -> CGFloat {
return UIFont.preferredFont(forTextStyle: font).pointSize
}
}

View File

@ -5,74 +5,80 @@ import SwiftUI
@Observable
public class Navigator: ObservableObject {
public static var shared: Navigator = Navigator()
public var path: [RouterDestination] = []
public var presentedSheet: SheetDestination?
public var presentedCover: SheetDestination?
public var selectedTab: TabDestination = .timeline
public var selectedTab: TabDestination {
set {
change(to: newValue)
}
get {
return self.currentTab
}
}
private var currentTab: TabDestination = .timeline
public var inSettings: Bool {
self.path.contains(RouterDestination.allSettings)
}
public private(set) var memorizedNav: [TabDestination : [RouterDestination]] = [:]
public var showTabbar: Bool {
get {
self.visiTabbar
}
set {
withAnimation(.spring) {
self.visiTabbar = newValue
}
}
}
private var visiTabbar: Bool = true
public var client: Client?
public func navigate(to: RouterDestination) {
path.append(to)
}
public func handle(url: URL) -> OpenURLAction.Result {
guard let client = self.client else { return .systemAction }
let path: String = url.absoluteString.replacingOccurrences(of: AppInfo.scheme, with: "") // remove all path
let urlPath: URL = URL(string: path)!
let server: String = urlPath.host() ?? client.server
let lastIndex = urlPath.pathComponents.count - 1
let actionType = urlPath.pathComponents[lastIndex - 1]
if client.isAuth && client.hasConnection(with: url) {
if urlPath.lastPathComponent.starts(with: "@") {
Task {
do {
print("\(urlPath.lastPathComponent)@\(server.replacingOccurrences(of: "www.", with: ""))")
let search: SearchResults = try await client.get(endpoint: Search.search(query: "\(urlPath.lastPathComponent)@\(server.replacingOccurrences(of: "www.", with: ""))", type: "accounts", offset: nil, following: nil), forceVersion: .v2)
print(search)
let acc: Account = search.accounts.first ?? .placeholder()
self.navigate(to: .account(acc: acc))
} catch {
print(error)
}
}
return OpenURLAction.Result.handled
} else {
self.presentedSheet = .safari(url: url)
}
} else {
Task {
do {
let connections: [String] = try await client.get(endpoint: Instances.peers)
client.addConnections(connections)
if client.hasConnection(with: url) {
_ = self.handle(url: url)
} else {
self.presentedSheet = .safari(url: url)
}
} catch {
self.presentedSheet = .safari(url: url)
}
}
return OpenURLAction.Result.handled
/// Changes the current tab from the current ``Navigator`` class
func change(to tab: TabDestination) {
savePath()
withAnimation(.spring) {
loadPath(from: tab)
}
return OpenURLAction.Result.handled
}
private func savePath() {
let lastTab: TabDestination = self.currentTab
let lastPath: [RouterDestination] = self.path
memorizedNav.updateValue(lastPath, forKey: lastTab)
}
private func loadPath(from tab: TabDestination) {
if let (newTab, newPath) = memorizedNav.first(where: { $0.key == tab }).map({ [$0.key : $0.value] })?.first {
self.currentTab = newTab
self.path = newPath
} else {
print("Couldn't find Navigator data from \(tab.id), created new ones")
self.currentTab = tab
self.path = []
}
}
/// This only applies on the current path, not the saved ones in ``memorizedNav``
public func removeSettingsOfPath() {
self.path = self.path.filter({ !RouterDestination.allSettings.contains($0) })
}
}
/// This can be used for universal ``SheetDestination``s
public class UniversalNavigator: Navigator {
static var shared: UniversalNavigator = UniversalNavigator()
public var tabNavigator: Navigator?
public static var `static`: UniversalNavigator = UniversalNavigator()
}
public enum TabDestination: Identifiable {

View File

@ -0,0 +1,55 @@
// Made by Lumaa
import Foundation
import SwiftUI
extension Navigator {
public func handle(url: URL) -> OpenURLAction.Result {
guard let client = self.client else { return .systemAction }
let path: String = url.absoluteString.replacingOccurrences(of: AppInfo.scheme, with: "") // remove all path
let urlPath: URL = URL(string: path)!
let server: String = urlPath.host() ?? client.server
let lastIndex = urlPath.pathComponents.count - 1
let actionType = urlPath.pathComponents[lastIndex - 1]
if client.isAuth && client.hasConnection(with: url) {
if urlPath.lastPathComponent.starts(with: "@") {
Task {
do {
print("\(urlPath.lastPathComponent)@\(server.replacingOccurrences(of: "www.", with: ""))")
let search: SearchResults = try await client.get(endpoint: Search.search(query: "\(urlPath.lastPathComponent)@\(server.replacingOccurrences(of: "www.", with: ""))", type: "accounts", offset: nil, following: nil), forceVersion: .v2)
print(search)
let acc: Account = search.accounts.first ?? .placeholder()
self.navigate(to: .account(acc: acc))
} catch {
print(error)
}
}
return OpenURLAction.Result.handled
} else {
self.presentedSheet = .safari(url: url)
}
} else {
Task {
do {
let connections: [String] = try await client.get(endpoint: Instances.peers)
client.addConnections(connections)
if client.hasConnection(with: url) {
_ = self.handle(url: url)
} else {
self.presentedSheet = .safari(url: url)
}
} catch {
self.presentedSheet = .safari(url: url)
}
}
return OpenURLAction.Result.handled
}
return OpenURLAction.Result.handled
}
}

View File

@ -209,6 +209,22 @@
}
}
},
"account" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Account"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Compte"
}
}
}
},
"account.block" : {
"localizations" : {
"en" : {
@ -1093,6 +1109,38 @@
}
}
},
"control.open.composer" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "New post"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Nouvelle publication"
}
}
}
},
"control.open.composer.description" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Create a new post"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Créer une nouvelle publication"
}
}
}
},
"discovery" : {
"localizations" : {
"en" : {
@ -1541,6 +1589,150 @@
}
}
},
"intent.open.composer" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Start a new post"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Démarrer une nouvelle publication"
}
}
}
},
"intent.open.composer.description" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Opens the Threaded app with the post composer "
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ouvre lapplication Threaded avec le compositeur de publications"
}
}
}
},
"intent.publish.any.issue" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "There was an issue while posting."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Il y a eu un soucis lors de la publication."
}
}
}
},
"intent.publish.any.visibility-dialog" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "How visible do you want your post to be?"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Quelle visibilité voulez-vous que cette publication ai ?"
}
}
}
},
"intent.publish.text" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Publish a text-based post"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Publier du texte dans une publication"
}
}
}
},
"intent.publish.text.account-dialog" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Select the account the post will be published on"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sélectionnez le compte sur lequel la publication sera publiée"
}
}
}
},
"intent.publish.text.content-dialog" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Write the content of your post"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Écrivez le contenu de votre publication"
}
}
}
},
"intent.publish.text.description" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Publish a text-based post on Mastodon"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Publie du texte dans une publication sur Mastodon"
}
}
}
},
"intent.publish.text.summary-${content}" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Post \"${content}\""
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Publier « ${content} »"
}
}
}
},
"login.instance.unsafe" : {
"localizations" : {
"en" : {
@ -2425,6 +2617,7 @@
}
},
"settings.account-switcher.send-to-watch" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@ -2438,7 +2631,8 @@
"value" : "Envoyer vers l'Apple Watch"
}
}
}
},
"shouldTranslate" : false
},
"settings.cancel" : {
"localizations" : {
@ -4439,6 +4633,151 @@
}
}
}
},
"widget.composer" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Quick Post"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Publication Rapide"
}
}
}
},
"widget.follow-count" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Follow Count"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Compteur de Followers"
}
}
}
},
"widget.follow-count.description" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Keep track of your follower count"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Garder trace de votre nombre de followers"
}
}
}
},
"widget.follow-goal" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Follow Goal"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Objectif de Followers"
}
}
}
},
"widget.follow-goal.description" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Keep track of your follower count and set a goal for it"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Gardez trace de votre nombre de followers et faites en un objectif"
}
}
}
},
"widget.followers" : {
"comment" : "Lowercase, shown in the \"Follow Count\" widget",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "followers"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "followers"
}
}
}
},
"widget.open.composer" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Create a new post"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Créez une nouvelle publication"
}
}
}
},
"widget.select-account" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Select an account"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sélectionnez un compte"
}
}
}
},
"widget.set-goal" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Follower goal"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Objectif de follower"
}
}
}
}
},
"version" : "1.0"

View File

@ -7,7 +7,8 @@ import RevenueCat
@main
struct ThreadedApp: App {
init() {
guard let plist = AppDelegate.readSecret() else { return }
guard let plist = AppDelegate.readSecret() else { fatalError("Missing Secret.plist file") }
if let apiKey = plist["RevenueCat_public"], let deviceId = UIDevice.current.identifierForVendor?.uuidString {
#if DEBUG
Purchases.logLevel = .debug
@ -16,6 +17,8 @@ struct ThreadedApp: App {
Purchases.configure(withAPIKey: apiKey, appUserID: deviceId)
}
}
ThreadedShortcuts.updateAppShortcutParameters()
}
var body: some Scene {
@ -48,7 +51,3 @@ public extension View {
.modelContainer(for: [LoggedAccount.self, ModelFilter.self])
}
}
extension AppInfo {
static var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
}

View File

@ -5,7 +5,7 @@ import SwiftUI
struct AccountView: View {
@Environment(AccountManager.self) private var accountManager: AccountManager
@State private var navigator: Navigator = Navigator()
@State private var navigator: Navigator = Navigator.shared
@State public var account: Account
var body: some View {

View File

@ -6,14 +6,15 @@ import SwiftUI
struct ContentView: View {
@UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
private var huggingFace: HuggingFace = HuggingFace()
// private var huggingFace: HuggingFace = HuggingFace()
@State private var preferences: UserPreferences = .defaultPreferences
@StateObject private var uniNavigator = UniversalNavigator.shared
@State private var navigator: Navigator = .shared
@StateObject private var uniNavigator = UniversalNavigator.static
@StateObject private var accountManager: AccountManager = AccountManager.shared
var body: some View {
ZStack {
TabView(selection: $uniNavigator.selectedTab, content: {
TabView(selection: $navigator.selectedTab, content: {
if accountManager.getAccount() != nil {
TimelineView(timelineModel: FetchTimeline(client: accountManager.forceClient()))
.background(Color.appBackground)
@ -40,18 +41,25 @@ struct ContentView: View {
}
.frame(maxWidth: appDelegate.windowWidth)
.overlay(alignment: .bottom) {
TabsView(selectedTab: $uniNavigator.selectedTab, postButton: {
TabsView(canTap: $navigator.showTabbar, postButton: {
uniNavigator.presentedSheet = .post(content: "", replyId: nil, editId: nil)
}, retapAction: {
navigator.path = []
// Navigator.shared.showTabbar.toggle()
})
.safeAreaPadding(.vertical, 10)
.zIndex(10)
.offset(
y: navigator.showTabbar ? 0 : CGFloat
.getFontSize(from: .extraLargeTitle) * 7.5
)
.allowsHitTesting(navigator.showTabbar)
}
.withSheets(sheetDestination: $uniNavigator.presentedSheet)
.withCovers(sheetDestination: $uniNavigator.presentedCover)
.environment(uniNavigator)
.environment(accountManager)
.environment(appDelegate)
.environment(huggingFace)
.environmentObject(preferences)
.navigationBarTitleDisplayMode(.inline)
.onAppear {
@ -69,8 +77,6 @@ struct ContentView: View {
await recognizeAccount()
}
}
_ = HuggingFace.getToken()
}
.onOpenURL(perform: { url in
guard preferences.browserType == .inApp else { return }

View File

@ -5,8 +5,8 @@ import SwiftUI
struct DiscoveryView: View {
@Environment(AccountManager.self) private var accountManager: AccountManager
@State private var navigator: Navigator = Navigator()
@State private var navigator: Navigator = Navigator.shared
@State private var searchQuery: String = ""
@State private var results: [String : SearchResults] = [:]
@State private var querying: Bool = false

View File

@ -6,7 +6,7 @@ import TipKit
struct NotificationsView: View {
@Environment(AccountManager.self) private var accountManager
@State private var navigator: Navigator = Navigator()
@State private var navigator: Navigator = Navigator.shared
@State private var notifications: [GroupedNotification] = []
@State private var loadingNotifs: Bool = true
@State private var lastId: Int? = nil

View File

@ -194,7 +194,8 @@ struct PostingView: View {
.pickerStyle(.menu)
.foregroundStyle(Color.gray)
}
// MARK: Post function
private func postText() {
Task {
if let client = accountManager.getClient() {
@ -772,7 +773,7 @@ extension PostingView {
struct AltTextView: View {
@Environment(AccountManager.self) private var accountManager: AccountManager
@Environment(HuggingFace.self) private var huggingFace: HuggingFace
// @Environment(HuggingFace.self) private var huggingFace: HuggingFace
@Environment(\.dismiss) private var dismiss
var container: MediaContainer

View File

@ -6,6 +6,7 @@ struct ProfileView: View {
@Environment(AccountManager.self) private var accountManager: AccountManager
@Environment(UniversalNavigator.self) private var uniNav: UniversalNavigator
@Environment(AppDelegate.self) private var appDelegate: AppDelegate
@Environment(\.openURL) private var openURL: OpenURLAction
@EnvironmentObject private var navigator: Navigator
@Namespace var accountAnims
@ -284,9 +285,9 @@ struct ProfileView: View {
TextEmoji(field.value, emojis: account.emojis)
}
.onTapGesture {
if let url = URL(string: field.value.asRawText), UIApplication.shared.canOpenURL(url) {
if let url = URL(string: field.value.asRawText) {
HapticManager.playHaptics(haptics: Haptic.success)
UIApplication.shared.open(url)
openURL(url)
} else {
HapticManager.playHaptics(haptics: Haptic.error)
}

View File

@ -4,6 +4,7 @@ import SwiftUI
struct AboutView: View {
@Environment(AppDelegate.self) private var appDelegate: AppDelegate
@Environment(\.openURL) private var openURL: OpenURLAction
@EnvironmentObject private var navigator: Navigator
@ObservedObject private var userPreferences: UserPreferences = .defaultPreferences
@ -19,7 +20,9 @@ struct AboutView: View {
.listRowThreaded()
Button {
UIApplication.shared.open(URL(string: "https://lumaa.fr/?utm_source=ThreadedApp")!)
if let url = URL(string: "https://lumaa.fr/?utm_source=ThreadedApp") {
openURL(url)
}
} label: {
Text("about.lumaa")
}

View File

@ -8,7 +8,9 @@ struct IconView: View {
ForEach(AppIcons.allCases, id: \.self) { icon in
Button {
HapticManager.playHaptics(haptics: Haptic.tap)
#if NS_EXTENSION_UNAVAILABLE
changeAppIcon(to: icon.assetName)
#endif
} label: {
HStack {
Image(icon.assetName)
@ -28,7 +30,8 @@ struct IconView: View {
.navigationBarTitleDisplayMode(.inline)
.listThreaded()
}
#if NS_EXTENSION_UNAVAILABLE
private func changeAppIcon(to iconName: String) {
UIApplication.shared.setAlternateIconName(iconName) { error in
if let error = error {
@ -37,6 +40,7 @@ struct IconView: View {
}
}
}
#endif
}
private enum AppIcons: CaseIterable {
@ -112,7 +116,3 @@ private enum AppIcons: CaseIterable {
}
}
}
#Preview {
IconView()
}

View File

@ -151,6 +151,14 @@ struct SettingsView: View {
.listThreaded()
.navigationTitle("settings")
.navigationBarTitleDisplayMode(.inline)
.onAppear {
navigator.showTabbar = false
}
.onChange(of: navigator.path) { _, newValue in
guard !newValue.isEmpty else { navigator.showTabbar = true; return }
navigator.showTabbar = newValue
.filter({ $0 == RouterDestination.settings }).first == nil
}
}
}
@ -164,7 +172,7 @@ extension SettingsView {
var logged: LoggedAccount
var app: AppAccount
private let connectivity: SessionDelegator = .init()
// private let connectivity: SessionDelegator = .init()
@State private var account: Account? = nil
@State private var error: Bool = false
@ -248,23 +256,23 @@ extension SettingsView {
Divider()
if connectivity.isWorking {
Button {
// double check in case states change in between
if connectivity.isWorking {
let message = GivenAccount(acct: app.accountName!, bearerToken: app.oauthToken?.accessToken ?? "")
connectivity.session.sendMessageData(message.turnToMessage(), replyHandler: { data in
let str = String(data: data, encoding: .utf8)
print(str ?? "No data?")
HapticManager.playHaptics(haptics: Haptic.success)
})
} else {
print("No Watch?")
}
} label: {
Label("settings.account-switcher.send-to-watch", systemImage: "applewatch.and.arrow.forward")
}
}
// if connectivity.isWorking {
// Button {
// // double check in case states change in between
// if connectivity.isWorking {
// let message = GivenAccount(acct: app.accountName!, bearerToken: app.oauthToken?.accessToken ?? "")
// connectivity.session.sendMessageData(message.turnToMessage(), replyHandler: { data in
// let str = String(data: data, encoding: .utf8)
// print(str ?? "No data?")
// HapticManager.playHaptics(haptics: Haptic.success)
// })
// } else {
// print("No Watch?")
// }
// } label: {
// Label("settings.account-switcher.send-to-watch", systemImage: "applewatch.and.arrow.forward")
// }
// }
}
} else {
Circle()
@ -297,7 +305,7 @@ extension SettingsView {
.task {
account = await findAccount(acct: app.accountName!)
connectivity.initialize()
// connectivity.initialize()
}
}

View File

@ -5,7 +5,7 @@ import SwiftData
struct TimelineView: View {
@Environment(AccountManager.self) private var accountManager: AccountManager
@State var navigator: Navigator = Navigator()
@State var navigator: Navigator = Navigator.shared
@State private var showPicker: Bool = false
@State private var timelines: [TimelineFilter] = [.home, .trending, .local, .federated]

View File

@ -1,11 +0,0 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,14 +0,0 @@
{
"images" : [
{
"filename" : "ThreadedApp.png",
"idiom" : "universal",
"platform" : "watchos",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

View File

@ -1,6 +0,0 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,152 +0,0 @@
//Made by Lumaa
import SwiftUI
import SwiftData
struct ContentView: View {
@State private var givenAccounts: [GivenAccount] = []
private let connectivity: SessionDelegator = .init()
@State private var fetching: Bool = false
@State private var currentAccount: (UIImage, Int)? = nil
var body: some View {
NavigationStack {
TabView {
if givenAccounts.isEmpty || givenAccounts.count < 1 {
ContentUnavailableView("iphone.login", systemImage: "iphone", description: Text("iphone.login.description"))
.containerBackground(Color.yellow.gradient, for: .tabView)
.scrollDisabled(true)
} else {
ForEach(givenAccounts, id: \.bearerToken) { acc in
ZStack {
if currentAccount == nil {
ProgressView()
.task {
self.currentAccount = await getData(givenAccount: acc)
}
} else {
let username: String = String(acc.acct.split(separator: "@")[0])
let server: String = String(acc.acct.split(separator: "@")[1])
VStack {
Image(uiImage: currentAccount!.0)
.resizable()
.scaledToFit()
.frame(width: 60, height: 60)
.clipShape(Circle())
.padding(.top, 7.5)
.onDisappear() {
guard !fetching else { print("Fetching..."); return }
self.currentAccount = nil
}
Text(String("@\(username)"))
.font(.title2.bold())
Text(server)
.font(.caption)
.foregroundStyle(Color.gray)
ScrollView(.horizontal) {
HStack {
VStack {
Text(currentAccount!.1, format: .number.notation(.compactName))
.font(.headline)
Text("account.followers")
.font(.caption)
.foregroundStyle(Color.gray)
}
.safeAreaPadding()
}
}
.padding(.top, 7.5)
}
}
}
}
}
}
.tabViewStyle(.verticalPage(transitionStyle: .blur))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItemGroup(placement: .topBarLeading) {
Button {
refresh()
} label: {
Image(systemName: "arrow.clockwise")
}
}
}
}
.onAppear {
connectivity.initialize()
self.refresh()
}
}
private func refresh() {
self.givenAccounts = self.getAccounts()
if connectivity.isWorking {
withAnimation {
self.givenAccounts.append(contentsOf: connectivity.allMessage.filter({ !self.givenAccounts.contains($0) }))
if let _givenAccounts = connectivity.lastMessage, self.givenAccounts.isEmpty {
self.givenAccounts = [_givenAccounts] // fallback
}
}
}
self.givenAccounts = self.givenAccounts.uniqued()
self.saveCurrentModels()
}
private func getAccounts() -> [GivenAccount] {
guard let modelContainer: ModelContainer = try? ModelContainer(for: LoggedAccount.self, configurations: ModelConfiguration(isStoredInMemoryOnly: false)) else { return [] }
let modelContext = ModelContext(modelContainer)
let loggedAccounts: [LoggedAccount]? = try? modelContext.fetch(FetchDescriptor<LoggedAccount>())
let given: [GivenAccount] = loggedAccounts?.map({ $0.toGiven() }) ?? []
return given
}
private func getData(givenAccount: GivenAccount) async -> (UIImage, Int) {
let server = givenAccount.acct.split(separator: "@")[1]
let client: Client = Client(server: String(server), oauthToken: OauthToken(accessToken: givenAccount.bearerToken, tokenType: "Bearer", scope: "", createdAt: .nan))
var pfp: UIImage
do {
fetching = true
let acc = try await client.getString(endpoint: Accounts.verifyCredentials, forceVersion: .v1)
fetching = false
if let serialized: [String : Any] = try JSONSerialization.jsonObject(with: acc.data(using: String.Encoding.utf8) ?? Data()) as? [String : Any] {
let avatar: String = serialized["avatar"] as! String
let task = try await URLSession.shared.data(from: URL(string: avatar)!)
pfp = UIImage(data: task.0) ?? UIImage()
let followers: Int = serialized["followers_count"] as! Int
return (pfp, followers)
}
} catch {
print(error)
}
return (UIImage(), 0)
}
private func saveModel(model: LoggedAccount) {
guard let modelContainer: ModelContainer = try? ModelContainer(for: LoggedAccount.self, configurations: ModelConfiguration(isStoredInMemoryOnly: false)) else { return }
let modelContext = ModelContext(modelContainer)
modelContext.insert(model)
}
private func saveCurrentModels() {
for given in self.givenAccounts {
saveModel(model: given.toLogged())
}
}
}
#Preview {
ContentView()
}

View File

@ -1,59 +0,0 @@
//Made by Lumaa
import Foundation
struct GivenAccount: Hashable {
static let messageSeparator: String = "||"
let acct: String
let bearerToken: String
init(acct: String, bearerToken: String) {
self.acct = acct
self.bearerToken = bearerToken
}
private func messageString() -> String {
return "\(self.acct)\(Self.messageSeparator)\(self.bearerToken)"
}
func turnToMessage() -> Data {
let str = self.messageString()
return str.data(using: .utf8) ?? Data()
}
func turnToDictionary() -> [String : Any] {
let str = self.messageString()
return ["givenAccount" : str]
}
func toLogged() -> LoggedAccount {
let oauth = OauthToken(accessToken: self.bearerToken, tokenType: "Bearer", scope: "", createdAt: .nan)
return LoggedAccount(token: oauth, acct: self.acct)
}
static func makeFromMessage(_ message: Data) -> GivenAccount {
if let string = String(data: message, encoding: .utf8) {
let decomposed = string.split(separator: Self.messageSeparator)
let acct = decomposed[0]
let bearerToken = decomposed[1]
return GivenAccount(acct: String(acct), bearerToken: String(bearerToken))
}
fatalError("Message couldn't be stringified")
}
static func makeFromMessage(_ message: String) -> GivenAccount {
let decomposed = message.split(separator: Self.messageSeparator)
let acct = decomposed[0]
let bearerToken = decomposed[1]
return GivenAccount(acct: String(acct), bearerToken: String(bearerToken))
}
}
extension LoggedAccount {
func toGiven() -> GivenAccount {
return GivenAccount(acct: self.acct, bearerToken: self.token.accessToken)
}
}

View File

@ -1,54 +0,0 @@
{
"sourceLanguage" : "en",
"strings" : {
"account.followers" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "followers"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "followers"
}
}
}
},
"iphone.login" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Login with your iPhone"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Connexion via l'iPhone"
}
}
}
},
"iphone.login.description" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Use your iPhone to see your account on your Apple Watch"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Utilisez votre iPhone pour voir vos comptes sur votre Apple Watch"
}
}
}
}
},
"version" : "1.0"
}

View File

@ -1,6 +0,0 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,10 +0,0 @@
<?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>com.apple.security.application-groups</key>
<array>
<string>group.lumaa.ThreadedApp</string>
</array>
</dict>
</plist>

View File

@ -1,20 +0,0 @@
//Made by Lumaa
import SwiftUI
@main
struct ThreadedWatch_Watch_AppApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.modelData()
}
}
}
extension View {
func modelData() -> some View {
self
.modelContainer(for: LoggedAccount.self)
}
}

View File

@ -1,122 +0,0 @@
//Made by Lumaa
import Foundation
import WatchConnectivity
#if os(watchOS)
import ClockKit
#endif
// Implement WCSessionDelegate methods to receive Watch Connectivity data and notify clients.
// Handle WCSession status changes.
//
class SessionDelegator: NSObject, WCSessionDelegate {
public var session: WCSession = .default
public var lastMessage: GivenAccount? = nil
public var allMessage: [GivenAccount] = []
public var isWorking: Bool {
self.session.isReachable
}
func initialize() {
guard WCSession.isSupported() else { fatalError("Doesn't support WCSession") }
session.delegate = self
session.activate()
}
// Monitor WCSession activation state changes.
//
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
return
}
// Monitor WCSession reachability state changes.
//
func sessionReachabilityDidChange(_ session: WCSession) {
self.session = session
}
// Did receive an app context.
//
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String: Any]) {
if let givenAccount = applicationContext["givenAccount"] as? String {
self.lastMessage = GivenAccount.makeFromMessage(givenAccount)
guard !self.allMessage.contains(where: { $0.bearerToken == self.lastMessage!.bearerToken }) else { return }
self.allMessage.append(self.lastMessage!)
}
}
// Did receive a message, and the peer doesn't need a response.
//
func session(_ session: WCSession, didReceiveMessage message: [String: Any]) {
if let givenAccount = message["givenAccount"] as? String {
self.lastMessage = GivenAccount.makeFromMessage(givenAccount)
guard !self.allMessage.contains(where: { $0.bearerToken == self.lastMessage!.bearerToken }) else { return }
self.allMessage.append(self.lastMessage!)
}
}
// Did receive a message, and the peer needs a response.
//
func session(_ session: WCSession, didReceiveMessage message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void) {
self.session(session, didReceiveMessage: message)
let response = ["success" : true, "timestamp": Int(Date.now.timeIntervalSince1970)] as [String : Any]
replyHandler(response)
}
// Did receive a piece of message data, and the peer doesn't need a response.
//
func session(_ session: WCSession, didReceiveMessageData messageData: Data) {
self.lastMessage = GivenAccount.makeFromMessage(messageData)
guard !self.allMessage.contains(where: { $0.bearerToken == self.lastMessage!.bearerToken }) else { return }
self.allMessage.append(self.lastMessage!)
}
// Did receive a piece of message data, and the peer needs a response.
//
func session(_ session: WCSession, didReceiveMessageData messageData: Data, replyHandler: @escaping (Data) -> Void) {
self.session(session, didReceiveMessageData: messageData)
replyHandler(messageData) // Echo back the data.
}
// Did receive a piece of userInfo.
//
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) {
return
}
// Did finish sending a piece of userInfo.
//
func session(_ session: WCSession, didFinish userInfoTransfer: WCSessionUserInfoTransfer, error: Error?) {
return
}
// Did receive a file.
//
func session(_ session: WCSession, didReceive file: WCSessionFile) {
return
}
// Did finish a file transfer.
//
func session(_ session: WCSession, didFinish fileTransfer: WCSessionFileTransfer, error: Error?) {
return
}
// WCSessionDelegate methods for iOS only.
//
#if os(iOS)
func sessionDidBecomeInactive(_ session: WCSession) {
print("\(#function): activationState = \(session.activationState.rawValue)")
}
func sessionDidDeactivate(_ session: WCSession) {
// Activate the new session after having switched to a new watch.
session.activate()
}
func sessionWatchStateDidChange(_ session: WCSession) {
print("\(#function): activationState = \(session.activationState.rawValue)")
}
#endif
}

View File

@ -5,6 +5,23 @@ import SwiftData
import WidgetKit
import AppIntents
// MARK: - Shortcuts
struct ThreadedShortcuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] = [
.init(
intent: OpenComposerIntent(),
phrases: [
"Start a \(.applicationName) post",
"Post on \(.applicationName)"
],
shortTitle: "status.posting",
systemImageName: "square.and.pencil"
)
]
static var shortcutTileColor: ShortcutTileColor = .grayBlue
}
// MARK: - Account Intents
/// Widgets that require to select only an account will use this `ConfigurationIntent`
@ -31,6 +48,7 @@ struct AccountEntity: AppEntity {
let client: Client
let id: String
let username: String
let server: String
/// Bearer token
let token: OauthToken
@ -42,14 +60,16 @@ struct AccountEntity: AppEntity {
}
init(acct: String, username: String, token: OauthToken) {
self.client = Client(server: String(acct.split(separator: "@")[1]), version: .v2, oauthToken: token)
self.server = String(acct.split(separator: "@")[1])
self.client = Client(server: self.server, version: .v2, oauthToken: token)
self.id = acct
self.username = username
self.token = token
}
init(loggedAccount: LoggedAccount) {
self.client = Client(server: String(loggedAccount.acct.split(separator: "@")[1]), version: .v2, oauthToken: loggedAccount.token)
self.server = loggedAccount.app?.server ?? ""
self.client = Client(server: self.server, version: .v2, oauthToken: loggedAccount.token)
self.id = loggedAccount.acct
self.username = String(loggedAccount.acct.split(separator: "@")[0])
self.token = loggedAccount.token
@ -103,3 +123,120 @@ struct AccountQuery: EntityQuery {
try? await suggestedEntities().first
}
}
// MARK: - Post Intents
extension Visibility: AppEnum {
public static var caseDisplayRepresentations: [Visibility : DisplayRepresentation] {
[
.pub : DisplayRepresentation(title: "status.posting.visibility.public"),
.priv : DisplayRepresentation(title: "status.posting.visibility.private"),
.unlisted : DisplayRepresentation(title: "status.posting.visibility.unlisted"),
.direct : DisplayRepresentation(title: "status.posting.visibility.direct")
]
}
public static var typeDisplayRepresentation: TypeDisplayRepresentation {
TypeDisplayRepresentation(name: "status.posting.visibility")
}
}
struct OpenComposerIntent: AppIntent {
static var title: LocalizedStringResource = "intent.open.composer"
static var description: IntentDescription? = IntentDescription("intent.open.composer.description")
static var isDiscoverable: Bool = true
static var openAppWhenRun: Bool = true
static var authenticationPolicy: IntentAuthenticationPolicy = .requiresLocalDeviceAuthentication
func perform() async throws -> some IntentResult {
UniversalNavigator.static.presentedSheet =
.post(content: "", replyId: nil, editId: nil)
return .result()
}
}
struct PublishTextIntent: AppIntent {
static var title: LocalizedStringResource = "intent.publish.text"
static var description: IntentDescription? = IntentDescription("intent.publish.text.description")
static var isDiscoverable: Bool = true
static var openAppWhenRun: Bool = false
static var authenticationPolicy: IntentAuthenticationPolicy = .requiresLocalDeviceAuthentication
@Parameter(title: "account", requestDisambiguationDialog: IntentDialog("intent.publish.text.account-dialog"))
var account: AccountEntity?
@Parameter(title: "status.posting.placeholder", requestValueDialog: IntentDialog("intent.publish.text.content-dialog"))
var content: String
@Parameter(title: "status.posting.visibility", requestDisambiguationDialog: IntentDialog("intent.publish.any.visibility-dialog"))
var visibility: Visibility
static var parameterSummary: any ParameterSummary {
Summary("intent.publish.text.summary-\(\.$content)") {
\.$account
\.$visibility
}
}
func perform() async throws -> some IntentResult & ShowsSnippetView & ReturnsValue<String> {
if let client = account?.client, !client.server.isEmpty {
let data: StatusData = .init(
status: self.content,
visibility: self.visibility
)
// posting requires v1
if let res = try? await client.post(endpoint: Statuses.postStatus(json: data), forceVersion: .v1), res.statusCode == 200 {
return .result(
value: self.content,
view: Self.StatusSuccess(acc: account!, json: data)
)
}
}
return await .result(value: "", view: IssueView())
}
private struct IssueView: View {
var body: some View {
Label("intent.publish.any.issue", systemImage: "exclamationmark.triangle.fill")
.foregroundStyle(Color.red)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.padding()
.background(Color.black)
.clipShape(Capsule())
.padding(.horizontal)
}
}
private struct StatusSuccess: View {
var acc: AccountEntity
var json: StatusData
var body: some View {
HStack {
VStack(alignment: .leading, spacing: 7.5) {
Text("@\(acc.username)")
.foregroundStyle(Color.white)
.bold()
Text(json.status)
.foregroundStyle(Color.white)
.lineLimit(2)
}
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding(.vertical)
.padding(.horizontal, 25)
.background(Color.black)
.clipShape(Capsule())
.padding(.horizontal)
}
}
}

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0xFF",
"green" : "0xFF",
"red" : "0xFF"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0x10",
"green" : "0x10",
"red" : "0x10"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,25 @@
{
"images" : [
{
"filename" : "HeroIcon_black.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "HeroIcon_white.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"localizable" : true
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -0,0 +1,19 @@
// Made by Lumaa
import SwiftUI
import WidgetKit
@available(iOS 18.0, *)
struct CreatePostControl: ControlWidget {
let kind: String = "CreatePostControl"
var body: some ControlWidgetConfiguration {
StaticControlConfiguration(kind: kind) {
ControlWidgetButton(action: OpenComposerIntent()) {
Label("control.open.composer", systemImage: "square.and.pencil")
}
}
.displayName("control.open.composer")
.description("control.open.composer.description")
}
}

View File

@ -0,0 +1,85 @@
// Made by Lumaa
import SwiftUI
import WidgetKit
struct CreatePostWidget: Widget {
let kind: String = "CreatePostWidget"
var body: some WidgetConfiguration {
StaticConfiguration(
kind: kind,
provider: Provider()
) { entry in
CreatePostWidget.WidgetView()
}
.configurationDisplayName("widget.open.composer")
.description("widget.open.composer")
.supportedFamilies([.systemSmall])
}
struct WidgetView: View {
@Environment(\.widgetFamily) private var family: WidgetFamily
@Environment(\.colorScheme) private var colorScheme: ColorScheme
var body: some View {
ZStack {
if family == .systemSmall {
VStack {
Button(intent: OpenComposerIntent()) {
Image(systemName: "square.and.pencil")
.resizable()
.scaledToFit()
.frame(width: 80, height: 80)
}
.buttonStyle(.plain)
Text("widget.composer")
.font(.caption)
}
}
}
.containerBackground(Color.appBackground, for: .widget)
}
}
struct Provider: TimelineProvider {
func getSnapshot(
in context: Context,
completion: @escaping @Sendable (CreatePostWidget.Entry) -> Void
) {
completion(.init(date: .now))
}
func getTimeline(
in context: Context,
completion: @escaping (Timeline<CreatePostWidget.Entry>) -> Void
) {
var entries: [CreatePostWidget.Entry] = []
// Generate a timeline consisting of two entries an hour apart, starting from the current date.
let currentDate = Date()
for hourOffset in 0 ..< 2 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = CreatePostWidget.Entry(date: entryDate)
entries.append(entry)
}
completion(Timeline(entries: entries, policy: .atEnd))
}
func placeholder(in context: Context) -> CreatePostWidget.Entry {
return .init(date: .now)
}
}
struct Entry: TimelineEntry {
let date: Date
}
}
#Preview(as: .systemSmall) {
CreatePostWidget()
} timeline: {
CreatePostWidget.Entry(date: .now)
}

View File

@ -1,89 +0,0 @@
{
"sourceLanguage" : "en",
"strings" : {
"@%@" : {
},
"account" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Account"
}
}
}
},
"widget.follow-count" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Follower Count"
}
}
}
},
"widget.follow-count.description" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Get the current amount of followers of the accounts you have logged in Threaded"
}
}
}
},
"widget.follow-goal" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Follower Goal"
}
}
}
},
"widget.follow-goal.description" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Set a follower goal and see the progress"
}
}
}
},
"widget.followers" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Followers"
}
}
}
},
"widget.select-account" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Select an account"
}
}
}
},
"widget.set-goal" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Goal"
}
}
}
}
},
"version" : "1.0"
}

View File

@ -1,233 +0,0 @@
//Made by Lumaa
import Foundation
import SwiftUI
import UIKit
// this is messy but it's alr
#if os(iOS)
public class AppDelegate: NSObject, UIWindowSceneDelegate, Sendable, UIApplicationDelegate {
public var window: UIWindow?
public private(set) var windowWidth: CGFloat = UIScreen.main.bounds.size.width
public private(set) var windowHeight: CGFloat = UIScreen.main.bounds.size.height
public private(set) var secret: [String: String] = [:]
public func scene(_ scene: UIScene, willConnectTo _: UISceneSession, options _: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else { return }
window = windowScene.keyWindow
}
override public init() {
super.init()
if let path = Bundle.main.path(forResource: "Secret", ofType: "plist") {
let url = URL(fileURLWithPath: path)
let data = try! Data(contentsOf: url)
if let plist = try! PropertyListSerialization.propertyList(from: data, options: .mutableContainers, format: nil) as? [String: String] {
secret = plist
}
}
windowWidth = window?.bounds.size.width ?? UIScreen.main.bounds.size.width
windowHeight = window?.bounds.size.height ?? UIScreen.main.bounds.size.height
Self.observedSceneDelegate.insert(self)
_ = Self.observer // just for activating the lazy static property
}
static func readSecret() -> [String: String]? {
if let path = Bundle.main.path(forResource: "Secret", ofType: "plist") {
let url = URL(fileURLWithPath: path)
let data = try! Data(contentsOf: url)
if let plist = try! PropertyListSerialization.propertyList(from: data, options: .mutableContainers, format: nil) as? [String: String] {
return plist
}
}
return nil
}
deinit {
Task { @MainActor in
Self.observedSceneDelegate.remove(self)
}
}
private static var observedSceneDelegate: Set<AppDelegate> = []
private static let observer = Task {
while true {
try? await Task.sleep(for: .seconds(0.1))
for delegate in observedSceneDelegate {
let newWidth = delegate.window?.bounds.size.width ?? UIScreen.main.bounds.size.width
if delegate.windowWidth != newWidth {
delegate.windowWidth = newWidth
}
let newHeight = delegate.window?.bounds.size.height ?? UIScreen.main.bounds.size.height
if delegate.windowHeight != newHeight {
delegate.windowHeight = newHeight
}
}
}
}
}
#endif
public extension URL {
static let placeholder: URL = URL(string: "https://cdn.pixabay.com/photo/2023/08/28/20/32/flower-8220018_1280.jpg")!
}
extension AppInfo {
static var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
}
public protocol Endpoint: Sendable {
func path() -> String
func queryItems() -> [URLQueryItem]?
var jsonValue: Encodable? { get }
}
public extension Endpoint {
var jsonValue: Encodable? {
nil
}
}
extension Endpoint {
func makePaginationParam(sinceId: String?, maxId: String?, mindId: String?) -> [URLQueryItem]? {
if let sinceId {
return [.init(name: "since_id", value: sinceId)]
} else if let maxId {
return [.init(name: "max_id", value: maxId)]
} else if let mindId {
return [.init(name: "min_id", value: mindId)]
}
return nil
}
}
public enum Oauth: Endpoint {
case authorize(clientId: String)
case token(code: String, clientId: String, clientSecret: String)
public func path() -> String {
switch self {
case .authorize:
"oauth/authorize"
case .token:
"oauth/token"
}
}
public var jsonValue: Encodable? {
switch self {
case let .token(code, clientId, clientSecret):
TokenData(clientId: clientId, clientSecret: clientSecret, code: code)
default:
nil
}
}
public struct TokenData: Encodable {
public let grantType = "authorization_code"
public let clientId: String
public let clientSecret: String
public let redirectUri = AppInfo.scheme
public let code: String
public let scope = AppInfo.scopes
}
public func queryItems() -> [URLQueryItem]? {
switch self {
case let .authorize(clientId):
return [
.init(name: "response_type", value: "code"),
.init(name: "client_id", value: clientId),
.init(name: "redirect_uri", value: AppInfo.scheme),
.init(name: "scope", value: AppInfo.scopes),
]
default:
return nil
}
}
}
public enum Accounts: Endpoint {
case verifyCredentials
public func path() -> String {
switch self {
case .verifyCredentials:
"accounts/verify_credentials"
}
}
public func queryItems() -> [URLQueryItem]? {
nil
}
}
public struct InstanceApp: Codable, Identifiable {
public let id: String
public let name: String
public let website: URL?
public let redirectUri: String
public let clientId: String
public let clientSecret: String
public let vapidKey: String?
}
extension InstanceApp: Sendable {}
public struct ServerError: Decodable, Error {
public let error: String?
public var httpCode: Int?
}
public enum Apps: Endpoint {
case registerApp
public func path() -> String {
switch self {
case .registerApp:
"apps"
}
}
public func queryItems() -> [URLQueryItem]? {
switch self {
case .registerApp:
return [
.init(name: "client_name", value: AppInfo.clientName),
.init(name: "redirect_uris", value: AppInfo.scheme),
.init(name: "scopes", value: AppInfo.scopes),
.init(name: "website", value: AppInfo.website),
]
}
}
}
public struct LinkHandler {
public let rawLink: String
public var maxId: String? {
do {
let regex = try Regex("max_id=[0-9]+")
if let match = rawLink.firstMatch(of: regex) {
return match.output.first?.substring?.replacingOccurrences(of: "max_id=", with: "")
}
} catch {
return nil
}
return nil
}
}
extension LinkHandler: Sendable {}
public extension Array where Element: Hashable {
func uniqued() -> [Element] {
var seen = Set<Element>()
return filter { seen.insert($0).inserted }
}
}

View File

@ -9,5 +9,10 @@ struct ThreadedWidgetsBundle: WidgetBundle {
var body: some Widget {
FollowCountWidget()
FollowGoalWidget()
CreatePostWidget()
if #available(iOS 18.0, *) {
CreatePostControl()
}
}
}