mirror of
https://github.com/mastodon/mastodon-ios
synced 2025-04-11 22:58:02 +02:00
Allow unfollow action from new notifications view
Contributes to #399 [BUG] Multiple interactions do not collapse into a single notification
This commit is contained in:
parent
872ac4c5cf
commit
c0f1e60c71
@ -259,9 +259,15 @@ enum RelationshipElement: Equatable {
|
||||
case acceptRejectButtons(isFollowing: Bool)
|
||||
case acceptedLabel
|
||||
case rejectedLabel
|
||||
case mutualLabel
|
||||
case followingLabel
|
||||
case pendingRequestLabel
|
||||
case mutualButton
|
||||
case followingButton
|
||||
case pendingRequestButton
|
||||
|
||||
enum FollowAction {
|
||||
case follow
|
||||
case unfollow
|
||||
case noAction
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
@ -283,11 +289,11 @@ enum RelationshipElement: Equatable {
|
||||
return "accepted"
|
||||
case .rejectedLabel:
|
||||
return "rejected"
|
||||
case .mutualLabel:
|
||||
case .mutualButton:
|
||||
return "mutual"
|
||||
case .followingLabel:
|
||||
case .followingButton:
|
||||
return "following"
|
||||
case .pendingRequestLabel:
|
||||
case .pendingRequestButton:
|
||||
return "pending"
|
||||
}
|
||||
}
|
||||
@ -296,7 +302,34 @@ enum RelationshipElement: Equatable {
|
||||
{
|
||||
return lhs.description == rhs.description
|
||||
}
|
||||
|
||||
var followAction: FollowAction {
|
||||
switch self {
|
||||
case .followButton, .requestButton:
|
||||
return .follow
|
||||
case .followingButton, .mutualButton, .pendingRequestButton:
|
||||
return .unfollow
|
||||
default:
|
||||
return .noAction
|
||||
}
|
||||
}
|
||||
|
||||
var buttonText: String? {
|
||||
switch self {
|
||||
case .followButton:
|
||||
return L10n.Common.Controls.Friendship.follow
|
||||
case .requestButton:
|
||||
return L10n.Common.Controls.Friendship.request
|
||||
case .mutualButton:
|
||||
return L10n.Common.Controls.Friendship.mutual
|
||||
case .followingButton:
|
||||
return L10n.Common.Controls.Friendship.following
|
||||
case .pendingRequestButton:
|
||||
return L10n.Common.Controls.Friendship.pending
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Entity.Relationship {
|
||||
@ -304,9 +337,9 @@ extension Mastodon.Entity.Relationship {
|
||||
var relationshipElement: RelationshipElement? {
|
||||
switch (following, followedBy) {
|
||||
case (true, true):
|
||||
return .mutualLabel
|
||||
return .mutualButton
|
||||
case (true, false):
|
||||
return .followingLabel
|
||||
return .followingButton
|
||||
case (false, true):
|
||||
if let account: NotificationAuthor = MastodonFeedItemCacheManager
|
||||
.shared.fullAccount(id)
|
||||
@ -314,7 +347,7 @@ extension Mastodon.Entity.Relationship {
|
||||
account.locked
|
||||
{
|
||||
if requested {
|
||||
return .pendingRequestLabel
|
||||
return .pendingRequestButton
|
||||
} else {
|
||||
return .requestButton
|
||||
}
|
||||
@ -549,7 +582,7 @@ struct NotificationRowView: View {
|
||||
trailingElement, grouped: accountInfo.totalActorCount > 1)
|
||||
}
|
||||
}
|
||||
.frame(height: smallAvatarSize) // this keeps GeometryReader from causing inconsistent visual spacing in the VStack
|
||||
.frame(height: smallAvatarSize) // this keeps GeometryReader from causing inconsistent visual spacing in the VStack
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@ -559,21 +592,14 @@ struct NotificationRowView: View {
|
||||
switch (elementType, grouped) {
|
||||
case (.fetching, false):
|
||||
ProgressView().progressViewStyle(.circular)
|
||||
case (.followButton, false):
|
||||
Button(L10n.Common.Controls.Friendship.follow) {
|
||||
viewModel.doAvatarRowButtonAction()
|
||||
case (.followButton, false), (.mutualButton, false),
|
||||
(.followingButton, false), (.pendingRequestButton, false):
|
||||
if let buttonText = elementType.buttonText {
|
||||
Button(buttonText) {
|
||||
viewModel.doAvatarRowButtonAction()
|
||||
}
|
||||
.buttonStyle(FollowButton(elementType))
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.foregroundStyle(.background)
|
||||
.controlSize(.small)
|
||||
.bold()
|
||||
case (.requestButton, false):
|
||||
Button(L10n.Common.Controls.Friendship.request) {
|
||||
viewModel.doAvatarRowButtonAction()
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.controlSize(.small)
|
||||
.bold()
|
||||
case (.acceptRejectButtons(let isFollowing), false):
|
||||
HStack {
|
||||
|
||||
@ -599,12 +625,6 @@ struct NotificationRowView: View {
|
||||
Text(L10n.Scene.Notification.FollowRequest.accepted)
|
||||
case (.rejectedLabel, false):
|
||||
Text(L10n.Scene.Notification.FollowRequest.rejected)
|
||||
case (.mutualLabel, false):
|
||||
Text(L10n.Common.Controls.Friendship.mutual)
|
||||
case (.followingLabel, false):
|
||||
Text(L10n.Common.Controls.Friendship.following)
|
||||
case (.pendingRequestLabel, false):
|
||||
Text(L10n.Common.Controls.Friendship.pending)
|
||||
case (.error(_), _):
|
||||
Image(systemName: "exclamationmark.triangle").foregroundStyle(.gray)
|
||||
default:
|
||||
@ -852,3 +872,58 @@ extension Mastodon.Entity.Status {
|
||||
navigateToStatus: navigateToStatus)
|
||||
}
|
||||
}
|
||||
|
||||
struct FollowButton: ButtonStyle {
|
||||
private let followAction: RelationshipElement.FollowAction
|
||||
|
||||
init(_ relationshipElement: RelationshipElement) {
|
||||
followAction = relationshipElement.followAction
|
||||
}
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.padding([.horizontal], 12)
|
||||
.padding([.vertical], 8)
|
||||
.background(backgroundColor)
|
||||
.foregroundStyle(textColor)
|
||||
.controlSize(.small)
|
||||
.fontWeight(fontWeight)
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
|
||||
private var backgroundColor: Color {
|
||||
switch followAction {
|
||||
case .follow:
|
||||
return Color(uiColor: Asset.Colors.Button.userFollow.color)
|
||||
case .unfollow:
|
||||
return Color(uiColor: Asset.Colors.Button.userFollowing.color)
|
||||
case .noAction:
|
||||
assertionFailure()
|
||||
return .clear
|
||||
}
|
||||
}
|
||||
|
||||
private var textColor: Color {
|
||||
switch followAction {
|
||||
case .follow:
|
||||
return .white
|
||||
case .unfollow:
|
||||
return Color(uiColor: Asset.Colors.Button.userFollowingTitle.color)
|
||||
case .noAction:
|
||||
assertionFailure()
|
||||
return .clear
|
||||
}
|
||||
}
|
||||
|
||||
private var fontWeight: SwiftUICore.Font.Weight {
|
||||
switch followAction {
|
||||
case .follow:
|
||||
return .bold
|
||||
case .unfollow:
|
||||
return .light
|
||||
case .noAction:
|
||||
assertionFailure()
|
||||
return .regular
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -238,7 +238,7 @@ class NotificationRowViewModel: ObservableObject {
|
||||
|
||||
switch (type, relationship.following) {
|
||||
case (.follow, true):
|
||||
element = .mutualLabel
|
||||
element = .mutualButton
|
||||
case (.follow, false):
|
||||
element = .followButton
|
||||
case (.followRequest, _):
|
||||
@ -312,8 +312,8 @@ extension NotificationRowViewModel {
|
||||
switch avatarRow {
|
||||
case .avatarRow(let accountInfo, let relationshipElement):
|
||||
switch relationshipElement {
|
||||
case .followButton, .requestButton:
|
||||
await doFollow(accountInfo)
|
||||
case .followButton, .requestButton, .mutualButton, .followingButton, .pendingRequestButton:
|
||||
await doFollowAction(relationshipElement.followAction, notificationSourceAccounts: accountInfo)
|
||||
case .acceptRejectButtons:
|
||||
await doAcceptFollowRequest(accountInfo, accept: accept)
|
||||
default:
|
||||
@ -326,25 +326,33 @@ extension NotificationRowViewModel {
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func doFollow(_ accountInfo: NotificationSourceAccounts) async {
|
||||
guard let accountID = accountInfo.firstAccountID,
|
||||
private func doFollowAction(_ action: RelationshipElement.FollowAction, notificationSourceAccounts: NotificationSourceAccounts) async {
|
||||
guard let accountID = notificationSourceAccounts.firstAccountID,
|
||||
let authBox = AuthenticationServiceProvider.shared.currentActiveUser
|
||||
.value
|
||||
else { return }
|
||||
let startingAvatarRow = avatarRow
|
||||
avatarRow = .avatarRow(accountInfo, .fetching)
|
||||
avatarRow = .avatarRow(notificationSourceAccounts, .fetching)
|
||||
do {
|
||||
let updatedElement: RelationshipElement
|
||||
let response = try await APIService.shared.follow(
|
||||
accountID, authenticationBox: authBox)
|
||||
if response.following {
|
||||
updatedElement = .followingLabel
|
||||
} else if response.requested {
|
||||
updatedElement = .pendingRequestLabel
|
||||
} else {
|
||||
updatedElement = .error(nil)
|
||||
let response: Mastodon.Entity.Relationship
|
||||
switch action {
|
||||
case .follow:
|
||||
response = try await APIService.shared.follow(
|
||||
accountID, authenticationBox: authBox)
|
||||
case .unfollow:
|
||||
response = try await APIService.shared.unfollow(accountID, authenticationBox: authBox)
|
||||
case .noAction:
|
||||
throw AppError.unexpected("action attempted for relationship element that has no action")
|
||||
}
|
||||
avatarRow = .avatarRow(accountInfo, updatedElement)
|
||||
if response.following {
|
||||
updatedElement = .followingButton
|
||||
} else if response.requested {
|
||||
updatedElement = .pendingRequestButton
|
||||
} else {
|
||||
updatedElement = .followButton
|
||||
}
|
||||
avatarRow = .avatarRow(notificationSourceAccounts, updatedElement)
|
||||
} catch {
|
||||
presentError(error)
|
||||
avatarRow = startingAvatarRow
|
||||
|
@ -70,6 +70,15 @@ extension APIService {
|
||||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput().value
|
||||
}
|
||||
|
||||
public func unfollow(_ accountID: String, authenticationBox: MastodonAuthenticationBox) async throws -> Mastodon.Entity.Relationship {
|
||||
return try await Mastodon.API.Account.unfollow(
|
||||
session: session,
|
||||
domain: authenticationBox.domain,
|
||||
accountID: accountID,
|
||||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput().value
|
||||
}
|
||||
|
||||
public func toggleShowReblogs(
|
||||
for user: Mastodon.Entity.Account,
|
||||
|
Loading…
x
Reference in New Issue
Block a user