diff --git a/Threaded.xcodeproj/project.pbxproj b/Threaded.xcodeproj/project.pbxproj index b1977b8..53542fb 100644 --- a/Threaded.xcodeproj/project.pbxproj +++ b/Threaded.xcodeproj/project.pbxproj @@ -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 = ""; }; B98BC74A2B46CF0400595441 /* ThreadedStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadedStyle.swift; sourceTree = ""; }; B98BC74C2B46CFCE00595441 /* UserPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPreferences.swift; sourceTree = ""; }; + B98DD8D72C6821F7009F40DD /* CreatePostControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePostControl.swift; sourceTree = ""; }; B98F47952B645DF40092000F /* EmojiSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiSelector.swift; sourceTree = ""; }; B98F47972B64670F0092000F /* ShopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopView.swift; sourceTree = ""; }; B98F47992B653CAE0092000F /* Compressor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Compressor.swift; sourceTree = ""; }; @@ -228,18 +296,11 @@ B999DE5D2B76F9D100509868 /* Message.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = ""; }; B999DE5F2B76FB3E00509868 /* ContactRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRow.swift; sourceTree = ""; }; B9A80DD92C66DE1000DE3D88 /* ReportStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportStatusView.swift; sourceTree = ""; }; + B9A80DDD2C67BFF800DE3D88 /* CreatePostWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePostWidget.swift; sourceTree = ""; }; + B9A80DE12C67C38E00DE3D88 /* URLNavigator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLNavigator.swift; sourceTree = ""; }; B9A8DAB92BB7364300A890CC /* PostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostsView.swift; sourceTree = ""; }; B9B469AF2B9A275F00AD5585 /* FollowGoalWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowGoalWidget.swift; sourceTree = ""; }; B9B469B12B9A6E8300AD5585 /* PrivacyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyView.swift; sourceTree = ""; }; - 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 = ""; }; - B9B469BB2B9A7E6800AD5585 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - B9B469BD2B9A7E6B00AD5585 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - B9B469C02B9A7E6B00AD5585 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - B9B469CC2B9A823600AD5585 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; - B9B469D22B9A838500AD5585 /* ThreadedWatch Watch App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "ThreadedWatch Watch App.entitlements"; sourceTree = ""; }; - B9B469D32B9A871C00AD5585 /* WatchConnectivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchConnectivity.swift; sourceTree = ""; }; - B9B469D62B9A8B0700AD5585 /* GivenAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GivenAccount.swift; sourceTree = ""; }; B9B469DA2B9B2EDB00AD5585 /* ComingSoonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComingSoonView.swift; sourceTree = ""; }; B9B63B202B442D1500BBC82D /* DynamicTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicTextEditor.swift; sourceTree = ""; }; B9B63B222B447B8000BBC82D /* PostCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCardView.swift; sourceTree = ""; }; @@ -257,10 +318,8 @@ B9C20D132B921C78004DC9B3 /* AppIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntent.swift; sourceTree = ""; }; B9C20D152B921C7B004DC9B3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; B9C20D172B921C7B004DC9B3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - B9C20D1E2B921E81004DC9B3 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; B9C20D582B923CDD004DC9B3 /* ThreadedWidgetsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ThreadedWidgetsExtension.entitlements; sourceTree = ""; }; B9C20D592B923D53004DC9B3 /* Threaded.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Threaded.entitlements; sourceTree = ""; }; - B9C20D602B949AD7004DC9B3 /* Redeclarations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Redeclarations.swift; sourceTree = ""; }; B9C7F46B2C387D3B009C36DC /* WarningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WarningView.swift; sourceTree = ""; }; B9CC45B72B40A2D6001E4FA5 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; B9CC45B92B40AA1E001E4FA5 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; @@ -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 = ""; }; - 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 = ""; - }; - B9B469BF2B9A7E6B00AD5585 /* Preview Content */ = { - isa = PBXGroup; - children = ( - B9B469C02B9A7E6B00AD5585 /* Preview Assets.xcassets */, - ); - path = "Preview Content"; - sourceTree = ""; - }; 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 = ""; @@ -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" */; diff --git a/Threaded.xcodeproj/xcshareddata/xcschemes/ThreadedWatch Watch App.xcscheme b/Threaded.xcodeproj/xcshareddata/xcschemes/ThreadedWatch Watch App.xcscheme index 46fecfa..a290bf6 100644 --- a/Threaded.xcodeproj/xcshareddata/xcschemes/ThreadedWatch Watch App.xcscheme +++ b/Threaded.xcodeproj/xcshareddata/xcschemes/ThreadedWatch Watch App.xcscheme @@ -85,6 +85,15 @@ ReferencedContainer = "container:Threaded.xcodeproj"> + + + + diff --git a/Threaded/Assets.xcassets/AppBackground.colorset/Contents.json b/Threaded/Assets.xcassets/AppBackground.colorset/Contents.json index fe808bf..b09d534 100644 --- a/Threaded/Assets.xcassets/AppBackground.colorset/Contents.json +++ b/Threaded/Assets.xcassets/AppBackground.colorset/Contents.json @@ -34,5 +34,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "localizable" : true } } diff --git a/Threaded/Components/Post/PostCardView.swift b/Threaded/Components/Post/PostCardView.swift index ee23eb3..a996a2c 100644 --- a/Threaded/Components/Post/PostCardView.swift +++ b/Threaded/Components/Post/PostCardView.swift @@ -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) } } } diff --git a/Threaded/Components/TabsView.swift b/Threaded/Components/TabsView.swift index aeed6ff..dcda01b 100644 --- a/Threaded/Components/TabsView.swift +++ b/Threaded/Components/TabsView.swift @@ -3,15 +3,19 @@ import SwiftUI struct TabsView: View { - @Binding var selectedTab: TabDestination - + @State var selectedTab: TabDestination = Navigator.shared.selectedTab + + var canTap: Binding = .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 + } } } diff --git a/Threaded/Data/Accounts/Account+Elms.swift b/Threaded/Data/Accounts/Account+Elms.swift index 5d8033c..0f45829 100644 --- a/Threaded/Data/Accounts/Account+Elms.swift +++ b/Threaded/Data/Accounts/Account+Elms.swift @@ -2,6 +2,7 @@ import Foundation +/// Mastodon visibility strings public enum Visibility: String, Codable, CaseIterable, Hashable, Equatable, Sendable { case pub = "public" case unlisted diff --git a/Threaded/Data/AppInfo.swift b/Threaded/Data/AppInfo.swift index 4637f90..c1c8f36 100644 --- a/Threaded/Data/AppInfo.swift +++ b/Threaded/Data/AppInfo.swift @@ -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" +} diff --git a/Threaded/Data/Client.swift b/Threaded/Data/Client.swift index 3b4cb02..3e65ae8 100644 --- a/Threaded/Data/Client.swift +++ b/Threaded/Data/Client.swift @@ -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 + } +} diff --git a/Threaded/Data/Navigator.swift b/Threaded/Data/Navigator.swift index 5d2fea4..8a136bc 100644 --- a/Threaded/Data/Navigator.swift +++ b/Threaded/Data/Navigator.swift @@ -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 { diff --git a/Threaded/Data/URLNavigator.swift b/Threaded/Data/URLNavigator.swift new file mode 100644 index 0000000..72354fa --- /dev/null +++ b/Threaded/Data/URLNavigator.swift @@ -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 + } +} diff --git a/Threaded/Localizable.xcstrings b/Threaded/Localizable.xcstrings index d9a2b47..4c51f7d 100644 --- a/Threaded/Localizable.xcstrings +++ b/Threaded/Localizable.xcstrings @@ -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 l’application 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" diff --git a/Threaded/ThreadedApp.swift b/Threaded/ThreadedApp.swift index 74b645a..92c3a8f 100644 --- a/Threaded/ThreadedApp.swift +++ b/Threaded/ThreadedApp.swift @@ -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" -} diff --git a/Threaded/Views/AccountView.swift b/Threaded/Views/AccountView.swift index 9146b4d..c24d30f 100644 --- a/Threaded/Views/AccountView.swift +++ b/Threaded/Views/AccountView.swift @@ -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 { diff --git a/Threaded/Views/ContentView.swift b/Threaded/Views/ContentView.swift index e2ac121..a6aafd4 100644 --- a/Threaded/Views/ContentView.swift +++ b/Threaded/Views/ContentView.swift @@ -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 } diff --git a/Threaded/Views/DiscoveryView.swift b/Threaded/Views/DiscoveryView.swift index 502a844..1645de0 100644 --- a/Threaded/Views/DiscoveryView.swift +++ b/Threaded/Views/DiscoveryView.swift @@ -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 diff --git a/Threaded/Views/NotificationsView.swift b/Threaded/Views/NotificationsView.swift index 39cb16c..f7d3525 100644 --- a/Threaded/Views/NotificationsView.swift +++ b/Threaded/Views/NotificationsView.swift @@ -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 diff --git a/Threaded/Views/PostingView.swift b/Threaded/Views/PostingView.swift index 73409d7..7eb0d3f 100644 --- a/Threaded/Views/PostingView.swift +++ b/Threaded/Views/PostingView.swift @@ -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 diff --git a/Threaded/Views/ProfileView.swift b/Threaded/Views/ProfileView.swift index 6109edc..1473eb8 100644 --- a/Threaded/Views/ProfileView.swift +++ b/Threaded/Views/ProfileView.swift @@ -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) } diff --git a/Threaded/Views/Settings/AboutView.swift b/Threaded/Views/Settings/AboutView.swift index 1c53040..b5af235 100644 --- a/Threaded/Views/Settings/AboutView.swift +++ b/Threaded/Views/Settings/AboutView.swift @@ -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") } diff --git a/Threaded/Views/Settings/IconView.swift b/Threaded/Views/Settings/IconView.swift index 4f080c5..eb5701c 100644 --- a/Threaded/Views/Settings/IconView.swift +++ b/Threaded/Views/Settings/IconView.swift @@ -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() -} diff --git a/Threaded/Views/Settings/SettingsView.swift b/Threaded/Views/Settings/SettingsView.swift index 93c3ca1..db1dd1e 100644 --- a/Threaded/Views/Settings/SettingsView.swift +++ b/Threaded/Views/Settings/SettingsView.swift @@ -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() } } diff --git a/Threaded/Views/TimelineView.swift b/Threaded/Views/TimelineView.swift index 32cfe3a..bb59f7d 100644 --- a/Threaded/Views/TimelineView.swift +++ b/Threaded/Views/TimelineView.swift @@ -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] diff --git a/ThreadedWatch/Assets.xcassets/AccentColor.colorset/Contents.json b/ThreadedWatch/Assets.xcassets/AccentColor.colorset/Contents.json deleted file mode 100644 index eb87897..0000000 --- a/ThreadedWatch/Assets.xcassets/AccentColor.colorset/Contents.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "colors" : [ - { - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ThreadedWatch/Assets.xcassets/AppIcon.appiconset/Contents.json b/ThreadedWatch/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 76fed1a..0000000 --- a/ThreadedWatch/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "images" : [ - { - "filename" : "ThreadedApp.png", - "idiom" : "universal", - "platform" : "watchos", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ThreadedWatch/Assets.xcassets/AppIcon.appiconset/ThreadedApp.png b/ThreadedWatch/Assets.xcassets/AppIcon.appiconset/ThreadedApp.png deleted file mode 100644 index 221a240..0000000 Binary files a/ThreadedWatch/Assets.xcassets/AppIcon.appiconset/ThreadedApp.png and /dev/null differ diff --git a/ThreadedWatch/Assets.xcassets/Contents.json b/ThreadedWatch/Assets.xcassets/Contents.json deleted file mode 100644 index 73c0059..0000000 --- a/ThreadedWatch/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ThreadedWatch/ContentView.swift b/ThreadedWatch/ContentView.swift deleted file mode 100644 index c133780..0000000 --- a/ThreadedWatch/ContentView.swift +++ /dev/null @@ -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()) - 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() -} diff --git a/ThreadedWatch/GivenAccount.swift b/ThreadedWatch/GivenAccount.swift deleted file mode 100644 index dad3254..0000000 --- a/ThreadedWatch/GivenAccount.swift +++ /dev/null @@ -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) - } -} diff --git a/ThreadedWatch/Localizable.xcstrings b/ThreadedWatch/Localizable.xcstrings deleted file mode 100644 index 23f6b1b..0000000 --- a/ThreadedWatch/Localizable.xcstrings +++ /dev/null @@ -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" -} \ No newline at end of file diff --git a/ThreadedWatch/Preview Content/Preview Assets.xcassets/Contents.json b/ThreadedWatch/Preview Content/Preview Assets.xcassets/Contents.json deleted file mode 100644 index 73c0059..0000000 --- a/ThreadedWatch/Preview Content/Preview Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ThreadedWatch/ThreadedWatch Watch App.entitlements b/ThreadedWatch/ThreadedWatch Watch App.entitlements deleted file mode 100644 index 74f4ed4..0000000 --- a/ThreadedWatch/ThreadedWatch Watch App.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - com.apple.security.application-groups - - group.lumaa.ThreadedApp - - - diff --git a/ThreadedWatch/ThreadedWatchApp.swift b/ThreadedWatch/ThreadedWatchApp.swift deleted file mode 100644 index b0a06d0..0000000 --- a/ThreadedWatch/ThreadedWatchApp.swift +++ /dev/null @@ -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) - } -} diff --git a/ThreadedWatch/WatchConnectivity.swift b/ThreadedWatch/WatchConnectivity.swift deleted file mode 100644 index b1e5bd0..0000000 --- a/ThreadedWatch/WatchConnectivity.swift +++ /dev/null @@ -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 -} diff --git a/ThreadedWidgets/AppIntent.swift b/ThreadedWidgets/AppIntent.swift index 65e8606..7bcbc99 100644 --- a/ThreadedWidgets/AppIntent.swift +++ b/ThreadedWidgets/AppIntent.swift @@ -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 { + 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) + } + } +} diff --git a/ThreadedWidgets/Assets.xcassets/AppBackground.colorset/Contents.json b/ThreadedWidgets/Assets.xcassets/AppBackground.colorset/Contents.json new file mode 100644 index 0000000..fe808bf --- /dev/null +++ b/ThreadedWidgets/Assets.xcassets/AppBackground.colorset/Contents.json @@ -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 + } +} diff --git a/ThreadedWidgets/Assets.xcassets/HeroIcon.imageset/Contents.json b/ThreadedWidgets/Assets.xcassets/HeroIcon.imageset/Contents.json new file mode 100644 index 0000000..d9c8eb6 --- /dev/null +++ b/ThreadedWidgets/Assets.xcassets/HeroIcon.imageset/Contents.json @@ -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 + } +} diff --git a/ThreadedWidgets/Assets.xcassets/HeroIcon.imageset/HeroIcon_black.png b/ThreadedWidgets/Assets.xcassets/HeroIcon.imageset/HeroIcon_black.png new file mode 100644 index 0000000..e9a8e46 Binary files /dev/null and b/ThreadedWidgets/Assets.xcassets/HeroIcon.imageset/HeroIcon_black.png differ diff --git a/ThreadedWidgets/Assets.xcassets/HeroIcon.imageset/HeroIcon_white.png b/ThreadedWidgets/Assets.xcassets/HeroIcon.imageset/HeroIcon_white.png new file mode 100644 index 0000000..82731ae Binary files /dev/null and b/ThreadedWidgets/Assets.xcassets/HeroIcon.imageset/HeroIcon_white.png differ diff --git a/ThreadedWidgets/CreatePostControl.swift b/ThreadedWidgets/CreatePostControl.swift new file mode 100644 index 0000000..f846564 --- /dev/null +++ b/ThreadedWidgets/CreatePostControl.swift @@ -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") + } +} diff --git a/ThreadedWidgets/CreatePostWidget.swift b/ThreadedWidgets/CreatePostWidget.swift new file mode 100644 index 0000000..66e075d --- /dev/null +++ b/ThreadedWidgets/CreatePostWidget.swift @@ -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) -> 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) +} diff --git a/ThreadedWidgets/Localizable.xcstrings b/ThreadedWidgets/Localizable.xcstrings deleted file mode 100644 index ac81bb9..0000000 --- a/ThreadedWidgets/Localizable.xcstrings +++ /dev/null @@ -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" -} \ No newline at end of file diff --git a/ThreadedWidgets/Redeclarations.swift b/ThreadedWidgets/Redeclarations.swift deleted file mode 100644 index 2b78f81..0000000 --- a/ThreadedWidgets/Redeclarations.swift +++ /dev/null @@ -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 = [] - 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() - return filter { seen.insert($0).inserted } - } -} diff --git a/ThreadedWidgets/ThreadedWidgetsBundle.swift b/ThreadedWidgets/ThreadedWidgetsBundle.swift index f9ce166..2bd8135 100644 --- a/ThreadedWidgets/ThreadedWidgetsBundle.swift +++ b/ThreadedWidgets/ThreadedWidgetsBundle.swift @@ -9,5 +9,10 @@ struct ThreadedWidgetsBundle: WidgetBundle { var body: some Widget { FollowCountWidget() FollowGoalWidget() + CreatePostWidget() + + if #available(iOS 18.0, *) { + CreatePostControl() + } } }