diff --git a/DB/Sources/DB/Content/ContentDatabase+Migration.swift b/DB/Sources/DB/Content/ContentDatabase+Migration.swift index b5f46ce..cf9d662 100644 --- a/DB/Sources/DB/Content/ContentDatabase+Migration.swift +++ b/DB/Sources/DB/Content/ContentDatabase+Migration.swift @@ -290,6 +290,12 @@ extension ContentDatabase { } } + migrator.registerMigration("1.0.0-notifying") { db in + try db.alter(table: "relationship") { t in + t.add(column: "notifying", .boolean) + } + } + return migrator } } diff --git a/Localizations/Localizable.strings b/Localizations/Localizable.strings index fb7460e..95e906c 100644 --- a/Localizations/Localizable.strings +++ b/Localizations/Localizable.strings @@ -34,6 +34,7 @@ "account.mute.confirm.duration" = "Duration"; "account.mute.target-%@" = "Mute %@"; "account.muted" = "Muted"; +"account.notify" = "Turn on notifications"; "account.reject-follow-request-button.accessibility-label" = "Reject follow request"; "account.request" = "Request"; "account.request.cancel" = "Cancel follow request"; @@ -51,6 +52,7 @@ "account.unfollow.confirm-%@" = "Unfollow %@?"; "account.unmute" = "Unmute"; "account.unmute.confirm-%@" = "Unmute %@?"; +"account.unnotify" = "Turn off notifications"; "activity.open-in-default-browser" = "Open in default browser"; "add" = "Add"; "apns-default-message" = "New notification"; diff --git a/Mastodon/Sources/Mastodon/Entities/Relationship.swift b/Mastodon/Sources/Mastodon/Entities/Relationship.swift index b8747b0..b67e417 100644 --- a/Mastodon/Sources/Mastodon/Entities/Relationship.swift +++ b/Mastodon/Sources/Mastodon/Entities/Relationship.swift @@ -11,6 +11,7 @@ public struct Relationship: Codable, Hashable { public let muting: Bool @DecodableDefault.False public private(set) var mutingNotifications: Bool @DecodableDefault.False public private(set) var showingReblogs: Bool + public let notifying: Bool? public let blocking: Bool public let domainBlocking: Bool @DecodableDefault.False public private(set) var blockedBy: Bool diff --git a/MastodonAPI/Sources/MastodonAPI/Endpoints/RelationshipEndpoint.swift b/MastodonAPI/Sources/MastodonAPI/Endpoints/RelationshipEndpoint.swift index ff8411c..74262a2 100644 --- a/MastodonAPI/Sources/MastodonAPI/Endpoints/RelationshipEndpoint.swift +++ b/MastodonAPI/Sources/MastodonAPI/Endpoints/RelationshipEndpoint.swift @@ -5,7 +5,7 @@ import HTTP import Mastodon public enum RelationshipEndpoint { - case accountsFollow(id: Account.Id, showReblogs: Bool? = nil) + case accountsFollow(id: Account.Id, showReblogs: Bool? = nil, notify: Bool? = nil) case accountsUnfollow(id: Account.Id) case accountsBlock(id: Account.Id) case accountsUnblock(id: Account.Id) @@ -32,7 +32,7 @@ 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"] @@ -59,12 +59,18 @@ extension RelationshipEndpoint: Endpoint { public var queryParameters: [URLQueryItem] { switch self { - case let .accountsFollow(_, showReblogs): + case let .accountsFollow(_, showReblogs, notify): + var params = [URLQueryItem]() + if let showReblogs = showReblogs { - return [URLQueryItem(name: "reblogs", value: String(showReblogs))] - } else { - return [] + params.append(URLQueryItem(name: "reblogs", value: String(showReblogs))) } + + if let notify = notify { + params.append(URLQueryItem(name: "notify", value: String(notify))) + } + + return params default: return [] } diff --git a/ServiceLayer/Sources/ServiceLayer/Services/AccountService.swift b/ServiceLayer/Sources/ServiceLayer/Services/AccountService.swift index 9dfeef0..5b3bfb3 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/AccountService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/AccountService.swift @@ -51,6 +51,14 @@ public extension AccountService { relationshipAction(.accountsFollow(id: account.id, showReblogs: true)) } + func notify() -> AnyPublisher { + relationshipAction(.accountsFollow(id: account.id, notify: true)) + } + + func unnotify() -> AnyPublisher { + relationshipAction(.accountsFollow(id: account.id, notify: false)) + } + func block() -> AnyPublisher { relationshipAction(.accountsBlock(id: account.id)) .collect() diff --git a/ViewModels/Sources/ViewModels/View Models/AccountViewModel.swift b/ViewModels/Sources/ViewModels/View Models/AccountViewModel.swift index b48903b..e6a6588 100644 --- a/ViewModels/Sources/ViewModels/View Models/AccountViewModel.swift +++ b/ViewModels/Sources/ViewModels/View Models/AccountViewModel.swift @@ -137,6 +137,14 @@ public extension AccountViewModel { ignorableOutputEvent(accountService.showReblogs()) } + func notify() { + ignorableOutputEvent(accountService.notify()) + } + + func unnotify() { + ignorableOutputEvent(accountService.unnotify()) + } + func confirmBlock() { eventsSubject.send(Just(.confirmBlock(self)).setFailureType(to: Error.self).eraseToAnyPublisher()) } diff --git a/Views/UIKit/AccountHeaderView.swift b/Views/UIKit/AccountHeaderView.swift index b7da474..0ca588b 100644 --- a/Views/UIKit/AccountHeaderView.swift +++ b/Views/UIKit/AccountHeaderView.swift @@ -15,6 +15,8 @@ final class AccountHeaderView: UIView { let relationshipButtonsStackView = UIStackView() let followButton = UIButton(type: .system) let unfollowButton = UIButton(type: .system) + let notifyButton = UIButton() + let unnotifyButton = UIButton() let displayNameLabel = AnimatedAttachmentLabel() let accountStackView = UIStackView() let accountLabel = UILabel() @@ -69,6 +71,19 @@ final class AccountHeaderView: UIView { comment: ""), for: .normal) + if relationship.following, let notifying = relationship.notifying { + if notifying { + notifyButton.isHidden = true + unnotifyButton.isHidden = false + } else { + notifyButton.isHidden = false + unnotifyButton.isHidden = true + } + } else { + notifyButton.isHidden = true + unnotifyButton.isHidden = true + } + relationshipButtonsStackView.isHidden = false unavailableLabel.isHidden = !relationship.blockedBy } else { @@ -201,7 +216,7 @@ final class AccountHeaderView: UIView { override func layoutSubviews() { super.layoutSubviews() - for button in [followButton, unfollowButton] { + for button in [followButton, unfollowButton, notifyButton, unnotifyButton] { let inset = (followButton.bounds.height - (button.titleLabel?.bounds.height ?? 0)) / 2 button.contentEdgeInsets = .init(top: 0, left: inset, bottom: 0, right: inset) @@ -284,7 +299,7 @@ private extension AccountHeaderView { relationshipButtonsStackView.spacing = .defaultSpacing relationshipButtonsStackView.addArrangedSubview(UIView()) - for button in [followButton, unfollowButton] { + for button in [followButton, unfollowButton, notifyButton, unnotifyButton] { relationshipButtonsStackView.addArrangedSubview(button) button.titleLabel?.font = .preferredFont(forTextStyle: .headline) button.titleLabel?.adjustsFontForContentSizeCategory = true @@ -296,6 +311,7 @@ private extension AccountHeaderView { systemName: "person.badge.plus", withConfiguration: UIImage.SymbolConfiguration(scale: .small)), for: .normal) + followButton.isHidden = true followButton.addAction( UIAction { [weak self] _ in self?.viewModel.accountViewModel?.follow() }, for: .touchUpInside) @@ -306,10 +322,32 @@ private extension AccountHeaderView { withConfiguration: UIImage.SymbolConfiguration(scale: .small)), for: .normal) unfollowButton.setTitle(NSLocalizedString("account.unfollow", comment: ""), for: .normal) + unfollowButton.isHidden = true unfollowButton.addAction( UIAction { [weak self] _ in self?.viewModel.accountViewModel?.confirmUnfollow() }, for: .touchUpInside) + notifyButton.setImage( + UIImage(systemName: "bell", + withConfiguration: UIImage.SymbolConfiguration(scale: .large)), + for: .normal) + notifyButton.accessibilityLabel = NSLocalizedString("account.notify", comment: "") + notifyButton.tintColor = .secondaryLabel + notifyButton.isHidden = true + notifyButton.addAction( + UIAction { [weak self] _ in self?.viewModel.accountViewModel?.notify() }, + for: .touchUpInside) + + unnotifyButton.setImage( + UIImage(systemName: "bell.fill", + withConfiguration: UIImage.SymbolConfiguration(scale: .large)), + for: .normal) + unnotifyButton.accessibilityLabel = NSLocalizedString("account.unnotify", comment: "") + unnotifyButton.isHidden = true + unnotifyButton.addAction( + UIAction { [weak self] _ in self?.viewModel.accountViewModel?.unnotify() }, + for: .touchUpInside) + addSubview(baseStackView) baseStackView.translatesAutoresizingMaskIntoConstraints = false baseStackView.axis = .vertical