diff --git a/Mlem.xcodeproj/project.pbxproj b/Mlem.xcodeproj/project.pbxproj index a640dd92e..a2ca2022b 100644 --- a/Mlem.xcodeproj/project.pbxproj +++ b/Mlem.xcodeproj/project.pbxproj @@ -81,6 +81,7 @@ 033FCB292C5E3933007B7CD1 /* IconSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033FCB252C5E3933007B7CD1 /* IconSettingsView.swift */; }; 033FCB2A2C5E3933007B7CD1 /* AlternateIconCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033FCB232C5E3933007B7CD1 /* AlternateIconCell.swift */; }; 033FCB3E2C5E7FA9007B7CD1 /* View+OutdatedFeedPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033FCB3D2C5E7FA9007B7CD1 /* View+OutdatedFeedPopup.swift */; }; + 034690932D0F4D720073E664 /* RemovableProviding+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 034690922D0F4D720073E664 /* RemovableProviding+Extensions.swift */; }; 034B947F2C091EDD00039AF4 /* ProfileHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 034B947E2C091EDD00039AF4 /* ProfileHeaderView.swift */; }; 034B94812C09306D00039AF4 /* CommunityOrPersonStub+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 034B94802C09306D00039AF4 /* CommunityOrPersonStub+Extensions.swift */; }; 034B94832C09340A00039AF4 /* MarkdownConfiguration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 034B94822C09340A00039AF4 /* MarkdownConfiguration+Extensions.swift */; }; @@ -505,6 +506,7 @@ 033FCB242C5E3933007B7CD1 /* AlternateIconLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlternateIconLabel.swift; sourceTree = ""; }; 033FCB252C5E3933007B7CD1 /* IconSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconSettingsView.swift; sourceTree = ""; }; 033FCB3D2C5E7FA9007B7CD1 /* View+OutdatedFeedPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+OutdatedFeedPopup.swift"; sourceTree = ""; }; + 034690922D0F4D720073E664 /* RemovableProviding+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RemovableProviding+Extensions.swift"; sourceTree = ""; }; 034B947E2C091EDD00039AF4 /* ProfileHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderView.swift; sourceTree = ""; }; 034B94802C09306D00039AF4 /* CommunityOrPersonStub+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CommunityOrPersonStub+Extensions.swift"; sourceTree = ""; }; 034B94822C09340A00039AF4 /* MarkdownConfiguration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MarkdownConfiguration+Extensions.swift"; sourceTree = ""; }; @@ -1763,6 +1765,7 @@ 03AFD0E42C3C14D50054B8AD /* InstanceStubProviding+Extensions.swift */, 035394962CA1AFAF00795AA5 /* InstanceStubProviding+Uptime.swift */, CDC199E92BE449790077B4F1 /* Interactable1Providing+Extensions.swift */, + 034690922D0F4D720073E664 /* RemovableProviding+Extensions.swift */, 0397D4852C6A24D2002C6CDC /* ReportableProviding+Extensions.swift */, 03CBD1922C61369A00E870BC /* Interactable2Providing+Extensions.swift */, 0389DDC22C38907C0005B808 /* Message1Providing+Extensions.swift */, @@ -2201,6 +2204,7 @@ CDB41E8E2C84CFA200BD2DE9 /* FixedImageView.swift in Sources */, 033F84C12C2AD072002E3EDF /* CommentWrapper.swift in Sources */, 034B94832C09340A00039AF4 /* MarkdownConfiguration+Extensions.swift in Sources */, + 034690932D0F4D720073E664 /* RemovableProviding+Extensions.swift in Sources */, 039F58952C7B618F00C61658 /* InboxSettingsView.swift in Sources */, CD4D58CF2B86DDEC00B82964 /* AccountSortMode.swift in Sources */, CDE1F18F2C63D75A008AF042 /* Settings.swift in Sources */, diff --git a/Mlem/App/Models/Account/GuestAccount.swift b/Mlem/App/Models/Account/GuestAccount.swift index 2d6467658..7839d5abb 100644 --- a/Mlem/App/Models/Account/GuestAccount.swift +++ b/Mlem/App/Models/Account/GuestAccount.swift @@ -71,6 +71,7 @@ class GuestAccount: Account { try container.encode(api.baseUrl, forKey: .instanceLink) } + @MainActor func update(instance: Instance3) { var shouldSave = false if avatar != instance.avatar { diff --git a/Mlem/App/Models/Account/UserAccount.swift b/Mlem/App/Models/Account/UserAccount.swift index 2ed4e2059..abb16590b 100644 --- a/Mlem/App/Models/Account/UserAccount.swift +++ b/Mlem/App/Models/Account/UserAccount.swift @@ -96,6 +96,7 @@ class UserAccount: Account, CommunityOrPersonStub { getKeychainId(actorId: actorId) } + @MainActor func update(person: Person4, instance: Instance3) { var shouldSave = false if avatar != person.avatar { diff --git a/Mlem/App/Models/Action/BasicAction.swift b/Mlem/App/Models/Action/BasicAction.swift index ea3acf39f..7455e0e07 100644 --- a/Mlem/App/Models/Action/BasicAction.swift +++ b/Mlem/App/Models/Action/BasicAction.swift @@ -53,4 +53,12 @@ struct BasicAction: Action { } } } + + func disabled(_ value: Bool) -> BasicAction { + var new = self + if value { + new.callback = nil + } + return new + } } diff --git a/Mlem/App/Models/Session/UserSession.swift b/Mlem/App/Models/Session/UserSession.swift index c5b59e8b7..0eeafa4e0 100644 --- a/Mlem/App/Models/Session/UserSession.swift +++ b/Mlem/App/Models/Session/UserSession.swift @@ -38,7 +38,7 @@ class UserSession: Session { try await self.api.fetchSiteVersion(task: Task { let (person, instance, blocks) = try await self.api.getMyPerson() if let person { - self.account.update(person: person, instance: instance) + await self.account.update(person: person, instance: instance) self.person = person } self.blocks = blocks diff --git a/Mlem/App/Utility/Extensions/Content Models/Comment1Providing+Extensions.swift b/Mlem/App/Utility/Extensions/Content Models/Comment1Providing+Extensions.swift index e88fb1d76..b0118c0a9 100644 --- a/Mlem/App/Utility/Extensions/Content Models/Comment1Providing+Extensions.swift +++ b/Mlem/App/Utility/Extensions/Content Models/Comment1Providing+Extensions.swift @@ -98,7 +98,7 @@ extension Comment1Providing { @ActionBuilder func moderatorMenuActions(feedback: Set = [.haptic, .toast]) -> [any Action] { if let self2, !isOwnComment { - self2.removeAction() + self2.removeAction().disabled(!canModerate) banActions() } if api.isAdmin { @@ -126,7 +126,7 @@ extension Comment1Providing { case .share: shareAction() case .selectText: selectTextAction() case .report: reportAction(communityContext: communityContext) - case .remove: removeAction() + case .remove: removeAction().disabled(!canModerate) } } diff --git a/Mlem/App/Utility/Extensions/Content Models/Community1Providing+Extensions.swift b/Mlem/App/Utility/Extensions/Content Models/Community1Providing+Extensions.swift index 90ba08908..46a5cd857 100644 --- a/Mlem/App/Utility/Extensions/Content Models/Community1Providing+Extensions.swift +++ b/Mlem/App/Utility/Extensions/Content Models/Community1Providing+Extensions.swift @@ -121,6 +121,12 @@ extension Community1Providing { copyNameAction() shareAction() blockAction(feedback: feedback) + if api.isAdmin { + ActionGroup { + removeAction() + purgeAction() + } + } } func swipeActions(behavior: SwipeBehavior) -> SwipeConfiguration { diff --git a/Mlem/App/Utility/Extensions/Content Models/Interactable1Providing+Extensions.swift b/Mlem/App/Utility/Extensions/Content Models/Interactable1Providing+Extensions.swift index eb56e2fcc..ec66b1087 100644 --- a/Mlem/App/Utility/Extensions/Content Models/Interactable1Providing+Extensions.swift +++ b/Mlem/App/Utility/Extensions/Content Models/Interactable1Providing+Extensions.swift @@ -86,14 +86,6 @@ extension Interactable1Providing { } } } - - func showRemoveSheet() { - guard let self2 else { - print("DEBUG no self2 found in toggleRemoved!") - return - } - NavigationModel.main.openSheet(.remove(self2)) - } // MARK: Counters @@ -173,14 +165,6 @@ extension Interactable1Providing { ) } - func removeAction(feedback: Set = []) -> BasicAction { - .init( - id: "remove\(uid)", - appearance: .remove(isOn: self2?.removed ?? false, isInProgress: !(self2?.removedManager.isInSync ?? true)), - callback: api.canInteract && (self2?.canModerate ?? false) ? showRemoveSheet : nil - ) - } - func banActions() -> [any Action] { let isModerator: Bool if let myPerson = api.myPerson, let community = community_ { diff --git a/Mlem/App/Utility/Extensions/Content Models/Post1Providing+Extensions.swift b/Mlem/App/Utility/Extensions/Content Models/Post1Providing+Extensions.swift index 42850f7f0..77cefda9c 100644 --- a/Mlem/App/Utility/Extensions/Content Models/Post1Providing+Extensions.swift +++ b/Mlem/App/Utility/Extensions/Content Models/Post1Providing+Extensions.swift @@ -191,7 +191,7 @@ extension Post1Providing { lockAction(feedback: feedback) } if let self2, !isOwnPost { - self2.removeAction() + self2.removeAction().disabled(!canModerate) banActions() } if api.isAdmin { @@ -225,7 +225,7 @@ extension Post1Providing { // in parenthesis, but the pre-commit hook removed the paranthesis // swiftlint:disable:next void_function_in_ternary case .pin: api.isAdmin ? pinAction(feedback: feedback) : pinToCommunityAction(feedback: feedback) - case .remove: removeAction(feedback: feedback) + case .remove: removeAction(feedback: feedback).disabled(!canModerate) } } diff --git a/Mlem/App/Utility/Extensions/Content Models/RemovableProviding+Extensions.swift b/Mlem/App/Utility/Extensions/Content Models/RemovableProviding+Extensions.swift new file mode 100644 index 000000000..165361a28 --- /dev/null +++ b/Mlem/App/Utility/Extensions/Content Models/RemovableProviding+Extensions.swift @@ -0,0 +1,22 @@ +// +// RemovableProviding+Extensions.swift +// Mlem +// +// Created by Sjmarf on 2024-12-15. +// + +import MlemMiddleware + +extension RemovableProviding { + func showRemoveSheet() { + NavigationModel.main.openSheet(.remove(self)) + } + + func removeAction(feedback: Set = []) -> BasicAction { + .init( + id: "remove\(uid)", + appearance: .remove(isOn: removed, isInProgress: !removedManager.isInSync), + callback: api.canInteract ? showRemoveSheet : nil + ) + } +} diff --git a/Mlem/App/Views/Pages/Community/CommunityView.swift b/Mlem/App/Views/Pages/Community/CommunityView.swift index d8c5ba3ac..3def5d5cd 100644 --- a/Mlem/App/Views/Pages/Community/CommunityView.swift +++ b/Mlem/App/Views/Pages/Community/CommunityView.swift @@ -128,12 +128,23 @@ struct CommunityView: View { @ViewBuilder func postsTab(community: any Community, postFeedLoader: CommunityPostFeedLoader) -> some View { - PostGridView(postFeedLoader: postFeedLoader) - .toolbar { - ToolbarItem(placement: .topBarTrailing) { - FeedSortPicker(feedLoader: postFeedLoader) - } + if community.removed { + VStack(spacing: Constants.main.standardSpacing) { + Image(systemName: Icons.remove) + .font(.title) + Text("This community has been removed.") + .fontWeight(.semibold) } + .foregroundStyle(palette.warning) + .padding(.top, Constants.main.doubleSpacing) + } else { + PostGridView(postFeedLoader: postFeedLoader) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + FeedSortPicker(feedLoader: postFeedLoader) + } + } + } } @ViewBuilder diff --git a/Mlem/App/Views/Pages/ContentRemovalEditorView.swift b/Mlem/App/Views/Pages/ContentRemovalEditorView.swift index e5266cc1e..8b7a48f30 100644 --- a/Mlem/App/Views/Pages/ContentRemovalEditorView.swift +++ b/Mlem/App/Views/Pages/ContentRemovalEditorView.swift @@ -17,18 +17,18 @@ struct ContentRemovalEditorView: View { case remove, restore } - let target: any Interactable2Providing + let target: any RemovableProviding @State var mode: Mode - @State var community: any Community + @State var community: (any Community)? @State var reason: String = "" @FocusState var reasonFocused: Bool @State var presentationSelection: PresentationDetent = .large - init(target: any Interactable2Providing) { + init(target: any RemovableProviding) { self.target = target self._mode = .init(wrappedValue: target.removed ? .restore : .remove) - self._community = .init(wrappedValue: target.community) + self._community = .init(wrappedValue: (target as? any Interactable2Providing)?.community) } var body: some View { @@ -41,7 +41,9 @@ struct ContentRemovalEditorView: View { Section { ReasonShortcutView(reason: $reason) } - RulesListView(model: community, reason: $reason) + if let community { + RulesListView(model: community, reason: $reason) + } if let instance = appState.firstSession.instance { RulesListView(model: instance, reason: $reason) } diff --git a/Mlem/App/Views/Shared/Navigation/NavigationPage.swift b/Mlem/App/Views/Shared/Navigation/NavigationPage.swift index 9216f3a61..74d0f95ca 100644 --- a/Mlem/App/Views/Shared/Navigation/NavigationPage.swift +++ b/Mlem/App/Views/Shared/Navigation/NavigationPage.swift @@ -37,7 +37,7 @@ enum NavigationPage: Hashable { case createComment(_ context: CommentEditorView.Context, commentTreeTracker: CommentTreeTracker? = nil) case editComment(_ comment: Comment2, context: CommentEditorView.Context?) case report(_ interactable: ReportableHashWrapper, community: AnyCommunity? = nil) - case remove(_ interactable: Interactable2HashWrapper) + case remove(_ removable: RemovableHashWrapper) case purge(_ purgable: PurgableHashWrapper) case ban(_ person: AnyPerson, isBannedFromCommunity: Bool, shouldBan: Bool, community: AnyCommunity?) case createPost( @@ -211,7 +211,7 @@ enum NavigationPage: Hashable { return report(.init(wrappedValue: interactable), community: anyCommunity) } - static func remove(_ interactable: any Interactable2Providing) -> NavigationPage { + static func remove(_ interactable: any RemovableProviding) -> NavigationPage { remove(.init(wrappedValue: interactable)) } @@ -313,6 +313,18 @@ struct ReportableHashWrapper: Hashable { } } +struct RemovableHashWrapper: Hashable { + var wrappedValue: any RemovableProviding + + func hash(into hasher: inout Hasher) { + hasher.combine(wrappedValue.hashValue) + } + + static func == (lhs: RemovableHashWrapper, rhs: RemovableHashWrapper) -> Bool { + lhs.hashValue == rhs.hashValue + } +} + struct PurgableHashWrapper: Hashable { var wrappedValue: any PurgableProviding diff --git a/Mlem/Localizable.xcstrings b/Mlem/Localizable.xcstrings index 7a1b16865..54a4662fb 100644 --- a/Mlem/Localizable.xcstrings +++ b/Mlem/Localizable.xcstrings @@ -1594,6 +1594,9 @@ } } } + }, + "This community has been removed." : { + }, "This community likely contains graphic or explicit content." : {