Add mute action to profiles

Closes #201
This commit is contained in:
Shadowfacts 2022-11-11 23:29:15 -05:00
parent f1511039ef
commit eb7fe22863
4 changed files with 190 additions and 4 deletions

View File

@ -120,14 +120,14 @@ public final class Account: AccountProtocol, Decodable {
return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(accountID)/unblock")
}
public static func mute(_ account: Account, notifications: Bool? = nil) -> Request<Relationship> {
return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(account.id)/mute", body: ParametersBody([
public static func mute(_ accountID: String, notifications: Bool? = nil) -> Request<Relationship> {
return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(accountID)/mute", body: ParametersBody([
"notifications" => notifications
]))
}
public static func unmute(_ account: Account) -> Request<Relationship> {
return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(account.id)/unmute")
public static func unmute(_ accountID: String) -> Request<Relationship> {
return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(accountID)/unmute")
}
public static func getLists(_ account: Account) -> Request<[List]> {

View File

@ -315,6 +315,7 @@
D6F6A550291F058600F496A8 /* CreateListService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F6A54F291F058600F496A8 /* CreateListService.swift */; };
D6F6A552291F098700F496A8 /* RenameListService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F6A551291F098700F496A8 /* RenameListService.swift */; };
D6F6A554291F0D9600F496A8 /* DeleteListService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F6A553291F0D9600F496A8 /* DeleteListService.swift */; };
D6F6A557291F4F1600F496A8 /* MuteAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F6A556291F4F1600F496A8 /* MuteAccountView.swift */; };
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F953EF21251A2900CF0F2B /* MastodonController.swift */; };
D6FF9860255C717400845181 /* AccountSwitchingContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6FF985F255C717400845181 /* AccountSwitchingContainerViewController.swift */; };
/* End PBXBuildFile section */
@ -685,6 +686,7 @@
D6F6A54F291F058600F496A8 /* CreateListService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateListService.swift; sourceTree = "<group>"; };
D6F6A551291F098700F496A8 /* RenameListService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenameListService.swift; sourceTree = "<group>"; };
D6F6A553291F0D9600F496A8 /* DeleteListService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteListService.swift; sourceTree = "<group>"; };
D6F6A556291F4F1600F496A8 /* MuteAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MuteAccountView.swift; sourceTree = "<group>"; };
D6F953EF21251A2900CF0F2B /* MastodonController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonController.swift; sourceTree = "<group>"; };
D6FF985F255C717400845181 /* AccountSwitchingContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSwitchingContainerViewController.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -901,6 +903,7 @@
D641C788213DD86D004B4513 /* Large Image */,
D627944B23A9A02400D38C68 /* Lists */,
D641C782213DD7F0004B4513 /* Main */,
D6F6A555291F4F0C00F496A8 /* Mute */,
D641C786213DD852004B4513 /* Notifications */,
D641C783213DD7FE004B4513 /* Onboarding */,
D641C789213DD87E004B4513 /* Preferences */,
@ -1486,6 +1489,14 @@
path = "Crash Reporter";
sourceTree = "<group>";
};
D6F6A555291F4F0C00F496A8 /* Mute */ = {
isa = PBXGroup;
children = (
D6F6A556291F4F1600F496A8 /* MuteAccountView.swift */,
);
path = Mute;
sourceTree = "<group>";
};
D6F953F121251A2F00CF0F2B /* API */ = {
isa = PBXGroup;
children = (
@ -1892,6 +1903,7 @@
D6B936712829F72900237D0E /* NSManagedObjectContext+Helpers.swift in Sources */,
D627944A23A6AD6100D38C68 /* BookmarksTableViewController.swift in Sources */,
D63CC7102911F1E4000E19DE /* UIScrollView+Top.swift in Sources */,
D6F6A557291F4F1600F496A8 /* MuteAccountView.swift in Sources */,
D64BC19223C271D9000D0238 /* MastodonActivity.swift in Sources */,
D6945C3A23AC75E2005C403C /* FindInstanceViewController.swift in Sources */,
D68E6F59253C9969001A1B4C /* MultiSourceEmojiLabel.swift in Sources */,

View File

@ -0,0 +1,144 @@
//
// MuteAccountView.swift
// Tusker
//
// Created by Shadowfacts on 11/11/22.
// Copyright © 2022 Shadowfacts. All rights reserved.
//
import SwiftUI
import Pachyderm
struct MuteAccountView: View {
private static let durationOptions: [MenuPicker<TimeInterval>.Option] = {
let f = DateComponentsFormatter()
f.maximumUnitCount = 1
f.unitsStyle = .full
f.allowedUnits = [.weekOfMonth, .day, .hour, .minute]
let durations: [TimeInterval] = [
30 * 60,
60 * 60,
6 * 60 * 60,
24 * 60 * 60,
3 * 24 * 60 * 60,
7 * 60 * 60 * 60,
]
return [
.init(title: "Forever", value: 0)
] + durations.map { .init(title: f.string(from: $0)!, value: $0) }
}()
let account: AccountMO
let mastodonController: MastodonController
@Environment(\.dismiss) private var dismiss: DismissAction
@ObservedObject private var preferences = Preferences.shared
@State private var muteNotifications = true
@State private var duration: TimeInterval = 0
@State private var isMuting = false
@State private var error: Error?
var body: some View {
NavigationView {
navigationViewContent
}
}
private var navigationViewContent: some View {
Form {
Section {
HStack {
ComposeAvatarImageView(url: account.avatar)
.frame(width: 50, height: 50)
.cornerRadius(preferences.avatarStyle.cornerRadiusFraction * 50)
VStack(alignment: .leading) {
AccountDisplayNameLabel(account: account, textStyle: .headline, emojiSize: 17)
Text("@\(account.acct)")
.fontWeight(.light)
.foregroundColor(.secondary)
}
}
.frame(height: 50)
.listRowBackground(EmptyView())
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
}
Section {
Toggle(isOn: $muteNotifications) {
Text("Hide notifications from this person")
}
} footer: {
if muteNotifications {
Text("This user's posts and notifications will be hidden.")
} else {
Text("This user's posts will be hidden from your timeline. You can still receive notifications from them.")
}
}
Section {
Picker(selection: $duration) {
ForEach(MuteAccountView.durationOptions, id: \.value) { option in
Text(option.title).tag(option.value)
}
} label: {
Text("Duration")
}
} footer: {
if duration != 0 {
Text("The mute will automatically be removed after the selected time.")
}
}
Button(action: self.mute) {
if isMuting {
HStack {
Text("Muting User")
Spacer()
ProgressView()
.progressViewStyle(.circular)
}
} else {
Text("Mute User")
}
}
.disabled(isMuting)
}
.alertWithData("Erorr Muting", data: $error, actions: { error in
Button("Ok") {}
}, message: { error in
Text(error.localizedDescription)
})
.navigationTitle("Mute \(account.displayOrUserName)")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
self.dismiss()
}
}
}
}
private func mute() {
isMuting = true
let req = Account.mute(account.id, notifications: muteNotifications)
Task {
do {
let (relationship, _) = try await mastodonController.run(req)
mastodonController.persistentContainer.addOrUpdate(relationship: relationship)
self.dismiss()
} catch {
self.error = error
isMuting = false
}
}
}
}
//struct MuteAccountView_Previews: PreviewProvider {
// static var previews: some View {
// MuteAccountView()
// }
//}

