From 23491e60b90c0a87673cf08648822981aa25dc85 Mon Sep 17 00:00:00 2001 From: sunxiaojian Date: Wed, 28 Apr 2021 16:18:20 +0800 Subject: [PATCH 01/10] chore: add userProvider chore: add userProvider --- Mastodon.xcodeproj/project.pbxproj | 48 +++++++++---------- .../Protocol/UserProvider/UserProvider.swift | 21 ++++++++ .../UserProvider/UserProviderFacade.swift | 36 +++++++------- ...htagTimelineViewController+Provider.swift} | 3 +- ...HomeTimelineViewController+Provider.swift} | 4 +- ... => FavoriteViewController+Provider.swift} | 2 + .../ProfileViewController+UserProvider.swift | 7 +++ ...UserTimelineViewController+Provider.swift} | 4 +- ...blicTimelineViewController+Provider.swift} | 4 +- .../Search/SearchViewController+Follow.swift | 7 +++ .../TableviewCell/StatusTableViewCell.swift | 2 +- ...ft => ThreadViewController+Provider.swift} | 4 +- 12 files changed, 94 insertions(+), 48 deletions(-) rename Mastodon/Scene/HashtagTimeline/{HashtagTimelineViewController+StatusProvider.swift => HashtagTimelineViewController+Provider.swift} (96%) rename Mastodon/Scene/HomeTimeline/{HomeTimelineViewController+StatusProvider.swift => HomeTimelineViewController+Provider.swift} (96%) rename Mastodon/Scene/Profile/Favorite/{FavoriteViewController+StatusProvider.swift => FavoriteViewController+Provider.swift} (98%) rename Mastodon/Scene/Profile/Timeline/{UserTimelineViewController+StatusProvider.swift => UserTimelineViewController+Provider.swift} (96%) rename Mastodon/Scene/PublicTimeline/{PublicTimelineViewController+StatusProvider.swift => PublicTimelineViewController+Provider.swift} (96%) rename Mastodon/Scene/Thread/{ThreadViewController+StatusProvider.swift => ThreadViewController+Provider.swift} (96%) diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 041a837c5..99ca696f1 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -13,7 +13,7 @@ 0F20220726134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F20220626134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift */; }; 0F20220D26134E3F000C64BF /* HashtagTimelineViewModel+LoadLatestState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F20220C26134E3F000C64BF /* HashtagTimelineViewModel+LoadLatestState.swift */; }; 0F202213261351F5000C64BF /* APIService+HashtagTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F202212261351F5000C64BF /* APIService+HashtagTimeline.swift */; }; - 0F202227261411BB000C64BF /* HashtagTimelineViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F202226261411BA000C64BF /* HashtagTimelineViewController+StatusProvider.swift */; }; + 0F202227261411BB000C64BF /* HashtagTimelineViewController+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F202226261411BA000C64BF /* HashtagTimelineViewController+Provider.swift */; }; 0F20222D261457EE000C64BF /* HashtagTimelineViewModel+LoadOldestState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F20222C261457EE000C64BF /* HashtagTimelineViewModel+LoadOldestState.swift */; }; 0F20223326145E51000C64BF /* HashtagTimelineViewModel+LoadMiddleState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F20223226145E51000C64BF /* HashtagTimelineViewModel+LoadMiddleState.swift */; }; 0F20223926146553000C64BF /* Array+removeDuplicates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F20223826146553000C64BF /* Array+removeDuplicates.swift */; }; @@ -59,7 +59,7 @@ 2D364F7825E66D8300204FDC /* MastodonResendEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D364F7725E66D8300204FDC /* MastodonResendEmailViewModel.swift */; }; 2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */; }; 2D38F1D525CD465300561493 /* HomeTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1D425CD465300561493 /* HomeTimelineViewController.swift */; }; - 2D38F1DF25CD46A400561493 /* HomeTimelineViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1DE25CD46A400561493 /* HomeTimelineViewController+StatusProvider.swift */; }; + 2D38F1DF25CD46A400561493 /* HomeTimelineViewController+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1DE25CD46A400561493 /* HomeTimelineViewController+Provider.swift */; }; 2D38F1E525CD46C100561493 /* HomeTimelineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1E425CD46C100561493 /* HomeTimelineViewModel.swift */; }; 2D38F1EB25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1EA25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift */; }; 2D38F1F125CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1F025CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift */; }; @@ -98,7 +98,7 @@ 2D6DE40026141DF600A63F6A /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D6DE3FF26141DF600A63F6A /* SearchViewModel.swift */; }; 2D76316525C14BD100929FB9 /* PublicTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76316425C14BD100929FB9 /* PublicTimelineViewController.swift */; }; 2D76316B25C14D4C00929FB9 /* PublicTimelineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76316A25C14D4C00929FB9 /* PublicTimelineViewModel.swift */; }; - 2D76317D25C14DF500929FB9 /* PublicTimelineViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76317C25C14DF400929FB9 /* PublicTimelineViewController+StatusProvider.swift */; }; + 2D76317D25C14DF500929FB9 /* PublicTimelineViewController+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76317C25C14DF400929FB9 /* PublicTimelineViewController+Provider.swift */; }; 2D76318325C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76318225C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift */; }; 2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76319E25C1521200929FB9 /* StatusSection.swift */; }; 2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7631A725C1535600929FB9 /* StatusTableViewCell.swift */; }; @@ -233,7 +233,7 @@ DB45FB1D25CA9D23005A8AC7 /* APIService+HomeTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FB1C25CA9D23005A8AC7 /* APIService+HomeTimeline.swift */; }; DB47229725F9EFAD00DA7F53 /* NSManagedObjectContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB47229625F9EFAD00DA7F53 /* NSManagedObjectContext.swift */; }; DB482A3F261331E8008AE74C /* UserTimelineViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB482A3E261331E8008AE74C /* UserTimelineViewModel+State.swift */; }; - DB482A45261335BA008AE74C /* UserTimelineViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB482A44261335BA008AE74C /* UserTimelineViewController+StatusProvider.swift */; }; + DB482A45261335BA008AE74C /* UserTimelineViewController+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB482A44261335BA008AE74C /* UserTimelineViewController+Provider.swift */; }; DB482A4B261340A7008AE74C /* APIService+UserTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB482A4A261340A7008AE74C /* APIService+UserTimeline.swift */; }; DB49A61425FF2C5600B98345 /* EmojiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB49A61325FF2C5600B98345 /* EmojiService.swift */; }; DB49A61F25FF32AA00B98345 /* EmojiService+CustomEmojiViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB49A61E25FF32AA00B98345 /* EmojiService+CustomEmojiViewModel.swift */; }; @@ -311,7 +311,7 @@ DB938F0F2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F0E2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift */; }; DB938F1526241FDF00E5B6C1 /* APIService+Thread.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F1426241FDF00E5B6C1 /* APIService+Thread.swift */; }; DB938F1F2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F1E2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift */; }; - DB938F25262438D600E5B6C1 /* ThreadViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F24262438D600E5B6C1 /* ThreadViewController+StatusProvider.swift */; }; + DB938F25262438D600E5B6C1 /* ThreadViewController+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F24262438D600E5B6C1 /* ThreadViewController+Provider.swift */; }; DB938F3326243D6200E5B6C1 /* TimelineTopLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F3226243D6200E5B6C1 /* TimelineTopLoaderTableViewCell.swift */; }; DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98336A25C9420100AD9700 /* APIService+App.swift */; }; DB98337125C9443200AD9700 /* APIService+Authentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98337025C9443200AD9700 /* APIService+Authentication.swift */; }; @@ -378,7 +378,7 @@ DBE3CDFB261C6CA500430CC6 /* FavoriteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CDFA261C6CA500430CC6 /* FavoriteViewModel.swift */; }; DBE3CE01261D623D00430CC6 /* FavoriteViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE00261D623D00430CC6 /* FavoriteViewModel+State.swift */; }; DBE3CE07261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE06261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift */; }; - DBE3CE0D261D767100430CC6 /* FavoriteViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE0C261D767100430CC6 /* FavoriteViewController+StatusProvider.swift */; }; + DBE3CE0D261D767100430CC6 /* FavoriteViewController+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE0C261D767100430CC6 /* FavoriteViewController+Provider.swift */; }; DBE3CE13261D7D4200430CC6 /* StatusTableViewControllerAspect.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE12261D7D4200430CC6 /* StatusTableViewControllerAspect.swift */; }; DBE64A8B260C49D200E6359A /* TwitterTextEditor in Frameworks */ = {isa = PBXBuildFile; productRef = DBE64A8A260C49D200E6359A /* TwitterTextEditor */; }; DBE64A8C260C49D200E6359A /* TwitterTextEditor in Embed Frameworks */ = {isa = PBXBuildFile; productRef = DBE64A8A260C49D200E6359A /* TwitterTextEditor */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; @@ -444,7 +444,7 @@ 0F20220626134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HashtagTimelineViewModel+Diffable.swift"; sourceTree = ""; }; 0F20220C26134E3F000C64BF /* HashtagTimelineViewModel+LoadLatestState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HashtagTimelineViewModel+LoadLatestState.swift"; sourceTree = ""; }; 0F202212261351F5000C64BF /* APIService+HashtagTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+HashtagTimeline.swift"; sourceTree = ""; }; - 0F202226261411BA000C64BF /* HashtagTimelineViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HashtagTimelineViewController+StatusProvider.swift"; sourceTree = ""; }; + 0F202226261411BA000C64BF /* HashtagTimelineViewController+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HashtagTimelineViewController+Provider.swift"; sourceTree = ""; }; 0F20222C261457EE000C64BF /* HashtagTimelineViewModel+LoadOldestState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HashtagTimelineViewModel+LoadOldestState.swift"; sourceTree = ""; }; 0F20223226145E51000C64BF /* HashtagTimelineViewModel+LoadMiddleState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HashtagTimelineViewModel+LoadMiddleState.swift"; sourceTree = ""; }; 0F20223826146553000C64BF /* Array+removeDuplicates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+removeDuplicates.swift"; sourceTree = ""; }; @@ -489,7 +489,7 @@ 2D364F7725E66D8300204FDC /* MastodonResendEmailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonResendEmailViewModel.swift; sourceTree = ""; }; 2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentOffsetAdjustableTimelineViewControllerDelegate.swift; sourceTree = ""; }; 2D38F1D425CD465300561493 /* HomeTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineViewController.swift; sourceTree = ""; }; - 2D38F1DE25CD46A400561493 /* HomeTimelineViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeTimelineViewController+StatusProvider.swift"; sourceTree = ""; }; + 2D38F1DE25CD46A400561493 /* HomeTimelineViewController+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeTimelineViewController+Provider.swift"; sourceTree = ""; }; 2D38F1E425CD46C100561493 /* HomeTimelineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineViewModel.swift; sourceTree = ""; }; 2D38F1EA25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeTimelineViewModel+LoadLatestState.swift"; sourceTree = ""; }; 2D38F1F025CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeTimelineViewModel+LoadMiddleState.swift"; sourceTree = ""; }; @@ -525,7 +525,7 @@ 2D6DE3FF26141DF600A63F6A /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = ""; }; 2D76316425C14BD100929FB9 /* PublicTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicTimelineViewController.swift; sourceTree = ""; }; 2D76316A25C14D4C00929FB9 /* PublicTimelineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicTimelineViewModel.swift; sourceTree = ""; }; - 2D76317C25C14DF400929FB9 /* PublicTimelineViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewController+StatusProvider.swift"; sourceTree = ""; }; + 2D76317C25C14DF400929FB9 /* PublicTimelineViewController+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewController+Provider.swift"; sourceTree = ""; }; 2D76318225C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewModel+Diffable.swift"; sourceTree = ""; }; 2D76319E25C1521200929FB9 /* StatusSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusSection.swift; sourceTree = ""; }; 2D7631A725C1535600929FB9 /* StatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = ""; }; @@ -671,7 +671,7 @@ DB45FB1C25CA9D23005A8AC7 /* APIService+HomeTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+HomeTimeline.swift"; sourceTree = ""; }; DB47229625F9EFAD00DA7F53 /* NSManagedObjectContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSManagedObjectContext.swift; sourceTree = ""; }; DB482A3E261331E8008AE74C /* UserTimelineViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserTimelineViewModel+State.swift"; sourceTree = ""; }; - DB482A44261335BA008AE74C /* UserTimelineViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserTimelineViewController+StatusProvider.swift"; sourceTree = ""; }; + DB482A44261335BA008AE74C /* UserTimelineViewController+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserTimelineViewController+Provider.swift"; sourceTree = ""; }; DB482A4A261340A7008AE74C /* APIService+UserTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+UserTimeline.swift"; sourceTree = ""; }; DB49A61325FF2C5600B98345 /* EmojiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiService.swift; sourceTree = ""; }; DB49A61E25FF32AA00B98345 /* EmojiService+CustomEmojiViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EmojiService+CustomEmojiViewModel.swift"; sourceTree = ""; }; @@ -750,7 +750,7 @@ DB938F0E2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadViewModel+LoadThreadState.swift"; sourceTree = ""; }; DB938F1426241FDF00E5B6C1 /* APIService+Thread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Thread.swift"; sourceTree = ""; }; DB938F1E2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadViewModel+Diffable.swift"; sourceTree = ""; }; - DB938F24262438D600E5B6C1 /* ThreadViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadViewController+StatusProvider.swift"; sourceTree = ""; }; + DB938F24262438D600E5B6C1 /* ThreadViewController+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadViewController+Provider.swift"; sourceTree = ""; }; DB938F3226243D6200E5B6C1 /* TimelineTopLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTopLoaderTableViewCell.swift; sourceTree = ""; }; DB98336A25C9420100AD9700 /* APIService+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+App.swift"; sourceTree = ""; }; DB98337025C9443200AD9700 /* APIService+Authentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Authentication.swift"; sourceTree = ""; }; @@ -816,7 +816,7 @@ DBE3CDFA261C6CA500430CC6 /* FavoriteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteViewModel.swift; sourceTree = ""; }; DBE3CE00261D623D00430CC6 /* FavoriteViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FavoriteViewModel+State.swift"; sourceTree = ""; }; DBE3CE06261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FavoriteViewModel+Diffable.swift"; sourceTree = ""; }; - DBE3CE0C261D767100430CC6 /* FavoriteViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FavoriteViewController+StatusProvider.swift"; sourceTree = ""; }; + DBE3CE0C261D767100430CC6 /* FavoriteViewController+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FavoriteViewController+Provider.swift"; sourceTree = ""; }; DBE3CE12261D7D4200430CC6 /* StatusTableViewControllerAspect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewControllerAspect.swift; sourceTree = ""; }; DBF53F5F25C14E88008AAC7B /* Mastodon.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = Mastodon.xctestplan; path = Mastodon/Mastodon.xctestplan; sourceTree = ""; }; DBF53F6025C14E9D008AAC7B /* MastodonSDK.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = MastodonSDK.xctestplan; sourceTree = ""; }; @@ -883,7 +883,7 @@ isa = PBXGroup; children = ( 0F2021FA2613262F000C64BF /* HashtagTimelineViewController.swift */, - 0F202226261411BA000C64BF /* HashtagTimelineViewController+StatusProvider.swift */, + 0F202226261411BA000C64BF /* HashtagTimelineViewController+Provider.swift */, 0F202200261326E6000C64BF /* HashtagTimelineViewModel.swift */, 0F20220626134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift */, 0F20220C26134E3F000C64BF /* HashtagTimelineViewModel+LoadLatestState.swift */, @@ -1005,7 +1005,7 @@ children = ( DB1F239626117C360057430E /* View */, 2D38F1D425CD465300561493 /* HomeTimelineViewController.swift */, - 2D38F1DE25CD46A400561493 /* HomeTimelineViewController+StatusProvider.swift */, + 2D38F1DE25CD46A400561493 /* HomeTimelineViewController+Provider.swift */, 2D5A3D6125CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift */, 2D38F1E425CD46C100561493 /* HomeTimelineViewModel.swift */, 2D5A3D2725CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift */, @@ -1126,7 +1126,7 @@ isa = PBXGroup; children = ( 2D76316425C14BD100929FB9 /* PublicTimelineViewController.swift */, - 2D76317C25C14DF400929FB9 /* PublicTimelineViewController+StatusProvider.swift */, + 2D76317C25C14DF400929FB9 /* PublicTimelineViewController+Provider.swift */, 2D76316A25C14D4C00929FB9 /* PublicTimelineViewModel.swift */, 2D32EAD925CBCC3300C9ED86 /* PublicTimelineViewModel+LoadMiddleState.swift */, 2D76318225C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift */, @@ -1803,7 +1803,7 @@ isa = PBXGroup; children = ( DB938EE52623F50700E5B6C1 /* ThreadViewController.swift */, - DB938F24262438D600E5B6C1 /* ThreadViewController+StatusProvider.swift */, + DB938F24262438D600E5B6C1 /* ThreadViewController+Provider.swift */, DB938EEC2623F79B00E5B6C1 /* ThreadViewModel.swift */, DB938F1E2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift */, DB938F0E2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift */, @@ -1968,7 +1968,7 @@ isa = PBXGroup; children = ( DBB525352611ECEB002F1F29 /* UserTimelineViewController.swift */, - DB482A44261335BA008AE74C /* UserTimelineViewController+StatusProvider.swift */, + DB482A44261335BA008AE74C /* UserTimelineViewController+Provider.swift */, DBB525552611EDCA002F1F29 /* UserTimelineViewModel.swift */, DBCBED1626132DB500B49291 /* UserTimelineViewModel+Diffable.swift */, DB482A3E261331E8008AE74C /* UserTimelineViewModel+State.swift */, @@ -2020,7 +2020,7 @@ isa = PBXGroup; children = ( DBE3CDEB261C6B2900430CC6 /* FavoriteViewController.swift */, - DBE3CE0C261D767100430CC6 /* FavoriteViewController+StatusProvider.swift */, + DBE3CE0C261D767100430CC6 /* FavoriteViewController+Provider.swift */, DBE3CDFA261C6CA500430CC6 /* FavoriteViewModel.swift */, DBE3CE06261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift */, DBE3CE00261D623D00430CC6 /* FavoriteViewModel+State.swift */, @@ -2500,7 +2500,7 @@ 0F202201261326E6000C64BF /* HashtagTimelineViewModel.swift in Sources */, DBD9149025DF6D8D00903DFD /* APIService+Onboarding.swift in Sources */, DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */, - DBE3CE0D261D767100430CC6 /* FavoriteViewController+StatusProvider.swift in Sources */, + DBE3CE0D261D767100430CC6 /* FavoriteViewController+Provider.swift in Sources */, 2D084B9326259545003AA3AF /* NotificationViewModel+LoadLatestState.swift in Sources */, DB98337F25C9452D00AD9700 /* APIService+APIError.swift in Sources */, DB9E0D6F25EE008500CFDD76 /* UIInterpolatingMotionEffect.swift in Sources */, @@ -2533,7 +2533,7 @@ 2DA6055125F74407006356F9 /* AudioContainerViewModel.swift in Sources */, 0FB3D2FE25E4CB6400AAD544 /* PickServerTitleCell.swift in Sources */, 5DA732CC2629CEF500A92342 /* UIView+Remove.swift in Sources */, - 2D38F1DF25CD46A400561493 /* HomeTimelineViewController+StatusProvider.swift in Sources */, + 2D38F1DF25CD46A400561493 /* HomeTimelineViewController+Provider.swift in Sources */, 2D206B9225F60EA700143C56 /* UIControl.swift in Sources */, 2D46975E25C2A54100CF4AA9 /* NSLayoutConstraint.swift in Sources */, 2D45E5BF25C9549700A6D639 /* PublicTimelineViewModel+State.swift in Sources */, @@ -2606,7 +2606,7 @@ DB44384F25E8C1FA008912A2 /* CALayer.swift in Sources */, 2D34D9CB261489930081BFC0 /* SearchViewController+Recommend.swift in Sources */, DB938F1526241FDF00E5B6C1 /* APIService+Thread.swift in Sources */, - DB482A45261335BA008AE74C /* UserTimelineViewController+StatusProvider.swift in Sources */, + DB482A45261335BA008AE74C /* UserTimelineViewController+Provider.swift in Sources */, 2D206B8625F5FB0900143C56 /* Double.swift in Sources */, DB9A485C2603010E008B817C /* PHPickerResultLoader.swift in Sources */, 2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */, @@ -2647,7 +2647,7 @@ DB45FAF925CA80A2005A8AC7 /* APIService+CoreData+MastodonAuthentication.swift in Sources */, 2D04F42525C255B9003F936F /* APIService+PublicTimeline.swift in Sources */, 2D34D9E226149C920081BFC0 /* SearchRecommendTagsCollectionViewCell.swift in Sources */, - 2D76317D25C14DF500929FB9 /* PublicTimelineViewController+StatusProvider.swift in Sources */, + 2D76317D25C14DF500929FB9 /* PublicTimelineViewController+Provider.swift in Sources */, 0F20223326145E51000C64BF /* HashtagTimelineViewModel+LoadMiddleState.swift in Sources */, 2D206B8025F5F45E00143C56 /* UIImage.swift in Sources */, 0F20220726134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift in Sources */, @@ -2661,7 +2661,7 @@ 2DA7D04A25CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift in Sources */, 2D76318325C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift in Sources */, 2D198655261C3C4300F0B013 /* SearchViewModel+LoadOldestState.swift in Sources */, - 0F202227261411BB000C64BF /* HashtagTimelineViewController+StatusProvider.swift in Sources */, + 0F202227261411BB000C64BF /* HashtagTimelineViewController+Provider.swift in Sources */, 2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */, 2D76316525C14BD100929FB9 /* PublicTimelineViewController.swift in Sources */, DB938F1F2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift in Sources */, @@ -2692,7 +2692,7 @@ 2D38F1FE25CD481700561493 /* StatusProvider.swift in Sources */, 5B24BBE2262DB19100A9381B /* APIService+Report.swift in Sources */, 5BB04FF5262F0E6D0043BFF6 /* ReportSection.swift in Sources */, - DB938F25262438D600E5B6C1 /* ThreadViewController+StatusProvider.swift in Sources */, + DB938F25262438D600E5B6C1 /* ThreadViewController+Provider.swift in Sources */, DB66729C25F9F91F00D60309 /* ComposeStatusItem.swift in Sources */, 0FB3D31E25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift in Sources */, 5DF1057925F88A1D00D6C0D4 /* PlayerContainerView.swift in Sources */, diff --git a/Mastodon/Protocol/UserProvider/UserProvider.swift b/Mastodon/Protocol/UserProvider/UserProvider.swift index 63a1f8e68..7426d89ac 100644 --- a/Mastodon/Protocol/UserProvider/UserProvider.swift +++ b/Mastodon/Protocol/UserProvider/UserProvider.swift @@ -13,4 +13,25 @@ import CoreDataStack protocol UserProvider: NeedsDependency & DisposeBagCollectable & UIViewController { // async func mastodonUser() -> Future + + func mastodonUser(for cell: UITableViewCell?, indexPath: IndexPath?) -> Future +} + +extension UserProvider where Self: StatusProvider { + func mastodonUser(for cell: UITableViewCell?, indexPath: IndexPath?) -> Future { + return Future { [weak self] promise in + guard let self = self else { return } + self.status(for: cell, indexPath: indexPath) + .sink { status in + promise(.success(status?.author)) + } + .store(in: &self.disposeBag) + } + } + + func mastodonUser() -> Future { + return Future { promise in + promise(.success(nil)) + } + } } diff --git a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift index 4e8227f69..729e2a932 100644 --- a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift +++ b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift @@ -202,24 +202,7 @@ extension UserProviderFacade { children.append(blockMenu) } - if needsShareAction { - let shareAction = UIAction(title: L10n.Common.Controls.Actions.shareUser(name), image: UIImage(systemName: "square.and.arrow.up"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in - guard let provider = provider else { return } - let activityViewController = createActivityViewControllerForMastodonUser(mastodonUser: mastodonUser, dependency: provider) - provider.coordinator.present( - scene: .activityViewController( - activityViewController: activityViewController, - sourceView: sourceView, - barButtonItem: barButtonItem - ), - from: provider, - transition: .activityViewControllerPresent(animated: true, completion: nil) - ) - } - children.append(shareAction) - } - - let reportAction = UIAction(title: L10n.Common.Controls.Actions.reportUser(name), image: UIImage(systemName: "exclamationmark.bubble"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in + let reportAction = UIAction(title: L10n.Common.Controls.Actions.reportUser(name), image: UIImage(systemName: "flag"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in guard let provider = provider else { return } guard let authenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else { return @@ -237,6 +220,23 @@ extension UserProviderFacade { } children.append(reportAction) + if needsShareAction { + let shareAction = UIAction(title: L10n.Common.Controls.Actions.shareUser(name), image: UIImage(systemName: "square.and.arrow.up"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in + guard let provider = provider else { return } + let activityViewController = createActivityViewControllerForMastodonUser(mastodonUser: mastodonUser, dependency: provider) + provider.coordinator.present( + scene: .activityViewController( + activityViewController: activityViewController, + sourceView: sourceView, + barButtonItem: barButtonItem + ), + from: provider, + transition: .activityViewControllerPresent(animated: true, completion: nil) + ) + } + children.append(shareAction) + } + return UIMenu(title: "", options: [], children: children) } diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController+StatusProvider.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController+Provider.swift similarity index 96% rename from Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController+StatusProvider.swift rename to Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController+Provider.swift index 191ad374d..3bc3a36b5 100644 --- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController+StatusProvider.swift +++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController+Provider.swift @@ -1,5 +1,5 @@ // -// HashtagTimelineViewController+StatusProvider.swift +// HashtagTimelineViewController+Provider.swift // Mastodon // // Created by BradGao on 2021/3/31. @@ -86,3 +86,4 @@ extension HashtagTimelineViewController: StatusProvider { } +extension HashtagTimelineViewController: UserProvider {} diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+StatusProvider.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+Provider.swift similarity index 96% rename from Mastodon/Scene/HomeTimeline/HomeTimelineViewController+StatusProvider.swift rename to Mastodon/Scene/HomeTimeline/HomeTimelineViewController+Provider.swift index aea931a62..d735d5843 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+StatusProvider.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+Provider.swift @@ -1,5 +1,5 @@ // -// HomeTimelineViewController+StatusProvider.swift +// HomeTimelineViewController+Provider.swift // Mastodon // // Created by sxiaojian on 2021/2/5. @@ -85,3 +85,5 @@ extension HomeTimelineViewController: StatusProvider { } } + +extension HomeTimelineViewController: UserProvider {} diff --git a/Mastodon/Scene/Profile/Favorite/FavoriteViewController+StatusProvider.swift b/Mastodon/Scene/Profile/Favorite/FavoriteViewController+Provider.swift similarity index 98% rename from Mastodon/Scene/Profile/Favorite/FavoriteViewController+StatusProvider.swift rename to Mastodon/Scene/Profile/Favorite/FavoriteViewController+Provider.swift index 68adc1e3e..88f368c15 100644 --- a/Mastodon/Scene/Profile/Favorite/FavoriteViewController+StatusProvider.swift +++ b/Mastodon/Scene/Profile/Favorite/FavoriteViewController+Provider.swift @@ -85,3 +85,5 @@ extension FavoriteViewController: StatusProvider { } } + +extension FavoriteViewController: UserProvider {} diff --git a/Mastodon/Scene/Profile/ProfileViewController+UserProvider.swift b/Mastodon/Scene/Profile/ProfileViewController+UserProvider.swift index 3a26db1c1..be124a0c2 100644 --- a/Mastodon/Scene/Profile/ProfileViewController+UserProvider.swift +++ b/Mastodon/Scene/Profile/ProfileViewController+UserProvider.swift @@ -8,8 +8,15 @@ import Foundation import Combine import CoreDataStack +import UIKit extension ProfileViewController: UserProvider { + func mastodonUser(for cell: UITableViewCell?, indexPath: IndexPath?) -> Future { + return Future { promise in + promise(.success(nil)) + } + } + func mastodonUser() -> Future { return Future { promise in diff --git a/Mastodon/Scene/Profile/Timeline/UserTimelineViewController+StatusProvider.swift b/Mastodon/Scene/Profile/Timeline/UserTimelineViewController+Provider.swift similarity index 96% rename from Mastodon/Scene/Profile/Timeline/UserTimelineViewController+StatusProvider.swift rename to Mastodon/Scene/Profile/Timeline/UserTimelineViewController+Provider.swift index 4fc857812..30029ae5b 100644 --- a/Mastodon/Scene/Profile/Timeline/UserTimelineViewController+StatusProvider.swift +++ b/Mastodon/Scene/Profile/Timeline/UserTimelineViewController+Provider.swift @@ -1,5 +1,5 @@ // -// UserTimelineViewController+StatusProvider.swift +// UserTimelineViewController+Provider.swift // Mastodon // // Created by MainasuK Cirno on 2021-3-30. @@ -85,3 +85,5 @@ extension UserTimelineViewController: StatusProvider { } } + +extension UserTimelineViewController: UserProvider {} diff --git a/Mastodon/Scene/PublicTimeline/PublicTimelineViewController+StatusProvider.swift b/Mastodon/Scene/PublicTimeline/PublicTimelineViewController+Provider.swift similarity index 96% rename from Mastodon/Scene/PublicTimeline/PublicTimelineViewController+StatusProvider.swift rename to Mastodon/Scene/PublicTimeline/PublicTimelineViewController+Provider.swift index 04fc526a0..96963914c 100644 --- a/Mastodon/Scene/PublicTimeline/PublicTimelineViewController+StatusProvider.swift +++ b/Mastodon/Scene/PublicTimeline/PublicTimelineViewController+Provider.swift @@ -1,5 +1,5 @@ // -// PublicTimelineViewController+StatusProvider.swift +// PublicTimelineViewController+Provider.swift // Mastodon // // Created by sxiaojian on 2021/1/27. @@ -85,3 +85,5 @@ extension PublicTimelineViewController: StatusProvider { } } + +extension PublicTimelineViewController: UserProvider {} diff --git a/Mastodon/Scene/Search/SearchViewController+Follow.swift b/Mastodon/Scene/Search/SearchViewController+Follow.swift index 8b0acda0a..8986dd680 100644 --- a/Mastodon/Scene/Search/SearchViewController+Follow.swift +++ b/Mastodon/Scene/Search/SearchViewController+Follow.swift @@ -11,6 +11,13 @@ import Foundation import UIKit extension SearchViewController: UserProvider { + + func mastodonUser(for cell: UITableViewCell?, indexPath: IndexPath?) -> Future { + return Future { promise in + promise(.success(nil)) + } + } + func mastodonUser() -> Future { Future { promise in promise(.success(self.viewModel.mastodonUser.value)) diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift index d546fea62..3ab03f3ee 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift @@ -344,7 +344,7 @@ extension StatusTableViewCell: ActionToolbarContainerDelegate { } func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, moreButtonDidPressed sender: UIButton) { - + } } diff --git a/Mastodon/Scene/Thread/ThreadViewController+StatusProvider.swift b/Mastodon/Scene/Thread/ThreadViewController+Provider.swift similarity index 96% rename from Mastodon/Scene/Thread/ThreadViewController+StatusProvider.swift rename to Mastodon/Scene/Thread/ThreadViewController+Provider.swift index 05cc6e4b2..a76a22d0b 100644 --- a/Mastodon/Scene/Thread/ThreadViewController+StatusProvider.swift +++ b/Mastodon/Scene/Thread/ThreadViewController+Provider.swift @@ -1,5 +1,5 @@ // -// ThreadViewController+StatusProvider.swift +// ThreadViewController+Provider.swift // Mastodon // // Created by MainasuK Cirno on 2021-4-12. @@ -86,3 +86,5 @@ extension ThreadViewController: StatusProvider { } } + +extension ThreadViewController: UserProvider {} From 273305cda9533b09828c986b9d8c30138034c74f Mon Sep 17 00:00:00 2001 From: sunxiaojian Date: Wed, 28 Apr 2021 19:56:30 +0800 Subject: [PATCH 02/10] chore: show menu in statusCell --- .../Section/NotificationSection.swift | 1 + .../Diffiable/Section/ReportSection.swift | 1 + .../Diffiable/Section/StatusSection.swift | 73 +++++++------- .../UserProvider/UserProviderFacade.swift | 99 ++++++++++++------- .../Scene/Profile/ProfileViewController.swift | 17 +++- .../Search/SearchViewController+Follow.swift | 4 +- 6 files changed, 119 insertions(+), 76 deletions(-) diff --git a/Mastodon/Diffiable/Section/NotificationSection.swift b/Mastodon/Diffiable/Section/NotificationSection.swift index 9c59350b4..20db29c93 100644 --- a/Mastodon/Diffiable/Section/NotificationSection.swift +++ b/Mastodon/Diffiable/Section/NotificationSection.swift @@ -50,6 +50,7 @@ extension NotificationSection { let frame = CGRect(x: 0, y: 0, width: tableView.readableContentGuide.layoutFrame.width - NotificationStatusTableViewCell.statusPadding.left - NotificationStatusTableViewCell.statusPadding.right, height: tableView.readableContentGuide.layoutFrame.height) StatusSection.configure( cell: cell, + indexPath: indexPath, dependency: dependency, readableLayoutFrame: frame, timestampUpdatePublisher: timestampUpdatePublisher, diff --git a/Mastodon/Diffiable/Section/ReportSection.swift b/Mastodon/Diffiable/Section/ReportSection.swift index 6faaae6c2..07321be96 100644 --- a/Mastodon/Diffiable/Section/ReportSection.swift +++ b/Mastodon/Diffiable/Section/ReportSection.swift @@ -41,6 +41,7 @@ extension ReportSection { let status = managedObjectContext.object(with: objectID) as! Status StatusSection.configure( cell: cell, + indexPath: indexPath, dependency: dependency, readableLayoutFrame: tableView.readableContentGuide.layoutFrame, timestampUpdatePublisher: timestampUpdatePublisher, diff --git a/Mastodon/Diffiable/Section/StatusSection.swift b/Mastodon/Diffiable/Section/StatusSection.swift index b897de47f..36eecdbe5 100644 --- a/Mastodon/Diffiable/Section/StatusSection.swift +++ b/Mastodon/Diffiable/Section/StatusSection.swift @@ -49,6 +49,7 @@ extension StatusSection { let timelineIndex = managedObjectContext.object(with: objectID) as! HomeTimelineIndex StatusSection.configure( cell: cell, + indexPath: indexPath, dependency: dependency, readableLayoutFrame: tableView.readableContentGuide.layoutFrame, timestampUpdatePublisher: timestampUpdatePublisher, @@ -71,6 +72,7 @@ extension StatusSection { let status = managedObjectContext.object(with: objectID) as! Status StatusSection.configure( cell: cell, + indexPath: indexPath, dependency: dependency, readableLayoutFrame: tableView.readableContentGuide.layoutFrame, timestampUpdatePublisher: timestampUpdatePublisher, @@ -136,6 +138,7 @@ extension StatusSection { static func configure( cell: StatusCell, + indexPath: IndexPath, dependency: NeedsDependency, readableLayoutFrame: CGRect?, timestampUpdatePublisher: AnyPublisher, @@ -223,7 +226,6 @@ extension StatusSection { meta.blurhashImagePublisher() .receive(on: DispatchQueue.main) .sink { [weak cell] image in - guard let cell = cell else { return } blurhashOverlayImageView.image = image image?.pngData().flatMap { blurhashImageCache.setObject($0 as NSData, forKey: blurhashImageDataKey) @@ -401,16 +403,16 @@ extension StatusSection { .store(in: &cell.disposeBag) } - // toolbar - StatusSection.configureActionToolBar( - cell: cell, - dependency: dependency, - status: status, - requestUserID: requestUserID - ) - - // separator line if let statusTableViewCell = cell as? StatusTableViewCell { + // toolbar + StatusSection.configureActionToolBar( + cell: statusTableViewCell, + indexPath: indexPath, + dependency: dependency, + status: status, + requestUserID: requestUserID + ) + // separator line statusTableViewCell.separatorLine.isHidden = statusItemAttribute.isSeparatorLineHidden } @@ -434,8 +436,10 @@ extension StatusSection { guard let dependency = dependency else { return } guard case .update(let object) = change.changeType, let status = object as? Status else { return } + guard let cell = cell as? StatusTableViewCell else { return } StatusSection.configureActionToolBar( cell: cell, + indexPath: indexPath, dependency: dependency, status: status, requestUserID: requestUserID @@ -593,7 +597,8 @@ extension StatusSection { } static func configureActionToolBar( - cell: StatusCell, + cell: StatusTableViewCell, + indexPath: IndexPath, dependency: NeedsDependency, status: Status, requestUserID: String @@ -623,7 +628,7 @@ extension StatusSection { cell.statusView.actionToolbarContainer.favoriteButton.setTitle(favoriteCountTitle, for: .normal) cell.statusView.actionToolbarContainer.isFavoriteButtonHighlight = isLike - self.setupStatusMoreButtonMenu(cell: cell, dependency: dependency, status: status) + self.setupStatusMoreButtonMenu(cell: cell, indexPath: indexPath, dependency: dependency, status: status) } static func configurePoll( @@ -752,37 +757,35 @@ extension StatusSection { } private static func setupStatusMoreButtonMenu( - cell: StatusCell, + cell: StatusTableViewCell, + indexPath: IndexPath, dependency: NeedsDependency, status: Status) { - cell.statusView.actionToolbarContainer.moreButton.menu = nil + guard let userProvider = dependency as? UserProvider else { fatalError() } guard let authenticationBox = dependency.context.authenticationService.activeMastodonAuthenticationBox.value else { return } let author = (status.reblog ?? status).author - guard authenticationBox.userID != author.id else { - return - } - var children: [UIMenuElement] = [] - let name = author.displayNameWithFallback - let reportAction = UIAction(title: L10n.Common.Controls.Actions.reportUser(name), image: UIImage(systemName: "exclamationmark.bubble"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { - [weak dependency] _ in - guard let dependency = dependency else { return } - let viewModel = ReportViewModel( - context: dependency.context, - domain: authenticationBox.domain, - user: status.author, - status: status) - dependency.coordinator.present( - scene: .report(viewModel: viewModel), - from: nil, - transition: .modal(animated: true, completion: nil) - ) - } - children.append(reportAction) - cell.statusView.actionToolbarContainer.moreButton.menu = UIMenu(title: "", options: [], children: children) + let canReport = authenticationBox.userID != author.id + + let isMuting = (author.mutingBy ?? Set()).map(\.id).contains(authenticationBox.userID) + let isBlocking = (author.blockingBy ?? Set()).map(\.id).contains(authenticationBox.userID) + cell.statusView.actionToolbarContainer.moreButton.showsMenuAsPrimaryAction = true + cell.statusView.actionToolbarContainer.moreButton.menu = UserProviderFacade.createProfileActionMenu( + for: author, + isMuting: isMuting, + isBlocking: isBlocking, + canReport: canReport, + provider: userProvider, + cell: cell, + indexPath: indexPath, + sourceView: cell.statusView.actionToolbarContainer.moreButton, + barButtonItem: nil, + shareUser: nil, + shareStatus: status + ) } } diff --git a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift index 729e2a932..0d839bd17 100644 --- a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift +++ b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift @@ -57,19 +57,28 @@ extension UserProviderFacade { extension UserProviderFacade { static func toggleUserBlockRelationship( - provider: UserProvider + provider: UserProvider, + cell: UITableViewCell?, + indexPath: IndexPath? ) -> AnyPublisher, Error> { // prepare authentication guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else { assertionFailure() return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher() } - - return _toggleUserBlockRelationship( - context: provider.context, - activeMastodonAuthenticationBox: activeMastodonAuthenticationBox, - mastodonUser: provider.mastodonUser().eraseToAnyPublisher() - ) + if let cell = cell, let indexPath = indexPath { + return _toggleUserBlockRelationship( + context: provider.context, + activeMastodonAuthenticationBox: activeMastodonAuthenticationBox, + mastodonUser: provider.mastodonUser(for: cell, indexPath: indexPath).eraseToAnyPublisher() + ) + } else { + return _toggleUserBlockRelationship( + context: provider.context, + activeMastodonAuthenticationBox: activeMastodonAuthenticationBox, + mastodonUser: provider.mastodonUser().eraseToAnyPublisher() + ) + } } private static func _toggleUserBlockRelationship( @@ -97,19 +106,28 @@ extension UserProviderFacade { extension UserProviderFacade { static func toggleUserMuteRelationship( - provider: UserProvider + provider: UserProvider, + cell: UITableViewCell?, + indexPath: IndexPath? ) -> AnyPublisher, Error> { // prepare authentication guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else { assertionFailure() return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher() } - - return _toggleUserMuteRelationship( - context: provider.context, - activeMastodonAuthenticationBox: activeMastodonAuthenticationBox, - mastodonUser: provider.mastodonUser().eraseToAnyPublisher() - ) + if let cell = cell, let indexPath = indexPath { + return _toggleUserMuteRelationship( + context: provider.context, + activeMastodonAuthenticationBox: activeMastodonAuthenticationBox, + mastodonUser: provider.mastodonUser(for: cell, indexPath: indexPath).eraseToAnyPublisher() + ) + } else { + return _toggleUserMuteRelationship( + context: provider.context, + activeMastodonAuthenticationBox: activeMastodonAuthenticationBox, + mastodonUser: provider.mastodonUser().eraseToAnyPublisher() + ) + } } private static func _toggleUserMuteRelationship( @@ -140,10 +158,14 @@ extension UserProviderFacade { for mastodonUser: MastodonUser, isMuting: Bool, isBlocking: Bool, - needsShareAction: Bool, + canReport: Bool, provider: UserProvider, + cell: UITableViewCell?, + indexPath: IndexPath?, sourceView: UIView?, - barButtonItem: UIBarButtonItem? + barButtonItem: UIBarButtonItem?, + shareUser: MastodonUser?, + shareStatus: Status? ) -> UIMenu { var children: [UIMenuElement] = [] let name = mastodonUser.displayNameWithFallback @@ -159,7 +181,9 @@ extension UserProviderFacade { guard let provider = provider else { return } UserProviderFacade.toggleUserMuteRelationship( - provider: provider + provider: provider, + cell: cell, + indexPath: indexPath ) .sink { _ in // do nothing @@ -186,7 +210,9 @@ extension UserProviderFacade { guard let provider = provider else { return } UserProviderFacade.toggleUserBlockRelationship( - provider: provider + provider: provider, + cell: cell, + indexPath: indexPath ) .sink { _ in // do nothing @@ -201,29 +227,30 @@ extension UserProviderFacade { let blockMenu = UIMenu(title: L10n.Common.Controls.Firendship.blockUser(name), image: UIImage(systemName: "hand.raised"), options: [], children: [blockAction]) children.append(blockMenu) } - - let reportAction = UIAction(title: L10n.Common.Controls.Actions.reportUser(name), image: UIImage(systemName: "flag"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in - guard let provider = provider else { return } - guard let authenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else { - return + if canReport { + let reportAction = UIAction(title: L10n.Common.Controls.Actions.reportUser(name), image: UIImage(systemName: "flag"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in + guard let provider = provider else { return } + guard let authenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else { + return + } + let viewModel = ReportViewModel( + context: provider.context, + domain: authenticationBox.domain, + user: mastodonUser, + status: nil) + provider.coordinator.present( + scene: .report(viewModel: viewModel), + from: provider, + transition: .modal(animated: true, completion: nil) + ) } - let viewModel = ReportViewModel( - context: provider.context, - domain: authenticationBox.domain, - user: mastodonUser, - status: nil) - provider.coordinator.present( - scene: .report(viewModel: viewModel), - from: provider, - transition: .modal(animated: true, completion: nil) - ) + children.append(reportAction) } - children.append(reportAction) - if needsShareAction { + if let shareUser = shareUser { let shareAction = UIAction(title: L10n.Common.Controls.Actions.shareUser(name), image: UIImage(systemName: "square.and.arrow.up"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in guard let provider = provider else { return } - let activityViewController = createActivityViewControllerForMastodonUser(mastodonUser: mastodonUser, dependency: provider) + let activityViewController = createActivityViewControllerForMastodonUser(mastodonUser: shareUser, dependency: provider) provider.coordinator.present( scene: .activityViewController( activityViewController: activityViewController, diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index 8fc915a0a..7a8742f18 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -377,7 +377,18 @@ extension ProfileViewController { let isMuting = relationshipActionOptionSet.contains(.muting) let isBlocking = relationshipActionOptionSet.contains(.blocking) let needsShareAction = self.viewModel.isMeBarButtonItemsHidden.value - self.moreMenuBarButtonItem.menu = UserProviderFacade.createProfileActionMenu(for: mastodonUser, isMuting: isMuting, isBlocking: isBlocking, needsShareAction: needsShareAction, provider: self, sourceView: nil, barButtonItem: self.moreMenuBarButtonItem) + self.moreMenuBarButtonItem.menu = UserProviderFacade.createProfileActionMenu( + for: mastodonUser, + isMuting: isMuting, + isBlocking: isBlocking, + canReport: true, + provider: self, + cell: nil, + indexPath: nil, + sourceView: nil, + barButtonItem: self.moreMenuBarButtonItem, + shareUser: needsShareAction ? mastodonUser : nil, + shareStatus: nil) } .store(in: &disposeBag) viewModel.isRelationshipActionButtonHidden @@ -692,7 +703,7 @@ extension ProfileViewController: ProfileHeaderViewDelegate { ) let unmuteAction = UIAlertAction(title: L10n.Common.Controls.Firendship.unmute, style: .default) { [weak self] _ in guard let self = self else { return } - UserProviderFacade.toggleUserMuteRelationship(provider: self) + UserProviderFacade.toggleUserMuteRelationship(provider: self, cell: nil, indexPath: nil) .sink { _ in // do nothing } receiveValue: { _ in @@ -714,7 +725,7 @@ extension ProfileViewController: ProfileHeaderViewDelegate { ) let unblockAction = UIAlertAction(title: L10n.Common.Controls.Firendship.unblock, style: .default) { [weak self] _ in guard let self = self else { return } - UserProviderFacade.toggleUserBlockRelationship(provider: self) + UserProviderFacade.toggleUserBlockRelationship(provider: self, cell: nil, indexPath: nil) .sink { _ in // do nothing } receiveValue: { _ in diff --git a/Mastodon/Scene/Search/SearchViewController+Follow.swift b/Mastodon/Scene/Search/SearchViewController+Follow.swift index 8986dd680..6682d846b 100644 --- a/Mastodon/Scene/Search/SearchViewController+Follow.swift +++ b/Mastodon/Scene/Search/SearchViewController+Follow.swift @@ -54,7 +54,7 @@ extension SearchViewController: SearchRecommendAccountsCollectionViewCellDelegat ) let unmuteAction = UIAlertAction(title: L10n.Common.Controls.Firendship.unmute, style: .default) { [weak self] _ in guard let self = self else { return } - UserProviderFacade.toggleUserMuteRelationship(provider: self) + UserProviderFacade.toggleUserMuteRelationship(provider: self, cell: nil, indexPath: nil) .sink { _ in // do nothing } receiveValue: { _ in @@ -76,7 +76,7 @@ extension SearchViewController: SearchRecommendAccountsCollectionViewCellDelegat ) let unblockAction = UIAlertAction(title: L10n.Common.Controls.Firendship.unblock, style: .default) { [weak self] _ in guard let self = self else { return } - UserProviderFacade.toggleUserBlockRelationship(provider: self) + UserProviderFacade.toggleUserBlockRelationship(provider: self, cell: nil, indexPath: nil) .sink { _ in // do nothing } receiveValue: { _ in From 236b5ca0dc83c18afd0f849e5acaafdadfa6d5af Mon Sep 17 00:00:00 2001 From: sunxiaojian Date: Thu, 29 Apr 2021 10:50:10 +0800 Subject: [PATCH 03/10] fix: add observer for more menu --- Mastodon/Diffiable/Section/StatusSection.swift | 14 +++++++++++++- Mastodon/Extension/CoreDataStack/Status.swift | 7 +++++++ Mastodon/Protocol/UserProvider/UserProvider.swift | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Mastodon/Diffiable/Section/StatusSection.swift b/Mastodon/Diffiable/Section/StatusSection.swift index 36eecdbe5..0e26e1479 100644 --- a/Mastodon/Diffiable/Section/StatusSection.swift +++ b/Mastodon/Diffiable/Section/StatusSection.swift @@ -628,6 +628,18 @@ extension StatusSection { cell.statusView.actionToolbarContainer.favoriteButton.setTitle(favoriteCountTitle, for: .normal) cell.statusView.actionToolbarContainer.isFavoriteButtonHighlight = isLike + ManagedObjectObserver.observe(object: status.authorForUserProvider) + .receive(on: DispatchQueue.main) + .sink { _ in + // do nothing + } receiveValue: { [weak dependency, weak cell] change in + guard let cell = cell else { return } + guard let dependency = dependency else { return } + if case .update( _) = change.changeType { + StatusSection.setupStatusMoreButtonMenu(cell: cell, indexPath: indexPath, dependency: dependency, status: status) + } + } + .store(in: &cell.disposeBag) self.setupStatusMoreButtonMenu(cell: cell, indexPath: indexPath, dependency: dependency, status: status) } @@ -767,7 +779,7 @@ extension StatusSection { guard let authenticationBox = dependency.context.authenticationService.activeMastodonAuthenticationBox.value else { return } - let author = (status.reblog ?? status).author + let author = status.authorForUserProvider let canReport = authenticationBox.userID != author.id let isMuting = (author.mutingBy ?? Set()).map(\.id).contains(authenticationBox.userID) diff --git a/Mastodon/Extension/CoreDataStack/Status.swift b/Mastodon/Extension/CoreDataStack/Status.swift index 880be6fa3..39b421dbc 100644 --- a/Mastodon/Extension/CoreDataStack/Status.swift +++ b/Mastodon/Extension/CoreDataStack/Status.swift @@ -63,3 +63,10 @@ extension Status { } } + +extension Status { + var authorForUserProvider: MastodonUser { + let author = (reblog ?? self).author + return author + } +} diff --git a/Mastodon/Protocol/UserProvider/UserProvider.swift b/Mastodon/Protocol/UserProvider/UserProvider.swift index 7426d89ac..f3ee36c32 100644 --- a/Mastodon/Protocol/UserProvider/UserProvider.swift +++ b/Mastodon/Protocol/UserProvider/UserProvider.swift @@ -23,7 +23,7 @@ extension UserProvider where Self: StatusProvider { guard let self = self else { return } self.status(for: cell, indexPath: indexPath) .sink { status in - promise(.success(status?.author)) + promise(.success(status?.authorForUserProvider)) } .store(in: &self.disposeBag) } From 211e2f25d5f7b95bd2e5c8a8a67ec0d2d80cbfef Mon Sep 17 00:00:00 2001 From: sunxiaojian Date: Thu, 29 Apr 2021 11:03:21 +0800 Subject: [PATCH 04/10] chore: add share post --- Localization/app.json | 1 + Mastodon/Extension/CoreDataStack/Status.swift | 13 ++++++++++ Mastodon/Generated/Strings.swift | 2 ++ .../UserProvider/UserProviderFacade.swift | 25 +++++++++++++++++++ .../Resources/en.lproj/Localizable.strings | 1 + 5 files changed, 42 insertions(+) diff --git a/Localization/app.json b/Localization/app.json index 96f366933..35cdda165 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -51,6 +51,7 @@ "preview": "Preview", "share": "Share", "share_user": "Share %s", + "share_post": "Share post", "open_in_safari": "Open in Safari", "find_people": "Find people to follow", "manually_search": "Manually search instead", diff --git a/Mastodon/Extension/CoreDataStack/Status.swift b/Mastodon/Extension/CoreDataStack/Status.swift index 39b421dbc..015671cb5 100644 --- a/Mastodon/Extension/CoreDataStack/Status.swift +++ b/Mastodon/Extension/CoreDataStack/Status.swift @@ -70,3 +70,16 @@ extension Status { return author } } + +extension Status { + + var statusURL: URL { + return URL(string: "https://\(self.domain)/web/statuses/\(self.id)")! + } + + var activityItems: [Any] { + var items: [Any] = [] + items.append(statusURL) + return items + } +} diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index 8dd327048..c0d1cac6f 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -96,6 +96,8 @@ internal enum L10n { internal static let seeMore = L10n.tr("Localizable", "Common.Controls.Actions.SeeMore") /// Share internal static let share = L10n.tr("Localizable", "Common.Controls.Actions.Share") + /// Share post + internal static let sharePost = L10n.tr("Localizable", "Common.Controls.Actions.SharePost") /// Share %@ internal static func shareUser(_ p1: Any) -> String { return L10n.tr("Localizable", "Common.Controls.Actions.ShareUser", String(describing: p1)) diff --git a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift index 0d839bd17..cbde09034 100644 --- a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift +++ b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift @@ -264,6 +264,23 @@ extension UserProviderFacade { children.append(shareAction) } + if let shareStatus = shareStatus { + let shareAction = UIAction(title: L10n.Common.Controls.Actions.sharePost, image: UIImage(systemName: "square.and.arrow.up"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in + guard let provider = provider else { return } + let activityViewController = createActivityViewControllerForMastodonUser(status: shareStatus, dependency: provider) + provider.coordinator.present( + scene: .activityViewController( + activityViewController: activityViewController, + sourceView: sourceView, + barButtonItem: barButtonItem + ), + from: provider, + transition: .activityViewControllerPresent(animated: true, completion: nil) + ) + } + children.append(shareAction) + } + return UIMenu(title: "", options: [], children: children) } @@ -274,5 +291,13 @@ extension UserProviderFacade { ) return activityViewController } + + static func createActivityViewControllerForMastodonUser(status: Status, dependency: NeedsDependency) -> UIActivityViewController { + let activityViewController = UIActivityViewController( + activityItems: status.activityItems, + applicationActivities: [SafariActivity(sceneCoordinator: dependency.coordinator)] + ) + return activityViewController + } } diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index da8bca1c9..a2f653d81 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -31,6 +31,7 @@ Please check your internet connection."; "Common.Controls.Actions.SavePhoto" = "Save photo"; "Common.Controls.Actions.SeeMore" = "See More"; "Common.Controls.Actions.Share" = "Share"; +"Common.Controls.Actions.SharePost" = "Share post"; "Common.Controls.Actions.ShareUser" = "Share %@"; "Common.Controls.Actions.SignIn" = "Sign In"; "Common.Controls.Actions.SignUp" = "Sign Up"; From ccdc48add110a8cf90d54d3d9b6d063376aca541 Mon Sep 17 00:00:00 2001 From: sunxiaojian Date: Thu, 29 Apr 2021 15:51:52 +0800 Subject: [PATCH 05/10] feature: blockDomain --- .../CoreData.xcdatamodel/contents | 17 ++- CoreDataStack/Entity/DomainBlock.swift | 73 ++++++++++ CoreDataStack/Entity/MastodonUser.swift | 3 +- CoreDataStack/Entity/Status.swift | 2 +- Localization/app.json | 7 +- Mastodon.xcodeproj/project.pbxproj | 12 ++ .../Diffiable/Section/StatusSection.swift | 3 +- .../CoreDataStack/MastodonUser.swift | 9 ++ Mastodon/Extension/CoreDataStack/Status.swift | 7 +- Mastodon/Generated/Strings.swift | 12 ++ .../UserProvider/UserProviderFacade.swift | 18 +++ .../Resources/en.lproj/Localizable.strings | 3 + .../Scene/Profile/ProfileViewController.swift | 3 + .../APIService/APIService+DomainBlock.swift | 129 +++++++++++++++++ Mastodon/Service/APIService/APIService.swift | 1 + Mastodon/Service/BlockDomainService.swift | 22 +++ .../API/Mastodon+API+DomainBlock.swift | 136 ++++++++++++++++++ .../MastodonSDK/API/Mastodon+API.swift | 9 ++ 18 files changed, 458 insertions(+), 8 deletions(-) create mode 100644 CoreDataStack/Entity/DomainBlock.swift create mode 100644 Mastodon/Service/APIService/APIService+DomainBlock.swift create mode 100644 Mastodon/Service/BlockDomainService.swift create mode 100644 MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+DomainBlock.swift diff --git a/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents b/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents index 0d0170282..1738e3310 100644 --- a/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents +++ b/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -24,6 +24,18 @@ + + + + + + + + + + + + @@ -252,6 +264,7 @@ + @@ -269,4 +282,4 @@ - + \ No newline at end of file diff --git a/CoreDataStack/Entity/DomainBlock.swift b/CoreDataStack/Entity/DomainBlock.swift new file mode 100644 index 000000000..71fa7d461 --- /dev/null +++ b/CoreDataStack/Entity/DomainBlock.swift @@ -0,0 +1,73 @@ +// +// DomainBlock.swift +// CoreDataStack +// +// Created by sxiaojian on 2021/4/29. +// + +import CoreData +import Foundation + +public final class DomainBlock: NSManagedObject { + @NSManaged public private(set) var blockedDomain: String + @NSManaged public private(set) var createAt: Date + + @NSManaged public private(set) var domain: String + @NSManaged public private(set) var userID: String + + override public func awakeFromInsert() { + super.awakeFromInsert() + setPrimitiveValue(Date(), forKey: #keyPath(DomainBlock.createAt)) + } +} + +extension DomainBlock { + @discardableResult + public static func insert( + into context: NSManagedObjectContext, + blockedDomain: String, + domain: String, + userID: String + ) -> DomainBlock { + let domainBlock: DomainBlock = context.insertObject() + domainBlock.domain = domain + domainBlock.blockedDomain = blockedDomain + domainBlock.userID = userID + return domainBlock + } +} + +extension DomainBlock: Managed { + public static var defaultSortDescriptors: [NSSortDescriptor] { + [NSSortDescriptor(keyPath: \DomainBlock.createAt, ascending: false)] + } +} + +extension DomainBlock { + static func predicate(domain: String) -> NSPredicate { + NSPredicate(format: "%K == %@", #keyPath(DomainBlock.domain), domain) + } + + static func predicate(userID: String) -> NSPredicate { + NSPredicate(format: "%K == %@", #keyPath(DomainBlock.userID), userID) + } + + static func predicate(blockedDomain: String) -> NSPredicate { + NSPredicate(format: "%K == %@", #keyPath(DomainBlock.blockedDomain), blockedDomain) + } + + public static func predicate(domain: String, userID: String) -> NSPredicate { + NSCompoundPredicate(andPredicateWithSubpredicates: [ + DomainBlock.predicate(domain: domain), + DomainBlock.predicate(userID: userID) + ]) + } + + public static func predicate(domain: String, userID: String, blockedDomain: String) -> NSPredicate { + NSCompoundPredicate(andPredicateWithSubpredicates: [ + DomainBlock.predicate(domain: domain), + DomainBlock.predicate(userID: userID), + DomainBlock.predicate(blockedDomain:blockedDomain) + ]) + } +} diff --git a/CoreDataStack/Entity/MastodonUser.swift b/CoreDataStack/Entity/MastodonUser.swift index 714b6d0f6..c094bf4f6 100644 --- a/CoreDataStack/Entity/MastodonUser.swift +++ b/CoreDataStack/Entity/MastodonUser.swift @@ -333,7 +333,7 @@ extension MastodonUser: Managed { extension MastodonUser { - static func predicate(domain: String) -> NSPredicate { + public static func predicate(domain: String) -> NSPredicate { return NSPredicate(format: "%K == %@", #keyPath(MastodonUser.domain), domain) } @@ -369,5 +369,4 @@ extension MastodonUser { MastodonUser.predicate(username: username) ]) } - } diff --git a/CoreDataStack/Entity/Status.swift b/CoreDataStack/Entity/Status.swift index 1bb71a1db..73c704046 100644 --- a/CoreDataStack/Entity/Status.swift +++ b/CoreDataStack/Entity/Status.swift @@ -310,7 +310,7 @@ extension Status: Managed { extension Status { - static func predicate(domain: String) -> NSPredicate { + public static func predicate(domain: String) -> NSPredicate { return NSPredicate(format: "%K == %@", #keyPath(Status.domain), domain) } diff --git a/Localization/app.json b/Localization/app.json index 35cdda165..80a9e2dd6 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -27,6 +27,10 @@ "title": "Sign out", "message": "Are you sure you want to sign out?", "confirm": "Sign Out" + }, + "block_domain": { + "message": "Are you really, really sure you want to block the entire %s ? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.", + "block_entire_domain": "Block entire domain" } }, "controls": { @@ -56,7 +60,8 @@ "find_people": "Find people to follow", "manually_search": "Manually search instead", "skip": "Skip", - "report_user": "Report %s" + "report_user": "Report %s", + "block_domain": "Block %s" }, "status": { "user_reblogged": "%s reblogged", diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 99ca696f1..65a7447ee 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -117,6 +117,9 @@ 2D939AB525EDD8A90076FA61 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AB425EDD8A90076FA61 /* String.swift */; }; 2D939AC825EE14620076FA61 /* CropViewController in Frameworks */ = {isa = PBXBuildFile; productRef = 2D939AC725EE14620076FA61 /* CropViewController */; }; 2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AE725EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift */; }; + 2D9DB967263A76FB007C1D71 /* BlockDomainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9DB966263A76FB007C1D71 /* BlockDomainService.swift */; }; + 2D9DB969263A833E007C1D71 /* DomainBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9DB968263A833E007C1D71 /* DomainBlock.swift */; }; + 2D9DB96B263A91D1007C1D71 /* APIService+DomainBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9DB96A263A91D1007C1D71 /* APIService+DomainBlock.swift */; }; 2DA504692601ADE7008F4E6C /* SawToothView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA504682601ADE7008F4E6C /* SawToothView.swift */; }; 2DA6054725F716A2006356F9 /* PlaybackState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA6054625F716A2006356F9 /* PlaybackState.swift */; }; 2DA6055125F74407006356F9 /* AudioContainerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA6055025F74407006356F9 /* AudioContainerViewModel.swift */; }; @@ -543,6 +546,9 @@ 2D927F1325C7EDD9004F19B8 /* Emoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emoji.swift; sourceTree = ""; }; 2D939AB425EDD8A90076FA61 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; 2D939AE725EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonRegisterViewController+Avatar.swift"; sourceTree = ""; }; + 2D9DB966263A76FB007C1D71 /* BlockDomainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockDomainService.swift; sourceTree = ""; }; + 2D9DB968263A833E007C1D71 /* DomainBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainBlock.swift; sourceTree = ""; }; + 2D9DB96A263A91D1007C1D71 /* APIService+DomainBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+DomainBlock.swift"; sourceTree = ""; }; 2DA504682601ADE7008F4E6C /* SawToothView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SawToothView.swift; sourceTree = ""; }; 2DA6054625F716A2006356F9 /* PlaybackState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackState.swift; sourceTree = ""; }; 2DA6055025F74407006356F9 /* AudioContainerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioContainerViewModel.swift; sourceTree = ""; }; @@ -1091,6 +1097,7 @@ 5DF1054025F886D400D6C0D4 /* ViedeoPlaybackService.swift */, DB71FD4B25F8C80E00512AE1 /* StatusPrefetchingService.swift */, DBC7A67B260DFADE00E57475 /* StatusPublishService.swift */, + 2D9DB966263A76FB007C1D71 /* BlockDomainService.swift */, ); path = Service; sourceTree = ""; @@ -1494,6 +1501,7 @@ DB98336A25C9420100AD9700 /* APIService+App.swift */, DB98337025C9443200AD9700 /* APIService+Authentication.swift */, DB98339B25C96DE600AD9700 /* APIService+Account.swift */, + 2D9DB96A263A91D1007C1D71 /* APIService+DomainBlock.swift */, 2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */, DB45FB1C25CA9D23005A8AC7 /* APIService+HomeTimeline.swift */, DB482A4A261340A7008AE74C /* APIService+UserTimeline.swift */, @@ -1683,6 +1691,7 @@ isa = PBXGroup; children = ( DB89BA2625C110B4008580ED /* Status.swift */, + 2D9DB968263A833E007C1D71 /* DomainBlock.swift */, 2D6125462625436B00299647 /* Notification.swift */, 2D0B7A1C261D839600B44727 /* SearchHistory.swift */, DB8AF52425C131D1002E6C99 /* MastodonUser.swift */, @@ -2405,6 +2414,7 @@ DB6B35182601FA3400DC1E11 /* MastodonAttachmentService.swift in Sources */, 0FB3D2F725E4C24D00AAD544 /* MastodonPickServerViewModel.swift in Sources */, 2D61335E25C1894B00CAE157 /* APIService.swift in Sources */, + 2D9DB967263A76FB007C1D71 /* BlockDomainService.swift in Sources */, DB66729625F9F91600D60309 /* ComposeStatusSection.swift in Sources */, DB482A3F261331E8008AE74C /* UserTimelineViewModel+State.swift in Sources */, 2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */, @@ -2535,6 +2545,7 @@ 5DA732CC2629CEF500A92342 /* UIView+Remove.swift in Sources */, 2D38F1DF25CD46A400561493 /* HomeTimelineViewController+Provider.swift in Sources */, 2D206B9225F60EA700143C56 /* UIControl.swift in Sources */, + 2D9DB96B263A91D1007C1D71 /* APIService+DomainBlock.swift in Sources */, 2D46975E25C2A54100CF4AA9 /* NSLayoutConstraint.swift in Sources */, 2D45E5BF25C9549700A6D639 /* PublicTimelineViewModel+State.swift in Sources */, DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */, @@ -2752,6 +2763,7 @@ 2DF75BB925D1474100694EC8 /* ManagedObjectObserver.swift in Sources */, 2D927F0225C7E4F2004F19B8 /* Mention.swift in Sources */, DB89BA1D25C1107F008580ED /* URL.swift in Sources */, + 2D9DB969263A833E007C1D71 /* DomainBlock.swift in Sources */, 2D0B7A1D261D839600B44727 /* SearchHistory.swift in Sources */, 2D927F0825C7E9A8004F19B8 /* Tag.swift in Sources */, 5B90C46E26259B2C0002E742 /* Subscription.swift in Sources */, diff --git a/Mastodon/Diffiable/Section/StatusSection.swift b/Mastodon/Diffiable/Section/StatusSection.swift index 0e26e1479..839682c4a 100644 --- a/Mastodon/Diffiable/Section/StatusSection.swift +++ b/Mastodon/Diffiable/Section/StatusSection.swift @@ -781,7 +781,7 @@ extension StatusSection { } let author = status.authorForUserProvider let canReport = authenticationBox.userID != author.id - + let canBlockDomain = authenticationBox.domain != author.domain let isMuting = (author.mutingBy ?? Set()).map(\.id).contains(authenticationBox.userID) let isBlocking = (author.blockingBy ?? Set()).map(\.id).contains(authenticationBox.userID) @@ -791,6 +791,7 @@ extension StatusSection { isMuting: isMuting, isBlocking: isBlocking, canReport: canReport, + canBlockDomain: canBlockDomain, provider: userProvider, cell: cell, indexPath: indexPath, diff --git a/Mastodon/Extension/CoreDataStack/MastodonUser.swift b/Mastodon/Extension/CoreDataStack/MastodonUser.swift index bb99b15d4..96f95e5e2 100644 --- a/Mastodon/Extension/CoreDataStack/MastodonUser.swift +++ b/Mastodon/Extension/CoreDataStack/MastodonUser.swift @@ -50,6 +50,15 @@ extension MastodonUser { } } + var domainFromAcct: String { + if !acct.contains("@") { + return domain + } else { + let domain = acct.split(separator: "@").last + return String(domain!) + } + } + } extension MastodonUser { diff --git a/Mastodon/Extension/CoreDataStack/Status.swift b/Mastodon/Extension/CoreDataStack/Status.swift index 015671cb5..5eec9f4aa 100644 --- a/Mastodon/Extension/CoreDataStack/Status.swift +++ b/Mastodon/Extension/CoreDataStack/Status.swift @@ -74,7 +74,12 @@ extension Status { extension Status { var statusURL: URL { - return URL(string: "https://\(self.domain)/web/statuses/\(self.id)")! + if let urlString = self.url, + let url = URL(string: urlString) { + return url + } else { + return URL(string: "https://\(self.domain)/web/statuses/\(self.id)")! + } } var activityItems: [Any] { diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index c0d1cac6f..18de7e8e5 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -13,6 +13,14 @@ internal enum L10n { internal enum Common { internal enum Alerts { + internal enum BlockDomain { + /// Block entire domain + internal static let blockEntireDomain = L10n.tr("Localizable", "Common.Alerts.BlockDomain.BlockEntireDomain") + /// Are you really, really sure you want to block the entire %@ ? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed. + internal static func message(_ p1: Any) -> String { + return L10n.tr("Localizable", "Common.Alerts.BlockDomain.Message", String(describing: p1)) + } + } internal enum Common { /// Please try again. internal static let pleaseTryAgain = L10n.tr("Localizable", "Common.Alerts.Common.PleaseTryAgain") @@ -60,6 +68,10 @@ internal enum L10n { internal static let add = L10n.tr("Localizable", "Common.Controls.Actions.Add") /// Back internal static let back = L10n.tr("Localizable", "Common.Controls.Actions.Back") + /// Block %@ + internal static func blockDomain(_ p1: Any) -> String { + return L10n.tr("Localizable", "Common.Controls.Actions.BlockDomain", String(describing: p1)) + } /// Cancel internal static let cancel = L10n.tr("Localizable", "Common.Controls.Actions.Cancel") /// Confirm diff --git a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift index cbde09034..a4356e71a 100644 --- a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift +++ b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift @@ -159,6 +159,7 @@ extension UserProviderFacade { isMuting: Bool, isBlocking: Bool, canReport: Bool, + canBlockDomain: Bool, provider: UserProvider, cell: UITableViewCell?, indexPath: IndexPath?, @@ -247,6 +248,23 @@ extension UserProviderFacade { children.append(reportAction) } + if canBlockDomain { + let blockDomainAction = UIAction(title: L10n.Common.Controls.Actions.blockDomain(mastodonUser.domain), image: UIImage(systemName: "nosign"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in + guard let provider = provider else { return } + let alertController = UIAlertController(title: "", message: L10n.Common.Alerts.BlockDomain.message(mastodonUser.domain), preferredStyle: .alert) + let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .default) { _ in + + } + alertController.addAction(cancelAction) + let blockDomainAction = UIAlertAction(title: L10n.Common.Alerts.BlockDomain.blockEntireDomain, style: .destructive) { _ in + BlockDomainService(context: provider.context).blockDomain(domain: mastodonUser.domain) + } + alertController.addAction(blockDomainAction) + provider.present(alertController, animated: true, completion: nil) + } + children.append(blockDomainAction) + } + if let shareUser = shareUser { let shareAction = UIAction(title: L10n.Common.Controls.Actions.shareUser(name), image: UIImage(systemName: "square.and.arrow.up"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in guard let provider = provider else { return } diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index a2f653d81..983de7327 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -1,3 +1,5 @@ +"Common.Alerts.BlockDomain.BlockEntireDomain" = "Block entire domain"; +"Common.Alerts.BlockDomain.Message" = "Are you really, really sure you want to block the entire %@ ? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed."; "Common.Alerts.Common.PleaseTryAgain" = "Please try again."; "Common.Alerts.Common.PleaseTryAgainLater" = "Please try again later."; "Common.Alerts.DiscardPostContent.Message" = "Confirm discard composed post content."; @@ -14,6 +16,7 @@ Please check your internet connection."; "Common.Alerts.VoteFailure.Title" = "Vote Failure"; "Common.Controls.Actions.Add" = "Add"; "Common.Controls.Actions.Back" = "Back"; +"Common.Controls.Actions.BlockDomain" = "Block %@"; "Common.Controls.Actions.Cancel" = "Cancel"; "Common.Controls.Actions.Confirm" = "Confirm"; "Common.Controls.Actions.Continue" = "Continue"; diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index 7a8742f18..21571faa7 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -374,14 +374,17 @@ extension ProfileViewController { self.moreMenuBarButtonItem.menu = nil return } + guard let currentDomain = self.viewModel.domain.value else { return } let isMuting = relationshipActionOptionSet.contains(.muting) let isBlocking = relationshipActionOptionSet.contains(.blocking) let needsShareAction = self.viewModel.isMeBarButtonItemsHidden.value + let canBlockDomain = mastodonUser.domain != currentDomain self.moreMenuBarButtonItem.menu = UserProviderFacade.createProfileActionMenu( for: mastodonUser, isMuting: isMuting, isBlocking: isBlocking, canReport: true, + canBlockDomain: canBlockDomain, provider: self, cell: nil, indexPath: nil, diff --git a/Mastodon/Service/APIService/APIService+DomainBlock.swift b/Mastodon/Service/APIService/APIService+DomainBlock.swift new file mode 100644 index 000000000..bb54d2983 --- /dev/null +++ b/Mastodon/Service/APIService/APIService+DomainBlock.swift @@ -0,0 +1,129 @@ +// +// APIService+DomainBlock.swift +// Mastodon +// +// Created by sxiaojian on 2021/4/29. +// + +import Foundation +import Combine +import CoreData +import CoreDataStack +import CommonOSLog +import DateToolsSwift +import MastodonSDK + +extension APIService { + + func getDomainblocks( + domain: String, + limit: Int = onceRequestDomainBlocksMaxCount, + authorizationBox: AuthenticationService.MastodonAuthenticationBox + ) -> AnyPublisher, Error> { + let authorization = authorizationBox.userAuthorization + + let query = Mastodon.API.DomainBlock.Query( + maxID: nil, sinceID: nil, limit: limit + ) + return Mastodon.API.DomainBlock.getDomainblocks( + domain: domain, + session: session, + authorization: authorization, + query: query + ) + .flatMap { response -> AnyPublisher, Error> in + return self.backgroundManagedObjectContext.performChanges { + response.value.forEach { domain in + // use constrain to avoid repeated save + let _ = DomainBlock.insert( + into: self.backgroundManagedObjectContext, + blockedDomain: domain, + domain: authorizationBox.domain, + userID: authorizationBox.userID + ) + } + } + .setFailureType(to: Error.self) + .tryMap { result -> Mastodon.Response.Content<[String]> in + switch result { + case .success: + return response + case .failure(let error): + throw error + } + } + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } + + func blockDomain( + domain: String, + authorizationBox: AuthenticationService.MastodonAuthenticationBox + ) -> AnyPublisher, Error> { + let authorization = authorizationBox.userAuthorization + + return Mastodon.API.DomainBlock.blockDomain( + domain: authorizationBox.domain, + blockDomain: domain, + session: session, + authorization: authorization + ) + .flatMap { response -> AnyPublisher, Error> in + return self.backgroundManagedObjectContext.performChanges { + let _ = DomainBlock.insert( + into: self.backgroundManagedObjectContext, + blockedDomain: domain, + domain: authorizationBox.domain, + userID: authorizationBox.userID + ) + } + .setFailureType(to: Error.self) + .tryMap { result -> Mastodon.Response.Content in + switch result { + case .success: + return response + case .failure(let error): + throw error + } + } + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } + + func unblockDomain( + domain: String, + authorizationBox: AuthenticationService.MastodonAuthenticationBox + ) -> AnyPublisher, Error> { + let authorization = authorizationBox.userAuthorization + + return Mastodon.API.DomainBlock.unblockDomain( + domain: authorizationBox.domain, + blockDomain: domain, + session: session, + authorization: authorization + ) + .flatMap { response -> AnyPublisher, Error> in + return self.backgroundManagedObjectContext.performChanges { +// let _ = DomainBlock.insert( +// into: self.backgroundManagedObjectContext, +// blockedDomain: domain, +// domain: authorizationBox.domain, +// userID: authorizationBox.userID +// ) + } + .setFailureType(to: Error.self) + .tryMap { result -> Mastodon.Response.Content in + switch result { + case .success: + return response + case .failure(let error): + throw error + } + } + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } +} diff --git a/Mastodon/Service/APIService/APIService.swift b/Mastodon/Service/APIService/APIService.swift index 11e6f5cac..b38e2e059 100644 --- a/Mastodon/Service/APIService/APIService.swift +++ b/Mastodon/Service/APIService/APIService.swift @@ -46,6 +46,7 @@ final class APIService { extension APIService { public static let onceRequestStatusMaxCount = 100 public static let onceRequestUserMaxCount = 100 + public static let onceRequestDomainBlocksMaxCount = 100 } extension APIService { diff --git a/Mastodon/Service/BlockDomainService.swift b/Mastodon/Service/BlockDomainService.swift new file mode 100644 index 000000000..91d226989 --- /dev/null +++ b/Mastodon/Service/BlockDomainService.swift @@ -0,0 +1,22 @@ +// +// BlockDomainService.swift +// Mastodon +// +// Created by sxiaojian on 2021/4/29. +// + +import CoreData +import CoreDataStack +import Foundation + +final class BlockDomainService { + let context: AppContext + + init(context: AppContext) { + self.context = context + } + + func blockDomain(domain: String) { + + } +} diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+DomainBlock.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+DomainBlock.swift new file mode 100644 index 000000000..f0ee51d90 --- /dev/null +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+DomainBlock.swift @@ -0,0 +1,136 @@ +// +// File.swift +// +// +// Created by sxiaojian on 2021/4/29. +// + +import Foundation +import Combine + +extension Mastodon.API.DomainBlock { + static func domainBlockEndpointURL(domain: String) -> URL { + Mastodon.API.endpointURL(domain: domain).appendingPathComponent("domain_blocks") + } + + /// Fetch domain blocks + /// + /// - Since: 1.4.0 + /// # Reference + /// [Document](https://docs.joinmastodon.org/methods/accounts/domain_blocks/) + /// - Parameters: + /// - domain: Mastodon instance domain. e.g. "example.com" + /// - session: `URLSession` + /// - authorization: User token + /// - Returns: `AnyPublisher` contains `String` nested in the response + public static func getDomainblocks( + domain: String, + session: URLSession, + authorization: Mastodon.API.OAuth.Authorization, + query: Mastodon.API.DomainBlock.Query + ) -> AnyPublisher, Error> { + let url = domainBlockEndpointURL(domain: domain) + let request = Mastodon.API.get(url: url, query: query, authorization: authorization) + return session.dataTaskPublisher(for: request) + .tryMap { data, response in + let value = try Mastodon.API.decode(type: [String].self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + .eraseToAnyPublisher() + } + + /// Block a domain + /// + /// - Since: 1.4.0 + /// # Reference + /// [Document](https://docs.joinmastodon.org/methods/accounts/domain_blocks/) + /// - Parameters: + /// - domain: Mastodon instance domain. e.g. "example.com" + /// - session: `URLSession` + /// - authorization: User token + /// - Returns: `AnyPublisher` contains `String` nested in the response + public static func blockDomain( + domain: String, + blockDomain:String, + session: URLSession, + authorization: Mastodon.API.OAuth.Authorization + ) -> AnyPublisher, Error> { + let query = Mastodon.API.DomainBlock.BlockQuery(domain: blockDomain) + let request = Mastodon.API.post( + url: domainBlockEndpointURL(domain: domain), + query: query, + authorization: authorization + ) + return session.dataTaskPublisher(for: request) + .tryMap { data, response in + let value = try Mastodon.API.decode(type: String.self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + .eraseToAnyPublisher() + } + + /// Unblock a domain + /// + /// - Since: 1.4.0 + /// # Reference + /// [Document](https://docs.joinmastodon.org/methods/accounts/domain_blocks/) + /// - Parameters: + /// - domain: Mastodon instance domain. e.g. "example.com" + /// - session: `URLSession` + /// - authorization: User token + /// - Returns: `AnyPublisher` contains `String` nested in the response + public static func unblockDomain( + domain: String, + blockDomain:String, + session: URLSession, + authorization: Mastodon.API.OAuth.Authorization + ) -> AnyPublisher, Error> { + let query = Mastodon.API.DomainBlock.BlockQuery(domain: blockDomain) + let request = Mastodon.API.delete( + url: domainBlockEndpointURL(domain: domain), + query: query, + authorization: authorization + ) + return session.dataTaskPublisher(for: request) + .tryMap { data, response in + let value = try Mastodon.API.decode(type: String.self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + .eraseToAnyPublisher() + } +} + +extension Mastodon.API.DomainBlock { + public struct Query: GetQuery { + public let maxID: Mastodon.Entity.Status.ID? + public let sinceID: Mastodon.Entity.Status.ID? + public let limit: Int? + + public init( + maxID: Mastodon.Entity.Status.ID?, + sinceID: Mastodon.Entity.Status.ID?, + limit: Int? + ) { + self.maxID = maxID + self.sinceID = sinceID + self.limit = limit + } + + var queryItems: [URLQueryItem]? { + var items: [URLQueryItem] = [] + maxID.flatMap { items.append(URLQueryItem(name: "max_id", value: $0)) } + sinceID.flatMap { items.append(URLQueryItem(name: "since_id", value: $0)) } + limit.flatMap { items.append(URLQueryItem(name: "limit", value: String($0))) } + guard !items.isEmpty else { return nil } + return items + } + } + + public struct BlockQuery: Codable, PostQuery { + public let domain: String + + public init(domain: String) { + self.domain = domain + } + } +} diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift index cfaa1736d..79408f833 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift @@ -117,6 +117,7 @@ extension Mastodon.API { public enum Notifications { } public enum Subscriptions { } public enum Reports { } + public enum DomainBlock { } } extension Mastodon.API.V2 { @@ -141,6 +142,14 @@ extension Mastodon.API { ) -> URLRequest { return buildRequest(url: url, method: .POST, query: query, authorization: authorization) } + + static func delete( + url: URL, + query: PostQuery?, + authorization: OAuth.Authorization? + ) -> URLRequest { + return buildRequest(url: url, method: .DELETE, query: query, authorization: authorization) + } static func patch( url: URL, From 33401b4e1f2e4eb35f67c6ad8590522db5e550c9 Mon Sep 17 00:00:00 2001 From: sunxiaojian Date: Fri, 30 Apr 2021 12:53:25 +0800 Subject: [PATCH 06/10] feature: finish domainBlock action and domainUnblick action --- .../CoreData.xcdatamodel/contents | 3 +- Localization/app.json | 5 +- .../xcshareddata/swiftpm/Package.resolved | 2 +- .../Diffiable/Section/StatusSection.swift | 49 +++++++--- Mastodon/Generated/Strings.swift | 10 +- .../UserProvider/UserProviderFacade.swift | 43 ++++++--- .../Resources/en.lproj/Localizable.strings | 3 +- .../Scene/Profile/ProfileViewController.swift | 6 +- Mastodon/Scene/Profile/ProfileViewModel.swift | 4 + .../APIService/APIService+DomainBlock.swift | 68 +++++++++----- Mastodon/Service/BlockDomainService.swift | 92 +++++++++++++++++-- .../API/Mastodon+API+DomainBlock.swift | 8 +- .../Entity/Mastodon+Entity+Empty.swift | 15 +++ 13 files changed, 234 insertions(+), 74 deletions(-) create mode 100644 MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Empty.swift diff --git a/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents b/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents index 1738e3310..7945a97af 100644 --- a/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents +++ b/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents @@ -26,6 +26,7 @@ + @@ -264,7 +265,7 @@ - + diff --git a/Localization/app.json b/Localization/app.json index 80a9e2dd6..5139cfa64 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -61,7 +61,8 @@ "manually_search": "Manually search instead", "skip": "Skip", "report_user": "Report %s", - "block_domain": "Block %s" + "block_domain": "Block %s", + "unblock_domain": "Unblock %s" }, "status": { "user_reblogged": "%s reblogged", @@ -91,7 +92,7 @@ "pending": "Pending", "block": "Block", "block_user": "Block %s", - "block_domain": "Block %s", + "block_domain": "Domain Blocked", "unblock": "Unblock", "unblock_user": "Unblock %s", "blocked": "Blocked", diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved index 741947371..4c5c26898 100644 --- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -51,7 +51,7 @@ "repositoryURL": "https://github.com/onevcat/Kingfisher.git", "state": { "branch": null, - "revision": "bbc4bc4def7eb05a7ba8e1219f80ee9be327334e", + "revision": "15d199e84677303a7004ed2c5ecaa1a90f3863f8", "version": "6.2.1" } }, diff --git a/Mastodon/Diffiable/Section/StatusSection.swift b/Mastodon/Diffiable/Section/StatusSection.swift index 839682c4a..30d3bde4c 100644 --- a/Mastodon/Diffiable/Section/StatusSection.swift +++ b/Mastodon/Diffiable/Section/StatusSection.swift @@ -781,24 +781,43 @@ extension StatusSection { } let author = status.authorForUserProvider let canReport = authenticationBox.userID != author.id - let canBlockDomain = authenticationBox.domain != author.domain + let isInSameDomain = authenticationBox.domain == author.domainFromAcct let isMuting = (author.mutingBy ?? Set()).map(\.id).contains(authenticationBox.userID) let isBlocking = (author.blockingBy ?? Set()).map(\.id).contains(authenticationBox.userID) cell.statusView.actionToolbarContainer.moreButton.showsMenuAsPrimaryAction = true - cell.statusView.actionToolbarContainer.moreButton.menu = UserProviderFacade.createProfileActionMenu( - for: author, - isMuting: isMuting, - isBlocking: isBlocking, - canReport: canReport, - canBlockDomain: canBlockDomain, - provider: userProvider, - cell: cell, - indexPath: indexPath, - sourceView: cell.statusView.actionToolbarContainer.moreButton, - barButtonItem: nil, - shareUser: nil, - shareStatus: status - ) + let managedObjectContext = userProvider.context.backgroundManagedObjectContext + managedObjectContext.perform { + let blockedDomain: DomainBlock? = { + let request = DomainBlock.sortedFetchRequest + request.predicate = DomainBlock.predicate(domain: authenticationBox.domain, userID: authenticationBox.userID, blockedDomain: author.domainFromAcct) + request.fetchLimit = 1 + request.returnsObjectsAsFaults = false + do { + return try managedObjectContext.fetch(request).first + } catch { + assertionFailure(error.localizedDescription) + return nil + } + }() + let isDomainBlocking = blockedDomain != nil + DispatchQueue.main.async { + cell.statusView.actionToolbarContainer.moreButton.menu = UserProviderFacade.createProfileActionMenu( + for: author, + isMuting: isMuting, + isBlocking: isBlocking, + canReport: canReport, + isInSameDomain: isInSameDomain, + isDomainBlocking: isDomainBlocking, + provider: userProvider, + cell: cell, + indexPath: indexPath, + sourceView: cell.statusView.actionToolbarContainer.moreButton, + barButtonItem: nil, + shareUser: nil, + shareStatus: status + ) + } + } } } diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index 18de7e8e5..26816a32f 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -124,14 +124,16 @@ internal enum L10n { internal static let takePhoto = L10n.tr("Localizable", "Common.Controls.Actions.TakePhoto") /// Try Again internal static let tryAgain = L10n.tr("Localizable", "Common.Controls.Actions.TryAgain") + /// Unblock %@ + internal static func unblockDomain(_ p1: Any) -> String { + return L10n.tr("Localizable", "Common.Controls.Actions.UnblockDomain", String(describing: p1)) + } } internal enum Firendship { /// Block internal static let block = L10n.tr("Localizable", "Common.Controls.Firendship.Block") - /// Block %@ - internal static func blockDomain(_ p1: Any) -> String { - return L10n.tr("Localizable", "Common.Controls.Firendship.BlockDomain", String(describing: p1)) - } + /// Domain Blocked + internal static let blockDomain = L10n.tr("Localizable", "Common.Controls.Firendship.BlockDomain") /// Blocked internal static let blocked = L10n.tr("Localizable", "Common.Controls.Firendship.Blocked") /// Block %@ diff --git a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift index a4356e71a..d1615212b 100644 --- a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift +++ b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift @@ -159,7 +159,8 @@ extension UserProviderFacade { isMuting: Bool, isBlocking: Bool, canReport: Bool, - canBlockDomain: Bool, + isInSameDomain: Bool, + isDomainBlocking: Bool, provider: UserProvider, cell: UITableViewCell?, indexPath: IndexPath?, @@ -248,21 +249,37 @@ extension UserProviderFacade { children.append(reportAction) } - if canBlockDomain { - let blockDomainAction = UIAction(title: L10n.Common.Controls.Actions.blockDomain(mastodonUser.domain), image: UIImage(systemName: "nosign"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in - guard let provider = provider else { return } - let alertController = UIAlertController(title: "", message: L10n.Common.Alerts.BlockDomain.message(mastodonUser.domain), preferredStyle: .alert) - let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .default) { _ in + if !isInSameDomain { + if isDomainBlocking { + let unblockDomainAction = UIAction(title: L10n.Common.Controls.Actions.unblockDomain(mastodonUser.domainFromAcct), image: UIImage(systemName: "nosign"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in + guard let provider = provider else { return } + BlockDomainService(userProvider: provider, + cell: cell, + indexPath: indexPath + ) + .unblockDomain() + } + children.append(unblockDomainAction) + } else { + let blockDomainAction = UIAction(title: L10n.Common.Controls.Actions.blockDomain(mastodonUser.domainFromAcct), image: UIImage(systemName: "nosign"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in + guard let provider = provider else { return } + let alertController = UIAlertController(title: "", message: L10n.Common.Alerts.BlockDomain.message(mastodonUser.domainFromAcct), preferredStyle: .alert) + let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .default) { _ in + } + alertController.addAction(cancelAction) + let blockDomainAction = UIAlertAction(title: L10n.Common.Alerts.BlockDomain.blockEntireDomain, style: .destructive) { _ in + BlockDomainService(userProvider: provider, + cell: cell, + indexPath: indexPath + ) + .blockDomain() + } + alertController.addAction(blockDomainAction) + provider.present(alertController, animated: true, completion: nil) } - alertController.addAction(cancelAction) - let blockDomainAction = UIAlertAction(title: L10n.Common.Alerts.BlockDomain.blockEntireDomain, style: .destructive) { _ in - BlockDomainService(context: provider.context).blockDomain(domain: mastodonUser.domain) - } - alertController.addAction(blockDomainAction) - provider.present(alertController, animated: true, completion: nil) + children.append(blockDomainAction) } - children.append(blockDomainAction) } if let shareUser = shareUser { diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index 983de7327..08c23a305 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -41,8 +41,9 @@ Please check your internet connection."; "Common.Controls.Actions.Skip" = "Skip"; "Common.Controls.Actions.TakePhoto" = "Take photo"; "Common.Controls.Actions.TryAgain" = "Try Again"; +"Common.Controls.Actions.UnblockDomain" = "Unblock %@"; "Common.Controls.Firendship.Block" = "Block"; -"Common.Controls.Firendship.BlockDomain" = "Block %@"; +"Common.Controls.Firendship.BlockDomain" = "Domain Blocked"; "Common.Controls.Firendship.BlockUser" = "Block %@"; "Common.Controls.Firendship.Blocked" = "Blocked"; "Common.Controls.Firendship.EditInfo" = "Edit info"; diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index 21571faa7..317d51c03 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -377,14 +377,16 @@ extension ProfileViewController { guard let currentDomain = self.viewModel.domain.value else { return } let isMuting = relationshipActionOptionSet.contains(.muting) let isBlocking = relationshipActionOptionSet.contains(.blocking) + let isDomainBlocking = relationshipActionOptionSet.contains(.domainBlocking) let needsShareAction = self.viewModel.isMeBarButtonItemsHidden.value - let canBlockDomain = mastodonUser.domain != currentDomain + let isInSameDomain = mastodonUser.domainFromAcct == currentDomain self.moreMenuBarButtonItem.menu = UserProviderFacade.createProfileActionMenu( for: mastodonUser, isMuting: isMuting, isBlocking: isBlocking, canReport: true, - canBlockDomain: canBlockDomain, + isInSameDomain: isInSameDomain, + isDomainBlocking: isDomainBlocking, provider: self, cell: nil, indexPath: nil, diff --git a/Mastodon/Scene/Profile/ProfileViewModel.swift b/Mastodon/Scene/Profile/ProfileViewModel.swift index 445952e96..c19ae2f63 100644 --- a/Mastodon/Scene/Profile/ProfileViewModel.swift +++ b/Mastodon/Scene/Profile/ProfileViewModel.swift @@ -327,6 +327,7 @@ extension ProfileViewModel { case muting case blocked case blocking + case domainBlocking case suspended case edit case editing @@ -349,6 +350,7 @@ extension ProfileViewModel { static let muting = RelationshipAction.muting.option static let blocked = RelationshipAction.blocked.option static let blocking = RelationshipAction.blocking.option + static let domainBlocking = RelationshipAction.domainBlocking.option static let suspended = RelationshipAction.suspended.option static let edit = RelationshipAction.edit.option static let editing = RelationshipAction.editing.option @@ -379,6 +381,7 @@ extension ProfileViewModel { case .muting: return L10n.Common.Controls.Firendship.muted case .blocked: return L10n.Common.Controls.Firendship.follow // blocked by user case .blocking: return L10n.Common.Controls.Firendship.blocked + case .domainBlocking: return L10n.Common.Controls.Firendship.blockDomain case .suspended: return L10n.Common.Controls.Firendship.follow case .edit: return L10n.Common.Controls.Firendship.editInfo case .editing: return L10n.Common.Controls.Actions.done @@ -400,6 +403,7 @@ extension ProfileViewModel { case .muting: return Asset.Colors.Background.alertYellow.color case .blocked: return Asset.Colors.Button.normal.color case .blocking: return Asset.Colors.Background.danger.color + case .domainBlocking: return Asset.Colors.Button.normal.color case .suspended: return Asset.Colors.Button.normal.color case .edit: return Asset.Colors.Button.normal.color case .editing: return Asset.Colors.Button.normal.color diff --git a/Mastodon/Service/APIService/APIService+DomainBlock.swift b/Mastodon/Service/APIService/APIService+DomainBlock.swift index bb54d2983..7d9936c5a 100644 --- a/Mastodon/Service/APIService/APIService+DomainBlock.swift +++ b/Mastodon/Service/APIService/APIService+DomainBlock.swift @@ -5,16 +5,15 @@ // Created by sxiaojian on 2021/4/29. // -import Foundation import Combine +import CommonOSLog import CoreData import CoreDataStack -import CommonOSLog import DateToolsSwift +import Foundation import MastodonSDK extension APIService { - func getDomainblocks( domain: String, limit: Int = onceRequestDomainBlocksMaxCount, @@ -32,10 +31,10 @@ extension APIService { query: query ) .flatMap { response -> AnyPublisher, Error> in - return self.backgroundManagedObjectContext.performChanges { + self.backgroundManagedObjectContext.performChanges { response.value.forEach { domain in // use constrain to avoid repeated save - let _ = DomainBlock.insert( + _ = DomainBlock.insert( into: self.backgroundManagedObjectContext, blockedDomain: domain, domain: authorizationBox.domain, @@ -58,28 +57,33 @@ extension APIService { } func blockDomain( - domain: String, + user: MastodonUser, authorizationBox: AuthenticationService.MastodonAuthenticationBox - ) -> AnyPublisher, Error> { + ) -> AnyPublisher, Error> { let authorization = authorizationBox.userAuthorization return Mastodon.API.DomainBlock.blockDomain( domain: authorizationBox.domain, - blockDomain: domain, + blockDomain: user.domainFromAcct, session: session, authorization: authorization ) - .flatMap { response -> AnyPublisher, Error> in - return self.backgroundManagedObjectContext.performChanges { - let _ = DomainBlock.insert( + .flatMap { response -> AnyPublisher, Error> in + self.backgroundManagedObjectContext.performChanges { + let requestMastodonUserRequest = MastodonUser.sortedFetchRequest + requestMastodonUserRequest.predicate = MastodonUser.predicate(domain: authorizationBox.domain, id: authorizationBox.userID) + requestMastodonUserRequest.fetchLimit = 1 + guard let requestMastodonUser = self.backgroundManagedObjectContext.safeFetch(requestMastodonUserRequest).first else { return } + _ = DomainBlock.insert( into: self.backgroundManagedObjectContext, - blockedDomain: domain, + blockedDomain: user.domainFromAcct, domain: authorizationBox.domain, userID: authorizationBox.userID ) + user.update(isDomainBlocking: true, by: requestMastodonUser) } .setFailureType(to: Error.self) - .tryMap { result -> Mastodon.Response.Content in + .tryMap { result -> Mastodon.Response.Content in switch result { case .success: return response @@ -93,28 +97,42 @@ extension APIService { } func unblockDomain( - domain: String, + user: MastodonUser, authorizationBox: AuthenticationService.MastodonAuthenticationBox - ) -> AnyPublisher, Error> { + ) -> AnyPublisher, Error> { let authorization = authorizationBox.userAuthorization return Mastodon.API.DomainBlock.unblockDomain( domain: authorizationBox.domain, - blockDomain: domain, + blockDomain: user.domainFromAcct, session: session, authorization: authorization ) - .flatMap { response -> AnyPublisher, Error> in - return self.backgroundManagedObjectContext.performChanges { -// let _ = DomainBlock.insert( -// into: self.backgroundManagedObjectContext, -// blockedDomain: domain, -// domain: authorizationBox.domain, -// userID: authorizationBox.userID -// ) + .flatMap { response -> AnyPublisher, Error> in + self.backgroundManagedObjectContext.performChanges { + let blockedDomain: DomainBlock? = { + let request = DomainBlock.sortedFetchRequest + request.predicate = DomainBlock.predicate(domain: authorizationBox.domain, userID: authorizationBox.userID, blockedDomain: user.domainFromAcct) + request.fetchLimit = 1 + request.returnsObjectsAsFaults = false + do { + return try self.backgroundManagedObjectContext.fetch(request).first + } catch { + assertionFailure(error.localizedDescription) + return nil + } + }() + if let blockedDomain = blockedDomain { + self.backgroundManagedObjectContext.delete(blockedDomain) + } + let requestMastodonUserRequest = MastodonUser.sortedFetchRequest + requestMastodonUserRequest.predicate = MastodonUser.predicate(domain: authorizationBox.domain, id: authorizationBox.userID) + requestMastodonUserRequest.fetchLimit = 1 + guard let requestMastodonUser = self.backgroundManagedObjectContext.safeFetch(requestMastodonUserRequest).first else { return } + user.update(isDomainBlocking: false, by: requestMastodonUser) } .setFailureType(to: Error.self) - .tryMap { result -> Mastodon.Response.Content in + .tryMap { result -> Mastodon.Response.Content in switch result { case .success: return response diff --git a/Mastodon/Service/BlockDomainService.swift b/Mastodon/Service/BlockDomainService.swift index 91d226989..0cc35f0d2 100644 --- a/Mastodon/Service/BlockDomainService.swift +++ b/Mastodon/Service/BlockDomainService.swift @@ -8,15 +8,95 @@ import CoreData import CoreDataStack import Foundation +import Combine +import MastodonSDK +import OSLog +import UIKit final class BlockDomainService { - let context: AppContext - - init(context: AppContext) { - self.context = context + let userProvider: UserProvider + let cell: UITableViewCell? + let indexPath: IndexPath? + init(userProvider: UserProvider, + cell: UITableViewCell?, + indexPath: IndexPath? + ) { + self.userProvider = userProvider + self.cell = cell + self.indexPath = indexPath } - func blockDomain(domain: String) { - + func blockDomain() { + guard let activeMastodonAuthenticationBox = self.userProvider.context.authenticationService.activeMastodonAuthenticationBox.value else { return } + guard let context = self.userProvider.context else { + return + } + var mastodonUser: AnyPublisher + if let cell = self.cell, let indexPath = self.indexPath { + mastodonUser = userProvider.mastodonUser(for: cell, indexPath: indexPath).eraseToAnyPublisher() + } else { + mastodonUser = userProvider.mastodonUser().eraseToAnyPublisher() + } + mastodonUser + .compactMap { mastodonUser -> AnyPublisher, Error>? in + guard let mastodonUser = mastodonUser else { + return nil + } + return context.apiService.blockDomain(user: mastodonUser, authorizationBox: activeMastodonAuthenticationBox) + } + .switchToLatest() + .flatMap { response -> AnyPublisher, Error> in + return context.apiService.getDomainblocks(domain: activeMastodonAuthenticationBox.domain, authorizationBox: activeMastodonAuthenticationBox) + } + .sink { completion in + switch completion { + case .finished: + break + case .failure(let error): + print(error) + } + } receiveValue: { response in + print(response) + } + .store(in: &userProvider.disposeBag) + } + + func unblockDomain() { + guard let activeMastodonAuthenticationBox = self.userProvider.context.authenticationService.activeMastodonAuthenticationBox.value else { return } + guard let context = self.userProvider.context else { + return + } + var mastodonUser: AnyPublisher + if let cell = self.cell, let indexPath = self.indexPath { + mastodonUser = userProvider.mastodonUser(for: cell, indexPath: indexPath).eraseToAnyPublisher() + } else { + mastodonUser = userProvider.mastodonUser().eraseToAnyPublisher() + } + mastodonUser + .compactMap { mastodonUser -> AnyPublisher, Error>? in + guard let mastodonUser = mastodonUser else { + return nil + } + return context.apiService.unblockDomain(user: mastodonUser, authorizationBox: activeMastodonAuthenticationBox) + } + .switchToLatest() + .flatMap { response -> AnyPublisher, Error> in + return context.apiService.getDomainblocks(domain: activeMastodonAuthenticationBox.domain, authorizationBox: activeMastodonAuthenticationBox) + } + .sink { completion in + switch completion { + case .finished: + break + case .failure(let error): + print(error) + } + } receiveValue: { response in + print(response) + } + .store(in: &userProvider.disposeBag) + } + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", (#file as NSString).lastPathComponent, #line, #function) } } diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+DomainBlock.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+DomainBlock.swift index f0ee51d90..356ac68a0 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+DomainBlock.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+DomainBlock.swift @@ -54,7 +54,7 @@ extension Mastodon.API.DomainBlock { blockDomain:String, session: URLSession, authorization: Mastodon.API.OAuth.Authorization - ) -> AnyPublisher, Error> { + ) -> AnyPublisher, Error> { let query = Mastodon.API.DomainBlock.BlockQuery(domain: blockDomain) let request = Mastodon.API.post( url: domainBlockEndpointURL(domain: domain), @@ -63,7 +63,7 @@ extension Mastodon.API.DomainBlock { ) return session.dataTaskPublisher(for: request) .tryMap { data, response in - let value = try Mastodon.API.decode(type: String.self, from: data, response: response) + let value = try Mastodon.API.decode(type: Mastodon.Entity.Empty.self, from: data, response: response) return Mastodon.Response.Content(value: value, response: response) } .eraseToAnyPublisher() @@ -84,7 +84,7 @@ extension Mastodon.API.DomainBlock { blockDomain:String, session: URLSession, authorization: Mastodon.API.OAuth.Authorization - ) -> AnyPublisher, Error> { + ) -> AnyPublisher, Error> { let query = Mastodon.API.DomainBlock.BlockQuery(domain: blockDomain) let request = Mastodon.API.delete( url: domainBlockEndpointURL(domain: domain), @@ -93,7 +93,7 @@ extension Mastodon.API.DomainBlock { ) return session.dataTaskPublisher(for: request) .tryMap { data, response in - let value = try Mastodon.API.decode(type: String.self, from: data, response: response) + let value = try Mastodon.API.decode(type: Mastodon.Entity.Empty.self, from: data, response: response) return Mastodon.Response.Content(value: value, response: response) } .eraseToAnyPublisher() diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Empty.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Empty.swift new file mode 100644 index 000000000..93ee8ed37 --- /dev/null +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Empty.swift @@ -0,0 +1,15 @@ +// +// File.swift +// +// +// Created by sxiaojian on 2021/4/30. +// + +import Foundation + +extension Mastodon.Entity { + public struct Empty: Codable { + + } + +} From 8a5c62990e9447bbda7e650fdff2ee15f8bfbb25 Mon Sep 17 00:00:00 2001 From: sunxiaojian Date: Fri, 30 Apr 2021 14:55:02 +0800 Subject: [PATCH 07/10] chore: add BlockDomainService --- CoreDataStack/Entity/DomainBlock.swift | 6 +- CoreDataStack/Entity/MastodonUser.swift | 3 +- CoreDataStack/Entity/Status.swift | 2 +- .../Diffiable/Section/StatusSection.swift | 74 +++++++--------- Mastodon/Extension/CoreDataStack/Status.swift | 10 +-- .../Protocol/UserProvider/UserProvider.swift | 10 +-- .../UserProvider/UserProviderFacade.swift | 28 ++----- .../Scene/Profile/ProfileViewController.swift | 62 +++++++------- .../APIService/APIService+DomainBlock.swift | 34 +++----- Mastodon/Service/BlockDomainService.swift | 84 ++++++++++++------- Mastodon/State/AppContext.swift | 6 ++ .../API/Mastodon+API+DomainBlock.swift | 12 ++- .../MastodonSDK/API/Mastodon+API.swift | 8 -- .../Entity/Mastodon+Entity+Empty.swift | 1 - .../Sources/MastodonSDK/Query/Query.swift | 5 ++ 15 files changed, 170 insertions(+), 175 deletions(-) diff --git a/CoreDataStack/Entity/DomainBlock.swift b/CoreDataStack/Entity/DomainBlock.swift index 71fa7d461..3dd244c75 100644 --- a/CoreDataStack/Entity/DomainBlock.swift +++ b/CoreDataStack/Entity/DomainBlock.swift @@ -51,7 +51,7 @@ extension DomainBlock { static func predicate(userID: String) -> NSPredicate { NSPredicate(format: "%K == %@", #keyPath(DomainBlock.userID), userID) } - + static func predicate(blockedDomain: String) -> NSPredicate { NSPredicate(format: "%K == %@", #keyPath(DomainBlock.blockedDomain), blockedDomain) } @@ -62,12 +62,12 @@ extension DomainBlock { DomainBlock.predicate(userID: userID) ]) } - + public static func predicate(domain: String, userID: String, blockedDomain: String) -> NSPredicate { NSCompoundPredicate(andPredicateWithSubpredicates: [ DomainBlock.predicate(domain: domain), DomainBlock.predicate(userID: userID), - DomainBlock.predicate(blockedDomain:blockedDomain) + DomainBlock.predicate(blockedDomain: blockedDomain) ]) } } diff --git a/CoreDataStack/Entity/MastodonUser.swift b/CoreDataStack/Entity/MastodonUser.swift index c094bf4f6..714b6d0f6 100644 --- a/CoreDataStack/Entity/MastodonUser.swift +++ b/CoreDataStack/Entity/MastodonUser.swift @@ -333,7 +333,7 @@ extension MastodonUser: Managed { extension MastodonUser { - public static func predicate(domain: String) -> NSPredicate { + static func predicate(domain: String) -> NSPredicate { return NSPredicate(format: "%K == %@", #keyPath(MastodonUser.domain), domain) } @@ -369,4 +369,5 @@ extension MastodonUser { MastodonUser.predicate(username: username) ]) } + } diff --git a/CoreDataStack/Entity/Status.swift b/CoreDataStack/Entity/Status.swift index 73c704046..1bb71a1db 100644 --- a/CoreDataStack/Entity/Status.swift +++ b/CoreDataStack/Entity/Status.swift @@ -310,7 +310,7 @@ extension Status: Managed { extension Status { - public static func predicate(domain: String) -> NSPredicate { + static func predicate(domain: String) -> NSPredicate { return NSPredicate(format: "%K == %@", #keyPath(Status.domain), domain) } diff --git a/Mastodon/Diffiable/Section/StatusSection.swift b/Mastodon/Diffiable/Section/StatusSection.swift index 30d3bde4c..118b3dfe7 100644 --- a/Mastodon/Diffiable/Section/StatusSection.swift +++ b/Mastodon/Diffiable/Section/StatusSection.swift @@ -628,18 +628,18 @@ extension StatusSection { cell.statusView.actionToolbarContainer.favoriteButton.setTitle(favoriteCountTitle, for: .normal) cell.statusView.actionToolbarContainer.isFavoriteButtonHighlight = isLike - ManagedObjectObserver.observe(object: status.authorForUserProvider) - .receive(on: DispatchQueue.main) - .sink { _ in - // do nothing - } receiveValue: { [weak dependency, weak cell] change in - guard let cell = cell else { return } - guard let dependency = dependency else { return } - if case .update( _) = change.changeType { - StatusSection.setupStatusMoreButtonMenu(cell: cell, indexPath: indexPath, dependency: dependency, status: status) - } - } - .store(in: &cell.disposeBag) + Publishers.CombineLatest( + dependency.context.blockDomainService.blockedDomains, + ManagedObjectObserver.observe(object: status.authorForUserProvider) + .assertNoFailure() + ) + .receive(on: DispatchQueue.main) + .sink { [weak dependency, weak cell] domains,change in + guard let cell = cell else { return } + guard let dependency = dependency else { return } + StatusSection.setupStatusMoreButtonMenu(cell: cell, indexPath: indexPath, dependency: dependency, status: status) + } + .store(in: &cell.disposeBag) self.setupStatusMoreButtonMenu(cell: cell, indexPath: indexPath, dependency: dependency, status: status) } @@ -784,40 +784,22 @@ extension StatusSection { let isInSameDomain = authenticationBox.domain == author.domainFromAcct let isMuting = (author.mutingBy ?? Set()).map(\.id).contains(authenticationBox.userID) let isBlocking = (author.blockingBy ?? Set()).map(\.id).contains(authenticationBox.userID) - + let isDomainBlocking = dependency.context.blockDomainService.blockedDomains.value.contains(author.domainFromAcct) cell.statusView.actionToolbarContainer.moreButton.showsMenuAsPrimaryAction = true - let managedObjectContext = userProvider.context.backgroundManagedObjectContext - managedObjectContext.perform { - let blockedDomain: DomainBlock? = { - let request = DomainBlock.sortedFetchRequest - request.predicate = DomainBlock.predicate(domain: authenticationBox.domain, userID: authenticationBox.userID, blockedDomain: author.domainFromAcct) - request.fetchLimit = 1 - request.returnsObjectsAsFaults = false - do { - return try managedObjectContext.fetch(request).first - } catch { - assertionFailure(error.localizedDescription) - return nil - } - }() - let isDomainBlocking = blockedDomain != nil - DispatchQueue.main.async { - cell.statusView.actionToolbarContainer.moreButton.menu = UserProviderFacade.createProfileActionMenu( - for: author, - isMuting: isMuting, - isBlocking: isBlocking, - canReport: canReport, - isInSameDomain: isInSameDomain, - isDomainBlocking: isDomainBlocking, - provider: userProvider, - cell: cell, - indexPath: indexPath, - sourceView: cell.statusView.actionToolbarContainer.moreButton, - barButtonItem: nil, - shareUser: nil, - shareStatus: status - ) - } - } + cell.statusView.actionToolbarContainer.moreButton.menu = UserProviderFacade.createProfileActionMenu( + for: author, + isMuting: isMuting, + isBlocking: isBlocking, + canReport: canReport, + isInSameDomain: isInSameDomain, + isDomainBlocking: isDomainBlocking, + provider: userProvider, + cell: cell, + indexPath: indexPath, + sourceView: cell.statusView.actionToolbarContainer.moreButton, + barButtonItem: nil, + shareUser: nil, + shareStatus: status + ) } } diff --git a/Mastodon/Extension/CoreDataStack/Status.swift b/Mastodon/Extension/CoreDataStack/Status.swift index 5eec9f4aa..1a909285d 100644 --- a/Mastodon/Extension/CoreDataStack/Status.swift +++ b/Mastodon/Extension/CoreDataStack/Status.swift @@ -5,8 +5,8 @@ // Created by MainasuK Cirno on 2021/2/4. // -import Foundation import CoreDataStack +import Foundation import MastodonSDK extension Status.Property { @@ -34,7 +34,6 @@ extension Status.Property { } extension Status { - enum SensitiveType { case none case all @@ -61,7 +60,6 @@ extension Status { // not sensitive return .none } - } extension Status { @@ -72,10 +70,10 @@ extension Status { } extension Status { - var statusURL: URL { if let urlString = self.url, - let url = URL(string: urlString) { + let url = URL(string: urlString) + { return url } else { return URL(string: "https://\(self.domain)/web/statuses/\(self.id)")! @@ -84,7 +82,7 @@ extension Status { var activityItems: [Any] { var items: [Any] = [] - items.append(statusURL) + items.append(self.statusURL) return items } } diff --git a/Mastodon/Protocol/UserProvider/UserProvider.swift b/Mastodon/Protocol/UserProvider/UserProvider.swift index f3ee36c32..58c7ba5f7 100644 --- a/Mastodon/Protocol/UserProvider/UserProvider.swift +++ b/Mastodon/Protocol/UserProvider/UserProvider.swift @@ -5,21 +5,21 @@ // Created by MainasuK Cirno on 2021-4-1. // -import UIKit import Combine import CoreData import CoreDataStack +import UIKit protocol UserProvider: NeedsDependency & DisposeBagCollectable & UIViewController { // async func mastodonUser() -> Future - + func mastodonUser(for cell: UITableViewCell?, indexPath: IndexPath?) -> Future } extension UserProvider where Self: StatusProvider { func mastodonUser(for cell: UITableViewCell?, indexPath: IndexPath?) -> Future { - return Future { [weak self] promise in + Future { [weak self] promise in guard let self = self else { return } self.status(for: cell, indexPath: indexPath) .sink { status in @@ -28,9 +28,9 @@ extension UserProvider where Self: StatusProvider { .store(in: &self.disposeBag) } } - + func mastodonUser() -> Future { - return Future { promise in + Future { promise in promise(.success(nil)) } } diff --git a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift index 62cbf4ffc..dccfd4605 100644 --- a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift +++ b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift @@ -5,16 +5,15 @@ // Created by MainasuK Cirno on 2021-4-1. // -import UIKit import Combine import CoreData import CoreDataStack import MastodonSDK +import UIKit -enum UserProviderFacade { } +enum UserProviderFacade {} extension UserProviderFacade { - static func toggleUserFollowRelationship( provider: UserProvider ) -> AnyPublisher, Error> { @@ -50,11 +49,9 @@ extension UserProviderFacade { .switchToLatest() .eraseToAnyPublisher() } - } extension UserProviderFacade { - static func toggleUserBlockRelationship( provider: UserProvider, cell: UITableViewCell?, @@ -99,11 +96,9 @@ extension UserProviderFacade { .switchToLatest() .eraseToAnyPublisher() } - } extension UserProviderFacade { - static func toggleUserMuteRelationship( provider: UserProvider, cell: UITableViewCell?, @@ -148,11 +143,9 @@ extension UserProviderFacade { .switchToLatest() .eraseToAnyPublisher() } - } extension UserProviderFacade { - static func createProfileActionMenu( for mastodonUser: MastodonUser, isMuting: Bool, @@ -238,7 +231,8 @@ extension UserProviderFacade { context: provider.context, domain: authenticationBox.domain, user: mastodonUser, - status: nil) + status: nil + ) provider.coordinator.present( scene: .report(viewModel: viewModel), from: provider, @@ -252,11 +246,7 @@ extension UserProviderFacade { if isDomainBlocking { let unblockDomainAction = UIAction(title: L10n.Common.Controls.Actions.unblockDomain(mastodonUser.domainFromAcct), image: UIImage(systemName: "nosign"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in guard let provider = provider else { return } - BlockDomainService(userProvider: provider, - cell: cell, - indexPath: indexPath - ) - .unblockDomain() + provider.context.blockDomainService.unblockDomain(userProvider: provider, cell: cell, indexPath: indexPath) } children.append(unblockDomainAction) } else { @@ -264,15 +254,10 @@ extension UserProviderFacade { guard let provider = provider else { return } let alertController = UIAlertController(title: "", message: L10n.Common.Alerts.BlockDomain.message(mastodonUser.domainFromAcct), preferredStyle: .alert) let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .default) { _ in - } alertController.addAction(cancelAction) let blockDomainAction = UIAlertAction(title: L10n.Common.Alerts.BlockDomain.blockEntireDomain, style: .destructive) { _ in - BlockDomainService(userProvider: provider, - cell: cell, - indexPath: indexPath - ) - .blockDomain() + provider.context.blockDomainService.blockDomain(userProvider: provider, cell: cell, indexPath: indexPath) } alertController.addAction(blockDomainAction) provider.present(alertController, animated: true, completion: nil) @@ -333,5 +318,4 @@ extension UserProviderFacade { ) return activityViewController } - } diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index 1a802712a..b4d52d750 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -366,36 +366,40 @@ extension ProfileViewController { .receive(on: DispatchQueue.main) .assign(to: \.text, on: profileHeaderViewController.profileHeaderView.usernameLabel) .store(in: &disposeBag) - viewModel.relationshipActionOptionSet - .receive(on: DispatchQueue.main) - .sink { [weak self] relationshipActionOptionSet in - guard let self = self else { return } - guard let mastodonUser = self.viewModel.mastodonUser.value else { - self.moreMenuBarButtonItem.menu = nil - return - } - guard let currentDomain = self.viewModel.domain.value else { return } - let isMuting = relationshipActionOptionSet.contains(.muting) - let isBlocking = relationshipActionOptionSet.contains(.blocking) - let isDomainBlocking = relationshipActionOptionSet.contains(.domainBlocking) - let needsShareAction = self.viewModel.isMeBarButtonItemsHidden.value - let isInSameDomain = mastodonUser.domainFromAcct == currentDomain - self.moreMenuBarButtonItem.menu = UserProviderFacade.createProfileActionMenu( - for: mastodonUser, - isMuting: isMuting, - isBlocking: isBlocking, - canReport: true, - isInSameDomain: isInSameDomain, - isDomainBlocking: isDomainBlocking, - provider: self, - cell: nil, - indexPath: nil, - sourceView: nil, - barButtonItem: self.moreMenuBarButtonItem, - shareUser: needsShareAction ? mastodonUser : nil, - shareStatus: nil) + Publishers.CombineLatest( + viewModel.relationshipActionOptionSet, + viewModel.context.blockDomainService.blockedDomains + ) + .receive(on: DispatchQueue.main) + .sink { [weak self] relationshipActionOptionSet,domains in + guard let self = self else { return } + guard let mastodonUser = self.viewModel.mastodonUser.value else { + self.moreMenuBarButtonItem.menu = nil + return } - .store(in: &disposeBag) + guard let currentDomain = self.viewModel.domain.value else { return } + let isMuting = relationshipActionOptionSet.contains(.muting) + let isBlocking = relationshipActionOptionSet.contains(.blocking) + let isDomainBlocking = domains.contains(mastodonUser.domainFromAcct) + let needsShareAction = self.viewModel.isMeBarButtonItemsHidden.value + let isInSameDomain = mastodonUser.domainFromAcct == currentDomain + self.moreMenuBarButtonItem.menu = UserProviderFacade.createProfileActionMenu( + for: mastodonUser, + isMuting: isMuting, + isBlocking: isBlocking, + canReport: true, + isInSameDomain: isInSameDomain, + isDomainBlocking: isDomainBlocking, + provider: self, + cell: nil, + indexPath: nil, + sourceView: nil, + barButtonItem: self.moreMenuBarButtonItem, + shareUser: needsShareAction ? mastodonUser : nil, + shareStatus: nil) + } + .store(in: &disposeBag) + viewModel.isRelationshipActionButtonHidden .receive(on: DispatchQueue.main) .sink { [weak self] isHidden in diff --git a/Mastodon/Service/APIService/APIService+DomainBlock.swift b/Mastodon/Service/APIService/APIService+DomainBlock.swift index 7d9936c5a..887c3f074 100644 --- a/Mastodon/Service/APIService/APIService+DomainBlock.swift +++ b/Mastodon/Service/APIService/APIService+DomainBlock.swift @@ -32,6 +32,19 @@ extension APIService { ) .flatMap { response -> AnyPublisher, Error> in self.backgroundManagedObjectContext.performChanges { + let blockedDomains: [DomainBlock] = { + let request = DomainBlock.sortedFetchRequest + request.predicate = DomainBlock.predicate(domain: authorizationBox.domain, userID: authorizationBox.userID) + request.returnsObjectsAsFaults = false + do { + return try self.backgroundManagedObjectContext.fetch(request) + } catch { + assertionFailure(error.localizedDescription) + return [] + } + }() + blockedDomains.forEach { self.backgroundManagedObjectContext.delete($0) } + response.value.forEach { domain in // use constrain to avoid repeated save _ = DomainBlock.insert( @@ -74,12 +87,6 @@ extension APIService { requestMastodonUserRequest.predicate = MastodonUser.predicate(domain: authorizationBox.domain, id: authorizationBox.userID) requestMastodonUserRequest.fetchLimit = 1 guard let requestMastodonUser = self.backgroundManagedObjectContext.safeFetch(requestMastodonUserRequest).first else { return } - _ = DomainBlock.insert( - into: self.backgroundManagedObjectContext, - blockedDomain: user.domainFromAcct, - domain: authorizationBox.domain, - userID: authorizationBox.userID - ) user.update(isDomainBlocking: true, by: requestMastodonUser) } .setFailureType(to: Error.self) @@ -110,21 +117,6 @@ extension APIService { ) .flatMap { response -> AnyPublisher, Error> in self.backgroundManagedObjectContext.performChanges { - let blockedDomain: DomainBlock? = { - let request = DomainBlock.sortedFetchRequest - request.predicate = DomainBlock.predicate(domain: authorizationBox.domain, userID: authorizationBox.userID, blockedDomain: user.domainFromAcct) - request.fetchLimit = 1 - request.returnsObjectsAsFaults = false - do { - return try self.backgroundManagedObjectContext.fetch(request).first - } catch { - assertionFailure(error.localizedDescription) - return nil - } - }() - if let blockedDomain = blockedDomain { - self.backgroundManagedObjectContext.delete(blockedDomain) - } let requestMastodonUserRequest = MastodonUser.sortedFetchRequest requestMastodonUserRequest.predicate = MastodonUser.predicate(domain: authorizationBox.domain, id: authorizationBox.userID) requestMastodonUserRequest.fetchLimit = 1 diff --git a/Mastodon/Service/BlockDomainService.swift b/Mastodon/Service/BlockDomainService.swift index 0cc35f0d2..217f1af1f 100644 --- a/Mastodon/Service/BlockDomainService.swift +++ b/Mastodon/Service/BlockDomainService.swift @@ -5,34 +5,56 @@ // Created by sxiaojian on 2021/4/29. // +import Combine import CoreData import CoreDataStack import Foundation -import Combine import MastodonSDK import OSLog import UIKit final class BlockDomainService { - let userProvider: UserProvider - let cell: UITableViewCell? - let indexPath: IndexPath? - init(userProvider: UserProvider, - cell: UITableViewCell?, - indexPath: IndexPath? + // input + weak var backgroundManagedObjectContext: NSManagedObjectContext? + weak var authenticationService: AuthenticationService? + + // output + let blockedDomains = CurrentValueSubject<[String], Never>([]) + + init( + backgroundManagedObjectContext: NSManagedObjectContext, + authenticationService: AuthenticationService ) { - self.userProvider = userProvider - self.cell = cell - self.indexPath = indexPath + self.backgroundManagedObjectContext = backgroundManagedObjectContext + self.authenticationService = authenticationService + guard let authorizationBox = authenticationService.activeMastodonAuthenticationBox.value else { return } + backgroundManagedObjectContext.perform { + let _blockedDomains: [DomainBlock] = { + let request = DomainBlock.sortedFetchRequest + request.predicate = DomainBlock.predicate(domain: authorizationBox.domain, userID: authorizationBox.userID) + request.returnsObjectsAsFaults = false + do { + return try backgroundManagedObjectContext.fetch(request) + } catch { + assertionFailure(error.localizedDescription) + return [] + } + }() + self.blockedDomains.value = _blockedDomains.map(\.blockedDomain) + } } - func blockDomain() { - guard let activeMastodonAuthenticationBox = self.userProvider.context.authenticationService.activeMastodonAuthenticationBox.value else { return } - guard let context = self.userProvider.context else { + func blockDomain( + userProvider: UserProvider, + cell: UITableViewCell?, + indexPath: IndexPath? + ) { + guard let activeMastodonAuthenticationBox = userProvider.context.authenticationService.activeMastodonAuthenticationBox.value else { return } + guard let context = userProvider.context else { return } var mastodonUser: AnyPublisher - if let cell = self.cell, let indexPath = self.indexPath { + if let cell = cell, let indexPath = indexPath { mastodonUser = userProvider.mastodonUser(for: cell, indexPath: indexPath).eraseToAnyPublisher() } else { mastodonUser = userProvider.mastodonUser().eraseToAnyPublisher() @@ -45,8 +67,8 @@ final class BlockDomainService { return context.apiService.blockDomain(user: mastodonUser, authorizationBox: activeMastodonAuthenticationBox) } .switchToLatest() - .flatMap { response -> AnyPublisher, Error> in - return context.apiService.getDomainblocks(domain: activeMastodonAuthenticationBox.domain, authorizationBox: activeMastodonAuthenticationBox) + .flatMap { _ -> AnyPublisher, Error> in + context.apiService.getDomainblocks(domain: activeMastodonAuthenticationBox.domain, authorizationBox: activeMastodonAuthenticationBox) } .sink { completion in switch completion { @@ -55,19 +77,23 @@ final class BlockDomainService { case .failure(let error): print(error) } - } receiveValue: { response in - print(response) + } receiveValue: { [weak self] response in + self?.blockedDomains.value = response.value } .store(in: &userProvider.disposeBag) } - - func unblockDomain() { - guard let activeMastodonAuthenticationBox = self.userProvider.context.authenticationService.activeMastodonAuthenticationBox.value else { return } - guard let context = self.userProvider.context else { + + func unblockDomain( + userProvider: UserProvider, + cell: UITableViewCell?, + indexPath: IndexPath? + ) { + guard let activeMastodonAuthenticationBox = userProvider.context.authenticationService.activeMastodonAuthenticationBox.value else { return } + guard let context = userProvider.context else { return } var mastodonUser: AnyPublisher - if let cell = self.cell, let indexPath = self.indexPath { + if let cell = cell, let indexPath = indexPath { mastodonUser = userProvider.mastodonUser(for: cell, indexPath: indexPath).eraseToAnyPublisher() } else { mastodonUser = userProvider.mastodonUser().eraseToAnyPublisher() @@ -80,8 +106,8 @@ final class BlockDomainService { return context.apiService.unblockDomain(user: mastodonUser, authorizationBox: activeMastodonAuthenticationBox) } .switchToLatest() - .flatMap { response -> AnyPublisher, Error> in - return context.apiService.getDomainblocks(domain: activeMastodonAuthenticationBox.domain, authorizationBox: activeMastodonAuthenticationBox) + .flatMap { _ -> AnyPublisher, Error> in + context.apiService.getDomainblocks(domain: activeMastodonAuthenticationBox.domain, authorizationBox: activeMastodonAuthenticationBox) } .sink { completion in switch completion { @@ -90,13 +116,9 @@ final class BlockDomainService { case .failure(let error): print(error) } - } receiveValue: { response in - print(response) + } receiveValue: { [weak self] response in + self?.blockedDomains.value = response.value } .store(in: &userProvider.disposeBag) } - - deinit { - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", (#file as NSString).lastPathComponent, #line, #function) - } } diff --git a/Mastodon/State/AppContext.swift b/Mastodon/State/AppContext.swift index 93287f6eb..962bc7928 100644 --- a/Mastodon/State/AppContext.swift +++ b/Mastodon/State/AppContext.swift @@ -30,6 +30,7 @@ class AppContext: ObservableObject { let statusPublishService = StatusPublishService() let notificationService: NotificationService let settingService: SettingService + let blockDomainService: BlockDomainService let documentStore: DocumentStore private var documentStoreSubscription: AnyCancellable! @@ -72,6 +73,11 @@ class AppContext: ObservableObject { notificationService: _notificationService ) + blockDomainService = BlockDomainService( + backgroundManagedObjectContext: _backgroundManagedObjectContext, + authenticationService: _authenticationService + ) + documentStore = DocumentStore() documentStoreSubscription = documentStore.objectWillChange .receive(on: DispatchQueue.main) diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+DomainBlock.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+DomainBlock.swift index 356ac68a0..04ed813ab 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+DomainBlock.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+DomainBlock.swift @@ -85,7 +85,7 @@ extension Mastodon.API.DomainBlock { session: URLSession, authorization: Mastodon.API.OAuth.Authorization ) -> AnyPublisher, Error> { - let query = Mastodon.API.DomainBlock.BlockQuery(domain: blockDomain) + let query = Mastodon.API.DomainBlock.BlockDeleteQuery(domain: blockDomain) let request = Mastodon.API.delete( url: domainBlockEndpointURL(domain: domain), query: query, @@ -126,7 +126,17 @@ extension Mastodon.API.DomainBlock { } } + public struct BlockDeleteQuery: Codable, DeleteQuery { + + public let domain: String + + public init(domain: String) { + self.domain = domain + } + } + public struct BlockQuery: Codable, PostQuery { + public let domain: String public init(domain: String) { diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift index dcc666472..e202568c5 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift @@ -142,14 +142,6 @@ extension Mastodon.API { ) -> URLRequest { return buildRequest(url: url, method: .POST, query: query, authorization: authorization) } - - static func delete( - url: URL, - query: PostQuery?, - authorization: OAuth.Authorization? - ) -> URLRequest { - return buildRequest(url: url, method: .DELETE, query: query, authorization: authorization) - } static func patch( url: URL, diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Empty.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Empty.swift index 93ee8ed37..494151178 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Empty.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Empty.swift @@ -11,5 +11,4 @@ extension Mastodon.Entity { public struct Empty: Codable { } - } diff --git a/MastodonSDK/Sources/MastodonSDK/Query/Query.swift b/MastodonSDK/Sources/MastodonSDK/Query/Query.swift index b729129bd..7fbe0da9e 100644 --- a/MastodonSDK/Sources/MastodonSDK/Query/Query.swift +++ b/MastodonSDK/Sources/MastodonSDK/Query/Query.swift @@ -60,3 +60,8 @@ protocol PutQuery: RequestQuery { } // DELETE protocol DeleteQuery: RequestQuery { } + +extension DeleteQuery { + // By default a `PostQuery` does not has query items + var queryItems: [URLQueryItem]? { nil } +} From 514e5b0443c692a214575c615713b7b24a7ccbc5 Mon Sep 17 00:00:00 2001 From: sunxiaojian Date: Fri, 30 Apr 2021 15:38:15 +0800 Subject: [PATCH 08/10] chore: remove useless change --- Localization/app.json | 2 +- Mastodon/Diffiable/Section/StatusSection.swift | 10 +++++++++- Mastodon/Generated/Strings.swift | 6 ++++-- Mastodon/Resources/en.lproj/Localizable.strings | 2 +- Mastodon/Scene/Profile/ProfileViewModel.swift | 4 ---- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Localization/app.json b/Localization/app.json index 9c2dbef19..85c1b10ea 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -92,7 +92,7 @@ "pending": "Pending", "block": "Block", "block_user": "Block %s", - "block_domain": "Domain Blocked", + "block_domain": "Block %s", "unblock": "Unblock", "unblock_user": "Unblock %s", "blocked": "Blocked", diff --git a/Mastodon/Diffiable/Section/StatusSection.swift b/Mastodon/Diffiable/Section/StatusSection.swift index 118b3dfe7..f31c2da89 100644 --- a/Mastodon/Diffiable/Section/StatusSection.swift +++ b/Mastodon/Diffiable/Section/StatusSection.swift @@ -634,9 +634,17 @@ extension StatusSection { .assertNoFailure() ) .receive(on: DispatchQueue.main) - .sink { [weak dependency, weak cell] domains,change in + .sink { [weak dependency, weak cell] _,change in guard let cell = cell else { return } guard let dependency = dependency else { return } + switch change.changeType { + case .delete: + return + case .update(_): + break + case .none: + break + } StatusSection.setupStatusMoreButtonMenu(cell: cell, indexPath: indexPath, dependency: dependency, status: status) } .store(in: &cell.disposeBag) diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index 7a1d88d6e..b06a0d68d 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -132,8 +132,10 @@ internal enum L10n { internal enum Firendship { /// Block internal static let block = L10n.tr("Localizable", "Common.Controls.Firendship.Block") - /// Domain Blocked - internal static let blockDomain = L10n.tr("Localizable", "Common.Controls.Firendship.BlockDomain") + /// Block %@ + internal static func blockDomain(_ p1: Any) -> String { + return L10n.tr("Localizable", "Common.Controls.Firendship.BlockDomain", String(describing: p1)) + } /// Blocked internal static let blocked = L10n.tr("Localizable", "Common.Controls.Firendship.Blocked") /// Block %@ diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index 234f441bd..e3ed6cf0c 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -43,7 +43,7 @@ Please check your internet connection."; "Common.Controls.Actions.TryAgain" = "Try Again"; "Common.Controls.Actions.UnblockDomain" = "Unblock %@"; "Common.Controls.Firendship.Block" = "Block"; -"Common.Controls.Firendship.BlockDomain" = "Domain Blocked"; +"Common.Controls.Firendship.BlockDomain" = "Block %@"; "Common.Controls.Firendship.BlockUser" = "Block %@"; "Common.Controls.Firendship.Blocked" = "Blocked"; "Common.Controls.Firendship.EditInfo" = "Edit info"; diff --git a/Mastodon/Scene/Profile/ProfileViewModel.swift b/Mastodon/Scene/Profile/ProfileViewModel.swift index c19ae2f63..445952e96 100644 --- a/Mastodon/Scene/Profile/ProfileViewModel.swift +++ b/Mastodon/Scene/Profile/ProfileViewModel.swift @@ -327,7 +327,6 @@ extension ProfileViewModel { case muting case blocked case blocking - case domainBlocking case suspended case edit case editing @@ -350,7 +349,6 @@ extension ProfileViewModel { static let muting = RelationshipAction.muting.option static let blocked = RelationshipAction.blocked.option static let blocking = RelationshipAction.blocking.option - static let domainBlocking = RelationshipAction.domainBlocking.option static let suspended = RelationshipAction.suspended.option static let edit = RelationshipAction.edit.option static let editing = RelationshipAction.editing.option @@ -381,7 +379,6 @@ extension ProfileViewModel { case .muting: return L10n.Common.Controls.Firendship.muted case .blocked: return L10n.Common.Controls.Firendship.follow // blocked by user case .blocking: return L10n.Common.Controls.Firendship.blocked - case .domainBlocking: return L10n.Common.Controls.Firendship.blockDomain case .suspended: return L10n.Common.Controls.Firendship.follow case .edit: return L10n.Common.Controls.Firendship.editInfo case .editing: return L10n.Common.Controls.Actions.done @@ -403,7 +400,6 @@ extension ProfileViewModel { case .muting: return Asset.Colors.Background.alertYellow.color case .blocked: return Asset.Colors.Button.normal.color case .blocking: return Asset.Colors.Background.danger.color - case .domainBlocking: return Asset.Colors.Button.normal.color case .suspended: return Asset.Colors.Button.normal.color case .edit: return Asset.Colors.Button.normal.color case .editing: return Asset.Colors.Button.normal.color From 801129857167dc545af5d33d2f1fb31605c2c4ca Mon Sep 17 00:00:00 2001 From: sunxiaojian Date: Thu, 6 May 2021 18:03:58 +0800 Subject: [PATCH 09/10] fix: ignore indexPath for userProvider --- .../Section/NotificationSection.swift | 1 - .../Diffiable/Section/ReportSection.swift | 1 - .../Diffiable/Section/StatusSection.swift | 12 ++------- .../StatusProvider+UITableViewDelegate.swift | 4 +-- .../Protocol/UserProvider/UserProvider.swift | 6 ++--- .../UserProvider/UserProviderFacade.swift | 25 ++++++++----------- .../ProfileViewController+UserProvider.swift | 2 +- .../Scene/Profile/ProfileViewController.swift | 5 ++-- .../Search/SearchViewController+Follow.swift | 6 ++--- Mastodon/Service/BlockDomainService.swift | 14 +++++------ 10 files changed, 29 insertions(+), 47 deletions(-) diff --git a/Mastodon/Diffiable/Section/NotificationSection.swift b/Mastodon/Diffiable/Section/NotificationSection.swift index 558f94dd9..ead5d48f8 100644 --- a/Mastodon/Diffiable/Section/NotificationSection.swift +++ b/Mastodon/Diffiable/Section/NotificationSection.swift @@ -50,7 +50,6 @@ extension NotificationSection { let frame = CGRect(x: 0, y: 0, width: tableView.readableContentGuide.layoutFrame.width - NotificationStatusTableViewCell.statusPadding.left - NotificationStatusTableViewCell.statusPadding.right, height: tableView.readableContentGuide.layoutFrame.height) StatusSection.configure( cell: cell, - indexPath: indexPath, dependency: dependency, readableLayoutFrame: frame, timestampUpdatePublisher: timestampUpdatePublisher, diff --git a/Mastodon/Diffiable/Section/ReportSection.swift b/Mastodon/Diffiable/Section/ReportSection.swift index 07321be96..6faaae6c2 100644 --- a/Mastodon/Diffiable/Section/ReportSection.swift +++ b/Mastodon/Diffiable/Section/ReportSection.swift @@ -41,7 +41,6 @@ extension ReportSection { let status = managedObjectContext.object(with: objectID) as! Status StatusSection.configure( cell: cell, - indexPath: indexPath, dependency: dependency, readableLayoutFrame: tableView.readableContentGuide.layoutFrame, timestampUpdatePublisher: timestampUpdatePublisher, diff --git a/Mastodon/Diffiable/Section/StatusSection.swift b/Mastodon/Diffiable/Section/StatusSection.swift index b5aadbf16..cb38ec8ab 100644 --- a/Mastodon/Diffiable/Section/StatusSection.swift +++ b/Mastodon/Diffiable/Section/StatusSection.swift @@ -49,7 +49,6 @@ extension StatusSection { let timelineIndex = managedObjectContext.object(with: objectID) as! HomeTimelineIndex StatusSection.configure( cell: cell, - indexPath: indexPath, dependency: dependency, readableLayoutFrame: tableView.readableContentGuide.layoutFrame, timestampUpdatePublisher: timestampUpdatePublisher, @@ -72,7 +71,6 @@ extension StatusSection { let status = managedObjectContext.object(with: objectID) as! Status StatusSection.configure( cell: cell, - indexPath: indexPath, dependency: dependency, readableLayoutFrame: tableView.readableContentGuide.layoutFrame, timestampUpdatePublisher: timestampUpdatePublisher, @@ -138,7 +136,6 @@ extension StatusSection { static func configure( cell: StatusCell, - indexPath: IndexPath, dependency: NeedsDependency, readableLayoutFrame: CGRect?, timestampUpdatePublisher: AnyPublisher, @@ -407,7 +404,6 @@ extension StatusSection { // toolbar StatusSection.configureActionToolBar( cell: statusTableViewCell, - indexPath: indexPath, dependency: dependency, status: status, requestUserID: requestUserID @@ -438,7 +434,6 @@ extension StatusSection { guard let statusTableViewCell = cell as? StatusTableViewCell else { return } StatusSection.configureActionToolBar( cell: statusTableViewCell, - indexPath: indexPath, dependency: dependency, status: status, requestUserID: requestUserID @@ -597,7 +592,6 @@ extension StatusSection { static func configureActionToolBar( cell: StatusTableViewCell, - indexPath: IndexPath, dependency: NeedsDependency, status: Status, requestUserID: String @@ -644,10 +638,10 @@ extension StatusSection { case .none: break } - StatusSection.setupStatusMoreButtonMenu(cell: cell, indexPath: indexPath, dependency: dependency, status: status) + StatusSection.setupStatusMoreButtonMenu(cell: cell, dependency: dependency, status: status) } .store(in: &cell.disposeBag) - self.setupStatusMoreButtonMenu(cell: cell, indexPath: indexPath, dependency: dependency, status: status) + self.setupStatusMoreButtonMenu(cell: cell, dependency: dependency, status: status) } static func configurePoll( @@ -777,7 +771,6 @@ extension StatusSection { private static func setupStatusMoreButtonMenu( cell: StatusTableViewCell, - indexPath: IndexPath, dependency: NeedsDependency, status: Status) { @@ -802,7 +795,6 @@ extension StatusSection { isDomainBlocking: isDomainBlocking, provider: userProvider, cell: cell, - indexPath: indexPath, sourceView: cell.statusView.actionToolbarContainer.moreButton, barButtonItem: nil, shareUser: nil, diff --git a/Mastodon/Protocol/StatusProvider/StatusProvider+UITableViewDelegate.swift b/Mastodon/Protocol/StatusProvider/StatusProvider+UITableViewDelegate.swift index 46e4c5ab5..98fa2d2cd 100644 --- a/Mastodon/Protocol/StatusProvider/StatusProvider+UITableViewDelegate.swift +++ b/Mastodon/Protocol/StatusProvider/StatusProvider+UITableViewDelegate.swift @@ -114,7 +114,7 @@ extension StatusTableViewCellDelegate where Self: StatusProvider { guard let imagePreviewPresentableCell = tableView.cellForRow(at: indexPath) as? ImagePreviewPresentableCell else { return nil } guard imagePreviewPresentableCell.isRevealing else { return nil } - let status = status(for: nil, indexPath: indexPath) + let status = self.status(for: nil, indexPath: indexPath) return contextMenuConfiguration(tableView, status: status, imagePreviewPresentableCell: imagePreviewPresentableCell, contextMenuConfigurationForRowAt: indexPath, point: point) } @@ -260,7 +260,7 @@ extension StatusTableViewCellDelegate where Self: StatusProvider { guard index < imageViews.count else { return } let imageView = imageViews[index] - let status = status(for: nil, indexPath: indexPath) + let status = self.status(for: nil, indexPath: indexPath) let initialFrame: CGRect? = { guard let previewViewController = animator.previewViewController else { return nil } return UIView.findContextMenuPreviewFrameInWindow(previewController: previewViewController) diff --git a/Mastodon/Protocol/UserProvider/UserProvider.swift b/Mastodon/Protocol/UserProvider/UserProvider.swift index 58c7ba5f7..f9939c740 100644 --- a/Mastodon/Protocol/UserProvider/UserProvider.swift +++ b/Mastodon/Protocol/UserProvider/UserProvider.swift @@ -14,14 +14,14 @@ protocol UserProvider: NeedsDependency & DisposeBagCollectable & UIViewControlle // async func mastodonUser() -> Future - func mastodonUser(for cell: UITableViewCell?, indexPath: IndexPath?) -> Future + func mastodonUser(for cell: UITableViewCell?) -> Future } extension UserProvider where Self: StatusProvider { - func mastodonUser(for cell: UITableViewCell?, indexPath: IndexPath?) -> Future { + func mastodonUser(for cell: UITableViewCell?) -> Future { Future { [weak self] promise in guard let self = self else { return } - self.status(for: cell, indexPath: indexPath) + self.status(for: cell, indexPath: nil) .sink { status in promise(.success(status?.authorForUserProvider)) } diff --git a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift index dccfd4605..2aeff0e09 100644 --- a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift +++ b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift @@ -54,19 +54,18 @@ extension UserProviderFacade { extension UserProviderFacade { static func toggleUserBlockRelationship( provider: UserProvider, - cell: UITableViewCell?, - indexPath: IndexPath? + cell: UITableViewCell? ) -> AnyPublisher, Error> { // prepare authentication guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else { assertionFailure() return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher() } - if let cell = cell, let indexPath = indexPath { + if let cell = cell { return _toggleUserBlockRelationship( context: provider.context, activeMastodonAuthenticationBox: activeMastodonAuthenticationBox, - mastodonUser: provider.mastodonUser(for: cell, indexPath: indexPath).eraseToAnyPublisher() + mastodonUser: provider.mastodonUser(for: cell).eraseToAnyPublisher() ) } else { return _toggleUserBlockRelationship( @@ -101,19 +100,18 @@ extension UserProviderFacade { extension UserProviderFacade { static func toggleUserMuteRelationship( provider: UserProvider, - cell: UITableViewCell?, - indexPath: IndexPath? + cell: UITableViewCell? ) -> AnyPublisher, Error> { // prepare authentication guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else { assertionFailure() return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher() } - if let cell = cell, let indexPath = indexPath { + if let cell = cell { return _toggleUserMuteRelationship( context: provider.context, activeMastodonAuthenticationBox: activeMastodonAuthenticationBox, - mastodonUser: provider.mastodonUser(for: cell, indexPath: indexPath).eraseToAnyPublisher() + mastodonUser: provider.mastodonUser(for: cell).eraseToAnyPublisher() ) } else { return _toggleUserMuteRelationship( @@ -155,7 +153,6 @@ extension UserProviderFacade { isDomainBlocking: Bool, provider: UserProvider, cell: UITableViewCell?, - indexPath: IndexPath?, sourceView: UIView?, barButtonItem: UIBarButtonItem?, shareUser: MastodonUser?, @@ -176,8 +173,7 @@ extension UserProviderFacade { UserProviderFacade.toggleUserMuteRelationship( provider: provider, - cell: cell, - indexPath: indexPath + cell: cell ) .sink { _ in // do nothing @@ -205,8 +201,7 @@ extension UserProviderFacade { UserProviderFacade.toggleUserBlockRelationship( provider: provider, - cell: cell, - indexPath: indexPath + cell: cell ) .sink { _ in // do nothing @@ -246,7 +241,7 @@ extension UserProviderFacade { if isDomainBlocking { let unblockDomainAction = UIAction(title: L10n.Common.Controls.Actions.unblockDomain(mastodonUser.domainFromAcct), image: UIImage(systemName: "nosign"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in guard let provider = provider else { return } - provider.context.blockDomainService.unblockDomain(userProvider: provider, cell: cell, indexPath: indexPath) + provider.context.blockDomainService.unblockDomain(userProvider: provider, cell: cell) } children.append(unblockDomainAction) } else { @@ -257,7 +252,7 @@ extension UserProviderFacade { } alertController.addAction(cancelAction) let blockDomainAction = UIAlertAction(title: L10n.Common.Alerts.BlockDomain.blockEntireDomain, style: .destructive) { _ in - provider.context.blockDomainService.blockDomain(userProvider: provider, cell: cell, indexPath: indexPath) + provider.context.blockDomainService.blockDomain(userProvider: provider, cell: cell) } alertController.addAction(blockDomainAction) provider.present(alertController, animated: true, completion: nil) diff --git a/Mastodon/Scene/Profile/ProfileViewController+UserProvider.swift b/Mastodon/Scene/Profile/ProfileViewController+UserProvider.swift index be124a0c2..6bfa132b8 100644 --- a/Mastodon/Scene/Profile/ProfileViewController+UserProvider.swift +++ b/Mastodon/Scene/Profile/ProfileViewController+UserProvider.swift @@ -11,7 +11,7 @@ import CoreDataStack import UIKit extension ProfileViewController: UserProvider { - func mastodonUser(for cell: UITableViewCell?, indexPath: IndexPath?) -> Future { + func mastodonUser(for cell: UITableViewCell?) -> Future { return Future { promise in promise(.success(nil)) } diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index 5d819c6b3..521df753b 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -399,7 +399,6 @@ extension ProfileViewController { isDomainBlocking: isDomainBlocking, provider: self, cell: nil, - indexPath: nil, sourceView: nil, barButtonItem: self.moreMenuBarButtonItem, shareUser: needsShareAction ? mastodonUser : nil, @@ -787,7 +786,7 @@ extension ProfileViewController: ProfileHeaderViewDelegate { ) let unmuteAction = UIAlertAction(title: L10n.Common.Controls.Firendship.unmute, style: .default) { [weak self] _ in guard let self = self else { return } - UserProviderFacade.toggleUserMuteRelationship(provider: self, cell: nil, indexPath: nil) + UserProviderFacade.toggleUserMuteRelationship(provider: self, cell: nil) .sink { _ in // do nothing } receiveValue: { _ in @@ -809,7 +808,7 @@ extension ProfileViewController: ProfileHeaderViewDelegate { ) let unblockAction = UIAlertAction(title: L10n.Common.Controls.Firendship.unblock, style: .default) { [weak self] _ in guard let self = self else { return } - UserProviderFacade.toggleUserBlockRelationship(provider: self, cell: nil, indexPath: nil) + UserProviderFacade.toggleUserBlockRelationship(provider: self, cell: nil) .sink { _ in // do nothing } receiveValue: { _ in diff --git a/Mastodon/Scene/Search/SearchViewController+Follow.swift b/Mastodon/Scene/Search/SearchViewController+Follow.swift index 6682d846b..a7f7faf90 100644 --- a/Mastodon/Scene/Search/SearchViewController+Follow.swift +++ b/Mastodon/Scene/Search/SearchViewController+Follow.swift @@ -12,7 +12,7 @@ import UIKit extension SearchViewController: UserProvider { - func mastodonUser(for cell: UITableViewCell?, indexPath: IndexPath?) -> Future { + func mastodonUser(for cell: UITableViewCell?) -> Future { return Future { promise in promise(.success(nil)) } @@ -54,7 +54,7 @@ extension SearchViewController: SearchRecommendAccountsCollectionViewCellDelegat ) let unmuteAction = UIAlertAction(title: L10n.Common.Controls.Firendship.unmute, style: .default) { [weak self] _ in guard let self = self else { return } - UserProviderFacade.toggleUserMuteRelationship(provider: self, cell: nil, indexPath: nil) + UserProviderFacade.toggleUserMuteRelationship(provider: self, cell: nil) .sink { _ in // do nothing } receiveValue: { _ in @@ -76,7 +76,7 @@ extension SearchViewController: SearchRecommendAccountsCollectionViewCellDelegat ) let unblockAction = UIAlertAction(title: L10n.Common.Controls.Firendship.unblock, style: .default) { [weak self] _ in guard let self = self else { return } - UserProviderFacade.toggleUserBlockRelationship(provider: self, cell: nil, indexPath: nil) + UserProviderFacade.toggleUserBlockRelationship(provider: self, cell: nil) .sink { _ in // do nothing } receiveValue: { _ in diff --git a/Mastodon/Service/BlockDomainService.swift b/Mastodon/Service/BlockDomainService.swift index 217f1af1f..036083e60 100644 --- a/Mastodon/Service/BlockDomainService.swift +++ b/Mastodon/Service/BlockDomainService.swift @@ -46,16 +46,15 @@ final class BlockDomainService { func blockDomain( userProvider: UserProvider, - cell: UITableViewCell?, - indexPath: IndexPath? + cell: UITableViewCell? ) { guard let activeMastodonAuthenticationBox = userProvider.context.authenticationService.activeMastodonAuthenticationBox.value else { return } guard let context = userProvider.context else { return } var mastodonUser: AnyPublisher - if let cell = cell, let indexPath = indexPath { - mastodonUser = userProvider.mastodonUser(for: cell, indexPath: indexPath).eraseToAnyPublisher() + if let cell = cell { + mastodonUser = userProvider.mastodonUser(for: cell).eraseToAnyPublisher() } else { mastodonUser = userProvider.mastodonUser().eraseToAnyPublisher() } @@ -85,16 +84,15 @@ final class BlockDomainService { func unblockDomain( userProvider: UserProvider, - cell: UITableViewCell?, - indexPath: IndexPath? + cell: UITableViewCell? ) { guard let activeMastodonAuthenticationBox = userProvider.context.authenticationService.activeMastodonAuthenticationBox.value else { return } guard let context = userProvider.context else { return } var mastodonUser: AnyPublisher - if let cell = cell, let indexPath = indexPath { - mastodonUser = userProvider.mastodonUser(for: cell, indexPath: indexPath).eraseToAnyPublisher() + if let cell = cell { + mastodonUser = userProvider.mastodonUser(for: cell).eraseToAnyPublisher() } else { mastodonUser = userProvider.mastodonUser().eraseToAnyPublisher() } From b8f3f4c8863bf701ed2f400a606660d067e0cf6e Mon Sep 17 00:00:00 2001 From: sunxiaojian Date: Thu, 6 May 2021 18:19:24 +0800 Subject: [PATCH 10/10] fix: remove ActionToolbarContainer.moreButtonDidPressed --- Localization/app.json | 2 +- .../Diffiable/Section/StatusSection.swift | 5 +- Mastodon/Generated/Strings.swift | 2 +- .../UserProvider/UserProviderFacade.swift | 105 +++++++++--------- .../Resources/en.lproj/Localizable.strings | 2 +- .../Scene/Profile/ProfileViewController.swift | 8 +- .../TableviewCell/StatusTableViewCell.swift | 4 - .../View/ToolBar/ActionToolBarContainer.swift | 7 -- .../Sources/MastodonSDK/Query/Query.swift | 2 +- 9 files changed, 69 insertions(+), 68 deletions(-) diff --git a/Localization/app.json b/Localization/app.json index 28b95fa2c..fbc670da6 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -29,7 +29,7 @@ "confirm": "Sign Out" }, "block_domain": { - "message": "Are you really, really sure you want to block the entire %s ? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.", + "message": "Are you really, really sure you want to block the entire %s? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.", "block_entire_domain": "Block entire domain" }, "save_photo_failure": { diff --git a/Mastodon/Diffiable/Section/StatusSection.swift b/Mastodon/Diffiable/Section/StatusSection.swift index cb38ec8ab..3994229ee 100644 --- a/Mastodon/Diffiable/Section/StatusSection.swift +++ b/Mastodon/Diffiable/Section/StatusSection.swift @@ -780,7 +780,8 @@ extension StatusSection { return } let author = status.authorForUserProvider - let canReport = authenticationBox.userID != author.id + let isMyself = authenticationBox.userID == author.id + let canReport = !isMyself let isInSameDomain = authenticationBox.domain == author.domainFromAcct let isMuting = (author.mutingBy ?? Set()).map(\.id).contains(authenticationBox.userID) let isBlocking = (author.blockingBy ?? Set()).map(\.id).contains(authenticationBox.userID) @@ -788,9 +789,9 @@ extension StatusSection { cell.statusView.actionToolbarContainer.moreButton.showsMenuAsPrimaryAction = true cell.statusView.actionToolbarContainer.moreButton.menu = UserProviderFacade.createProfileActionMenu( for: author, + isMyself: isMyself, isMuting: isMuting, isBlocking: isBlocking, - canReport: canReport, isInSameDomain: isInSameDomain, isDomainBlocking: isDomainBlocking, provider: userProvider, diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index b12d78df8..4ec5e7037 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -16,7 +16,7 @@ internal enum L10n { internal enum BlockDomain { /// Block entire domain internal static let blockEntireDomain = L10n.tr("Localizable", "Common.Alerts.BlockDomain.BlockEntireDomain") - /// Are you really, really sure you want to block the entire %@ ? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed. + /// Are you really, really sure you want to block the entire %@? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed. internal static func message(_ p1: Any) -> String { return L10n.tr("Localizable", "Common.Alerts.BlockDomain.Message", String(describing: p1)) } diff --git a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift index 2aeff0e09..9e20e414a 100644 --- a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift +++ b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift @@ -146,9 +146,9 @@ extension UserProviderFacade { extension UserProviderFacade { static func createProfileActionMenu( for mastodonUser: MastodonUser, + isMyself: Bool, isMuting: Bool, isBlocking: Bool, - canReport: Bool, isInSameDomain: Bool, isDomainBlocking: Bool, provider: UserProvider, @@ -161,62 +161,67 @@ extension UserProviderFacade { var children: [UIMenuElement] = [] let name = mastodonUser.displayNameWithFallback - // mute - let muteAction = UIAction( - title: isMuting ? L10n.Common.Controls.Firendship.unmuteUser(name) : L10n.Common.Controls.Firendship.mute, - image: isMuting ? UIImage(systemName: "speaker") : UIImage(systemName: "speaker.slash"), - discoverabilityTitle: isMuting ? nil : L10n.Common.Controls.Firendship.muteUser(name), - attributes: isMuting ? [] : .destructive, - state: .off - ) { [weak provider] _ in - guard let provider = provider else { return } + if !isMyself { + // mute + let muteAction = UIAction( + title: isMuting ? L10n.Common.Controls.Firendship.unmuteUser(name) : L10n.Common.Controls.Firendship.mute, + image: isMuting ? UIImage(systemName: "speaker") : UIImage(systemName: "speaker.slash"), + discoverabilityTitle: isMuting ? nil : L10n.Common.Controls.Firendship.muteUser(name), + attributes: isMuting ? [] : .destructive, + state: .off + ) { [weak provider] _ in + guard let provider = provider else { return } - UserProviderFacade.toggleUserMuteRelationship( - provider: provider, - cell: cell - ) - .sink { _ in - // do nothing - } receiveValue: { _ in - // do nothing + UserProviderFacade.toggleUserMuteRelationship( + provider: provider, + cell: cell + ) + .sink { _ in + // do nothing + } receiveValue: { _ in + // do nothing + } + .store(in: &provider.context.disposeBag) + } + if isMuting { + children.append(muteAction) + } else { + let muteMenu = UIMenu(title: L10n.Common.Controls.Firendship.muteUser(name), image: UIImage(systemName: "speaker.slash"), options: [], children: [muteAction]) + children.append(muteMenu) } - .store(in: &provider.context.disposeBag) - } - if isMuting { - children.append(muteAction) - } else { - let muteMenu = UIMenu(title: L10n.Common.Controls.Firendship.muteUser(name), image: UIImage(systemName: "speaker.slash"), options: [], children: [muteAction]) - children.append(muteMenu) } - // block - let blockAction = UIAction( - title: isBlocking ? L10n.Common.Controls.Firendship.unblockUser(name) : L10n.Common.Controls.Firendship.block, - image: isBlocking ? UIImage(systemName: "hand.raised.slash") : UIImage(systemName: "hand.raised"), - discoverabilityTitle: isBlocking ? nil : L10n.Common.Controls.Firendship.blockUser(name), - attributes: isBlocking ? [] : .destructive, - state: .off - ) { [weak provider] _ in - guard let provider = provider else { return } + if !isMyself { + // block + let blockAction = UIAction( + title: isBlocking ? L10n.Common.Controls.Firendship.unblockUser(name) : L10n.Common.Controls.Firendship.block, + image: isBlocking ? UIImage(systemName: "hand.raised.slash") : UIImage(systemName: "hand.raised"), + discoverabilityTitle: isBlocking ? nil : L10n.Common.Controls.Firendship.blockUser(name), + attributes: isBlocking ? [] : .destructive, + state: .off + ) { [weak provider] _ in + guard let provider = provider else { return } - UserProviderFacade.toggleUserBlockRelationship( - provider: provider, - cell: cell - ) - .sink { _ in - // do nothing - } receiveValue: { _ in - // do nothing + UserProviderFacade.toggleUserBlockRelationship( + provider: provider, + cell: cell + ) + .sink { _ in + // do nothing + } receiveValue: { _ in + // do nothing + } + .store(in: &provider.context.disposeBag) + } + if isBlocking { + children.append(blockAction) + } else { + let blockMenu = UIMenu(title: L10n.Common.Controls.Firendship.blockUser(name), image: UIImage(systemName: "hand.raised"), options: [], children: [blockAction]) + children.append(blockMenu) } - .store(in: &provider.context.disposeBag) } - if isBlocking { - children.append(blockAction) - } else { - let blockMenu = UIMenu(title: L10n.Common.Controls.Firendship.blockUser(name), image: UIImage(systemName: "hand.raised"), options: [], children: [blockAction]) - children.append(blockMenu) - } - if canReport { + + if !isMyself { let reportAction = UIAction(title: L10n.Common.Controls.Actions.reportUser(name), image: UIImage(systemName: "flag"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in guard let provider = provider else { return } guard let authenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else { diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index e18674a7b..59f83a5cd 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -1,5 +1,5 @@ "Common.Alerts.BlockDomain.BlockEntireDomain" = "Block entire domain"; -"Common.Alerts.BlockDomain.Message" = "Are you really, really sure you want to block the entire %@ ? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed."; +"Common.Alerts.BlockDomain.Message" = "Are you really, really sure you want to block the entire %@? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed."; "Common.Alerts.Common.PleaseTryAgain" = "Please try again."; "Common.Alerts.Common.PleaseTryAgainLater" = "Please try again later."; "Common.Alerts.DiscardPostContent.Message" = "Confirm discard composed post content."; diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index 521df753b..d186d13df 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -384,17 +384,23 @@ extension ProfileViewController { self.moreMenuBarButtonItem.menu = nil return } + guard let currentMastodonUser = self.viewModel.currentMastodonUser.value else { + self.moreMenuBarButtonItem.menu = nil + return + } guard let currentDomain = self.viewModel.domain.value else { return } let isMuting = relationshipActionOptionSet.contains(.muting) let isBlocking = relationshipActionOptionSet.contains(.blocking) let isDomainBlocking = domains.contains(mastodonUser.domainFromAcct) let needsShareAction = self.viewModel.isMeBarButtonItemsHidden.value let isInSameDomain = mastodonUser.domainFromAcct == currentDomain + let isMyself = currentMastodonUser.id == mastodonUser.id + self.moreMenuBarButtonItem.menu = UserProviderFacade.createProfileActionMenu( for: mastodonUser, + isMyself: isMyself, isMuting: isMuting, isBlocking: isBlocking, - canReport: true, isInSameDomain: isInSameDomain, isDomainBlocking: isDomainBlocking, provider: self, diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift index 4a897a349..462577a4b 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift @@ -356,8 +356,4 @@ extension StatusTableViewCell: ActionToolbarContainerDelegate { delegate?.statusTableViewCell(self, actionToolbarContainer: actionToolbarContainer, likeButtonDidPressed: sender) } - func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, moreButtonDidPressed sender: UIButton) { - - } - } diff --git a/Mastodon/Scene/Share/View/ToolBar/ActionToolBarContainer.swift b/Mastodon/Scene/Share/View/ToolBar/ActionToolBarContainer.swift index b777207d2..a2f57dee2 100644 --- a/Mastodon/Scene/Share/View/ToolBar/ActionToolBarContainer.swift +++ b/Mastodon/Scene/Share/View/ToolBar/ActionToolBarContainer.swift @@ -12,7 +12,6 @@ protocol ActionToolbarContainerDelegate: class { func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, replayButtonDidPressed sender: UIButton) func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, reblogButtonDidPressed sender: UIButton) func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, starButtonDidPressed sender: UIButton) - func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, moreButtonDidPressed sender: UIButton) } @@ -63,7 +62,6 @@ extension ActionToolbarContainer { replyButton.addTarget(self, action: #selector(ActionToolbarContainer.replyButtonDidPressed(_:)), for: .touchUpInside) reblogButton.addTarget(self, action: #selector(ActionToolbarContainer.reblogButtonDidPressed(_:)), for: .touchUpInside) favoriteButton.addTarget(self, action: #selector(ActionToolbarContainer.favoriteButtonDidPressed(_:)), for: .touchUpInside) - moreButton.addTarget(self, action: #selector(ActionToolbarContainer.moreButtonDidPressed(_:)), for: .touchUpInside) } } @@ -194,11 +192,6 @@ extension ActionToolbarContainer { delegate?.actionToolbarContainer(self, starButtonDidPressed: sender) } - @objc private func moreButtonDidPressed(_ sender: UIButton) { - os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) - delegate?.actionToolbarContainer(self, moreButtonDidPressed: sender) - } - } #if DEBUG diff --git a/MastodonSDK/Sources/MastodonSDK/Query/Query.swift b/MastodonSDK/Sources/MastodonSDK/Query/Query.swift index 7fbe0da9e..7e27eb50a 100644 --- a/MastodonSDK/Sources/MastodonSDK/Query/Query.swift +++ b/MastodonSDK/Sources/MastodonSDK/Query/Query.swift @@ -62,6 +62,6 @@ protocol PutQuery: RequestQuery { } protocol DeleteQuery: RequestQuery { } extension DeleteQuery { - // By default a `PostQuery` does not has query items + // By default a `DeleteQuery` does not has query items var queryItems: [URLQueryItem]? { nil } }