Profile actions
This commit is contained in:
parent
0b7b3d3dc4
commit
2397758456
|
@ -27,7 +27,7 @@ extension ContentDatabase {
|
|||
t.column("emojis", .blob).notNull()
|
||||
t.column("bot", .boolean).notNull()
|
||||
t.column("discoverable", .boolean)
|
||||
t.column("movedId", .text).references("accountRecord")
|
||||
t.column("movedId", .text).references("accountRecord", onDelete: .cascade)
|
||||
}
|
||||
|
||||
try db.create(table: "relationship") { t in
|
||||
|
@ -61,7 +61,7 @@ extension ContentDatabase {
|
|||
t.column("id", .text).primaryKey(onConflict: .replace)
|
||||
t.column("uri", .text).notNull()
|
||||
t.column("createdAt", .datetime).notNull()
|
||||
t.column("accountId", .text).notNull().references("accountRecord")
|
||||
t.column("accountId", .text).notNull().references("accountRecord", onDelete: .cascade)
|
||||
t.column("content", .text).notNull()
|
||||
t.column("visibility", .text).notNull()
|
||||
t.column("sensitive", .boolean).notNull()
|
||||
|
@ -77,7 +77,7 @@ extension ContentDatabase {
|
|||
t.column("url", .text)
|
||||
t.column("inReplyToId", .text)
|
||||
t.column("inReplyToAccountId", .text)
|
||||
t.column("reblogId", .text).references("statusRecord")
|
||||
t.column("reblogId", .text).references("statusRecord", onDelete: .cascade)
|
||||
t.column("poll", .blob)
|
||||
t.column("card", .blob)
|
||||
t.column("language", .text)
|
||||
|
@ -135,7 +135,7 @@ extension ContentDatabase {
|
|||
try db.create(table: "conversationRecord") { t in
|
||||
t.column("id", .text).primaryKey(onConflict: .replace)
|
||||
t.column("unread", .boolean).notNull()
|
||||
t.column("lastStatusId", .text).references("statusRecord")
|
||||
t.column("lastStatusId", .text).references("statusRecord", onDelete: .cascade)
|
||||
}
|
||||
|
||||
try db.create(table: "conversationAccountJoin") { t in
|
||||
|
@ -155,8 +155,8 @@ extension ContentDatabase {
|
|||
try db.create(table: "notificationRecord") { t in
|
||||
t.column("id", .text).primaryKey(onConflict: .replace)
|
||||
t.column("type", .text).notNull()
|
||||
t.column("accountId", .text).notNull().references("accountRecord")
|
||||
t.column("statusId").references("statusRecord")
|
||||
t.column("accountId", .text).notNull().references("accountRecord", onDelete: .cascade)
|
||||
t.column("statusId").references("statusRecord", onDelete: .cascade)
|
||||
}
|
||||
|
||||
try db.create(table: "statusAncestorJoin") { t in
|
||||
|
|
|
@ -233,6 +233,21 @@ public extension ContentDatabase {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func mute(id: Account.Id) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher {
|
||||
try StatusRecord.filter(StatusRecord.Columns.accountId == id).deleteAll($0)
|
||||
try NotificationRecord.filter(NotificationRecord.Columns.accountId == id).deleteAll($0)
|
||||
}
|
||||
.ignoreOutput()
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func block(id: Account.Id) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher(updates: AccountRecord.filter(AccountRecord.Columns.id == id).deleteAll)
|
||||
.ignoreOutput()
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func append(accounts: [Account], toList list: AccountList) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher {
|
||||
try list.save($0)
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
"account.block-account" = "Block %@";
|
||||
"account.field.verified" = "Verified %@";
|
||||
"account.follow" = "Follow";
|
||||
"account.following" = "Following";
|
||||
"account.hide-reblogs-account" = "Hide reblogs from %@";
|
||||
"account.mute-account" = "Mute %@";
|
||||
"account.request" = "Request";
|
||||
"account.statuses" = "Posts";
|
||||
"account.statuses-and-replies" = "Posts & Replies";
|
||||
"account.media" = "Media";
|
||||
"account.show-reblogs-account" = "Show reblogs from %@";
|
||||
"account.unblock-account" = "Unblock %@";
|
||||
"account.unfollow-account" = "Unfollow %@";
|
||||
"account.unmute-account" = "Unmute %@";
|
||||
"add" = "Add";
|
||||
"apns-default-message" = "New notification";
|
||||
"add-identity.instance-url" = "Instance URL";
|
||||
|
|
|
@ -5,8 +5,15 @@ import HTTP
|
|||
import Mastodon
|
||||
|
||||
public enum RelationshipEndpoint {
|
||||
case accountsFollow(id: Account.Id)
|
||||
case accountsFollow(id: Account.Id, showReblogs: Bool? = nil)
|
||||
case accountsUnfollow(id: Account.Id)
|
||||
case accountsBlock(id: Account.Id)
|
||||
case accountsUnblock(id: Account.Id)
|
||||
case accountsMute(id: Account.Id)
|
||||
case accountsUnmute(id: Account.Id)
|
||||
case accountsPin(id: Account.Id)
|
||||
case accountsUnpin(id: Account.Id)
|
||||
case note(String, id: Account.Id)
|
||||
}
|
||||
|
||||
extension RelationshipEndpoint: Endpoint {
|
||||
|
@ -18,17 +25,50 @@ extension RelationshipEndpoint: Endpoint {
|
|||
|
||||
public var pathComponentsInContext: [String] {
|
||||
switch self {
|
||||
case let .accountsFollow(id):
|
||||
case let .accountsFollow(id, _):
|
||||
return [id, "follow"]
|
||||
case let .accountsUnfollow(id):
|
||||
return [id, "unfollow"]
|
||||
case let .accountsBlock(id):
|
||||
return [id, "block"]
|
||||
case let .accountsUnblock(id):
|
||||
return [id, "unblock"]
|
||||
case let .accountsMute(id):
|
||||
return [id, "mute"]
|
||||
case let .accountsUnmute(id):
|
||||
return [id, "unmute"]
|
||||
case let .accountsPin(id):
|
||||
return [id, "pin"]
|
||||
case let .accountsUnpin(id):
|
||||
return [id, "unpin"]
|
||||
case let .note(_, id):
|
||||
return [id, "note"]
|
||||
}
|
||||
}
|
||||
|
||||
public var queryParameters: [URLQueryItem] {
|
||||
switch self {
|
||||
case let .accountsFollow(_, showReblogs):
|
||||
if let showReblogs = showReblogs {
|
||||
return [URLQueryItem(name: "reblogs", value: String(showReblogs))]
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
public var jsonBody: [String: Any]? {
|
||||
switch self {
|
||||
case let .note(note, _):
|
||||
return ["comment": note]
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public var method: HTTPMethod {
|
||||
switch self {
|
||||
case .accountsFollow, .accountsUnfollow:
|
||||
return .post
|
||||
}
|
||||
.post
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,17 +31,63 @@ public struct AccountService {
|
|||
|
||||
public extension AccountService {
|
||||
func follow() -> AnyPublisher<Never, Error> {
|
||||
mastodonAPIClient.request(RelationshipEndpoint.accountsFollow(id: account.id))
|
||||
.flatMap { contentDatabase.insert(relationships: [$0]) }
|
||||
.eraseToAnyPublisher()
|
||||
relationshipAction(.accountsFollow(id: account.id))
|
||||
}
|
||||
|
||||
func unfollow() -> AnyPublisher<Never, Error> {
|
||||
mastodonAPIClient.request(RelationshipEndpoint.accountsUnfollow(id: account.id))
|
||||
.flatMap {
|
||||
contentDatabase.insert(relationships: [$0])
|
||||
.merge(with: contentDatabase.unfollow(id: account.id))
|
||||
}
|
||||
relationshipAction(.accountsUnfollow(id: account.id))
|
||||
.collect()
|
||||
.flatMap { _ in contentDatabase.unfollow(id: account.id) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func hideReblogs() -> AnyPublisher<Never, Error> {
|
||||
relationshipAction(.accountsFollow(id: account.id, showReblogs: false))
|
||||
}
|
||||
|
||||
func showReblogs() -> AnyPublisher<Never, Error> {
|
||||
relationshipAction(.accountsFollow(id: account.id, showReblogs: true))
|
||||
}
|
||||
|
||||
func block() -> AnyPublisher<Never, Error> {
|
||||
relationshipAction(.accountsBlock(id: account.id))
|
||||
.collect()
|
||||
.flatMap { _ in contentDatabase.block(id: account.id) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func unblock() -> AnyPublisher<Never, Error> {
|
||||
relationshipAction(.accountsUnblock(id: account.id))
|
||||
}
|
||||
|
||||
func mute() -> AnyPublisher<Never, Error> {
|
||||
relationshipAction(.accountsMute(id: account.id))
|
||||
.collect()
|
||||
.flatMap { _ in contentDatabase.mute(id: account.id) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func unmute() -> AnyPublisher<Never, Error> {
|
||||
relationshipAction(.accountsUnmute(id: account.id))
|
||||
}
|
||||
|
||||
func pin() -> AnyPublisher<Never, Error> {
|
||||
relationshipAction(.accountsPin(id: account.id))
|
||||
}
|
||||
|
||||
func unpin() -> AnyPublisher<Never, Error> {
|
||||
relationshipAction(.accountsUnpin(id: account.id))
|
||||
}
|
||||
|
||||
func set(note: String) -> AnyPublisher<Never, Error> {
|
||||
relationshipAction(.note(note, id: account.id))
|
||||
}
|
||||
}
|
||||
|
||||
private extension AccountService {
|
||||
func relationshipAction(_ endpoint: RelationshipEndpoint) -> AnyPublisher<Never, Error> {
|
||||
mastodonAPIClient.request(endpoint)
|
||||
.flatMap { contentDatabase.insert(relationships: [$0]) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Combine
|
||||
import Mastodon
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
|
@ -24,9 +25,18 @@ final class ProfileViewController: TableViewController {
|
|||
|
||||
viewModel.$accountViewModel
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
accountHeaderView.viewModel = self?.viewModel
|
||||
self?.sizeTableHeaderFooterViews()
|
||||
.sink { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
accountHeaderView.viewModel = self.viewModel
|
||||
self.sizeTableHeaderFooterViews()
|
||||
|
||||
if let accountViewModel = $0,
|
||||
let relationship = accountViewModel.relationship {
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||
image: UIImage(systemName: "ellipsis.circle"),
|
||||
menu: self.menu(accountViewModel: accountViewModel, relationship: relationship))
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
|
@ -54,3 +64,68 @@ final class ProfileViewController: TableViewController {
|
|||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
|
||||
private extension ProfileViewController {
|
||||
// swiftlint:disable:next function_body_length
|
||||
func menu(accountViewModel: AccountViewModel, relationship: Relationship) -> UIMenu {
|
||||
var actions = [UIAction]()
|
||||
|
||||
if relationship.following {
|
||||
if relationship.showingReblogs {
|
||||
actions.append(UIAction(
|
||||
title: String.localizedStringWithFormat(
|
||||
NSLocalizedString("account.hide-reblogs-account", comment: ""),
|
||||
accountViewModel.accountName),
|
||||
image: UIImage(systemName: "arrow.2.squarepath")) { _ in
|
||||
accountViewModel.hideReblogs()
|
||||
})
|
||||
} else {
|
||||
actions.append(UIAction(
|
||||
title: String.localizedStringWithFormat(
|
||||
NSLocalizedString("account.show-reblogs-account", comment: ""),
|
||||
accountViewModel.accountName),
|
||||
image: UIImage(systemName: "arrow.2.squarepath")) { _ in
|
||||
accountViewModel.showReblogs()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if relationship.muting {
|
||||
actions.append(UIAction(
|
||||
title: String.localizedStringWithFormat(
|
||||
NSLocalizedString("account.unmute-account", comment: ""),
|
||||
accountViewModel.accountName),
|
||||
image: UIImage(systemName: "speaker")) { _ in
|
||||
accountViewModel.unmute()
|
||||
})
|
||||
} else {
|
||||
actions.append(UIAction(
|
||||
title: String.localizedStringWithFormat(
|
||||
NSLocalizedString("account.mute-account", comment: ""),
|
||||
accountViewModel.accountName),
|
||||
image: UIImage(systemName: "speaker.slash")) { _ in
|
||||
accountViewModel.mute()
|
||||
})
|
||||
}
|
||||
|
||||
if relationship.blocking {
|
||||
actions.append(UIAction(
|
||||
title: String.localizedStringWithFormat(
|
||||
NSLocalizedString("account.unblock-account", comment: ""),
|
||||
accountViewModel.accountName),
|
||||
image: UIImage(systemName: "slash.circle")) { _ in
|
||||
accountViewModel.unblock()
|
||||
})
|
||||
} else {
|
||||
actions.append(UIAction(
|
||||
title: String.localizedStringWithFormat(
|
||||
NSLocalizedString("account.block-account", comment: ""),
|
||||
accountViewModel.accountName),
|
||||
image: UIImage(systemName: "slash.circle")) { _ in
|
||||
accountViewModel.block()
|
||||
})
|
||||
}
|
||||
|
||||
return UIMenu(children: actions)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,10 +67,52 @@ public extension AccountViewModel {
|
|||
}
|
||||
|
||||
func follow() {
|
||||
eventsSubject.send(accountService.follow().map { _ in .ignorableOutput }.eraseToAnyPublisher())
|
||||
ignorableOutputEvent(accountService.follow())
|
||||
}
|
||||
|
||||
func unfollow() {
|
||||
eventsSubject.send(accountService.unfollow().map { _ in .ignorableOutput }.eraseToAnyPublisher())
|
||||
ignorableOutputEvent(accountService.unfollow())
|
||||
}
|
||||
|
||||
func hideReblogs() {
|
||||
ignorableOutputEvent(accountService.hideReblogs())
|
||||
}
|
||||
|
||||
func showReblogs() {
|
||||
ignorableOutputEvent(accountService.showReblogs())
|
||||
}
|
||||
|
||||
func block() {
|
||||
ignorableOutputEvent(accountService.block())
|
||||
}
|
||||
|
||||
func unblock() {
|
||||
ignorableOutputEvent(accountService.unblock())
|
||||
}
|
||||
|
||||
func mute() {
|
||||
ignorableOutputEvent(accountService.mute())
|
||||
}
|
||||
|
||||
func unmute() {
|
||||
ignorableOutputEvent(accountService.unmute())
|
||||
}
|
||||
|
||||
func pin() {
|
||||
ignorableOutputEvent(accountService.pin())
|
||||
}
|
||||
|
||||
func unpin() {
|
||||
ignorableOutputEvent(accountService.unpin())
|
||||
}
|
||||
|
||||
func set(note: String) {
|
||||
ignorableOutputEvent(accountService.set(note: note))
|
||||
}
|
||||
}
|
||||
|
||||
private extension AccountViewModel {
|
||||
func ignorableOutputEvent(_ action: AnyPublisher<Never, Error>) {
|
||||
eventsSubject.send(action.map { _ in .ignorableOutput }.eraseToAnyPublisher())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ extension ProfileViewModel: CollectionViewModel {
|
|||
}
|
||||
|
||||
public var expandAll: AnyPublisher<ExpandAllState, Never> {
|
||||
collectionViewModel.flatMap(\.expandAll).eraseToAnyPublisher()
|
||||
Empty().eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public var alertItems: AnyPublisher<AlertItem, Never> {
|
||||
|
|
Loading…
Reference in New Issue