View File

@ -10,6 +10,7 @@ import UIKit
import SafariServices
import Pachyderm
import WebURLFoundationExtras
import SwiftUI
protocol MenuActionProvider: AnyObject {
var navigationDelegate: TuskerNavigationDelegate? { get }
@ -68,6 +69,7 @@ extension MenuActionProvider {
if accountID != loggedInAccountID {
actionsSection.append(relationshipAction(accountID: accountID, mastodonController: mastodonController, builder: { [unowned self] in self.followAction(for: $0, mastodonController: $1) }))
suppressSection.append(relationshipAction(accountID: accountID, mastodonController: mastodonController, builder: { [unowned self] in self.blockAction(for: $0, mastodonController: $1) }))
suppressSection.append(relationshipAction(accountID: accountID, mastodonController: mastodonController, builder: { [unowned self] in self.muteAction(for: $0, mastodonController: $1) }))
}
addOpenInNewWindow(actions: &shareSection, activity: UserActivityManager.showProfileActivity(id: accountID, accountID: loggedInAccountID))
@ -441,6 +443,34 @@ extension MenuActionProvider {
}
}
@MainActor
private func muteAction(for relationship: RelationshipMO, mastodonController: MastodonController) -> UIMenuElement {
if relationship.muting || relationship.mutingNotifications {
return UIAction(title: "Unmute", image: UIImage(systemName: "speaker")) { [unowned self] _ in
let req = Account.unmute(relationship.accountID)
mastodonController.run(req) { response in
switch response {
case .failure(let error):
if let toastable = self.toastableViewController {
let config = ToastConfiguration(from: error, with: "Error Unmuting", in: toastable, retryAction: nil)
DispatchQueue.main.async {
toastable.showToast(configuration: config, animated: true)
}
}
case .success(let relationship, _):
mastodonController.persistentContainer.addOrUpdate(relationship: relationship)
}
}
}
} else {
return UIAction(title: "Mute", image: UIImage(systemName: "speaker.slash")) { [unowned self] _ in
let view = MuteAccountView(account: relationship.account!, mastodonController: mastodonController)
let host = UIHostingController(rootView: view)
self.navigationDelegate?.present(host, animated: true)
}
}
}
}
private func fetchRelationship(accountID: String, mastodonController: MastodonController) async -> RelationshipMO? {