2
2
mirror of https://github.com/mastodon/mastodon-ios synced 2025-04-11 22:58:02 +02:00

Use images instead of text for buttons to approve/reject follow requests

Includes refactoring of the RelationshipElement enum to make them less confusing.

Contributes to #399 [BUG] Multiple interactions do not collapse into a single notification
This commit is contained in:
shannon 2025-02-13 09:18:41 -05:00
parent 24c17878b8
commit d411035930
2 changed files with 109 additions and 74 deletions

View File

@ -254,14 +254,11 @@ enum RelationshipElement: Equatable {
case unfetched(Mastodon.Entity.NotificationType, accountID: String)
case fetching
case error(Error?)
case followButton
case requestButton
case acceptRejectButtons(isFollowing: Bool)
case acceptedLabel
case rejectedLabel
case mutualButton
case followingButton
case pendingRequestButton
case iDoNotFollowThem(theirAccountIsLocked: Bool)
case iFollowThem(theyFollowMe: Bool)
case iHaveRequestedToFollowThem
case theyHaveRequestedToFollowMe(iFollowThem: Bool)
case iHaveAnsweredTheirRequestToFollowMe(didAccept: Bool)
enum FollowAction {
case follow
@ -279,22 +276,32 @@ enum RelationshipElement: Equatable {
return "fetching"
case .error:
return "error"
case .followButton:
return "follow"
case .requestButton:
return "request"
case .acceptRejectButtons:
return "acceptReject"
case .acceptedLabel:
return "accepted"
case .rejectedLabel:
return "rejected"
case .mutualButton:
return "mutual"
case .followingButton:
return "following"
case .pendingRequestButton:
return "pending"
case .iDoNotFollowThem(let theirAccountIsLocked):
if theirAccountIsLocked {
return "iDoNotFollowThem+canRequestToFollow"
} else {
return "iDoNotFollowThem+canFollow"
}
case .theyHaveRequestedToFollowMe(let iFollowThem):
if iFollowThem {
return "theyHaveRequestedToFollowMe+iFollowThem"
} else {
return "theyHaveRequestedToFollowMe+iDoNotFollowThem"
}
case .iHaveAnsweredTheirRequestToFollowMe(let didAccept):
if didAccept {
return "iAcceptedTheirFollowRequest"
} else {
return "iRejectedTheirFollowRequest"
}
case .iFollowThem(let theyFollowMe):
if theyFollowMe {
return "iFollowThem+theyFollowMe"
} else {
return "iFollowThem+theyDoNotFollowMe"
}
case .iHaveRequestedToFollowThem:
return "iHaveRequestedToFollowThem"
}
}
@ -305,9 +312,9 @@ enum RelationshipElement: Equatable {
var followAction: FollowAction {
switch self {
case .followButton, .requestButton:
case .iDoNotFollowThem:
return .follow
case .followingButton, .mutualButton, .pendingRequestButton:
case .iFollowThem, .iHaveRequestedToFollowThem:
return .unfollow
default:
return .noAction
@ -316,15 +323,19 @@ enum RelationshipElement: Equatable {
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:
case .iDoNotFollowThem(let theirAccountIsLocked):
if theirAccountIsLocked {
return L10n.Common.Controls.Friendship.request
} else {
return L10n.Common.Controls.Friendship.follow
}
case .iFollowThem(let theyFollowMe):
if theyFollowMe {
return L10n.Common.Controls.Friendship.mutual
} else {
return L10n.Common.Controls.Friendship.following
}
case .iHaveRequestedToFollowThem:
return L10n.Common.Controls.Friendship.pending
default:
return nil
@ -336,10 +347,8 @@ extension Mastodon.Entity.Relationship {
@MainActor
var relationshipElement: RelationshipElement? {
switch (following, followedBy) {
case (true, true):
return .mutualButton
case (true, false):
return .followingButton
case (true, _):
return .iFollowThem(theyFollowMe: followedBy)
case (false, true):
if let account: NotificationAuthor = MastodonFeedItemCacheManager
.shared.fullAccount(id)
@ -347,12 +356,12 @@ extension Mastodon.Entity.Relationship {
account.locked
{
if requested {
return .pendingRequestButton
return .iHaveRequestedToFollowThem
} else {
return .requestButton
return .iDoNotFollowThem(theirAccountIsLocked: true)
}
}
return .followButton
return .iDoNotFollowThem(theirAccountIsLocked: false)
case (false, false):
return nil
}
@ -592,41 +601,47 @@ struct NotificationRowView: View {
switch (elementType, grouped) {
case (.fetching, false):
ProgressView().progressViewStyle(.circular)
case (.followButton, false), (.mutualButton, false),
(.followingButton, false), (.pendingRequestButton, false):
case (.iDoNotFollowThem, false), (.iFollowThem, false), (.iHaveRequestedToFollowThem, false):
if let buttonText = elementType.buttonText {
Button(buttonText) {
viewModel.doAvatarRowButtonAction()
}
.buttonStyle(FollowButton(elementType))
}
case (.acceptRejectButtons(let isFollowing), false):
case (.theyHaveRequestedToFollowMe(let iFollowThem), false):
HStack {
if isFollowing {
Text(L10n.Common.Controls.Friendship.following)
if iFollowThem {
Button(L10n.Common.Controls.Friendship.following)
{
// TODO: allow unfollow here?
}
.buttonStyle(FollowButton(.iFollowThem(theyFollowMe: false)))
.fixedSize()
}
Button(L10n.Scene.Notification.FollowRequest.reject) {
Button(action: {
viewModel.doAvatarRowButtonAction(false)
}) {
lightwieghtImageView("xmark.circle", size: smallAvatarSize)
}
.buttonStyle(.borderedProminent)
.controlSize(.small)
.bold()
.buttonStyle(ImageButton(foregroundColor: .secondary, backgroundColor: .clear))
Button(L10n.Scene.Notification.FollowRequest.accept) {
Button(action: {
viewModel.doAvatarRowButtonAction(true)
}) {
lightwieghtImageView("checkmark.circle", size: smallAvatarSize)
}
.buttonStyle(.borderedProminent)
.controlSize(.small)
.bold()
.buttonStyle(ImageButton(foregroundColor: .secondary, backgroundColor: .clear))
}
case (.iHaveAnsweredTheirRequestToFollowMe(let didAccept), false):
if didAccept {
lightwieghtImageView("checkmark", size: smallAvatarSize)
} else {
lightwieghtImageView("xmark", size: smallAvatarSize)
}
case (.acceptedLabel, false):
Text(L10n.Scene.Notification.FollowRequest.accepted)
case (.rejectedLabel, false):
Text(L10n.Scene.Notification.FollowRequest.rejected)
case (.error(_), _):
Image(systemName: "exclamationmark.triangle").foregroundStyle(.gray)
lightwieghtImageView("exclamationmark.triangle", size: smallAvatarSize)
default:
Spacer().frame(width: 0)
}
@ -927,3 +942,24 @@ struct FollowButton: ButtonStyle {
}
}
}
struct ImageButton: ButtonStyle {
let foregroundColor: Color
let backgroundColor: Color
func makeBody(configuration: Configuration) -> some View {
configuration.label
.foregroundStyle(foregroundColor)
.background(backgroundColor)
.clipShape(Capsule())
}
}
@ViewBuilder func lightwieghtImageView(_ systemName: String, size: CGFloat) -> some View {
Image(systemName: systemName)
.resizable()
.aspectRatio(contentMode: .fit)
.fontWeight(.light)
.frame(width: size, height: size)
}

View File

@ -226,7 +226,7 @@ class NotificationRowViewModel: ObservableObject {
) {
switch type {
case .follow, .followRequest:
guard let accountID = sourceAccounts.firstAccountID else { return }
guard let accountID = sourceAccounts.firstAccountID, let accountIsLocked = sourceAccounts.primaryAuthorAccount?.locked else { return }
avatarRow = .avatarRow(sourceAccounts, .fetching)
Task { @MainActor in
@ -238,12 +238,11 @@ class NotificationRowViewModel: ObservableObject {
switch (type, relationship.following) {
case (.follow, true):
element = .mutualButton
element = .iFollowThem(theyFollowMe: true)
case (.follow, false):
element = .followButton
element = .iDoNotFollowThem(theirAccountIsLocked: accountIsLocked)
case (.followRequest, _):
element = .acceptRejectButtons(
isFollowing: relationship.following)
element = .theyHaveRequestedToFollowMe(iFollowThem: relationship.following)
default:
element = .noneNeeded
}
@ -312,10 +311,10 @@ extension NotificationRowViewModel {
switch avatarRow {
case .avatarRow(let accountInfo, let relationshipElement):
switch relationshipElement {
case .followButton, .requestButton, .mutualButton, .followingButton, .pendingRequestButton:
case .iDoNotFollowThem, .iFollowThem, .iHaveRequestedToFollowThem:
await doFollowAction(relationshipElement.followAction, notificationSourceAccounts: accountInfo)
case .acceptRejectButtons:
await doAcceptFollowRequest(accountInfo, accept: accept)
case .theyHaveRequestedToFollowMe:
await doAnswerFollowRequest(accountInfo, accept: accept)
default:
return
}
@ -327,7 +326,7 @@ extension NotificationRowViewModel {
@MainActor
private func doFollowAction(_ action: RelationshipElement.FollowAction, notificationSourceAccounts: NotificationSourceAccounts) async {
guard let accountID = notificationSourceAccounts.firstAccountID,
guard let accountID = notificationSourceAccounts.firstAccountID, let theirAccountIsLocked = notificationSourceAccounts.primaryAuthorAccount?.locked,
let authBox = AuthenticationServiceProvider.shared.currentActiveUser
.value
else { return }
@ -346,11 +345,11 @@ extension NotificationRowViewModel {
throw AppError.unexpected("action attempted for relationship element that has no action")
}
if response.following {
updatedElement = .followingButton
updatedElement = .iFollowThem(theyFollowMe: response.followedBy)
} else if response.requested {
updatedElement = .pendingRequestButton
updatedElement = .iHaveRequestedToFollowThem
} else {
updatedElement = .followButton
updatedElement = .iDoNotFollowThem(theirAccountIsLocked: theirAccountIsLocked)
}
avatarRow = .avatarRow(notificationSourceAccounts, updatedElement)
} catch {
@ -360,7 +359,7 @@ extension NotificationRowViewModel {
}
@MainActor
private func doAcceptFollowRequest(
private func doAnswerFollowRequest(
_ accountInfo: NotificationSourceAccounts, accept: Bool
) async {
guard let accountID = accountInfo.firstAccountID,
@ -381,7 +380,7 @@ extension NotificationRowViewModel {
return
}
self.avatarRow = .avatarRow(
accountInfo, accept ? .acceptedLabel : .rejectedLabel)
accountInfo, .iHaveAnsweredTheirRequestToFollowMe(didAccept: accept))
} catch {
presentError(error)
self.avatarRow = startingAvatarRow