diff --git a/Mlem.xcodeproj/project.pbxproj b/Mlem.xcodeproj/project.pbxproj index dcf308fe1..d5ade612e 100644 --- a/Mlem.xcodeproj/project.pbxproj +++ b/Mlem.xcodeproj/project.pbxproj @@ -448,7 +448,6 @@ 030030A02C416B0B009A65FF /* RefreshPopupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshPopupView.swift; sourceTree = ""; }; 030050D22D109B7E002B1E99 /* ReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportView.swift; sourceTree = ""; }; 030050D42D10AE30002B1E99 /* Report+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Report+Extensions.swift"; sourceTree = ""; }; - 030050D62D118FA5002B1E99 /* MlemMiddleware */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = MlemMiddleware; path = ../MlemMiddleware; sourceTree = SOURCE_ROOT; }; 03036C732C71408700C6DA1D /* CounterAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterAppearance.swift; sourceTree = ""; }; 03036C752C71427B00C6DA1D /* InteractionBarCounterLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractionBarCounterLabelView.swift; sourceTree = ""; }; 03036C822C727D0500C6DA1D /* InteractionBarEditorView+Logic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "InteractionBarEditorView+Logic.swift"; sourceTree = ""; }; @@ -1292,7 +1291,6 @@ 6363D5B827EE196700E34822 = { isa = PBXGroup; children = ( - 030050D62D118FA5002B1E99 /* MlemMiddleware */, 6363D5C327EE196700E34822 /* Mlem */, 6363D5D927EE196A00E34822 /* MlemTests */, 6363D5E327EE196A00E34822 /* MlemUITests */, @@ -2992,8 +2990,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/mlemgroup/MlemMiddleware"; requirement = { - kind = upToNextMinorVersion; - minimumVersion = 0.53.0; + branch = sjmarf/report; + kind = branch; }; }; CDE4AC402CA3706400981010 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */ = { diff --git a/Mlem.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mlem.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 000000000..b8ac15ea9 --- /dev/null +++ b/Mlem.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,168 @@ +{ + "originHash" : "e91c2cec61691543665fad5843f30eafc5288bb40fc3a60d50c7d7e9194f3135", + "pins" : [ + { + "identity" : "combine-schedulers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/combine-schedulers", + "state" : { + "revision" : "ec62f32d21584214a4b27c8cee2b2ad70ab2c38a", + "version" : "0.11.0" + } + }, + { + "identity" : "gifu", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kaishin/Gifu", + "state" : { + "branch" : "master", + "revision" : "639e40d5dbd86dfa94fc61296c270b49af6f8a2a" + } + }, + { + "identity" : "keychainaccess", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kishikawakatsumi/KeychainAccess.git", + "state" : { + "branch" : "master", + "revision" : "e0c7eebc5a4465a3c4680764f26b7a61f567cdaf" + } + }, + { + "identity" : "lemmymarkdownui", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mlemgroup/LemmyMarkdownUI", + "state" : { + "revision" : "b42b1e301194fd56a144cf199ab2b807a0413a97", + "version" : "0.5.2" + } + }, + { + "identity" : "libwebp-xcode", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SDWebImage/libwebp-Xcode.git", + "state" : { + "revision" : "b2b1d20a90b14d11f6ef4241da6b81c1d3f171e4", + "version" : "1.3.2" + } + }, + { + "identity" : "mlemmiddleware", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mlemgroup/MlemMiddleware", + "state" : { + "branch" : "sjmarf/report", + "revision" : "082d54e3e607ea8afcbc0f31006fd3ba05896e3d" + } + }, + { + "identity" : "nuke", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kean/Nuke", + "state" : { + "revision" : "8e431251dea0081b6ab154dab61a6ec74e4b6577", + "version" : "12.6.0" + } + }, + { + "identity" : "sdwebimage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SDWebImage/SDWebImage.git", + "state" : { + "revision" : "8a1be70a625683bc04d6903e2935bf23f3c6d609", + "version" : "5.19.7" + } + }, + { + "identity" : "sdwebimageswiftui", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SDWebImage/SDWebImageSwiftUI", + "state" : { + "revision" : "5aa947356f4ea49a0c3b9968564267f6ea5abea7", + "version" : "3.1.2" + } + }, + { + "identity" : "sdwebimagewebpcoder", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SDWebImage/SDWebImageWebPCoder", + "state" : { + "revision" : "f534cfe830a7807ecc3d0332127a502426cfa067", + "version" : "0.14.6" + } + }, + { + "identity" : "semaphore", + "kind" : "remoteSourceControl", + "location" : "https://github.com/groue/Semaphore", + "state" : { + "revision" : "f1c4a0acabeb591068dea6cffdd39660b86dec28", + "version" : "0.0.8" + } + }, + { + "identity" : "swift-clocks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-clocks", + "state" : { + "revision" : "0fbaebfc013715dab44d715a4d350ba37f297e4d", + "version" : "0.4.0" + } + }, + { + "identity" : "swift-concurrency-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-concurrency-extras", + "state" : { + "revision" : "a46265bb4f75808b0e15d971eebc408f557870a3", + "version" : "0.1.2" + } + }, + { + "identity" : "swift-dependencies", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-dependencies", + "state" : { + "revision" : "16fd42ae04c6e7f74a6a86395d04722c641cccee", + "version" : "0.6.0" + } + }, + { + "identity" : "swiftui-flow", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tevelee/SwiftUI-Flow", + "state" : { + "revision" : "b528bd06ae70fbd2936d9561a456fb6ea65bc7ff", + "version" : "2.5.0" + } + }, + { + "identity" : "swiftui-introspect", + "kind" : "remoteSourceControl", + "location" : "https://github.com/siteline/SwiftUI-Introspect", + "state" : { + "revision" : "807f73ce09a9b9723f12385e592b4e0aaebd3336", + "version" : "1.3.0" + } + }, + { + "identity" : "swiftyjson", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SwiftyJSON/SwiftyJSON.git", + "state" : { + "revision" : "2b6054efa051565954e1d2b9da831680026cd768", + "version" : "4.3.0" + } + }, + { + "identity" : "xctest-dynamic-overlay", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "state" : { + "revision" : "50843cbb8551db836adec2290bb4bc6bac5c1865", + "version" : "0.9.0" + } + } + ], + "version" : 3 +} diff --git a/Mlem/App/Utility/Extensions/Content Models/Message1Providing+Extensions.swift b/Mlem/App/Utility/Extensions/Content Models/Message1Providing+Extensions.swift index 16a907c1d..6c0364645 100644 --- a/Mlem/App/Utility/Extensions/Content Models/Message1Providing+Extensions.swift +++ b/Mlem/App/Utility/Extensions/Content Models/Message1Providing+Extensions.swift @@ -24,7 +24,23 @@ extension Message1Providing { } @ActionBuilder - func menuActions(feedback: Set = [.haptic, .toast]) -> [any Action] { + func allMenuActions( + feedback: Set = [.haptic, .toast], + report: Report? = nil + ) -> [any Action] { + basicMenuActions(feedback: feedback) + if api.isAdmin { + ActionGroup { + moderatorMenuActions(feedback: feedback, report: report) + } + } + } + + @ActionBuilder + func basicMenuActions( + feedback: Set = [.haptic, .toast], + report: Report? = nil + ) -> [any Action] { if !isOwnMessage { replyAction() markReadAction(feedback: feedback) @@ -35,11 +51,25 @@ extension Message1Providing { if isOwnMessage { deleteAction(feedback: feedback) } else { - reportAction() + if report == nil { + reportAction() + } blockCreatorAction(feedback: feedback) } } + @ActionBuilder + func moderatorMenuActions( + feedback: Set = [.haptic, .toast], + report: Report? = nil + ) -> [any Action] { + if let report { + ActionGroup { + report.menuActions() + } + } + } + // These actions are also defined in Interactable1Providing... another protocol for these may be a good idea func replyAction() -> BasicAction { diff --git a/Mlem/App/Views/Root/Tabs/Inbox/InboxView+Views.swift b/Mlem/App/Views/Root/Tabs/Inbox/InboxView+Views.swift index ddd566aad..28d775d04 100644 --- a/Mlem/App/Views/Root/Tabs/Inbox/InboxView+Views.swift +++ b/Mlem/App/Views/Root/Tabs/Inbox/InboxView+Views.swift @@ -17,7 +17,7 @@ extension InboxView { Group { switch item { case let .message(message): - MessageView(message: message) + MessageView(message: message, isInInbox: true) case let .reply(reply): ReplyView(reply: reply) } @@ -137,11 +137,14 @@ extension InboxView { @ViewBuilder var headerView: some View { + let availableFeeds = availableFeeds Menu { - Picker("Feed", selection: $selectedFeed) { - ForEach(Feed.allCases) { feedType in - Label(String(localized: feedType.label), systemImage: feedType.systemImage) - .tag(feedType) + if availableFeeds.count > 1 { + Picker("Feed", selection: $selectedFeed) { + ForEach(availableFeeds) { feedType in + Label(String(localized: feedType.label), systemImage: feedType.systemImage) + .tag(feedType) + } } } } label: { @@ -154,7 +157,7 @@ extension InboxView { iconNameFill: selectedFeed.systemImageFill, iconScaleFactor: 0.5 ), - dropdownStyle: .enabled(showBadge: false) + dropdownStyle: availableFeeds.count > 1 ? .enabled(showBadge: false) : .disabled ) } } diff --git a/Mlem/App/Views/Root/Tabs/Inbox/InboxView.swift b/Mlem/App/Views/Root/Tabs/Inbox/InboxView.swift index b57a4045e..59c57e6e9 100644 --- a/Mlem/App/Views/Root/Tabs/Inbox/InboxView.swift +++ b/Mlem/App/Views/Root/Tabs/Inbox/InboxView.swift @@ -31,19 +31,6 @@ struct InboxView: View { @State var waitingOnMarkAllAsRead: Bool = false @State var markAllAsReadTrigger: Bool = false - var feedLoader: StandardFeedLoader { - switch selectedTab { - case .all: - inboxFeedLoader - case .replies: - replyFeedLoader - case .mentions: - mentionFeedLoader - case .messages: - messageFeedLoader - } - } - init() { @Setting(\.internetSpeed) var internetSpeed @Setting(\.showReadInInbox) var showRead @@ -81,6 +68,26 @@ struct InboxView: View { self._inboxFeedLoader = .init(wrappedValue: inboxFeedLoader) } + var feedLoader: StandardFeedLoader { + switch selectedTab { + case .all: + inboxFeedLoader + case .replies: + replyFeedLoader + case .mentions: + mentionFeedLoader + case .messages: + messageFeedLoader + } + } + + var availableFeeds: [Feed] { + if appState.firstApi.isAdmin || !(appState.firstPerson?.moderatedCommunities.isEmpty ?? true) { + return [.inbox, .modMail] + } + return [.inbox] + } + var body: some View { if appState.firstSession is GuestSession { signedOutInfoView diff --git a/Mlem/App/Views/Shared/MessageView.swift b/Mlem/App/Views/Shared/MessageView.swift index a6da59e88..af5580f27 100644 --- a/Mlem/App/Views/Shared/MessageView.swift +++ b/Mlem/App/Views/Shared/MessageView.swift @@ -12,15 +12,21 @@ import SwiftUI struct MessageView: View { @Environment(Palette.self) private var palette @Environment(AppState.self) private var appState + @Environment(\.reportContext) private var reportContext + + @Setting(\.moderatorActionGrouping) var moderatorActionGrouping let message: any Message + let isInInbox: Bool let embeddedContent: EmbeddedContent init( message: any Message, + isInInbox: Bool = false, @ViewBuilder embeddedContent: () -> EmbeddedContent = { EmptyView() } ) { self.message = message + self.isInInbox = isInInbox self.embeddedContent = embeddedContent() } @@ -29,10 +35,12 @@ struct MessageView: View { HStack { FullyQualifiedLinkView(entity: message.creator_, labelStyle: .small, showAvatar: true) Spacer() - Image(systemName: message.isOwnMessage ? Icons.send : Icons.message) - .symbolVariant(message.read ? .none : .fill) - .foregroundStyle(palette.accent) - EllipsisMenu(size: 24) { message.menuActions() } + if isInInbox { + Image(systemName: message.isOwnMessage ? Icons.send : Icons.message) + .symbolVariant(message.read ? .none : .fill) + .foregroundStyle(palette.accent) + } + ellipsisMenus .frame(height: 10) } if message.deleted { @@ -61,7 +69,26 @@ struct MessageView: View { .quickSwipes(message.swipeActions(behavior: .standard)) .clipShape(.rect(cornerRadius: Constants.main.standardSpacing)) .contentShape(.contextMenuPreview, .rect(cornerRadius: Constants.main.standardSpacing)) - .contextMenu { message.menuActions() } + .contextMenu { message.allMenuActions(report: reportContext) } .paletteBorder(cornerRadius: Constants.main.standardSpacing) } + + var ellipsisMenus: some View { + HStack { + if moderatorActionGrouping == .separateMenu { + if message.api.isAdmin { + EllipsisMenu(systemImage: Icons.moderation, size: 24) { + message.moderatorMenuActions(report: reportContext) + } + } + EllipsisMenu(size: 24) { + message.basicMenuActions() + } + } else { + EllipsisMenu(size: 24) { + message.allMenuActions(report: reportContext) + } + } + } + } }