chore: [WIP] restore the replyTo entry for compose
This commit is contained in:
parent
56f04db40f
commit
02e3ad9a16
|
@ -28,7 +28,6 @@
|
|||
2D206B8625F5FB0900143C56 /* Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B8525F5FB0900143C56 /* Double.swift */; };
|
||||
2D206B9225F60EA700143C56 /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B9125F60EA700143C56 /* UIControl.swift */; };
|
||||
2D24E1232626ED9D00A59D4F /* UIView+Gesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D24E1222626ED9D00A59D4F /* UIView+Gesture.swift */; };
|
||||
2D32EAAC25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */; };
|
||||
2D35237A26256D920031AF25 /* NotificationSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D35237926256D920031AF25 /* NotificationSection.swift */; };
|
||||
2D364F7225E66D7500204FDC /* MastodonResendEmailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D364F7125E66D7500204FDC /* MastodonResendEmailViewController.swift */; };
|
||||
2D364F7825E66D8300204FDC /* MastodonResendEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D364F7725E66D8300204FDC /* MastodonResendEmailViewModel.swift */; };
|
||||
|
@ -44,12 +43,10 @@
|
|||
2D571B2F26004EC000540450 /* NavigationBarProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D571B2E26004EC000540450 /* NavigationBarProgressView.swift */; };
|
||||
2D59819B25E4A581000FB903 /* MastodonConfirmEmailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D59819A25E4A581000FB903 /* MastodonConfirmEmailViewController.swift */; };
|
||||
2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5981A025E4A593000FB903 /* MastodonConfirmEmailViewModel.swift */; };
|
||||
2D5A3D0325CF8742002347D6 /* ControlContainableScrollViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5A3D0225CF8742002347D6 /* ControlContainableScrollViews.swift */; };
|
||||
2D5A3D2825CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5A3D2725CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift */; };
|
||||
2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5A3D3725CF8D9F002347D6 /* ScrollViewContainer.swift */; };
|
||||
2D5A3D6225CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5A3D6125CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift */; };
|
||||
2D607AD826242FC500B70763 /* NotificationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D607AD726242FC500B70763 /* NotificationViewModel.swift */; };
|
||||
2D650FAB25ECDC9300851B58 /* Mastodon+Entity+Error+Detail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D650FAA25ECDC9300851B58 /* Mastodon+Entity+Error+Detail.swift */; };
|
||||
2D694A7425F9EB4E0038ADDC /* ContentWarningOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D694A7325F9EB4E0038ADDC /* ContentWarningOverlayView.swift */; };
|
||||
2D6DE40026141DF600A63F6A /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D6DE3FF26141DF600A63F6A /* SearchViewModel.swift */; };
|
||||
2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76319E25C1521200929FB9 /* StatusSection.swift */; };
|
||||
|
@ -62,13 +59,9 @@
|
|||
2D84350525FF858100EECE90 /* UIScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D84350425FF858100EECE90 /* UIScrollView.swift */; };
|
||||
2D939AB525EDD8A90076FA61 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AB425EDD8A90076FA61 /* String.swift */; };
|
||||
2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AE725EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift */; };
|
||||
2DA504692601ADE7008F4E6C /* SawToothView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA504682601ADE7008F4E6C /* SawToothView.swift */; };
|
||||
2DA7D04425CA52B200804E11 /* TimelineLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA7D04325CA52B200804E11 /* TimelineLoaderTableViewCell.swift */; };
|
||||
2DA7D04A25CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA7D04925CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift */; };
|
||||
2DAC9E38262FC2320062E1A6 /* SuggestionAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DAC9E37262FC2320062E1A6 /* SuggestionAccountViewController.swift */; };
|
||||
2DAC9E3E262FC2400062E1A6 /* SuggestionAccountViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DAC9E3D262FC2400062E1A6 /* SuggestionAccountViewModel.swift */; };
|
||||
2DAC9E46262FC9FD0062E1A6 /* SuggestionAccountTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DAC9E45262FC9FD0062E1A6 /* SuggestionAccountTableViewCell.swift */; };
|
||||
2DB72C8C262D764300CE6173 /* Mastodon+Entity+Notification+Type.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DB72C8B262D764300CE6173 /* Mastodon+Entity+Notification+Type.swift */; };
|
||||
2DCB73FD2615C13900EC03D4 /* SearchRecommendCollectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DCB73FC2615C13900EC03D4 /* SearchRecommendCollectionHeader.swift */; };
|
||||
2DE0FACE2615F7AD00CDF649 /* RecommendAccountSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DE0FACD2615F7AD00CDF649 /* RecommendAccountSection.swift */; };
|
||||
2DF123A725C3B0210020F248 /* ActiveLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF123A625C3B0210020F248 /* ActiveLabel.swift */; };
|
||||
|
@ -84,9 +77,6 @@
|
|||
5D0393902612D259007FE196 /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D03938F2612D259007FE196 /* WebViewController.swift */; };
|
||||
5D0393962612D266007FE196 /* WebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D0393952612D266007FE196 /* WebViewModel.swift */; };
|
||||
5DA732CC2629CEF500A92342 /* UIView+Remove.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DA732CB2629CEF500A92342 /* UIView+Remove.swift */; };
|
||||
5DDDF1932617442700311060 /* Mastodon+Entity+Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DDDF1922617442700311060 /* Mastodon+Entity+Account.swift */; };
|
||||
5DDDF1992617447F00311060 /* Mastodon+Entity+Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DDDF1982617447F00311060 /* Mastodon+Entity+Tag.swift */; };
|
||||
5DDDF1A92617489F00311060 /* Mastodon+Entity+History.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DDDF1A82617489F00311060 /* Mastodon+Entity+History.swift */; };
|
||||
5DF1056425F887CB00D6C0D4 /* AVPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF1056325F887CB00D6C0D4 /* AVPlayer.swift */; };
|
||||
5E0DEC05797A7E6933788DDB /* Pods_MastodonTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 452147B2903DF38070FE56A2 /* Pods_MastodonTests.framework */; };
|
||||
5E44BF88AD33646E64727BCF /* Pods_MastodonTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */; };
|
||||
|
@ -110,8 +100,6 @@
|
|||
DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */; };
|
||||
DB03A793272A7E5700EE37C5 /* SidebarListHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03A792272A7E5700EE37C5 /* SidebarListHeaderView.swift */; };
|
||||
DB03A795272A981400EE37C5 /* ContentSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03A794272A981400EE37C5 /* ContentSplitViewController.swift */; };
|
||||
DB03F7F32689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7F22689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift */; };
|
||||
DB03F7F52689B782007B274C /* ComposeTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7F42689B782007B274C /* ComposeTableView.swift */; };
|
||||
DB0617EB277EF3820030EE79 /* GradientBorderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0617EA277EF3820030EE79 /* GradientBorderView.swift */; };
|
||||
DB0617ED277F02C50030EE79 /* OnboardingNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0617EC277F02C50030EE79 /* OnboardingNavigationController.swift */; };
|
||||
DB0617EF277F12720030EE79 /* NavigationActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0617EE277F12720030EE79 /* NavigationActionView.swift */; };
|
||||
|
@ -130,8 +118,6 @@
|
|||
DB0F9D54283EB3C000379AF8 /* ProfileHeaderView+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0F9D53283EB3C000379AF8 /* ProfileHeaderView+ViewModel.swift */; };
|
||||
DB0F9D56283EB46200379AF8 /* ProfileHeaderView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0F9D55283EB46200379AF8 /* ProfileHeaderView+Configuration.swift */; };
|
||||
DB0FCB68279507EF006C02E2 /* DataSourceFacade+Meta.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0FCB67279507EF006C02E2 /* DataSourceFacade+Meta.swift */; };
|
||||
DB0FCB7027951368006C02E2 /* TimelineMiddleLoaderTableViewCell+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0FCB6F27951368006C02E2 /* TimelineMiddleLoaderTableViewCell+ViewModel.swift */; };
|
||||
DB0FCB7227952986006C02E2 /* NamingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0FCB7127952986006C02E2 /* NamingState.swift */; };
|
||||
DB0FCB7427956939006C02E2 /* DataSourceFacade+Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0FCB7327956939006C02E2 /* DataSourceFacade+Status.swift */; };
|
||||
DB0FCB76279571C5006C02E2 /* ThreadViewController+DataSourceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0FCB75279571C5006C02E2 /* ThreadViewController+DataSourceProvider.swift */; };
|
||||
DB0FCB7827957678006C02E2 /* DataSourceProvider+UITableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0FCB7727957678006C02E2 /* DataSourceProvider+UITableViewDelegate.swift */; };
|
||||
|
@ -174,14 +160,6 @@
|
|||
DB2F073525E8ECF000957B2D /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */; };
|
||||
DB2FF510260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2FF50F260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift */; };
|
||||
DB336F3F278E668C0031E64B /* StatusTableViewCell+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB336F3E278E668C0031E64B /* StatusTableViewCell+ViewModel.swift */; };
|
||||
DB336F41278E68480031E64B /* StatusView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB336F40278E68480031E64B /* StatusView+Configuration.swift */; };
|
||||
DB336F43278EB1690031E64B /* MediaView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB336F42278EB1680031E64B /* MediaView+Configuration.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 */; };
|
||||
DB3E6FDD2806A40F00B035AE /* DiscoveryHashtagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FDC2806A40F00B035AE /* DiscoveryHashtagsViewController.swift */; };
|
||||
DB3E6FE02806A4ED00B035AE /* DiscoveryHashtagsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FDF2806A4ED00B035AE /* DiscoveryHashtagsViewModel.swift */; };
|
||||
DB3E6FE22806A50100B035AE /* DiscoveryHashtagsViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FE12806A50100B035AE /* DiscoveryHashtagsViewModel+Diffable.swift */; };
|
||||
|
@ -208,8 +186,6 @@
|
|||
DB427DF825BAA00100D1B89D /* MastodonUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DF725BAA00100D1B89D /* MastodonUITests.swift */; };
|
||||
DB443CD42694627B00159B29 /* AppearanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB443CD32694627B00159B29 /* AppearanceView.swift */; };
|
||||
DB44767B260B3B8C00B66B82 /* CustomEmojiPickerInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB44767A260B3B8C00B66B82 /* CustomEmojiPickerInputView.swift */; };
|
||||
DB447681260B3ED600B66B82 /* CustomEmojiPickerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB447680260B3ED600B66B82 /* CustomEmojiPickerSection.swift */; };
|
||||
DB44768B260B3F2100B66B82 /* CustomEmojiPickerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB44768A260B3F2100B66B82 /* CustomEmojiPickerItem.swift */; };
|
||||
DB447691260B406600B66B82 /* CustomEmojiPickerItemCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB447690260B406600B66B82 /* CustomEmojiPickerItemCollectionViewCell.swift */; };
|
||||
DB447697260B439000B66B82 /* CustomEmojiPickerHeaderCollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB447696260B439000B66B82 /* CustomEmojiPickerHeaderCollectionReusableView.swift */; };
|
||||
DB4481B925EE289600BEFB67 /* UITableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4481B825EE289600BEFB67 /* UITableView.swift */; };
|
||||
|
@ -282,8 +258,6 @@
|
|||
DB64BA482851F29300ADF1B7 /* Account+Fetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB64BA472851F29300ADF1B7 /* Account+Fetch.swift */; };
|
||||
DB65C63727A2AF6C008BAC2E /* ReportItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB65C63627A2AF6C008BAC2E /* ReportItem.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 */; };
|
||||
DB6746EB278ED8B0008A6B94 /* PollOptionView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6746EA278ED8B0008A6B94 /* PollOptionView+Configuration.swift */; };
|
||||
DB6746ED278F45F0008A6B94 /* AutoGenerateProtocolRelayDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6746EC278F45F0008A6B94 /* AutoGenerateProtocolRelayDelegate.swift */; };
|
||||
DB6746F0278F463B008A6B94 /* AutoGenerateProtocolDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6746EF278F463B008A6B94 /* AutoGenerateProtocolDelegate.swift */; };
|
||||
|
@ -311,14 +285,10 @@
|
|||
DB6B74FE272FF59000C70B6E /* UserItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B74FD272FF59000C70B6E /* UserItem.swift */; };
|
||||
DB6B7500272FF73800C70B6E /* UserTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B74FF272FF73800C70B6E /* UserTableViewCell.swift */; };
|
||||
DB6B750427300B4000C70B6E /* TimelineFooterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B750327300B4000C70B6E /* TimelineFooterTableViewCell.swift */; };
|
||||
DB6C8C0F25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6C8C0E25F0A6AE00AAA452 /* Mastodon+Entity+Error.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 */; };
|
||||
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 */; };
|
||||
DB6F5E35264E78E7009108F4 /* AutoCompleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6F5E34264E78E7009108F4 /* AutoCompleteViewController.swift */; };
|
||||
DB6F5E38264E994A009108F4 /* AutoCompleteTopChevronView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6F5E37264E994A009108F4 /* AutoCompleteTopChevronView.swift */; };
|
||||
DB72601C25E36A2100235243 /* MastodonServerRulesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */; };
|
||||
DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */; };
|
||||
DB7274F4273BB9B200577D95 /* ListBatchFetchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7274F3273BB9B200577D95 /* ListBatchFetchViewModel.swift */; };
|
||||
|
@ -353,7 +323,6 @@
|
|||
DB938F0926240F3C00E5B6C1 /* RemoteThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F0826240F3C00E5B6C1 /* RemoteThreadViewModel.swift */; };
|
||||
DB938F0F2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F0E2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift */; };
|
||||
DB938F1F2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F1E2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift */; };
|
||||
DB938F3326243D6200E5B6C1 /* TimelineTopLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F3226243D6200E5B6C1 /* TimelineTopLoaderTableViewCell.swift */; };
|
||||
DB98EB4727B0DFAA0082E365 /* ReportStatusViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98EB4627B0DFAA0082E365 /* ReportStatusViewModel+State.swift */; };
|
||||
DB98EB4927B0F0CD0082E365 /* ReportStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98EB4827B0F0CD0082E365 /* ReportStatusTableViewCell.swift */; };
|
||||
DB98EB4C27B0F2BC0082E365 /* ReportStatusTableViewCell+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98EB4B27B0F2BC0082E365 /* ReportStatusTableViewCell+ViewModel.swift */; };
|
||||
|
@ -387,7 +356,6 @@
|
|||
DBA94434265CBB5300C537E1 /* ProfileFieldSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA94433265CBB5300C537E1 /* ProfileFieldSection.swift */; };
|
||||
DBA94436265CBB7400C537E1 /* ProfileFieldItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA94435265CBB7400C537E1 /* ProfileFieldItem.swift */; };
|
||||
DBA9443E265CFA6400C537E1 /* ProfileFieldCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA9443D265CFA6400C537E1 /* ProfileFieldCollectionViewCell.swift */; };
|
||||
DBA94440265D137600C537E1 /* Mastodon+Entity+Field.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA9443F265D137600C537E1 /* Mastodon+Entity+Field.swift */; };
|
||||
DBABE3EC25ECAC4B00879EE5 /* WelcomeIllustrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBABE3EB25ECAC4B00879EE5 /* WelcomeIllustrationView.swift */; };
|
||||
DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */; };
|
||||
DBB3BA2A26A81C020004F2D4 /* FLAnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */; };
|
||||
|
@ -408,20 +376,12 @@
|
|||
DBB8AB4626AECDE200F6D281 /* SendPostIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB8AB4526AECDE200F6D281 /* SendPostIntentHandler.swift */; };
|
||||
DBB8AB4F26AED13F00F6D281 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB427DDE25BAA00100D1B89D /* Assets.xcassets */; };
|
||||
DBB9759C262462E1004620BD /* ThreadMetaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB9759B262462E1004620BD /* ThreadMetaView.swift */; };
|
||||
DBBC24A826A52F9000398BB9 /* ComposeToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24A726A52F9000398BB9 /* ComposeToolbarView.swift */; };
|
||||
DBBC24AC26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24AB26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift */; };
|
||||
DBBC24DC26A54BCB00398BB9 /* MastodonRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D626A54BCB00398BB9 /* MastodonRegex.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 */; };
|
||||
DBBF1DC5265251C300E5B703 /* AutoCompleteViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DC4265251C300E5B703 /* AutoCompleteViewModel+Diffable.swift */; };
|
||||
DBBF1DC7265251D400E5B703 /* AutoCompleteViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DC6265251D400E5B703 /* AutoCompleteViewModel+State.swift */; };
|
||||
DBBF1DC92652538500E5B703 /* AutoCompleteSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DC82652538500E5B703 /* AutoCompleteSection.swift */; };
|
||||
DBBF1DCB2652539E00E5B703 /* AutoCompleteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DCA2652539E00E5B703 /* AutoCompleteItem.swift */; };
|
||||
DBC6461526A170AB00B0E31B /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC6461426A170AB00B0E31B /* ShareViewController.swift */; };
|
||||
DBC6461526A170AB00B0E31B /* ComposeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC6461426A170AB00B0E31B /* ComposeViewController.swift */; };
|
||||
DBC6461826A170AB00B0E31B /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DBC6461626A170AB00B0E31B /* MainInterface.storyboard */; };
|
||||
DBC6461C26A170AB00B0E31B /* ShareActionExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = DBC6461226A170AB00B0E31B /* ShareActionExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
DBC6462326A1712000B0E31B /* ShareViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC6462226A1712000B0E31B /* ShareViewModel.swift */; };
|
||||
DBC6462326A1712000B0E31B /* ComposeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC6462226A1712000B0E31B /* ComposeViewModel.swift */; };
|
||||
DBC6462826A1736300B0E31B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB427DDE25BAA00100D1B89D /* Assets.xcassets */; };
|
||||
DBC7A672260C897100E57475 /* StatusContentWarningEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC7A671260C897100E57475 /* StatusContentWarningEditorView.swift */; };
|
||||
DBCA0EBC282BB38A0029E2B0 /* PageboyNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCA0EBB282BB38A0029E2B0 /* PageboyNavigateable.swift */; };
|
||||
|
@ -474,14 +434,6 @@
|
|||
DBFEEC99279BDCDE004F81DD /* ProfileAboutViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEEC98279BDCDE004F81DD /* ProfileAboutViewModel.swift */; };
|
||||
DBFEEC9B279BDDD9004F81DD /* ProfileAboutViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEEC9A279BDDD9004F81DD /* ProfileAboutViewModel+Diffable.swift */; };
|
||||
DBFEEC9D279C12C1004F81DD /* ProfileFieldEditCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEEC9C279C12C1004F81DD /* ProfileFieldEditCollectionViewCell.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 */; };
|
||||
DBFEF06D26A67FB7006D7ED1 /* StatusAttachmentViewModel+UploadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF06C26A67FB7006D7ED1 /* StatusAttachmentViewModel+UploadState.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -572,7 +524,6 @@
|
|||
2D206B8525F5FB0900143C56 /* Double.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Double.swift; sourceTree = "<group>"; };
|
||||
2D206B9125F60EA700143C56 /* UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControl.swift; sourceTree = "<group>"; };
|
||||
2D24E1222626ED9D00A59D4F /* UIView+Gesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Gesture.swift"; sourceTree = "<group>"; };
|
||||
2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMiddleLoaderTableViewCell.swift; sourceTree = "<group>"; };
|
||||
2D35237926256D920031AF25 /* NotificationSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSection.swift; sourceTree = "<group>"; };
|
||||
2D364F7125E66D7500204FDC /* MastodonResendEmailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonResendEmailViewController.swift; sourceTree = "<group>"; };
|
||||
2D364F7725E66D8300204FDC /* MastodonResendEmailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonResendEmailViewModel.swift; sourceTree = "<group>"; };
|
||||
|
@ -588,12 +539,10 @@
|
|||
2D571B2E26004EC000540450 /* NavigationBarProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarProgressView.swift; sourceTree = "<group>"; };
|
||||
2D59819A25E4A581000FB903 /* MastodonConfirmEmailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonConfirmEmailViewController.swift; sourceTree = "<group>"; };
|
||||
2D5981A025E4A593000FB903 /* MastodonConfirmEmailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonConfirmEmailViewModel.swift; sourceTree = "<group>"; };
|
||||
2D5A3D0225CF8742002347D6 /* ControlContainableScrollViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlContainableScrollViews.swift; sourceTree = "<group>"; };
|
||||
2D5A3D2725CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeTimelineViewModel+Diffable.swift"; sourceTree = "<group>"; };
|
||||
2D5A3D3725CF8D9F002347D6 /* ScrollViewContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollViewContainer.swift; sourceTree = "<group>"; };
|
||||
2D5A3D6125CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeTimelineViewController+DebugAction.swift"; sourceTree = "<group>"; };
|
||||
2D607AD726242FC500B70763 /* NotificationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationViewModel.swift; sourceTree = "<group>"; };
|
||||
2D650FAA25ECDC9300851B58 /* Mastodon+Entity+Error+Detail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+Error+Detail.swift"; sourceTree = "<group>"; };
|
||||
2D694A7325F9EB4E0038ADDC /* ContentWarningOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentWarningOverlayView.swift; sourceTree = "<group>"; };
|
||||
2D6DE3FF26141DF600A63F6A /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = "<group>"; };
|
||||
2D76319E25C1521200929FB9 /* StatusSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusSection.swift; sourceTree = "<group>"; };
|
||||
|
@ -606,14 +555,10 @@
|
|||
2D84350425FF858100EECE90 /* UIScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIScrollView.swift; sourceTree = "<group>"; };
|
||||
2D939AB425EDD8A90076FA61 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
|
||||
2D939AE725EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonRegisterViewController+Avatar.swift"; sourceTree = "<group>"; };
|
||||
2DA504682601ADE7008F4E6C /* SawToothView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SawToothView.swift; sourceTree = "<group>"; };
|
||||
2DA7D04325CA52B200804E11 /* TimelineLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineLoaderTableViewCell.swift; sourceTree = "<group>"; };
|
||||
2DA7D04925CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineBottomLoaderTableViewCell.swift; sourceTree = "<group>"; };
|
||||
2DA7D05025CA545E00804E11 /* LoadMoreConfigurableTableViewContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreConfigurableTableViewContainer.swift; sourceTree = "<group>"; };
|
||||
2DAC9E37262FC2320062E1A6 /* SuggestionAccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionAccountViewController.swift; sourceTree = "<group>"; };
|
||||
2DAC9E3D262FC2400062E1A6 /* SuggestionAccountViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionAccountViewModel.swift; sourceTree = "<group>"; };
|
||||
2DAC9E45262FC9FD0062E1A6 /* SuggestionAccountTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionAccountTableViewCell.swift; sourceTree = "<group>"; };
|
||||
2DB72C8B262D764300CE6173 /* Mastodon+Entity+Notification+Type.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+Notification+Type.swift"; sourceTree = "<group>"; };
|
||||
2DCB73FC2615C13900EC03D4 /* SearchRecommendCollectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchRecommendCollectionHeader.swift; sourceTree = "<group>"; };
|
||||
2DE0FACD2615F7AD00CDF649 /* RecommendAccountSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendAccountSection.swift; sourceTree = "<group>"; };
|
||||
2DF123A625C3B0210020F248 /* ActiveLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveLabel.swift; sourceTree = "<group>"; };
|
||||
|
@ -638,9 +583,6 @@
|
|||
5D0393952612D266007FE196 /* WebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewModel.swift; sourceTree = "<group>"; };
|
||||
5DA732CB2629CEF500A92342 /* UIView+Remove.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Remove.swift"; sourceTree = "<group>"; };
|
||||
5DA82A9B4ABDAFA3AB9A49C7 /* Pods-MastodonTests.asdk.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.asdk.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.asdk.xcconfig"; sourceTree = "<group>"; };
|
||||
5DDDF1922617442700311060 /* Mastodon+Entity+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+Account.swift"; sourceTree = "<group>"; };
|
||||
5DDDF1982617447F00311060 /* Mastodon+Entity+Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+Tag.swift"; sourceTree = "<group>"; };
|
||||
5DDDF1A82617489F00311060 /* Mastodon+Entity+History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+History.swift"; sourceTree = "<group>"; };
|
||||
5DF1056325F887CB00D6C0D4 /* AVPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayer.swift; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
6213AF5928939C8400BCADB6 /* BookmarkViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkViewModel.swift; sourceTree = "<group>"; };
|
||||
|
@ -684,8 +626,6 @@
|
|||
DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveUserInterfaceStyleBarButtonItem.swift; sourceTree = "<group>"; };
|
||||
DB03A792272A7E5700EE37C5 /* SidebarListHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarListHeaderView.swift; sourceTree = "<group>"; };
|
||||
DB03A794272A981400EE37C5 /* ContentSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentSplitViewController.swift; sourceTree = "<group>"; };
|
||||
DB03F7F22689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeRepliedToStatusContentTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DB03F7F42689B782007B274C /* ComposeTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeTableView.swift; sourceTree = "<group>"; };
|
||||
DB0617EA277EF3820030EE79 /* GradientBorderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientBorderView.swift; sourceTree = "<group>"; };
|
||||
DB0617EC277F02C50030EE79 /* OnboardingNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingNavigationController.swift; sourceTree = "<group>"; };
|
||||
DB0617EE277F12720030EE79 /* NavigationActionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationActionView.swift; sourceTree = "<group>"; };
|
||||
|
@ -707,8 +647,6 @@
|
|||
DB0F9D53283EB3C000379AF8 /* ProfileHeaderView+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileHeaderView+ViewModel.swift"; sourceTree = "<group>"; };
|
||||
DB0F9D55283EB46200379AF8 /* ProfileHeaderView+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileHeaderView+Configuration.swift"; sourceTree = "<group>"; };
|
||||
DB0FCB67279507EF006C02E2 /* DataSourceFacade+Meta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Meta.swift"; sourceTree = "<group>"; };
|
||||
DB0FCB6F27951368006C02E2 /* TimelineMiddleLoaderTableViewCell+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimelineMiddleLoaderTableViewCell+ViewModel.swift"; sourceTree = "<group>"; };
|
||||
DB0FCB7127952986006C02E2 /* NamingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamingState.swift; sourceTree = "<group>"; };
|
||||
DB0FCB7327956939006C02E2 /* DataSourceFacade+Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Status.swift"; sourceTree = "<group>"; };
|
||||
DB0FCB75279571C5006C02E2 /* ThreadViewController+DataSourceProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadViewController+DataSourceProvider.swift"; sourceTree = "<group>"; };
|
||||
DB0FCB7727957678006C02E2 /* DataSourceProvider+UITableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceProvider+UITableViewDelegate.swift"; sourceTree = "<group>"; };
|
||||
|
@ -748,14 +686,6 @@
|
|||
DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationViewModel.swift; sourceTree = "<group>"; };
|
||||
DB2FF50F260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollExpiresOptionCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
DB336F3E278E668C0031E64B /* StatusTableViewCell+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusTableViewCell+ViewModel.swift"; sourceTree = "<group>"; };
|
||||
DB336F40278E68480031E64B /* StatusView+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusView+Configuration.swift"; sourceTree = "<group>"; };
|
||||
DB336F42278EB1680031E64B /* MediaView+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaView+Configuration.swift"; sourceTree = "<group>"; };
|
||||
DB36679C268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusAttachmentTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DB36679E268ABAF20027D07F /* ComposeStatusAttachmentSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusAttachmentSection.swift; sourceTree = "<group>"; };
|
||||
DB3667A0268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusAttachmentItem.swift; sourceTree = "<group>"; };
|
||||
DB3667A3268AE2370027D07F /* ComposeStatusPollTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DB3667A5268AE2620027D07F /* ComposeStatusPollSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollSection.swift; sourceTree = "<group>"; };
|
||||
DB3667A7268AE2900027D07F /* ComposeStatusPollItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollItem.swift; sourceTree = "<group>"; };
|
||||
DB3D0FED25BAA42200EAA174 /* MastodonSDK */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MastodonSDK; sourceTree = "<group>"; };
|
||||
DB3E6FDC2806A40F00B035AE /* DiscoveryHashtagsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryHashtagsViewController.swift; sourceTree = "<group>"; };
|
||||
DB3E6FDF2806A4ED00B035AE /* DiscoveryHashtagsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryHashtagsViewModel.swift; sourceTree = "<group>"; };
|
||||
|
@ -789,8 +719,6 @@
|
|||
DB427DF925BAA00100D1B89D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
DB443CD32694627B00159B29 /* AppearanceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceView.swift; sourceTree = "<group>"; };
|
||||
DB44767A260B3B8C00B66B82 /* CustomEmojiPickerInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiPickerInputView.swift; sourceTree = "<group>"; };
|
||||
DB447680260B3ED600B66B82 /* CustomEmojiPickerSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiPickerSection.swift; sourceTree = "<group>"; };
|
||||
DB44768A260B3F2100B66B82 /* CustomEmojiPickerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiPickerItem.swift; sourceTree = "<group>"; };
|
||||
DB447690260B406600B66B82 /* CustomEmojiPickerItemCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiPickerItemCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
DB447696260B439000B66B82 /* CustomEmojiPickerHeaderCollectionReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiPickerHeaderCollectionReusableView.swift; sourceTree = "<group>"; };
|
||||
DB4481B825EE289600BEFB67 /* UITableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITableView.swift; sourceTree = "<group>"; };
|
||||
|
@ -893,8 +821,6 @@
|
|||
DB64BA472851F29300ADF1B7 /* Account+Fetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account+Fetch.swift"; sourceTree = "<group>"; };
|
||||
DB65C63627A2AF6C008BAC2E /* ReportItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportItem.swift; sourceTree = "<group>"; };
|
||||
DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+DataSource.swift"; sourceTree = "<group>"; };
|
||||
DB66729525F9F91600D60309 /* ComposeStatusSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusSection.swift; sourceTree = "<group>"; };
|
||||
DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusItem.swift; sourceTree = "<group>"; };
|
||||
DB6746EA278ED8B0008A6B94 /* PollOptionView+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PollOptionView+Configuration.swift"; sourceTree = "<group>"; };
|
||||
DB6746EC278F45F0008A6B94 /* AutoGenerateProtocolRelayDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoGenerateProtocolRelayDelegate.swift; sourceTree = "<group>"; };
|
||||
DB6746EF278F463B008A6B94 /* AutoGenerateProtocolDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoGenerateProtocolDelegate.swift; sourceTree = "<group>"; };
|
||||
|
@ -922,14 +848,10 @@
|
|||
DB6B74FD272FF59000C70B6E /* UserItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserItem.swift; sourceTree = "<group>"; };
|
||||
DB6B74FF272FF73800C70B6E /* UserTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DB6B750327300B4000C70B6E /* TimelineFooterTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineFooterTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DB6C8C0E25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+Error.swift"; sourceTree = "<group>"; };
|
||||
DB6D1B43263691CF00ACB481 /* Mastodon+API+Subscriptions+Policy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+API+Subscriptions+Policy.swift"; sourceTree = "<group>"; };
|
||||
DB6D9F3426351B7A008423CD /* NotificationService+Decrypt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationService+Decrypt.swift"; sourceTree = "<group>"; };
|
||||
DB6D9F7C26358ED4008423CD /* SettingsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSection.swift; sourceTree = "<group>"; };
|
||||
DB6D9F8326358EEC008423CD /* SettingsItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsItem.swift; sourceTree = "<group>"; };
|
||||
DB6D9F9626367249008423CD /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
|
||||
DB6F5E34264E78E7009108F4 /* AutoCompleteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteViewController.swift; sourceTree = "<group>"; };
|
||||
DB6F5E37264E994A009108F4 /* AutoCompleteTopChevronView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteTopChevronView.swift; sourceTree = "<group>"; };
|
||||
DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewController.swift; sourceTree = "<group>"; };
|
||||
DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewModel.swift; sourceTree = "<group>"; };
|
||||
DB7274F3273BB9B200577D95 /* ListBatchFetchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListBatchFetchViewModel.swift; sourceTree = "<group>"; };
|
||||
|
@ -976,7 +898,6 @@
|
|||
DB938F0826240F3C00E5B6C1 /* RemoteThreadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteThreadViewModel.swift; sourceTree = "<group>"; };
|
||||
DB938F0E2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadViewModel+LoadThreadState.swift"; sourceTree = "<group>"; };
|
||||
DB938F1E2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadViewModel+Diffable.swift"; sourceTree = "<group>"; };
|
||||
DB938F3226243D6200E5B6C1 /* TimelineTopLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTopLoaderTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DB98EB4627B0DFAA0082E365 /* ReportStatusViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReportStatusViewModel+State.swift"; sourceTree = "<group>"; };
|
||||
DB98EB4827B0F0CD0082E365 /* ReportStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportStatusTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DB98EB4B27B0F2BC0082E365 /* ReportStatusTableViewCell+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReportStatusTableViewCell+ViewModel.swift"; sourceTree = "<group>"; };
|
||||
|
@ -1022,7 +943,6 @@
|
|||
DBA94433265CBB5300C537E1 /* ProfileFieldSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldSection.swift; sourceTree = "<group>"; };
|
||||
DBA94435265CBB7400C537E1 /* ProfileFieldItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldItem.swift; sourceTree = "<group>"; };
|
||||
DBA9443D265CFA6400C537E1 /* ProfileFieldCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
DBA9443F265D137600C537E1 /* Mastodon+Entity+Field.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+Field.swift"; sourceTree = "<group>"; };
|
||||
DBABE3EB25ECAC4B00879EE5 /* WelcomeIllustrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeIllustrationView.swift; sourceTree = "<group>"; };
|
||||
DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteProfileViewModel.swift; sourceTree = "<group>"; };
|
||||
DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FLAnimatedImageView.swift; sourceTree = "<group>"; };
|
||||
|
@ -1042,20 +962,13 @@
|
|||
DBB8AB4526AECDE200F6D281 /* SendPostIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendPostIntentHandler.swift; sourceTree = "<group>"; };
|
||||
DBB9759B262462E1004620BD /* ThreadMetaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadMetaView.swift; sourceTree = "<group>"; };
|
||||
DBBC24A726A52F9000398BB9 /* ComposeToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeToolbarView.swift; sourceTree = "<group>"; };
|
||||
DBBC24AB26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusContentTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonRegex.swift; sourceTree = "<group>"; };
|
||||
DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerAppearance.swift; sourceTree = "<group>"; };
|
||||
DBBF1DBE2652401B00E5B703 /* AutoCompleteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteViewModel.swift; sourceTree = "<group>"; };
|
||||
DBBF1DC126524D2900E5B703 /* AutoCompleteTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DBBF1DC4265251C300E5B703 /* AutoCompleteViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AutoCompleteViewModel+Diffable.swift"; sourceTree = "<group>"; };
|
||||
DBBF1DC6265251D400E5B703 /* AutoCompleteViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AutoCompleteViewModel+State.swift"; sourceTree = "<group>"; };
|
||||
DBBF1DC82652538500E5B703 /* AutoCompleteSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteSection.swift; sourceTree = "<group>"; };
|
||||
DBBF1DCA2652539E00E5B703 /* AutoCompleteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteItem.swift; sourceTree = "<group>"; };
|
||||
DBC6461226A170AB00B0E31B /* ShareActionExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareActionExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DBC6461426A170AB00B0E31B /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
||||
DBC6461426A170AB00B0E31B /* ComposeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewController.swift; sourceTree = "<group>"; };
|
||||
DBC6461726A170AB00B0E31B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
|
||||
DBC6461926A170AB00B0E31B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
DBC6462226A1712000B0E31B /* ShareViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShareViewModel.swift; sourceTree = "<group>"; };
|
||||
DBC6462226A1712000B0E31B /* ComposeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComposeViewModel.swift; sourceTree = "<group>"; };
|
||||
DBC7A671260C897100E57475 /* StatusContentWarningEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentWarningEditorView.swift; sourceTree = "<group>"; };
|
||||
DBC9E3A3282E13BB0063A4D9 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/Intents.strings; sourceTree = "<group>"; };
|
||||
DBC9E3A4282E13BB0063A4D9 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
|
@ -1320,8 +1233,6 @@
|
|||
2D152A8A25C295B8009AA50C /* Content */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB336F40278E68480031E64B /* StatusView+Configuration.swift */,
|
||||
DB336F42278EB1680031E64B /* MediaView+Configuration.swift */,
|
||||
DB6746EA278ED8B0008A6B94 /* PollOptionView+Configuration.swift */,
|
||||
DB0FCB992797F7AD006C02E2 /* UserView+Configuration.swift */,
|
||||
DB63F776279A9A2A00455B82 /* NotificationView+Configuration.swift */,
|
||||
|
@ -1406,7 +1317,6 @@
|
|||
2D5A3D0125CF8640002347D6 /* Vender */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2D5A3D0225CF8742002347D6 /* ControlContainableScrollViews.swift */,
|
||||
DB6180EC26391C6C0018D199 /* TransitioningMath.swift */,
|
||||
DB75BF1D263C1C1B00EDBF1F /* CustomScheduler.swift */,
|
||||
DBF156E32702DB3F00EC00B7 /* HandleTapAction.swift */,
|
||||
|
@ -1420,7 +1330,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
DB697DD7278F4C34004EF2F7 /* Provider */,
|
||||
DB0FCB7127952986006C02E2 /* NamingState.swift */,
|
||||
2D5A3D3725CF8D9F002347D6 /* ScrollViewContainer.swift */,
|
||||
DB4AA6B227BA34B6009EC082 /* CellFrameCacheContainer.swift */,
|
||||
2D38F20725CD491300561493 /* DisposeBagCollectable.swift */,
|
||||
|
@ -1443,7 +1352,6 @@
|
|||
DB0FCB892796BE1E006C02E2 /* RecommandAccount */,
|
||||
DB4F097926A039C400D62E92 /* Status */,
|
||||
DB65C63527A2AF52008BAC2E /* Report */,
|
||||
DB4F097626A0398000D62E92 /* Compose */,
|
||||
DB0617F727855B010030EE79 /* Notification */,
|
||||
DB4F097726A039A200D62E92 /* Search */,
|
||||
DB3E6FE52806A5BA00B035AE /* Discovery */,
|
||||
|
@ -1467,7 +1375,6 @@
|
|||
2D7631A525C1532D00929FB9 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2DA504672601ADBA008F4E6C /* Decoration */,
|
||||
2D42FF8325C82245004A627A /* Button */,
|
||||
DBA9B90325F1D4420012E7B6 /* Control */,
|
||||
2D152A8A25C295B8009AA50C /* Content */,
|
||||
|
@ -1487,11 +1394,6 @@
|
|||
DB0FCB7D27958957006C02E2 /* StatusThreadRootTableViewCell+ViewModel.swift */,
|
||||
DB6B74FF272FF73800C70B6E /* UserTableViewCell.swift */,
|
||||
DB0FCB972797F6BF006C02E2 /* UserTableViewCell+ViewModel.swift */,
|
||||
2DA7D04325CA52B200804E11 /* TimelineLoaderTableViewCell.swift */,
|
||||
DB938F3226243D6200E5B6C1 /* TimelineTopLoaderTableViewCell.swift */,
|
||||
2DA7D04925CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift */,
|
||||
2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */,
|
||||
DB0FCB6F27951368006C02E2 /* TimelineMiddleLoaderTableViewCell+ViewModel.swift */,
|
||||
DBE3CDBA261C427900430CC6 /* TimelineHeaderTableViewCell.swift */,
|
||||
DB6B750327300B4000C70B6E /* TimelineFooterTableViewCell.swift */,
|
||||
DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */,
|
||||
|
@ -1499,14 +1401,6 @@
|
|||
path = TableviewCell;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2DA504672601ADBA008F4E6C /* Decoration */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2DA504682601ADE7008F4E6C /* SawToothView.swift */,
|
||||
);
|
||||
path = Decoration;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2DAC9E36262FC20B0062E1A6 /* SuggestionAccount */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1638,17 +1532,6 @@
|
|||
path = Onboarding;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB03F7F1268990A2007B274C /* TableViewCell */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB03F7F22689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift */,
|
||||
DBBC24AB26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift */,
|
||||
DB36679C268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift */,
|
||||
DB3667A3268AE2370027D07F /* ComposeStatusPollTableViewCell.swift */,
|
||||
);
|
||||
path = TableViewCell;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB0617F727855B010030EE79 /* Notification */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1808,13 +1691,6 @@
|
|||
path = Discovery;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB3E6FEA2806BD2500B035AE /* MastodonUI */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
path = MastodonUI;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB3E6FED2806D7FC00B035AE /* News */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1934,23 +1810,6 @@
|
|||
path = SearchResult;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB4F097626A0398000D62E92 /* Compose */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB66729525F9F91600D60309 /* ComposeStatusSection.swift */,
|
||||
DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */,
|
||||
DB36679E268ABAF20027D07F /* ComposeStatusAttachmentSection.swift */,
|
||||
DB3667A0268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift */,
|
||||
DB3667A5268AE2620027D07F /* ComposeStatusPollSection.swift */,
|
||||
DB3667A7268AE2900027D07F /* ComposeStatusPollItem.swift */,
|
||||
DB447680260B3ED600B66B82 /* CustomEmojiPickerSection.swift */,
|
||||
DB44768A260B3F2100B66B82 /* CustomEmojiPickerItem.swift */,
|
||||
DBBF1DC82652538500E5B703 /* AutoCompleteSection.swift */,
|
||||
DBBF1DCA2652539E00E5B703 /* AutoCompleteItem.swift */,
|
||||
);
|
||||
path = Compose;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB4F097726A039A200D62E92 /* Search */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -2016,7 +1875,6 @@
|
|||
DB55D32225FB4D320002F825 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB03F7F42689B782007B274C /* ComposeTableView.swift */,
|
||||
DBA0A11225FB3FC10079C110 /* ComposeToolbarView.swift */,
|
||||
DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */,
|
||||
DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */,
|
||||
|
@ -2260,34 +2118,6 @@
|
|||
path = Follower;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB6C8C0525F0921200AAA452 /* MastodonSDK */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB6C8C0E25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift */,
|
||||
5DDDF1922617442700311060 /* Mastodon+Entity+Account.swift */,
|
||||
5DDDF1982617447F00311060 /* Mastodon+Entity+Tag.swift */,
|
||||
5DDDF1A82617489F00311060 /* Mastodon+Entity+History.swift */,
|
||||
2D650FAA25ECDC9300851B58 /* Mastodon+Entity+Error+Detail.swift */,
|
||||
2DB72C8B262D764300CE6173 /* Mastodon+Entity+Notification+Type.swift */,
|
||||
DBA9443F265D137600C537E1 /* Mastodon+Entity+Field.swift */,
|
||||
DB6D1B43263691CF00ACB481 /* Mastodon+API+Subscriptions+Policy.swift */,
|
||||
);
|
||||
path = MastodonSDK;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB6F5E36264E78EA009108F4 /* AutoComplete */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DBBF1DC02652402000E5B703 /* View */,
|
||||
DBBF1DC326524D3100E5B703 /* Cell */,
|
||||
DB6F5E34264E78E7009108F4 /* AutoCompleteViewController.swift */,
|
||||
DBBF1DBE2652401B00E5B703 /* AutoCompleteViewModel.swift */,
|
||||
DBBF1DC4265251C300E5B703 /* AutoCompleteViewModel+Diffable.swift */,
|
||||
DBBF1DC6265251D400E5B703 /* AutoCompleteViewModel+State.swift */,
|
||||
);
|
||||
path = AutoComplete;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB72602125E36A2500235243 /* ServerRules */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -2312,10 +2142,8 @@
|
|||
DB789A1025F9F29B0071ACA0 /* Compose */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB6F5E36264E78EA009108F4 /* AutoComplete */,
|
||||
DB55D32225FB4D320002F825 /* View */,
|
||||
DB789A2125F9F76D0071ACA0 /* CollectionViewCell */,
|
||||
DB03F7F1268990A2007B274C /* TableViewCell */,
|
||||
DB789A0A25F9F2950071ACA0 /* ComposeViewController.swift */,
|
||||
DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */,
|
||||
DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */,
|
||||
|
@ -2404,8 +2232,6 @@
|
|||
DB8AF56225C138BC002E6C99 /* Extension */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB3E6FEA2806BD2500B035AE /* MastodonUI */,
|
||||
DB6C8C0525F0921200AAA452 /* MastodonSDK */,
|
||||
2DF123A625C3B0210020F248 /* ActiveLabel.swift */,
|
||||
5DF1056325F887CB00D6C0D4 /* AVPlayer.swift */,
|
||||
2D206B8525F5FB0900143C56 /* Double.swift */,
|
||||
|
@ -2700,22 +2526,6 @@
|
|||
path = Helper;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DBBF1DC02652402000E5B703 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB6F5E37264E994A009108F4 /* AutoCompleteTopChevronView.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DBBF1DC326524D3100E5B703 /* Cell */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DBBF1DC126524D2900E5B703 /* AutoCompleteTableViewCell.swift */,
|
||||
);
|
||||
path = Cell;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DBC6461326A170AB00B0E31B /* ShareActionExtension */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -2897,8 +2707,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
DBFEF05426A576EE006D7ED1 /* View */,
|
||||
DBC6462226A1712000B0E31B /* ShareViewModel.swift */,
|
||||
DBC6461426A170AB00B0E31B /* ShareViewController.swift */,
|
||||
DBC6462226A1712000B0E31B /* ComposeViewModel.swift */,
|
||||
DBC6461426A170AB00B0E31B /* ComposeViewController.swift */,
|
||||
);
|
||||
path = Scene;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3347,19 +3157,14 @@
|
|||
DB65C63727A2AF6C008BAC2E /* ReportItem.swift in Sources */,
|
||||
DB5B54B22833C24B00DEF8B2 /* RebloggedByViewController+DataSourceProvider.swift in Sources */,
|
||||
2D59819B25E4A581000FB903 /* MastodonConfirmEmailViewController.swift in Sources */,
|
||||
DBA94440265D137600C537E1 /* Mastodon+Entity+Field.swift in Sources */,
|
||||
DBBF1DC7265251D400E5B703 /* AutoCompleteViewModel+State.swift in Sources */,
|
||||
DB03A793272A7E5700EE37C5 /* SidebarListHeaderView.swift in Sources */,
|
||||
DB4FFC2B269EC39600D62E92 /* SearchToSearchDetailViewControllerAnimatedTransitioning.swift in Sources */,
|
||||
DB5B7298273112C800081888 /* FollowingListViewModel.swift in Sources */,
|
||||
0FB3D2F725E4C24D00AAD544 /* MastodonPickServerViewModel.swift in Sources */,
|
||||
DB5B54AE2833C15F00DEF8B2 /* UserListViewModel+Diffable.swift in Sources */,
|
||||
DB336F43278EB1690031E64B /* MediaView+Configuration.swift in Sources */,
|
||||
DB66729625F9F91600D60309 /* ComposeStatusSection.swift in Sources */,
|
||||
DB482A3F261331E8008AE74C /* UserTimelineViewModel+State.swift in Sources */,
|
||||
DB3E6FE02806A4ED00B035AE /* DiscoveryHashtagsViewModel.swift in Sources */,
|
||||
2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */,
|
||||
DB447681260B3ED600B66B82 /* CustomEmojiPickerSection.swift in Sources */,
|
||||
DB0FCB7427956939006C02E2 /* DataSourceFacade+Status.swift in Sources */,
|
||||
DBB525502611ED6D002F1F29 /* ProfileHeaderView.swift in Sources */,
|
||||
DB63F75A279953F200455B82 /* SearchHistoryUserCollectionViewCell+ViewModel.swift in Sources */,
|
||||
|
@ -3395,13 +3200,11 @@
|
|||
DB697DD9278F4CED004EF2F7 /* HomeTimelineViewController+DataSourceProvider.swift in Sources */,
|
||||
DB9A488A26034D40008B817C /* ComposeViewModel+PublishState.swift in Sources */,
|
||||
DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */,
|
||||
2DA504692601ADE7008F4E6C /* SawToothView.swift in Sources */,
|
||||
2D8434F525FF465D00EECE90 /* HomeTimelineNavigationBarTitleViewModel.swift in Sources */,
|
||||
DB938F0F2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift in Sources */,
|
||||
DB6180F226391CF40018D199 /* MediaPreviewImageViewModel.swift in Sources */,
|
||||
62FD27D12893707600B205C5 /* BookmarkViewController.swift in Sources */,
|
||||
DBD5B1F627BCD3D200BD6B38 /* SuggestionAccountTableViewCell+ViewModel.swift in Sources */,
|
||||
5DDDF1932617442700311060 /* Mastodon+Entity+Account.swift in Sources */,
|
||||
DB63F767279A5EB300455B82 /* NotificationTimelineViewModel.swift in Sources */,
|
||||
2D607AD826242FC500B70763 /* NotificationViewModel.swift in Sources */,
|
||||
DB5B54AB2833C12A00DEF8B2 /* RebloggedByViewController.swift in Sources */,
|
||||
|
@ -3447,7 +3250,6 @@
|
|||
DBCBED1726132DB500B49291 /* UserTimelineViewModel+Diffable.swift in Sources */,
|
||||
2DE0FACE2615F7AD00CDF649 /* RecommendAccountSection.swift in Sources */,
|
||||
2DAC9E3E262FC2400062E1A6 /* SuggestionAccountViewModel.swift in Sources */,
|
||||
DB3667A8268AE2900027D07F /* ComposeStatusPollItem.swift in Sources */,
|
||||
DB603113279EBEBA00A935FE /* DataSourceFacade+Block.swift in Sources */,
|
||||
DB63F777279A9A2A00455B82 /* NotificationView+Configuration.swift in Sources */,
|
||||
DB029E95266A20430062874E /* MastodonAuthenticationController.swift in Sources */,
|
||||
|
@ -3457,7 +3259,6 @@
|
|||
DBE3CDEC261C6B2900430CC6 /* FavoriteViewController.swift in Sources */,
|
||||
DB938EE62623F50700E5B6C1 /* ThreadViewController.swift in Sources */,
|
||||
DB6180F426391D110018D199 /* MediaPreviewImageView.swift in Sources */,
|
||||
DB336F41278E68480031E64B /* StatusView+Configuration.swift in Sources */,
|
||||
DBF9814A265E24F500E4BA07 /* ProfileFieldCollectionViewHeaderFooterView.swift in Sources */,
|
||||
2D939AB525EDD8A90076FA61 /* String.swift in Sources */,
|
||||
DB4481B925EE289600BEFB67 /* UITableView.swift in Sources */,
|
||||
|
@ -3470,12 +3271,9 @@
|
|||
DB5B549F2833A72500DEF8B2 /* FamiliarFollowersViewModel+Diffable.swift in Sources */,
|
||||
DB6B351E2601FAEE00DC1E11 /* ComposeStatusAttachmentCollectionViewCell.swift in Sources */,
|
||||
DB8F7076279E954700E1225B /* DataSourceFacade+Follow.swift in Sources */,
|
||||
DB36679F268ABAF20027D07F /* ComposeStatusAttachmentSection.swift in Sources */,
|
||||
2DA7D04425CA52B200804E11 /* TimelineLoaderTableViewCell.swift in Sources */,
|
||||
DB63F7542799491600455B82 /* DataSourceFacade+SearchHistory.swift in Sources */,
|
||||
DB7A9F912818EAF10016AF98 /* MastodonRegisterView.swift in Sources */,
|
||||
DBF1572F27046F1A00EC00B7 /* SecondaryPlaceholderViewController.swift in Sources */,
|
||||
DB03F7F32689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift in Sources */,
|
||||
2D4AD8A826316D3500613EFC /* SelectedAccountItem.swift in Sources */,
|
||||
DBE3CDFB261C6CA500430CC6 /* FavoriteViewModel.swift in Sources */,
|
||||
DBE3CE01261D623D00430CC6 /* FavoriteViewModel+State.swift in Sources */,
|
||||
|
@ -3487,7 +3285,6 @@
|
|||
DB63F7452799056400455B82 /* HashtagTableViewCell.swift in Sources */,
|
||||
DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */,
|
||||
DB3EA8EB281B7E0700598866 /* DiscoveryCommunityViewModel+State.swift in Sources */,
|
||||
DB0FCB7227952986006C02E2 /* NamingState.swift in Sources */,
|
||||
DB0F8150264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift in Sources */,
|
||||
DB98EB5327B0F9890082E365 /* ReportHeadlineTableViewCell.swift in Sources */,
|
||||
DB5B729C273113C200081888 /* FollowingListViewModel+Diffable.swift in Sources */,
|
||||
|
@ -3496,14 +3293,12 @@
|
|||
DBB9759C262462E1004620BD /* ThreadMetaView.swift in Sources */,
|
||||
DB5B729E273113F300081888 /* FollowingListViewModel+State.swift in Sources */,
|
||||
2DF123A725C3B0210020F248 /* ActiveLabel.swift in Sources */,
|
||||
5DDDF1A92617489F00311060 /* Mastodon+Entity+History.swift in Sources */,
|
||||
DBF9814C265E339500E4BA07 /* ProfileFieldAddEntryCollectionViewCell.swift in Sources */,
|
||||
DB63F76227996B6600455B82 /* SearchHistoryViewController+DataSourceProvider.swift in Sources */,
|
||||
DB73BF4927140BA300781945 /* UICollectionViewDiffableDataSource.swift in Sources */,
|
||||
DBA5E7AB263BD3F5004598BB /* TimelineTableViewCellContextMenuConfiguration.swift in Sources */,
|
||||
DB73B490261F030A002E9E9F /* SafariActivity.swift in Sources */,
|
||||
DB63F7492799126300455B82 /* FollowerListViewController+DataSourceProvider.swift in Sources */,
|
||||
DB6D1B44263691CF00ACB481 /* Mastodon+API+Subscriptions+Policy.swift in Sources */,
|
||||
2D198643261BF09500F0B013 /* SearchResultItem.swift in Sources */,
|
||||
2DAC9E38262FC2320062E1A6 /* SuggestionAccountViewController.swift in Sources */,
|
||||
DB66728C25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift in Sources */,
|
||||
|
@ -3529,14 +3324,12 @@
|
|||
DB852D1926FAEB6B00FC9D81 /* SidebarViewController.swift in Sources */,
|
||||
2D206B9225F60EA700143C56 /* UIControl.swift in Sources */,
|
||||
DBDFF1932805554900557A48 /* DiscoveryPostsViewModel.swift in Sources */,
|
||||
DBBF1DC92652538500E5B703 /* AutoCompleteSection.swift in Sources */,
|
||||
DB3E6FE72806A7A200B035AE /* DiscoveryItem.swift in Sources */,
|
||||
DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */,
|
||||
DBEFCD79282A147000C0ABEA /* ReportStatusViewModel.swift in Sources */,
|
||||
DB7F48452620241000796008 /* ProfileHeaderViewModel.swift in Sources */,
|
||||
DB0A322E280EE9FD001729D2 /* DiscoveryIntroBannerView.swift in Sources */,
|
||||
2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */,
|
||||
5DDDF1992617447F00311060 /* Mastodon+Entity+Tag.swift in Sources */,
|
||||
5B90C45F262599800002E742 /* SettingsToggleTableViewCell.swift in Sources */,
|
||||
2D694A7425F9EB4E0038ADDC /* ContentWarningOverlayView.swift in Sources */,
|
||||
DB0FCB7827957678006C02E2 /* DataSourceProvider+UITableViewDelegate.swift in Sources */,
|
||||
|
@ -3550,12 +3343,9 @@
|
|||
0F20222D261457EE000C64BF /* HashtagTimelineViewModel+State.swift in Sources */,
|
||||
DB0009A626AEE5DC009B9D2D /* Intents.intentdefinition in Sources */,
|
||||
5B90C462262599800002E742 /* SettingsSectionHeader.swift in Sources */,
|
||||
DB44768B260B3F2100B66B82 /* CustomEmojiPickerItem.swift in Sources */,
|
||||
5DF1056425F887CB00D6C0D4 /* AVPlayer.swift in Sources */,
|
||||
DB3E6FEF2806D82600B035AE /* DiscoveryNewsViewModel.swift in Sources */,
|
||||
DBBF1DCB2652539E00E5B703 /* AutoCompleteItem.swift in Sources */,
|
||||
DBC7A672260C897100E57475 /* StatusContentWarningEditorView.swift in Sources */,
|
||||
DB3667A6268AE2620027D07F /* ComposeStatusPollSection.swift in Sources */,
|
||||
DB6B750427300B4000C70B6E /* TimelineFooterTableViewCell.swift in Sources */,
|
||||
DB98EB4C27B0F2BC0082E365 /* ReportStatusTableViewCell+ViewModel.swift in Sources */,
|
||||
DB852D1F26FB037800FC9D81 /* SidebarViewModel.swift in Sources */,
|
||||
|
@ -3568,7 +3358,6 @@
|
|||
DB0617EF277F12720030EE79 /* NavigationActionView.swift in Sources */,
|
||||
DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */,
|
||||
2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */,
|
||||
DB3667A1268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift in Sources */,
|
||||
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */,
|
||||
DBA94434265CBB5300C537E1 /* ProfileFieldSection.swift in Sources */,
|
||||
DBCA0EBC282BB38A0029E2B0 /* PageboyNavigateable.swift in Sources */,
|
||||
|
@ -3587,11 +3376,8 @@
|
|||
DB023D2C27A10464005AC798 /* NotificationTimelineViewController+DataSourceProvider.swift in Sources */,
|
||||
DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */,
|
||||
DBF1D257269DBAC600C1C08A /* SearchDetailViewModel.swift in Sources */,
|
||||
DB03F7F52689B782007B274C /* ComposeTableView.swift in Sources */,
|
||||
DBB45B5927B39FE4002DC5A7 /* MediaPreviewVideoViewModel.swift in Sources */,
|
||||
DB6C8C0F25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift in Sources */,
|
||||
DB0FCB76279571C5006C02E2 /* ThreadViewController+DataSourceProvider.swift in Sources */,
|
||||
DB0FCB7027951368006C02E2 /* TimelineMiddleLoaderTableViewCell+ViewModel.swift in Sources */,
|
||||
DB1E346825F518E20079D7DF /* CategoryPickerSection.swift in Sources */,
|
||||
DB7274F4273BB9B200577D95 /* ListBatchFetchViewModel.swift in Sources */,
|
||||
DB0618052785A73D0030EE79 /* RegisterItem.swift in Sources */,
|
||||
|
@ -3605,7 +3391,6 @@
|
|||
DB3E6FDD2806A40F00B035AE /* DiscoveryHashtagsViewController.swift in Sources */,
|
||||
DB938EED2623F79B00E5B6C1 /* ThreadViewModel.swift in Sources */,
|
||||
DB6988DE2848D11C002398EF /* PagerTabStripNavigateable.swift in Sources */,
|
||||
DBBC24AC26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift in Sources */,
|
||||
2DCB73FD2615C13900EC03D4 /* SearchRecommendCollectionHeader.swift in Sources */,
|
||||
DB852D1C26FB021500FC9D81 /* RootSplitViewController.swift in Sources */,
|
||||
DB697DD1278F4871004EF2F7 /* AutoGenerateTableViewDelegate.swift in Sources */,
|
||||
|
@ -3619,7 +3404,6 @@
|
|||
DB2F073525E8ECF000957B2D /* AuthenticationViewModel.swift in Sources */,
|
||||
DB63F779279ABF9C00455B82 /* DataSourceFacade+Reblog.swift in Sources */,
|
||||
DB4F0963269ED06300D62E92 /* SearchResultViewController.swift in Sources */,
|
||||
DBBF1DC5265251C300E5B703 /* AutoCompleteViewModel+Diffable.swift in Sources */,
|
||||
DB603111279EB38500A935FE /* DataSourceFacade+Mute.swift in Sources */,
|
||||
DB68A04A25E9027700CFDF14 /* AdaptiveStatusBarStyleNavigationController.swift in Sources */,
|
||||
0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */,
|
||||
|
@ -3636,7 +3420,6 @@
|
|||
DBA5E7A9263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift in Sources */,
|
||||
DBF156E22702DA6900EC00B7 /* UIStatusBarManager+HandleTapAction.m in Sources */,
|
||||
2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */,
|
||||
DB6F5E35264E78E7009108F4 /* AutoCompleteViewController.swift in Sources */,
|
||||
DB697DE1278F5296004EF2F7 /* DataSourceFacade+Model.swift in Sources */,
|
||||
DBCC3B8F26148F7B0045B23D /* CachedProfileViewModel.swift in Sources */,
|
||||
DB4F097526A037F500D62E92 /* SearchHistoryViewModel.swift in Sources */,
|
||||
|
@ -3650,9 +3433,7 @@
|
|||
DB9F58F126EF512300E7BBE9 /* AccountListTableViewCell.swift in Sources */,
|
||||
2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */,
|
||||
DB938F0326240EA300E5B6C1 /* CachedThreadViewModel.swift in Sources */,
|
||||
2D650FAB25ECDC9300851B58 /* Mastodon+Entity+Error+Detail.swift in Sources */,
|
||||
DBA5A53526F0A36A00CACBAA /* AddAccountTableViewCell.swift in Sources */,
|
||||
2DB72C8C262D764300CE6173 /* Mastodon+Entity+Notification+Type.swift in Sources */,
|
||||
2D35237A26256D920031AF25 /* NotificationSection.swift in Sources */,
|
||||
2D4AD89C263165B500613EFC /* SuggestionAccountCollectionViewCell.swift in Sources */,
|
||||
DB98EB6927B21A7C0082E365 /* ReportResultActionTableViewCell.swift in Sources */,
|
||||
|
@ -3672,8 +3453,6 @@
|
|||
DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */,
|
||||
DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */,
|
||||
DBEFCD74282A130400C0ABEA /* ReportReasonViewModel.swift in Sources */,
|
||||
2D5A3D0325CF8742002347D6 /* ControlContainableScrollViews.swift in Sources */,
|
||||
DB36679D268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift in Sources */,
|
||||
DBA0A11325FB3FC10079C110 /* ComposeToolbarView.swift in Sources */,
|
||||
DBFEEC96279BDC67004F81DD /* ProfileAboutViewController.swift in Sources */,
|
||||
DB63F74F2799405600455B82 /* SearchHistoryViewModel+Diffable.swift in Sources */,
|
||||
|
@ -3689,10 +3468,8 @@
|
|||
DB6B74FC272FF55800C70B6E /* UserSection.swift in Sources */,
|
||||
DB0FCB862796BDA1006C02E2 /* SearchSection.swift in Sources */,
|
||||
DB1D61CF26F1B33600DA8662 /* WelcomeViewModel.swift in Sources */,
|
||||
2DA7D04A25CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift in Sources */,
|
||||
DBD376B2269302A4007FEC24 /* UITableViewCell.swift in Sources */,
|
||||
DB4F0966269ED52200D62E92 /* SearchResultViewModel.swift in Sources */,
|
||||
DBBF1DBF2652401B00E5B703 /* AutoCompleteViewModel.swift in Sources */,
|
||||
DB6180FA26391F2E0018D199 /* MediaPreviewViewModel.swift in Sources */,
|
||||
2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */,
|
||||
DB6B7500272FF73800C70B6E /* UserTableViewCell.swift in Sources */,
|
||||
|
@ -3708,7 +3485,6 @@
|
|||
DB3E6FE42806A5B800B035AE /* DiscoverySection.swift in Sources */,
|
||||
DB8190C62601FF0400020C08 /* AttachmentContainerView.swift in Sources */,
|
||||
DB697DDB278F4DE3004EF2F7 /* DataSourceProvider+StatusTableViewCellDelegate.swift in Sources */,
|
||||
2D32EAAC25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift in Sources */,
|
||||
DB87D4512609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift in Sources */,
|
||||
DBB45B5627B39FC9002DC5A7 /* MediaPreviewVideoViewController.swift in Sources */,
|
||||
DB0FCB8027968F70006C02E2 /* MastodonStatusThreadViewModel.swift in Sources */,
|
||||
|
@ -3724,7 +3500,6 @@
|
|||
DB3E6FF12806D96900B035AE /* DiscoveryNewsViewModel+Diffable.swift in Sources */,
|
||||
DB3E6FF82807C45300B035AE /* DiscoveryForYouViewModel.swift in Sources */,
|
||||
DB0F9D56283EB46200379AF8 /* ProfileHeaderView+Configuration.swift in Sources */,
|
||||
DB6F5E38264E994A009108F4 /* AutoCompleteTopChevronView.swift in Sources */,
|
||||
DB6746F0278F463B008A6B94 /* AutoGenerateProtocolDelegate.swift in Sources */,
|
||||
DBB525412611ED54002F1F29 /* ProfileHeaderViewController.swift in Sources */,
|
||||
DBDFF19A28055A1400557A48 /* DiscoveryViewController.swift in Sources */,
|
||||
|
@ -3738,10 +3513,7 @@
|
|||
DB63F74B279914A000455B82 /* FollowingListViewController+DataSourceProvider.swift in Sources */,
|
||||
DBEFCD7D282A2A3B00C0ABEA /* ReportServerRulesViewController.swift in Sources */,
|
||||
DBB525362611ECEB002F1F29 /* UserTimelineViewController.swift in Sources */,
|
||||
DB938F3326243D6200E5B6C1 /* TimelineTopLoaderTableViewCell.swift in Sources */,
|
||||
DB98EB4927B0F0CD0082E365 /* ReportStatusTableViewCell.swift in Sources */,
|
||||
DB3667A4268AE2370027D07F /* ComposeStatusPollTableViewCell.swift in Sources */,
|
||||
DBBF1DC226524D2900E5B703 /* AutoCompleteTableViewCell.swift in Sources */,
|
||||
DBF3B7412733EB9400E21627 /* MastodonLocalCode.swift in Sources */,
|
||||
DB98EB6527B216500082E365 /* ReportResultViewModel.swift in Sources */,
|
||||
DB4F096A269EDAD200D62E92 /* SearchResultViewModel+State.swift in Sources */,
|
||||
|
@ -3751,7 +3523,6 @@
|
|||
DB023D2A27A0FE5C005AC798 /* DataSourceProvider+NotificationTableViewCellDelegate.swift in Sources */,
|
||||
DB98EB6027B10E150082E365 /* ReportCommentTableViewCell.swift in Sources */,
|
||||
DB0FCB962797E6C2006C02E2 /* SearchResultViewController+DataSourceProvider.swift in Sources */,
|
||||
DB66729C25F9F91F00D60309 /* ComposeStatusItem.swift in Sources */,
|
||||
DB6180E326391A4C0018D199 /* ViewControllerAnimatedTransitioning.swift in Sources */,
|
||||
DBD5B1F827BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift in Sources */,
|
||||
0FB3D31E25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift in Sources */,
|
||||
|
@ -3796,18 +3567,9 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DBFEF05E26A57715006D7ED1 /* ComposeView.swift in Sources */,
|
||||
DBFEF06026A57715006D7ED1 /* StatusAttachmentView.swift in Sources */,
|
||||
DBC6462326A1712000B0E31B /* ShareViewModel.swift in Sources */,
|
||||
DBFEF06D26A67FB7006D7ED1 /* StatusAttachmentViewModel+UploadState.swift in Sources */,
|
||||
DBFEF05F26A57715006D7ED1 /* StatusAuthorView.swift in Sources */,
|
||||
DBFEF05D26A57715006D7ED1 /* ContentWarningEditorView.swift in Sources */,
|
||||
DBFEF05C26A57715006D7ED1 /* StatusEditorView.swift in Sources */,
|
||||
DBC6462326A1712000B0E31B /* ComposeViewModel.swift in Sources */,
|
||||
DBB3BA2B26A81D060004F2D4 /* FLAnimatedImageView.swift in Sources */,
|
||||
DBBC24A826A52F9000398BB9 /* ComposeToolbarView.swift in Sources */,
|
||||
DBFEF05B26A57715006D7ED1 /* ComposeViewModel.swift in Sources */,
|
||||
DBFEF06326A577F2006D7ED1 /* StatusAttachmentViewModel.swift in Sources */,
|
||||
DBC6461526A170AB00B0E31B /* ShareViewController.swift in Sources */,
|
||||
DBC6461526A170AB00B0E31B /* ComposeViewController.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -112,12 +112,12 @@
|
|||
<key>NotificationService.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>25</integer>
|
||||
<integer>18</integer>
|
||||
</dict>
|
||||
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>24</integer>
|
||||
<integer>17</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
//
|
||||
// CustomEmojiPickerSection.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
enum CustomEmojiPickerSection: Equatable, Hashable {
|
||||
case emoji(name: String)
|
||||
}
|
||||
|
||||
extension CustomEmojiPickerSection {
|
||||
static func collectionViewDiffableDataSource(
|
||||
for collectionView: UICollectionView,
|
||||
dependency: NeedsDependency
|
||||
) -> UICollectionViewDiffableDataSource<CustomEmojiPickerSection, CustomEmojiPickerItem> {
|
||||
let dataSource = UICollectionViewDiffableDataSource<CustomEmojiPickerSection, CustomEmojiPickerItem>(collectionView: collectionView) { [weak dependency] collectionView, indexPath, item -> UICollectionViewCell? in
|
||||
guard let _ = dependency else { return nil }
|
||||
switch item {
|
||||
case .emoji(let attribute):
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: CustomEmojiPickerItemCollectionViewCell.self), for: indexPath) as! CustomEmojiPickerItemCollectionViewCell
|
||||
let placeholder = UIImage.placeholder(size: CustomEmojiPickerItemCollectionViewCell.itemSize, color: .systemFill)
|
||||
.af.imageRounded(withCornerRadius: 4)
|
||||
|
||||
let isAnimated = !UserDefaults.shared.preferredStaticEmoji
|
||||
let url = URL(string: isAnimated ? attribute.emoji.url : attribute.emoji.staticURL)
|
||||
cell.emojiImageView.sd_setImage(
|
||||
with: url,
|
||||
placeholderImage: placeholder,
|
||||
options: [],
|
||||
context: nil
|
||||
)
|
||||
cell.accessibilityLabel = attribute.emoji.shortcode
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
dataSource.supplementaryViewProvider = { [weak dataSource] collectionView, kind, indexPath -> UICollectionReusableView? in
|
||||
guard let dataSource = dataSource else { return nil }
|
||||
let sections = dataSource.snapshot().sectionIdentifiers
|
||||
guard indexPath.section < sections.count else { return nil }
|
||||
let section = sections[indexPath.section]
|
||||
|
||||
switch kind {
|
||||
case String(describing: CustomEmojiPickerHeaderCollectionReusableView.self):
|
||||
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: String(describing: CustomEmojiPickerHeaderCollectionReusableView.self), for: indexPath) as! CustomEmojiPickerHeaderCollectionReusableView
|
||||
switch section {
|
||||
case .emoji(let name):
|
||||
header.titleLabel.text = name
|
||||
}
|
||||
return header
|
||||
default:
|
||||
assertionFailure()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return dataSource
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ import MetaTextKit
|
|||
import MastodonMeta
|
||||
import MastodonAsset
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonLocalization
|
||||
|
||||
enum NotificationSection: Equatable, Hashable {
|
||||
|
|
|
@ -14,6 +14,7 @@ import UIKit
|
|||
import os.log
|
||||
import MastodonAsset
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonLocalization
|
||||
|
||||
enum ReportSection: Equatable, Hashable {
|
||||
|
|
|
@ -10,8 +10,9 @@ import UIKit
|
|||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
import MetaTextKit
|
||||
import MastodonUI
|
||||
import MastodonMeta
|
||||
import MetaTextKit
|
||||
|
||||
enum UserSection: Hashable {
|
||||
case main
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
//
|
||||
// Mastodon+Entity+Account.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by xiaojian sun on 2021/4/2.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonSDK
|
||||
import MastodonMeta
|
||||
|
||||
extension Mastodon.Entity.Account: Hashable {
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
}
|
||||
|
||||
public static func == (lhs: Mastodon.Entity.Account, rhs: Mastodon.Entity.Account) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Entity.Account {
|
||||
|
||||
var displayNameWithFallback: String {
|
||||
return !displayName.isEmpty ? displayName : username
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Mastodon.Entity.Account {
|
||||
public func avatarImageURL() -> URL? {
|
||||
let string = UserDefaults.shared.preferredStaticAvatar ? avatarStatic ?? avatar : avatar
|
||||
return URL(string: string)
|
||||
}
|
||||
|
||||
public func avatarImageURLWithFallback(domain: String) -> URL {
|
||||
return avatarImageURL() ?? URL(string: "https://\(domain)/avatars/original/missing.png")!
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Entity.Account {
|
||||
var emojiMeta: MastodonContent.Emojis {
|
||||
let isAnimated = !UserDefaults.shared.preferredStaticEmoji
|
||||
|
||||
var dict = MastodonContent.Emojis()
|
||||
for emoji in emojis ?? [] {
|
||||
dict[emoji.shortcode] = isAnimated ? emoji.url : emoji.staticURL
|
||||
}
|
||||
return dict
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
//
|
||||
// Mastodon+Entity+Tag.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by xiaojian sun on 2021/4/2.
|
||||
//
|
||||
|
||||
import MastodonSDK
|
||||
|
||||
//extension Mastodon.Entity.Tag: Hashable {
|
||||
// public func hash(into hasher: inout Hasher) {
|
||||
// hasher.combine(name)
|
||||
// }
|
||||
//
|
||||
// public static func == (lhs: Mastodon.Entity.Tag, rhs: Mastodon.Entity.Tag) -> Bool {
|
||||
// return lhs.name == rhs.name
|
||||
// }
|
||||
//}
|
|
@ -1,12 +0,0 @@
|
|||
//
|
||||
// NamingState.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK on 2022-1-17.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol NamingState {
|
||||
var name: String { get }
|
||||
}
|
|
@ -103,8 +103,8 @@ extension DataSourceFacade {
|
|||
|
||||
let composeViewModel = ComposeViewModel(
|
||||
context: provider.context,
|
||||
composeKind: .reply(status: status),
|
||||
authContext: provider.authContext
|
||||
authContext: provider.authContext,
|
||||
kind: .reply(status: status)
|
||||
)
|
||||
_ = provider.coordinator.present(
|
||||
scene: .compose(viewModel: composeViewModel),
|
||||
|
|
|
@ -99,10 +99,10 @@ extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvid
|
|||
|
||||
let composeViewModel = ComposeViewModel(
|
||||
context: self.context,
|
||||
composeKind: .reply(status: status),
|
||||
authContext: authContext
|
||||
authContext: authContext,
|
||||
kind: .reply(status: status)
|
||||
)
|
||||
self.coordinator.present(
|
||||
_ = self.coordinator.present(
|
||||
scene: .compose(viewModel: composeViewModel),
|
||||
from: self,
|
||||
transition: .modal(animated: true, completion: nil)
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
//
|
||||
// AutoCompleteViewModel+Diffable.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-5-17.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension AutoCompleteViewModel {
|
||||
|
||||
func setupDiffableDataSource(
|
||||
for tableView: UITableView
|
||||
) {
|
||||
diffableDataSource = AutoCompleteSection.tableViewDiffableDataSource(for: tableView)
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<AutoCompleteSection, AutoCompleteItem>()
|
||||
snapshot.appendSections([.main])
|
||||
diffableDataSource?.apply(snapshot)
|
||||
}
|
||||
|
||||
}
|
|
@ -13,66 +13,66 @@ import MastodonCore
|
|||
import MastodonUI
|
||||
import MastodonLocalization
|
||||
|
||||
protocol ComposeStatusPollExpiresOptionCollectionViewCellDelegate: AnyObject {
|
||||
func composeStatusPollExpiresOptionCollectionViewCell(_ cell: ComposeStatusPollExpiresOptionCollectionViewCell, didSelectExpiresOption expiresOption: ComposeStatusPollItem.PollExpiresOptionAttribute.ExpiresOption)
|
||||
}
|
||||
|
||||
final class ComposeStatusPollExpiresOptionCollectionViewCell: UICollectionViewCell {
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
weak var delegate: ComposeStatusPollExpiresOptionCollectionViewCellDelegate?
|
||||
|
||||
let durationButton: UIButton = {
|
||||
let button = HighlightDimmableButton()
|
||||
button.titleLabel?.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 12))
|
||||
button.expandEdgeInsets = UIEdgeInsets(top: 0, left: -10, bottom: -20, right: -20)
|
||||
button.setTitle(L10n.Scene.Compose.Poll.durationTime(L10n.Scene.Compose.Poll.thirtyMinutes), for: .normal)
|
||||
button.setTitleColor(Asset.Colors.brand.color, for: .normal)
|
||||
return button
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
_init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
_init()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ComposeStatusPollExpiresOptionCollectionViewCell {
|
||||
|
||||
private typealias ExpiresOption = ComposeStatusPollItem.PollExpiresOptionAttribute.ExpiresOption
|
||||
|
||||
private func _init() {
|
||||
durationButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addSubview(durationButton)
|
||||
NSLayoutConstraint.activate([
|
||||
durationButton.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||
durationButton.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor, constant: PollOptionView.checkmarkBackgroundLeadingMargin),
|
||||
durationButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
])
|
||||
|
||||
let children = ExpiresOption.allCases.map { expiresOption -> UIAction in
|
||||
UIAction(title: expiresOption.title, image: nil, identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] action in
|
||||
guard let self = self else { return }
|
||||
self.expiresOptionActionHandler(action, expiresOption: expiresOption)
|
||||
}
|
||||
}
|
||||
durationButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: .displayInline, children: children)
|
||||
durationButton.showsMenuAsPrimaryAction = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ComposeStatusPollExpiresOptionCollectionViewCell {
|
||||
|
||||
private func expiresOptionActionHandler(_ sender: UIAction, expiresOption: ExpiresOption) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: select %s", ((#file as NSString).lastPathComponent), #line, #function, expiresOption.title)
|
||||
delegate?.composeStatusPollExpiresOptionCollectionViewCell(self, didSelectExpiresOption: expiresOption)
|
||||
}
|
||||
|
||||
}
|
||||
//protocol ComposeStatusPollExpiresOptionCollectionViewCellDelegate: AnyObject {
|
||||
// func composeStatusPollExpiresOptionCollectionViewCell(_ cell: ComposeStatusPollExpiresOptionCollectionViewCell, didSelectExpiresOption expiresOption: ComposeStatusPollItem.PollExpiresOptionAttribute.ExpiresOption)
|
||||
//}
|
||||
//
|
||||
//final class ComposeStatusPollExpiresOptionCollectionViewCell: UICollectionViewCell {
|
||||
//
|
||||
// var disposeBag = Set<AnyCancellable>()
|
||||
// weak var delegate: ComposeStatusPollExpiresOptionCollectionViewCellDelegate?
|
||||
//
|
||||
// let durationButton: UIButton = {
|
||||
// let button = HighlightDimmableButton()
|
||||
// button.titleLabel?.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 12))
|
||||
// button.expandEdgeInsets = UIEdgeInsets(top: 0, left: -10, bottom: -20, right: -20)
|
||||
// button.setTitle(L10n.Scene.Compose.Poll.durationTime(L10n.Scene.Compose.Poll.thirtyMinutes), for: .normal)
|
||||
// button.setTitleColor(Asset.Colors.brand.color, for: .normal)
|
||||
// return button
|
||||
// }()
|
||||
//
|
||||
// override init(frame: CGRect) {
|
||||
// super.init(frame: frame)
|
||||
// _init()
|
||||
// }
|
||||
//
|
||||
// required init?(coder: NSCoder) {
|
||||
// super.init(coder: coder)
|
||||
// _init()
|
||||
// }
|
||||
//
|
||||
//}
|
||||
//
|
||||
//extension ComposeStatusPollExpiresOptionCollectionViewCell {
|
||||
//
|
||||
// private typealias ExpiresOption = ComposeStatusPollItem.PollExpiresOptionAttribute.ExpiresOption
|
||||
//
|
||||
// private func _init() {
|
||||
// durationButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
// contentView.addSubview(durationButton)
|
||||
// NSLayoutConstraint.activate([
|
||||
// durationButton.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||
// durationButton.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor, constant: PollOptionView.checkmarkBackgroundLeadingMargin),
|
||||
// durationButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
// ])
|
||||
//
|
||||
// let children = ExpiresOption.allCases.map { expiresOption -> UIAction in
|
||||
// UIAction(title: expiresOption.title, image: nil, identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] action in
|
||||
// guard let self = self else { return }
|
||||
// self.expiresOptionActionHandler(action, expiresOption: expiresOption)
|
||||
// }
|
||||
// }
|
||||
// durationButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: .displayInline, children: children)
|
||||
// durationButton.showsMenuAsPrimaryAction = true
|
||||
// }
|
||||
//
|
||||
//}
|
||||
//
|
||||
//extension ComposeStatusPollExpiresOptionCollectionViewCell {
|
||||
//
|
||||
// private func expiresOptionActionHandler(_ sender: UIAction, expiresOption: ExpiresOption) {
|
||||
// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: select %s", ((#file as NSString).lastPathComponent), #line, #function, expiresOption.title)
|
||||
// delegate?.composeStatusPollExpiresOptionCollectionViewCell(self, didSelectExpiresOption: expiresOption)
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -18,497 +18,473 @@ import MastodonSDK
|
|||
|
||||
extension ComposeViewModel {
|
||||
|
||||
func setupDataSource(
|
||||
tableView: UITableView,
|
||||
metaTextDelegate: MetaTextDelegate,
|
||||
metaTextViewDelegate: UITextViewDelegate,
|
||||
customEmojiPickerInputViewModel: CustomEmojiPickerInputViewModel,
|
||||
composeStatusAttachmentCollectionViewCellDelegate: ComposeStatusAttachmentCollectionViewCellDelegate,
|
||||
composeStatusPollOptionCollectionViewCellDelegate: ComposeStatusPollOptionCollectionViewCellDelegate,
|
||||
composeStatusPollOptionAppendEntryCollectionViewCellDelegate: ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate,
|
||||
composeStatusPollExpiresOptionCollectionViewCellDelegate: ComposeStatusPollExpiresOptionCollectionViewCellDelegate
|
||||
) {
|
||||
// UI
|
||||
bind()
|
||||
|
||||
// content
|
||||
bind(cell: composeStatusContentTableViewCell, tableView: tableView)
|
||||
composeStatusContentTableViewCell.metaText.delegate = metaTextDelegate
|
||||
composeStatusContentTableViewCell.metaText.textView.delegate = metaTextViewDelegate
|
||||
|
||||
// attachment
|
||||
bind(cell: composeStatusAttachmentTableViewCell, tableView: tableView)
|
||||
composeStatusAttachmentTableViewCell.composeStatusAttachmentCollectionViewCellDelegate = composeStatusAttachmentCollectionViewCellDelegate
|
||||
|
||||
// poll
|
||||
bind(cell: composeStatusPollTableViewCell, tableView: tableView)
|
||||
composeStatusPollTableViewCell.delegate = self
|
||||
composeStatusPollTableViewCell.customEmojiPickerInputViewModel = customEmojiPickerInputViewModel
|
||||
composeStatusPollTableViewCell.composeStatusPollOptionCollectionViewCellDelegate = composeStatusPollOptionCollectionViewCellDelegate
|
||||
composeStatusPollTableViewCell.composeStatusPollOptionAppendEntryCollectionViewCellDelegate = composeStatusPollOptionAppendEntryCollectionViewCellDelegate
|
||||
composeStatusPollTableViewCell.composeStatusPollExpiresOptionCollectionViewCellDelegate = composeStatusPollExpiresOptionCollectionViewCellDelegate
|
||||
|
||||
// setup data source
|
||||
tableView.dataSource = self
|
||||
}
|
||||
|
||||
func setupCustomEmojiPickerDiffableDataSource(
|
||||
for collectionView: UICollectionView,
|
||||
dependency: NeedsDependency
|
||||
) {
|
||||
let diffableDataSource = CustomEmojiPickerSection.collectionViewDiffableDataSource(
|
||||
for: collectionView,
|
||||
dependency: dependency
|
||||
)
|
||||
self.customEmojiPickerDiffableDataSource = diffableDataSource
|
||||
|
||||
let _domain = customEmojiViewModel?.domain
|
||||
customEmojiViewModel?.emojis
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self, weak diffableDataSource] emojis in
|
||||
guard let _ = self else { return }
|
||||
guard let diffableDataSource = diffableDataSource else { return }
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<CustomEmojiPickerSection, CustomEmojiPickerItem>()
|
||||
let domain = _domain?.uppercased() ?? " "
|
||||
let customEmojiSection = CustomEmojiPickerSection.emoji(name: domain)
|
||||
snapshot.appendSections([customEmojiSection])
|
||||
let items: [CustomEmojiPickerItem] = {
|
||||
var items = [CustomEmojiPickerItem]()
|
||||
for emoji in emojis where emoji.visibleInPicker {
|
||||
let attribute = CustomEmojiPickerItem.CustomEmojiAttribute(emoji: emoji)
|
||||
let item = CustomEmojiPickerItem.emoji(attribute: attribute)
|
||||
items.append(item)
|
||||
}
|
||||
return items
|
||||
}()
|
||||
snapshot.appendItems(items, toSection: customEmojiSection)
|
||||
|
||||
diffableDataSource.apply(snapshot)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
// func setupDataSource(
|
||||
// tableView: UITableView,
|
||||
// metaTextDelegate: MetaTextDelegate,
|
||||
// metaTextViewDelegate: UITextViewDelegate,
|
||||
// customEmojiPickerInputViewModel: CustomEmojiPickerInputViewModel,
|
||||
// composeStatusAttachmentCollectionViewCellDelegate: ComposeStatusAttachmentCollectionViewCellDelegate,
|
||||
// composeStatusPollOptionCollectionViewCellDelegate: ComposeStatusPollOptionCollectionViewCellDelegate,
|
||||
// composeStatusPollOptionAppendEntryCollectionViewCellDelegate: ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate,
|
||||
// composeStatusPollExpiresOptionCollectionViewCellDelegate: ComposeStatusPollExpiresOptionCollectionViewCellDelegate
|
||||
// ) {
|
||||
// // UI
|
||||
// bind()
|
||||
//
|
||||
// // content
|
||||
// bind(cell: composeStatusContentTableViewCell, tableView: tableView)
|
||||
// composeStatusContentTableViewCell.metaText.delegate = metaTextDelegate
|
||||
// composeStatusContentTableViewCell.metaText.textView.delegate = metaTextViewDelegate
|
||||
//
|
||||
// // attachment
|
||||
// bind(cell: composeStatusAttachmentTableViewCell, tableView: tableView)
|
||||
// composeStatusAttachmentTableViewCell.composeStatusAttachmentCollectionViewCellDelegate = composeStatusAttachmentCollectionViewCellDelegate
|
||||
//
|
||||
// // poll
|
||||
// bind(cell: composeStatusPollTableViewCell, tableView: tableView)
|
||||
// composeStatusPollTableViewCell.delegate = self
|
||||
// composeStatusPollTableViewCell.customEmojiPickerInputViewModel = customEmojiPickerInputViewModel
|
||||
// composeStatusPollTableViewCell.composeStatusPollOptionCollectionViewCellDelegate = composeStatusPollOptionCollectionViewCellDelegate
|
||||
// composeStatusPollTableViewCell.composeStatusPollOptionAppendEntryCollectionViewCellDelegate = composeStatusPollOptionAppendEntryCollectionViewCellDelegate
|
||||
// composeStatusPollTableViewCell.composeStatusPollExpiresOptionCollectionViewCellDelegate = composeStatusPollExpiresOptionCollectionViewCellDelegate
|
||||
//
|
||||
// // setup data source
|
||||
// tableView.dataSource = self
|
||||
// }
|
||||
//
|
||||
// func setupCustomEmojiPickerDiffableDataSource(
|
||||
// for collectionView: UICollectionView,
|
||||
// dependency: NeedsDependency
|
||||
// ) {
|
||||
// let diffableDataSource = CustomEmojiPickerSection.collectionViewDiffableDataSource(
|
||||
// for: collectionView,
|
||||
// dependency: dependency
|
||||
// )
|
||||
// self.customEmojiPickerDiffableDataSource = diffableDataSource
|
||||
//
|
||||
// let _domain = customEmojiViewModel?.domain
|
||||
// customEmojiViewModel?.emojis
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak self, weak diffableDataSource] emojis in
|
||||
// guard let _ = self else { return }
|
||||
// guard let diffableDataSource = diffableDataSource else { return }
|
||||
//
|
||||
// var snapshot = NSDiffableDataSourceSnapshot<CustomEmojiPickerSection, CustomEmojiPickerItem>()
|
||||
// let domain = _domain?.uppercased() ?? " "
|
||||
// let customEmojiSection = CustomEmojiPickerSection.emoji(name: domain)
|
||||
// snapshot.appendSections([customEmojiSection])
|
||||
// let items: [CustomEmojiPickerItem] = {
|
||||
// var items = [CustomEmojiPickerItem]()
|
||||
// for emoji in emojis where emoji.visibleInPicker {
|
||||
// let attribute = CustomEmojiPickerItem.CustomEmojiAttribute(emoji: emoji)
|
||||
// let item = CustomEmojiPickerItem.emoji(attribute: attribute)
|
||||
// items.append(item)
|
||||
// }
|
||||
// return items
|
||||
// }()
|
||||
// snapshot.appendItems(items, toSection: customEmojiSection)
|
||||
//
|
||||
// diffableDataSource.apply(snapshot)
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDataSource
|
||||
extension ComposeViewModel: UITableViewDataSource {
|
||||
//// MARK: - UITableViewDataSource
|
||||
//extension ComposeViewModel: UITableViewDataSource {
|
||||
|
||||
enum Section: CaseIterable {
|
||||
case repliedTo
|
||||
case status
|
||||
case attachment
|
||||
case poll
|
||||
}
|
||||
// func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
// switch Section.allCases[indexPath.section] {
|
||||
// case .repliedTo:
|
||||
// let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ComposeRepliedToStatusContentTableViewCell.self), for: indexPath) as! ComposeRepliedToStatusContentTableViewCell
|
||||
// guard case let .reply(record) = composeKind else { return cell }
|
||||
//
|
||||
// // bind frame publisher
|
||||
// cell.framePublisher
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .assign(to: \.repliedToCellFrame, on: self)
|
||||
// .store(in: &cell.disposeBag)
|
||||
//
|
||||
// // set initial width
|
||||
// if cell.statusView.frame.width == .zero {
|
||||
// cell.statusView.frame.size.width = tableView.frame.width
|
||||
// }
|
||||
//
|
||||
// // configure status
|
||||
// context.managedObjectContext.performAndWait {
|
||||
// guard let replyTo = record.object(in: context.managedObjectContext) else { return }
|
||||
// cell.statusView.configure(status: replyTo)
|
||||
// }
|
||||
//
|
||||
// return cell
|
||||
// case .status:
|
||||
// return composeStatusContentTableViewCell
|
||||
// case .attachment:
|
||||
// return composeStatusAttachmentTableViewCell
|
||||
// case .poll:
|
||||
// return composeStatusPollTableViewCell
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return Section.allCases.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
switch Section.allCases[section] {
|
||||
case .repliedTo:
|
||||
switch composeKind {
|
||||
case .reply: return 1
|
||||
default: return 0
|
||||
}
|
||||
case .status: return 1
|
||||
case .attachment: return 1
|
||||
case .poll: return 1
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
switch Section.allCases[indexPath.section] {
|
||||
case .repliedTo:
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ComposeRepliedToStatusContentTableViewCell.self), for: indexPath) as! ComposeRepliedToStatusContentTableViewCell
|
||||
guard case let .reply(record) = composeKind else { return cell }
|
||||
|
||||
// bind frame publisher
|
||||
cell.framePublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.assign(to: \.repliedToCellFrame, on: self)
|
||||
.store(in: &cell.disposeBag)
|
||||
|
||||
// set initial width
|
||||
if cell.statusView.frame.width == .zero {
|
||||
cell.statusView.frame.size.width = tableView.frame.width
|
||||
}
|
||||
|
||||
// configure status
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let replyTo = record.object(in: context.managedObjectContext) else { return }
|
||||
cell.statusView.configure(status: replyTo)
|
||||
}
|
||||
|
||||
return cell
|
||||
case .status:
|
||||
return composeStatusContentTableViewCell
|
||||
case .attachment:
|
||||
return composeStatusAttachmentTableViewCell
|
||||
case .poll:
|
||||
return composeStatusPollTableViewCell
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ComposeStatusPollTableViewCellDelegate
|
||||
extension ComposeViewModel: ComposeStatusPollTableViewCellDelegate {
|
||||
func composeStatusPollTableViewCell(_ cell: ComposeStatusPollTableViewCell, pollOptionAttributesDidReorder options: [ComposeStatusPollItem.PollOptionAttribute]) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
|
||||
self.pollOptionAttributes = options
|
||||
}
|
||||
}
|
||||
|
||||
extension ComposeViewModel {
|
||||
private func bind() {
|
||||
$isCustomEmojiComposing
|
||||
.assign(to: \.value, on: customEmojiPickerInputViewModel.isCustomEmojiComposing)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
$isContentWarningComposing
|
||||
.assign(to: \.isContentWarningComposing, on: composeStatusAttribute)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// bind compose toolbar UI state
|
||||
Publishers.CombineLatest(
|
||||
$isPollComposing,
|
||||
$attachmentServices
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { [weak self] isPollComposing, attachmentServices in
|
||||
guard let self = self else { return }
|
||||
let shouldMediaDisable = isPollComposing || attachmentServices.count >= self.maxMediaAttachments
|
||||
let shouldPollDisable = attachmentServices.count > 0
|
||||
|
||||
self.isMediaToolbarButtonEnabled = !shouldMediaDisable
|
||||
self.isPollToolbarButtonEnabled = !shouldPollDisable
|
||||
})
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// calculate `Idempotency-Key`
|
||||
let content = Publishers.CombineLatest3(
|
||||
composeStatusAttribute.$isContentWarningComposing,
|
||||
composeStatusAttribute.$contentWarningContent,
|
||||
composeStatusAttribute.$composeContent
|
||||
)
|
||||
.map { isContentWarningComposing, contentWarningContent, composeContent -> String in
|
||||
if isContentWarningComposing {
|
||||
return contentWarningContent + (composeContent ?? "")
|
||||
} else {
|
||||
return composeContent ?? ""
|
||||
}
|
||||
}
|
||||
let attachmentIDs = $attachmentServices.map { attachments -> String in
|
||||
let attachmentIDs = attachments.compactMap { $0.attachment.value?.id }
|
||||
return attachmentIDs.joined(separator: ",")
|
||||
}
|
||||
let pollOptionsAndDuration = Publishers.CombineLatest3(
|
||||
$isPollComposing,
|
||||
$pollOptionAttributes,
|
||||
pollExpiresOptionAttribute.expiresOption
|
||||
)
|
||||
.map { isPollComposing, pollOptionAttributes, expiresOption -> String in
|
||||
guard isPollComposing else {
|
||||
return ""
|
||||
}
|
||||
|
||||
let pollOptions = pollOptionAttributes.map { $0.option.value }.joined(separator: ",")
|
||||
return pollOptions + expiresOption.rawValue
|
||||
}
|
||||
|
||||
Publishers.CombineLatest4(
|
||||
content,
|
||||
attachmentIDs,
|
||||
pollOptionsAndDuration,
|
||||
$selectedStatusVisibility
|
||||
)
|
||||
.map { content, attachmentIDs, pollOptionsAndDuration, selectedStatusVisibility -> String in
|
||||
var hasher = Hasher()
|
||||
hasher.combine(content)
|
||||
hasher.combine(attachmentIDs)
|
||||
hasher.combine(pollOptionsAndDuration)
|
||||
hasher.combine(selectedStatusVisibility.visibility.rawValue)
|
||||
let hashValue = hasher.finalize()
|
||||
return "\(hashValue)"
|
||||
}
|
||||
.assign(to: \.value, on: idempotencyKey)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// bind modal dismiss state
|
||||
composeStatusAttribute.$composeContent
|
||||
.receive(on: DispatchQueue.main)
|
||||
.map { [weak self] content in
|
||||
let content = content ?? ""
|
||||
if content.isEmpty {
|
||||
return true
|
||||
}
|
||||
// if preInsertedContent plus a space is equal to the content, simply dismiss the modal
|
||||
if let preInsertedContent = self?.preInsertedContent {
|
||||
return content == preInsertedContent
|
||||
}
|
||||
return false
|
||||
}
|
||||
.assign(to: &$shouldDismiss)
|
||||
|
||||
// bind compose bar button item UI state
|
||||
let isComposeContentEmpty = composeStatusAttribute.$composeContent
|
||||
.map { ($0 ?? "").isEmpty }
|
||||
let isComposeContentValid = $characterCount
|
||||
.compactMap { [weak self] characterCount -> Bool in
|
||||
guard let self = self else { return characterCount <= 500 }
|
||||
return characterCount <= self.composeContentLimit
|
||||
}
|
||||
let isMediaEmpty = $attachmentServices
|
||||
.map { $0.isEmpty }
|
||||
let isMediaUploadAllSuccess = $attachmentServices
|
||||
.map { services in
|
||||
services.allSatisfy { $0.uploadStateMachineSubject.value is MastodonAttachmentService.UploadState.Finish }
|
||||
}
|
||||
let isPollAttributeAllValid = $pollOptionAttributes
|
||||
.map { pollAttributes in
|
||||
pollAttributes.allSatisfy { attribute -> Bool in
|
||||
!attribute.option.value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
let isPublishBarButtonItemEnabledPrecondition1 = Publishers.CombineLatest4(
|
||||
isComposeContentEmpty,
|
||||
isComposeContentValid,
|
||||
isMediaEmpty,
|
||||
isMediaUploadAllSuccess
|
||||
)
|
||||
.map { isComposeContentEmpty, isComposeContentValid, isMediaEmpty, isMediaUploadAllSuccess -> Bool in
|
||||
if isMediaEmpty {
|
||||
return isComposeContentValid && !isComposeContentEmpty
|
||||
} else {
|
||||
return isComposeContentValid && isMediaUploadAllSuccess
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
let isPublishBarButtonItemEnabledPrecondition2 = Publishers.CombineLatest4(
|
||||
isComposeContentEmpty,
|
||||
isComposeContentValid,
|
||||
$isPollComposing,
|
||||
isPollAttributeAllValid
|
||||
)
|
||||
.map { isComposeContentEmpty, isComposeContentValid, isPollComposing, isPollAttributeAllValid -> Bool in
|
||||
if isPollComposing {
|
||||
return isComposeContentValid && !isComposeContentEmpty && isPollAttributeAllValid
|
||||
} else {
|
||||
return isComposeContentValid && !isComposeContentEmpty
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
Publishers.CombineLatest(
|
||||
isPublishBarButtonItemEnabledPrecondition1,
|
||||
isPublishBarButtonItemEnabledPrecondition2
|
||||
)
|
||||
.map { $0 && $1 }
|
||||
.assign(to: &$isPublishBarButtonItemEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
extension ComposeViewModel {
|
||||
private func bind(
|
||||
cell: ComposeStatusContentTableViewCell,
|
||||
tableView: UITableView
|
||||
) {
|
||||
// bind status content character count
|
||||
Publishers.CombineLatest3(
|
||||
composeStatusAttribute.$composeContent,
|
||||
composeStatusAttribute.$isContentWarningComposing,
|
||||
composeStatusAttribute.$contentWarningContent
|
||||
)
|
||||
.map { composeContent, isContentWarningComposing, contentWarningContent -> Int in
|
||||
let composeContent = composeContent ?? ""
|
||||
var count = composeContent.count
|
||||
if isContentWarningComposing {
|
||||
count += contentWarningContent.count
|
||||
}
|
||||
return count
|
||||
}
|
||||
.assign(to: &$characterCount)
|
||||
|
||||
// bind content warning
|
||||
composeStatusAttribute.$isContentWarningComposing
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak cell, weak tableView] isContentWarningComposing in
|
||||
guard let cell = cell else { return }
|
||||
guard let tableView = tableView else { return }
|
||||
|
||||
// self size input cell
|
||||
cell.statusContentWarningEditorView.isHidden = !isContentWarningComposing
|
||||
cell.statusContentWarningEditorView.alpha = 0
|
||||
UIView.animate(withDuration: 0.33, delay: 0, options: [.curveEaseOut]) {
|
||||
cell.statusContentWarningEditorView.alpha = 1
|
||||
tableView.beginUpdates()
|
||||
tableView.endUpdates()
|
||||
} completion: { _ in
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
cell.contentWarningContent
|
||||
.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak tableView, weak self] text in
|
||||
guard let self = self else { return }
|
||||
// bind input data
|
||||
self.composeStatusAttribute.contentWarningContent = text
|
||||
|
||||
// self size input cell
|
||||
guard let tableView = tableView else { return }
|
||||
UIView.performWithoutAnimation {
|
||||
tableView.beginUpdates()
|
||||
tableView.endUpdates()
|
||||
}
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
|
||||
// configure custom emoji picker
|
||||
ComposeStatusSection.configureCustomEmojiPicker(
|
||||
viewModel: customEmojiPickerInputViewModel,
|
||||
customEmojiReplaceableTextInput: cell.metaText.textView,
|
||||
disposeBag: &disposeBag
|
||||
)
|
||||
ComposeStatusSection.configureCustomEmojiPicker(
|
||||
viewModel: customEmojiPickerInputViewModel,
|
||||
customEmojiReplaceableTextInput: cell.statusContentWarningEditorView.textView,
|
||||
disposeBag: &disposeBag
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension ComposeViewModel {
|
||||
private func bind(
|
||||
cell: ComposeStatusPollTableViewCell,
|
||||
tableView: UITableView
|
||||
) {
|
||||
Publishers.CombineLatest(
|
||||
$isPollComposing,
|
||||
$pollOptionAttributes
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] isPollComposing, pollOptionAttributes in
|
||||
guard let self = self else { return }
|
||||
guard self.isViewAppeared else { return }
|
||||
|
||||
let cell = self.composeStatusPollTableViewCell
|
||||
guard let dataSource = cell.dataSource else { return }
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<ComposeStatusPollSection, ComposeStatusPollItem>()
|
||||
snapshot.appendSections([.main])
|
||||
var items: [ComposeStatusPollItem] = []
|
||||
if isPollComposing {
|
||||
for attribute in pollOptionAttributes {
|
||||
items.append(.pollOption(attribute: attribute))
|
||||
}
|
||||
if pollOptionAttributes.count < self.maxPollOptions {
|
||||
items.append(.pollOptionAppendEntry)
|
||||
}
|
||||
items.append(.pollExpiresOption(attribute: self.pollExpiresOptionAttribute))
|
||||
}
|
||||
snapshot.appendItems(items, toSection: .main)
|
||||
|
||||
tableView.performBatchUpdates {
|
||||
if #available(iOS 15.0, *) {
|
||||
dataSource.apply(snapshot, animatingDifferences: false)
|
||||
} else {
|
||||
dataSource.apply(snapshot, animatingDifferences: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// bind delegate
|
||||
$pollOptionAttributes
|
||||
.sink { [weak self] pollAttributes in
|
||||
guard let self = self else { return }
|
||||
pollAttributes.forEach { $0.delegate = self }
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
}
|
||||
|
||||
extension ComposeViewModel {
|
||||
private func bind(
|
||||
cell: ComposeStatusAttachmentTableViewCell,
|
||||
tableView: UITableView
|
||||
) {
|
||||
cell.collectionViewHeightDidUpdate
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
guard let _ = self else { return }
|
||||
tableView.beginUpdates()
|
||||
tableView.endUpdates()
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
$attachmentServices
|
||||
.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] attachmentServices in
|
||||
guard let self = self else { return }
|
||||
guard self.isViewAppeared else { return }
|
||||
|
||||
let cell = self.composeStatusAttachmentTableViewCell
|
||||
guard let dataSource = cell.dataSource else { return }
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<ComposeStatusAttachmentSection, ComposeStatusAttachmentItem>()
|
||||
snapshot.appendSections([.main])
|
||||
let items = attachmentServices.map { ComposeStatusAttachmentItem.attachment(attachmentService: $0) }
|
||||
snapshot.appendItems(items, toSection: .main)
|
||||
|
||||
if #available(iOS 15.0, *) {
|
||||
dataSource.applySnapshotUsingReloadData(snapshot)
|
||||
} else {
|
||||
dataSource.apply(snapshot, animatingDifferences: false)
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// setup attribute updater
|
||||
$attachmentServices
|
||||
.receive(on: DispatchQueue.main)
|
||||
.debounce(for: 0.3, scheduler: DispatchQueue.main)
|
||||
.sink { attachmentServices in
|
||||
// drive service upload state
|
||||
// make image upload in the queue
|
||||
for attachmentService in attachmentServices {
|
||||
// skip when prefix N task when task finish OR fail OR uploading
|
||||
guard let currentState = attachmentService.uploadStateMachine.currentState else { break }
|
||||
if currentState is MastodonAttachmentService.UploadState.Fail {
|
||||
continue
|
||||
}
|
||||
if currentState is MastodonAttachmentService.UploadState.Finish {
|
||||
continue
|
||||
}
|
||||
if currentState is MastodonAttachmentService.UploadState.Processing {
|
||||
continue
|
||||
}
|
||||
if currentState is MastodonAttachmentService.UploadState.Uploading {
|
||||
break
|
||||
}
|
||||
// trigger uploading one by one
|
||||
if currentState is MastodonAttachmentService.UploadState.Initial {
|
||||
attachmentService.uploadStateMachine.enter(MastodonAttachmentService.UploadState.Uploading.self)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// bind delegate
|
||||
$attachmentServices
|
||||
.sink { [weak self] attachmentServices in
|
||||
guard let self = self else { return }
|
||||
attachmentServices.forEach { $0.delegate = self }
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
}
|
||||
//// MARK: - ComposeStatusPollTableViewCellDelegate
|
||||
//extension ComposeViewModel: ComposeStatusPollTableViewCellDelegate {
|
||||
// func composeStatusPollTableViewCell(_ cell: ComposeStatusPollTableViewCell, pollOptionAttributesDidReorder options: [ComposeStatusPollItem.PollOptionAttribute]) {
|
||||
// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
//
|
||||
// self.pollOptionAttributes = options
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//extension ComposeViewModel {
|
||||
// private func bind() {
|
||||
// $isCustomEmojiComposing
|
||||
// .assign(to: \.value, on: customEmojiPickerInputViewModel.isCustomEmojiComposing)
|
||||
// .store(in: &disposeBag)
|
||||
//
|
||||
// $isContentWarningComposing
|
||||
// .assign(to: \.isContentWarningComposing, on: composeStatusAttribute)
|
||||
// .store(in: &disposeBag)
|
||||
//
|
||||
// // bind compose toolbar UI state
|
||||
// Publishers.CombineLatest(
|
||||
// $isPollComposing,
|
||||
// $attachmentServices
|
||||
// )
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink(receiveValue: { [weak self] isPollComposing, attachmentServices in
|
||||
// guard let self = self else { return }
|
||||
// let shouldMediaDisable = isPollComposing || attachmentServices.count >= self.maxMediaAttachments
|
||||
// let shouldPollDisable = attachmentServices.count > 0
|
||||
//
|
||||
// self.isMediaToolbarButtonEnabled = !shouldMediaDisable
|
||||
// self.isPollToolbarButtonEnabled = !shouldPollDisable
|
||||
// })
|
||||
// .store(in: &disposeBag)
|
||||
//
|
||||
// // calculate `Idempotency-Key`
|
||||
// let content = Publishers.CombineLatest3(
|
||||
// composeStatusAttribute.$isContentWarningComposing,
|
||||
// composeStatusAttribute.$contentWarningContent,
|
||||
// composeStatusAttribute.$composeContent
|
||||
// )
|
||||
// .map { isContentWarningComposing, contentWarningContent, composeContent -> String in
|
||||
// if isContentWarningComposing {
|
||||
// return contentWarningContent + (composeContent ?? "")
|
||||
// } else {
|
||||
// return composeContent ?? ""
|
||||
// }
|
||||
// }
|
||||
// let attachmentIDs = $attachmentServices.map { attachments -> String in
|
||||
// let attachmentIDs = attachments.compactMap { $0.attachment.value?.id }
|
||||
// return attachmentIDs.joined(separator: ",")
|
||||
// }
|
||||
// let pollOptionsAndDuration = Publishers.CombineLatest3(
|
||||
// $isPollComposing,
|
||||
// $pollOptionAttributes,
|
||||
// pollExpiresOptionAttribute.expiresOption
|
||||
// )
|
||||
// .map { isPollComposing, pollOptionAttributes, expiresOption -> String in
|
||||
// guard isPollComposing else {
|
||||
// return ""
|
||||
// }
|
||||
//
|
||||
// let pollOptions = pollOptionAttributes.map { $0.option.value }.joined(separator: ",")
|
||||
// return pollOptions + expiresOption.rawValue
|
||||
// }
|
||||
//
|
||||
// Publishers.CombineLatest4(
|
||||
// content,
|
||||
// attachmentIDs,
|
||||
// pollOptionsAndDuration,
|
||||
// $selectedStatusVisibility
|
||||
// )
|
||||
// .map { content, attachmentIDs, pollOptionsAndDuration, selectedStatusVisibility -> String in
|
||||
// var hasher = Hasher()
|
||||
// hasher.combine(content)
|
||||
// hasher.combine(attachmentIDs)
|
||||
// hasher.combine(pollOptionsAndDuration)
|
||||
// hasher.combine(selectedStatusVisibility.visibility.rawValue)
|
||||
// let hashValue = hasher.finalize()
|
||||
// return "\(hashValue)"
|
||||
// }
|
||||
// .assign(to: \.value, on: idempotencyKey)
|
||||
// .store(in: &disposeBag)
|
||||
//
|
||||
// // bind modal dismiss state
|
||||
// composeStatusAttribute.$composeContent
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .map { [weak self] content in
|
||||
// let content = content ?? ""
|
||||
// if content.isEmpty {
|
||||
// return true
|
||||
// }
|
||||
// // if preInsertedContent plus a space is equal to the content, simply dismiss the modal
|
||||
// if let preInsertedContent = self?.preInsertedContent {
|
||||
// return content == preInsertedContent
|
||||
// }
|
||||
// return false
|
||||
// }
|
||||
// .assign(to: &$shouldDismiss)
|
||||
//
|
||||
// // bind compose bar button item UI state
|
||||
// let isComposeContentEmpty = composeStatusAttribute.$composeContent
|
||||
// .map { ($0 ?? "").isEmpty }
|
||||
// let isComposeContentValid = $characterCount
|
||||
// .compactMap { [weak self] characterCount -> Bool in
|
||||
// guard let self = self else { return characterCount <= 500 }
|
||||
// return characterCount <= self.composeContentLimit
|
||||
// }
|
||||
// let isMediaEmpty = $attachmentServices
|
||||
// .map { $0.isEmpty }
|
||||
// let isMediaUploadAllSuccess = $attachmentServices
|
||||
// .map { services in
|
||||
// services.allSatisfy { $0.uploadStateMachineSubject.value is MastodonAttachmentService.UploadState.Finish }
|
||||
// }
|
||||
// let isPollAttributeAllValid = $pollOptionAttributes
|
||||
// .map { pollAttributes in
|
||||
// pollAttributes.allSatisfy { attribute -> Bool in
|
||||
// !attribute.option.value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// let isPublishBarButtonItemEnabledPrecondition1 = Publishers.CombineLatest4(
|
||||
// isComposeContentEmpty,
|
||||
// isComposeContentValid,
|
||||
// isMediaEmpty,
|
||||
// isMediaUploadAllSuccess
|
||||
// )
|
||||
// .map { isComposeContentEmpty, isComposeContentValid, isMediaEmpty, isMediaUploadAllSuccess -> Bool in
|
||||
// if isMediaEmpty {
|
||||
// return isComposeContentValid && !isComposeContentEmpty
|
||||
// } else {
|
||||
// return isComposeContentValid && isMediaUploadAllSuccess
|
||||
// }
|
||||
// }
|
||||
// .eraseToAnyPublisher()
|
||||
//
|
||||
// let isPublishBarButtonItemEnabledPrecondition2 = Publishers.CombineLatest4(
|
||||
// isComposeContentEmpty,
|
||||
// isComposeContentValid,
|
||||
// $isPollComposing,
|
||||
// isPollAttributeAllValid
|
||||
// )
|
||||
// .map { isComposeContentEmpty, isComposeContentValid, isPollComposing, isPollAttributeAllValid -> Bool in
|
||||
// if isPollComposing {
|
||||
// return isComposeContentValid && !isComposeContentEmpty && isPollAttributeAllValid
|
||||
// } else {
|
||||
// return isComposeContentValid && !isComposeContentEmpty
|
||||
// }
|
||||
// }
|
||||
// .eraseToAnyPublisher()
|
||||
//
|
||||
// Publishers.CombineLatest(
|
||||
// isPublishBarButtonItemEnabledPrecondition1,
|
||||
// isPublishBarButtonItemEnabledPrecondition2
|
||||
// )
|
||||
// .map { $0 && $1 }
|
||||
// .assign(to: &$isPublishBarButtonItemEnabled)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//extension ComposeViewModel {
|
||||
// private func bind(
|
||||
// cell: ComposeStatusContentTableViewCell,
|
||||
// tableView: UITableView
|
||||
// ) {
|
||||
// // bind status content character count
|
||||
// Publishers.CombineLatest3(
|
||||
// composeStatusAttribute.$composeContent,
|
||||
// composeStatusAttribute.$isContentWarningComposing,
|
||||
// composeStatusAttribute.$contentWarningContent
|
||||
// )
|
||||
// .map { composeContent, isContentWarningComposing, contentWarningContent -> Int in
|
||||
// let composeContent = composeContent ?? ""
|
||||
// var count = composeContent.count
|
||||
// if isContentWarningComposing {
|
||||
// count += contentWarningContent.count
|
||||
// }
|
||||
// return count
|
||||
// }
|
||||
// .assign(to: &$characterCount)
|
||||
//
|
||||
// // bind content warning
|
||||
// composeStatusAttribute.$isContentWarningComposing
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak cell, weak tableView] isContentWarningComposing in
|
||||
// guard let cell = cell else { return }
|
||||
// guard let tableView = tableView else { return }
|
||||
//
|
||||
// // self size input cell
|
||||
// cell.statusContentWarningEditorView.isHidden = !isContentWarningComposing
|
||||
// cell.statusContentWarningEditorView.alpha = 0
|
||||
// UIView.animate(withDuration: 0.33, delay: 0, options: [.curveEaseOut]) {
|
||||
// cell.statusContentWarningEditorView.alpha = 1
|
||||
// tableView.beginUpdates()
|
||||
// tableView.endUpdates()
|
||||
// } completion: { _ in
|
||||
// // do nothing
|
||||
// }
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
//
|
||||
// cell.contentWarningContent
|
||||
// .removeDuplicates()
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak tableView, weak self] text in
|
||||
// guard let self = self else { return }
|
||||
// // bind input data
|
||||
// self.composeStatusAttribute.contentWarningContent = text
|
||||
//
|
||||
// // self size input cell
|
||||
// guard let tableView = tableView else { return }
|
||||
// UIView.performWithoutAnimation {
|
||||
// tableView.beginUpdates()
|
||||
// tableView.endUpdates()
|
||||
// }
|
||||
// }
|
||||
// .store(in: &cell.disposeBag)
|
||||
//
|
||||
// // configure custom emoji picker
|
||||
// ComposeStatusSection.configureCustomEmojiPicker(
|
||||
// viewModel: customEmojiPickerInputViewModel,
|
||||
// customEmojiReplaceableTextInput: cell.metaText.textView,
|
||||
// disposeBag: &disposeBag
|
||||
// )
|
||||
// ComposeStatusSection.configureCustomEmojiPicker(
|
||||
// viewModel: customEmojiPickerInputViewModel,
|
||||
// customEmojiReplaceableTextInput: cell.statusContentWarningEditorView.textView,
|
||||
// disposeBag: &disposeBag
|
||||
// )
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//extension ComposeViewModel {
|
||||
// private func bind(
|
||||
// cell: ComposeStatusPollTableViewCell,
|
||||
// tableView: UITableView
|
||||
// ) {
|
||||
// Publishers.CombineLatest(
|
||||
// $isPollComposing,
|
||||
// $pollOptionAttributes
|
||||
// )
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak self] isPollComposing, pollOptionAttributes in
|
||||
// guard let self = self else { return }
|
||||
// guard self.isViewAppeared else { return }
|
||||
//
|
||||
// let cell = self.composeStatusPollTableViewCell
|
||||
// guard let dataSource = cell.dataSource else { return }
|
||||
//
|
||||
// var snapshot = NSDiffableDataSourceSnapshot<ComposeStatusPollSection, ComposeStatusPollItem>()
|
||||
// snapshot.appendSections([.main])
|
||||
// var items: [ComposeStatusPollItem] = []
|
||||
// if isPollComposing {
|
||||
// for attribute in pollOptionAttributes {
|
||||
// items.append(.pollOption(attribute: attribute))
|
||||
// }
|
||||
// if pollOptionAttributes.count < self.maxPollOptions {
|
||||
// items.append(.pollOptionAppendEntry)
|
||||
// }
|
||||
// items.append(.pollExpiresOption(attribute: self.pollExpiresOptionAttribute))
|
||||
// }
|
||||
// snapshot.appendItems(items, toSection: .main)
|
||||
//
|
||||
// tableView.performBatchUpdates {
|
||||
// if #available(iOS 15.0, *) {
|
||||
// dataSource.apply(snapshot, animatingDifferences: false)
|
||||
// } else {
|
||||
// dataSource.apply(snapshot, animatingDifferences: true)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
//
|
||||
// // bind delegate
|
||||
// $pollOptionAttributes
|
||||
// .sink { [weak self] pollAttributes in
|
||||
// guard let self = self else { return }
|
||||
// pollAttributes.forEach { $0.delegate = self }
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//extension ComposeViewModel {
|
||||
// private func bind(
|
||||
// cell: ComposeStatusAttachmentTableViewCell,
|
||||
// tableView: UITableView
|
||||
// ) {
|
||||
// cell.collectionViewHeightDidUpdate
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak self] _ in
|
||||
// guard let _ = self else { return }
|
||||
// tableView.beginUpdates()
|
||||
// tableView.endUpdates()
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
//
|
||||
// $attachmentServices
|
||||
// .removeDuplicates()
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak self] attachmentServices in
|
||||
// guard let self = self else { return }
|
||||
// guard self.isViewAppeared else { return }
|
||||
//
|
||||
// let cell = self.composeStatusAttachmentTableViewCell
|
||||
// guard let dataSource = cell.dataSource else { return }
|
||||
//
|
||||
// var snapshot = NSDiffableDataSourceSnapshot<ComposeStatusAttachmentSection, ComposeStatusAttachmentItem>()
|
||||
// snapshot.appendSections([.main])
|
||||
// let items = attachmentServices.map { ComposeStatusAttachmentItem.attachment(attachmentService: $0) }
|
||||
// snapshot.appendItems(items, toSection: .main)
|
||||
//
|
||||
// if #available(iOS 15.0, *) {
|
||||
// dataSource.applySnapshotUsingReloadData(snapshot)
|
||||
// } else {
|
||||
// dataSource.apply(snapshot, animatingDifferences: false)
|
||||
// }
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
//
|
||||
// // setup attribute updater
|
||||
// $attachmentServices
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .debounce(for: 0.3, scheduler: DispatchQueue.main)
|
||||
// .sink { attachmentServices in
|
||||
// // drive service upload state
|
||||
// // make image upload in the queue
|
||||
// for attachmentService in attachmentServices {
|
||||
// // skip when prefix N task when task finish OR fail OR uploading
|
||||
// guard let currentState = attachmentService.uploadStateMachine.currentState else { break }
|
||||
// if currentState is MastodonAttachmentService.UploadState.Fail {
|
||||
// continue
|
||||
// }
|
||||
// if currentState is MastodonAttachmentService.UploadState.Finish {
|
||||
// continue
|
||||
// }
|
||||
// if currentState is MastodonAttachmentService.UploadState.Processing {
|
||||
// continue
|
||||
// }
|
||||
// if currentState is MastodonAttachmentService.UploadState.Uploading {
|
||||
// break
|
||||
// }
|
||||
// // trigger uploading one by one
|
||||
// if currentState is MastodonAttachmentService.UploadState.Initial {
|
||||
// attachmentService.uploadStateMachine.enter(MastodonAttachmentService.UploadState.Uploading.self)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
//
|
||||
// // bind delegate
|
||||
// $attachmentServices
|
||||
// .sink { [weak self] attachmentServices in
|
||||
// guard let self = self else { return }
|
||||
// attachmentServices.forEach { $0.delegate = self }
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -12,153 +12,153 @@ import CoreDataStack
|
|||
import GameplayKit
|
||||
import MastodonSDK
|
||||
|
||||
extension ComposeViewModel {
|
||||
class PublishState: GKState {
|
||||
weak var viewModel: ComposeViewModel?
|
||||
|
||||
init(viewModel: ComposeViewModel) {
|
||||
self.viewModel = viewModel
|
||||
}
|
||||
|
||||
override func didEnter(from previousState: GKState?) {
|
||||
os_log("%{public}s[%{public}ld], %{public}s: enter %s, previous: %s", ((#file as NSString).lastPathComponent), #line, #function, self.debugDescription, previousState.debugDescription)
|
||||
viewModel?.publishStateMachinePublisher.value = self
|
||||
}
|
||||
}
|
||||
}
|
||||
//extension ComposeViewModel {
|
||||
// class PublishState: GKState {
|
||||
// weak var viewModel: ComposeViewModel?
|
||||
//
|
||||
// init(viewModel: ComposeViewModel) {
|
||||
// self.viewModel = viewModel
|
||||
// }
|
||||
//
|
||||
// override func didEnter(from previousState: GKState?) {
|
||||
// os_log("%{public}s[%{public}ld], %{public}s: enter %s, previous: %s", ((#file as NSString).lastPathComponent), #line, #function, self.debugDescription, previousState.debugDescription)
|
||||
// viewModel?.publishStateMachinePublisher.value = self
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
extension ComposeViewModel.PublishState {
|
||||
class Initial: ComposeViewModel.PublishState {
|
||||
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||
return stateClass == Publishing.self
|
||||
}
|
||||
}
|
||||
|
||||
class Publishing: ComposeViewModel.PublishState {
|
||||
|
||||
var publishingSubscription: AnyCancellable?
|
||||
|
||||
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||
return stateClass == Fail.self || stateClass == Finish.self
|
||||
}
|
||||
|
||||
override func didEnter(from previousState: GKState?) {
|
||||
super.didEnter(from: previousState)
|
||||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||
|
||||
viewModel.updatePublishDate()
|
||||
|
||||
let authenticationBox = viewModel.authenticationBox
|
||||
let domain = authenticationBox.domain
|
||||
let attachmentServices = viewModel.attachmentServices
|
||||
let mediaIDs = attachmentServices.compactMap { attachmentService in
|
||||
attachmentService.attachment.value?.id
|
||||
}
|
||||
let pollOptions: [String]? = {
|
||||
guard viewModel.isPollComposing else { return nil }
|
||||
return viewModel.pollOptionAttributes.map { attribute in attribute.option.value }
|
||||
}()
|
||||
let pollExpiresIn: Int? = {
|
||||
guard viewModel.isPollComposing else { return nil }
|
||||
return viewModel.pollExpiresOptionAttribute.expiresOption.value.seconds
|
||||
}()
|
||||
let inReplyToID: Mastodon.Entity.Status.ID? = {
|
||||
guard case let .reply(status) = viewModel.composeKind else { return nil }
|
||||
var id: Mastodon.Entity.Status.ID?
|
||||
viewModel.context.managedObjectContext.performAndWait {
|
||||
guard let replyTo = status.object(in: viewModel.context.managedObjectContext) else { return }
|
||||
id = replyTo.id
|
||||
}
|
||||
return id
|
||||
}()
|
||||
let sensitive: Bool = viewModel.isContentWarningComposing
|
||||
let spoilerText: String? = {
|
||||
let text = viewModel.composeStatusAttribute.contentWarningContent.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !text.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
return text
|
||||
}()
|
||||
let visibility = viewModel.selectedStatusVisibility.visibility
|
||||
|
||||
let updateMediaQuerySubscriptions: [AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Attachment>, Error>] = {
|
||||
var subscriptions: [AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Attachment>, Error>] = []
|
||||
for attachmentService in attachmentServices {
|
||||
guard let attachmentID = attachmentService.attachment.value?.id else { continue }
|
||||
let description = attachmentService.description.value?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
guard !description.isEmpty else { continue }
|
||||
let query = Mastodon.API.Media.UpdateMediaQuery(
|
||||
file: nil,
|
||||
thumbnail: nil,
|
||||
description: description,
|
||||
focus: nil
|
||||
)
|
||||
let subscription = viewModel.context.apiService.updateMedia(
|
||||
domain: domain,
|
||||
attachmentID: attachmentID,
|
||||
query: query,
|
||||
mastodonAuthenticationBox: authenticationBox
|
||||
)
|
||||
subscriptions.append(subscription)
|
||||
}
|
||||
return subscriptions
|
||||
}()
|
||||
|
||||
let idempotencyKey = viewModel.idempotencyKey.value
|
||||
|
||||
publishingSubscription = Publishers.MergeMany(updateMediaQuerySubscriptions)
|
||||
.collect()
|
||||
.asyncMap { attachments -> Mastodon.Response.Content<Mastodon.Entity.Status> in
|
||||
let query = Mastodon.API.Statuses.PublishStatusQuery(
|
||||
status: viewModel.composeStatusAttribute.composeContent,
|
||||
mediaIDs: mediaIDs.isEmpty ? nil : mediaIDs,
|
||||
pollOptions: pollOptions,
|
||||
pollExpiresIn: pollExpiresIn,
|
||||
inReplyToID: inReplyToID,
|
||||
sensitive: sensitive,
|
||||
spoilerText: spoilerText,
|
||||
visibility: visibility
|
||||
)
|
||||
return try await viewModel.context.apiService.publishStatus(
|
||||
domain: domain,
|
||||
idempotencyKey: idempotencyKey,
|
||||
query: query,
|
||||
authenticationBox: authenticationBox
|
||||
)
|
||||
}
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { completion in
|
||||
switch completion {
|
||||
case .failure(let error):
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: publish status %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
|
||||
stateMachine.enter(Fail.self)
|
||||
case .finished:
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: publish status success", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
stateMachine.enter(Finish.self)
|
||||
}
|
||||
} receiveValue: { response in
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: status %s published: %s", ((#file as NSString).lastPathComponent), #line, #function, response.value.id, response.value.uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Fail: ComposeViewModel.PublishState {
|
||||
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||
// allow discard publishing
|
||||
return stateClass == Publishing.self || stateClass == Discard.self
|
||||
}
|
||||
}
|
||||
|
||||
class Discard: ComposeViewModel.PublishState {
|
||||
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
class Finish: ComposeViewModel.PublishState {
|
||||
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//extension ComposeViewModel.PublishState {
|
||||
// class Initial: ComposeViewModel.PublishState {
|
||||
// override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||
// return stateClass == Publishing.self
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// class Publishing: ComposeViewModel.PublishState {
|
||||
//
|
||||
// var publishingSubscription: AnyCancellable?
|
||||
//
|
||||
// override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||
// return stateClass == Fail.self || stateClass == Finish.self
|
||||
// }
|
||||
//
|
||||
// override func didEnter(from previousState: GKState?) {
|
||||
// super.didEnter(from: previousState)
|
||||
// guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||
//
|
||||
// viewModel.updatePublishDate()
|
||||
//
|
||||
// let authenticationBox = viewModel.authenticationBox
|
||||
// let domain = authenticationBox.domain
|
||||
// let attachmentServices = viewModel.attachmentServices
|
||||
// let mediaIDs = attachmentServices.compactMap { attachmentService in
|
||||
// attachmentService.attachment.value?.id
|
||||
// }
|
||||
// let pollOptions: [String]? = {
|
||||
// guard viewModel.isPollComposing else { return nil }
|
||||
// return viewModel.pollOptionAttributes.map { attribute in attribute.option.value }
|
||||
// }()
|
||||
// let pollExpiresIn: Int? = {
|
||||
// guard viewModel.isPollComposing else { return nil }
|
||||
// return viewModel.pollExpiresOptionAttribute.expiresOption.value.seconds
|
||||
// }()
|
||||
// let inReplyToID: Mastodon.Entity.Status.ID? = {
|
||||
// guard case let .reply(status) = viewModel.composeKind else { return nil }
|
||||
// var id: Mastodon.Entity.Status.ID?
|
||||
// viewModel.context.managedObjectContext.performAndWait {
|
||||
// guard let replyTo = status.object(in: viewModel.context.managedObjectContext) else { return }
|
||||
// id = replyTo.id
|
||||
// }
|
||||
// return id
|
||||
// }()
|
||||
// let sensitive: Bool = viewModel.isContentWarningComposing
|
||||
// let spoilerText: String? = {
|
||||
// let text = viewModel.composeStatusAttribute.contentWarningContent.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
// guard !text.isEmpty else {
|
||||
// return nil
|
||||
// }
|
||||
// return text
|
||||
// }()
|
||||
// let visibility = viewModel.selectedStatusVisibility.visibility
|
||||
//
|
||||
// let updateMediaQuerySubscriptions: [AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Attachment>, Error>] = {
|
||||
// var subscriptions: [AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Attachment>, Error>] = []
|
||||
// for attachmentService in attachmentServices {
|
||||
// guard let attachmentID = attachmentService.attachment.value?.id else { continue }
|
||||
// let description = attachmentService.description.value?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
// guard !description.isEmpty else { continue }
|
||||
// let query = Mastodon.API.Media.UpdateMediaQuery(
|
||||
// file: nil,
|
||||
// thumbnail: nil,
|
||||
// description: description,
|
||||
// focus: nil
|
||||
// )
|
||||
// let subscription = viewModel.context.apiService.updateMedia(
|
||||
// domain: domain,
|
||||
// attachmentID: attachmentID,
|
||||
// query: query,
|
||||
// mastodonAuthenticationBox: authenticationBox
|
||||
// )
|
||||
// subscriptions.append(subscription)
|
||||
// }
|
||||
// return subscriptions
|
||||
// }()
|
||||
//
|
||||
// let idempotencyKey = viewModel.idempotencyKey.value
|
||||
//
|
||||
// publishingSubscription = Publishers.MergeMany(updateMediaQuerySubscriptions)
|
||||
// .collect()
|
||||
// .asyncMap { attachments -> Mastodon.Response.Content<Mastodon.Entity.Status> in
|
||||
// let query = Mastodon.API.Statuses.PublishStatusQuery(
|
||||
// status: viewModel.composeStatusAttribute.composeContent,
|
||||
// mediaIDs: mediaIDs.isEmpty ? nil : mediaIDs,
|
||||
// pollOptions: pollOptions,
|
||||
// pollExpiresIn: pollExpiresIn,
|
||||
// inReplyToID: inReplyToID,
|
||||
// sensitive: sensitive,
|
||||
// spoilerText: spoilerText,
|
||||
// visibility: visibility
|
||||
// )
|
||||
// return try await viewModel.context.apiService.publishStatus(
|
||||
// domain: domain,
|
||||
// idempotencyKey: idempotencyKey,
|
||||
// query: query,
|
||||
// authenticationBox: authenticationBox
|
||||
// )
|
||||
// }
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { completion in
|
||||
// switch completion {
|
||||
// case .failure(let error):
|
||||
// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: publish status %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
|
||||
// stateMachine.enter(Fail.self)
|
||||
// case .finished:
|
||||
// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: publish status success", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
// stateMachine.enter(Finish.self)
|
||||
// }
|
||||
// } receiveValue: { response in
|
||||
// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: status %s published: %s", ((#file as NSString).lastPathComponent), #line, #function, response.value.id, response.value.uri)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// class Fail: ComposeViewModel.PublishState {
|
||||
// override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||
// // allow discard publishing
|
||||
// return stateClass == Publishing.self || stateClass == Discard.self
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// class Discard: ComposeViewModel.PublishState {
|
||||
// override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||
// return false
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// class Finish: ComposeViewModel.PublishState {
|
||||
// override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||
// return false
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
|
|
@ -28,159 +28,159 @@ final class ComposeViewModel: NSObject {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
let composeKind: ComposeStatusSection.ComposeKind
|
||||
let authContext: AuthContext
|
||||
let kind: ComposeContentViewModel.Kind
|
||||
|
||||
var authenticationBox: MastodonAuthenticationBox {
|
||||
authContext.mastodonAuthenticationBox
|
||||
}
|
||||
|
||||
@Published var isPollComposing = false
|
||||
@Published var isCustomEmojiComposing = false
|
||||
@Published var isContentWarningComposing = false
|
||||
|
||||
@Published var selectedStatusVisibility: ComposeToolbarView.VisibilitySelectionType
|
||||
@Published var repliedToCellFrame: CGRect = .zero
|
||||
@Published var autoCompleteRetryLayoutTimes = 0
|
||||
@Published var autoCompleteInfo: ComposeViewController.AutoCompleteInfo? = nil
|
||||
// var authenticationBox: MastodonAuthenticationBox {
|
||||
// authContext.mastodonAuthenticationBox
|
||||
// }
|
||||
//
|
||||
// @Published var isPollComposing = false
|
||||
// @Published var isCustomEmojiComposing = false
|
||||
// @Published var isContentWarningComposing = false
|
||||
//
|
||||
// @Published var selectedStatusVisibility: ComposeToolbarView.VisibilitySelectionType
|
||||
// @Published var repliedToCellFrame: CGRect = .zero
|
||||
// @Published var autoCompleteRetryLayoutTimes = 0
|
||||
// @Published var autoCompleteInfo: ComposeViewController.AutoCompleteInfo? = nil
|
||||
|
||||
let traitCollectionDidChangePublisher = CurrentValueSubject<Void, Never>(Void()) // use CurrentValueSubject to make initial event emit
|
||||
var isViewAppeared = false
|
||||
// let traitCollectionDidChangePublisher = CurrentValueSubject<Void, Never>(Void()) // use CurrentValueSubject to make initial event emit
|
||||
// var isViewAppeared = false
|
||||
|
||||
// output
|
||||
let instanceConfiguration: Mastodon.Entity.Instance.Configuration?
|
||||
var composeContentLimit: Int {
|
||||
guard let maxCharacters = instanceConfiguration?.statuses?.maxCharacters else { return 500 }
|
||||
return max(1, maxCharacters)
|
||||
}
|
||||
var maxMediaAttachments: Int {
|
||||
guard let maxMediaAttachments = instanceConfiguration?.statuses?.maxMediaAttachments else {
|
||||
return 4
|
||||
}
|
||||
// FIXME: update timeline media preview UI
|
||||
return min(4, max(1, maxMediaAttachments))
|
||||
// return max(1, maxMediaAttachments)
|
||||
}
|
||||
var maxPollOptions: Int {
|
||||
guard let maxOptions = instanceConfiguration?.polls?.maxOptions else { return 4 }
|
||||
return max(2, maxOptions)
|
||||
}
|
||||
|
||||
let composeStatusAttribute = ComposeStatusItem.ComposeStatusAttribute()
|
||||
let composeStatusContentTableViewCell = ComposeStatusContentTableViewCell()
|
||||
let composeStatusAttachmentTableViewCell = ComposeStatusAttachmentTableViewCell()
|
||||
let composeStatusPollTableViewCell = ComposeStatusPollTableViewCell()
|
||||
|
||||
// var dataSource: UITableViewDiffableDataSource<ComposeStatusSection, ComposeStatusItem>?
|
||||
var customEmojiPickerDiffableDataSource: UICollectionViewDiffableDataSource<CustomEmojiPickerSection, CustomEmojiPickerItem>?
|
||||
private(set) lazy var publishStateMachine: GKStateMachine = {
|
||||
// exclude timeline middle fetcher state
|
||||
let stateMachine = GKStateMachine(states: [
|
||||
PublishState.Initial(viewModel: self),
|
||||
PublishState.Publishing(viewModel: self),
|
||||
PublishState.Fail(viewModel: self),
|
||||
PublishState.Discard(viewModel: self),
|
||||
PublishState.Finish(viewModel: self),
|
||||
])
|
||||
stateMachine.enter(PublishState.Initial.self)
|
||||
return stateMachine
|
||||
}()
|
||||
private(set) lazy var publishStateMachinePublisher = CurrentValueSubject<PublishState?, Never>(nil)
|
||||
private(set) var publishDate = Date() // update it when enter Publishing state
|
||||
|
||||
// TODO: group post material into Hashable class
|
||||
var idempotencyKey = CurrentValueSubject<String, Never>(UUID().uuidString)
|
||||
|
||||
// UI & UX
|
||||
@Published var title: String
|
||||
@Published var shouldDismiss = true
|
||||
@Published var isPublishBarButtonItemEnabled = false
|
||||
@Published var isMediaToolbarButtonEnabled = true
|
||||
@Published var isPollToolbarButtonEnabled = true
|
||||
@Published var characterCount = 0
|
||||
@Published var collectionViewState: CollectionViewState = .fold
|
||||
|
||||
// for hashtag: "#<hashtag> "
|
||||
// for mention: "@<mention> "
|
||||
var preInsertedContent: String?
|
||||
|
||||
// custom emojis
|
||||
let customEmojiViewModel: EmojiService.CustomEmojiViewModel?
|
||||
let customEmojiPickerInputViewModel = CustomEmojiPickerInputViewModel()
|
||||
@Published var isLoadingCustomEmoji = false
|
||||
|
||||
// attachment
|
||||
@Published var attachmentServices: [MastodonAttachmentService] = []
|
||||
|
||||
// polls
|
||||
@Published var pollOptionAttributes: [ComposeStatusPollItem.PollOptionAttribute] = []
|
||||
let pollExpiresOptionAttribute = ComposeStatusPollItem.PollExpiresOptionAttribute()
|
||||
// let instanceConfiguration: Mastodon.Entity.Instance.Configuration?
|
||||
// var composeContentLimit: Int {
|
||||
// guard let maxCharacters = instanceConfiguration?.statuses?.maxCharacters else { return 500 }
|
||||
// return max(1, maxCharacters)
|
||||
// }
|
||||
// var maxMediaAttachments: Int {
|
||||
// guard let maxMediaAttachments = instanceConfiguration?.statuses?.maxMediaAttachments else {
|
||||
// return 4
|
||||
// }
|
||||
// // FIXME: update timeline media preview UI
|
||||
// return min(4, max(1, maxMediaAttachments))
|
||||
// // return max(1, maxMediaAttachments)
|
||||
// }
|
||||
// var maxPollOptions: Int {
|
||||
// guard let maxOptions = instanceConfiguration?.polls?.maxOptions else { return 4 }
|
||||
// return max(2, maxOptions)
|
||||
// }
|
||||
//
|
||||
// let composeStatusAttribute = ComposeStatusItem.ComposeStatusAttribute()
|
||||
// let composeStatusContentTableViewCell = ComposeStatusContentTableViewCell()
|
||||
// let composeStatusAttachmentTableViewCell = ComposeStatusAttachmentTableViewCell()
|
||||
// let composeStatusPollTableViewCell = ComposeStatusPollTableViewCell()
|
||||
//
|
||||
// // var dataSource: UITableViewDiffableDataSource<ComposeStatusSection, ComposeStatusItem>?
|
||||
// var customEmojiPickerDiffableDataSource: UICollectionViewDiffableDataSource<CustomEmojiPickerSection, CustomEmojiPickerItem>?
|
||||
// private(set) lazy var publishStateMachine: GKStateMachine = {
|
||||
// // exclude timeline middle fetcher state
|
||||
// let stateMachine = GKStateMachine(states: [
|
||||
// PublishState.Initial(viewModel: self),
|
||||
// PublishState.Publishing(viewModel: self),
|
||||
// PublishState.Fail(viewModel: self),
|
||||
// PublishState.Discard(viewModel: self),
|
||||
// PublishState.Finish(viewModel: self),
|
||||
// ])
|
||||
// stateMachine.enter(PublishState.Initial.self)
|
||||
// return stateMachine
|
||||
// }()
|
||||
// private(set) lazy var publishStateMachinePublisher = CurrentValueSubject<PublishState?, Never>(nil)
|
||||
// private(set) var publishDate = Date() // update it when enter Publishing state
|
||||
//
|
||||
// // TODO: group post material into Hashable class
|
||||
// var idempotencyKey = CurrentValueSubject<String, Never>(UUID().uuidString)
|
||||
//
|
||||
// // UI & UX
|
||||
// @Published var title: String
|
||||
// @Published var shouldDismiss = true
|
||||
// @Published var isPublishBarButtonItemEnabled = false
|
||||
// @Published var isMediaToolbarButtonEnabled = true
|
||||
// @Published var isPollToolbarButtonEnabled = true
|
||||
// @Published var characterCount = 0
|
||||
// @Published var collectionViewState: CollectionViewState = .fold
|
||||
//
|
||||
// // for hashtag: "#<hashtag> "
|
||||
// // for mention: "@<mention> "
|
||||
// var preInsertedContent: String?
|
||||
//
|
||||
// // custom emojis
|
||||
// let customEmojiViewModel: EmojiService.CustomEmojiViewModel?
|
||||
// let customEmojiPickerInputViewModel = CustomEmojiPickerInputViewModel()
|
||||
// @Published var isLoadingCustomEmoji = false
|
||||
//
|
||||
// // attachment
|
||||
// @Published var attachmentServices: [MastodonAttachmentService] = []
|
||||
//
|
||||
// // polls
|
||||
// @Published var pollOptionAttributes: [ComposeStatusPollItem.PollOptionAttribute] = []
|
||||
// let pollExpiresOptionAttribute = ComposeStatusPollItem.PollExpiresOptionAttribute()
|
||||
|
||||
init(
|
||||
context: AppContext,
|
||||
composeKind: ComposeStatusSection.ComposeKind,
|
||||
authContext: AuthContext
|
||||
authContext: AuthContext,
|
||||
kind: ComposeContentViewModel.Kind
|
||||
) {
|
||||
self.context = context
|
||||
self.composeKind = composeKind
|
||||
self.authContext = authContext
|
||||
self.kind = kind
|
||||
|
||||
self.title = {
|
||||
switch composeKind {
|
||||
case .post, .hashtag, .mention: return L10n.Scene.Compose.Title.newPost
|
||||
case .reply: return L10n.Scene.Compose.Title.newReply
|
||||
}
|
||||
}()
|
||||
self.selectedStatusVisibility = {
|
||||
// default private when user locked
|
||||
var visibility: ComposeToolbarView.VisibilitySelectionType = {
|
||||
guard let author = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
|
||||
else {
|
||||
return .public
|
||||
}
|
||||
return author.locked ? .private : .public
|
||||
}()
|
||||
// set visibility for reply post
|
||||
switch composeKind {
|
||||
case .reply(let record):
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let status = record.object(in: context.managedObjectContext) else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
let repliedStatusVisibility = status.visibility
|
||||
switch repliedStatusVisibility {
|
||||
case .public, .unlisted:
|
||||
// keep default
|
||||
break
|
||||
case .private:
|
||||
visibility = .private
|
||||
case .direct:
|
||||
visibility = .direct
|
||||
case ._other:
|
||||
assertionFailure()
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
return visibility
|
||||
}()
|
||||
// set limit
|
||||
self.instanceConfiguration = {
|
||||
var configuration: Mastodon.Entity.Instance.Configuration? = nil
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let authentication = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext) else { return }
|
||||
configuration = authentication.instance?.configuration
|
||||
}
|
||||
return configuration
|
||||
}()
|
||||
self.customEmojiViewModel = context.emojiService.dequeueCustomEmojiViewModel(for: authContext.mastodonAuthenticationBox.domain)
|
||||
super.init()
|
||||
// end init
|
||||
|
||||
setup(cell: composeStatusContentTableViewCell)
|
||||
// self.title = {
|
||||
// switch composeKind {
|
||||
// case .post, .hashtag, .mention: return L10n.Scene.Compose.Title.newPost
|
||||
// case .reply: return L10n.Scene.Compose.Title.newReply
|
||||
// }
|
||||
// }()
|
||||
// self.selectedStatusVisibility = {
|
||||
// // default private when user locked
|
||||
// var visibility: ComposeToolbarView.VisibilitySelectionType = {
|
||||
// guard let author = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
|
||||
// else {
|
||||
// return .public
|
||||
// }
|
||||
// return author.locked ? .private : .public
|
||||
// }()
|
||||
// // set visibility for reply post
|
||||
// switch composeKind {
|
||||
// case .reply(let record):
|
||||
// context.managedObjectContext.performAndWait {
|
||||
// guard let status = record.object(in: context.managedObjectContext) else {
|
||||
// assertionFailure()
|
||||
// return
|
||||
// }
|
||||
// let repliedStatusVisibility = status.visibility
|
||||
// switch repliedStatusVisibility {
|
||||
// case .public, .unlisted:
|
||||
// // keep default
|
||||
// break
|
||||
// case .private:
|
||||
// visibility = .private
|
||||
// case .direct:
|
||||
// visibility = .direct
|
||||
// case ._other:
|
||||
// assertionFailure()
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// default:
|
||||
// break
|
||||
// }
|
||||
// return visibility
|
||||
// }()
|
||||
// // set limit
|
||||
// self.instanceConfiguration = {
|
||||
// var configuration: Mastodon.Entity.Instance.Configuration? = nil
|
||||
// context.managedObjectContext.performAndWait {
|
||||
// guard let authentication = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext) else { return }
|
||||
// configuration = authentication.instance?.configuration
|
||||
// }
|
||||
// return configuration
|
||||
// }()
|
||||
// self.customEmojiViewModel = context.emojiService.dequeueCustomEmojiViewModel(for: authContext.mastodonAuthenticationBox.domain)
|
||||
// super.init()
|
||||
// // end init
|
||||
//
|
||||
// setup(cell: composeStatusContentTableViewCell)
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
@ -190,199 +190,192 @@ final class ComposeViewModel: NSObject {
|
|||
}
|
||||
|
||||
extension ComposeViewModel {
|
||||
enum CollectionViewState {
|
||||
case fold // snap to input
|
||||
case expand // snap to reply
|
||||
}
|
||||
// func createNewPollOptionIfPossible() {
|
||||
// guard pollOptionAttributes.count < maxPollOptions else { return }
|
||||
//
|
||||
// let attribute = ComposeStatusPollItem.PollOptionAttribute()
|
||||
// pollOptionAttributes = pollOptionAttributes + [attribute]
|
||||
// }
|
||||
//
|
||||
// func updatePublishDate() {
|
||||
// publishDate = Date()
|
||||
// }
|
||||
}
|
||||
|
||||
extension ComposeViewModel {
|
||||
func createNewPollOptionIfPossible() {
|
||||
guard pollOptionAttributes.count < maxPollOptions else { return }
|
||||
|
||||
let attribute = ComposeStatusPollItem.PollOptionAttribute()
|
||||
pollOptionAttributes = pollOptionAttributes + [attribute]
|
||||
}
|
||||
|
||||
func updatePublishDate() {
|
||||
publishDate = Date()
|
||||
}
|
||||
}
|
||||
|
||||
extension ComposeViewModel {
|
||||
|
||||
enum AttachmentPrecondition: Error, LocalizedError {
|
||||
case videoAttachWithPhoto
|
||||
case moreThanOneVideo
|
||||
|
||||
var errorDescription: String? {
|
||||
return L10n.Common.Alerts.PublishPostFailure.title
|
||||
}
|
||||
|
||||
var failureReason: String? {
|
||||
switch self {
|
||||
case .videoAttachWithPhoto:
|
||||
return L10n.Common.Alerts.PublishPostFailure.AttachmentsMessage.videoAttachWithPhoto
|
||||
case .moreThanOneVideo:
|
||||
return L10n.Common.Alerts.PublishPostFailure.AttachmentsMessage.moreThanOneVideo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check exclusive limit:
|
||||
// - up to 1 video
|
||||
// - up to N photos
|
||||
func checkAttachmentPrecondition() throws {
|
||||
let attachmentServices = self.attachmentServices
|
||||
guard !attachmentServices.isEmpty else { return }
|
||||
var photoAttachmentServices: [MastodonAttachmentService] = []
|
||||
var videoAttachmentServices: [MastodonAttachmentService] = []
|
||||
attachmentServices.forEach { service in
|
||||
guard let file = service.file.value else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
switch file {
|
||||
case .jpeg, .png, .gif:
|
||||
photoAttachmentServices.append(service)
|
||||
case .other:
|
||||
videoAttachmentServices.append(service)
|
||||
}
|
||||
}
|
||||
|
||||
if !videoAttachmentServices.isEmpty {
|
||||
guard videoAttachmentServices.count == 1 else {
|
||||
throw AttachmentPrecondition.moreThanOneVideo
|
||||
}
|
||||
guard photoAttachmentServices.isEmpty else {
|
||||
throw AttachmentPrecondition.videoAttachWithPhoto
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - MastodonAttachmentServiceDelegate
|
||||
extension ComposeViewModel: MastodonAttachmentServiceDelegate {
|
||||
func mastodonAttachmentService(_ service: MastodonAttachmentService, uploadStateDidChange state: MastodonAttachmentService.UploadState?) {
|
||||
// trigger new output event
|
||||
attachmentServices = attachmentServices
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ComposePollAttributeDelegate
|
||||
extension ComposeViewModel: ComposePollAttributeDelegate {
|
||||
func composePollAttribute(_ attribute: ComposeStatusPollItem.PollOptionAttribute, pollOptionDidChange: String?) {
|
||||
// trigger update
|
||||
pollOptionAttributes = pollOptionAttributes
|
||||
}
|
||||
}
|
||||
|
||||
extension ComposeViewModel {
|
||||
private func setup(
|
||||
cell: ComposeStatusContentTableViewCell
|
||||
) {
|
||||
setupStatusHeader(cell: cell)
|
||||
setupStatusAuthor(cell: cell)
|
||||
setupStatusContent(cell: cell)
|
||||
}
|
||||
|
||||
private func setupStatusHeader(
|
||||
cell: ComposeStatusContentTableViewCell
|
||||
) {
|
||||
// configure header
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
managedObjectContext.performAndWait {
|
||||
guard case let .reply(record) = self.composeKind,
|
||||
let replyTo = record.object(in: managedObjectContext)
|
||||
else {
|
||||
cell.statusView.viewModel.header = .none
|
||||
return
|
||||
}
|
||||
|
||||
let info: StatusView.ViewModel.Header.ReplyInfo
|
||||
do {
|
||||
let content = MastodonContent(
|
||||
content: replyTo.author.displayNameWithFallback,
|
||||
emojis: replyTo.author.emojis.asDictionary
|
||||
)
|
||||
let metaContent = try MastodonMetaContent.convert(document: content)
|
||||
info = .init(header: metaContent)
|
||||
} catch {
|
||||
let metaContent = PlaintextMetaContent(string: replyTo.author.displayNameWithFallback)
|
||||
info = .init(header: metaContent)
|
||||
}
|
||||
cell.statusView.viewModel.header = .reply(info: info)
|
||||
}
|
||||
}
|
||||
|
||||
private func setupStatusAuthor(
|
||||
cell: ComposeStatusContentTableViewCell
|
||||
) {
|
||||
self.context.managedObjectContext.performAndWait {
|
||||
guard let author = authenticationBox.authenticationRecord.object(in: self.context.managedObjectContext)?.user else { return }
|
||||
cell.statusView.configureAuthor(author: author)
|
||||
}
|
||||
}
|
||||
|
||||
private func setupStatusContent(
|
||||
cell: ComposeStatusContentTableViewCell
|
||||
) {
|
||||
switch composeKind {
|
||||
case .reply(let record):
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let status = record.object(in: context.managedObjectContext) else { return }
|
||||
let author = self.authenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
|
||||
|
||||
var mentionAccts: [String] = []
|
||||
if author?.id != status.author.id {
|
||||
mentionAccts.append("@" + status.author.acct)
|
||||
}
|
||||
let mentions = status.mentions
|
||||
.filter { author?.id != $0.id }
|
||||
for mention in mentions {
|
||||
let acct = "@" + mention.acct
|
||||
guard !mentionAccts.contains(acct) else { continue }
|
||||
mentionAccts.append(acct)
|
||||
}
|
||||
for acct in mentionAccts {
|
||||
UITextChecker.learnWord(acct)
|
||||
}
|
||||
if let spoilerText = status.spoilerText, !spoilerText.isEmpty {
|
||||
self.isContentWarningComposing = true
|
||||
self.composeStatusAttribute.contentWarningContent = spoilerText
|
||||
}
|
||||
|
||||
let initialComposeContent = mentionAccts.joined(separator: " ")
|
||||
let preInsertedContent: String? = initialComposeContent.isEmpty ? nil : initialComposeContent + " "
|
||||
self.preInsertedContent = preInsertedContent
|
||||
self.composeStatusAttribute.composeContent = preInsertedContent
|
||||
}
|
||||
case .hashtag(let hashtag):
|
||||
let initialComposeContent = "#" + hashtag
|
||||
UITextChecker.learnWord(initialComposeContent)
|
||||
let preInsertedContent = initialComposeContent + " "
|
||||
self.preInsertedContent = preInsertedContent
|
||||
self.composeStatusAttribute.composeContent = preInsertedContent
|
||||
case .mention(let record):
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let user = record.object(in: context.managedObjectContext) else { return }
|
||||
let initialComposeContent = "@" + user.acct
|
||||
UITextChecker.learnWord(initialComposeContent)
|
||||
let preInsertedContent = initialComposeContent + " "
|
||||
self.preInsertedContent = preInsertedContent
|
||||
self.composeStatusAttribute.composeContent = preInsertedContent
|
||||
}
|
||||
case .post:
|
||||
self.preInsertedContent = nil
|
||||
}
|
||||
|
||||
// configure content warning
|
||||
if let composeContent = composeStatusAttribute.composeContent {
|
||||
cell.metaText.textView.text = composeContent
|
||||
}
|
||||
|
||||
// configure content warning
|
||||
cell.statusContentWarningEditorView.textView.text = composeStatusAttribute.contentWarningContent
|
||||
}
|
||||
}
|
||||
//extension ComposeViewModel {
|
||||
//
|
||||
// enum AttachmentPrecondition: Error, LocalizedError {
|
||||
// case videoAttachWithPhoto
|
||||
// case moreThanOneVideo
|
||||
//
|
||||
// var errorDescription: String? {
|
||||
// return L10n.Common.Alerts.PublishPostFailure.title
|
||||
// }
|
||||
//
|
||||
// var failureReason: String? {
|
||||
// switch self {
|
||||
// case .videoAttachWithPhoto:
|
||||
// return L10n.Common.Alerts.PublishPostFailure.AttachmentsMessage.videoAttachWithPhoto
|
||||
// case .moreThanOneVideo:
|
||||
// return L10n.Common.Alerts.PublishPostFailure.AttachmentsMessage.moreThanOneVideo
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // check exclusive limit:
|
||||
// // - up to 1 video
|
||||
// // - up to N photos
|
||||
// func checkAttachmentPrecondition() throws {
|
||||
// let attachmentServices = self.attachmentServices
|
||||
// guard !attachmentServices.isEmpty else { return }
|
||||
// var photoAttachmentServices: [MastodonAttachmentService] = []
|
||||
// var videoAttachmentServices: [MastodonAttachmentService] = []
|
||||
// attachmentServices.forEach { service in
|
||||
// guard let file = service.file.value else {
|
||||
// assertionFailure()
|
||||
// return
|
||||
// }
|
||||
// switch file {
|
||||
// case .jpeg, .png, .gif:
|
||||
// photoAttachmentServices.append(service)
|
||||
// case .other:
|
||||
// videoAttachmentServices.append(service)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if !videoAttachmentServices.isEmpty {
|
||||
// guard videoAttachmentServices.count == 1 else {
|
||||
// throw AttachmentPrecondition.moreThanOneVideo
|
||||
// }
|
||||
// guard photoAttachmentServices.isEmpty else {
|
||||
// throw AttachmentPrecondition.videoAttachWithPhoto
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//}
|
||||
//
|
||||
//// MARK: - MastodonAttachmentServiceDelegate
|
||||
//extension ComposeViewModel: MastodonAttachmentServiceDelegate {
|
||||
// func mastodonAttachmentService(_ service: MastodonAttachmentService, uploadStateDidChange state: MastodonAttachmentService.UploadState?) {
|
||||
// // trigger new output event
|
||||
// attachmentServices = attachmentServices
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// MARK: - ComposePollAttributeDelegate
|
||||
//extension ComposeViewModel: ComposePollAttributeDelegate {
|
||||
// func composePollAttribute(_ attribute: ComposeStatusPollItem.PollOptionAttribute, pollOptionDidChange: String?) {
|
||||
// // trigger update
|
||||
// pollOptionAttributes = pollOptionAttributes
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//extension ComposeViewModel {
|
||||
// private func setup(
|
||||
// cell: ComposeStatusContentTableViewCell
|
||||
// ) {
|
||||
// setupStatusHeader(cell: cell)
|
||||
// setupStatusAuthor(cell: cell)
|
||||
// setupStatusContent(cell: cell)
|
||||
// }
|
||||
//
|
||||
// private func setupStatusHeader(
|
||||
// cell: ComposeStatusContentTableViewCell
|
||||
// ) {
|
||||
// // configure header
|
||||
// let managedObjectContext = context.managedObjectContext
|
||||
// managedObjectContext.performAndWait {
|
||||
// guard case let .reply(record) = self.composeKind,
|
||||
// let replyTo = record.object(in: managedObjectContext)
|
||||
// else {
|
||||
// cell.statusView.viewModel.header = .none
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// let info: StatusView.ViewModel.Header.ReplyInfo
|
||||
// do {
|
||||
// let content = MastodonContent(
|
||||
// content: replyTo.author.displayNameWithFallback,
|
||||
// emojis: replyTo.author.emojis.asDictionary
|
||||
// )
|
||||
// let metaContent = try MastodonMetaContent.convert(document: content)
|
||||
// info = .init(header: metaContent)
|
||||
// } catch {
|
||||
// let metaContent = PlaintextMetaContent(string: replyTo.author.displayNameWithFallback)
|
||||
// info = .init(header: metaContent)
|
||||
// }
|
||||
// cell.statusView.viewModel.header = .reply(info: info)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private func setupStatusAuthor(
|
||||
// cell: ComposeStatusContentTableViewCell
|
||||
// ) {
|
||||
// self.context.managedObjectContext.performAndWait {
|
||||
// guard let author = authenticationBox.authenticationRecord.object(in: self.context.managedObjectContext)?.user else { return }
|
||||
// cell.statusView.configureAuthor(author: author)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private func setupStatusContent(
|
||||
// cell: ComposeStatusContentTableViewCell
|
||||
// ) {
|
||||
// switch composeKind {
|
||||
// case .reply(let record):
|
||||
// context.managedObjectContext.performAndWait {
|
||||
// guard let status = record.object(in: context.managedObjectContext) else { return }
|
||||
// let author = self.authenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
|
||||
//
|
||||
// var mentionAccts: [String] = []
|
||||
// if author?.id != status.author.id {
|
||||
// mentionAccts.append("@" + status.author.acct)
|
||||
// }
|
||||
// let mentions = status.mentions
|
||||
// .filter { author?.id != $0.id }
|
||||
// for mention in mentions {
|
||||
// let acct = "@" + mention.acct
|
||||
// guard !mentionAccts.contains(acct) else { continue }
|
||||
// mentionAccts.append(acct)
|
||||
// }
|
||||
// for acct in mentionAccts {
|
||||
// UITextChecker.learnWord(acct)
|
||||
// }
|
||||
// if let spoilerText = status.spoilerText, !spoilerText.isEmpty {
|
||||
// self.isContentWarningComposing = true
|
||||
// self.composeStatusAttribute.contentWarningContent = spoilerText
|
||||
// }
|
||||
//
|
||||
// let initialComposeContent = mentionAccts.joined(separator: " ")
|
||||
// let preInsertedContent: String? = initialComposeContent.isEmpty ? nil : initialComposeContent + " "
|
||||
// self.preInsertedContent = preInsertedContent
|
||||
// self.composeStatusAttribute.composeContent = preInsertedContent
|
||||
// }
|
||||
// case .hashtag(let hashtag):
|
||||
// let initialComposeContent = "#" + hashtag
|
||||
// UITextChecker.learnWord(initialComposeContent)
|
||||
// let preInsertedContent = initialComposeContent + " "
|
||||
// self.preInsertedContent = preInsertedContent
|
||||
// self.composeStatusAttribute.composeContent = preInsertedContent
|
||||
// case .mention(let record):
|
||||
// context.managedObjectContext.performAndWait {
|
||||
// guard let user = record.object(in: context.managedObjectContext) else { return }
|
||||
// let initialComposeContent = "@" + user.acct
|
||||
// UITextChecker.learnWord(initialComposeContent)
|
||||
// let preInsertedContent = initialComposeContent + " "
|
||||
// self.preInsertedContent = preInsertedContent
|
||||
// self.composeStatusAttribute.composeContent = preInsertedContent
|
||||
// }
|
||||
// case .post:
|
||||
// self.preInsertedContent = nil
|
||||
// }
|
||||
//
|
||||
// // configure content warning
|
||||
// if let composeContent = composeStatusAttribute.composeContent {
|
||||
// cell.metaText.textView.text = composeContent
|
||||
// }
|
||||
//
|
||||
// // configure content warning
|
||||
// cell.statusContentWarningEditorView.textView.text = composeStatusAttribute.contentWarningContent
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -1,171 +0,0 @@
|
|||
//
|
||||
// ComposeStatusAttachmentTableViewCell.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-6-29.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import Combine
|
||||
import AlamofireImage
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
import UIHostingConfigurationBackport
|
||||
|
||||
final class ComposeStatusAttachmentTableViewCell: UITableViewCell {
|
||||
|
||||
private(set) var dataSource: UICollectionViewDiffableDataSource<ComposeStatusAttachmentSection, ComposeStatusAttachmentItem>!
|
||||
weak var composeStatusAttachmentCollectionViewCellDelegate: ComposeStatusAttachmentCollectionViewCellDelegate?
|
||||
var observations = Set<NSKeyValueObservation>()
|
||||
|
||||
private static func createLayout() -> UICollectionViewLayout {
|
||||
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))
|
||||
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
||||
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))
|
||||
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
|
||||
let section = NSCollectionLayoutSection(group: group)
|
||||
section.contentInsetsReference = .readableContent
|
||||
return UICollectionViewCompositionalLayout(section: section)
|
||||
}
|
||||
|
||||
private(set) var collectionViewHeightLayoutConstraint: NSLayoutConstraint!
|
||||
let collectionView: UICollectionView = {
|
||||
let collectionViewLayout = ComposeStatusAttachmentTableViewCell.createLayout()
|
||||
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
|
||||
collectionView.register(ComposeStatusAttachmentCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusAttachmentCollectionViewCell.self))
|
||||
collectionView.backgroundColor = .clear
|
||||
collectionView.alwaysBounceVertical = true
|
||||
collectionView.isScrollEnabled = false
|
||||
return collectionView
|
||||
}()
|
||||
let collectionViewHeightDidUpdate = PassthroughSubject<Void, Never>()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
_init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
_init()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ComposeStatusAttachmentTableViewCell {
|
||||
|
||||
private func _init() {
|
||||
backgroundColor = .clear
|
||||
contentView.backgroundColor = .clear
|
||||
|
||||
collectionView.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addSubview(collectionView)
|
||||
collectionViewHeightLayoutConstraint = collectionView.heightAnchor.constraint(equalToConstant: 200).priority(.defaultHigh)
|
||||
NSLayoutConstraint.activate([
|
||||
collectionView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||
collectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||
collectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||||
collectionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
collectionViewHeightLayoutConstraint,
|
||||
])
|
||||
|
||||
collectionView.observe(\.contentSize, options: [.initial, .new]) { [weak self] collectionView, _ in
|
||||
guard let self = self else { return }
|
||||
self.collectionViewHeightLayoutConstraint.constant = collectionView.contentSize.height
|
||||
self.collectionViewHeightDidUpdate.send()
|
||||
}
|
||||
.store(in: &observations)
|
||||
|
||||
self.dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
|
||||
[weak self] collectionView, indexPath, item -> UICollectionViewCell? in
|
||||
guard let _ = self else { return UICollectionViewCell() }
|
||||
switch item {
|
||||
case .attachment:
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusAttachmentCollectionViewCell.self), for: indexPath) as! ComposeStatusAttachmentCollectionViewCell
|
||||
cell.contentConfiguration = UIHostingConfigurationBackport {
|
||||
HStack {
|
||||
Image(systemName: "star")
|
||||
Text("Favorites")
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
// cell.attachmentContainerView.descriptionTextView.text = attachmentService.description.value
|
||||
// cell.delegate = self.composeStatusAttachmentCollectionViewCellDelegate
|
||||
// attachmentService.thumbnailImage
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak cell] thumbnailImage in
|
||||
// guard let cell = cell else { return }
|
||||
// let size = cell.attachmentContainerView.previewImageView.frame.size != .zero ? cell.attachmentContainerView.previewImageView.frame.size : CGSize(width: 1, height: 1)
|
||||
// guard let image = thumbnailImage else {
|
||||
// let placeholder = UIImage.placeholder(
|
||||
// size: size,
|
||||
// color: ThemeService.shared.currentTheme.value.systemGroupedBackgroundColor
|
||||
// )
|
||||
// .af.imageRounded(
|
||||
// withCornerRadius: AttachmentContainerView.containerViewCornerRadius
|
||||
// )
|
||||
// cell.attachmentContainerView.previewImageView.image = placeholder
|
||||
// return
|
||||
// }
|
||||
// // cannot get correct size. set corner radius on layer
|
||||
// cell.attachmentContainerView.previewImageView.image = image
|
||||
// }
|
||||
// .store(in: &cell.disposeBag)
|
||||
// Publishers.CombineLatest(
|
||||
// attachmentService.uploadStateMachineSubject.eraseToAnyPublisher(),
|
||||
// attachmentService.error.eraseToAnyPublisher()
|
||||
// )
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak cell, weak attachmentService] uploadState, error in
|
||||
// guard let cell = cell else { return }
|
||||
// guard let attachmentService = attachmentService else { return }
|
||||
// cell.attachmentContainerView.emptyStateView.isHidden = error == nil
|
||||
// cell.attachmentContainerView.descriptionBackgroundView.isHidden = error != nil
|
||||
// if let error = error {
|
||||
// cell.attachmentContainerView.activityIndicatorView.stopAnimating()
|
||||
// cell.attachmentContainerView.emptyStateView.label.text = error.localizedDescription
|
||||
// } else {
|
||||
// guard let uploadState = uploadState else { return }
|
||||
// switch uploadState {
|
||||
// case is MastodonAttachmentService.UploadState.Finish:
|
||||
// cell.attachmentContainerView.activityIndicatorView.stopAnimating()
|
||||
// case is MastodonAttachmentService.UploadState.Fail:
|
||||
// cell.attachmentContainerView.activityIndicatorView.stopAnimating()
|
||||
// // FIXME: not display
|
||||
// cell.attachmentContainerView.emptyStateView.label.text = {
|
||||
// if let file = attachmentService.file.value {
|
||||
// switch file {
|
||||
// case .jpeg, .png, .gif:
|
||||
// return L10n.Scene.Compose.Attachment.attachmentBroken(L10n.Scene.Compose.Attachment.photo)
|
||||
// case .other:
|
||||
// return L10n.Scene.Compose.Attachment.attachmentBroken(L10n.Scene.Compose.Attachment.video)
|
||||
// }
|
||||
// } else {
|
||||
// return L10n.Scene.Compose.Attachment.attachmentBroken(L10n.Scene.Compose.Attachment.photo)
|
||||
// }
|
||||
// }()
|
||||
// default:
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .store(in: &cell.disposeBag)
|
||||
// NotificationCenter.default.publisher(
|
||||
// for: UITextView.textDidChangeNotification,
|
||||
// object: cell.attachmentContainerView.descriptionTextView
|
||||
// )
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { notification in
|
||||
// guard let textField = notification.object as? UITextView else { return }
|
||||
// let text = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
// attachmentService.description.value = text
|
||||
// }
|
||||
// .store(in: &cell.disposeBag)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
//
|
||||
// ComposeStatusContentTableViewCell.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-6-28.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
import MetaTextKit
|
||||
import UITextView_Placeholder
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
import MastodonUI
|
||||
|
||||
protocol ComposeStatusContentTableViewCellDelegate: AnyObject {
|
||||
func composeStatusContentTableViewCell(_ cell: ComposeStatusContentTableViewCell, textViewShouldBeginEditing textView: UITextView) -> Bool
|
||||
}
|
||||
|
||||
final class ComposeStatusContentTableViewCell: UITableViewCell {
|
||||
|
||||
let logger = Logger(subsystem: "ComposeStatusContentTableViewCell", category: "View")
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
weak var delegate: ComposeStatusContentTableViewCellDelegate?
|
||||
|
||||
let statusView = StatusView()
|
||||
|
||||
let statusContentWarningEditorView = StatusContentWarningEditorView()
|
||||
|
||||
let textEditorViewContainerView = UIView()
|
||||
|
||||
static let metaTextViewTag: Int = 333
|
||||
let metaText: MetaText = {
|
||||
let metaText = MetaText()
|
||||
metaText.textView.backgroundColor = .clear
|
||||
metaText.textView.isScrollEnabled = false
|
||||
metaText.textView.keyboardType = .twitter
|
||||
metaText.textView.textDragInteraction?.isEnabled = false // disable drag for link and attachment
|
||||
metaText.textView.textContainer.lineFragmentPadding = 10 // leading inset
|
||||
metaText.textView.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular))
|
||||
metaText.textView.attributedPlaceholder = {
|
||||
var attributes = metaText.textAttributes
|
||||
attributes[.foregroundColor] = Asset.Colors.Label.secondary.color
|
||||
return NSAttributedString(
|
||||
string: L10n.Scene.Compose.contentInputPlaceholder,
|
||||
attributes: attributes
|
||||
)
|
||||
}()
|
||||
metaText.paragraphStyle = {
|
||||
let style = NSMutableParagraphStyle()
|
||||
style.lineSpacing = 5
|
||||
style.paragraphSpacing = 0
|
||||
return style
|
||||
}()
|
||||
metaText.textAttributes = [
|
||||
.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)),
|
||||
.foregroundColor: Asset.Colors.Label.primary.color,
|
||||
]
|
||||
metaText.linkAttributes = [
|
||||
.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)),
|
||||
.foregroundColor: Asset.Colors.brand.color,
|
||||
]
|
||||
return metaText
|
||||
}()
|
||||
|
||||
// output
|
||||
let contentWarningContent = PassthroughSubject<String, Never>()
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
||||
metaText.delegate = nil
|
||||
metaText.textView.delegate = nil
|
||||
}
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
_init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
_init()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ComposeStatusContentTableViewCell {
|
||||
|
||||
private func _init() {
|
||||
selectionStyle = .none
|
||||
layer.zPosition = 999
|
||||
backgroundColor = .clear
|
||||
preservesSuperviewLayoutMargins = true
|
||||
|
||||
let containerStackView = UIStackView()
|
||||
containerStackView.axis = .vertical
|
||||
containerStackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addSubview(containerStackView)
|
||||
NSLayoutConstraint.activate([
|
||||
containerStackView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||
containerStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||
containerStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||||
containerStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
])
|
||||
containerStackView.preservesSuperviewLayoutMargins = true
|
||||
|
||||
containerStackView.addArrangedSubview(statusContentWarningEditorView)
|
||||
statusContentWarningEditorView.setContentHuggingPriority(.required - 1, for: .vertical)
|
||||
|
||||
let statusContainerView = UIView()
|
||||
statusContainerView.preservesSuperviewLayoutMargins = true
|
||||
containerStackView.addArrangedSubview(statusContainerView)
|
||||
statusView.translatesAutoresizingMaskIntoConstraints = false
|
||||
statusContainerView.addSubview(statusView)
|
||||
NSLayoutConstraint.activate([
|
||||
statusView.topAnchor.constraint(equalTo: statusContainerView.topAnchor, constant: 20),
|
||||
statusView.leadingAnchor.constraint(equalTo: statusContainerView.leadingAnchor),
|
||||
statusView.trailingAnchor.constraint(equalTo: statusContainerView.trailingAnchor),
|
||||
statusView.bottomAnchor.constraint(equalTo: statusContainerView.bottomAnchor),
|
||||
])
|
||||
statusView.setup(style: .composeStatusAuthor)
|
||||
|
||||
containerStackView.addArrangedSubview(textEditorViewContainerView)
|
||||
metaText.textView.translatesAutoresizingMaskIntoConstraints = false
|
||||
textEditorViewContainerView.addSubview(metaText.textView)
|
||||
NSLayoutConstraint.activate([
|
||||
metaText.textView.topAnchor.constraint(equalTo: textEditorViewContainerView.topAnchor),
|
||||
metaText.textView.leadingAnchor.constraint(equalTo: textEditorViewContainerView.layoutMarginsGuide.leadingAnchor),
|
||||
metaText.textView.trailingAnchor.constraint(equalTo: textEditorViewContainerView.layoutMarginsGuide.trailingAnchor),
|
||||
metaText.textView.bottomAnchor.constraint(equalTo: textEditorViewContainerView.bottomAnchor),
|
||||
metaText.textView.heightAnchor.constraint(greaterThanOrEqualToConstant: 64).priority(.defaultHigh),
|
||||
])
|
||||
statusContentWarningEditorView.textView.delegate = self
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UITextViewDelegate
|
||||
extension ComposeStatusContentTableViewCell: UITextViewDelegate {
|
||||
|
||||
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
|
||||
return delegate?.composeStatusContentTableViewCell(self, textViewShouldBeginEditing: textView) ?? true
|
||||
}
|
||||
|
||||
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||
switch textView {
|
||||
case statusContentWarningEditorView.textView:
|
||||
// disable input line break
|
||||
guard text != "\n" else { return false }
|
||||
return true
|
||||
default:
|
||||
assertionFailure()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func textViewDidChange(_ textView: UITextView) {
|
||||
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): text: \(textView.text ?? "<nil>")")
|
||||
guard textView === statusContentWarningEditorView.textView else { return }
|
||||
// replace line break with space
|
||||
// needs check input state to prevent break the IME
|
||||
if textView.markedTextRange == nil {
|
||||
textView.text = textView.text.replacingOccurrences(of: "\n", with: " ")
|
||||
}
|
||||
contentWarningContent.send(textView.text)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
//
|
||||
// ComposeStatusPollTableViewCell.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-6-29.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
||||
protocol ComposeStatusPollTableViewCellDelegate: AnyObject {
|
||||
func composeStatusPollTableViewCell(_ cell: ComposeStatusPollTableViewCell, pollOptionAttributesDidReorder options: [ComposeStatusPollItem.PollOptionAttribute])
|
||||
}
|
||||
|
||||
final class ComposeStatusPollTableViewCell: UITableViewCell {
|
||||
|
||||
let logger = Logger(subsystem: "ComposeStatusPollTableViewCell", category: "UI")
|
||||
|
||||
private(set) var dataSource: UICollectionViewDiffableDataSource<ComposeStatusPollSection, ComposeStatusPollItem>!
|
||||
var observations = Set<NSKeyValueObservation>()
|
||||
|
||||
weak var customEmojiPickerInputViewModel: CustomEmojiPickerInputViewModel?
|
||||
weak var delegate: ComposeStatusPollTableViewCellDelegate?
|
||||
weak var composeStatusPollOptionCollectionViewCellDelegate: ComposeStatusPollOptionCollectionViewCellDelegate?
|
||||
weak var composeStatusPollOptionAppendEntryCollectionViewCellDelegate: ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate?
|
||||
weak var composeStatusPollExpiresOptionCollectionViewCellDelegate: ComposeStatusPollExpiresOptionCollectionViewCellDelegate?
|
||||
|
||||
private static func createLayout() -> UICollectionViewLayout {
|
||||
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))
|
||||
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
||||
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))
|
||||
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
|
||||
let section = NSCollectionLayoutSection(group: group)
|
||||
section.contentInsetsReference = .readableContent
|
||||
return UICollectionViewCompositionalLayout(section: section)
|
||||
}
|
||||
|
||||
private(set) var collectionViewHeightLayoutConstraint: NSLayoutConstraint!
|
||||
let collectionView: UICollectionView = {
|
||||
let collectionViewLayout = ComposeStatusPollTableViewCell.createLayout()
|
||||
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
|
||||
collectionView.register(ComposeStatusPollOptionCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusPollOptionCollectionViewCell.self))
|
||||
collectionView.register(ComposeStatusPollOptionAppendEntryCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusPollOptionAppendEntryCollectionViewCell.self))
|
||||
collectionView.register(ComposeStatusPollExpiresOptionCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusPollExpiresOptionCollectionViewCell.self))
|
||||
collectionView.backgroundColor = .clear
|
||||
collectionView.alwaysBounceVertical = true
|
||||
collectionView.isScrollEnabled = false
|
||||
collectionView.dragInteractionEnabled = true
|
||||
return collectionView
|
||||
}()
|
||||
let collectionViewHeightDidUpdate = PassthroughSubject<Void, Never>()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
_init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
_init()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ComposeStatusPollTableViewCell {
|
||||
|
||||
private func _init() {
|
||||
backgroundColor = .clear
|
||||
contentView.backgroundColor = .clear
|
||||
|
||||
collectionView.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addSubview(collectionView)
|
||||
collectionViewHeightLayoutConstraint = collectionView.heightAnchor.constraint(equalToConstant: 300).priority(.defaultHigh)
|
||||
NSLayoutConstraint.activate([
|
||||
collectionView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||
collectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||
collectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||||
collectionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
collectionViewHeightLayoutConstraint,
|
||||
])
|
||||
|
||||
collectionView.observe(\.contentSize, options: [.initial, .new]) { [weak self] collectionView, _ in
|
||||
guard let self = self else { return }
|
||||
self.collectionViewHeightLayoutConstraint.constant = collectionView.contentSize.height
|
||||
self.collectionViewHeightDidUpdate.send()
|
||||
}
|
||||
.store(in: &observations)
|
||||
|
||||
self.dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { [
|
||||
weak self
|
||||
] collectionView, indexPath, item -> UICollectionViewCell? in
|
||||
guard let self = self else { return UICollectionViewCell() }
|
||||
|
||||
switch item {
|
||||
case .pollOption(let attribute):
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusPollOptionCollectionViewCell.self), for: indexPath) as! ComposeStatusPollOptionCollectionViewCell
|
||||
cell.pollOptionView.optionTextField.text = attribute.option.value
|
||||
cell.pollOptionView.optionTextField.placeholder = L10n.Scene.Compose.Poll.optionNumber(indexPath.item + 1)
|
||||
cell.pollOption
|
||||
.receive(on: DispatchQueue.main)
|
||||
.assign(to: \.value, on: attribute.option)
|
||||
.store(in: &cell.disposeBag)
|
||||
cell.delegate = self.composeStatusPollOptionCollectionViewCellDelegate
|
||||
if let customEmojiPickerInputViewModel = self.customEmojiPickerInputViewModel {
|
||||
ComposeStatusSection.configureCustomEmojiPicker(viewModel: customEmojiPickerInputViewModel, customEmojiReplaceableTextInput: cell.pollOptionView.optionTextField, disposeBag: &cell.disposeBag)
|
||||
}
|
||||
return cell
|
||||
case .pollOptionAppendEntry:
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusPollOptionAppendEntryCollectionViewCell.self), for: indexPath) as! ComposeStatusPollOptionAppendEntryCollectionViewCell
|
||||
cell.delegate = self.composeStatusPollOptionAppendEntryCollectionViewCellDelegate
|
||||
return cell
|
||||
case .pollExpiresOption(let attribute):
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusPollExpiresOptionCollectionViewCell.self), for: indexPath) as! ComposeStatusPollExpiresOptionCollectionViewCell
|
||||
cell.durationButton.setTitle(L10n.Scene.Compose.Poll.durationTime(attribute.expiresOption.value.title), for: .normal)
|
||||
attribute.expiresOption
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak cell] expiresOption in
|
||||
guard let cell = cell else { return }
|
||||
cell.durationButton.setTitle(L10n.Scene.Compose.Poll.durationTime(expiresOption.title), for: .normal)
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
cell.delegate = self.composeStatusPollExpiresOptionCollectionViewCellDelegate
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
collectionView.dragDelegate = self
|
||||
collectionView.dropDelegate = self
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UICollectionViewDragDelegate
|
||||
extension ComposeStatusPollTableViewCell: UICollectionViewDragDelegate {
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath) else { return [] }
|
||||
switch item {
|
||||
case .pollOption:
|
||||
let itemProvider = NSItemProvider(object: String(item.hashValue) as NSString)
|
||||
let dragItem = UIDragItem(itemProvider: itemProvider)
|
||||
dragItem.localObject = item
|
||||
return [dragItem]
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, dragSessionIsRestrictedToDraggingApplication session: UIDragSession) -> Bool {
|
||||
// drag to app should be the same app
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UICollectionViewDropDelegate
|
||||
extension ComposeStatusPollTableViewCell: UICollectionViewDropDelegate {
|
||||
// didUpdate
|
||||
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
|
||||
guard collectionView.hasActiveDrag,
|
||||
let destinationIndexPath = destinationIndexPath,
|
||||
let item = dataSource.itemIdentifier(for: destinationIndexPath)
|
||||
else {
|
||||
return UICollectionViewDropProposal(operation: .forbidden)
|
||||
}
|
||||
|
||||
switch item {
|
||||
case .pollOption:
|
||||
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
|
||||
default:
|
||||
return UICollectionViewDropProposal(operation: .cancel)
|
||||
}
|
||||
}
|
||||
|
||||
// performDrop
|
||||
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
|
||||
guard let dropItem = coordinator.items.first,
|
||||
let item = dropItem.dragItem.localObject as? ComposeStatusPollItem,
|
||||
case .pollOption = item
|
||||
else { return }
|
||||
|
||||
guard coordinator.proposal.operation == .move else { return }
|
||||
guard let destinationIndexPath = coordinator.destinationIndexPath,
|
||||
let _ = collectionView.cellForItem(at: destinationIndexPath) as? ComposeStatusPollOptionCollectionViewCell
|
||||
else { return }
|
||||
|
||||
var snapshot = dataSource.snapshot()
|
||||
guard destinationIndexPath.row < snapshot.itemIdentifiers.count else { return }
|
||||
let anchorItem = snapshot.itemIdentifiers[destinationIndexPath.row]
|
||||
snapshot.moveItem(item, afterItem: anchorItem)
|
||||
dataSource.apply(snapshot)
|
||||
|
||||
coordinator.drop(dropItem.dragItem, toItemAt: destinationIndexPath)
|
||||
}
|
||||
}
|
||||
|
||||
extension ComposeStatusPollTableViewCell: UICollectionViewDelegate {
|
||||
func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(originalIndexPath.debugDescription) -> \(proposedIndexPath.debugDescription)")
|
||||
|
||||
guard let _ = collectionView.cellForItem(at: proposedIndexPath) as? ComposeStatusPollOptionCollectionViewCell else {
|
||||
return originalIndexPath
|
||||
}
|
||||
|
||||
return proposedIndexPath
|
||||
}
|
||||
}
|
|
@ -8,6 +8,8 @@
|
|||
import UIKit
|
||||
import Combine
|
||||
import MetaTextKit
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
|
||||
final class CustomEmojiPickerInputViewModel {
|
||||
|
||||
|
|
|
@ -11,15 +11,11 @@ import GameplayKit
|
|||
import MastodonSDK
|
||||
|
||||
extension DiscoveryCommunityViewModel {
|
||||
class State: GKState, NamingState {
|
||||
class State: GKState {
|
||||
|
||||
let logger = Logger(subsystem: "DiscoveryCommunityViewModel.State", category: "StateMachine")
|
||||
|
||||
let id = UUID()
|
||||
|
||||
var name: String {
|
||||
String(describing: Self.self)
|
||||
}
|
||||
|
||||
weak var viewModel: DiscoveryCommunityViewModel?
|
||||
|
||||
|
@ -29,8 +25,10 @@ extension DiscoveryCommunityViewModel {
|
|||
|
||||
override func didEnter(from previousState: GKState?) {
|
||||
super.didEnter(from: previousState)
|
||||
let previousState = previousState as? DiscoveryCommunityViewModel.State
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] enter \(self.name), previous: \(previousState?.name ?? "<nil>")")
|
||||
|
||||
let from = previousState.flatMap { String(describing: $0) } ?? "nil"
|
||||
let to = String(describing: self)
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(from) -> \(to)")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
@ -39,7 +37,7 @@ extension DiscoveryCommunityViewModel {
|
|||
}
|
||||
|
||||
deinit {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(self.name)")
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(String(describing: self))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,16 +11,12 @@ import GameplayKit
|
|||
import MastodonSDK
|
||||
|
||||
extension DiscoveryNewsViewModel {
|
||||
class State: GKState, NamingState {
|
||||
class State: GKState {
|
||||
|
||||
let logger = Logger(subsystem: "DiscoveryNewsViewModel.State", category: "StateMachine")
|
||||
|
||||
let id = UUID()
|
||||
|
||||
var name: String {
|
||||
String(describing: Self.self)
|
||||
}
|
||||
|
||||
weak var viewModel: DiscoveryNewsViewModel?
|
||||
|
||||
init(viewModel: DiscoveryNewsViewModel) {
|
||||
|
@ -29,8 +25,10 @@ extension DiscoveryNewsViewModel {
|
|||
|
||||
override func didEnter(from previousState: GKState?) {
|
||||
super.didEnter(from: previousState)
|
||||
let previousState = previousState as? DiscoveryNewsViewModel.State
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] enter \(self.name), previous: \(previousState?.name ?? "<nil>")")
|
||||
|
||||
let from = previousState.flatMap { String(describing: $0) } ?? "nil"
|
||||
let to = String(describing: self)
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(from) -> \(to)")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
@ -39,7 +37,7 @@ extension DiscoveryNewsViewModel {
|
|||
}
|
||||
|
||||
deinit {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(self.name)")
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(String(describing: self))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,16 +12,12 @@ import MastodonSDK
|
|||
import MastodonCore
|
||||
|
||||
extension DiscoveryPostsViewModel {
|
||||
class State: GKState, NamingState {
|
||||
class State: GKState {
|
||||
|
||||
let logger = Logger(subsystem: "DiscoveryPostsViewModel.State", category: "StateMachine")
|
||||
|
||||
let id = UUID()
|
||||
|
||||
var name: String {
|
||||
String(describing: Self.self)
|
||||
}
|
||||
|
||||
weak var viewModel: DiscoveryPostsViewModel?
|
||||
|
||||
init(viewModel: DiscoveryPostsViewModel) {
|
||||
|
@ -30,8 +26,10 @@ extension DiscoveryPostsViewModel {
|
|||
|
||||
override func didEnter(from previousState: GKState?) {
|
||||
super.didEnter(from: previousState)
|
||||
let previousState = previousState as? DiscoveryPostsViewModel.State
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] enter \(self.name), previous: \(previousState?.name ?? "<nil>")")
|
||||
|
||||
let from = previousState.flatMap { String(describing: $0) } ?? "nil"
|
||||
let to = String(describing: self)
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(from) -> \(to)")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
@ -40,7 +38,7 @@ extension DiscoveryPostsViewModel {
|
|||
}
|
||||
|
||||
deinit {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(self.name)")
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(String(describing: self))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import GameplayKit
|
|||
import CoreData
|
||||
import MastodonAsset
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonLocalization
|
||||
|
||||
final class HashtagTimelineViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
|
||||
|
@ -168,10 +169,10 @@ extension HashtagTimelineViewController {
|
|||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
let composeViewModel = ComposeViewModel(
|
||||
context: context,
|
||||
composeKind: .hashtag(hashtag: viewModel.hashtag),
|
||||
authContext: viewModel.authContext
|
||||
authContext: viewModel.authContext,
|
||||
kind: .hashtag(hashtag: viewModel.hashtag)
|
||||
)
|
||||
coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||
_ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import GameplayKit
|
|||
import CoreDataStack
|
||||
|
||||
extension HashtagTimelineViewModel {
|
||||
class State: GKState, NamingState {
|
||||
class State: GKState {
|
||||
|
||||
let logger = Logger(subsystem: "HashtagTimelineViewModel.LoadOldestState", category: "StateMachine")
|
||||
|
||||
|
@ -28,10 +28,11 @@ extension HashtagTimelineViewModel {
|
|||
}
|
||||
|
||||
override func didEnter(from previousState: GKState?) {
|
||||
let previousState = previousState as? HashtagTimelineViewModel.State
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] enter \(self.name), previous: \(previousState?.name ?? "<nil>")")
|
||||
|
||||
viewModel?.loadOldestStateMachinePublisher.send(self)
|
||||
super.didEnter(from: previousState)
|
||||
|
||||
let from = previousState.flatMap { String(describing: $0) } ?? "nil"
|
||||
let to = String(describing: self)
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(from) -> \(to)")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
|
|
@ -51,7 +51,6 @@ final class HashtagTimelineViewModel {
|
|||
stateMachine.enter(State.Initial.self)
|
||||
return stateMachine
|
||||
}()
|
||||
lazy var loadOldestStateMachinePublisher = CurrentValueSubject<State?, Never>(nil)
|
||||
|
||||
init(context: AppContext, authContext: AuthContext, hashtag: String) {
|
||||
self.context = context
|
||||
|
|
|
@ -9,6 +9,7 @@ import os.log
|
|||
import UIKit
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonUI
|
||||
|
||||
extension HomeTimelineViewModel {
|
||||
|
||||
|
|
|
@ -11,15 +11,11 @@ import GameplayKit
|
|||
import MastodonSDK
|
||||
|
||||
extension HomeTimelineViewModel {
|
||||
class LoadOldestState: GKState, NamingState {
|
||||
class LoadOldestState: GKState {
|
||||
|
||||
let logger = Logger(subsystem: "HomeTimelineViewModel.LoadOldestState", category: "StateMachine")
|
||||
|
||||
let id = UUID()
|
||||
|
||||
var name: String {
|
||||
String(describing: Self.self)
|
||||
}
|
||||
|
||||
weak var viewModel: HomeTimelineViewModel?
|
||||
|
||||
|
@ -29,10 +25,10 @@ extension HomeTimelineViewModel {
|
|||
|
||||
override func didEnter(from previousState: GKState?) {
|
||||
super.didEnter(from: previousState)
|
||||
let previousState = previousState as? HomeTimelineViewModel.LoadOldestState
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] enter \(self.name), previous: \(previousState?.name ?? "<nil>")")
|
||||
|
||||
viewModel?.loadOldestStateMachinePublisher.send(self)
|
||||
let from = previousState.flatMap { String(describing: $0) } ?? "nil"
|
||||
let to = String(describing: self)
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(from) -> \(to)")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
@ -41,7 +37,7 @@ extension HomeTimelineViewModel {
|
|||
}
|
||||
|
||||
deinit {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(self.name)")
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(String(describing: self))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import GameplayKit
|
|||
import AlamofireImage
|
||||
import DateToolsSwift
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
|
||||
final class HomeTimelineViewModel: NSObject {
|
||||
|
||||
|
|
|
@ -12,16 +12,12 @@ import MastodonSDK
|
|||
import os.log
|
||||
|
||||
extension NotificationTimelineViewModel {
|
||||
class LoadOldestState: GKState, NamingState {
|
||||
class LoadOldestState: GKState {
|
||||
|
||||
let logger = Logger(subsystem: "NotificationTimelineViewModel.LoadOldestState", category: "StateMachine")
|
||||
|
||||
let id = UUID()
|
||||
|
||||
var name: String {
|
||||
String(describing: Self.self)
|
||||
}
|
||||
|
||||
weak var viewModel: NotificationTimelineViewModel?
|
||||
|
||||
init(viewModel: NotificationTimelineViewModel) {
|
||||
|
@ -30,8 +26,10 @@ extension NotificationTimelineViewModel {
|
|||
|
||||
override func didEnter(from previousState: GKState?) {
|
||||
super.didEnter(from: previousState)
|
||||
let previousState = previousState as? NotificationTimelineViewModel.LoadOldestState
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] enter \(self.name), previous: \(previousState?.name ?? "<nil>")")
|
||||
|
||||
let from = previousState.flatMap { String(describing: $0) } ?? "nil"
|
||||
let to = String(describing: self)
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(from) -> \(to)")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
@ -40,7 +38,7 @@ extension NotificationTimelineViewModel {
|
|||
}
|
||||
|
||||
deinit {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(self.name)")
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(String(describing: self))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import MastodonCore
|
|||
import MastodonUI
|
||||
import MastodonLocalization
|
||||
|
||||
final class PickServerLoaderTableViewCell: TimelineLoaderTableViewCell {
|
||||
public final class PickServerLoaderTableViewCell: TimelineLoaderTableViewCell {
|
||||
|
||||
let containerView: UIView = {
|
||||
let view = UIView()
|
||||
|
@ -30,7 +30,7 @@ final class PickServerLoaderTableViewCell: TimelineLoaderTableViewCell {
|
|||
return label
|
||||
}()
|
||||
|
||||
override func _init() {
|
||||
public override func _init() {
|
||||
super._init()
|
||||
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import os.log
|
|||
import UIKit
|
||||
import Tabman
|
||||
import MastodonAsset
|
||||
import MastodonUI
|
||||
import MastodonLocalization
|
||||
|
||||
protocol PickServerServerSectionTableHeaderViewDelegate: AnyObject {
|
||||
|
|
|
@ -12,6 +12,7 @@ import Combine
|
|||
import GameplayKit
|
||||
import MastodonAsset
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonLocalization
|
||||
|
||||
final class BookmarkViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
|
||||
|
|
|
@ -12,16 +12,12 @@ import MastodonSDK
|
|||
import MastodonCore
|
||||
|
||||
extension BookmarkViewModel {
|
||||
class State: GKState, NamingState {
|
||||
class State: GKState {
|
||||
|
||||
let logger = Logger(subsystem: "BookmarkViewModel.State", category: "StateMachine")
|
||||
|
||||
let id = UUID()
|
||||
|
||||
var name: String {
|
||||
String(describing: Self.self)
|
||||
}
|
||||
|
||||
weak var viewModel: BookmarkViewModel?
|
||||
|
||||
init(viewModel: BookmarkViewModel) {
|
||||
|
@ -30,8 +26,10 @@ extension BookmarkViewModel {
|
|||
|
||||
override func didEnter(from previousState: GKState?) {
|
||||
super.didEnter(from: previousState)
|
||||
let previousState = previousState as? BookmarkViewModel.State
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] enter \(self.name), previous: \(previousState?.name ?? "<nil>")")
|
||||
|
||||
let from = previousState.flatMap { String(describing: $0) } ?? "nil"
|
||||
let to = String(describing: self)
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(from) -> \(to)")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
@ -40,7 +38,7 @@ extension BookmarkViewModel {
|
|||
}
|
||||
|
||||
deinit {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(self.name)")
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(String(describing: self))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import Combine
|
|||
import GameplayKit
|
||||
import MastodonAsset
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonLocalization
|
||||
|
||||
final class FavoriteViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
|
||||
|
|
|
@ -12,15 +12,11 @@ import MastodonCore
|
|||
import MastodonSDK
|
||||
|
||||
extension FavoriteViewModel {
|
||||
class State: GKState, NamingState {
|
||||
class State: GKState {
|
||||
|
||||
let logger = Logger(subsystem: "FavoriteViewModel.State", category: "StateMachine")
|
||||
|
||||
let id = UUID()
|
||||
|
||||
var name: String {
|
||||
String(describing: Self.self)
|
||||
}
|
||||
|
||||
weak var viewModel: FavoriteViewModel?
|
||||
|
||||
|
@ -30,8 +26,10 @@ extension FavoriteViewModel {
|
|||
|
||||
override func didEnter(from previousState: GKState?) {
|
||||
super.didEnter(from: previousState)
|
||||
let previousState = previousState as? FavoriteViewModel.State
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] enter \(self.name), previous: \(previousState?.name ?? "<nil>")")
|
||||
|
||||
let from = previousState.flatMap { String(describing: $0) } ?? "nil"
|
||||
let to = String(describing: self)
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(from) -> \(to)")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
@ -40,7 +38,7 @@ extension FavoriteViewModel {
|
|||
}
|
||||
|
||||
deinit {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(self.name)")
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(String(describing: self))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import UIKit
|
|||
import GameplayKit
|
||||
import Combine
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonLocalization
|
||||
|
||||
final class FollowerListViewController: UIViewController, NeedsDependency {
|
||||
|
|
|
@ -12,7 +12,7 @@ import MastodonSDK
|
|||
import MastodonCore
|
||||
|
||||
extension FollowerListViewModel {
|
||||
class State: GKState, NamingState {
|
||||
class State: GKState {
|
||||
|
||||
let logger = Logger(subsystem: "FollowerListViewModel.State", category: "StateMachine")
|
||||
|
||||
|
@ -30,8 +30,10 @@ extension FollowerListViewModel {
|
|||
|
||||
override func didEnter(from previousState: GKState?) {
|
||||
super.didEnter(from: previousState)
|
||||
let previousState = previousState as? FollowerListViewModel.State
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] enter \(self.name), previous: \(previousState?.name ?? "<nil>")")
|
||||
|
||||
let from = previousState.flatMap { String(describing: $0) } ?? "nil"
|
||||
let to = String(describing: self)
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(from) -> \(to)")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
@ -40,7 +42,7 @@ extension FollowerListViewModel {
|
|||
}
|
||||
|
||||
deinit {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(self.name)")
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(String(describing: self))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import GameplayKit
|
|||
import Combine
|
||||
import MastodonLocalization
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
|
||||
final class FollowingListViewController: UIViewController, NeedsDependency {
|
||||
|
||||
|
|
|
@ -11,15 +11,11 @@ import GameplayKit
|
|||
import MastodonSDK
|
||||
|
||||
extension FollowingListViewModel {
|
||||
class State: GKState, NamingState {
|
||||
class State: GKState {
|
||||
|
||||
let logger = Logger(subsystem: "FollowingListViewModel.State", category: "StateMachine")
|
||||
|
||||
let id = UUID()
|
||||
|
||||
var name: String {
|
||||
String(describing: Self.self)
|
||||
}
|
||||
|
||||
weak var viewModel: FollowingListViewModel?
|
||||
|
||||
|
@ -29,8 +25,10 @@ extension FollowingListViewModel {
|
|||
|
||||
override func didEnter(from previousState: GKState?) {
|
||||
super.didEnter(from: previousState)
|
||||
let previousState = previousState as? FollowingListViewModel.State
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] enter \(self.name), previous: \(previousState?.name ?? "<nil>")")
|
||||
|
||||
let from = previousState.flatMap { String(describing: $0) } ?? "nil"
|
||||
let to = String(describing: self)
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(from) -> \(to)")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
@ -39,7 +37,7 @@ extension FollowingListViewModel {
|
|||
}
|
||||
|
||||
deinit {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(self.name)")
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(String(describing: self))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -499,7 +499,7 @@ extension ProfileViewController {
|
|||
user: record
|
||||
)
|
||||
guard let activityViewController = _activityViewController else { return }
|
||||
self.coordinator.present(
|
||||
_ = self.coordinator.present(
|
||||
scene: .activityViewController(
|
||||
activityViewController: activityViewController,
|
||||
sourceView: nil,
|
||||
|
@ -514,13 +514,13 @@ extension ProfileViewController {
|
|||
@objc private func favoriteBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
let favoriteViewModel = FavoriteViewModel(context: context, authContext: viewModel.authContext)
|
||||
coordinator.present(scene: .favorite(viewModel: favoriteViewModel), from: self, transition: .show)
|
||||
_ = coordinator.present(scene: .favorite(viewModel: favoriteViewModel), from: self, transition: .show)
|
||||
}
|
||||
|
||||
@objc private func bookmarkBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
let bookmarkViewModel = BookmarkViewModel(context: context, authContext: viewModel.authContext)
|
||||
coordinator.present(scene: .bookmark(viewModel: bookmarkViewModel), from: self, transition: .show)
|
||||
_ = coordinator.present(scene: .bookmark(viewModel: bookmarkViewModel), from: self, transition: .show)
|
||||
}
|
||||
|
||||
@objc private func replyBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||
|
@ -528,10 +528,10 @@ extension ProfileViewController {
|
|||
guard let mastodonUser = viewModel.user else { return }
|
||||
let composeViewModel = ComposeViewModel(
|
||||
context: context,
|
||||
composeKind: .mention(user: .init(objectID: mastodonUser.objectID)),
|
||||
authContext: viewModel.authContext
|
||||
authContext: viewModel.authContext,
|
||||
kind: .mention(user: mastodonUser.asRecrod)
|
||||
)
|
||||
coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||
_ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||
}
|
||||
|
||||
@objc private func refreshControlValueChanged(_ sender: UIRefreshControl) {
|
||||
|
|
|
@ -12,16 +12,12 @@ import MastodonCore
|
|||
import MastodonSDK
|
||||
|
||||
extension UserTimelineViewModel {
|
||||
class State: GKState, NamingState {
|
||||
class State: GKState {
|
||||
|
||||
let logger = Logger(subsystem: "UserTimelineViewModel.State", category: "StateMachine")
|
||||
|
||||
let id = UUID()
|
||||
|
||||
var name: String {
|
||||
String(describing: Self.self)
|
||||
}
|
||||
|
||||
weak var viewModel: UserTimelineViewModel?
|
||||
|
||||
init(viewModel: UserTimelineViewModel) {
|
||||
|
@ -30,8 +26,10 @@ extension UserTimelineViewModel {
|
|||
|
||||
override func didEnter(from previousState: GKState?) {
|
||||
super.didEnter(from: previousState)
|
||||
let previousState = previousState as? UserTimelineViewModel.State
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] enter \(self.name), previous: \(previousState?.name ?? "<nil>")")
|
||||
|
||||
let from = previousState.flatMap { String(describing: $0) } ?? "nil"
|
||||
let to = String(describing: self)
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(from) -> \(to)")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
@ -40,7 +38,7 @@ extension UserTimelineViewModel {
|
|||
}
|
||||
|
||||
deinit {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(self.name)")
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(String(describing: self))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,15 +11,11 @@ import GameplayKit
|
|||
import MastodonSDK
|
||||
|
||||
extension UserListViewModel {
|
||||
class State: GKState, NamingState {
|
||||
class State: GKState {
|
||||
|
||||
let logger = Logger(subsystem: "UserListViewModel.State", category: "StateMachine")
|
||||
|
||||
let id = UUID()
|
||||
|
||||
var name: String {
|
||||
String(describing: Self.self)
|
||||
}
|
||||
|
||||
weak var viewModel: UserListViewModel?
|
||||
|
||||
|
@ -29,8 +25,10 @@ extension UserListViewModel {
|
|||
|
||||
override func didEnter(from previousState: GKState?) {
|
||||
super.didEnter(from: previousState)
|
||||
let previousState = previousState as? UserListViewModel.State
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] enter \(self.name), previous: \(previousState?.name ?? "<nil>")")
|
||||
|
||||
let from = previousState.flatMap { String(describing: $0) } ?? "nil"
|
||||
let to = String(describing: self)
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(from) -> \(to)")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
@ -39,7 +37,7 @@ extension UserListViewModel {
|
|||
}
|
||||
|
||||
deinit {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(self.name)")
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(String(describing: self))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import Combine
|
|||
import CoreDataStack
|
||||
import MastodonAsset
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonLocalization
|
||||
|
||||
protocol ReportStatusViewControllerDelegate: AnyObject {
|
||||
|
|
|
@ -10,6 +10,7 @@ import UIKit
|
|||
import Combine
|
||||
import MastodonAsset
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonLocalization
|
||||
|
||||
protocol ReportSupplementaryViewControllerDelegate: AnyObject {
|
||||
|
|
|
@ -372,8 +372,8 @@ extension MainTabBarController {
|
|||
guard let authContext = self.authContext else { return }
|
||||
let composeViewModel = ComposeViewModel(
|
||||
context: context,
|
||||
composeKind: .post,
|
||||
authContext: authContext
|
||||
authContext: authContext,
|
||||
kind: .post
|
||||
)
|
||||
_ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: nil, transition: .modal(animated: true, completion: nil))
|
||||
}
|
||||
|
@ -741,8 +741,8 @@ extension MainTabBarController {
|
|||
guard let authContext = self.authContext else { return }
|
||||
let composeViewModel = ComposeViewModel(
|
||||
context: context,
|
||||
composeKind: .post,
|
||||
authContext: authContext
|
||||
authContext: authContext,
|
||||
kind: .post
|
||||
)
|
||||
_ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: nil, transition: .modal(animated: true, completion: nil))
|
||||
}
|
||||
|
|
|
@ -207,8 +207,8 @@ extension SidebarViewController: UICollectionViewDelegate {
|
|||
case .compose:
|
||||
let composeViewModel = ComposeViewModel(
|
||||
context: context,
|
||||
composeKind: .post,
|
||||
authContext: authContext
|
||||
authContext: authContext,
|
||||
kind: .post
|
||||
)
|
||||
_ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||
default:
|
||||
|
|
|
@ -12,15 +12,12 @@ import MastodonSDK
|
|||
import MastodonCore
|
||||
|
||||
extension SearchResultViewModel {
|
||||
class State: GKState, NamingState {
|
||||
class State: GKState {
|
||||
|
||||
let logger = Logger(subsystem: "SearchResultViewModel.State", category: "StateMachine")
|
||||
|
||||
let id = UUID()
|
||||
|
||||
var name: String {
|
||||
String(describing: Self.self)
|
||||
}
|
||||
weak var viewModel: SearchResultViewModel?
|
||||
|
||||
init(viewModel: SearchResultViewModel) {
|
||||
|
@ -29,8 +26,10 @@ extension SearchResultViewModel {
|
|||
|
||||
override func didEnter(from previousState: GKState?) {
|
||||
super.didEnter(from: previousState)
|
||||
let previousState = previousState as? SearchResultViewModel.State
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] enter \(self.name), previous: \(previousState?.name ?? "<nil>")")
|
||||
|
||||
let from = previousState.flatMap { String(describing: $0) } ?? "nil"
|
||||
let to = String(describing: self)
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(from) -> \(to)")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
@ -39,7 +38,7 @@ extension SearchResultViewModel {
|
|||
}
|
||||
|
||||
deinit {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(self.name)")
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(String(describing: self))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import OSLog
|
|||
import UIKit
|
||||
import MastodonAsset
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonLocalization
|
||||
|
||||
class SuggestionAccountViewController: UIViewController, NeedsDependency {
|
||||
|
|
|
@ -13,6 +13,7 @@ import AVKit
|
|||
import MastodonMeta
|
||||
import MastodonAsset
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonLocalization
|
||||
|
||||
final class ThreadViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
|
||||
|
@ -114,8 +115,8 @@ extension ThreadViewController {
|
|||
guard case let .root(threadContext) = viewModel.root else { return }
|
||||
let composeViewModel = ComposeViewModel(
|
||||
context: context,
|
||||
composeKind: .reply(status: threadContext.status),
|
||||
authContext: viewModel.authContext
|
||||
authContext: viewModel.authContext,
|
||||
kind: .reply(status: threadContext.status)
|
||||
)
|
||||
_ = coordinator.present(
|
||||
scene: .compose(viewModel: composeViewModel),
|
||||
|
|
|
@ -10,6 +10,7 @@ import Combine
|
|||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonSDK
|
||||
|
||||
extension ThreadViewModel {
|
||||
|
|
|
@ -13,15 +13,11 @@ import CoreDataStack
|
|||
import MastodonSDK
|
||||
|
||||
extension ThreadViewModel {
|
||||
class LoadThreadState: GKState, NamingState {
|
||||
class LoadThreadState: GKState {
|
||||
|
||||
let logger = Logger(subsystem: "ThreadViewModel.LoadThreadState", category: "StateMachine")
|
||||
|
||||
let id = UUID()
|
||||
|
||||
var name: String {
|
||||
String(describing: Self.self)
|
||||
}
|
||||
|
||||
weak var viewModel: ThreadViewModel?
|
||||
|
||||
|
@ -31,8 +27,10 @@ extension ThreadViewModel {
|
|||
|
||||
override func didEnter(from previousState: GKState?) {
|
||||
super.didEnter(from: previousState)
|
||||
let previousState = previousState as? ThreadViewModel.LoadThreadState
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] enter \(self.name), previous: \(previousState?.name ?? "<nil>")")
|
||||
|
||||
let from = previousState.flatMap { String(describing: $0) } ?? "nil"
|
||||
let to = String(describing: self)
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(from) -> \(to)")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
@ -41,7 +39,7 @@ extension ThreadViewModel {
|
|||
}
|
||||
|
||||
deinit {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(self.name)")
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(String(describing: self))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -149,10 +149,10 @@ extension SceneDelegate {
|
|||
if let authContext = coordinator?.authContext {
|
||||
let composeViewModel = ComposeViewModel(
|
||||
context: AppContext.shared,
|
||||
composeKind: .post,
|
||||
authContext: authContext
|
||||
authContext: authContext,
|
||||
kind: .post
|
||||
)
|
||||
coordinator?.present(scene: .compose(viewModel: composeViewModel), from: nil, transition: .modal(animated: true, completion: nil))
|
||||
_ = coordinator?.present(scene: .compose(viewModel: composeViewModel), from: nil, transition: .modal(animated: true, completion: nil))
|
||||
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): present compose scene")
|
||||
} else {
|
||||
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): not authenticated")
|
||||
|
|
|
@ -11,7 +11,7 @@ import MastodonAsset
|
|||
import MastodonLocalization
|
||||
|
||||
extension Mastodon.API.Subscriptions.Policy {
|
||||
var title: String {
|
||||
public var title: String {
|
||||
switch self {
|
||||
case .all: return L10n.Scene.Settings.Section.Notifications.Trigger.anyone
|
||||
case .follower: return L10n.Scene.Settings.Section.Notifications.Trigger.follower
|
|
@ -7,14 +7,15 @@
|
|||
|
||||
import Foundation
|
||||
import MastodonSDK
|
||||
import MastodonMeta
|
||||
|
||||
extension Mastodon.Entity.Account {
|
||||
public var displayNameWithFallback: String {
|
||||
if displayName.isEmpty {
|
||||
return username
|
||||
} else {
|
||||
return displayName
|
||||
}
|
||||
extension Mastodon.Entity.Account: Hashable {
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
}
|
||||
|
||||
public static func == (lhs: Mastodon.Entity.Account, rhs: Mastodon.Entity.Account) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,3 +29,21 @@ extension Mastodon.Entity.Account {
|
|||
return avatarImageURL() ?? URL(string: "https://\(domain)/avatars/original/missing.png")!
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Entity.Account {
|
||||
public var displayNameWithFallback: String {
|
||||
return !displayName.isEmpty ? displayName : username
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Entity.Account {
|
||||
public var emojiMeta: MastodonContent.Emojis {
|
||||
let isAnimated = !UserDefaults.shared.preferredStaticEmoji
|
||||
|
||||
var dict = MastodonContent.Emojis()
|
||||
for emoji in emojis ?? [] {
|
||||
dict[emoji.shortcode] = isAnimated ? emoji.url : emoji.staticURL
|
||||
}
|
||||
return dict
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ extension Mastodon.Entity.Error.Detail: LocalizedError {
|
|||
|
||||
extension Mastodon.Entity.Error.Detail {
|
||||
|
||||
enum Item: String {
|
||||
public enum Item: String {
|
||||
case username
|
||||
case email
|
||||
case password
|
||||
|
@ -82,32 +82,32 @@ extension Mastodon.Entity.Error.Detail {
|
|||
}
|
||||
}
|
||||
|
||||
var usernameErrorDescriptions: [String] {
|
||||
public var usernameErrorDescriptions: [String] {
|
||||
guard let username = username, !username.isEmpty else { return [] }
|
||||
return username.map { Mastodon.Entity.Error.Detail.localizeError(item: .username, for: $0) }
|
||||
}
|
||||
|
||||
var emailErrorDescriptions: [String] {
|
||||
public var emailErrorDescriptions: [String] {
|
||||
guard let email = email, !email.isEmpty else { return [] }
|
||||
return email.map { Mastodon.Entity.Error.Detail.localizeError(item: .email, for: $0) }
|
||||
}
|
||||
|
||||
var passwordErrorDescriptions: [String] {
|
||||
public var passwordErrorDescriptions: [String] {
|
||||
guard let password = password, !password.isEmpty else { return [] }
|
||||
return password.map { Mastodon.Entity.Error.Detail.localizeError(item: .password, for: $0) }
|
||||
}
|
||||
|
||||
var agreementErrorDescriptions: [String] {
|
||||
public var agreementErrorDescriptions: [String] {
|
||||
guard let agreement = agreement, !agreement.isEmpty else { return [] }
|
||||
return agreement.map { Mastodon.Entity.Error.Detail.localizeError(item: .agreement, for: $0) }
|
||||
}
|
||||
|
||||
var localeErrorDescriptions: [String] {
|
||||
public var localeErrorDescriptions: [String] {
|
||||
guard let locale = locale, !locale.isEmpty else { return [] }
|
||||
return locale.map { Mastodon.Entity.Error.Detail.localizeError(item: .locale, for: $0) }
|
||||
}
|
||||
|
||||
var reasonErrorDescriptions: [String] {
|
||||
public var reasonErrorDescriptions: [String] {
|
||||
guard let reason = reason, !reason.isEmpty else { return [] }
|
||||
return reason.map { Mastodon.Entity.Error.Detail.localizeError(item: .reason, for: $0) }
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
import Foundation
|
||||
import MastodonSDK
|
||||
|
||||
enum AutoCompleteItem {
|
||||
public enum AutoCompleteItem {
|
||||
case hashtag(tag: Mastodon.Entity.Tag)
|
||||
case hashtagV1(tag: String)
|
||||
case account(account: Mastodon.Entity.Account)
|
||||
|
@ -17,7 +17,7 @@ enum AutoCompleteItem {
|
|||
}
|
||||
|
||||
extension AutoCompleteItem: Equatable {
|
||||
static func == (lhs: AutoCompleteItem, rhs: AutoCompleteItem) -> Bool {
|
||||
public static func == (lhs: AutoCompleteItem, rhs: AutoCompleteItem) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.hashtag(let tagLeft), hashtag(let tagRight)):
|
||||
return tagLeft.name == tagRight.name
|
||||
|
@ -36,7 +36,7 @@ extension AutoCompleteItem: Equatable {
|
|||
}
|
||||
|
||||
extension AutoCompleteItem: Hashable {
|
||||
func hash(into hasher: inout Hasher) {
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
switch self {
|
||||
case .hashtag(let tag):
|
||||
hasher.combine(tag.name)
|
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// AutoCompleteSection.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-5-17.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonSDK
|
||||
import MastodonMeta
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
||||
public enum AutoCompleteSection: Equatable, Hashable {
|
||||
case main
|
||||
}
|
|
@ -6,7 +6,6 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import MastodonCore
|
||||
|
||||
enum ComposeStatusAttachmentItem {
|
||||
case attachment(attachmentService: MastodonAttachmentService)
|
|
@ -8,7 +8,6 @@
|
|||
import Foundation
|
||||
import Combine
|
||||
import CoreData
|
||||
import MastodonCore
|
||||
import MastodonMeta
|
||||
import CoreDataStack
|
||||
|
|
@ -13,7 +13,7 @@ import MetaTextKit
|
|||
import MastodonMeta
|
||||
import AlamofireImage
|
||||
|
||||
enum ComposeStatusSection: Equatable, Hashable {
|
||||
public enum ComposeStatusSection: Equatable, Hashable {
|
||||
case replyTo
|
||||
case status
|
||||
case attachment
|
||||
|
@ -21,20 +21,11 @@ enum ComposeStatusSection: Equatable, Hashable {
|
|||
}
|
||||
|
||||
extension ComposeStatusSection {
|
||||
public enum ComposeKind {
|
||||
case post
|
||||
case hashtag(hashtag: String)
|
||||
case mention(user: ManagedObjectRecord<MastodonUser>)
|
||||
case reply(status: ManagedObjectRecord<Status>)
|
||||
}
|
||||
}
|
||||
|
||||
extension ComposeStatusSection {
|
||||
|
||||
static func configure(
|
||||
cell: ComposeStatusContentTableViewCell,
|
||||
attribute: ComposeStatusItem.ComposeStatusAttribute
|
||||
) {
|
||||
// static func configure(
|
||||
// cell: ComposeStatusContentTableViewCell,
|
||||
// attribute: ComposeStatusItem.ComposeStatusAttribute
|
||||
// ) {
|
||||
// cell.prepa
|
||||
// // set avatar
|
||||
// attribute.avatarURL
|
||||
|
@ -62,18 +53,18 @@ extension ComposeStatusSection {
|
|||
// cell.statusView.usernameLabel.text = username.flatMap { "@" + $0 } ?? " "
|
||||
// }
|
||||
// .store(in: &cell.disposeBag)
|
||||
}
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
protocol CustomEmojiReplaceableTextInput: UITextInput & UIResponder {
|
||||
public protocol CustomEmojiReplaceableTextInput: UITextInput & UIResponder {
|
||||
var inputView: UIView? { get set }
|
||||
}
|
||||
|
||||
class CustomEmojiReplaceableTextInputReference {
|
||||
weak var value: CustomEmojiReplaceableTextInput?
|
||||
public class CustomEmojiReplaceableTextInputReference {
|
||||
public weak var value: CustomEmojiReplaceableTextInput?
|
||||
|
||||
init(value: CustomEmojiReplaceableTextInput? = nil) {
|
||||
public init(value: CustomEmojiReplaceableTextInput? = nil) {
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
@ -83,21 +74,21 @@ extension UITextView: CustomEmojiReplaceableTextInput { }
|
|||
|
||||
extension ComposeStatusSection {
|
||||
|
||||
static func configureCustomEmojiPicker(
|
||||
viewModel: CustomEmojiPickerInputViewModel?,
|
||||
customEmojiReplaceableTextInput: CustomEmojiReplaceableTextInput,
|
||||
disposeBag: inout Set<AnyCancellable>
|
||||
) {
|
||||
guard let viewModel = viewModel else { return }
|
||||
viewModel.isCustomEmojiComposing
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak viewModel] isCustomEmojiComposing in
|
||||
guard let viewModel = viewModel else { return }
|
||||
customEmojiReplaceableTextInput.inputView = isCustomEmojiComposing ? viewModel.customEmojiPickerInputView : nil
|
||||
customEmojiReplaceableTextInput.reloadInputViews()
|
||||
viewModel.append(customEmojiReplaceableTextInput: customEmojiReplaceableTextInput)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
// static func configureCustomEmojiPicker(
|
||||
// viewModel: CustomEmojiPickerInputViewModel?,
|
||||
// customEmojiReplaceableTextInput: CustomEmojiReplaceableTextInput,
|
||||
// disposeBag: inout Set<AnyCancellable>
|
||||
// ) {
|
||||
// guard let viewModel = viewModel else { return }
|
||||
// viewModel.isCustomEmojiComposing
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak viewModel] isCustomEmojiComposing in
|
||||
// guard let viewModel = viewModel else { return }
|
||||
// customEmojiReplaceableTextInput.inputView = isCustomEmojiComposing ? viewModel.customEmojiPickerInputView : nil
|
||||
// customEmojiReplaceableTextInput.reloadInputViews()
|
||||
// viewModel.append(customEmojiReplaceableTextInput: customEmojiReplaceableTextInput)
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
// }
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
//
|
||||
// CustomEmojiPickerSection.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum CustomEmojiPickerSection: Equatable, Hashable {
|
||||
case emoji(name: String)
|
||||
}
|
|
@ -12,7 +12,6 @@ import MastodonSDK
|
|||
import CoreData
|
||||
import CoreDataStack
|
||||
import CommonOSLog
|
||||
import MastodonCore
|
||||
|
||||
extension APIService {
|
||||
|
||||
|
|
|
@ -79,7 +79,6 @@ extension AuthenticationService {
|
|||
|
||||
public func activeMastodonUser(domain: String, userID: MastodonUser.ID) async throws -> Bool {
|
||||
var isActive = false
|
||||
var _mastodonAuthentication: MastodonAuthentication?
|
||||
|
||||
let managedObjectContext = backgroundManagedObjectContext
|
||||
|
||||
|
@ -91,7 +90,6 @@ extension AuthenticationService {
|
|||
return
|
||||
}
|
||||
mastodonAuthentication.update(activedAt: Date())
|
||||
_mastodonAuthentication = mastodonAuthentication
|
||||
isActive = true
|
||||
}
|
||||
|
||||
|
|
|
@ -1,25 +1,20 @@
|
|||
//
|
||||
// AutoCompleteSection.swift
|
||||
// Mastodon
|
||||
// AutoCompleteSection+Diffable.swift
|
||||
//
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-5-17.
|
||||
// Created by MainasuK on 22/10/10.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonSDK
|
||||
import MastodonMeta
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
import MastodonCore
|
||||
|
||||
enum AutoCompleteSection: Equatable, Hashable {
|
||||
case main
|
||||
}
|
||||
import MastodonSDK
|
||||
import MastodonLocalization
|
||||
import MastodonMeta
|
||||
|
||||
extension AutoCompleteSection {
|
||||
|
||||
static func tableViewDiffableDataSource(
|
||||
for tableView: UITableView
|
||||
public static func tableViewDiffableDataSource(
|
||||
tableView: UITableView
|
||||
) -> UITableViewDiffableDataSource<AutoCompleteSection, AutoCompleteItem> {
|
||||
UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item in
|
||||
switch item {
|
|
@ -0,0 +1,59 @@
|
|||
//
|
||||
// CustomEmojiPickerSection+Diffable.swift
|
||||
//
|
||||
//
|
||||
// Created by MainasuK on 22/10/10.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MastodonCore
|
||||
|
||||
extension CustomEmojiPickerSection {
|
||||
// static func collectionViewDiffableDataSource(
|
||||
// collectionView: UICollectionView,
|
||||
// dependency: NeedsDependency
|
||||
// ) -> UICollectionViewDiffableDataSource<CustomEmojiPickerSection, CustomEmojiPickerItem> {
|
||||
// let dataSource = UICollectionViewDiffableDataSource<CustomEmojiPickerSection, CustomEmojiPickerItem>(collectionView: collectionView) { [weak dependency] collectionView, indexPath, item -> UICollectionViewCell? in
|
||||
// guard let _ = dependency else { return nil }
|
||||
// switch item {
|
||||
// case .emoji(let attribute):
|
||||
// let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: CustomEmojiPickerItemCollectionViewCell.self), for: indexPath) as! CustomEmojiPickerItemCollectionViewCell
|
||||
// let placeholder = UIImage.placeholder(size: CustomEmojiPickerItemCollectionViewCell.itemSize, color: .systemFill)
|
||||
// .af.imageRounded(withCornerRadius: 4)
|
||||
//
|
||||
// let isAnimated = !UserDefaults.shared.preferredStaticEmoji
|
||||
// let url = URL(string: isAnimated ? attribute.emoji.url : attribute.emoji.staticURL)
|
||||
// cell.emojiImageView.sd_setImage(
|
||||
// with: url,
|
||||
// placeholderImage: placeholder,
|
||||
// options: [],
|
||||
// context: nil
|
||||
// )
|
||||
// cell.accessibilityLabel = attribute.emoji.shortcode
|
||||
// return cell
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// dataSource.supplementaryViewProvider = { [weak dataSource] collectionView, kind, indexPath -> UICollectionReusableView? in
|
||||
// guard let dataSource = dataSource else { return nil }
|
||||
// let sections = dataSource.snapshot().sectionIdentifiers
|
||||
// guard indexPath.section < sections.count else { return nil }
|
||||
// let section = sections[indexPath.section]
|
||||
//
|
||||
// switch kind {
|
||||
// case String(describing: CustomEmojiPickerHeaderCollectionReusableView.self):
|
||||
// let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: String(describing: CustomEmojiPickerHeaderCollectionReusableView.self), for: indexPath) as! CustomEmojiPickerHeaderCollectionReusableView
|
||||
// switch section {
|
||||
// case .emoji(let name):
|
||||
// header.titleLabel.text = name
|
||||
// }
|
||||
// return header
|
||||
// default:
|
||||
// assertionFailure()
|
||||
// return nil
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return dataSource
|
||||
// }
|
||||
}
|
|
@ -88,7 +88,7 @@ extension AutoCompleteViewController {
|
|||
])
|
||||
|
||||
tableView.delegate = self
|
||||
viewModel.setupDiffableDataSource(for: tableView)
|
||||
// viewModel.setupDiffableDataSource(tableView: tableView)
|
||||
|
||||
// bind to layout chevron
|
||||
viewModel.symbolBoundingRect
|
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// AutoCompleteViewModel+Diffable.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-5-17.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension AutoCompleteViewModel {
|
||||
|
||||
// func setupDiffableDataSource(
|
||||
// tableView: UITableView
|
||||
// ) {
|
||||
// diffableDataSource = AutoCompleteSection.tableViewDiffableDataSource(for: tableView)
|
||||
//
|
||||
// var snapshot = NSDiffableDataSourceSnapshot<AutoCompleteSection, AutoCompleteItem>()
|
||||
// snapshot.appendSections([.main])
|
||||
// diffableDataSource?.apply(snapshot)
|
||||
// }
|
||||
|
||||
}
|
|
@ -12,16 +12,12 @@ import MastodonSDK
|
|||
import MastodonCore
|
||||
|
||||
extension AutoCompleteViewModel {
|
||||
class State: GKState, NamingState {
|
||||
class State: GKState {
|
||||
|
||||
let logger = Logger(subsystem: "AutoCompleteViewModel.State", category: "StateMachine")
|
||||
|
||||
let id = UUID()
|
||||
|
||||
var name: String {
|
||||
String(describing: Self.self)
|
||||
}
|
||||
|
||||
weak var viewModel: AutoCompleteViewModel?
|
||||
|
||||
init(viewModel: AutoCompleteViewModel) {
|
||||
|
@ -30,8 +26,10 @@ extension AutoCompleteViewModel {
|
|||
|
||||
override func didEnter(from previousState: GKState?) {
|
||||
super.didEnter(from: previousState)
|
||||
let previousState = previousState as? AutoCompleteViewModel.State
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] enter \(self.name), previous: \(previousState?.name ?? "<nil>")")
|
||||
|
||||
let from = previousState.flatMap { String(describing: $0) } ?? "nil"
|
||||
let to = String(describing: self)
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(from) -> \(to)")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
@ -40,7 +38,7 @@ extension AutoCompleteViewModel {
|
|||
}
|
||||
|
||||
deinit {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(self.name)")
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(String(describing: self))")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,10 +10,8 @@ import FLAnimatedImage
|
|||
import MetaTextKit
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
import MastodonUI
|
||||
|
||||
|
||||
final class AutoCompleteTableViewCell: UITableViewCell {
|
||||
public final class AutoCompleteTableViewCell: UITableViewCell {
|
||||
|
||||
static let avatarImageSize = CGSize(width: 42, height: 42)
|
||||
static let avatarImageCornerRadius: CGFloat = 4
|
||||
|
@ -51,17 +49,17 @@ final class AutoCompleteTableViewCell: UITableViewCell {
|
|||
|
||||
let separatorLine = UIView.separatorLine
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
_init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
public required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
_init()
|
||||
}
|
||||
|
||||
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
|
||||
public override func setHighlighted(_ highlighted: Bool, animated: Bool) {
|
||||
super.setHighlighted(highlighted, animated: animated)
|
||||
|
||||
// workaround for hitTest trigger highlighted issue
|
|
@ -8,11 +8,93 @@
|
|||
import SwiftUI
|
||||
|
||||
public struct ComposeContentView: View {
|
||||
|
||||
@ObservedObject var viewModel: ComposeContentViewModel
|
||||
|
||||
@State var contentOffsetDelta: CGFloat = .zero
|
||||
|
||||
public var body: some View {
|
||||
ScrollView {
|
||||
VStack {
|
||||
Text("Hello")
|
||||
}
|
||||
}
|
||||
}
|
||||
VStack(spacing: .zero) {
|
||||
GeometryReader { geometry in
|
||||
Color.clear.preference(
|
||||
key: ScrollOffsetPreferenceKey.self,
|
||||
value: geometry.frame(in: .named("scrollView")).origin
|
||||
)
|
||||
}.frame(width: 0, height: 0)
|
||||
.onPreferenceChange(ScrollOffsetPreferenceKey.self) { offset in
|
||||
print("contentOffset: \(offset)")
|
||||
}
|
||||
VStack {
|
||||
Text("Reply")
|
||||
}
|
||||
.frame(height: 100)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.blue)
|
||||
.background(
|
||||
GeometryReader { geometry in
|
||||
Color.clear.preference(
|
||||
key: ViewFramePreferenceKey.self,
|
||||
value: geometry.frame(in: .named("scrollView"))
|
||||
)
|
||||
}
|
||||
.onPreferenceChange(ViewFramePreferenceKey.self) { frame in
|
||||
print("reply frame: \(frame)")
|
||||
}
|
||||
)
|
||||
VStack {
|
||||
Text("Content")
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.orange)
|
||||
} // end VStack
|
||||
.offset(y: contentOffsetDelta)
|
||||
} // end ScrollView
|
||||
.coordinateSpace(name: "scrollView")
|
||||
} // end body
|
||||
}
|
||||
|
||||
private struct ScrollOffsetPreferenceKey: PreferenceKey {
|
||||
static var defaultValue: CGPoint = .zero
|
||||
|
||||
static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) { }
|
||||
}
|
||||
|
||||
private struct ViewFramePreferenceKey: PreferenceKey {
|
||||
static var defaultValue: CGRect = .zero
|
||||
|
||||
static func reduce(value: inout CGRect, nextValue: () -> CGRect) { }
|
||||
}
|
||||
|
||||
//struct ScrollView<Content: View>: View {
|
||||
// let axes: Axis.Set
|
||||
// let showsIndicators: Bool
|
||||
// let offsetChanged: (CGPoint) -> Void
|
||||
// let content: Content
|
||||
//
|
||||
// init(
|
||||
// axes: Axis.Set = .vertical,
|
||||
// showsIndicators: Bool = true,
|
||||
// offsetChanged: @escaping (CGPoint) -> Void = { _ in },
|
||||
// @ViewBuilder content: () -> Content
|
||||
// ) {
|
||||
// self.axes = axes
|
||||
// self.showsIndicators = showsIndicators
|
||||
// self.offsetChanged = offsetChanged
|
||||
// self.content = content()
|
||||
// }
|
||||
//
|
||||
// var body: some View {
|
||||
// SwiftUI.ScrollView(axes, showsIndicators: showsIndicators) {
|
||||
// GeometryReader { geometry in
|
||||
// Color.clear.preference(
|
||||
// key: ScrollOffsetPreferenceKey.self,
|
||||
// value: geometry.frame(in: .named("scrollView")).origin
|
||||
// )
|
||||
// }.frame(width: 0, height: 0)
|
||||
// content
|
||||
// }
|
||||
// .coordinateSpace(name: "scrollView")
|
||||
// .onPreferenceChange(ScrollOffsetPreferenceKey.self, perform: offsetChanged)
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -7,18 +7,47 @@
|
|||
|
||||
import os.log
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
|
||||
public final class ComposeContentViewController: UIViewController {
|
||||
|
||||
let logger = Logger(subsystem: "ComposeContentViewController", category: "ViewController")
|
||||
|
||||
public var viewModel: ComposeContentViewModel!
|
||||
|
||||
|
||||
let tableView: ComposeTableView = {
|
||||
let tableView = ComposeTableView()
|
||||
tableView.alwaysBounceVertical = true
|
||||
tableView.separatorStyle = .none
|
||||
tableView.tableFooterView = UIView()
|
||||
return tableView
|
||||
}()
|
||||
|
||||
deinit {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ComposeContentViewController {
|
||||
public override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(tableView)
|
||||
NSLayoutConstraint.activate([
|
||||
tableView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
])
|
||||
|
||||
tableView.delegate = self
|
||||
viewModel.setupDataSource(tableView: tableView)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
extension ComposeContentViewController: UITableViewDelegate { }
|
||||
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
//
|
||||
// ComposeContentViewModel+DataSource.swift
|
||||
//
|
||||
//
|
||||
// Created by MainasuK on 22/10/10.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonCore
|
||||
import CoreDataStack
|
||||
|
||||
extension ComposeContentViewModel {
|
||||
|
||||
func setupDataSource(
|
||||
tableView: UITableView
|
||||
) {
|
||||
tableView.dataSource = self
|
||||
|
||||
setupTableViewCell(tableView: tableView)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ComposeContentViewModel {
|
||||
enum Section: CaseIterable {
|
||||
case replyTo
|
||||
case status
|
||||
case attachment
|
||||
case poll
|
||||
}
|
||||
|
||||
private func setupTableViewCell(tableView: UITableView) {
|
||||
switch kind {
|
||||
case .post:
|
||||
break
|
||||
case .reply(let status):
|
||||
let cell = composeReplyToTableViewCell
|
||||
// bind frame publisher
|
||||
// cell.framePublisher
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .assign(to: \.repliedToCellFrame, on: self)
|
||||
// .store(in: &cell.disposeBag)
|
||||
|
||||
// set initial width
|
||||
cell.statusView.frame.size.width = tableView.frame.width
|
||||
|
||||
// configure status
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let replyTo = status.object(in: context.managedObjectContext) else { return }
|
||||
cell.statusView.configure(status: replyTo)
|
||||
}
|
||||
case .hashtag(let hashtag):
|
||||
break
|
||||
case .mention(let user):
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ComposeContentViewModel: UITableViewDataSource {
|
||||
public func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return Section.allCases.count
|
||||
}
|
||||
|
||||
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
switch Section.allCases[section] {
|
||||
case .replyTo:
|
||||
switch kind {
|
||||
case .reply: return 1
|
||||
default: return 0
|
||||
}
|
||||
case .status: return 1
|
||||
case .attachment: return 1
|
||||
case .poll: return 1
|
||||
}
|
||||
}
|
||||
|
||||
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
switch Section.allCases[indexPath.section] {
|
||||
case .replyTo:
|
||||
return composeReplyToTableViewCell
|
||||
case .status:
|
||||
return UITableViewCell()
|
||||
case .attachment:
|
||||
return UITableViewCell()
|
||||
case .poll:
|
||||
return UITableViewCell()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,15 +6,41 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
|
||||
final class ComposeContentViewModel: ObservableObject {
|
||||
public final class ComposeContentViewModel: NSObject, ObservableObject {
|
||||
|
||||
// tableViewCell
|
||||
let composeReplyToTableViewCell = ComposeReplyToTableViewCell()
|
||||
|
||||
// input
|
||||
let context: AppContext
|
||||
|
||||
init(context: AppContext) {
|
||||
let kind: Kind
|
||||
|
||||
public init(
|
||||
context: AppContext,
|
||||
kind: Kind
|
||||
) {
|
||||
self.context = context
|
||||
self.kind = kind
|
||||
super.init()
|
||||
// end init
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ComposeContentViewModel {
|
||||
public enum Kind {
|
||||
case post
|
||||
case hashtag(hashtag: String)
|
||||
case mention(user: ManagedObjectRecord<MastodonUser>)
|
||||
case reply(status: ManagedObjectRecord<Status>)
|
||||
}
|
||||
|
||||
public enum ViewState {
|
||||
case fold // snap to input
|
||||
case expand // snap to reply
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// ComposeRepliedToStatusContentTableViewCell.swift
|
||||
// ComposeReplyToTableViewCell.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-6-28.
|
||||
|
@ -7,15 +7,14 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import MastodonUI
|
||||
|
||||
final class ComposeRepliedToStatusContentTableViewCell: UITableViewCell {
|
||||
final class ComposeReplyToTableViewCell: UITableViewCell {
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
let statusView = StatusView()
|
||||
|
||||
let framePublisher = PassthroughSubject<CGRect, Never>()
|
||||
@Published var framePublisher: CGRect = .zero
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
@ -36,12 +35,12 @@ final class ComposeRepliedToStatusContentTableViewCell: UITableViewCell {
|
|||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
framePublisher.send(bounds)
|
||||
framePublisher = bounds
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ComposeRepliedToStatusContentTableViewCell {
|
||||
extension ComposeReplyToTableViewCell {
|
||||
|
||||
private func _init() {
|
||||
selectionStyle = .none
|
|
@ -0,0 +1,172 @@
|
|||
//
|
||||
// ComposeStatusAttachmentTableViewCell.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-6-29.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import Combine
|
||||
import AlamofireImage
|
||||
import MastodonAsset
|
||||
import MastodonCore
|
||||
import MastodonLocalization
|
||||
import UIHostingConfigurationBackport
|
||||
|
||||
//final class ComposeStatusAttachmentTableViewCell: UITableViewCell {
|
||||
//
|
||||
// private(set) var dataSource: UICollectionViewDiffableDataSource<ComposeStatusAttachmentSection, ComposeStatusAttachmentItem>!
|
||||
// weak var composeStatusAttachmentCollectionViewCellDelegate: ComposeStatusAttachmentCollectionViewCellDelegate?
|
||||
// var observations = Set<NSKeyValueObservation>()
|
||||
//
|
||||
// private static func createLayout() -> UICollectionViewLayout {
|
||||
// let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))
|
||||
// let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
||||
// let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))
|
||||
// let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
|
||||
// let section = NSCollectionLayoutSection(group: group)
|
||||
// section.contentInsetsReference = .readableContent
|
||||
// return UICollectionViewCompositionalLayout(section: section)
|
||||
// }
|
||||
//
|
||||
// private(set) var collectionViewHeightLayoutConstraint: NSLayoutConstraint!
|
||||
// let collectionView: UICollectionView = {
|
||||
// let collectionViewLayout = ComposeStatusAttachmentTableViewCell.createLayout()
|
||||
// let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
|
||||
// collectionView.register(ComposeStatusAttachmentCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusAttachmentCollectionViewCell.self))
|
||||
// collectionView.backgroundColor = .clear
|
||||
// collectionView.alwaysBounceVertical = true
|
||||
// collectionView.isScrollEnabled = false
|
||||
// return collectionView
|
||||
// }()
|
||||
// let collectionViewHeightDidUpdate = PassthroughSubject<Void, Never>()
|
||||
//
|
||||
// override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
// super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
// _init()
|
||||
// }
|
||||
//
|
||||
// required init?(coder: NSCoder) {
|
||||
// super.init(coder: coder)
|
||||
// _init()
|
||||
// }
|
||||
//
|
||||
//}
|
||||
//
|
||||
//extension ComposeStatusAttachmentTableViewCell {
|
||||
//
|
||||
// private func _init() {
|
||||
// backgroundColor = .clear
|
||||
// contentView.backgroundColor = .clear
|
||||
//
|
||||
// collectionView.translatesAutoresizingMaskIntoConstraints = false
|
||||
// contentView.addSubview(collectionView)
|
||||
// collectionViewHeightLayoutConstraint = collectionView.heightAnchor.constraint(equalToConstant: 200).priority(.defaultHigh)
|
||||
// NSLayoutConstraint.activate([
|
||||
// collectionView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||
// collectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||
// collectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||||
// collectionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
// collectionViewHeightLayoutConstraint,
|
||||
// ])
|
||||
//
|
||||
// collectionView.observe(\.contentSize, options: [.initial, .new]) { [weak self] collectionView, _ in
|
||||
// guard let self = self else { return }
|
||||
// self.collectionViewHeightLayoutConstraint.constant = collectionView.contentSize.height
|
||||
// self.collectionViewHeightDidUpdate.send()
|
||||
// }
|
||||
// .store(in: &observations)
|
||||
//
|
||||
// self.dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
|
||||
// [weak self] collectionView, indexPath, item -> UICollectionViewCell? in
|
||||
// guard let _ = self else { return UICollectionViewCell() }
|
||||
// switch item {
|
||||
// case .attachment:
|
||||
// let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusAttachmentCollectionViewCell.self), for: indexPath) as! ComposeStatusAttachmentCollectionViewCell
|
||||
// cell.contentConfiguration = UIHostingConfigurationBackport {
|
||||
// HStack {
|
||||
// Image(systemName: "star")
|
||||
// Text("Favorites")
|
||||
// Spacer()
|
||||
// }
|
||||
// }
|
||||
//// cell.attachmentContainerView.descriptionTextView.text = attachmentService.description.value
|
||||
//// cell.delegate = self.composeStatusAttachmentCollectionViewCellDelegate
|
||||
//// attachmentService.thumbnailImage
|
||||
//// .receive(on: DispatchQueue.main)
|
||||
//// .sink { [weak cell] thumbnailImage in
|
||||
//// guard let cell = cell else { return }
|
||||
//// let size = cell.attachmentContainerView.previewImageView.frame.size != .zero ? cell.attachmentContainerView.previewImageView.frame.size : CGSize(width: 1, height: 1)
|
||||
//// guard let image = thumbnailImage else {
|
||||
//// let placeholder = UIImage.placeholder(
|
||||
//// size: size,
|
||||
//// color: ThemeService.shared.currentTheme.value.systemGroupedBackgroundColor
|
||||
//// )
|
||||
//// .af.imageRounded(
|
||||
//// withCornerRadius: AttachmentContainerView.containerViewCornerRadius
|
||||
//// )
|
||||
//// cell.attachmentContainerView.previewImageView.image = placeholder
|
||||
//// return
|
||||
//// }
|
||||
//// // cannot get correct size. set corner radius on layer
|
||||
//// cell.attachmentContainerView.previewImageView.image = image
|
||||
//// }
|
||||
//// .store(in: &cell.disposeBag)
|
||||
//// Publishers.CombineLatest(
|
||||
//// attachmentService.uploadStateMachineSubject.eraseToAnyPublisher(),
|
||||
//// attachmentService.error.eraseToAnyPublisher()
|
||||
//// )
|
||||
//// .receive(on: DispatchQueue.main)
|
||||
//// .sink { [weak cell, weak attachmentService] uploadState, error in
|
||||
//// guard let cell = cell else { return }
|
||||
//// guard let attachmentService = attachmentService else { return }
|
||||
//// cell.attachmentContainerView.emptyStateView.isHidden = error == nil
|
||||
//// cell.attachmentContainerView.descriptionBackgroundView.isHidden = error != nil
|
||||
//// if let error = error {
|
||||
//// cell.attachmentContainerView.activityIndicatorView.stopAnimating()
|
||||
//// cell.attachmentContainerView.emptyStateView.label.text = error.localizedDescription
|
||||
//// } else {
|
||||
//// guard let uploadState = uploadState else { return }
|
||||
//// switch uploadState {
|
||||
//// case is MastodonAttachmentService.UploadState.Finish:
|
||||
//// cell.attachmentContainerView.activityIndicatorView.stopAnimating()
|
||||
//// case is MastodonAttachmentService.UploadState.Fail:
|
||||
//// cell.attachmentContainerView.activityIndicatorView.stopAnimating()
|
||||
//// // FIXME: not display
|
||||
//// cell.attachmentContainerView.emptyStateView.label.text = {
|
||||
//// if let file = attachmentService.file.value {
|
||||
//// switch file {
|
||||
//// case .jpeg, .png, .gif:
|
||||
//// return L10n.Scene.Compose.Attachment.attachmentBroken(L10n.Scene.Compose.Attachment.photo)
|
||||
//// case .other:
|
||||
//// return L10n.Scene.Compose.Attachment.attachmentBroken(L10n.Scene.Compose.Attachment.video)
|
||||
//// }
|
||||
//// } else {
|
||||
//// return L10n.Scene.Compose.Attachment.attachmentBroken(L10n.Scene.Compose.Attachment.photo)
|
||||
//// }
|
||||
//// }()
|
||||
//// default:
|
||||
//// break
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
//// .store(in: &cell.disposeBag)
|
||||
//// NotificationCenter.default.publisher(
|
||||
//// for: UITextView.textDidChangeNotification,
|
||||
//// object: cell.attachmentContainerView.descriptionTextView
|
||||
//// )
|
||||
//// .receive(on: DispatchQueue.main)
|
||||
//// .sink { notification in
|
||||
//// guard let textField = notification.object as? UITextView else { return }
|
||||
//// let text = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
//// attachmentService.description.value = text
|
||||
//// }
|
||||
//// .store(in: &cell.disposeBag)
|
||||
// return cell
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//}
|
||||
//
|
|
@ -0,0 +1,172 @@
|
|||
//
|
||||
// ComposeStatusContentTableViewCell.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-6-28.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
import MetaTextKit
|
||||
import UITextView_Placeholder
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
import MastodonUI
|
||||
|
||||
//protocol ComposeStatusContentTableViewCellDelegate: AnyObject {
|
||||
// func composeStatusContentTableViewCell(_ cell: ComposeStatusContentTableViewCell, textViewShouldBeginEditing textView: UITextView) -> Bool
|
||||
//}
|
||||
|
||||
final class ComposeStatusContentTableViewCell: UITableViewCell {
|
||||
|
||||
// let logger = Logger(subsystem: "ComposeStatusContentTableViewCell", category: "View")
|
||||
//
|
||||
// var disposeBag = Set<AnyCancellable>()
|
||||
// weak var delegate: ComposeStatusContentTableViewCellDelegate?
|
||||
//
|
||||
// let statusView = StatusView()
|
||||
//
|
||||
// let statusContentWarningEditorView = StatusContentWarningEditorView()
|
||||
//
|
||||
// let textEditorViewContainerView = UIView()
|
||||
//
|
||||
// static let metaTextViewTag: Int = 333
|
||||
// let metaText: MetaText = {
|
||||
// let metaText = MetaText()
|
||||
// metaText.textView.backgroundColor = .clear
|
||||
// metaText.textView.isScrollEnabled = false
|
||||
// metaText.textView.keyboardType = .twitter
|
||||
// metaText.textView.textDragInteraction?.isEnabled = false // disable drag for link and attachment
|
||||
// metaText.textView.textContainer.lineFragmentPadding = 10 // leading inset
|
||||
// metaText.textView.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular))
|
||||
// metaText.textView.attributedPlaceholder = {
|
||||
// var attributes = metaText.textAttributes
|
||||
// attributes[.foregroundColor] = Asset.Colors.Label.secondary.color
|
||||
// return NSAttributedString(
|
||||
// string: L10n.Scene.Compose.contentInputPlaceholder,
|
||||
// attributes: attributes
|
||||
// )
|
||||
// }()
|
||||
// metaText.paragraphStyle = {
|
||||
// let style = NSMutableParagraphStyle()
|
||||
// style.lineSpacing = 5
|
||||
// style.paragraphSpacing = 0
|
||||
// return style
|
||||
// }()
|
||||
// metaText.textAttributes = [
|
||||
// .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)),
|
||||
// .foregroundColor: Asset.Colors.Label.primary.color,
|
||||
// ]
|
||||
// metaText.linkAttributes = [
|
||||
// .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)),
|
||||
// .foregroundColor: Asset.Colors.brand.color,
|
||||
// ]
|
||||
// return metaText
|
||||
// }()
|
||||
//
|
||||
// // output
|
||||
// let contentWarningContent = PassthroughSubject<String, Never>()
|
||||
//
|
||||
// override func prepareForReuse() {
|
||||
// super.prepareForReuse()
|
||||
//
|
||||
// metaText.delegate = nil
|
||||
// metaText.textView.delegate = nil
|
||||
// }
|
||||
//
|
||||
// override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
// super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
// _init()
|
||||
// }
|
||||
//
|
||||
// required init?(coder: NSCoder) {
|
||||
// super.init(coder: coder)
|
||||
// _init()
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
extension ComposeStatusContentTableViewCell {
|
||||
|
||||
// private func _init() {
|
||||
// selectionStyle = .none
|
||||
// layer.zPosition = 999
|
||||
// backgroundColor = .clear
|
||||
// preservesSuperviewLayoutMargins = true
|
||||
//
|
||||
// let containerStackView = UIStackView()
|
||||
// containerStackView.axis = .vertical
|
||||
// containerStackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
// contentView.addSubview(containerStackView)
|
||||
// NSLayoutConstraint.activate([
|
||||
// containerStackView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||
// containerStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||
// containerStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||||
// containerStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
// ])
|
||||
// containerStackView.preservesSuperviewLayoutMargins = true
|
||||
//
|
||||
// containerStackView.addArrangedSubview(statusContentWarningEditorView)
|
||||
// statusContentWarningEditorView.setContentHuggingPriority(.required - 1, for: .vertical)
|
||||
//
|
||||
// let statusContainerView = UIView()
|
||||
// statusContainerView.preservesSuperviewLayoutMargins = true
|
||||
// containerStackView.addArrangedSubview(statusContainerView)
|
||||
// statusView.translatesAutoresizingMaskIntoConstraints = false
|
||||
// statusContainerView.addSubview(statusView)
|
||||
// NSLayoutConstraint.activate([
|
||||
// statusView.topAnchor.constraint(equalTo: statusContainerView.topAnchor, constant: 20),
|
||||
// statusView.leadingAnchor.constraint(equalTo: statusContainerView.leadingAnchor),
|
||||
// statusView.trailingAnchor.constraint(equalTo: statusContainerView.trailingAnchor),
|
||||
// statusView.bottomAnchor.constraint(equalTo: statusContainerView.bottomAnchor),
|
||||
// ])
|
||||
// statusView.setup(style: .composeStatusAuthor)
|
||||
//
|
||||
// containerStackView.addArrangedSubview(textEditorViewContainerView)
|
||||
// metaText.textView.translatesAutoresizingMaskIntoConstraints = false
|
||||
// textEditorViewContainerView.addSubview(metaText.textView)
|
||||
// NSLayoutConstraint.activate([
|
||||
// metaText.textView.topAnchor.constraint(equalTo: textEditorViewContainerView.topAnchor),
|
||||
// metaText.textView.leadingAnchor.constraint(equalTo: textEditorViewContainerView.layoutMarginsGuide.leadingAnchor),
|
||||
// metaText.textView.trailingAnchor.constraint(equalTo: textEditorViewContainerView.layoutMarginsGuide.trailingAnchor),
|
||||
// metaText.textView.bottomAnchor.constraint(equalTo: textEditorViewContainerView.bottomAnchor),
|
||||
// metaText.textView.heightAnchor.constraint(greaterThanOrEqualToConstant: 64).priority(.defaultHigh),
|
||||
// ])
|
||||
// statusContentWarningEditorView.textView.delegate = self
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UITextViewDelegate
|
||||
//extension ComposeStatusContentTableViewCell: UITextViewDelegate {
|
||||
//
|
||||
// func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
|
||||
// return delegate?.composeStatusContentTableViewCell(self, textViewShouldBeginEditing: textView) ?? true
|
||||
// }
|
||||
//
|
||||
// func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||
// switch textView {
|
||||
// case statusContentWarningEditorView.textView:
|
||||
// // disable input line break
|
||||
// guard text != "\n" else { return false }
|
||||
// return true
|
||||
// default:
|
||||
// assertionFailure()
|
||||
// return true
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func textViewDidChange(_ textView: UITextView) {
|
||||
// logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): text: \(textView.text ?? "<nil>")")
|
||||
// guard textView === statusContentWarningEditorView.textView else { return }
|
||||
// // replace line break with space
|
||||
// // needs check input state to prevent break the IME
|
||||
// if textView.markedTextRange == nil {
|
||||
// textView.text = textView.text.replacingOccurrences(of: "\n", with: " ")
|
||||
// }
|
||||
// contentWarningContent.send(textView.text)
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
//
|
||||
// ComposeStatusPollTableViewCell.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-6-29.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
||||
//protocol ComposeStatusPollTableViewCellDelegate: AnyObject {
|
||||
// func composeStatusPollTableViewCell(_ cell: ComposeStatusPollTableViewCell, pollOptionAttributesDidReorder options: [ComposeStatusPollItem.PollOptionAttribute])
|
||||
//}
|
||||
//
|
||||
//final class ComposeStatusPollTableViewCell: UITableViewCell {
|
||||
//
|
||||
// let logger = Logger(subsystem: "ComposeStatusPollTableViewCell", category: "UI")
|
||||
//
|
||||
// private(set) var dataSource: UICollectionViewDiffableDataSource<ComposeStatusPollSection, ComposeStatusPollItem>!
|
||||
// var observations = Set<NSKeyValueObservation>()
|
||||
//
|
||||
// weak var customEmojiPickerInputViewModel: CustomEmojiPickerInputViewModel?
|
||||
// weak var delegate: ComposeStatusPollTableViewCellDelegate?
|
||||
// weak var composeStatusPollOptionCollectionViewCellDelegate: ComposeStatusPollOptionCollectionViewCellDelegate?
|
||||
// weak var composeStatusPollOptionAppendEntryCollectionViewCellDelegate: ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate?
|
||||
// weak var composeStatusPollExpiresOptionCollectionViewCellDelegate: ComposeStatusPollExpiresOptionCollectionViewCellDelegate?
|
||||
//
|
||||
// private static func createLayout() -> UICollectionViewLayout {
|
||||
// let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))
|
||||
// let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
||||
// let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))
|
||||
// let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
|
||||
// let section = NSCollectionLayoutSection(group: group)
|
||||
// section.contentInsetsReference = .readableContent
|
||||
// return UICollectionViewCompositionalLayout(section: section)
|
||||
// }
|
||||
//
|
||||
// private(set) var collectionViewHeightLayoutConstraint: NSLayoutConstraint!
|
||||
// let collectionView: UICollectionView = {
|
||||
// let collectionViewLayout = ComposeStatusPollTableViewCell.createLayout()
|
||||
// let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
|
||||
// collectionView.register(ComposeStatusPollOptionCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusPollOptionCollectionViewCell.self))
|
||||
// collectionView.register(ComposeStatusPollOptionAppendEntryCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusPollOptionAppendEntryCollectionViewCell.self))
|
||||
// collectionView.register(ComposeStatusPollExpiresOptionCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusPollExpiresOptionCollectionViewCell.self))
|
||||
// collectionView.backgroundColor = .clear
|
||||
// collectionView.alwaysBounceVertical = true
|
||||
// collectionView.isScrollEnabled = false
|
||||
// collectionView.dragInteractionEnabled = true
|
||||
// return collectionView
|
||||
// }()
|
||||
// let collectionViewHeightDidUpdate = PassthroughSubject<Void, Never>()
|
||||
//
|
||||
// override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
// super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
// _init()
|
||||
// }
|
||||
//
|
||||
// required init?(coder: NSCoder) {
|
||||
// super.init(coder: coder)
|
||||
// _init()
|
||||
// }
|
||||
//
|
||||
//}
|
||||
//
|
||||
//extension ComposeStatusPollTableViewCell {
|
||||
//
|
||||
// private func _init() {
|
||||
// backgroundColor = .clear
|
||||
// contentView.backgroundColor = .clear
|
||||
//
|
||||
// collectionView.translatesAutoresizingMaskIntoConstraints = false
|
||||
// contentView.addSubview(collectionView)
|
||||
// collectionViewHeightLayoutConstraint = collectionView.heightAnchor.constraint(equalToConstant: 300).priority(.defaultHigh)
|
||||
// NSLayoutConstraint.activate([
|
||||
// collectionView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||
// collectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||
// collectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||||
// collectionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
// collectionViewHeightLayoutConstraint,
|
||||
// ])
|
||||
//
|
||||
// collectionView.observe(\.contentSize, options: [.initial, .new]) { [weak self] collectionView, _ in
|
||||
// guard let self = self else { return }
|
||||
// self.collectionViewHeightLayoutConstraint.constant = collectionView.contentSize.height
|
||||
// self.collectionViewHeightDidUpdate.send()
|
||||
// }
|
||||
// .store(in: &observations)
|
||||
//
|
||||
// self.dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { [
|
||||
// weak self
|
||||
// ] collectionView, indexPath, item -> UICollectionViewCell? in
|
||||
// guard let self = self else { return UICollectionViewCell() }
|
||||
//
|
||||
// switch item {
|
||||
// case .pollOption(let attribute):
|
||||
// let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusPollOptionCollectionViewCell.self), for: indexPath) as! ComposeStatusPollOptionCollectionViewCell
|
||||
// cell.pollOptionView.optionTextField.text = attribute.option.value
|
||||
// cell.pollOptionView.optionTextField.placeholder = L10n.Scene.Compose.Poll.optionNumber(indexPath.item + 1)
|
||||
// cell.pollOption
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .assign(to: \.value, on: attribute.option)
|
||||
// .store(in: &cell.disposeBag)
|
||||
// cell.delegate = self.composeStatusPollOptionCollectionViewCellDelegate
|
||||
// if let customEmojiPickerInputViewModel = self.customEmojiPickerInputViewModel {
|
||||
// ComposeStatusSection.configureCustomEmojiPicker(viewModel: customEmojiPickerInputViewModel, customEmojiReplaceableTextInput: cell.pollOptionView.optionTextField, disposeBag: &cell.disposeBag)
|
||||
// }
|
||||
// return cell
|
||||
// case .pollOptionAppendEntry:
|
||||
// let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusPollOptionAppendEntryCollectionViewCell.self), for: indexPath) as! ComposeStatusPollOptionAppendEntryCollectionViewCell
|
||||
// cell.delegate = self.composeStatusPollOptionAppendEntryCollectionViewCellDelegate
|
||||
// return cell
|
||||
// case .pollExpiresOption(let attribute):
|
||||
// let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusPollExpiresOptionCollectionViewCell.self), for: indexPath) as! ComposeStatusPollExpiresOptionCollectionViewCell
|
||||
// cell.durationButton.setTitle(L10n.Scene.Compose.Poll.durationTime(attribute.expiresOption.value.title), for: .normal)
|
||||
// attribute.expiresOption
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak cell] expiresOption in
|
||||
// guard let cell = cell else { return }
|
||||
// cell.durationButton.setTitle(L10n.Scene.Compose.Poll.durationTime(expiresOption.title), for: .normal)
|
||||
// }
|
||||
// .store(in: &cell.disposeBag)
|
||||
// cell.delegate = self.composeStatusPollExpiresOptionCollectionViewCellDelegate
|
||||
// return cell
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// collectionView.dragDelegate = self
|
||||
// collectionView.dropDelegate = self
|
||||
// }
|
||||
//
|
||||
//}
|
||||
//
|
||||
//// MARK: - UICollectionViewDragDelegate
|
||||
//extension ComposeStatusPollTableViewCell: UICollectionViewDragDelegate {
|
||||
//
|
||||
// func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
|
||||
// guard let item = dataSource.itemIdentifier(for: indexPath) else { return [] }
|
||||
// switch item {
|
||||
// case .pollOption:
|
||||
// let itemProvider = NSItemProvider(object: String(item.hashValue) as NSString)
|
||||
// let dragItem = UIDragItem(itemProvider: itemProvider)
|
||||
// dragItem.localObject = item
|
||||
// return [dragItem]
|
||||
// default:
|
||||
// return []
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func collectionView(_ collectionView: UICollectionView, dragSessionIsRestrictedToDraggingApplication session: UIDragSession) -> Bool {
|
||||
// // drag to app should be the same app
|
||||
// return true
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// MARK: - UICollectionViewDropDelegate
|
||||
//extension ComposeStatusPollTableViewCell: UICollectionViewDropDelegate {
|
||||
// // didUpdate
|
||||
// func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
|
||||
// guard collectionView.hasActiveDrag,
|
||||
// let destinationIndexPath = destinationIndexPath,
|
||||
// let item = dataSource.itemIdentifier(for: destinationIndexPath)
|
||||
// else {
|
||||
// return UICollectionViewDropProposal(operation: .forbidden)
|
||||
// }
|
||||
//
|
||||
// switch item {
|
||||
// case .pollOption:
|
||||
// return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
|
||||
// default:
|
||||
// return UICollectionViewDropProposal(operation: .cancel)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // performDrop
|
||||
// func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
|
||||
// guard let dropItem = coordinator.items.first,
|
||||
// let item = dropItem.dragItem.localObject as? ComposeStatusPollItem,
|
||||
// case .pollOption = item
|
||||
// else { return }
|
||||
//
|
||||
// guard coordinator.proposal.operation == .move else { return }
|
||||
// guard let destinationIndexPath = coordinator.destinationIndexPath,
|
||||
// let _ = collectionView.cellForItem(at: destinationIndexPath) as? ComposeStatusPollOptionCollectionViewCell
|
||||
// else { return }
|
||||
//
|
||||
// var snapshot = dataSource.snapshot()
|
||||
// guard destinationIndexPath.row < snapshot.itemIdentifiers.count else { return }
|
||||
// let anchorItem = snapshot.itemIdentifiers[destinationIndexPath.row]
|
||||
// snapshot.moveItem(item, afterItem: anchorItem)
|
||||
// dataSource.apply(snapshot)
|
||||
//
|
||||
// coordinator.drop(dropItem.dragItem, toItemAt: destinationIndexPath)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//extension ComposeStatusPollTableViewCell: UICollectionViewDelegate {
|
||||
// func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath {
|
||||
// logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(originalIndexPath.debugDescription) -> \(proposedIndexPath.debugDescription)")
|
||||
//
|
||||
// guard let _ = collectionView.cellForItem(at: proposedIndexPath) as? ComposeStatusPollOptionCollectionViewCell else {
|
||||
// return originalIndexPath
|
||||
// }
|
||||
//
|
||||
// return proposedIndexPath
|
||||
// }
|
||||
//}
|
|
@ -17,9 +17,9 @@ import UIKit
|
|||
// they feel broken. Feel free to add your own exceptions if you have custom
|
||||
// controls that require swiping or dragging to function.
|
||||
|
||||
final class ControlContainableScrollView: UIScrollView {
|
||||
public final class ControlContainableScrollView: UIScrollView {
|
||||
|
||||
override func touchesShouldCancel(in view: UIView) -> Bool {
|
||||
public override func touchesShouldCancel(in view: UIView) -> Bool {
|
||||
if view is UIControl
|
||||
&& !(view is UITextInput)
|
||||
&& !(view is UISlider)
|
||||
|
@ -32,9 +32,9 @@ final class ControlContainableScrollView: UIScrollView {
|
|||
|
||||
}
|
||||
|
||||
final class ControlContainableTableView: UITableView {
|
||||
public final class ControlContainableTableView: UITableView {
|
||||
|
||||
override func touchesShouldCancel(in view: UIView) -> Bool {
|
||||
public override func touchesShouldCancel(in view: UIView) -> Bool {
|
||||
if view is UIControl
|
||||
&& !(view is UITextInput)
|
||||
&& !(view is UISlider)
|
||||
|
@ -47,9 +47,9 @@ final class ControlContainableTableView: UITableView {
|
|||
|
||||
}
|
||||
|
||||
final class ControlContainableCollectionView: UICollectionView {
|
||||
public final class ControlContainableCollectionView: UICollectionView {
|
||||
|
||||
override func touchesShouldCancel(in view: UIView) -> Bool {
|
||||
public override func touchesShouldCancel(in view: UIView) -> Bool {
|
||||
if view is UIControl
|
||||
&& !(view is UITextInput)
|
||||
&& !(view is UISlider)
|
|
@ -9,6 +9,7 @@
|
|||
import UIKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import Photos
|
||||
import AlamofireImage
|
||||
import MastodonCore
|
||||
|
@ -178,3 +179,59 @@ extension MediaView.Configuration {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
extension MediaView {
|
||||
public static func configuration(status: Status) -> [MediaView.Configuration] {
|
||||
func videoInfo(from attachment: MastodonAttachment) -> MediaView.Configuration.VideoInfo {
|
||||
MediaView.Configuration.VideoInfo(
|
||||
aspectRadio: attachment.size,
|
||||
assetURL: attachment.assetURL,
|
||||
previewURL: attachment.previewURL,
|
||||
durationMS: attachment.durationMS
|
||||
)
|
||||
}
|
||||
|
||||
let status = status.reblog ?? status
|
||||
let attachments = status.attachments
|
||||
let configurations = attachments.map { attachment -> MediaView.Configuration in
|
||||
let configuration: MediaView.Configuration = {
|
||||
switch attachment.kind {
|
||||
case .image:
|
||||
let info = MediaView.Configuration.ImageInfo(
|
||||
aspectRadio: attachment.size,
|
||||
assetURL: attachment.assetURL
|
||||
)
|
||||
return .init(
|
||||
info: .image(info: info),
|
||||
blurhash: attachment.blurhash
|
||||
)
|
||||
case .video:
|
||||
let info = videoInfo(from: attachment)
|
||||
return .init(
|
||||
info: .video(info: info),
|
||||
blurhash: attachment.blurhash
|
||||
)
|
||||
case .gifv:
|
||||
let info = videoInfo(from: attachment)
|
||||
return .init(
|
||||
info: .gif(info: info),
|
||||
blurhash: attachment.blurhash
|
||||
)
|
||||
case .audio:
|
||||
let info = videoInfo(from: attachment)
|
||||
return .init(
|
||||
info: .video(info: info),
|
||||
blurhash: attachment.blurhash
|
||||
)
|
||||
} // end switch
|
||||
}()
|
||||
|
||||
configuration.load()
|
||||
configuration.isReveal = status.isMediaSensitive ? status.isSensitiveToggled : true
|
||||
|
||||
return configuration
|
||||
}
|
||||
|
||||
return configurations
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,11 +7,9 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import MastodonUI
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonLocalization
|
||||
import MastodonMeta
|
||||
import Meta
|
||||
|
@ -125,11 +123,12 @@ extension StatusView {
|
|||
if let authenticationBox = viewModel.authContext?.mastodonAuthenticationBox {
|
||||
Just(inReplyToAccountID)
|
||||
.asyncMap { userID in
|
||||
return try await AppContext.shared.apiService.accountInfo(
|
||||
return try await Mastodon.API.Account.accountInfo(
|
||||
session: .shared,
|
||||
domain: authenticationBox.domain,
|
||||
userID: userID,
|
||||
authorization: authenticationBox.userAuthorization
|
||||
)
|
||||
).singleOutput()
|
||||
}
|
||||
.sink { completion in
|
||||
// do nothing
|
|
@ -10,7 +10,7 @@ import UIKit
|
|||
import Combine
|
||||
import MastodonCore
|
||||
|
||||
final class SawToothView: UIView {
|
||||
public final class SawToothView: UIView {
|
||||
static let widthUint = 8
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
@ -41,7 +41,7 @@ final class SawToothView: UIView {
|
|||
setNeedsDisplay()
|
||||
}
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
public override func draw(_ rect: CGRect) {
|
||||
let bezierPath = UIBezierPath()
|
||||
let bottomY = rect.height
|
||||
let topY = 0
|
|
@ -8,18 +8,17 @@
|
|||
import UIKit
|
||||
import Combine
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
|
||||
final class TimelineBottomLoaderTableViewCell: TimelineLoaderTableViewCell {
|
||||
public final class TimelineBottomLoaderTableViewCell: TimelineLoaderTableViewCell {
|
||||
|
||||
override func prepareForReuse() {
|
||||
public override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
||||
loadMoreLabel.isHidden = true
|
||||
loadMoreButton.isHidden = true
|
||||
}
|
||||
|
||||
override func _init() {
|
||||
public override func _init() {
|
||||
super._init()
|
||||
|
||||
activityIndicatorView.isHidden = false
|
|
@ -9,23 +9,22 @@ import UIKit
|
|||
import Combine
|
||||
import MastodonAsset
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonLocalization
|
||||
|
||||
class TimelineLoaderTableViewCell: UITableViewCell {
|
||||
open class TimelineLoaderTableViewCell: UITableViewCell {
|
||||
|
||||
static let buttonHeight: CGFloat = 44
|
||||
static let buttonMargin: CGFloat = 12
|
||||
static let cellHeight: CGFloat = buttonHeight + 2 * buttonMargin
|
||||
static let labelFont = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .medium))
|
||||
public static let buttonHeight: CGFloat = 44
|
||||
public static let buttonMargin: CGFloat = 12
|
||||
public static let cellHeight: CGFloat = buttonHeight + 2 * buttonMargin
|
||||
public static let labelFont = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .medium))
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
private var _disposeBag = Set<AnyCancellable>()
|
||||
|
||||
let stackView = UIStackView()
|
||||
public let stackView = UIStackView()
|
||||
|
||||
let loadMoreButton: UIButton = {
|
||||
public let loadMoreButton: UIButton = {
|
||||
let button = HighlightDimmableButton()
|
||||
button.titleLabel?.font = TimelineLoaderTableViewCell.labelFont
|
||||
button.setTitleColor(ThemeService.tintColor, for: .normal)
|
||||
|
@ -34,49 +33,49 @@ class TimelineLoaderTableViewCell: UITableViewCell {
|
|||
return button
|
||||
}()
|
||||
|
||||
let loadMoreLabel: UILabel = {
|
||||
public let loadMoreLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = TimelineLoaderTableViewCell.labelFont
|
||||
return label
|
||||
}()
|
||||
|
||||
let activityIndicatorView: UIActivityIndicatorView = {
|
||||
public let activityIndicatorView: UIActivityIndicatorView = {
|
||||
let activityIndicatorView = UIActivityIndicatorView(style: .medium)
|
||||
activityIndicatorView.tintColor = Asset.Colors.Label.secondary.color
|
||||
activityIndicatorView.hidesWhenStopped = true
|
||||
return activityIndicatorView
|
||||
}()
|
||||
|
||||
override func prepareForReuse() {
|
||||
public override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
disposeBag.removeAll()
|
||||
}
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
_init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
public required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
_init()
|
||||
}
|
||||
|
||||
func startAnimating() {
|
||||
public func startAnimating() {
|
||||
activityIndicatorView.startAnimating()
|
||||
self.loadMoreButton.isEnabled = false
|
||||
self.loadMoreLabel.textColor = Asset.Colors.Label.secondary.color
|
||||
self.loadMoreLabel.text = L10n.Common.Controls.Timeline.Loader.loadingMissingPosts
|
||||
}
|
||||
|
||||
func stopAnimating() {
|
||||
public func stopAnimating() {
|
||||
activityIndicatorView.stopAnimating()
|
||||
self.loadMoreButton.isEnabled = true
|
||||
self.loadMoreLabel.textColor = ThemeService.tintColor
|
||||
self.loadMoreLabel.text = ""
|
||||
}
|
||||
|
||||
func _init() {
|
||||
open func _init() {
|
||||
selectionStyle = .none
|
||||
backgroundColor = .clear
|
||||
|
|
@ -10,7 +10,7 @@ import Combine
|
|||
import CoreDataStack
|
||||
|
||||
extension TimelineMiddleLoaderTableViewCell {
|
||||
class ViewModel {
|
||||
public class ViewModel {
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
@Published var isFetching = false
|
||||
|
@ -18,7 +18,7 @@ extension TimelineMiddleLoaderTableViewCell {
|
|||
}
|
||||
|
||||
extension TimelineMiddleLoaderTableViewCell.ViewModel {
|
||||
func bind(cell: TimelineMiddleLoaderTableViewCell) {
|
||||
public func bind(cell: TimelineMiddleLoaderTableViewCell) {
|
||||
$isFetching
|
||||
.sink { isFetching in
|
||||
if isFetching {
|
||||
|
@ -33,7 +33,7 @@ extension TimelineMiddleLoaderTableViewCell.ViewModel {
|
|||
|
||||
|
||||
extension TimelineMiddleLoaderTableViewCell {
|
||||
func configure(
|
||||
public func configure(
|
||||
feed: Feed,
|
||||
delegate: TimelineMiddleLoaderTableViewCellDelegate?
|
||||
) {
|
|
@ -9,13 +9,12 @@ import Combine
|
|||
import CoreData
|
||||
import os.log
|
||||
import UIKit
|
||||
import MastodonUI
|
||||
|
||||
protocol TimelineMiddleLoaderTableViewCellDelegate: AnyObject {
|
||||
public protocol TimelineMiddleLoaderTableViewCellDelegate: AnyObject {
|
||||
func timelineMiddleLoaderTableViewCell(_ cell: TimelineMiddleLoaderTableViewCell, loadMoreButtonDidPressed button: UIButton)
|
||||
}
|
||||
|
||||
final class TimelineMiddleLoaderTableViewCell: TimelineLoaderTableViewCell {
|
||||
public final class TimelineMiddleLoaderTableViewCell: TimelineLoaderTableViewCell {
|
||||
|
||||
weak var delegate: TimelineMiddleLoaderTableViewCellDelegate?
|
||||
|
||||
|
@ -28,7 +27,7 @@ final class TimelineMiddleLoaderTableViewCell: TimelineLoaderTableViewCell {
|
|||
let topSawToothView = SawToothView()
|
||||
let bottomSawToothView = SawToothView()
|
||||
|
||||
override func _init() {
|
||||
public override func _init() {
|
||||
super._init()
|
||||
|
||||
loadMoreButton.isHidden = false
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue