diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index ae5393fe9..ea21a7dff 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -67,12 +67,10 @@ 2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F20725CD491300561493 /* DisposeBagCollectable.swift */; }; 2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.swift */; }; 2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */ = {isa = PBXBuildFile; productRef = 2D42FF6025C8177C004A627A /* ActiveLabel */; }; - 2D42FF6B25C817D2004A627A /* MastodonStatusContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF6A25C817D2004A627A /* MastodonStatusContent.swift */; }; 2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF7D25C82218004A627A /* ActionToolBarContainer.swift */; }; 2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */; }; 2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF8E25C8228A004A627A /* UIButton.swift */; }; 2D45E5BF25C9549700A6D639 /* PublicTimelineViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D45E5BE25C9549700A6D639 /* PublicTimelineViewModel+State.swift */; }; - 2D46975E25C2A54100CF4AA9 /* NSLayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */; }; 2D4AD89C263165B500613EFC /* SuggestionAccountCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D4AD89B263165B500613EFC /* SuggestionAccountCollectionViewCell.swift */; }; 2D4AD8A226316CD200613EFC /* SelectedAccountSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D4AD8A126316CD200613EFC /* SelectedAccountSection.swift */; }; 2D4AD8A826316D3500613EFC /* SelectedAccountItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D4AD8A726316D3500613EFC /* SelectedAccountItem.swift */; }; @@ -141,6 +139,7 @@ 2DF75BB925D1474100694EC8 /* ManagedObjectObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF75BB825D1474100694EC8 /* ManagedObjectObserver.swift */; }; 2DF75BC725D1475D00694EC8 /* ManagedObjectContextObjectsDidChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF75BC625D1475D00694EC8 /* ManagedObjectContextObjectsDidChange.swift */; }; 2DFAD5372617010500F9EE7C /* SearchResultTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DFAD5362617010500F9EE7C /* SearchResultTableViewCell.swift */; }; + 4278334D6033AEEE0A1C5155 /* Pods_ShareActionExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A32B0CACBF35F4CC3CFAA043 /* Pods_ShareActionExtension.framework */; }; 5B24BBDA262DB14800A9381B /* ReportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B24BBD7262DB14800A9381B /* ReportViewModel.swift */; }; 5B24BBDB262DB14800A9381B /* ReportViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B24BBD8262DB14800A9381B /* ReportViewModel+Diffable.swift */; }; 5B24BBE2262DB19100A9381B /* APIService+Report.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B24BBE1262DB19100A9381B /* APIService+Report.swift */; }; @@ -187,7 +186,6 @@ DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */; }; DB03F7EB268976B5007B274C /* MastodonMeta in Frameworks */ = {isa = PBXBuildFile; productRef = DB03F7EA268976B5007B274C /* MastodonMeta */; }; DB03F7ED268976B5007B274C /* MetaTextView in Frameworks */ = {isa = PBXBuildFile; productRef = DB03F7EC268976B5007B274C /* MetaTextView */; }; - DB03F7F026899097007B274C /* ComposeStatusContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7EF26899097007B274C /* ComposeStatusContentTableViewCell.swift */; }; DB03F7F32689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7F22689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift */; }; DB03F7F52689B782007B274C /* ComposeTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7F42689B782007B274C /* ComposeTableView.swift */; }; DB040ED126538E3D00BEE9D8 /* Trie.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB040ED026538E3C00BEE9D8 /* Trie.swift */; }; @@ -196,7 +194,6 @@ DB0E2D2E26833FF700865C3C /* NukeFLAnimatedImagePlugin in Frameworks */ = {isa = PBXBuildFile; productRef = DB0E2D2D26833FF700865C3C /* NukeFLAnimatedImagePlugin */; }; DB0F8150264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */; }; DB118A8225E4B6E600FAB162 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */; }; - DB118A8C25E4BFB500FAB162 /* HighlightDimmableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */; }; DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */; }; DB1D842C26551A1C000346B3 /* StatusProvider+StatusTableViewKeyCommandNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D842B26551A1C000346B3 /* StatusProvider+StatusTableViewKeyCommandNavigateable.swift */; }; DB1D842E26552C4D000346B3 /* StatusTableViewControllerNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D842D26552C4D000346B3 /* StatusTableViewControllerNavigateable.swift */; }; @@ -206,8 +203,6 @@ DB1D84382657B275000346B3 /* SegmentedControlNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D84372657B275000346B3 /* SegmentedControlNavigateable.swift */; }; DB1E346825F518E20079D7DF /* CategoryPickerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1E346725F518E20079D7DF /* CategoryPickerSection.swift */; }; DB1E347825F519300079D7DF /* PickServerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1E347725F519300079D7DF /* PickServerItem.swift */; }; - DB1EE7AE267F3071000CC337 /* MastodonStatusContent+ParseResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1EE7AD267F3071000CC337 /* MastodonStatusContent+ParseResult.swift */; }; - DB1EE7B0267F3088000CC337 /* MastodonStatusContent+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1EE7AF267F3088000CC337 /* MastodonStatusContent+Appearance.swift */; }; DB1EE7B2267F9525000CC337 /* StatusProvider+StatusNodeDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1EE7B1267F9525000CC337 /* StatusProvider+StatusNodeDelegate.swift */; }; DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD43525F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift */; }; DB1FD44425F26CCC004CFCFC /* PickServerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD44325F26CCC004CFCFC /* PickServerSection.swift */; }; @@ -216,21 +211,28 @@ DB221B16260C395900AEFE46 /* CustomEmojiPickerInputViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB221B15260C395900AEFE46 /* CustomEmojiPickerInputViewModel.swift */; }; DB297B1B2679FAE200704C90 /* PlaceholderImageCacheService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB297B1A2679FAE200704C90 /* PlaceholderImageCacheService.swift */; }; DB2B3ABC25E37E15007045F9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB2B3ABE25E37E15007045F9 /* InfoPlist.strings */; }; - DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2B3AE825E38850007045F9 /* UIViewPreview.swift */; }; DB2F073525E8ECF000957B2D /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */; }; DB2FF510260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2FF50F260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift */; }; DB35FC1F2612F1D9006193C9 /* ProfileRelationshipActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB35FC1E2612F1D9006193C9 /* ProfileRelationshipActionButton.swift */; }; DB35FC252612FD7A006193C9 /* ProfileFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB35FC242612FD7A006193C9 /* ProfileFieldView.swift */; }; - DB35FC2F26130172006193C9 /* MastodonField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB35FC2E26130172006193C9 /* MastodonField.swift */; }; DB36679D268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB36679C268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift */; }; DB36679F268ABAF20027D07F /* ComposeStatusAttachmentSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB36679E268ABAF20027D07F /* ComposeStatusAttachmentSection.swift */; }; DB3667A1268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667A0268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift */; }; DB3667A4268AE2370027D07F /* ComposeStatusPollTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667A3268AE2370027D07F /* ComposeStatusPollTableViewCell.swift */; }; DB3667A6268AE2620027D07F /* ComposeStatusPollSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667A5268AE2620027D07F /* ComposeStatusPollSection.swift */; }; DB3667A8268AE2900027D07F /* ComposeStatusPollItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667A7268AE2900027D07F /* ComposeStatusPollItem.swift */; }; - DB3667CA268B14A80027D07F /* ReplicaStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667C9268B14A80027D07F /* ReplicaStatusView.swift */; }; DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB3D0FF225BAA61700EAA174 /* AlamofireImage */; }; DB3D100D25BAA75E00EAA174 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB3D100F25BAA75E00EAA174 /* Localizable.strings */; }; + DB41ED7A26A54D4400F58330 /* MastodonStatusContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D726A54BCB00398BB9 /* MastodonStatusContent.swift */; }; + DB41ED7B26A54D4D00F58330 /* MastodonStatusContent+ParseResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D926A54BCB00398BB9 /* MastodonStatusContent+ParseResult.swift */; }; + DB41ED7C26A54D5500F58330 /* MastodonStatusContent+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24DB26A54BCB00398BB9 /* MastodonStatusContent+Appearance.swift */; }; + DB41ED7E26A54D6D00F58330 /* Fuzi in Frameworks */ = {isa = PBXBuildFile; productRef = DB41ED7D26A54D6D00F58330 /* Fuzi */; }; + DB41ED8026A54D7C00F58330 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB41ED7F26A54D7C00F58330 /* AlamofireImage */; }; + DB41ED8226A54D8A00F58330 /* MastodonMeta in Frameworks */ = {isa = PBXBuildFile; productRef = DB41ED8126A54D8A00F58330 /* MastodonMeta */; }; + DB41ED8426A54D8A00F58330 /* MetaTextView in Frameworks */ = {isa = PBXBuildFile; productRef = DB41ED8326A54D8A00F58330 /* MetaTextView */; }; + DB41ED8926A54F4000F58330 /* ComposeStatusAttachmentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B351D2601FAEE00DC1E11 /* ComposeStatusAttachmentCollectionViewCell.swift */; }; + DB41ED8A26A54F4C00F58330 /* AttachmentContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */; }; + DB41ED8B26A54F5800F58330 /* AttachmentContainerView+EmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */; }; DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD525BAA00100D1B89D /* AppDelegate.swift */; }; DB427DD825BAA00100D1B89D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD725BAA00100D1B89D /* SceneDelegate.swift */; }; DB427DDD25BAA00100D1B89D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DB427DDB25BAA00100D1B89D /* Main.storyboard */; }; @@ -250,7 +252,6 @@ DB4481B925EE289600BEFB67 /* UITableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4481B825EE289600BEFB67 /* UITableView.swift */; }; DB4481C625EE2ADA00BEFB67 /* PollSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4481C525EE2ADA00BEFB67 /* PollSection.swift */; }; DB4481CC25EE2AFE00BEFB67 /* PollItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4481CB25EE2AFE00BEFB67 /* PollItem.swift */; }; - DB4563BD25E11A24004DA0B9 /* KeyboardResponderService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4563BC25E11A24004DA0B9 /* KeyboardResponderService.swift */; }; DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FAB525CA5485005A8AC7 /* UIAlertController.swift */; }; DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FAD625CA6C76005A8AC7 /* UIBarButtonItem.swift */; }; DB45FADD25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FADC25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift */; }; @@ -281,12 +282,9 @@ DB4FFC2B269EC39600D62E92 /* SearchToSearchDetailViewControllerAnimatedTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4FFC29269EC39600D62E92 /* SearchToSearchDetailViewControllerAnimatedTransitioning.swift */; }; DB4FFC2C269EC39600D62E92 /* SearchTransitionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4FFC2A269EC39600D62E92 /* SearchTransitionController.swift */; }; DB5086A525CC0B7000C2C187 /* AvatarBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086A425CC0B7000C2C187 /* AvatarBarButtonItem.swift */; }; - DB5086AB25CC0BBB00C2C187 /* AvatarConfigurableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */; }; - DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */; }; DB51D172262832380062B7A1 /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB51D170262832380062B7A1 /* BlurHashDecode.swift */; }; DB51D173262832380062B7A1 /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB51D171262832380062B7A1 /* BlurHashEncode.swift */; }; DB52D33A26839DD800D43133 /* ImageTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB52D33926839DD800D43133 /* ImageTask.swift */; }; - DB55D33025FB630A0002F825 /* TwitterTextEditor+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB55D32F25FB630A0002F825 /* TwitterTextEditor+String.swift */; }; DB564BD0269F2F83001E39A7 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DB564BCE269F2F83001E39A7 /* Localizable.stringsdict */; }; DB564BD3269F3B35001E39A7 /* StatusFilterService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB564BD2269F3B35001E39A7 /* StatusFilterService.swift */; }; DB59F0FE25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F0FD25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift */; }; @@ -307,7 +305,7 @@ DB6180F826391D660018D199 /* MediaPreviewingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180F726391D660018D199 /* MediaPreviewingViewController.swift */; }; DB6180FA26391F2E0018D199 /* MediaPreviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180F926391F2E0018D199 /* MediaPreviewViewModel.swift */; }; DB63BE7F268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB63BE7E268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift */; }; - DB66728C25F9F8DC00D60309 /* ComposeViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66728B25F9F8DC00D60309 /* ComposeViewModel+Diffable.swift */; }; + DB66728C25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */; }; DB66729625F9F91600D60309 /* ComposeStatusSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729525F9F91600D60309 /* ComposeStatusSection.swift */; }; DB66729C25F9F91F00D60309 /* ComposeStatusItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */; }; DB68045B2636DC6A00430867 /* MastodonNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68045A2636DC6A00430867 /* MastodonNotification.swift */; }; @@ -330,7 +328,6 @@ DB6B35182601FA3400DC1E11 /* MastodonAttachmentService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B35172601FA3400DC1E11 /* MastodonAttachmentService.swift */; }; DB6B351E2601FAEE00DC1E11 /* ComposeStatusAttachmentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B351D2601FAEE00DC1E11 /* ComposeStatusAttachmentCollectionViewCell.swift */; }; DB6C8C0F25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6C8C0E25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift */; }; - DB6D1B24263684C600ACB481 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B23263684C600ACB481 /* UserDefaults.swift */; }; DB6D1B3D2636857500ACB481 /* AppearancePreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B3C2636857500ACB481 /* AppearancePreference.swift */; }; DB6D1B44263691CF00ACB481 /* Mastodon+API+Subscriptions+Policy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B43263691CF00ACB481 /* Mastodon+API+Subscriptions+Policy.swift */; }; DB6D9F3526351B7A008423CD /* NotificationService+Decrypt.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F3426351B7A008423CD /* NotificationService+Decrypt.swift */; }; @@ -344,7 +341,6 @@ DB6D9F7D26358ED4008423CD /* SettingsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F7C26358ED4008423CD /* SettingsSection.swift */; }; DB6D9F8426358EEC008423CD /* SettingsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F8326358EEC008423CD /* SettingsItem.swift */; }; DB6D9F9726367249008423CD /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F9626367249008423CD /* SettingsViewController.swift */; }; - DB6F5E2F264E5518009108F4 /* MastodonRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6F5E2E264E5518009108F4 /* MastodonRegex.swift */; }; DB6F5E32264E7410009108F4 /* TwitterTextEditor in Frameworks */ = {isa = PBXBuildFile; productRef = DB6F5E31264E7410009108F4 /* TwitterTextEditor */; }; DB6F5E33264E7410009108F4 /* TwitterTextEditor in Embed Frameworks */ = {isa = PBXBuildFile; productRef = DB6F5E31264E7410009108F4 /* TwitterTextEditor */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; DB6F5E35264E78E7009108F4 /* AutoCompleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6F5E34264E78E7009108F4 /* AutoCompleteViewController.swift */; }; @@ -407,7 +403,6 @@ DB98338725C945ED00AD9700 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98338525C945ED00AD9700 /* Strings.swift */; }; DB98338825C945ED00AD9700 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98338625C945ED00AD9700 /* Assets.swift */; }; DB98339C25C96DE600AD9700 /* APIService+Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98339B25C96DE600AD9700 /* APIService+Account.swift */; }; - DB9A485C2603010E008B817C /* PHPickerResultLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A485B2603010E008B817C /* PHPickerResultLoader.swift */; }; DB9A486C26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */; }; DB9A487E2603456B008B817C /* UITextView+Placeholder in Frameworks */ = {isa = PBXBuildFile; productRef = DB9A487D2603456B008B817C /* UITextView+Placeholder */; }; DB9A488A26034D40008B817C /* ComposeViewModel+PublishState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A488926034D40008B817C /* ComposeViewModel+PublishState.swift */; }; @@ -454,7 +449,6 @@ DBAE3F8E2616E0B1004B8251 /* APIService+Block.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F8D2616E0B1004B8251 /* APIService+Block.swift */; }; DBAE3F942616E28B004B8251 /* APIService+Follow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F932616E28B004B8251 /* APIService+Follow.swift */; }; DBAE3F9E2616E308004B8251 /* APIService+Mute.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */; }; - DBAE3FA92617106E004B8251 /* MastodonMetricFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FA82617106E004B8251 /* MastodonMetricFormatter.swift */; }; DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */; }; DBAEDE5C267A058D00D25FF5 /* BlurhashImageCacheService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAEDE5B267A058D00D25FF5 /* BlurhashImageCacheService.swift */; }; DBAEDE5F267A0B1500D25FF5 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = DBAEDE5E267A0B1500D25FF5 /* Nuke */; }; @@ -473,6 +467,36 @@ DBB5256E2612D5A1002F1F29 /* ProfileStatusDashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB5256D2612D5A1002F1F29 /* ProfileStatusDashboardView.swift */; }; DBB525852612D6DD002F1F29 /* ProfileStatusDashboardMeterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB525842612D6DD002F1F29 /* ProfileStatusDashboardMeterView.swift */; }; DBB9759C262462E1004620BD /* ThreadMetaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB9759B262462E1004620BD /* ThreadMetaView.swift */; }; + DBBC24A826A52F9000398BB9 /* ComposeToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24A726A52F9000398BB9 /* ComposeToolbarView.swift */; }; + DBBC24AA26A5301B00398BB9 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24A926A5301B00398BB9 /* MastodonSDK */; }; + DBBC24AC26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24AB26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift */; }; + DBBC24AE26A53DC100398BB9 /* ReplicaStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24AD26A53DC100398BB9 /* ReplicaStatusView.swift */; }; + DBBC24B026A53DF900398BB9 /* ReplicaStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24AD26A53DC100398BB9 /* ReplicaStatusView.swift */; }; + DBBC24B226A53ED200398BB9 /* ActiveLabel in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24B126A53ED200398BB9 /* ActiveLabel */; }; + DBBC24B326A53EE700398BB9 /* ActiveLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF123A625C3B0210020F248 /* ActiveLabel.swift */; }; + DBBC24B526A540AE00398BB9 /* AvatarConfigurableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24B426A540AE00398BB9 /* AvatarConfigurableView.swift */; }; + DBBC24B626A5419700398BB9 /* ComposeStatusContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24AB26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift */; }; + DBBC24B826A5421800398BB9 /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24B726A5421800398BB9 /* CommonOSLog */; }; + DBBC24B926A5426000398BB9 /* StatusContentWarningEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC7A671260C897100E57475 /* StatusContentWarningEditorView.swift */; }; + DBBC24BC26A542F500398BB9 /* ThemeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BB26A542F500398BB9 /* ThemeService.swift */; }; + DBBC24C026A5443100398BB9 /* MastodonTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BE26A5443100398BB9 /* MastodonTheme.swift */; }; + DBBC24C126A5443100398BB9 /* SystemTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BF26A5443100398BB9 /* SystemTheme.swift */; }; + DBBC24C426A544B900398BB9 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24C326A544B900398BB9 /* Theme.swift */; }; + DBBC24C626A5456000398BB9 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24C326A544B900398BB9 /* Theme.swift */; }; + DBBC24C726A5456400398BB9 /* SystemTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BF26A5443100398BB9 /* SystemTheme.swift */; }; + DBBC24C826A5456400398BB9 /* ThemeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BB26A542F500398BB9 /* ThemeService.swift */; }; + DBBC24C926A5456400398BB9 /* MastodonTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BE26A5443100398BB9 /* MastodonTheme.swift */; }; + DBBC24CB26A546C000398BB9 /* ThemePreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD376AB2692ECDB007FEC24 /* ThemePreference.swift */; }; + DBBC24CD26A5471E00398BB9 /* MastodonExtension in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24CC26A5471E00398BB9 /* MastodonExtension */; }; + DBBC24CF26A547AE00398BB9 /* ThemeService+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24CE26A547AE00398BB9 /* ThemeService+Appearance.swift */; }; + DBBC24D126A5484F00398BB9 /* UITextView+Placeholder in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24D026A5484F00398BB9 /* UITextView+Placeholder */; }; + DBBC24D226A5488600398BB9 /* AvatarConfigurableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24B426A540AE00398BB9 /* AvatarConfigurableView.swift */; }; + DBBC24DC26A54BCB00398BB9 /* MastodonRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */; }; + DBBC24DD26A54BCB00398BB9 /* MastodonStatusContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D726A54BCB00398BB9 /* MastodonStatusContent.swift */; }; + DBBC24DE26A54BCB00398BB9 /* MastodonMetricFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */; }; + DBBC24DF26A54BCB00398BB9 /* MastodonStatusContent+ParseResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D926A54BCB00398BB9 /* MastodonStatusContent+ParseResult.swift */; }; + DBBC24E026A54BCB00398BB9 /* MastodonField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24DA26A54BCB00398BB9 /* MastodonField.swift */; }; + DBBC24E126A54BCB00398BB9 /* MastodonStatusContent+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24DB26A54BCB00398BB9 /* MastodonStatusContent+Appearance.swift */; }; DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */; }; DBBF1DBF2652401B00E5B703 /* AutoCompleteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DBE2652401B00E5B703 /* AutoCompleteViewModel.swift */; }; DBBF1DC226524D2900E5B703 /* AutoCompleteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DC126524D2900E5B703 /* AutoCompleteTableViewCell.swift */; }; @@ -514,11 +538,7 @@ DBCC3B9526157E6E0045B23D /* APIService+Relationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCC3B9426157E6E0045B23D /* APIService+Relationship.swift */; }; DBCC3B9B261584A00045B23D /* PrivateNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCC3B9A2615849F0045B23D /* PrivateNote.swift */; }; DBCCC71E25F73297007E1AB6 /* APIService+Reblog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCCC71D25F73297007E1AB6 /* APIService+Reblog.swift */; }; - DBD376A72692EA00007FEC24 /* ThemeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD376A62692EA00007FEC24 /* ThemeService.swift */; }; - DBD376AA2692EA4F007FEC24 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD376A92692EA4F007FEC24 /* Theme.swift */; }; DBD376AC2692ECDB007FEC24 /* ThemePreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD376AB2692ECDB007FEC24 /* ThemePreference.swift */; }; - DBD376AE2692EE0A007FEC24 /* MastodonTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD376AD2692EE0A007FEC24 /* MastodonTheme.swift */; }; - DBD376B02692F20F007FEC24 /* SystemTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD376AF2692F20F007FEC24 /* SystemTheme.swift */; }; DBD376B2269302A4007FEC24 /* UITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD376B1269302A4007FEC24 /* UITableViewCell.swift */; }; DBD9149025DF6D8D00903DFD /* APIService+Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD9148F25DF6D8D00903DFD /* APIService+Onboarding.swift */; }; DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE0821425CD382600FD6BBD /* MastodonRegisterViewController.swift */; }; @@ -531,7 +551,6 @@ DBE3CE07261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE06261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift */; }; DBE3CE0D261D767100430CC6 /* FavoriteViewController+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE0C261D767100430CC6 /* FavoriteViewController+Provider.swift */; }; DBE3CE13261D7D4200430CC6 /* StatusTableViewControllerAspect.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE12261D7D4200430CC6 /* StatusTableViewControllerAspect.swift */; }; - DBE54ABF2636C889004E7C0B /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B23263684C600ACB481 /* UserDefaults.swift */; }; DBE54AC62636C89F004E7C0B /* NotificationPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */; }; DBE54ACC2636C8FD004E7C0B /* NotificationPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */; }; DBF1D24E269DAF5D00C1C08A /* SearchDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF1D24D269DAF5D00C1C08A /* SearchDetailViewController.swift */; }; @@ -543,6 +562,13 @@ DBF96326262EC0A6001D8D25 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DBF96325262EC0A6001D8D25 /* AuthenticationServices.framework */; }; DBF9814A265E24F500E4BA07 /* ProfileFieldCollectionViewHeaderFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF98149265E24F500E4BA07 /* ProfileFieldCollectionViewHeaderFooterView.swift */; }; DBF9814C265E339500E4BA07 /* ProfileFieldAddEntryCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF9814B265E339500E4BA07 /* ProfileFieldAddEntryCollectionViewCell.swift */; }; + DBFEF05B26A57715006D7ED1 /* ComposeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF05726A576EE006D7ED1 /* ComposeViewModel.swift */; }; + DBFEF05C26A57715006D7ED1 /* StatusEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF05526A576EE006D7ED1 /* StatusEditorView.swift */; }; + DBFEF05D26A57715006D7ED1 /* ContentWarningEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF05826A576EE006D7ED1 /* ContentWarningEditorView.swift */; }; + DBFEF05E26A57715006D7ED1 /* ComposeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF05626A576EE006D7ED1 /* ComposeView.swift */; }; + DBFEF05F26A57715006D7ED1 /* StatusAuthorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF05926A576EE006D7ED1 /* StatusAuthorView.swift */; }; + DBFEF06026A57715006D7ED1 /* StatusAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF05A26A576EE006D7ED1 /* StatusAttachmentView.swift */; }; + DBFEF06326A577F2006D7ED1 /* StatusAttachmentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF06226A577F2006D7ED1 /* StatusAttachmentViewModel.swift */; }; EE93E8E8F9E0C39EAAEBD92F /* Pods_AppShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F4A2A2D7000E477CA459ADA9 /* Pods_AppShared.framework */; }; /* End PBXBuildFile section */ @@ -728,12 +754,10 @@ 2D38F1FD25CD481700561493 /* StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusProvider.swift; sourceTree = ""; }; 2D38F20725CD491300561493 /* DisposeBagCollectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisposeBagCollectable.swift; sourceTree = ""; }; 2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITapGestureRecognizer.swift; sourceTree = ""; }; - 2D42FF6A25C817D2004A627A /* MastodonStatusContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonStatusContent.swift; sourceTree = ""; }; 2D42FF7D25C82218004A627A /* ActionToolBarContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionToolBarContainer.swift; sourceTree = ""; }; 2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HitTestExpandedButton.swift; sourceTree = ""; }; 2D42FF8E25C8228A004A627A /* UIButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIButton.swift; sourceTree = ""; }; 2D45E5BE25C9549700A6D639 /* PublicTimelineViewModel+State.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewModel+State.swift"; sourceTree = ""; }; - 2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSLayoutConstraint.swift; sourceTree = ""; }; 2D4AD89B263165B500613EFC /* SuggestionAccountCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionAccountCollectionViewCell.swift; sourceTree = ""; }; 2D4AD8A126316CD200613EFC /* SelectedAccountSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedAccountSection.swift; sourceTree = ""; }; 2D4AD8A726316D3500613EFC /* SelectedAccountItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedAccountItem.swift; sourceTree = ""; }; @@ -826,6 +850,7 @@ 5BB04FE8262EFC300043BFF6 /* ReportedStatusTableviewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportedStatusTableviewCell.swift; sourceTree = ""; }; 5BB04FEE262F0DCB0043BFF6 /* ReportViewModel+Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReportViewModel+Data.swift"; sourceTree = ""; }; 5BB04FF4262F0E6D0043BFF6 /* ReportSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportSection.swift; sourceTree = ""; }; + 5CE45680252519F42FEA2D13 /* Pods-ShareActionExtension.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareActionExtension.asdk - release.xcconfig"; path = "Target Support Files/Pods-ShareActionExtension/Pods-ShareActionExtension.asdk - release.xcconfig"; sourceTree = ""; }; 5D03938F2612D259007FE196 /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = ""; }; 5D0393952612D266007FE196 /* WebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewModel.swift; sourceTree = ""; }; 5DA732CB2629CEF500A92342 /* UIView+Remove.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Remove.swift"; sourceTree = ""; }; @@ -840,16 +865,20 @@ 5DF1057E25F88A4100D6C0D4 /* TouchBlockingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchBlockingView.swift; sourceTree = ""; }; 5DF1058425F88AE500D6C0D4 /* NeedsDependency+AVPlayerViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NeedsDependency+AVPlayerViewControllerDelegate.swift"; sourceTree = ""; }; 5DFC35DE262068D20045711D /* SearchViewController+Follow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchViewController+Follow.swift"; sourceTree = ""; }; + 6130CBE4B26E3C976ACC1688 /* Pods-ShareActionExtension.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareActionExtension.asdk - debug.xcconfig"; path = "Target Support Files/Pods-ShareActionExtension/Pods-ShareActionExtension.asdk - debug.xcconfig"; sourceTree = ""; }; 75E3471C898DDD9631729B6E /* Pods-Mastodon.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.release.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.release.xcconfig"; sourceTree = ""; }; + 77EE917BC055E6621C0452B6 /* Pods-ShareActionExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareActionExtension.debug.xcconfig"; path = "Target Support Files/Pods-ShareActionExtension/Pods-ShareActionExtension.debug.xcconfig"; sourceTree = ""; }; 7CEFFAE9AF9284B13C0A758D /* Pods-MastodonTests.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.asdk - debug.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.asdk - debug.xcconfig"; sourceTree = ""; }; 819CEC9DCAD8E8E7BD85A7BB /* Pods-Mastodon.asdk.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.asdk.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.asdk.xcconfig"; sourceTree = ""; }; 8850E70A1D5FF51432E43653 /* Pods-Mastodon-MastodonUITests.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.asdk - release.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.asdk - release.xcconfig"; sourceTree = ""; }; 8ED8C4B1F1BA2DCFF2926BB1 /* Pods-Mastodon-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-NotificationService/Pods-Mastodon-NotificationService.debug.xcconfig"; sourceTree = ""; }; 9553C689FFA9EBC880CAB78D /* Pods-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.debug.xcconfig"; sourceTree = ""; }; + 95AD0663479892A2109EEFD0 /* Pods-ShareActionExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareActionExtension.release.xcconfig"; path = "Target Support Files/Pods-ShareActionExtension/Pods-ShareActionExtension.release.xcconfig"; sourceTree = ""; }; 9776D7C4B79101CF70181127 /* Pods-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.release.xcconfig"; sourceTree = ""; }; 9780A4C98FFC65B32B50D1C0 /* Pods-MastodonTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.release.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.release.xcconfig"; sourceTree = ""; }; 9A0982D8F349244EB558CDFD /* Pods-AppShared.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppShared.debug.xcconfig"; path = "Target Support Files/Pods-AppShared/Pods-AppShared.debug.xcconfig"; sourceTree = ""; }; 9CFF58FD900AC059428700E7 /* Pods-NotificationService.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.asdk - release.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.asdk - release.xcconfig"; sourceTree = ""; }; + A32B0CACBF35F4CC3CFAA043 /* Pods_ShareActionExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ShareActionExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Mastodon.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A67FD038ECDA0E411AF8DB4D /* Pods-Mastodon-MastodonUITests.asdk.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.asdk.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.asdk.xcconfig"; sourceTree = ""; }; A9B1FB898DFD6063B044298C /* Pods-AppShared.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppShared.asdk - debug.xcconfig"; path = "Target Support Files/Pods-AppShared/Pods-AppShared.asdk - debug.xcconfig"; sourceTree = ""; }; @@ -864,7 +893,6 @@ DB029E94266A20430062874E /* MastodonAuthenticationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonAuthenticationController.swift; sourceTree = ""; }; DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadReplyLoaderTableViewCell.swift; sourceTree = ""; }; DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveUserInterfaceStyleBarButtonItem.swift; sourceTree = ""; }; - DB03F7EF26899097007B274C /* ComposeStatusContentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusContentTableViewCell.swift; sourceTree = ""; }; DB03F7F22689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeRepliedToStatusContentTableViewCell.swift; sourceTree = ""; }; DB03F7F42689B782007B274C /* ComposeTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeTableView.swift; sourceTree = ""; }; DB040ED026538E3C00BEE9D8 /* Trie.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Trie.swift; sourceTree = ""; }; @@ -874,7 +902,6 @@ DB0F814E264CFFD300F2A12B /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/InfoPlist.strings; sourceTree = ""; }; DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerLoaderTableViewCell.swift; sourceTree = ""; }; DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDimmableButton.swift; sourceTree = ""; }; DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollTableView.swift; sourceTree = ""; }; DB1D842B26551A1C000346B3 /* StatusProvider+StatusTableViewKeyCommandNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusProvider+StatusTableViewKeyCommandNavigateable.swift"; sourceTree = ""; }; DB1D842D26552C4D000346B3 /* StatusTableViewControllerNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewControllerNavigateable.swift; sourceTree = ""; }; @@ -884,8 +911,6 @@ DB1D84372657B275000346B3 /* SegmentedControlNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedControlNavigateable.swift; sourceTree = ""; }; DB1E346725F518E20079D7DF /* CategoryPickerSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryPickerSection.swift; sourceTree = ""; }; DB1E347725F519300079D7DF /* PickServerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PickServerItem.swift; sourceTree = ""; }; - DB1EE7AD267F3071000CC337 /* MastodonStatusContent+ParseResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonStatusContent+ParseResult.swift"; sourceTree = ""; }; - DB1EE7AF267F3088000CC337 /* MastodonStatusContent+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonStatusContent+Appearance.swift"; sourceTree = ""; }; DB1EE7B1267F9525000CC337 /* StatusProvider+StatusNodeDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusProvider+StatusNodeDelegate.swift"; sourceTree = ""; }; DB1FD43525F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonPickServerViewModel+LoadIndexedServerState.swift"; sourceTree = ""; }; DB1FD44325F26CCC004CFCFC /* PickServerSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerSection.swift; sourceTree = ""; }; @@ -895,19 +920,16 @@ DB221B15260C395900AEFE46 /* CustomEmojiPickerInputViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiPickerInputViewModel.swift; sourceTree = ""; }; DB297B1A2679FAE200704C90 /* PlaceholderImageCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderImageCacheService.swift; sourceTree = ""; }; DB2B3ABD25E37E15007045F9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; - DB2B3AE825E38850007045F9 /* UIViewPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewPreview.swift; sourceTree = ""; }; DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationViewModel.swift; sourceTree = ""; }; DB2FF50F260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollExpiresOptionCollectionViewCell.swift; sourceTree = ""; }; DB35FC1E2612F1D9006193C9 /* ProfileRelationshipActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileRelationshipActionButton.swift; sourceTree = ""; }; DB35FC242612FD7A006193C9 /* ProfileFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldView.swift; sourceTree = ""; }; - DB35FC2E26130172006193C9 /* MastodonField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonField.swift; sourceTree = ""; }; DB36679C268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusAttachmentTableViewCell.swift; sourceTree = ""; }; DB36679E268ABAF20027D07F /* ComposeStatusAttachmentSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusAttachmentSection.swift; sourceTree = ""; }; DB3667A0268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusAttachmentItem.swift; sourceTree = ""; }; DB3667A3268AE2370027D07F /* ComposeStatusPollTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollTableViewCell.swift; sourceTree = ""; }; DB3667A5268AE2620027D07F /* ComposeStatusPollSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollSection.swift; sourceTree = ""; }; DB3667A7268AE2900027D07F /* ComposeStatusPollItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollItem.swift; sourceTree = ""; }; - DB3667C9268B14A80027D07F /* ReplicaStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplicaStatusView.swift; sourceTree = ""; }; DB3D0FED25BAA42200EAA174 /* MastodonSDK */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MastodonSDK; sourceTree = ""; }; DB3D100E25BAA75E00EAA174 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; DB427DD225BAA00100D1B89D /* Mastodon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Mastodon.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -935,7 +957,6 @@ DB4481B825EE289600BEFB67 /* UITableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITableView.swift; sourceTree = ""; }; DB4481C525EE2ADA00BEFB67 /* PollSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollSection.swift; sourceTree = ""; }; DB4481CB25EE2AFE00BEFB67 /* PollItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollItem.swift; sourceTree = ""; }; - DB4563BC25E11A24004DA0B9 /* KeyboardResponderService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardResponderService.swift; sourceTree = ""; }; DB45FAB525CA5485005A8AC7 /* UIAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertController.swift; sourceTree = ""; }; DB45FAD625CA6C76005A8AC7 /* UIBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIBarButtonItem.swift; sourceTree = ""; }; DB45FADC25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+CoreData+MastodonUser.swift"; sourceTree = ""; }; @@ -966,12 +987,9 @@ DB4FFC29269EC39600D62E92 /* SearchToSearchDetailViewControllerAnimatedTransitioning.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchToSearchDetailViewControllerAnimatedTransitioning.swift; sourceTree = ""; }; DB4FFC2A269EC39600D62E92 /* SearchTransitionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchTransitionController.swift; sourceTree = ""; }; DB5086A425CC0B7000C2C187 /* AvatarBarButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarBarButtonItem.swift; sourceTree = ""; }; - DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarConfigurableView.swift; sourceTree = ""; }; - DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplashPreference.swift; sourceTree = ""; }; DB51D170262832380062B7A1 /* BlurHashDecode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = ""; }; DB51D171262832380062B7A1 /* BlurHashEncode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurHashEncode.swift; sourceTree = ""; }; DB52D33926839DD800D43133 /* ImageTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTask.swift; sourceTree = ""; }; - DB55D32F25FB630A0002F825 /* TwitterTextEditor+String.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TwitterTextEditor+String.swift"; sourceTree = ""; }; DB564BCF269F2F83001E39A7 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ar; path = ar.lproj/Localizable.stringsdict; sourceTree = ""; }; DB564BD1269F2F8A001E39A7 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; DB564BD2269F3B35001E39A7 /* StatusFilterService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusFilterService.swift; sourceTree = ""; }; @@ -993,7 +1011,7 @@ DB6180F726391D660018D199 /* MediaPreviewingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewingViewController.swift; sourceTree = ""; }; DB6180F926391F2E0018D199 /* MediaPreviewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewViewModel.swift; sourceTree = ""; }; DB63BE7E268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationViewController+StatusProvider.swift"; sourceTree = ""; }; - DB66728B25F9F8DC00D60309 /* ComposeViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+Diffable.swift"; sourceTree = ""; }; + DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+DataSource.swift"; sourceTree = ""; }; DB66729525F9F91600D60309 /* ComposeStatusSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusSection.swift; sourceTree = ""; }; DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusItem.swift; sourceTree = ""; }; DB68045A2636DC6A00430867 /* MastodonNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonNotification.swift; sourceTree = ""; }; @@ -1011,7 +1029,6 @@ DB6B35172601FA3400DC1E11 /* MastodonAttachmentService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonAttachmentService.swift; sourceTree = ""; }; DB6B351D2601FAEE00DC1E11 /* ComposeStatusAttachmentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusAttachmentCollectionViewCell.swift; sourceTree = ""; }; DB6C8C0E25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+Error.swift"; sourceTree = ""; }; - DB6D1B23263684C600ACB481 /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = ""; }; DB6D1B3C2636857500ACB481 /* AppearancePreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearancePreference.swift; sourceTree = ""; }; DB6D1B43263691CF00ACB481 /* Mastodon+API+Subscriptions+Policy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+API+Subscriptions+Policy.swift"; sourceTree = ""; }; DB6D9F3426351B7A008423CD /* NotificationService+Decrypt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationService+Decrypt.swift"; sourceTree = ""; }; @@ -1024,7 +1041,6 @@ DB6D9F7C26358ED4008423CD /* SettingsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSection.swift; sourceTree = ""; }; DB6D9F8326358EEC008423CD /* SettingsItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsItem.swift; sourceTree = ""; }; DB6D9F9626367249008423CD /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; - DB6F5E2E264E5518009108F4 /* MastodonRegex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegex.swift; sourceTree = ""; }; DB6F5E34264E78E7009108F4 /* AutoCompleteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteViewController.swift; sourceTree = ""; }; DB6F5E37264E994A009108F4 /* AutoCompleteTopChevronView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteTopChevronView.swift; sourceTree = ""; }; DB71FD2B25F86A5100512AE1 /* AvatarStackContainerButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarStackContainerButton.swift; sourceTree = ""; }; @@ -1087,7 +1103,6 @@ DB98338525C945ED00AD9700 /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; DB98338625C945ED00AD9700 /* Assets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Assets.swift; sourceTree = ""; }; DB98339B25C96DE600AD9700 /* APIService+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Account.swift"; sourceTree = ""; }; - DB9A485B2603010E008B817C /* PHPickerResultLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHPickerResultLoader.swift; sourceTree = ""; }; DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttachmentContainerView+EmptyStateView.swift"; sourceTree = ""; }; DB9A488926034D40008B817C /* ComposeViewModel+PublishState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+PublishState.swift"; sourceTree = ""; }; DB9A488F26035963008B817C /* APIService+Media.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Media.swift"; sourceTree = ""; }; @@ -1130,7 +1145,6 @@ DBAE3F8D2616E0B1004B8251 /* APIService+Block.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Block.swift"; sourceTree = ""; }; DBAE3F932616E28B004B8251 /* APIService+Follow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Follow.swift"; sourceTree = ""; }; DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Mute.swift"; sourceTree = ""; }; - DBAE3FA82617106E004B8251 /* MastodonMetricFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonMetricFormatter.swift; sourceTree = ""; }; DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteProfileViewModel.swift; sourceTree = ""; }; DBAEDE5B267A058D00D25FF5 /* BlurhashImageCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurhashImageCacheService.swift; sourceTree = ""; }; DBAEDE60267B342D00D25FF5 /* StatusContentCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentCacheService.swift; sourceTree = ""; }; @@ -1147,6 +1161,21 @@ DBB5256D2612D5A1002F1F29 /* ProfileStatusDashboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileStatusDashboardView.swift; sourceTree = ""; }; DBB525842612D6DD002F1F29 /* ProfileStatusDashboardMeterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileStatusDashboardMeterView.swift; sourceTree = ""; }; DBB9759B262462E1004620BD /* ThreadMetaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadMetaView.swift; sourceTree = ""; }; + DBBC24A726A52F9000398BB9 /* ComposeToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeToolbarView.swift; sourceTree = ""; }; + DBBC24AB26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusContentTableViewCell.swift; sourceTree = ""; }; + DBBC24AD26A53DC100398BB9 /* ReplicaStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplicaStatusView.swift; sourceTree = ""; }; + DBBC24B426A540AE00398BB9 /* AvatarConfigurableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarConfigurableView.swift; sourceTree = ""; }; + DBBC24BB26A542F500398BB9 /* ThemeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeService.swift; sourceTree = ""; }; + DBBC24BE26A5443100398BB9 /* MastodonTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonTheme.swift; sourceTree = ""; }; + DBBC24BF26A5443100398BB9 /* SystemTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemTheme.swift; sourceTree = ""; }; + DBBC24C326A544B900398BB9 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; + DBBC24CE26A547AE00398BB9 /* ThemeService+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThemeService+Appearance.swift"; sourceTree = ""; }; + DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonRegex.swift; sourceTree = ""; }; + DBBC24D726A54BCB00398BB9 /* MastodonStatusContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonStatusContent.swift; sourceTree = ""; }; + DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonMetricFormatter.swift; sourceTree = ""; }; + DBBC24D926A54BCB00398BB9 /* MastodonStatusContent+ParseResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MastodonStatusContent+ParseResult.swift"; sourceTree = ""; }; + DBBC24DA26A54BCB00398BB9 /* MastodonField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonField.swift; sourceTree = ""; }; + DBBC24DB26A54BCB00398BB9 /* MastodonStatusContent+Appearance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MastodonStatusContent+Appearance.swift"; sourceTree = ""; }; DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerAppearance.swift; sourceTree = ""; }; DBBF1DBE2652401B00E5B703 /* AutoCompleteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteViewModel.swift; sourceTree = ""; }; DBBF1DC126524D2900E5B703 /* AutoCompleteTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteTableViewCell.swift; sourceTree = ""; }; @@ -1180,11 +1209,7 @@ DBCC3B9426157E6E0045B23D /* APIService+Relationship.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Relationship.swift"; sourceTree = ""; }; DBCC3B9A2615849F0045B23D /* PrivateNote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateNote.swift; sourceTree = ""; }; DBCCC71D25F73297007E1AB6 /* APIService+Reblog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Reblog.swift"; sourceTree = ""; }; - DBD376A62692EA00007FEC24 /* ThemeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeService.swift; sourceTree = ""; }; - DBD376A92692EA4F007FEC24 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; DBD376AB2692ECDB007FEC24 /* ThemePreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemePreference.swift; sourceTree = ""; }; - DBD376AD2692EE0A007FEC24 /* MastodonTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonTheme.swift; sourceTree = ""; }; - DBD376AF2692F20F007FEC24 /* SystemTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemTheme.swift; sourceTree = ""; }; DBD376B1269302A4007FEC24 /* UITableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITableViewCell.swift; sourceTree = ""; }; DBD9148F25DF6D8D00903DFD /* APIService+Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Onboarding.swift"; sourceTree = ""; }; DBE0821425CD382600FD6BBD /* MastodonRegisterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterViewController.swift; sourceTree = ""; }; @@ -1209,6 +1234,14 @@ DBF96325262EC0A6001D8D25 /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = System/Library/Frameworks/AuthenticationServices.framework; sourceTree = SDKROOT; }; DBF98149265E24F500E4BA07 /* ProfileFieldCollectionViewHeaderFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldCollectionViewHeaderFooterView.swift; sourceTree = ""; }; DBF9814B265E339500E4BA07 /* ProfileFieldAddEntryCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldAddEntryCollectionViewCell.swift; sourceTree = ""; }; + DBFEF05526A576EE006D7ED1 /* StatusEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditorView.swift; sourceTree = ""; }; + DBFEF05626A576EE006D7ED1 /* ComposeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeView.swift; sourceTree = ""; }; + DBFEF05726A576EE006D7ED1 /* ComposeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewModel.swift; sourceTree = ""; }; + DBFEF05826A576EE006D7ED1 /* ContentWarningEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentWarningEditorView.swift; sourceTree = ""; }; + DBFEF05926A576EE006D7ED1 /* StatusAuthorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusAuthorView.swift; sourceTree = ""; }; + DBFEF05A26A576EE006D7ED1 /* StatusAttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusAttachmentView.swift; sourceTree = ""; }; + DBFEF06226A577F2006D7ED1 /* StatusAttachmentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusAttachmentViewModel.swift; sourceTree = ""; }; + DBFEF06726A58D07006D7ED1 /* ShareActionExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareActionExtension.entitlements; sourceTree = ""; }; DDB1B139FA8EA26F510D58B6 /* Pods-AppShared.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppShared.asdk - release.xcconfig"; path = "Target Support Files/Pods-AppShared/Pods-AppShared.asdk - release.xcconfig"; sourceTree = ""; }; E5C7236E58D14A0322FE00F2 /* Pods-Mastodon-MastodonUITests.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.asdk - debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.asdk - debug.xcconfig"; sourceTree = ""; }; EC6E707B68A67DB08EC288FA /* Pods-MastodonTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.debug.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.debug.xcconfig"; sourceTree = ""; }; @@ -1296,9 +1329,18 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + DBBC24B826A5421800398BB9 /* CommonOSLog in Frameworks */, DBC6462526A1720B00B0E31B /* MastodonUI in Frameworks */, DBC6463726A195DB00B0E31B /* CoreDataStack.framework in Frameworks */, + DB41ED8426A54D8A00F58330 /* MetaTextView in Frameworks */, + DB41ED8226A54D8A00F58330 /* MastodonMeta in Frameworks */, + DB41ED7E26A54D6D00F58330 /* Fuzi in Frameworks */, + DBBC24D126A5484F00398BB9 /* UITextView+Placeholder in Frameworks */, + DBBC24B226A53ED200398BB9 /* ActiveLabel in Frameworks */, + DBBC24AA26A5301B00398BB9 /* MastodonSDK in Frameworks */, + DB41ED8026A54D7C00F58330 /* AlamofireImage in Frameworks */, DBC6463326A195DB00B0E31B /* AppShared.framework in Frameworks */, + 4278334D6033AEEE0A1C5155 /* Pods_ShareActionExtension.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1306,6 +1348,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + DBBC24CD26A5471E00398BB9 /* MastodonExtension in Frameworks */, DB00CA972632DDB600A54956 /* CommonOSLog in Frameworks */, DB6D9F42263527CE008423CD /* AlamofireImage in Frameworks */, DB6804A52637CDCC00430867 /* AppShared.framework in Frameworks */, @@ -1415,6 +1458,10 @@ 46DAB0EBDDFB678347CD96FF /* Pods-MastodonTests.asdk - release.xcconfig */, 3B7FD8F28DDA8FBCE5562B78 /* Pods-NotificationService.asdk - debug.xcconfig */, 9CFF58FD900AC059428700E7 /* Pods-NotificationService.asdk - release.xcconfig */, + 77EE917BC055E6621C0452B6 /* Pods-ShareActionExtension.debug.xcconfig */, + 6130CBE4B26E3C976ACC1688 /* Pods-ShareActionExtension.asdk - debug.xcconfig */, + 5CE45680252519F42FEA2D13 /* Pods-ShareActionExtension.asdk - release.xcconfig */, + 95AD0663479892A2109EEFD0 /* Pods-ShareActionExtension.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -1505,7 +1552,6 @@ children = ( DB5086A425CC0B7000C2C187 /* AvatarBarButtonItem.swift */, 2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */, - DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */, 0FAA101125E105390017CCDE /* PrimaryActionButton.swift */, ); path = Button; @@ -1532,9 +1578,6 @@ isa = PBXGroup; children = ( 2D5A3D0225CF8742002347D6 /* ControlContainableScrollViews.swift */, - DB2B3AE825E38850007045F9 /* UIViewPreview.swift */, - DB55D32F25FB630A0002F825 /* TwitterTextEditor+String.swift */, - DB9A485B2603010E008B817C /* PHPickerResultLoader.swift */, DB51D170262832380062B7A1 /* BlurHashDecode.swift */, DB51D171262832380062B7A1 /* BlurHashEncode.swift */, DB6180EC26391C6C0018D199 /* TransitioningMath.swift */, @@ -1549,10 +1592,9 @@ children = ( DB45FB0425CA87B4005A8AC7 /* APIService */, DB49A61925FF327D00B98345 /* EmojiService */, - DBD376A82692EA3F007FEC24 /* ThemeService */, DB9A489B26036E19008B817C /* MastodonAttachmentService */, + DBBC24BD26A5441A00398BB9 /* ThemeService */, DB45FB0E25CA87D0005A8AC7 /* AuthenticationService.swift */, - DB4563BC25E11A24004DA0B9 /* KeyboardResponderService.swift */, 2D206B8B25F6015000143C56 /* AudioPlaybackService.swift */, 2DA6054625F716A2006356F9 /* PlaybackState.swift */, 5DF1054025F886D400D6C0D4 /* VideoPlaybackService.swift */, @@ -1585,7 +1627,7 @@ children = ( 2D38F1FC25CD47D900561493 /* StatusProvider */, DBAE3F742615DD63004B8251 /* UserProvider */, - DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */, + DBBC24B426A540AE00398BB9 /* AvatarConfigurableView.swift */, 2D69CFF325CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift */, 2D5A3D3725CF8D9F002347D6 /* ScrollViewContainer.swift */, DB59F10325EF5EBC001F1DAB /* TableViewCellHeightCacheableContainer.swift */, @@ -1765,6 +1807,7 @@ 452147B2903DF38070FE56A2 /* Pods_MastodonTests.framework */, F4A2A2D7000E477CA459ADA9 /* Pods_AppShared.framework */, 374AA339A20E0FAC75BCDA6D /* Pods_NotificationService.framework */, + A32B0CACBF35F4CC3CFAA043 /* Pods_ShareActionExtension.framework */, ); name = Frameworks; sourceTree = ""; @@ -1850,7 +1893,6 @@ isa = PBXGroup; children = ( DB03F7F22689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift */, - DB03F7EF26899097007B274C /* ComposeStatusContentTableViewCell.swift */, DB36679C268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift */, DB3667A3268AE2370027D07F /* ComposeStatusPollTableViewCell.swift */, ); @@ -1961,10 +2003,10 @@ 2D61335525C1886800CAE157 /* Service */, DB8AF55525C1379F002E6C99 /* Scene */, DB8AF54125C13647002E6C99 /* Coordinator */, - DB9E0D6925EDFFE500CFDD76 /* Helper */, DB8AF56225C138BC002E6C99 /* Extension */, 2D5A3D0125CF8640002347D6 /* Vender */, DB73B495261F030D002E9E9F /* Activity */, + DBBC24D526A54BCB00398BB9 /* Helper */, DB5086CB25CC0DB400C2C187 /* Preference */, 2D69CFF225CA9E2200C3A1B2 /* Protocol */, DB98338425C945ED00AD9700 /* Generated */, @@ -2130,7 +2172,6 @@ DB5086CB25CC0DB400C2C187 /* Preference */ = { isa = PBXGroup; children = ( - DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */, DBA465942696E387002B41DB /* AppPreference.swift */, DB6D1B3C2636857500ACB481 /* AppearancePreference.swift */, DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */, @@ -2144,7 +2185,7 @@ DB55D32225FB4D320002F825 /* View */ = { isa = PBXGroup; children = ( - DB3667C9268B14A80027D07F /* ReplicaStatusView.swift */, + DBBC24AD26A53DC100398BB9 /* ReplicaStatusView.swift */, DB03F7F42689B782007B274C /* ComposeTableView.swift */, DBA0A11225FB3FC10079C110 /* ComposeToolbarView.swift */, DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */, @@ -2291,7 +2332,7 @@ DB03F7F1268990A2007B274C /* TableViewCell */, DB789A0A25F9F2950071ACA0 /* ComposeViewController.swift */, DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */, - DB66728B25F9F8DC00D60309 /* ComposeViewModel+Diffable.swift */, + DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */, DB9A488926034D40008B817C /* ComposeViewModel+PublishState.swift */, ); path = Compose; @@ -2300,6 +2341,7 @@ DB789A2125F9F76D0071ACA0 /* CollectionViewCell */ = { isa = PBXGroup; children = ( + DBBC24AB26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift */, DB6B351D2601FAEE00DC1E11 /* ComposeStatusAttachmentCollectionViewCell.swift */, DB87D4442609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift */, DB87D4502609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift */, @@ -2445,7 +2487,6 @@ DB6C8C0525F0921200AAA452 /* MastodonSDK */, DB44384E25E8C1FA008912A2 /* CALayer.swift */, 2DF123A625C3B0210020F248 /* ActiveLabel.swift */, - 2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */, DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */, DB0140CE25C42AEE00F9F3CF /* OSLog.swift */, DB68A06225E905E000CFDF14 /* UIApplication.swift */, @@ -2472,7 +2513,6 @@ 0F20223826146553000C64BF /* Array.swift */, DBCC3B2F261440A50045B23D /* UITabBarController.swift */, DBCC3B35261440BA0045B23D /* UINavigationController.swift */, - DB6D1B23263684C600ACB481 /* UserDefaults.swift */, DB97131E2666078B00BD1E90 /* Date.swift */, DBAC6489267DC355007FE9FD /* NSDiffableDataSourceSnapshot.swift */, DB52D33926839DD800D43133 /* ImageTask.swift */, @@ -2584,19 +2624,6 @@ path = ViewModel; sourceTree = ""; }; - DB9E0D6925EDFFE500CFDD76 /* Helper */ = { - isa = PBXGroup; - children = ( - 2D42FF6A25C817D2004A627A /* MastodonStatusContent.swift */, - DB1EE7AD267F3071000CC337 /* MastodonStatusContent+ParseResult.swift */, - DB1EE7AF267F3088000CC337 /* MastodonStatusContent+Appearance.swift */, - DB6F5E2E264E5518009108F4 /* MastodonRegex.swift */, - DB35FC2E26130172006193C9 /* MastodonField.swift */, - DBAE3FA82617106E004B8251 /* MastodonMetricFormatter.swift */, - ); - path = Helper; - sourceTree = ""; - }; DBA5E7A6263BD298004598BB /* ContextMenu */ = { isa = PBXGroup; children = ( @@ -2716,6 +2743,31 @@ path = View; sourceTree = ""; }; + DBBC24BD26A5441A00398BB9 /* ThemeService */ = { + isa = PBXGroup; + children = ( + DBBC24C326A544B900398BB9 /* Theme.swift */, + DBBC24BE26A5443100398BB9 /* MastodonTheme.swift */, + DBBC24BF26A5443100398BB9 /* SystemTheme.swift */, + DBBC24BB26A542F500398BB9 /* ThemeService.swift */, + DBBC24CE26A547AE00398BB9 /* ThemeService+Appearance.swift */, + ); + path = ThemeService; + sourceTree = ""; + }; + DBBC24D526A54BCB00398BB9 /* Helper */ = { + isa = PBXGroup; + children = ( + DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */, + DBBC24D726A54BCB00398BB9 /* MastodonStatusContent.swift */, + DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */, + DBBC24D926A54BCB00398BB9 /* MastodonStatusContent+ParseResult.swift */, + DBBC24DA26A54BCB00398BB9 /* MastodonField.swift */, + DBBC24DB26A54BCB00398BB9 /* MastodonStatusContent+Appearance.swift */, + ); + path = Helper; + sourceTree = ""; + }; DBBF1DC02652402000E5B703 /* View */ = { isa = PBXGroup; children = ( @@ -2735,10 +2787,10 @@ DBC6461326A170AB00B0E31B /* ShareActionExtension */ = { isa = PBXGroup; children = ( - DBC6462226A1712000B0E31B /* ShareViewModel.swift */, - DBC6461426A170AB00B0E31B /* ShareViewController.swift */, - DBC6461626A170AB00B0E31B /* MainInterface.storyboard */, + DBFEF06726A58D07006D7ED1 /* ShareActionExtension.entitlements */, DBC6461926A170AB00B0E31B /* Info.plist */, + DBC6461626A170AB00B0E31B /* MainInterface.storyboard */, + DBFEF06126A57721006D7ED1 /* Scene */, ); path = ShareActionExtension; sourceTree = ""; @@ -2769,17 +2821,6 @@ path = FetchedResultsController; sourceTree = ""; }; - DBD376A82692EA3F007FEC24 /* ThemeService */ = { - isa = PBXGroup; - children = ( - DBD376A62692EA00007FEC24 /* ThemeService.swift */, - DBD376A92692EA4F007FEC24 /* Theme.swift */, - DBD376AD2692EE0A007FEC24 /* MastodonTheme.swift */, - DBD376AF2692F20F007FEC24 /* SystemTheme.swift */, - ); - path = ThemeService; - sourceTree = ""; - }; DBE0821A25CD382900FD6BBD /* Register */ = { isa = PBXGroup; children = ( @@ -2850,6 +2891,31 @@ path = NotificationService; sourceTree = ""; }; + DBFEF05426A576EE006D7ED1 /* View */ = { + isa = PBXGroup; + children = ( + DBBC24A726A52F9000398BB9 /* ComposeToolbarView.swift */, + DBFEF05526A576EE006D7ED1 /* StatusEditorView.swift */, + DBFEF05626A576EE006D7ED1 /* ComposeView.swift */, + DBFEF05726A576EE006D7ED1 /* ComposeViewModel.swift */, + DBFEF05826A576EE006D7ED1 /* ContentWarningEditorView.swift */, + DBFEF05926A576EE006D7ED1 /* StatusAuthorView.swift */, + DBFEF05A26A576EE006D7ED1 /* StatusAttachmentView.swift */, + DBFEF06226A577F2006D7ED1 /* StatusAttachmentViewModel.swift */, + ); + path = View; + sourceTree = ""; + }; + DBFEF06126A57721006D7ED1 /* Scene */ = { + isa = PBXGroup; + children = ( + DBFEF05426A576EE006D7ED1 /* View */, + DBC6462226A1712000B0E31B /* ShareViewModel.swift */, + DBC6461426A170AB00B0E31B /* ShareViewController.swift */, + ); + path = Scene; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -3023,6 +3089,7 @@ isa = PBXNativeTarget; buildConfigurationList = DBC6462126A170AB00B0E31B /* Build configuration list for PBXNativeTarget "ShareActionExtension" */; buildPhases = ( + 641DEA63EE5B048C9A551302 /* [CP] Check Pods Manifest.lock */, DBC6460E26A170AB00B0E31B /* Sources */, DBC6460F26A170AB00B0E31B /* Frameworks */, DBC6461026A170AB00B0E31B /* Resources */, @@ -3036,6 +3103,14 @@ name = ShareActionExtension; packageProductDependencies = ( DBC6462426A1720B00B0E31B /* MastodonUI */, + DBBC24A926A5301B00398BB9 /* MastodonSDK */, + DBBC24B126A53ED200398BB9 /* ActiveLabel */, + DBBC24B726A5421800398BB9 /* CommonOSLog */, + DBBC24D026A5484F00398BB9 /* UITextView+Placeholder */, + DB41ED7D26A54D6D00F58330 /* Fuzi */, + DB41ED7F26A54D7C00F58330 /* AlamofireImage */, + DB41ED8126A54D8A00F58330 /* MastodonMeta */, + DB41ED8326A54D8A00F58330 /* MetaTextView */, ); productName = ShareActionExtension; productReference = DBC6461226A170AB00B0E31B /* ShareActionExtension.appex */; @@ -3059,6 +3134,7 @@ packageProductDependencies = ( DB00CA962632DDB600A54956 /* CommonOSLog */, DB6D9F41263527CE008423CD /* AlamofireImage */, + DBBC24CC26A5471E00398BB9 /* MastodonExtension */, ); productName = NotificationService; productReference = DBF8AE13263293E400C9C23C /* NotificationService.appex */; @@ -3262,6 +3338,28 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Mastodon/Pods-Mastodon-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; + 641DEA63EE5B048C9A551302 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ShareActionExtension-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 6E033728B42BA1C0018B6131 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -3392,7 +3490,6 @@ buildActionMask = 2147483647; files = ( DBB525212611EBD6002F1F29 /* ProfilePagingViewController.swift in Sources */, - DB35FC2F26130172006193C9 /* MastodonField.swift in Sources */, DB6180EB26391C140018D199 /* MediaPreviewTransitionItem.swift in Sources */, DB98337125C9443200AD9700 /* APIService+Authentication.swift in Sources */, DBE3CDCF261C42ED00430CC6 /* TimelineHeaderView.swift in Sources */, @@ -3433,7 +3530,6 @@ DB1FD45A25F27898004CFCFC /* CategoryPickerItem.swift in Sources */, DB6180F626391D580018D199 /* MediaPreviewableViewController.swift in Sources */, 2D571B2F26004EC000540450 /* NavigationBarProgressView.swift in Sources */, - DBD376AA2692EA4F007FEC24 /* Theme.swift in Sources */, 0FAA101225E105390017CCDE /* PrimaryActionButton.swift in Sources */, DB443CD42694627B00159B29 /* AppearanceView.swift in Sources */, DBF1D24E269DAF5D00C1C08A /* SearchDetailViewController.swift in Sources */, @@ -3470,6 +3566,7 @@ DB297B1B2679FAE200704C90 /* PlaceholderImageCacheService.swift in Sources */, 2D8FCA082637EABB00137F46 /* APIService+FollowRequest.swift in Sources */, 2D152A8C25C295CC009AA50C /* StatusView.swift in Sources */, + DBBC24DE26A54BCB00398BB9 /* MastodonMetricFormatter.swift in Sources */, 2DE0FAC82615F5F000CDF649 /* SearchRecommendAccountsCollectionViewCell.swift in Sources */, DBF1D251269DB01200C1C08A /* SearchHistoryViewController.swift in Sources */, 2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */, @@ -3478,6 +3575,7 @@ 2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */, DB789A0B25F9F2950071ACA0 /* ComposeViewController.swift in Sources */, DB938F0926240F3C00E5B6C1 /* RemoteThreadViewModel.swift in Sources */, + DBBC24AE26A53DC100398BB9 /* ReplicaStatusView.swift in Sources */, DB75BF1E263C1C1B00EDBF1F /* CustomScheduler.swift in Sources */, 0FAA102725E1126A0017CCDE /* MastodonPickServerViewController.swift in Sources */, DB59F0FE25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift in Sources */, @@ -3494,6 +3592,7 @@ DB2FF510260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift in Sources */, 0F202213261351F5000C64BF /* APIService+HashtagTimeline.swift in Sources */, DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */, + DBBC24DC26A54BCB00398BB9 /* MastodonRegex.swift in Sources */, 2D69D00A25CAA00300C3A1B2 /* APIService+CoreData+Status.swift in Sources */, DB4481C625EE2ADA00BEFB67 /* PollSection.swift in Sources */, DBAC648A267DC355007FE9FD /* NSDiffableDataSourceSnapshot.swift in Sources */, @@ -3532,10 +3631,10 @@ DBE3CDFB261C6CA500430CC6 /* FavoriteViewModel.swift in Sources */, DB8AF52F25C13561002E6C99 /* DocumentStore.swift in Sources */, 2D206B7225F5D27F00143C56 /* AudioContainerView.swift in Sources */, + DBBC24C126A5443100398BB9 /* SystemTheme.swift in Sources */, DB9D6C2425E502C60051B173 /* MosaicImageViewModel.swift in Sources */, DBE3CE01261D623D00430CC6 /* FavoriteViewModel+State.swift in Sources */, 2D82BA0525E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift in Sources */, - DB55D33025FB630A0002F825 /* TwitterTextEditor+String.swift in Sources */, 2D38F1EB25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift in Sources */, 0F202201261326E6000C64BF /* HashtagTimelineViewModel.swift in Sources */, DB6D9F9726367249008423CD /* SettingsViewController.swift in Sources */, @@ -3559,7 +3658,7 @@ DB9A48962603685D008B817C /* MastodonAttachmentService+UploadState.swift in Sources */, 2D198643261BF09500F0B013 /* SearchResultItem.swift in Sources */, 2DAC9E38262FC2320062E1A6 /* SuggestionAccountViewController.swift in Sources */, - DB66728C25F9F8DC00D60309 /* ComposeViewModel+Diffable.swift in Sources */, + DB66728C25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift in Sources */, DB6180E02639194B0018D199 /* MediaPreviewPagingViewController.swift in Sources */, DBE0822425CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift in Sources */, 2DF75B9B25D0E27500694EC8 /* StatusProviderFacade.swift in Sources */, @@ -3591,13 +3690,13 @@ 2D206B9225F60EA700143C56 /* UIControl.swift in Sources */, 2D9DB96B263A91D1007C1D71 /* APIService+DomainBlock.swift in Sources */, DBBF1DC92652538500E5B703 /* AutoCompleteSection.swift in Sources */, - 2D46975E25C2A54100CF4AA9 /* NSLayoutConstraint.swift in Sources */, 2D45E5BF25C9549700A6D639 /* PublicTimelineViewModel+State.swift in Sources */, DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */, DB7F48452620241000796008 /* ProfileHeaderViewModel.swift in Sources */, 2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */, 5DDDF1992617447F00311060 /* Mastodon+Entity+Tag.swift in Sources */, 5B90C45F262599800002E742 /* SettingsToggleTableViewCell.swift in Sources */, + DBBC24DF26A54BCB00398BB9 /* MastodonStatusContent+ParseResult.swift in Sources */, 2D694A7425F9EB4E0038ADDC /* ContentWarningOverlayView.swift in Sources */, DBAE3F682615DD60004B8251 /* UserProvider.swift in Sources */, DBAC6488267D388B007FE9FD /* ASTableNode.swift in Sources */, @@ -3607,12 +3706,11 @@ DB4481CC25EE2AFE00BEFB67 /* PollItem.swift in Sources */, DB44767B260B3B8C00B66B82 /* CustomEmojiPickerInputView.swift in Sources */, 0F20222D261457EE000C64BF /* HashtagTimelineViewModel+LoadOldestState.swift in Sources */, - DB4563BD25E11A24004DA0B9 /* KeyboardResponderService.swift in Sources */, - DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */, 5B90C462262599800002E742 /* SettingsSectionHeader.swift in Sources */, DB44768B260B3F2100B66B82 /* CustomEmojiPickerItem.swift in Sources */, 2DF75BA125D0E29D00694EC8 /* StatusProvider+StatusTableViewCellDelegate.swift in Sources */, 5DF1056425F887CB00D6C0D4 /* AVPlayer.swift in Sources */, + DBBC24E126A54BCB00398BB9 /* MastodonStatusContent+Appearance.swift in Sources */, DBBF1DCB2652539E00E5B703 /* AutoCompleteItem.swift in Sources */, 2DA6054725F716A2006356F9 /* PlaybackState.swift in Sources */, DB35FC1F2612F1D9006193C9 /* ProfileRelationshipActionButton.swift in Sources */, @@ -3627,11 +3725,9 @@ DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */, 2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */, 2D084B8D26258EA3003AA3AF /* NotificationViewModel+Diffable.swift in Sources */, - DB6D1B24263684C600ACB481 /* UserDefaults.swift in Sources */, DB3667A1268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift in Sources */, DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */, DBA94434265CBB5300C537E1 /* ProfileFieldSection.swift in Sources */, - DB6F5E2F264E5518009108F4 /* MastodonRegex.swift in Sources */, DB023295267F0AB800031745 /* ASMetaEditableTextNode.swift in Sources */, 2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */, DB4F096C269EFA2000D62E92 /* SearchResultViewController+StatusProvider.swift in Sources */, @@ -3650,7 +3746,9 @@ DBB525642612C988002F1F29 /* MeProfileViewModel.swift in Sources */, 5BB04FE9262EFC300043BFF6 /* ReportedStatusTableviewCell.swift in Sources */, DBAE3F822615DDA3004B8251 /* ProfileViewController+UserProvider.swift in Sources */, + DBBC24C426A544B900398BB9 /* Theme.swift in Sources */, DB938EED2623F79B00E5B6C1 /* ThreadViewModel.swift in Sources */, + DBBC24AC26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift in Sources */, DBC7A67C260DFADE00E57475 /* StatusPublishService.swift in Sources */, DBCBCC092680B01B000F5B51 /* AsyncHomeTimelineViewModel+LoadMiddleState.swift in Sources */, 2DCB73FD2615C13900EC03D4 /* SearchRecommendCollectionHeader.swift in Sources */, @@ -3663,6 +3761,7 @@ DB4F0963269ED06300D62E92 /* SearchResultViewController.swift in Sources */, DBBF1DC5265251C300E5B703 /* AutoCompleteViewModel+Diffable.swift in Sources */, DB68A04A25E9027700CFDF14 /* AdaptiveStatusBarStyleNavigationController.swift in Sources */, + DBBC24BC26A542F500398BB9 /* ThemeService.swift in Sources */, 0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */, DB6D9F8426358EEC008423CD /* SettingsItem.swift in Sources */, 2D364F7825E66D8300204FDC /* MastodonResendEmailViewModel.swift in Sources */, @@ -3670,6 +3769,7 @@ DBA465932696B495002B41DB /* APIService+WebFinger.swift in Sources */, DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */, DB9D6BF825E4F5690051B173 /* NotificationViewController.swift in Sources */, + DBBC24DD26A54BCB00398BB9 /* MastodonStatusContent.swift in Sources */, 2DAC9E46262FC9FD0062E1A6 /* SuggestionAccountTableViewCell.swift in Sources */, DB4FFC2C269EC39600D62E92 /* SearchTransitionController.swift in Sources */, DBA5E7A9263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift in Sources */, @@ -3689,7 +3789,6 @@ DB938F1526241FDF00E5B6C1 /* APIService+Thread.swift in Sources */, DB482A45261335BA008AE74C /* UserTimelineViewController+Provider.swift in Sources */, 2D206B8625F5FB0900143C56 /* Double.swift in Sources */, - DB9A485C2603010E008B817C /* PHPickerResultLoader.swift in Sources */, 2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */, DB35FC252612FD7A006193C9 /* ProfileFieldView.swift in Sources */, DB938F0326240EA300E5B6C1 /* CachedThreadViewModel.swift in Sources */, @@ -3697,8 +3796,6 @@ 2D650FAB25ECDC9300851B58 /* Mastodon+Entity+Error+Detail.swift in Sources */, 2D24E12D2626FD2E00A59D4F /* NotificationViewModel+LoadOldestState.swift in Sources */, 2DB72C8C262D764300CE6173 /* Mastodon+Entity+Notification+Type.swift in Sources */, - DB118A8C25E4BFB500FAB162 /* HighlightDimmableButton.swift in Sources */, - DBAE3FA92617106E004B8251 /* MastodonMetricFormatter.swift in Sources */, 2D35237A26256D920031AF25 /* NotificationSection.swift in Sources */, DB084B5725CBC56C00F898ED /* Status.swift in Sources */, DBCBCC072680AFEC000F5B51 /* AsyncHomeTimelineViewModel+LoadLatestState.swift in Sources */, @@ -3707,7 +3804,6 @@ DB9282B225F3222800823B15 /* PickServerEmptyStateView.swift in Sources */, DB1FD45025F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift in Sources */, DBD376AC2692ECDB007FEC24 /* ThemePreference.swift in Sources */, - DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */, DB4F097D26A03A5B00D62E92 /* SearchHistoryItem.swift in Sources */, DB68046C2636DC9E00430867 /* MastodonNotification.swift in Sources */, DBAE3F9E2616E308004B8251 /* APIService+Mute.swift in Sources */, @@ -3719,12 +3815,12 @@ DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */, DB98338725C945ED00AD9700 /* Strings.swift in Sources */, 2D7867192625B77500211898 /* NotificationItem.swift in Sources */, - DB1EE7AE267F3071000CC337 /* MastodonStatusContent+ParseResult.swift in Sources */, DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */, DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */, 2D5A3D0325CF8742002347D6 /* ControlContainableScrollViews.swift in Sources */, DB36679D268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift in Sources */, DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */, + DBBC24E026A54BCB00398BB9 /* MastodonField.swift in Sources */, DBCBCC032680AF6E000F5B51 /* AsyncHomeTimelineViewController+DebugAction.swift in Sources */, DBA0A11325FB3FC10079C110 /* ComposeToolbarView.swift in Sources */, 2D32EADA25CBCC3300C9ED86 /* PublicTimelineViewModel+LoadMiddleState.swift in Sources */, @@ -3738,7 +3834,6 @@ DB45FAF925CA80A2005A8AC7 /* APIService+CoreData+MastodonAuthentication.swift in Sources */, 2D04F42525C255B9003F936F /* APIService+PublicTimeline.swift in Sources */, 2D34D9E226149C920081BFC0 /* SearchRecommendTagsCollectionViewCell.swift in Sources */, - DB1EE7B0267F3088000CC337 /* MastodonStatusContent+Appearance.swift in Sources */, 2D76317D25C14DF500929FB9 /* PublicTimelineViewController+Provider.swift in Sources */, 0F20223326145E51000C64BF /* HashtagTimelineViewModel+LoadMiddleState.swift in Sources */, 2D206B8025F5F45E00143C56 /* UIImage.swift in Sources */, @@ -3747,7 +3842,6 @@ DBE54AC62636C89F004E7C0B /* NotificationPreference.swift in Sources */, 2D5A3D2825CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift in Sources */, DB98339C25C96DE600AD9700 /* APIService+Account.swift in Sources */, - 2D42FF6B25C817D2004A627A /* MastodonStatusContent.swift in Sources */, 2DF75BA725D10E1000694EC8 /* APIService+Favorite.swift in Sources */, DB9D6C3825E508BE0051B173 /* Attachment.swift in Sources */, 5DFC35DF262068D20045711D /* SearchViewController+Follow.swift in Sources */, @@ -3766,17 +3860,18 @@ DB938F1F2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift in Sources */, 2D69CFF425CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift in Sources */, DB482A4B261340A7008AE74C /* APIService+UserTimeline.swift in Sources */, - DB3667CA268B14A80027D07F /* ReplicaStatusView.swift in Sources */, DB427DD825BAA00100D1B89D /* SceneDelegate.swift in Sources */, 0F2021FB2613262F000C64BF /* HashtagTimelineViewController.swift in Sources */, DBCC3B30261440A50045B23D /* UITabBarController.swift in Sources */, DB8190C62601FF0400020C08 /* AttachmentContainerView.swift in Sources */, DB51D173262832380062B7A1 /* BlurHashEncode.swift in Sources */, - DB5086AB25CC0BBB00C2C187 /* AvatarConfigurableView.swift in Sources */, 2D32EAAC25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift in Sources */, DB71FD2C25F86A5100512AE1 /* AvatarStackContainerButton.swift in Sources */, DB87D4512609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift in Sources */, + DBBC24C026A5443100398BB9 /* MastodonTheme.swift in Sources */, + DBBC24B526A540AE00398BB9 /* AvatarConfigurableView.swift in Sources */, DB9A489026035963008B817C /* APIService+Media.swift in Sources */, + DBBC24CF26A547AE00398BB9 /* ThemeService+Appearance.swift in Sources */, 2D198649261C0B8500F0B013 /* SearchResultSection.swift in Sources */, DB4F097B26A039FF00D62E92 /* SearchHistorySection.swift in Sources */, DBB525302611EBF3002F1F29 /* ProfilePagingViewModel.swift in Sources */, @@ -3790,18 +3885,14 @@ DB9D6BFF25E4F5940051B173 /* ProfileViewController.swift in Sources */, 0FB3D30825E524C600AAD544 /* PickServerCategoriesCell.swift in Sources */, 2D4AD8A226316CD200613EFC /* SelectedAccountSection.swift in Sources */, - DBD376A72692EA00007FEC24 /* ThemeService.swift in Sources */, - DBD376AE2692EE0A007FEC24 /* MastodonTheme.swift in Sources */, DB789A1225F9F2CC0071ACA0 /* ComposeViewModel.swift in Sources */, DBB525362611ECEB002F1F29 /* UserTimelineViewController.swift in Sources */, DB6D1B3D2636857500ACB481 /* AppearancePreference.swift in Sources */, - DBD376B02692F20F007FEC24 /* SystemTheme.swift in Sources */, DB938F3326243D6200E5B6C1 /* TimelineTopLoaderTableViewCell.swift in Sources */, DB3667A4268AE2370027D07F /* ComposeStatusPollTableViewCell.swift in Sources */, DBBF1DC226524D2900E5B703 /* AutoCompleteTableViewCell.swift in Sources */, 2D38F1FE25CD481700561493 /* StatusProvider.swift in Sources */, DB1EE7B2267F9525000CC337 /* StatusProvider+StatusNodeDelegate.swift in Sources */, - DB03F7F026899097007B274C /* ComposeStatusContentTableViewCell.swift in Sources */, 5B24BBE2262DB19100A9381B /* APIService+Report.swift in Sources */, DB4F096A269EDAD200D62E92 /* SearchResultViewModel+State.swift in Sources */, 5BB04FF5262F0E6D0043BFF6 /* ReportSection.swift in Sources */, @@ -3901,8 +3992,32 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DBFEF05E26A57715006D7ED1 /* ComposeView.swift in Sources */, + DB41ED7C26A54D5500F58330 /* MastodonStatusContent+Appearance.swift in Sources */, + DBFEF06026A57715006D7ED1 /* StatusAttachmentView.swift in Sources */, DBC6462326A1712000B0E31B /* ShareViewModel.swift in Sources */, + DBBC24B326A53EE700398BB9 /* ActiveLabel.swift in Sources */, + DBBC24CB26A546C000398BB9 /* ThemePreference.swift in Sources */, + DBFEF05F26A57715006D7ED1 /* StatusAuthorView.swift in Sources */, + DBFEF05D26A57715006D7ED1 /* ContentWarningEditorView.swift in Sources */, + DB41ED7B26A54D4D00F58330 /* MastodonStatusContent+ParseResult.swift in Sources */, + DB41ED8A26A54F4C00F58330 /* AttachmentContainerView.swift in Sources */, + DBFEF05C26A57715006D7ED1 /* StatusEditorView.swift in Sources */, + DBBC24C726A5456400398BB9 /* SystemTheme.swift in Sources */, + DBBC24B626A5419700398BB9 /* ComposeStatusContentTableViewCell.swift in Sources */, DBC6462926A1736700B0E31B /* Strings.swift in Sources */, + DBBC24C826A5456400398BB9 /* ThemeService.swift in Sources */, + DBBC24C926A5456400398BB9 /* MastodonTheme.swift in Sources */, + DBBC24B926A5426000398BB9 /* StatusContentWarningEditorView.swift in Sources */, + DB41ED8B26A54F5800F58330 /* AttachmentContainerView+EmptyStateView.swift in Sources */, + DBBC24A826A52F9000398BB9 /* ComposeToolbarView.swift in Sources */, + DB41ED7A26A54D4400F58330 /* MastodonStatusContent.swift in Sources */, + DBFEF05B26A57715006D7ED1 /* ComposeViewModel.swift in Sources */, + DBBC24B026A53DF900398BB9 /* ReplicaStatusView.swift in Sources */, + DBBC24C626A5456000398BB9 /* Theme.swift in Sources */, + DBFEF06326A577F2006D7ED1 /* StatusAttachmentViewModel.swift in Sources */, + DB41ED8926A54F4000F58330 /* ComposeStatusAttachmentCollectionViewCell.swift in Sources */, + DBBC24D226A5488600398BB9 /* AvatarConfigurableView.swift in Sources */, DBC6462C26A176B000B0E31B /* Assets.swift in Sources */, DBC6461526A170AB00B0E31B /* ShareViewController.swift in Sources */, ); @@ -3914,7 +4029,6 @@ files = ( DBE54ACC2636C8FD004E7C0B /* NotificationPreference.swift in Sources */, DB68045B2636DC6A00430867 /* MastodonNotification.swift in Sources */, - DBE54ABF2636C889004E7C0B /* UserDefaults.swift in Sources */, DB6D9F3526351B7A008423CD /* NotificationService+Decrypt.swift in Sources */, DB6804662636DC9000430867 /* String.swift in Sources */, DBCBCBF4267CB070000F5B51 /* Decode85.swift in Sources */, @@ -4169,7 +4283,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 2E1F6A67FDF9771D3E064FDC /* Pods-Mastodon.debug.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; @@ -4197,7 +4310,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 75E3471C898DDD9631729B6E /* Pods-Mastodon.release.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; @@ -4462,7 +4574,9 @@ }; DBC6461D26A170AB00B0E31B /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 77EE917BC055E6621C0452B6 /* Pods-ShareActionExtension.debug.xcconfig */; buildSettings = { + CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 40; DEVELOPMENT_TEAM = 5Z4GVSS33P; @@ -4476,6 +4590,7 @@ PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -4483,7 +4598,9 @@ }; DBC6461E26A170AB00B0E31B /* ASDK - Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 6130CBE4B26E3C976ACC1688 /* Pods-ShareActionExtension.asdk - debug.xcconfig */; buildSettings = { + CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 40; DEVELOPMENT_TEAM = 5Z4GVSS33P; @@ -4497,6 +4614,7 @@ PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -4504,7 +4622,9 @@ }; DBC6461F26A170AB00B0E31B /* ASDK - Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 5CE45680252519F42FEA2D13 /* Pods-ShareActionExtension.asdk - release.xcconfig */; buildSettings = { + CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 40; DEVELOPMENT_TEAM = 5Z4GVSS33P; @@ -4518,6 +4638,7 @@ PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -4525,7 +4646,9 @@ }; DBC6462026A170AB00B0E31B /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 95AD0663479892A2109EEFD0 /* Pods-ShareActionExtension.release.xcconfig */; buildSettings = { + CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 40; DEVELOPMENT_TEAM = 5Z4GVSS33P; @@ -4539,6 +4662,7 @@ PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -4610,7 +4734,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = BD7598A87F4497045EDEF252 /* Pods-Mastodon.asdk - release.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; @@ -4844,7 +4967,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 1D6D967E77A5357E2C6110D9 /* Pods-Mastodon.asdk - debug.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; @@ -5356,6 +5478,26 @@ package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */; productName = AlamofireImage; }; + DB41ED7D26A54D6D00F58330 /* Fuzi */ = { + isa = XCSwiftPackageProductDependency; + package = DBAC649F267E6D01007FE9FD /* XCRemoteSwiftPackageReference "Fuzi" */; + productName = Fuzi; + }; + DB41ED7F26A54D7C00F58330 /* AlamofireImage */ = { + isa = XCSwiftPackageProductDependency; + package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */; + productName = AlamofireImage; + }; + DB41ED8126A54D8A00F58330 /* MastodonMeta */ = { + isa = XCSwiftPackageProductDependency; + package = DB03F7E9268976B5007B274C /* XCRemoteSwiftPackageReference "MetaTextView" */; + productName = MastodonMeta; + }; + DB41ED8326A54D8A00F58330 /* MetaTextView */ = { + isa = XCSwiftPackageProductDependency; + package = DB03F7E9268976B5007B274C /* XCRemoteSwiftPackageReference "MetaTextView" */; + productName = MetaTextView; + }; DB68050F2637D0F800430867 /* KeychainAccess */ = { isa = XCSwiftPackageProductDependency; package = DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */; @@ -5401,6 +5543,29 @@ package = DBB525062611EAC0002F1F29 /* XCRemoteSwiftPackageReference "Tabman" */; productName = Tabman; }; + DBBC24A926A5301B00398BB9 /* MastodonSDK */ = { + isa = XCSwiftPackageProductDependency; + productName = MastodonSDK; + }; + DBBC24B126A53ED200398BB9 /* ActiveLabel */ = { + isa = XCSwiftPackageProductDependency; + package = 2D42FF5F25C8177C004A627A /* XCRemoteSwiftPackageReference "ActiveLabel" */; + productName = ActiveLabel; + }; + DBBC24B726A5421800398BB9 /* CommonOSLog */ = { + isa = XCSwiftPackageProductDependency; + package = DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */; + productName = CommonOSLog; + }; + DBBC24CC26A5471E00398BB9 /* MastodonExtension */ = { + isa = XCSwiftPackageProductDependency; + productName = MastodonExtension; + }; + DBBC24D026A5484F00398BB9 /* UITextView+Placeholder */ = { + isa = XCSwiftPackageProductDependency; + package = DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */; + productName = "UITextView+Placeholder"; + }; DBC6462426A1720B00B0E31B /* MastodonUI */ = { isa = XCSwiftPackageProductDependency; productName = MastodonUI; diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index e417a0d4e..4dc2e933a 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,27 +12,27 @@ CoreDataStack.xcscheme_^#shared#^_ orderHint - 20 + 23 Mastodon - ASDK.xcscheme_^#shared#^_ orderHint - 5 + 2 Mastodon - RTL.xcscheme_^#shared#^_ orderHint - 7 + 3 Mastodon - Release.xcscheme_^#shared#^_ orderHint - 3 + 1 Mastodon.xcscheme_^#shared#^_ orderHint - 1 + 0 NotificationService.xcscheme_^#shared#^_ diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved index 78b4a755e..55f7d7113 100644 --- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -163,6 +163,15 @@ "version": "1.0.0" } }, + { + "package": "Introspect", + "repositoryURL": "https://github.com/siteline/SwiftUI-Introspect.git", + "state": { + "branch": null, + "revision": "2e09be8af614401bc9f87d40093ec19ce56ccaf2", + "version": "0.1.3" + } + }, { "package": "SwiftyJSON", "repositoryURL": "https://github.com/SwiftyJSON/SwiftyJSON.git", diff --git a/Mastodon/Extension/ActiveLabel.swift b/Mastodon/Extension/ActiveLabel.swift index ebb82655b..584888195 100644 --- a/Mastodon/Extension/ActiveLabel.swift +++ b/Mastodon/Extension/ActiveLabel.swift @@ -9,6 +9,7 @@ import UIKit import Foundation import ActiveLabel import os.log +import MastodonUI extension ActiveLabel { @@ -58,7 +59,7 @@ extension ActiveLabel { } extension ActiveLabel { - func configure(text: String) { + public func configure(text: String) { attributedText = nil activeEntities.removeAll() self.text = text @@ -69,7 +70,7 @@ extension ActiveLabel { extension ActiveLabel { /// status content - func configure(content: String, emojiDict: MastodonStatusContent.EmojiDict) { + public func configure(content: String, emojiDict: MastodonStatusContent.EmojiDict) { attributedText = nil activeEntities.removeAll() @@ -83,7 +84,7 @@ extension ActiveLabel { } } - func configure(contentParseResult parseResult: MastodonStatusContent.ParseResult?) { + public func configure(contentParseResult parseResult: MastodonStatusContent.ParseResult?) { attributedText = nil activeEntities.removeAll() text = parseResult?.trimmed ?? "" @@ -92,14 +93,14 @@ extension ActiveLabel { } /// account note - func configure(note: String, emojiDict: MastodonStatusContent.EmojiDict) { + public func configure(note: String, emojiDict: MastodonStatusContent.EmojiDict) { configure(content: note, emojiDict: emojiDict) } } extension ActiveLabel { /// account field - func configure(field: String, emojiDict: MastodonStatusContent.EmojiDict) { + public func configure(field: String, emojiDict: MastodonStatusContent.EmojiDict) { configure(content: field, emojiDict: emojiDict) } } diff --git a/Mastodon/Helper/MastodonField.swift b/Mastodon/Helper/MastodonField.swift index 437b0924b..86bc2cc9b 100644 --- a/Mastodon/Helper/MastodonField.swift +++ b/Mastodon/Helper/MastodonField.swift @@ -5,59 +5,59 @@ // Created by MainasuK Cirno on 2021-3-30. // -import Foundation -import ActiveLabel - -enum MastodonField { - - @available(*, deprecated, message: "rely on server meta rendering") - static func parse(field string: String, emojiDict: MastodonStatusContent.EmojiDict) -> ParseResult { - // use content parser get emoji entities - let value = string - - var string = string - var entities: [ActiveEntity] = [] - - do { - let contentParseresult = try MastodonStatusContent.parse(content: string, emojiDict: emojiDict) - string = contentParseresult.trimmed - entities.append(contentsOf: contentParseresult.activeEntities) - } catch { - // assertionFailure(error.localizedDescription) - } - - let mentionMatches = string.matches(pattern: "(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?)") - let hashtagMatches = string.matches(pattern: "(?:#([^\\s.]+))") - let urlMatches = string.matches(pattern: "(?i)https?://\\S+(?:/|\\b)") - - - for match in mentionMatches { - guard let text = string.substring(with: match, at: 0) else { continue } - let entity = ActiveEntity(range: match.range, type: .mention(text, userInfo: nil)) - entities.append(entity) - } - - for match in hashtagMatches { - guard let text = string.substring(with: match, at: 0) else { continue } - let entity = ActiveEntity(range: match.range, type: .hashtag(text, userInfo: nil)) - entities.append(entity) - } - - for match in urlMatches { - guard let text = string.substring(with: match, at: 0) else { continue } - let entity = ActiveEntity(range: match.range, type: .url(text, trimmed: text, url: text, userInfo: nil)) - entities.append(entity) - } - - return ParseResult(value: value, trimmed: string, activeEntities: entities) - } - -} - -extension MastodonField { - struct ParseResult { - let value: String - let trimmed: String - let activeEntities: [ActiveEntity] - } -} +//import Foundation +//import ActiveLabel +// +//enum MastodonField { +// +// @available(*, deprecated, message: "rely on server meta rendering") +// public static func parse(field string: String, emojiDict: MastodonStatusContent.EmojiDict) -> ParseResult { +// // use content parser get emoji entities +// let value = string +// +// var string = string +// var entities: [ActiveEntity] = [] +// +// do { +// let contentParseresult = try MastodonStatusContent.parse(content: string, emojiDict: emojiDict) +// string = contentParseresult.trimmed +// entities.append(contentsOf: contentParseresult.activeEntities) +// } catch { +// // assertionFailure(error.localizedDescription) +// } +// +// let mentionMatches = string.matches(pattern: "(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?)") +// let hashtagMatches = string.matches(pattern: "(?:#([^\\s.]+))") +// let urlMatches = string.matches(pattern: "(?i)https?://\\S+(?:/|\\b)") +// +// +// for match in mentionMatches { +// guard let text = string.substring(with: match, at: 0) else { continue } +// let entity = ActiveEntity(range: match.range, type: .mention(text, userInfo: nil)) +// entities.append(entity) +// } +// +// for match in hashtagMatches { +// guard let text = string.substring(with: match, at: 0) else { continue } +// let entity = ActiveEntity(range: match.range, type: .hashtag(text, userInfo: nil)) +// entities.append(entity) +// } +// +// for match in urlMatches { +// guard let text = string.substring(with: match, at: 0) else { continue } +// let entity = ActiveEntity(range: match.range, type: .url(text, trimmed: text, url: text, userInfo: nil)) +// entities.append(entity) +// } +// +// return ParseResult(value: value, trimmed: string, activeEntities: entities) +// } +// +//} +// +//extension MastodonField { +// public struct ParseResult { +// let value: String +// let trimmed: String +// let activeEntities: [ActiveEntity] +// } +//} diff --git a/Mastodon/Helper/MastodonMetricFormatter.swift b/Mastodon/Helper/MastodonMetricFormatter.swift index 0711669fb..3c9c4dd75 100644 --- a/Mastodon/Helper/MastodonMetricFormatter.swift +++ b/Mastodon/Helper/MastodonMetricFormatter.swift @@ -7,9 +7,9 @@ import Foundation -final class MastodonMetricFormatter: Formatter { +final public class MastodonMetricFormatter: Formatter { - func string(from number: Int) -> String? { + public func string(from number: Int) -> String? { let isPositive = number >= 0 let symbol = isPositive ? "" : "-" diff --git a/Mastodon/Helper/MastodonRegex.swift b/Mastodon/Helper/MastodonRegex.swift index c390ea513..c8c3f498b 100644 --- a/Mastodon/Helper/MastodonRegex.swift +++ b/Mastodon/Helper/MastodonRegex.swift @@ -7,19 +7,19 @@ import Foundation -enum MastodonRegex { +public enum MastodonRegex { /// mention, hashtag. /// @... /// #... - static let highlightPattern = "(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+))" + public static let highlightPattern = "(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+))" /// emoji /// :shortcode: /// accept ^\B: or \s: but not accept \B: to force user input a space to make emoji take effect /// precondition :\B with following space - static let emojiPattern = "(?:(^\\B:|\\s:)([a-zA-Z0-9_]+)(:\\B(?=\\s)))" + public static let emojiPattern = "(?:(^\\B:|\\s:)([a-zA-Z0-9_]+)(:\\B(?=\\s)))" /// mention, hashtag, emoji /// @… /// #… /// :… - static let autoCompletePattern = "(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+))|(^\\B:|\\s:)([a-zA-Z0-9_]+)" + public static let autoCompletePattern = "(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+))|(^\\B:|\\s:)([a-zA-Z0-9_]+)" } diff --git a/Mastodon/Helper/MastodonStatusContent+Appearance.swift b/Mastodon/Helper/MastodonStatusContent+Appearance.swift index f627093c6..d82d68b85 100644 --- a/Mastodon/Helper/MastodonStatusContent+Appearance.swift +++ b/Mastodon/Helper/MastodonStatusContent+Appearance.swift @@ -8,7 +8,7 @@ import UIKit extension MastodonStatusContent { - struct Appearance { + public struct Appearance { let attributes: [NSAttributedString.Key: Any] let urlAttributes: [NSAttributedString.Key: Any] let hashtagAttributes: [NSAttributedString.Key: Any] diff --git a/Mastodon/Helper/MastodonStatusContent+ParseResult.swift b/Mastodon/Helper/MastodonStatusContent+ParseResult.swift index f1f02fae1..9de0b341f 100644 --- a/Mastodon/Helper/MastodonStatusContent+ParseResult.swift +++ b/Mastodon/Helper/MastodonStatusContent+ParseResult.swift @@ -9,20 +9,20 @@ import Foundation import ActiveLabel extension MastodonStatusContent { - struct ParseResult: Hashable { - let document: String - let original: String - let trimmed: String - let activeEntities: [ActiveEntity] + public struct ParseResult: Hashable { + public let document: String + public let original: String + public let trimmed: String + public let activeEntities: [ActiveEntity] - static func == (lhs: MastodonStatusContent.ParseResult, rhs: MastodonStatusContent.ParseResult) -> Bool { + public static func == (lhs: MastodonStatusContent.ParseResult, rhs: MastodonStatusContent.ParseResult) -> Bool { return lhs.document == rhs.document && lhs.original == rhs.original && lhs.trimmed == rhs.trimmed && lhs.activeEntities.count == rhs.activeEntities.count // FIXME: } - func hash(into hasher: inout Hasher) { + public func hash(into hasher: inout Hasher) { hasher.combine(document) hasher.combine(original) hasher.combine(trimmed) @@ -57,7 +57,7 @@ extension ActiveEntityType { static let appScheme = "mastodon" - init?(url: URL) { + public init?(url: URL) { guard let scheme = url.scheme?.lowercased() else { return nil } guard scheme == ActiveEntityType.appScheme else { self = .url("", trimmed: "", url: url.absoluteString, userInfo: nil) @@ -78,7 +78,7 @@ extension ActiveEntityType { return nil } - var uri: URL? { + public var uri: URL? { switch self { case .url(_, _, let url, _): return URL(string: url) diff --git a/Mastodon/Helper/MastodonStatusContent.swift b/Mastodon/Helper/MastodonStatusContent.swift index d19463a85..90e697daf 100755 --- a/Mastodon/Helper/MastodonStatusContent.swift +++ b/Mastodon/Helper/MastodonStatusContent.swift @@ -10,14 +10,14 @@ import Combine import ActiveLabel import Fuzi -enum MastodonStatusContent { +public enum MastodonStatusContent { - typealias EmojiShortcode = String - typealias EmojiDict = [EmojiShortcode: URL] + public typealias EmojiShortcode = String + public typealias EmojiDict = [EmojiShortcode: URL] static let workingQueue = DispatchQueue(label: "org.joinmastodon.app.ActiveLabel.working-queue", qos: .userInteractive, attributes: .concurrent) - static func parseResult(content: String, emojiDict: MastodonStatusContent.EmojiDict) -> AnyPublisher { + public static func parseResult(content: String, emojiDict: MastodonStatusContent.EmojiDict) -> AnyPublisher { return Future { promise in self.workingQueue.async { let parseResult = try? MastodonStatusContent.parse(content: content, emojiDict: emojiDict) @@ -27,7 +27,7 @@ enum MastodonStatusContent { .eraseToAnyPublisher() } - static func parse(content: String, emojiDict: EmojiDict) throws -> MastodonStatusContent.ParseResult { + public static func parse(content: String, emojiDict: EmojiDict) throws -> MastodonStatusContent.ParseResult { let document: String = { var content = content for (shortcode, url) in emojiDict { diff --git a/Mastodon/Preference/AppearancePreference.swift b/Mastodon/Preference/AppearancePreference.swift index 1b4c42807..4630c1234 100644 --- a/Mastodon/Preference/AppearancePreference.swift +++ b/Mastodon/Preference/AppearancePreference.swift @@ -19,7 +19,8 @@ extension UserDefaults { @objc dynamic var preferredStaticAvatar: Bool { get { - register(defaults: [#function: false]) + // default false + // without set register to profile timeline performance return bool(forKey: #function) } set { self[#function] = newValue } diff --git a/Mastodon/Preference/NotificationPreference.swift b/Mastodon/Preference/NotificationPreference.swift index 289cd1fdf..63092d56d 100644 --- a/Mastodon/Preference/NotificationPreference.swift +++ b/Mastodon/Preference/NotificationPreference.swift @@ -6,6 +6,7 @@ // import UIKit +import MastodonExtension extension UserDefaults { diff --git a/Mastodon/Preference/SplashPreference.swift b/Mastodon/Preference/SplashPreference.swift deleted file mode 100644 index c622b8657..000000000 --- a/Mastodon/Preference/SplashPreference.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// SplashPreference.swift -// Mastodon -// -// Created by Cirno MainasuK on 2020-2-4. -// - -import UIKit - -extension UserDefaults { - // TODO: splash scene -} diff --git a/Mastodon/Preference/ThemePreference.swift b/Mastodon/Preference/ThemePreference.swift index 5faf6097c..624047798 100644 --- a/Mastodon/Preference/ThemePreference.swift +++ b/Mastodon/Preference/ThemePreference.swift @@ -6,6 +6,7 @@ // import UIKit +import MastodonExtension extension UserDefaults { diff --git a/Mastodon/Protocol/AvatarConfigurableView.swift b/Mastodon/Protocol/AvatarConfigurableView.swift index 9fe56ab40..5807eed18 100644 --- a/Mastodon/Protocol/AvatarConfigurableView.swift +++ b/Mastodon/Protocol/AvatarConfigurableView.swift @@ -24,11 +24,22 @@ extension AvatarConfigurableView { public func configure(with configuration: AvatarConfigurableViewConfiguration) { let placeholderImage: UIImage = { guard let placeholderImage = configuration.placeholderImage else { + #if APP_EXTENSION + let placeholderImage = configuration.placeholderImage ?? UIImage.placeholder(size: Self.configurableAvatarImageSize, color: .systemFill) + if Self.configurableAvatarImageCornerRadius < Self.configurableAvatarImageSize.width * 0.5 { + return placeholderImage + .af.imageAspectScaled(toFill: Self.configurableAvatarImageSize) + .af.imageRounded(withCornerRadius: Self.configurableAvatarImageCornerRadius, divideRadiusByImageScale: false) + } else { + return placeholderImage.af.imageRoundedIntoCircle() + } + #else return AppContext.shared.placeholderImageCacheService.image( color: .systemFill, size: Self.configurableAvatarImageSize, cornerRadius: Self.configurableAvatarImageCornerRadius ) + #endif } return placeholderImage }() @@ -115,7 +126,7 @@ extension AvatarConfigurableView { } struct AvatarConfigurableViewConfiguration { - + let avatarImageURL: URL? let placeholderImage: UIImage? let borderColor: UIColor? diff --git a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusAttachmentCollectionViewCell.swift b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusAttachmentCollectionViewCell.swift index a5906a9da..8d8cabcf6 100644 --- a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusAttachmentCollectionViewCell.swift +++ b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusAttachmentCollectionViewCell.swift @@ -8,13 +8,16 @@ import os.log import UIKit import Combine +import MastodonUI protocol ComposeStatusAttachmentCollectionViewCellDelegate: AnyObject { func composeStatusAttachmentCollectionViewCell(_ cell: ComposeStatusAttachmentCollectionViewCell, removeButtonDidPressed button: UIButton) } final class ComposeStatusAttachmentCollectionViewCell: UICollectionViewCell { - + + let logger = Logger(subsystem: "ComposeStatusAttachmentCollectionViewCell", category: "UI") + var disposeBag = Set() static let verticalMarginHeight: CGFloat = ComposeStatusAttachmentCollectionViewCell.removeButtonSize.height * 0.5 @@ -58,7 +61,7 @@ final class ComposeStatusAttachmentCollectionViewCell: UICollectionViewCell { } deinit { - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") } } @@ -96,7 +99,7 @@ extension ComposeStatusAttachmentCollectionViewCell { extension ComposeStatusAttachmentCollectionViewCell { @objc private func removeButtonDidPressed(_ sender: UIButton) { - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") delegate?.composeStatusAttachmentCollectionViewCell(self, removeButtonDidPressed: sender) } diff --git a/Mastodon/Scene/Compose/TableViewCell/ComposeStatusContentTableViewCell.swift b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusContentTableViewCell.swift similarity index 95% rename from Mastodon/Scene/Compose/TableViewCell/ComposeStatusContentTableViewCell.swift rename to Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusContentTableViewCell.swift index aa641fe4a..7dbda5b36 100644 --- a/Mastodon/Scene/Compose/TableViewCell/ComposeStatusContentTableViewCell.swift +++ b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusContentTableViewCell.swift @@ -5,14 +5,16 @@ // Created by MainasuK Cirno on 2021-6-28. // - import os.log import UIKit import Combine import MetaTextView +import UITextView_Placeholder final class ComposeStatusContentTableViewCell: UITableViewCell { + let logger = Logger(subsystem: "ComposeStatusContentTableViewCell", category: "UI") + var disposeBag = Set() let statusView = ReplicaStatusView() @@ -149,7 +151,7 @@ extension ComposeStatusContentTableViewCell: UITextViewDelegate { } func textViewDidChange(_ textView: UITextView) { - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: text: %s", ((#file as NSString).lastPathComponent), #line, #function, textView.text) + logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): text: \(textView.text ?? "")") guard textView === statusContentWarningEditorView.textView else { return } // replace line break with space textView.text = textView.text.replacingOccurrences(of: "\n", with: " ") diff --git a/Mastodon/Scene/Compose/ComposeViewController.swift b/Mastodon/Scene/Compose/ComposeViewController.swift index b48b01a7e..b48bcc631 100644 --- a/Mastodon/Scene/Compose/ComposeViewController.swift +++ b/Mastodon/Scene/Compose/ComposeViewController.swift @@ -76,7 +76,11 @@ final class ComposeViewController: UIViewController, NeedsDependency { let composeToolbarView = ComposeToolbarView() var composeToolbarViewBottomLayoutConstraint: NSLayoutConstraint! - let composeToolbarBackgroundView = UIView() + let composeToolbarBackgroundView: UIView = { + let view = UIView() + view.backgroundColor = Asset.Scene.Compose.toolbarBackground.color + return view + }() static func createPhotoLibraryPickerConfiguration(selectionLimit: Int = 4) -> PHPickerConfiguration { var configuration = PHPickerConfiguration() @@ -189,7 +193,7 @@ extension ComposeViewController { ]) tableView.delegate = self - viewModel.setupDiffableDataSource( + viewModel.setupDataSource( tableView: tableView, metaTextDelegate: self, metaTextViewDelegate: self, @@ -264,7 +268,6 @@ extension ComposeViewController { self.view.layoutIfNeeded() } } - self.updateKeyboardBackground(isKeyboardDisplay: isShow) return } // isShow AND dock state @@ -280,14 +283,12 @@ extension ComposeViewController { self.autoCompleteViewController.tableView.contentInset.bottom = autoCompleteTableViewBottomInset self.autoCompleteViewController.tableView.verticalScrollIndicatorInsets.bottom = autoCompleteTableViewBottomInset - // adjust inset for collectionView + // adjust inset for tableView let contentFrame = self.view.convert(self.tableView.frame, to: nil) let padding = contentFrame.maxY + extraMargin - endFrame.minY guard padding > 0 else { self.tableView.contentInset.bottom = self.view.safeAreaInsets.bottom + extraMargin self.tableView.verticalScrollIndicatorInsets.bottom = self.view.safeAreaInsets.bottom + extraMargin - - self.updateKeyboardBackground(isKeyboardDisplay: false) return } @@ -297,7 +298,6 @@ extension ComposeViewController { self.composeToolbarViewBottomLayoutConstraint.constant = endFrame.height self.view.layoutIfNeeded() } - self.updateKeyboardBackground(isKeyboardDisplay: isShow) }) .store(in: &disposeBag) @@ -587,10 +587,6 @@ extension ComposeViewController { imagePicker.delegate = self return imagePicker } - - private func updateKeyboardBackground(isKeyboardDisplay: Bool) { - composeToolbarBackgroundView.backgroundColor = Asset.Scene.Compose.toolbarBackground.color - } private func setupBackgroundColor(theme: Theme) { view.backgroundColor = theme.systemElevatedBackgroundColor diff --git a/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift b/Mastodon/Scene/Compose/ComposeViewModel+DataSource.swift similarity index 99% rename from Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift rename to Mastodon/Scene/Compose/ComposeViewModel+DataSource.swift index 59de0d60a..4b4e80c52 100644 --- a/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift +++ b/Mastodon/Scene/Compose/ComposeViewModel+DataSource.swift @@ -16,7 +16,7 @@ import MetaTextView extension ComposeViewModel { - func setupDiffableDataSource( + func setupDataSource( tableView: UITableView, metaTextDelegate: MetaTextDelegate, metaTextViewDelegate: UITextViewDelegate, diff --git a/Mastodon/Scene/Compose/View/AttachmentContainerView+EmptyStateView.swift b/Mastodon/Scene/Compose/View/AttachmentContainerView+EmptyStateView.swift index b9e92f515..b441fa253 100644 --- a/Mastodon/Scene/Compose/View/AttachmentContainerView+EmptyStateView.swift +++ b/Mastodon/Scene/Compose/View/AttachmentContainerView+EmptyStateView.swift @@ -6,6 +6,7 @@ // import UIKit +import MastodonUI extension AttachmentContainerView { final class EmptyStateView: UIView { diff --git a/Mastodon/Scene/Compose/View/ComposeToolbarView.swift b/Mastodon/Scene/Compose/View/ComposeToolbarView.swift index 3edb17e41..aecd5d063 100644 --- a/Mastodon/Scene/Compose/View/ComposeToolbarView.swift +++ b/Mastodon/Scene/Compose/View/ComposeToolbarView.swift @@ -96,9 +96,6 @@ final class ComposeToolbarView: UIView { extension ComposeToolbarView { private func _init() { - // magic keyboard color (iOS 14): - // light with white background: RGB 214 216 222 - // dark with black background: RGB 43 43 43 backgroundColor = Asset.Scene.Compose.toolbarBackground.color let stackView = UIStackView() diff --git a/Mastodon/Scene/Compose/View/ReplicaStatusView.swift b/Mastodon/Scene/Compose/View/ReplicaStatusView.swift index 0f53b113a..9dde2f638 100644 --- a/Mastodon/Scene/Compose/View/ReplicaStatusView.swift +++ b/Mastodon/Scene/Compose/View/ReplicaStatusView.swift @@ -48,7 +48,7 @@ final class ReplicaStatusView: UIView { let headerIconLabel: UILabel = { let label = UILabel() - label.attributedText = StatusView.iconAttributedString(image: StatusView.reblogIconImage) + label.attributedText = ReplicaStatusView.iconAttributedString(image: ReplicaStatusView.reblogIconImage) return label }() @@ -67,7 +67,6 @@ final class ReplicaStatusView: UIView { return view }() let avatarImageView: UIImageView = FLAnimatedImageView() - let avatarStackedContainerButton: AvatarStackContainerButton = AvatarStackContainerButton() let nameLabel: ActiveLabel = { let label = ActiveLabel(style: .statusName) @@ -157,7 +156,7 @@ extension ReplicaStatusView { headerContainerStackView.topAnchor.constraint(equalTo: headerContainerView.topAnchor), headerContainerStackView.leadingAnchor.constraint(equalTo: headerContainerView.leadingAnchor), headerContainerStackView.trailingAnchor.constraint(equalTo: headerContainerView.trailingAnchor), - headerContainerView.bottomAnchor.constraint(equalTo: headerContainerStackView.bottomAnchor, constant: StatusView.containerStackViewSpacing).priority(.defaultHigh), + headerContainerView.bottomAnchor.constraint(equalTo: headerContainerStackView.bottomAnchor, constant: ReplicaStatusView.containerStackViewSpacing).priority(.defaultHigh), ]) containerStackView.addArrangedSubview(headerContainerView) defer { @@ -167,15 +166,15 @@ extension ReplicaStatusView { // author container: [avatar | author meta container | reveal button] let authorContainerStackView = UIStackView() authorContainerStackView.axis = .horizontal - authorContainerStackView.spacing = StatusView.avatarToLabelSpacing + authorContainerStackView.spacing = ReplicaStatusView.avatarToLabelSpacing authorContainerStackView.distribution = .fill // avatar avatarView.translatesAutoresizingMaskIntoConstraints = false authorContainerStackView.addArrangedSubview(avatarView) NSLayoutConstraint.activate([ - avatarView.widthAnchor.constraint(equalToConstant: StatusView.avatarImageSize.width).priority(.required - 1), - avatarView.heightAnchor.constraint(equalToConstant: StatusView.avatarImageSize.height).priority(.required - 1), + avatarView.widthAnchor.constraint(equalToConstant: ReplicaStatusView.avatarImageSize.width).priority(.required - 1), + avatarView.heightAnchor.constraint(equalToConstant: ReplicaStatusView.avatarImageSize.height).priority(.required - 1), ]) avatarImageView.translatesAutoresizingMaskIntoConstraints = false avatarView.addSubview(avatarImageView) @@ -185,14 +184,6 @@ extension ReplicaStatusView { avatarImageView.trailingAnchor.constraint(equalTo: avatarView.trailingAnchor), avatarImageView.bottomAnchor.constraint(equalTo: avatarView.bottomAnchor), ]) - avatarStackedContainerButton.translatesAutoresizingMaskIntoConstraints = false - avatarView.addSubview(avatarStackedContainerButton) - NSLayoutConstraint.activate([ - avatarStackedContainerButton.topAnchor.constraint(equalTo: avatarView.topAnchor), - avatarStackedContainerButton.leadingAnchor.constraint(equalTo: avatarView.leadingAnchor), - avatarStackedContainerButton.trailingAnchor.constraint(equalTo: avatarView.trailingAnchor), - avatarStackedContainerButton.bottomAnchor.constraint(equalTo: avatarView.bottomAnchor), - ]) // author meta container: [title container | subtitle container] let authorMetaContainerStackView = UIStackView() @@ -235,7 +226,7 @@ extension ReplicaStatusView { authorContainerStackView.topAnchor.constraint(equalTo: authorContainerView.topAnchor), authorContainerStackView.leadingAnchor.constraint(equalTo: authorContainerView.leadingAnchor), authorContainerStackView.trailingAnchor.constraint(equalTo: authorContainerView.trailingAnchor), - authorContainerView.bottomAnchor.constraint(equalTo: authorContainerStackView.bottomAnchor, constant: StatusView.containerStackViewSpacing).priority(.defaultHigh), + authorContainerView.bottomAnchor.constraint(equalTo: authorContainerStackView.bottomAnchor, constant: ReplicaStatusView.containerStackViewSpacing).priority(.defaultHigh), ]) containerStackView.addArrangedSubview(authorContainerView) @@ -252,8 +243,6 @@ extension ReplicaStatusView { // status statusContainerStackView.addArrangedSubview(contentMetaText.textView) contentMetaText.textView.setContentCompressionResistancePriority(.required - 1, for: .vertical) - - avatarStackedContainerButton.isHidden = true } } diff --git a/Mastodon/Scene/Compose/View/StatusContentWarningEditorView.swift b/Mastodon/Scene/Compose/View/StatusContentWarningEditorView.swift index 1d7ae65e3..1ce274a55 100644 --- a/Mastodon/Scene/Compose/View/StatusContentWarningEditorView.swift +++ b/Mastodon/Scene/Compose/View/StatusContentWarningEditorView.swift @@ -6,6 +6,7 @@ // import UIKit +import MastodonUI final class StatusContentWarningEditorView: UIView { @@ -72,28 +73,6 @@ extension StatusContentWarningEditorView { containerStackView.addArrangedSubview(iconImageView) iconImageView.setContentHuggingPriority(.required - 1, for: .horizontal) containerStackView.addArrangedSubview(textView) - -// iconImageView.translatesAutoresizingMaskIntoConstraints = false -// addSubview(iconImageView) -// NSLayoutConstraint.activate([ -// iconImageView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), -// iconImageView.widthAnchor.constraint(equalToConstant: StatusView.avatarImageSize.width).priority(.defaultHigh), // center alignment to avatar -// ]) -// iconImageView.setContentHuggingPriority(.required - 2, for: .horizontal) -// -// textView.translatesAutoresizingMaskIntoConstraints = false -// addSubview(textView) -// NSLayoutConstraint.activate([ -// textView.centerYAnchor.constraint(equalTo: centerYAnchor), -// textView.topAnchor.constraint(equalTo: topAnchor, constant: 6), -// textView.leadingAnchor.constraint(equalTo: iconImageView.trailingAnchor, constant: StatusView.avatarToLabelSpacing - 4), // align to name label. minus magic 4pt to remove addition inset -// textView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), -// bottomAnchor.constraint(equalTo: textView.bottomAnchor, constant: 6), -// textView.heightAnchor.constraint(greaterThanOrEqualToConstant: 44).priority(.defaultHigh), -// ]) -// -// textView.setContentHuggingPriority(.required - 1, for: .vertical) -// textView.setContentCompressionResistancePriority(.required - 1, for: .vertical) } } diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift index 0b439753b..a55f1ebf6 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift @@ -57,10 +57,6 @@ extension HomeTimelineViewController { guard let self = self else { return } self.showThreadAction(action) }, - UIAction(title: "Show Share Action Compose", image: UIImage(systemName: "square.and.arrow.up"), attributes: []) { [weak self] action in - guard let self = self else { return } - self.showShareActionExtensionComposeView(action) - }, UIAction(title: "Settings", image: UIImage(systemName: "gear"), attributes: []) { [weak self] action in guard let self = self else { return } self.showSettings(action) @@ -370,13 +366,5 @@ extension HomeTimelineViewController { ) } - @objc private func showShareActionExtensionComposeView(_ sender: UIAction) { - let viewController = UIHostingController( - rootView: ComposeView().environmentObject(MastodonUI.ComposeViewModel()) - ) - let navigationController = UINavigationController(rootViewController: viewController) - present(navigationController, animated: true, completion: nil) - } - } #endif diff --git a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift index 618faf1c2..a88d3e06c 100644 --- a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift +++ b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift @@ -465,7 +465,7 @@ extension ProfileHeaderViewController: PHPickerViewControllerDelegate { func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { picker.dismiss(animated: true, completion: nil) guard let result = results.first else { return } - PHPickerResultLoader.loadImageData(from: result) + ItemProviderLoader.loadImageData(from: result) .sink { [weak self] completion in guard let _ = self else { return } switch completion { diff --git a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift index cafa31451..f2841b261 100644 --- a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift +++ b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift @@ -12,6 +12,7 @@ import PhotosUI import GameplayKit import MobileCoreServices import MastodonSDK +import MastodonUI protocol MastodonAttachmentServiceDelegate: AnyObject { func mastodonAttachmentService(_ service: MastodonAttachmentService, uploadStateDidChange state: MastodonAttachmentService.UploadState?) @@ -62,10 +63,10 @@ final class MastodonAttachmentService { Just(pickerResult) .flatMap { result -> AnyPublisher in if result.itemProvider.hasRepresentationConforming(toTypeIdentifier: UTType.image.identifier, fileOptions: []) { - return PHPickerResultLoader.loadImageData(from: result).eraseToAnyPublisher() + return ItemProviderLoader.loadImageData(from: result).eraseToAnyPublisher() } if result.itemProvider.hasRepresentationConforming(toTypeIdentifier: UTType.movie.identifier, fileOptions: []) { - return PHPickerResultLoader.loadVideoData(from: result).eraseToAnyPublisher() + return ItemProviderLoader.loadVideoData(from: result).eraseToAnyPublisher() } return Fail(error: AttachmentError.invalidAttachmentType).eraseToAnyPublisher() } @@ -186,7 +187,6 @@ extension MastodonAttachmentService { case invalidAttachmentType case attachmentTooLarge } - } extension MastodonAttachmentService { diff --git a/Mastodon/Service/ThemeService/Theme.swift b/Mastodon/Service/ThemeService/Theme.swift index 6516945d8..c9378e878 100644 --- a/Mastodon/Service/ThemeService/Theme.swift +++ b/Mastodon/Service/ThemeService/Theme.swift @@ -7,7 +7,7 @@ import UIKit -protocol Theme { +public protocol Theme { var systemBackgroundColor: UIColor { get } var secondarySystemBackgroundColor: UIColor { get } var tertiarySystemBackgroundColor: UIColor { get } @@ -36,13 +36,13 @@ protocol Theme { } -enum ThemeName: String, CaseIterable { +public enum ThemeName: String, CaseIterable { case system case mastodon } extension ThemeName { - var theme: Theme { + public var theme: Theme { switch self { case .system: return SystemTheme() case .mastodon: return MastodonTheme() diff --git a/Mastodon/Service/ThemeService/ThemeService+Appearance.swift b/Mastodon/Service/ThemeService/ThemeService+Appearance.swift new file mode 100644 index 000000000..da14a7772 --- /dev/null +++ b/Mastodon/Service/ThemeService/ThemeService+Appearance.swift @@ -0,0 +1,57 @@ +// +// ThemeService+Appearance.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021-7-19. +// + +import UIKit + +extension ThemeService { + func set(themeName: ThemeName) { + UserDefaults.shared.currentThemeNameRawValue = themeName.rawValue + + let theme = themeName.theme + apply(theme: theme) + currentTheme.value = theme + } + + func apply(theme: Theme) { + // set navigation bar appearance + let appearance = UINavigationBarAppearance() + appearance.configureWithDefaultBackground() + appearance.backgroundColor = theme.navigationBarBackgroundColor + UINavigationBar.appearance().standardAppearance = appearance + UINavigationBar.appearance().compactAppearance = appearance + UINavigationBar.appearance().scrollEdgeAppearance = appearance + + // set tab bar appearance + let tabBarAppearance = UITabBarAppearance() + tabBarAppearance.configureWithDefaultBackground() + + let tabBarItemAppearance = UITabBarItemAppearance() + tabBarItemAppearance.selected.iconColor = theme.tabBarItemSelectedIconColor + tabBarItemAppearance.focused.iconColor = theme.tabBarItemFocusedIconColor + tabBarItemAppearance.normal.iconColor = theme.tabBarItemNormalIconColor + tabBarItemAppearance.disabled.iconColor = theme.tabBarItemDisabledIconColor + tabBarAppearance.stackedLayoutAppearance = tabBarItemAppearance + tabBarAppearance.inlineLayoutAppearance = tabBarItemAppearance + tabBarAppearance.compactInlineLayoutAppearance = tabBarItemAppearance + + tabBarAppearance.backgroundColor = theme.tabBarBackgroundColor + tabBarAppearance.selectionIndicatorTintColor = Asset.Colors.brandBlue.color + UITabBar.appearance().standardAppearance = tabBarAppearance + UITabBar.appearance().barTintColor = theme.tabBarBackgroundColor + + // set table view cell appearance + UITableViewCell.appearance().backgroundColor = theme.tableViewCellBackgroundColor + UITableViewCell.appearance(whenContainedInInstancesOf: [SettingsViewController.self]).backgroundColor = theme.secondarySystemGroupedBackgroundColor + UITableViewCell.appearance().selectionColor = theme.tableViewCellSelectionBackgroundColor + + // set search bar appearance + UISearchBar.appearance().tintColor = Asset.Colors.brandBlue.color + UISearchBar.appearance().barTintColor = theme.navigationBarBackgroundColor + let cancelButtonAttributes: [NSAttributedString.Key : Any] = [NSAttributedString.Key.foregroundColor: Asset.Colors.brandBlue.color] + UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).setTitleTextAttributes(cancelButtonAttributes, for: .normal) + } +} diff --git a/Mastodon/Service/ThemeService/ThemeService.swift b/Mastodon/Service/ThemeService/ThemeService.swift index c55000b8a..35d5b3491 100644 --- a/Mastodon/Service/ThemeService/ThemeService.swift +++ b/Mastodon/Service/ThemeService/ThemeService.swift @@ -21,52 +21,4 @@ final class ThemeService { currentTheme = CurrentValueSubject(theme) } - func set(themeName: ThemeName) { - UserDefaults.shared.currentThemeNameRawValue = themeName.rawValue - - let theme = themeName.theme - apply(theme: theme) - currentTheme.value = theme - } - - func apply(theme: Theme) { - // set navigation bar appearance - let appearance = UINavigationBarAppearance() - appearance.configureWithDefaultBackground() - appearance.backgroundColor = theme.navigationBarBackgroundColor - UINavigationBar.appearance().standardAppearance = appearance - UINavigationBar.appearance().compactAppearance = appearance - UINavigationBar.appearance().scrollEdgeAppearance = appearance - - // set tab bar appearance - let tabBarAppearance = UITabBarAppearance() - tabBarAppearance.configureWithDefaultBackground() - - let tabBarItemAppearance = UITabBarItemAppearance() - tabBarItemAppearance.selected.iconColor = theme.tabBarItemSelectedIconColor - tabBarItemAppearance.focused.iconColor = theme.tabBarItemFocusedIconColor - tabBarItemAppearance.normal.iconColor = theme.tabBarItemNormalIconColor - tabBarItemAppearance.disabled.iconColor = theme.tabBarItemDisabledIconColor - tabBarAppearance.stackedLayoutAppearance = tabBarItemAppearance - tabBarAppearance.inlineLayoutAppearance = tabBarItemAppearance - tabBarAppearance.compactInlineLayoutAppearance = tabBarItemAppearance - - tabBarAppearance.backgroundColor = theme.tabBarBackgroundColor - tabBarAppearance.selectionIndicatorTintColor = Asset.Colors.brandBlue.color - UITabBar.appearance().standardAppearance = tabBarAppearance - UITabBar.appearance().barTintColor = theme.tabBarBackgroundColor - - // set table view cell appearance - UITableViewCell.appearance().backgroundColor = theme.tableViewCellBackgroundColor - UITableViewCell.appearance(whenContainedInInstancesOf: [SettingsViewController.self]).backgroundColor = theme.secondarySystemGroupedBackgroundColor - UITableViewCell.appearance().selectionColor = theme.tableViewCellSelectionBackgroundColor - - // set search bar appearance - UISearchBar.appearance().tintColor = Asset.Colors.brandBlue.color - UISearchBar.appearance().barTintColor = theme.navigationBarBackgroundColor - let cancelButtonAttributes: [NSAttributedString.Key : Any] = [NSAttributedString.Key.foregroundColor: Asset.Colors.brandBlue.color] - UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).setTitleTextAttributes(cancelButtonAttributes, for: .normal) - } - } - diff --git a/Mastodon/Supporting Files/AppDelegate.swift b/Mastodon/Supporting Files/AppDelegate.swift index 3598e0192..56382babf 100644 --- a/Mastodon/Supporting Files/AppDelegate.swift +++ b/Mastodon/Supporting Files/AppDelegate.swift @@ -10,6 +10,7 @@ import UIKit import UserNotifications import AppShared import AVFoundation +@_exported import MastodonUI #if ASDK import AsyncDisplayKit diff --git a/MastodonSDK/Package.swift b/MastodonSDK/Package.swift index 4b3ba9d45..ef5f93131 100644 --- a/MastodonSDK/Package.swift +++ b/MastodonSDK/Package.swift @@ -24,6 +24,8 @@ let package = Package( .package(url: "https://github.com/apple/swift-nio.git", from: "1.0.0"), .package(url: "https://github.com/kean/Nuke.git", from: "10.3.1"), .package(name: "NukeFLAnimatedImagePlugin", url: "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git", from: "8.0.0"), + .package(name: "UITextView+Placeholder", url: "https://github.com/MainasuK/UITextView-Placeholder.git", from: "1.4.1"), + .package(name: "Introspect", url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "0.1.3") ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. @@ -38,9 +40,12 @@ let package = Package( .target( name: "MastodonUI", dependencies: [ + "MastodonSDK", "MastodonExtension", "Nuke", - "NukeFLAnimatedImagePlugin" + "NukeFLAnimatedImagePlugin", + "UITextView+Placeholder", + "Introspect", ] ), .target( diff --git a/Mastodon/Extension/NSLayoutConstraint.swift b/MastodonSDK/Sources/MastodonExtension/NSLayoutConstraint.swift similarity index 67% rename from Mastodon/Extension/NSLayoutConstraint.swift rename to MastodonSDK/Sources/MastodonExtension/NSLayoutConstraint.swift index eea697e2b..057b17859 100644 --- a/Mastodon/Extension/NSLayoutConstraint.swift +++ b/MastodonSDK/Sources/MastodonExtension/NSLayoutConstraint.swift @@ -8,12 +8,12 @@ import UIKit extension NSLayoutConstraint { - func priority(_ priority: UILayoutPriority) -> Self { + public func priority(_ priority: UILayoutPriority) -> Self { self.priority = priority return self } - func identifier(_ identifier: String?) -> Self { + public func identifier(_ identifier: String?) -> Self { self.identifier = identifier return self } diff --git a/Mastodon/Extension/UserDefaults.swift b/MastodonSDK/Sources/MastodonExtension/UserDefaults.swift similarity index 81% rename from Mastodon/Extension/UserDefaults.swift rename to MastodonSDK/Sources/MastodonExtension/UserDefaults.swift index 619d6c250..4bba77dc1 100644 --- a/Mastodon/Extension/UserDefaults.swift +++ b/MastodonSDK/Sources/MastodonExtension/UserDefaults.swift @@ -6,11 +6,10 @@ // import Foundation -import AppShared extension UserDefaults { - subscript(key: String) -> T? { + public subscript(key: String) -> T? { get { if let rawValue = value(forKey: key) as? T.RawValue { return T(rawValue: rawValue) @@ -20,7 +19,7 @@ extension UserDefaults { set { set(newValue?.rawValue, forKey: key) } } - subscript(key: String) -> T? { + public subscript(key: String) -> T? { get { return value(forKey: key) as? T } set { set(newValue, forKey: key) } } diff --git a/MastodonSDK/Sources/MastodonUI/Compose/ComposeView.swift b/MastodonSDK/Sources/MastodonUI/Compose/ComposeView.swift deleted file mode 100644 index 10ae383d2..000000000 --- a/MastodonSDK/Sources/MastodonUI/Compose/ComposeView.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// ComposeView.swift -// -// -// Created by MainasuK Cirno on 2021-7-16. -// - -import SwiftUI - -public struct ComposeView: View { - - @EnvironmentObject public var viewModel: ComposeViewModel - - public init() { } - - public var body: some View { - GeometryReader { proxy in - ScrollView(.vertical) { - StatusAuthorView( - avatarImageURL: viewModel.avatarImageURL, - name: viewModel.authorName, - username: viewModel.authorUsername - ) - TextEditorView( - string: $viewModel.statusContent, - width: viewModel.frame.width, - attributedString: viewModel.statusContentAttributedString - ) - .frame(width: viewModel.frame.width) - .frame(minHeight: 100) - ForEach(viewModel.attachments, id: \.self) { image in - Image(uiImage: image) - .resizable() - .aspectRatio(16.0/9.0, contentMode: .fill) - .frame(maxWidth: .infinity) - .background(Color.gray) - .cornerRadius(4) - } - } // end ScrollView - .preference( - key: ComposeViewFramePreferenceKey.self, - value: proxy.frame(in: .local) - ) - .onPreferenceChange(ComposeViewFramePreferenceKey.self) { frame in - viewModel.frame = frame - print(frame) - } - } - } -} - -struct ComposeViewFramePreferenceKey: PreferenceKey { - static var defaultValue: CGRect = .zero - static func reduce(value: inout CGRect, nextValue: () -> CGRect) { } -} - -struct ComposeView_Previews: PreviewProvider { - - static let viewModel: ComposeViewModel = { - let viewModel = ComposeViewModel() - return viewModel - }() - - static var previews: some View { - ComposeView().environmentObject(viewModel) - } - -} diff --git a/MastodonSDK/Sources/MastodonUI/Compose/ComposeViewModel.swift b/MastodonSDK/Sources/MastodonUI/Compose/ComposeViewModel.swift deleted file mode 100644 index 4679c61c8..000000000 --- a/MastodonSDK/Sources/MastodonUI/Compose/ComposeViewModel.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// ComposeViewModel.swift -// ShareActionExtension -// -// Created by MainasuK Cirno on 2021-7-16. -// - -import Foundation -import SwiftUI -import Combine - -public class ComposeViewModel: ObservableObject { - - var disposeBag = Set() - - @Published var frame: CGRect = .zero - - @Published var avatarImageURL: URL? - @Published var authorName: String = "" - @Published var authorUsername: String = "" - - @Published var statusContent = "" - @Published var statusContentAttributedString = NSAttributedString() - @Published var contentWarningContent = "" - - @Published var attachments: [UIImage] = [] - - public init() { - $statusContent - .map { NSAttributedString(string: $0) } - .assign(to: &$statusContentAttributedString) - - #if DEBUG - avatarImageURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif") - authorName = "Alice" - authorUsername = "alice" - attachments = [ - UIImage(systemName: "photo")!, - UIImage(systemName: "photo")!, - UIImage(systemName: "photo")!, - UIImage(systemName: "photo")!, - ] - #endif - } - -} diff --git a/Mastodon/Service/KeyboardResponderService.swift b/MastodonSDK/Sources/MastodonUI/Service/KeyboardResponderService.swift similarity index 90% rename from Mastodon/Service/KeyboardResponderService.swift rename to MastodonSDK/Sources/MastodonUI/Service/KeyboardResponderService.swift index d4bf9b58b..65328afa8 100644 --- a/Mastodon/Service/KeyboardResponderService.swift +++ b/MastodonSDK/Sources/MastodonUI/Service/KeyboardResponderService.swift @@ -8,7 +8,7 @@ import UIKit import Combine -final class KeyboardResponderService { +final public class KeyboardResponderService { var disposeBag = Set() @@ -16,9 +16,9 @@ final class KeyboardResponderService { public static let shared = KeyboardResponderService() // output - let isShow = CurrentValueSubject(false) - let state = CurrentValueSubject(.none) - let endFrame = CurrentValueSubject(.zero) + public let isShow = CurrentValueSubject(false) + public let state = CurrentValueSubject(.none) + public let endFrame = CurrentValueSubject(.zero) private init() { NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification, object: nil) @@ -82,7 +82,7 @@ extension KeyboardResponderService { } extension KeyboardResponderService { - enum KeyboardState { + public enum KeyboardState { case none case notLocal case notDock // undock | split diff --git a/MastodonSDK/Sources/MastodonUI/AnimatedImage.swift b/MastodonSDK/Sources/MastodonUI/SwiftUI/AnimatedImage.swift similarity index 78% rename from MastodonSDK/Sources/MastodonUI/AnimatedImage.swift rename to MastodonSDK/Sources/MastodonUI/SwiftUI/AnimatedImage.swift index d6c916785..9a0f65108 100644 --- a/MastodonSDK/Sources/MastodonUI/AnimatedImage.swift +++ b/MastodonSDK/Sources/MastodonUI/SwiftUI/AnimatedImage.swift @@ -9,23 +9,27 @@ import SwiftUI import Nuke import FLAnimatedImage -struct AnimatedImage: UIViewRepresentable { +public struct AnimatedImage: UIViewRepresentable { - let imageURL: URL? + public let imageURL: URL? - func makeUIView(context: Context) -> FLAnimatedImageViewProxy { + public init(imageURL: URL?) { + self.imageURL = imageURL + } + + public func makeUIView(context: Context) -> FLAnimatedImageViewProxy { let proxy = FLAnimatedImageViewProxy(frame: .zero) Nuke.loadImage(with: imageURL, into: proxy.imageView) return proxy } - func updateUIView(_ proxy: FLAnimatedImageViewProxy, context: Context) { + public func updateUIView(_ proxy: FLAnimatedImageViewProxy, context: Context) { Nuke.cancelRequest(for: proxy.imageView) Nuke.loadImage(with: imageURL, into: proxy.imageView) } } -final class FLAnimatedImageViewProxy: UIView { +final public class FLAnimatedImageViewProxy: UIView { let imageView = FLAnimatedImageView() override init(frame: CGRect) { diff --git a/Mastodon/Vender/PHPickerResultLoader.swift b/MastodonSDK/Sources/MastodonUI/Vendor/ItemProviderLoader.swift similarity index 74% rename from Mastodon/Vender/PHPickerResultLoader.swift rename to MastodonSDK/Sources/MastodonUI/Vendor/ItemProviderLoader.swift index 8a3bca621..2524fcb11 100644 --- a/Mastodon/Vender/PHPickerResultLoader.swift +++ b/MastodonSDK/Sources/MastodonUI/Vendor/ItemProviderLoader.swift @@ -1,6 +1,6 @@ // -// PHPickerResultLoader.swift -// Mastodon +// ItemProviderLoader.swift +// MastodonUI // // Created by MainasuK Cirno on 2021-3-18. // @@ -14,11 +14,19 @@ import MastodonSDK // load image with low memory usage // Refs: https://christianselig.com/2020/09/phpickerviewcontroller-efficiently/ -enum PHPickerResultLoader { +public enum ItemProviderLoader { + static let logger = Logger(subsystem: "ItemProviderLoader", category: "logic") +} + +extension ItemProviderLoader { + + public static func loadImageData(from result: PHPickerResult) -> Future { + loadImageData(from: result.itemProvider) + } - static func loadImageData(from result: PHPickerResult) -> Future { + public static func loadImageData(from itemProvider: NSItemProvider) -> Future { Future { promise in - result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { url, error in + itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { url, error in if let error = error { promise(.failure(error)) return @@ -63,17 +71,25 @@ enum PHPickerResultLoader { CGImageDestinationFinalize(imageDestination) let dataSize = ByteCountFormatter.string(fromByteCount: Int64(data.length), countStyle: .memory) - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: load image %s", ((#file as NSString).lastPathComponent), #line, #function, dataSize) + logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): load image \(dataSize)") let file = Mastodon.Query.MediaAttachment.jpeg(data as Data) promise(.success(file)) } } } - - static func loadVideoData(from result: PHPickerResult) -> Future { + +} + +extension ItemProviderLoader { + + public static func loadVideoData(from result: PHPickerResult) -> Future { + loadVideoData(from: result.itemProvider) + } + + public static func loadVideoData(from itemProvider: NSItemProvider) -> Future { Future { promise in - result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { url, error in + itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { url, error in if let error = error { promise(.failure(error)) return diff --git a/Mastodon/Vender/TwitterTextEditor+String.swift b/MastodonSDK/Sources/MastodonUI/Vendor/TwitterTextEditor+String.swift similarity index 84% rename from Mastodon/Vender/TwitterTextEditor+String.swift rename to MastodonSDK/Sources/MastodonUI/Vendor/TwitterTextEditor+String.swift index 7abdba3a3..b6b3926bd 100644 --- a/Mastodon/Vender/TwitterTextEditor+String.swift +++ b/MastodonSDK/Sources/MastodonUI/Vendor/TwitterTextEditor+String.swift @@ -10,16 +10,16 @@ import Foundation extension String { @inlinable - var length: Int { + public var length: Int { (self as NSString).length } @inlinable - func substring(with range: NSRange) -> String { + public func substring(with range: NSRange) -> String { (self as NSString).substring(with: range) } - func substring(with result: NSTextCheckingResult, at index: Int) -> String? { + public func substring(with result: NSTextCheckingResult, at index: Int) -> String? { guard index < result.numberOfRanges else { return nil } @@ -30,7 +30,7 @@ extension String { return substring(with: result.range(at: index)) } - func firstMatch(pattern: String, + public func firstMatch(pattern: String, options: NSRegularExpression.Options = [], range: NSRange? = nil) -> NSTextCheckingResult? { @@ -41,7 +41,7 @@ extension String { return regularExpression.firstMatch(in: self, options: [], range: range) } - func matches(pattern: String, + public func matches(pattern: String, options: NSRegularExpression.Options = [], range: NSRange? = nil) -> [NSTextCheckingResult] { diff --git a/Mastodon/Vender/UIViewPreview.swift b/MastodonSDK/Sources/MastodonUI/Vendor/UIViewPreview.swift similarity index 100% rename from Mastodon/Vender/UIViewPreview.swift rename to MastodonSDK/Sources/MastodonUI/Vendor/UIViewPreview.swift diff --git a/Mastodon/Scene/Share/View/Button/HighlightDimmableButton.swift b/MastodonSDK/Sources/MastodonUI/View/Button/HighlightDimmableButton.swift similarity index 59% rename from Mastodon/Scene/Share/View/Button/HighlightDimmableButton.swift rename to MastodonSDK/Sources/MastodonUI/View/Button/HighlightDimmableButton.swift index 5202d376a..c956c6041 100644 --- a/Mastodon/Scene/Share/View/Button/HighlightDimmableButton.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Button/HighlightDimmableButton.swift @@ -7,25 +7,25 @@ import UIKit -final class HighlightDimmableButton: UIButton { +final public class HighlightDimmableButton: UIButton { - var expandEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + public var expandEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) - override init(frame: CGRect) { + public override init(frame: CGRect) { super.init(frame: frame) _init() } - required init?(coder: NSCoder) { + public required init?(coder: NSCoder) { super.init(coder: coder) _init() } - override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { return bounds.inset(by: expandEdgeInsets).contains(point) } - override var isHighlighted: Bool { + public override var isHighlighted: Bool { didSet { alpha = isHighlighted ? 0.6 : 1 } diff --git a/MastodonSDK/Sources/MastodonUI/RoundedEdgesButton.swift b/MastodonSDK/Sources/MastodonUI/View/Button/RoundedEdgesButton.swift similarity index 100% rename from MastodonSDK/Sources/MastodonUI/RoundedEdgesButton.swift rename to MastodonSDK/Sources/MastodonUI/View/Button/RoundedEdgesButton.swift diff --git a/Podfile b/Podfile index ecfb510b6..cd7dbfd62 100644 --- a/Podfile +++ b/Podfile @@ -34,6 +34,11 @@ target 'NotificationService' do use_frameworks! end +target 'ShareActionExtension' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! +end + target 'AppShared' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! diff --git a/Podfile.lock b/Podfile.lock index 079d0080e..516d109e8 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -78,6 +78,6 @@ SPEC CHECKSUMS: Texture: 2f109e937850d94d1d07232041c9c7313ccddb81 "UITextField+Shake": 298ac5a0f239d731bdab999b19b628c956ca0ac3 -PODFILE CHECKSUM: f2f99b5771c5c36ef69d13999b88cea5b0e8bfe1 +PODFILE CHECKSUM: adf1bf30957525fcafb99001323d1c6ad9995b9d COCOAPODS: 1.10.1 diff --git a/ShareActionExtension/Scene/ShareViewController.swift b/ShareActionExtension/Scene/ShareViewController.swift new file mode 100644 index 000000000..3fba5adb0 --- /dev/null +++ b/ShareActionExtension/Scene/ShareViewController.swift @@ -0,0 +1,228 @@ +// +// ShareViewController.swift +// MastodonShareAction +// +// Created by MainasuK Cirno on 2021-7-16. +// + +import os.log +import UIKit +import Combine +import MastodonUI +import SwiftUI + +class ShareViewController: UIViewController { + + let logger = Logger(subsystem: "ShareViewController", category: "UI") + + var disposeBag = Set() + let viewModel = ShareViewModel() + + let publishButton: UIButton = { + let button = RoundedEdgesButton(type: .custom) + button.setTitle(L10n.Scene.Compose.composeAction, for: .normal) + button.titleLabel?.font = .systemFont(ofSize: 14, weight: .bold) + button.setBackgroundImage(.placeholder(color: Asset.Colors.brandBlue.color), for: .normal) + button.setBackgroundImage(.placeholder(color: Asset.Colors.brandBlue.color.withAlphaComponent(0.5)), for: .highlighted) + button.setBackgroundImage(.placeholder(color: Asset.Colors.Button.disabled.color), for: .disabled) + button.setTitleColor(.white, for: .normal) + button.contentEdgeInsets = UIEdgeInsets(top: 6, left: 16, bottom: 5, right: 16) // set 28pt height + button.adjustsImageWhenHighlighted = false + return button + }() + + private(set) lazy var cancelBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .plain, target: self, action: #selector(ShareViewController.cancelBarButtonItemPressed(_:))) + private(set) lazy var publishBarButtonItem: UIBarButtonItem = { + let barButtonItem = UIBarButtonItem(customView: publishButton) + barButtonItem.target = self + barButtonItem.action = #selector(ShareViewController.publishBarButtonItemPressed(_:)) + return barButtonItem + }() + + let activityIndicatorBarButtonItem: UIBarButtonItem = { + let indicatorView = UIActivityIndicatorView(style: .medium) + let barButtonItem = UIBarButtonItem(customView: indicatorView) + indicatorView.startAnimating() + return barButtonItem + }() + + + let viewSafeAreaDidChange = PassthroughSubject() + let composeToolbarView = ComposeToolbarView() + var composeToolbarViewBottomLayoutConstraint: NSLayoutConstraint! + let composeToolbarBackgroundView: UIView = { + let view = UIView() + view.backgroundColor = Asset.Scene.Compose.toolbarBackground.color + return view + }() +} + +extension ShareViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = Asset.Colors.Background.systemBackground.color + + navigationItem.leftBarButtonItem = cancelBarButtonItem + viewModel.isBusy + .receive(on: DispatchQueue.main) + .sink { [weak self] isBusy in + guard let self = self else { return } + self.navigationItem.rightBarButtonItem = isBusy ? self.activityIndicatorBarButtonItem : self.publishBarButtonItem + } + .store(in: &disposeBag) + + let hostingViewController = UIHostingController( + rootView: ComposeView().environmentObject(viewModel.composeViewModel) + ) + addChild(hostingViewController) + view.addSubview(hostingViewController.view) + hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(hostingViewController.view) + NSLayoutConstraint.activate([ + hostingViewController.view.topAnchor.constraint(equalTo: view.topAnchor), + hostingViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + hostingViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), + hostingViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + hostingViewController.didMove(toParent: self) + + composeToolbarView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(composeToolbarView) + composeToolbarViewBottomLayoutConstraint = view.bottomAnchor.constraint(equalTo: composeToolbarView.bottomAnchor) + NSLayoutConstraint.activate([ + composeToolbarView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + composeToolbarView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + composeToolbarViewBottomLayoutConstraint, + composeToolbarView.heightAnchor.constraint(equalToConstant: ComposeToolbarView.toolbarHeight), + ]) + composeToolbarView.preservesSuperviewLayoutMargins = true + composeToolbarView.delegate = self + + composeToolbarBackgroundView.translatesAutoresizingMaskIntoConstraints = false + view.insertSubview(composeToolbarBackgroundView, belowSubview: composeToolbarView) + NSLayoutConstraint.activate([ + composeToolbarBackgroundView.topAnchor.constraint(equalTo: composeToolbarView.topAnchor), + composeToolbarBackgroundView.leadingAnchor.constraint(equalTo: composeToolbarView.leadingAnchor), + composeToolbarBackgroundView.trailingAnchor.constraint(equalTo: composeToolbarView.trailingAnchor), + view.bottomAnchor.constraint(equalTo: composeToolbarBackgroundView.bottomAnchor), + ]) + + // FIXME: using iOS 15 toolbar for .keyboard placement + let keyboardEventPublishers = Publishers.CombineLatest3( + KeyboardResponderService.shared.isShow, + KeyboardResponderService.shared.state, + KeyboardResponderService.shared.endFrame + ) + + Publishers.CombineLatest( + keyboardEventPublishers, + viewSafeAreaDidChange + ) + .sink(receiveValue: { [weak self] keyboardEvents, _ in + guard let self = self else { return } + + let (isShow, state, endFrame) = keyboardEvents + guard isShow, state == .dock else { + UIView.animate(withDuration: 0.3) { + self.composeToolbarViewBottomLayoutConstraint.constant = self.view.safeAreaInsets.bottom + self.view.layoutIfNeeded() + } + return + } + // isShow AND dock state + + UIView.animate(withDuration: 0.3) { + self.composeToolbarViewBottomLayoutConstraint.constant = endFrame.height + self.view.layoutIfNeeded() + } + }) + .store(in: &disposeBag) + + // bind visibility toolbar UI + Publishers.CombineLatest( + viewModel.selectedStatusVisibility, + viewModel.traitCollectionDidChangePublisher + ) + .receive(on: DispatchQueue.main) + .sink { [weak self] type, _ in + guard let self = self else { return } + let image = type.image(interfaceStyle: self.traitCollection.userInterfaceStyle) + self.composeToolbarView.visibilityButton.setImage(image, for: .normal) + self.composeToolbarView.activeVisibilityType.value = type + } + .store(in: &disposeBag) + + // bind counter + viewModel.characterCount + .receive(on: DispatchQueue.main) + .sink { [weak self] characterCount in + guard let self = self else { return } + let count = ShareViewModel.composeContentLimit - characterCount + self.composeToolbarView.characterCountLabel.text = "\(count)" + switch count { + case _ where count < 0: + self.composeToolbarView.characterCountLabel.font = .monospacedDigitSystemFont(ofSize: 24, weight: .bold) + self.composeToolbarView.characterCountLabel.textColor = Asset.Colors.danger.color + self.composeToolbarView.characterCountLabel.accessibilityLabel = L10n.Scene.Compose.Accessibility.inputLimitExceedsCount(abs(count)) + default: + self.composeToolbarView.characterCountLabel.font = .monospacedDigitSystemFont(ofSize: 15, weight: .regular) + self.composeToolbarView.characterCountLabel.textColor = Asset.Colors.Label.secondary.color + self.composeToolbarView.characterCountLabel.accessibilityLabel = L10n.Scene.Compose.Accessibility.inputLimitRemainsCount(count) + } + } + .store(in: &disposeBag) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + viewModel.viewDidAppear.value = true + viewModel.inputItems.value = extensionContext?.inputItems.compactMap { $0 as? NSExtensionItem } ?? [] + } + + override func viewSafeAreaInsetsDidChange() { + super.viewSafeAreaInsetsDidChange() + + viewSafeAreaDidChange.send() + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + viewModel.traitCollectionDidChangePublisher.send() + } + +} + +extension ShareViewController { + @objc private func cancelBarButtonItemPressed(_ sender: UIBarButtonItem) { + logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + extensionContext?.cancelRequest(withError: ShareViewModel.ShareError.userCancelShare) + } + + @objc private func publishBarButtonItemPressed(_ sender: UIBarButtonItem) { + logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + } +} + +// MARK - ComposeToolbarViewDelegate +extension ShareViewController: ComposeToolbarViewDelegate { + + func composeToolbarView(_ composeToolbarView: ComposeToolbarView, contentWarningButtonDidPressed sender: UIButton) { + logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + + withAnimation { + viewModel.composeViewModel.isContentWarningComposing.toggle() + } + } + + func composeToolbarView(_ composeToolbarView: ComposeToolbarView, visibilityButtonDidPressed sender: UIButton, visibilitySelectionType type: ComposeToolbarView.VisibilitySelectionType) { + logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + + viewModel.selectedStatusVisibility.value = type + } + + +} diff --git a/ShareActionExtension/ShareViewModel.swift b/ShareActionExtension/Scene/ShareViewModel.swift similarity index 54% rename from ShareActionExtension/ShareViewModel.swift rename to ShareActionExtension/Scene/ShareViewModel.swift index 8384eb126..777e5be5f 100644 --- a/ShareActionExtension/ShareViewModel.swift +++ b/ShareActionExtension/Scene/ShareViewModel.swift @@ -11,6 +11,8 @@ import Combine import CoreData import CoreDataStack import MastodonUI +import SwiftUI +import UniformTypeIdentifiers final class ShareViewModel { @@ -18,10 +20,15 @@ final class ShareViewModel { var disposeBag = Set() + static let composeContentLimit: Int = 500 + // input - let viewDidAppear = CurrentValueSubject(false) private var coreDataStack: CoreDataStack? var managedObjectContext: NSManagedObjectContext? + var inputItems = CurrentValueSubject<[NSExtensionItem], Never>([]) + let viewDidAppear = CurrentValueSubject(false) + let traitCollectionDidChangePublisher = CurrentValueSubject(Void()) // use CurrentValueSubject to make initial event emit + let selectedStatusVisibility = CurrentValueSubject(.public) // output let authentication = CurrentValueSubject?, Never>(nil) @@ -29,6 +36,7 @@ final class ShareViewModel { let isBusy = CurrentValueSubject(true) let isValid = CurrentValueSubject(false) let composeViewModel = ComposeViewModel() + let characterCount = CurrentValueSubject(0) init() { viewDidAppear.receive(on: DispatchQueue.main) @@ -40,15 +48,63 @@ final class ShareViewModel { } .store(in: &disposeBag) + Publishers.CombineLatest( + inputItems.removeDuplicates(), + viewDidAppear.removeDuplicates() + ) + .receive(on: DispatchQueue.main) + .sink { [weak self] inputItems, _ in + guard let self = self else { return } + self.parse(inputItems: inputItems) + } + .store(in: &disposeBag) + authentication .map { result in result == nil } .assign(to: \.value, on: isFetchAuthentication) .store(in: &disposeBag) + authentication + .compactMap { result -> Bool? in + guard let result = result else { return nil } + switch result { + case .success(let authentication): + return authentication.user.locked + case .failure: + return nil + } + } + .map { locked -> ComposeToolbarView.VisibilitySelectionType in + locked ? .private : .public + } + .assign(to: \.value, on: selectedStatusVisibility) + .store(in: &disposeBag) + isFetchAuthentication .receive(on: DispatchQueue.main) .assign(to: \.value, on: isBusy) .store(in: &disposeBag) + + composeViewModel.statusPlaceholder = L10n.Scene.Compose.contentInputPlaceholder + composeViewModel.contentWarningPlaceholder = L10n.Scene.Compose.ContentWarning.placeholder + composeViewModel.toolbarHeight = ComposeToolbarView.toolbarHeight + + setupBackgroundColor(theme: ThemeService.shared.currentTheme.value) + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.main) + .sink { [weak self] theme in + guard let self = self else { return } + self.setupBackgroundColor(theme: theme) + } + .store(in: &disposeBag) + + composeViewModel.$characterCount + .assign(to: \.value, on: characterCount) + .store(in: &disposeBag) + } + + private func setupBackgroundColor(theme: Theme) { + composeViewModel.contentWarningBackgroundColor = Color(theme.contentWarningOverlayBackgroundColor) } } @@ -99,3 +155,32 @@ extension ShareViewModel { } } } + +extension ShareViewModel { + func parse(inputItems: [NSExtensionItem]) { + var itemProviders: [NSItemProvider] = [] + + for item in inputItems { + itemProviders.append(contentsOf: item.attachments ?? []) + } + + let _movieProvider = itemProviders.first(where: { provider in + return provider.hasRepresentationConforming(toTypeIdentifier: UTType.movie.identifier, fileOptions: []) + }) + + let imageProviders = itemProviders.filter { provider in + return provider.hasRepresentationConforming(toTypeIdentifier: UTType.image.identifier, fileOptions: []) + } + + if let movieProvider = _movieProvider { + composeViewModel.setupAttachmentViewModels([ + StatusAttachmentViewModel(itemProvider: movieProvider) + ]) + } else { + let viewModels = imageProviders.map { provider in + StatusAttachmentViewModel(itemProvider: provider) + } + composeViewModel.setupAttachmentViewModels(viewModels) + } + } +} diff --git a/ShareActionExtension/Scene/View/ComposeToolbarView.swift b/ShareActionExtension/Scene/View/ComposeToolbarView.swift new file mode 100644 index 000000000..e938ebfa2 --- /dev/null +++ b/ShareActionExtension/Scene/View/ComposeToolbarView.swift @@ -0,0 +1,246 @@ +// +// ComposeToolbarView.swift +// ShareActionExtension +// +// Created by MainasuK Cirno on 2021-7-19. +// + +import os.log +import UIKit +import Combine +import MastodonSDK +import MastodonUI + +protocol ComposeToolbarViewDelegate: AnyObject { + func composeToolbarView(_ composeToolbarView: ComposeToolbarView, contentWarningButtonDidPressed sender: UIButton) + func composeToolbarView(_ composeToolbarView: ComposeToolbarView, visibilityButtonDidPressed sender: UIButton, visibilitySelectionType type: ComposeToolbarView.VisibilitySelectionType) +} + +final class ComposeToolbarView: UIView { + + var disposeBag = Set() + + static let toolbarButtonSize: CGSize = CGSize(width: 44, height: 44) + static let toolbarHeight: CGFloat = 44 + + weak var delegate: ComposeToolbarViewDelegate? + + let contentWarningButton: UIButton = { + let button = HighlightDimmableButton() + ComposeToolbarView.configureToolbarButtonAppearance(button: button) + button.setImage(UIImage(systemName: "exclamationmark.shield", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular)), for: .normal) + button.accessibilityLabel = L10n.Scene.Compose.Accessibility.enableContentWarning + return button + }() + + let visibilityButton: UIButton = { + let button = HighlightDimmableButton() + ComposeToolbarView.configureToolbarButtonAppearance(button: button) + button.setImage(UIImage(systemName: "person.3", withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .medium)), for: .normal) + button.accessibilityLabel = L10n.Scene.Compose.Accessibility.postVisibilityMenu + return button + }() + + let characterCountLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 15, weight: .regular) + label.text = "500" + label.textColor = Asset.Colors.Label.secondary.color + label.accessibilityLabel = L10n.Scene.Compose.Accessibility.inputLimitRemainsCount(500) + return label + }() + + let activeVisibilityType = CurrentValueSubject(.public) + + override init(frame: CGRect) { + super.init(frame: frame) + _init() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension ComposeToolbarView { + + private func _init() { + backgroundColor = Asset.Scene.Compose.toolbarBackground.color + + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.spacing = 0 + stackView.distribution = .fillEqually + stackView.translatesAutoresizingMaskIntoConstraints = false + addSubview(stackView) + NSLayoutConstraint.activate([ + stackView.centerYAnchor.constraint(equalTo: centerYAnchor), + layoutMarginsGuide.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: 8), // tweak button margin offset + ]) + + let buttons = [ + contentWarningButton, + visibilityButton, + ] + buttons.forEach { button in + button.translatesAutoresizingMaskIntoConstraints = false + stackView.addArrangedSubview(button) + NSLayoutConstraint.activate([ + button.widthAnchor.constraint(equalToConstant: 44), + button.heightAnchor.constraint(equalToConstant: 44), + ]) + } + + characterCountLabel.translatesAutoresizingMaskIntoConstraints = false + addSubview(characterCountLabel) + NSLayoutConstraint.activate([ + characterCountLabel.topAnchor.constraint(equalTo: topAnchor), + characterCountLabel.leadingAnchor.constraint(greaterThanOrEqualTo: stackView.trailingAnchor, constant: 8), + characterCountLabel.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), + characterCountLabel.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + characterCountLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) + + contentWarningButton.addTarget(self, action: #selector(ComposeToolbarView.contentWarningButtonDidPressed(_:)), for: .touchUpInside) + visibilityButton.menu = createVisibilityContextMenu(interfaceStyle: traitCollection.userInterfaceStyle) + visibilityButton.showsMenuAsPrimaryAction = true + + updateToolbarButtonUserInterfaceStyle() + + // update menu when selected visibility type changed + activeVisibilityType + .receive(on: RunLoop.main) + .sink { [weak self] type in + guard let self = self else { return } + self.visibilityButton.menu = self.createVisibilityContextMenu(interfaceStyle: self.traitCollection.userInterfaceStyle) + } + .store(in: &disposeBag) + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + updateToolbarButtonUserInterfaceStyle() + } + +} + +extension ComposeToolbarView { + enum MediaSelectionType: String { + case camera + case photoLibrary + case browse + } + + enum VisibilitySelectionType: String, CaseIterable { + case `public` + // TODO: remove unlisted option from codebase + // case unlisted + case `private` + case direct + + var title: String { + switch self { + case .public: return L10n.Scene.Compose.Visibility.public + // case .unlisted: return L10n.Scene.Compose.Visibility.unlisted + case .private: return L10n.Scene.Compose.Visibility.private + case .direct: return L10n.Scene.Compose.Visibility.direct + } + } + + func image(interfaceStyle: UIUserInterfaceStyle) -> UIImage { + switch self { + case .public: return UIImage(systemName: "globe", withConfiguration: UIImage.SymbolConfiguration(pointSize: 19, weight: .medium))! + // case .unlisted: return UIImage(systemName: "eye.slash", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular))! + case .private: + switch interfaceStyle { + case .light: return UIImage(systemName: "person.3", withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .medium))! + default: return UIImage(systemName: "person.3.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .medium))! + } + case .direct: return UIImage(systemName: "at", withConfiguration: UIImage.SymbolConfiguration(pointSize: 19, weight: .regular))! + } + } + + var visibility: Mastodon.Entity.Status.Visibility { + switch self { + case .public: return .public + // case .unlisted: return .unlisted + case .private: return .private + case .direct: return .direct + } + } + } +} + +extension ComposeToolbarView { + + private static func configureToolbarButtonAppearance(button: UIButton) { + button.tintColor = Asset.Colors.brandBlue.color + button.setBackgroundImage(.placeholder(size: ComposeToolbarView.toolbarButtonSize, color: .systemFill), for: .highlighted) + button.layer.masksToBounds = true + button.layer.cornerRadius = 5 + button.layer.cornerCurve = .continuous + } + + private func updateToolbarButtonUserInterfaceStyle() { + switch traitCollection.userInterfaceStyle { + case .light: + contentWarningButton.setImage(UIImage(systemName: "exclamationmark.shield", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular))!, for: .normal) + + case .dark: + contentWarningButton.setImage(UIImage(systemName: "exclamationmark.shield.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular))!, for: .normal) + + default: + assertionFailure() + } + + visibilityButton.menu = createVisibilityContextMenu(interfaceStyle: traitCollection.userInterfaceStyle) + } + + private func createVisibilityContextMenu(interfaceStyle: UIUserInterfaceStyle) -> UIMenu { + let children: [UIMenuElement] = VisibilitySelectionType.allCases.map { type in + let state: UIMenuElement.State = activeVisibilityType.value == type ? .on : .off + return UIAction(title: type.title, image: type.image(interfaceStyle: interfaceStyle), identifier: nil, discoverabilityTitle: nil, attributes: [], state: state) { [weak self] action in + guard let self = self else { return } + os_log(.info, "%{public}s[%{public}ld], %{public}s: visibilitySelectionType: %s", ((#file as NSString).lastPathComponent), #line, #function, type.rawValue) + self.delegate?.composeToolbarView(self, visibilityButtonDidPressed: self.visibilityButton, visibilitySelectionType: type) + } + } + return UIMenu(title: "", image: nil, identifier: nil, options: .displayInline, children: children) + } + +} + +extension ComposeToolbarView { + + @objc private func contentWarningButtonDidPressed(_ sender: UIButton) { + os_log(.info, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + delegate?.composeToolbarView(self, contentWarningButtonDidPressed: sender) + } + +} + +#if canImport(SwiftUI) && DEBUG +import SwiftUI + +struct ComposeToolbarView_Previews: PreviewProvider { + + static var previews: some View { + UIViewPreview(width: 375) { + let toolbarView = ComposeToolbarView() + toolbarView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + toolbarView.widthAnchor.constraint(equalToConstant: 375).priority(.defaultHigh), + toolbarView.heightAnchor.constraint(equalToConstant: 64).priority(.defaultHigh), + ]) + return toolbarView + } + .previewLayout(.fixed(width: 375, height: 100)) + } + +} + +#endif + diff --git a/ShareActionExtension/Scene/View/ComposeView.swift b/ShareActionExtension/Scene/View/ComposeView.swift new file mode 100644 index 000000000..65b30913a --- /dev/null +++ b/ShareActionExtension/Scene/View/ComposeView.swift @@ -0,0 +1,117 @@ +// +// ComposeView.swift +// +// +// Created by MainasuK Cirno on 2021-7-16. +// + +import UIKit +import SwiftUI + +public struct ComposeView: View { + + @EnvironmentObject var viewModel: ComposeViewModel + @State var statusEditorViewWidth: CGFloat = .zero + + let horizontalMargin: CGFloat = 20 + + public init() { } + + public var body: some View { + GeometryReader { proxy in + List { + // Content Warning + if viewModel.isContentWarningComposing { + ContentWarningEditorView( + contentWarningContent: $viewModel.contentWarningContent, + placeholder: viewModel.contentWarningPlaceholder + ) + .padding(EdgeInsets(top: 6, leading: horizontalMargin, bottom: 6, trailing: horizontalMargin)) + .background(viewModel.contentWarningBackgroundColor) + .transition(.opacity) + .listRow() + } + + // Author + StatusAuthorView( + avatarImageURL: viewModel.avatarImageURL, + name: viewModel.authorName, + username: viewModel.authorUsername + ) + .padding(EdgeInsets(top: 20, leading: horizontalMargin, bottom: 16, trailing: horizontalMargin)) + .listRow() + + // Editor + StatusEditorView( + string: $viewModel.statusContent, + placeholder: viewModel.statusPlaceholder, + width: statusEditorViewWidth, + attributedString: viewModel.statusContentAttributedString, + keyboardType: .twitter + ) + .frame(width: statusEditorViewWidth) + .frame(minHeight: 100) + .padding(EdgeInsets(top: 0, leading: horizontalMargin, bottom: 0, trailing: horizontalMargin)) + .listRow() + + // Attachments + ForEach(viewModel.attachmentViewModels) { viewModel in + StatusAttachmentView( + image: viewModel.thumbnailImage, + removeButtonAction: { + self.viewModel.removeAttachmentViewModel(viewModel) + } + ) + } + .padding(EdgeInsets(top: 16, leading: horizontalMargin, bottom: 0, trailing: horizontalMargin)) + .fixedSize(horizontal: false, vertical: true) + .listRow() + + // bottom padding + Color.clear + .frame(height: viewModel.toolbarHeight + 20) + .listRow() + } // end List + .introspectTableView(customize: { tableView in + tableView.keyboardDismissMode = .onDrag + tableView.verticalScrollIndicatorInsets.bottom = viewModel.toolbarHeight + }) + .preference( + key: ComposeListViewFramePreferenceKey.self, + value: proxy.frame(in: .local) + ) + .onPreferenceChange(ComposeListViewFramePreferenceKey.self) { frame in + var frame = frame + frame.size.width = frame.width - 2 * horizontalMargin + statusEditorViewWidth = frame.width + } + } + } +} + +struct ComposeListViewFramePreferenceKey: PreferenceKey { + static var defaultValue: CGRect = .zero + static func reduce(value: inout CGRect, nextValue: () -> CGRect) { } +} + +extension View { + func listRow() -> some View { + self.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) + .listRowInsets(EdgeInsets(top: -1, leading: -1, bottom: -1, trailing: -1)) + .background(Color(.systemBackground)) + } +} + + +struct ComposeView_Previews: PreviewProvider { + + static let viewModel: ComposeViewModel = { + let viewModel = ComposeViewModel() + return viewModel + }() + + static var previews: some View { + ComposeView().environmentObject(viewModel) + } + +} diff --git a/ShareActionExtension/Scene/View/ComposeViewModel.swift b/ShareActionExtension/Scene/View/ComposeViewModel.swift new file mode 100644 index 000000000..06db193d3 --- /dev/null +++ b/ShareActionExtension/Scene/View/ComposeViewModel.swift @@ -0,0 +1,80 @@ +// +// ComposeViewModel.swift +// ShareActionExtension +// +// Created by MainasuK Cirno on 2021-7-16. +// + +import Foundation +import SwiftUI +import Combine + +class ComposeViewModel: ObservableObject { + + var disposeBag = Set() + + @Published var toolbarHeight: CGFloat = 0 + + @Published var avatarImageURL: URL? + @Published var authorName: String = "" + @Published var authorUsername: String = "" + + @Published var statusContent = "" + @Published var statusPlaceholder = "" + @Published var statusContentAttributedString = NSAttributedString() + + @Published var isContentWarningComposing = false + @Published var contentWarningBackgroundColor = Color.secondary + @Published var contentWarningPlaceholder = "" + @Published var contentWarningContent = "" + + @Published private(set) var attachmentViewModels: [StatusAttachmentViewModel] = [] + + @Published var characterCount = 0 + + public init() { + $statusContent + .map { NSAttributedString(string: $0) } + .assign(to: &$statusContentAttributedString) + + Publishers.CombineLatest3( + $statusContent, + $isContentWarningComposing, + $contentWarningContent + ) + .map { statusContent, isContentWarningComposing, contentWarningContent in + var count = statusContent.count + if isContentWarningComposing { + count += contentWarningContent.count + } + return count + } + .assign(to: &$characterCount) + + #if DEBUG + avatarImageURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif") + authorName = "Alice" + authorUsername = "alice" + #endif + } + +} + +extension ComposeViewModel { + func setupAttachmentViewModels(_ viewModels: [StatusAttachmentViewModel]) { + attachmentViewModels = viewModels + for viewModel in viewModels { + viewModel.objectWillChange.sink { [weak self] _ in + guard let self = self else { return } + self.objectWillChange.send() + } + .store(in: &viewModel.disposeBag) + } + } + + func removeAttachmentViewModel(_ viewModel: StatusAttachmentViewModel) { + if let index = attachmentViewModels.firstIndex(where: { $0 === viewModel }) { + attachmentViewModels.remove(at: index) + } + } +} diff --git a/ShareActionExtension/Scene/View/ContentWarningEditorView.swift b/ShareActionExtension/Scene/View/ContentWarningEditorView.swift new file mode 100644 index 000000000..833c919fc --- /dev/null +++ b/ShareActionExtension/Scene/View/ContentWarningEditorView.swift @@ -0,0 +1,48 @@ +// +// ContentWarningEditorView.swift +// +// +// Created by MainasuK Cirno on 2021-7-19. +// + +import SwiftUI +import Introspect + +struct ContentWarningEditorView: View { + + @Binding var contentWarningContent: String + let placeholder: String + let spacing: CGFloat = 11 + + var body: some View { + HStack(alignment: .center, spacing: spacing) { + Image(systemName: "exclamationmark.shield") + .font(.system(size: 30, weight: .regular)) + Text(contentWarningContent.isEmpty ? " " : contentWarningContent) + .opacity(0) + .padding(.all, 8) + .frame(maxWidth: .infinity) + .overlay( + TextEditor(text: $contentWarningContent) + .introspectTextView { textView in + textView.backgroundColor = .clear + textView.placeholder = placeholder + } + ) + } + } +} + +struct ContentWarningEditorView_Previews: PreviewProvider { + + @State static var content = "" + + static var previews: some View { + ContentWarningEditorView( + contentWarningContent: $content, + placeholder: "Write an accurate warning here..." + ) + .previewLayout(.fixed(width: 375, height: 100)) + } +} + diff --git a/ShareActionExtension/Scene/View/StatusAttachmentView.swift b/ShareActionExtension/Scene/View/StatusAttachmentView.swift new file mode 100644 index 000000000..0f52afebb --- /dev/null +++ b/ShareActionExtension/Scene/View/StatusAttachmentView.swift @@ -0,0 +1,65 @@ +// +// StatusAttachmentView.swift +// +// +// Created by MainasuK Cirno on 2021-7-19. +// + +import SwiftUI + +struct StatusAttachmentView: View { + + let image: UIImage? + let removeButtonAction: () -> Void + + var body: some View { + let image = image ?? UIImage.placeholder(color: .systemFill) + Color.clear + .aspectRatio(CGSize(width: 16, height: 9), contentMode: .fill) + .overlay( + Image(uiImage: image) + .resizable() + .aspectRatio(contentMode: .fill) + ) + .background(Color.gray) + .cornerRadius(4) + .badgeView( + Button(action: { + removeButtonAction() + }, label: { + Image(systemName: "minus.circle.fill") + .renderingMode(.original) + .font(.system(size: 22, weight: .bold, design: .default)) + }) + .buttonStyle(BorderlessButtonStyle()) + ) + } +} + +extension View { + func badgeView(_ content: Content) -> some View where Content: View { + overlay( + ZStack { + content + } + .alignmentGuide(.top) { $0.height / 2 } + .alignmentGuide(.trailing) { $0.width / 2 } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing) + ) + } +} + + +struct StatusAttachmentView_Previews: PreviewProvider { + static var previews: some View { + ScrollView { + StatusAttachmentView( + image: UIImage(systemName: "photo"), + removeButtonAction: { + // do nothing + } + ) + .padding(20) + } + } +} diff --git a/ShareActionExtension/Scene/View/StatusAttachmentViewModel.swift b/ShareActionExtension/Scene/View/StatusAttachmentViewModel.swift new file mode 100644 index 000000000..ba6a4d51f --- /dev/null +++ b/ShareActionExtension/Scene/View/StatusAttachmentViewModel.swift @@ -0,0 +1,104 @@ +// +// StatusAttachmentViewModel.swift +// ShareActionExtension +// +// Created by MainasuK Cirno on 2021-7-19. +// + +import os.log +import Foundation +import SwiftUI +import Combine +import MastodonSDK +import MastodonUI +import AVFoundation +import MobileCoreServices +import UniformTypeIdentifiers + +final class StatusAttachmentViewModel: ObservableObject, Identifiable { + + let logger = Logger(subsystem: "StatusAttachmentViewModel", category: "logic") + + var disposeBag = Set() + + let id = UUID() + let itemProvider: NSItemProvider + + // input + let file = CurrentValueSubject(nil) + @Published var description = "" + + // output + @Published var thumbnailImage: UIImage? + @Published var error: Error? + + init(itemProvider: NSItemProvider) { + self.itemProvider = itemProvider + + Just(itemProvider) + .receive(on: DispatchQueue.main) + .flatMap { result -> AnyPublisher in + if itemProvider.hasRepresentationConforming(toTypeIdentifier: UTType.image.identifier, fileOptions: []) { + return ItemProviderLoader.loadImageData(from: result).eraseToAnyPublisher() + } + if itemProvider.hasRepresentationConforming(toTypeIdentifier: UTType.movie.identifier, fileOptions: []) { + return ItemProviderLoader.loadVideoData(from: result).eraseToAnyPublisher() + } + return Fail(error: AttachmentError.invalidAttachmentType).eraseToAnyPublisher() + } + .sink { [weak self] completion in + guard let self = self else { return } + switch completion { + case .failure(let error): + self.error = error +// self.uploadStateMachine.enter(UploadState.Fail.self) + case .finished: + break + } + } receiveValue: { [weak self] file in + guard let self = self else { return } + self.file.value = file +// self.uploadStateMachine.enter(UploadState.Initial.self) + } + .store(in: &disposeBag) + + + file + .receive(on: DispatchQueue.main) + .map { file -> UIImage? in + guard let file = file else { + return nil + } + + switch file { + case .jpeg(let data), .png(let data): + return data.flatMap { UIImage(data: $0) } + case .gif: + // TODO: + return nil + case .other(let url, _, _): + guard let url = url, FileManager.default.fileExists(atPath: url.path) else { return nil } + let asset = AVURLAsset(url: url) + let assetImageGenerator = AVAssetImageGenerator(asset: asset) + assetImageGenerator.appliesPreferredTrackTransform = true // fix orientation + do { + let cgImage = try assetImageGenerator.copyCGImage(at: CMTimeMake(value: 0, timescale: 1), actualTime: nil) + let image = UIImage(cgImage: cgImage) + return image + } catch { + self.logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): thumbnail generate fail: \(error.localizedDescription)") + return nil + } + } + } + .assign(to: &$thumbnailImage) + } + +} + +extension StatusAttachmentViewModel { + enum AttachmentError: Error { + case invalidAttachmentType + case attachmentTooLarge + } +} diff --git a/MastodonSDK/Sources/MastodonUI/Compose/StatusAuthorView.swift b/ShareActionExtension/Scene/View/StatusAuthorView.swift similarity index 98% rename from MastodonSDK/Sources/MastodonUI/Compose/StatusAuthorView.swift rename to ShareActionExtension/Scene/View/StatusAuthorView.swift index 110dfb7f6..c729bb797 100644 --- a/MastodonSDK/Sources/MastodonUI/Compose/StatusAuthorView.swift +++ b/ShareActionExtension/Scene/View/StatusAuthorView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import MastodonUI import Nuke import NukeFLAnimatedImagePlugin import FLAnimatedImage diff --git a/MastodonSDK/Sources/MastodonUI/Compose/TextEditorView.swift b/ShareActionExtension/Scene/View/StatusEditorView.swift similarity index 77% rename from MastodonSDK/Sources/MastodonUI/Compose/TextEditorView.swift rename to ShareActionExtension/Scene/View/StatusEditorView.swift index 4f087f6b4..e69448832 100644 --- a/MastodonSDK/Sources/MastodonUI/Compose/TextEditorView.swift +++ b/ShareActionExtension/Scene/View/StatusEditorView.swift @@ -1,45 +1,50 @@ // -// TextEditorView.swift -// +// StatusEditorView.swift +// // // Created by MainasuK Cirno on 2021-7-16. // import UIKit import SwiftUI +import UITextView_Placeholder -public struct TextEditorView: UIViewRepresentable { +public struct StatusEditorView: UIViewRepresentable { @Binding var string: String - + let placeholder: String let width: CGFloat let attributedString: NSAttributedString + let keyboardType: UIKeyboardType public init( string: Binding, + placeholder: String, width: CGFloat, - attributedString: NSAttributedString + attributedString: NSAttributedString, + keyboardType: UIKeyboardType ) { self._string = string + self.placeholder = placeholder self.width = width self.attributedString = attributedString + self.keyboardType = keyboardType } public func makeUIView(context: Context) -> UITextView { let textView = UITextView(frame: .zero) + textView.placeholder = placeholder textView.isScrollEnabled = false textView.font = .preferredFont(forTextStyle: .body) textView.textColor = .label - + textView.keyboardType = keyboardType textView.delegate = context.coordinator textView.translatesAutoresizingMaskIntoConstraints = false let widthLayoutConstraint = textView.widthAnchor.constraint(equalToConstant: 100) widthLayoutConstraint.priority = .required - 1 context.coordinator.widthLayoutConstraint = widthLayoutConstraint - - return textView } @@ -57,10 +62,10 @@ public struct TextEditorView: UIViewRepresentable { } public class Coordinator: NSObject, UITextViewDelegate { - var parent: TextEditorView + var parent: StatusEditorView var widthLayoutConstraint: NSLayoutConstraint? - init(_ parent: TextEditorView) { + init(_ parent: StatusEditorView) { self.parent = parent } diff --git a/ShareActionExtension/ShareActionExtension.entitlements b/ShareActionExtension/ShareActionExtension.entitlements new file mode 100644 index 000000000..c3bc3f816 --- /dev/null +++ b/ShareActionExtension/ShareActionExtension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.org.joinmastodon.app + + + diff --git a/ShareActionExtension/ShareViewController.swift b/ShareActionExtension/ShareViewController.swift deleted file mode 100644 index e44e3f9f5..000000000 --- a/ShareActionExtension/ShareViewController.swift +++ /dev/null @@ -1,107 +0,0 @@ -// -// ShareViewController.swift -// MastodonShareAction -// -// Created by MainasuK Cirno on 2021-7-16. -// - -import os.log -import UIKit -import Combine -import MastodonUI -import SwiftUI - -class ShareViewController: UIViewController { - - let logger = Logger(subsystem: "ShareViewController", category: "UI") - - var disposeBag = Set() - let viewModel = ShareViewModel() - - let publishButton: UIButton = { - let button = RoundedEdgesButton(type: .custom) - button.setTitle(L10n.Scene.Compose.composeAction, for: .normal) - button.titleLabel?.font = .systemFont(ofSize: 14, weight: .bold) - button.setBackgroundImage(.placeholder(color: Asset.Colors.brandBlue.color), for: .normal) - button.setBackgroundImage(.placeholder(color: Asset.Colors.brandBlue.color.withAlphaComponent(0.5)), for: .highlighted) - button.setBackgroundImage(.placeholder(color: Asset.Colors.Button.disabled.color), for: .disabled) - button.setTitleColor(.white, for: .normal) - button.contentEdgeInsets = UIEdgeInsets(top: 6, left: 16, bottom: 5, right: 16) // set 28pt height - button.adjustsImageWhenHighlighted = false - return button - }() - - private(set) lazy var cancelBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .plain, target: self, action: #selector(ShareViewController.cancelBarButtonItemPressed(_:))) - private(set) lazy var publishBarButtonItem: UIBarButtonItem = { - let barButtonItem = UIBarButtonItem(customView: publishButton) - barButtonItem.target = self - barButtonItem.action = #selector(ShareViewController.publishBarButtonItemPressed(_:)) - return barButtonItem - }() - - let activityIndicatorBarButtonItem: UIBarButtonItem = { - let indicatorView = UIActivityIndicatorView(style: .medium) - let barButtonItem = UIBarButtonItem(customView: indicatorView) - indicatorView.startAnimating() - return barButtonItem - }() - -} - -extension ShareViewController { - - override func viewDidLoad() { - super.viewDidLoad() - - view.backgroundColor = Asset.Colors.Background.systemBackground.color - - navigationItem.leftBarButtonItem = cancelBarButtonItem - viewModel.isBusy - .receive(on: DispatchQueue.main) - .sink { [weak self] isBusy in - guard let self = self else { return } - self.navigationItem.rightBarButtonItem = isBusy ? self.activityIndicatorBarButtonItem : self.publishBarButtonItem - } - .store(in: &disposeBag) - - let hostingViewController = UIHostingController( - rootView: ComposeView().environmentObject(viewModel.composeViewModel) - ) - addChild(hostingViewController) - view.addSubview(hostingViewController.view) - hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(hostingViewController.view) - NSLayoutConstraint.activate([ - hostingViewController.view.topAnchor.constraint(equalTo: view.topAnchor), - hostingViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - hostingViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), - hostingViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) - hostingViewController.didMove(toParent: self) - -// viewModel.authentication -// .receive(on: DispatchQueue.main) -// .sink { [weak self] result in -// guard let self = self else { return } -// } - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - viewModel.viewDidAppear.value = true -// extensionContext - } - -} - -extension ShareViewController { - @objc private func cancelBarButtonItemPressed(_ sender: UIBarButtonItem) { - logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") - extensionContext?.cancelRequest(withError: ShareViewModel.ShareError.userCancelShare) - } - - @objc private func publishBarButtonItemPressed(_ sender: UIBarButtonItem) { - logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") - } -}