mirror of
https://github.com/mastodon/mastodon-ios
synced 2025-04-11 22:58:02 +02:00
Add label above notification to call attention to public vs. private mentions and replies
Contributes to #399 [BUG] Multiple interactions do not collapse into a single notification
This commit is contained in:
parent
4847c461a0
commit
c85bbad148
@ -156,6 +156,10 @@
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"reply": "Reply",
|
||||
"private_reply": "Private reply",
|
||||
"mention": "Mention",
|
||||
"private_mention": "Private mention",
|
||||
"user_reblogged": "%s boosted",
|
||||
"user_replied_to": "Replied to %s",
|
||||
"show_post": "Show Post",
|
||||
|
@ -633,15 +633,27 @@ struct FilteredNotificationsRowView: View {
|
||||
}
|
||||
}
|
||||
|
||||
let actionSuperheaderHeight: CGFloat = 20
|
||||
|
||||
struct NotificationRowView: View {
|
||||
@ObservedObject var viewModel: NotificationRowViewModel
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
HStack(alignment: .top) {
|
||||
if let iconStyle = viewModel.iconStyle {
|
||||
// LEFT GUTTER WITH TOP-ALIGNED ICON or AVATAR
|
||||
VStack {
|
||||
Spacer()
|
||||
VStack(spacing: 4) {
|
||||
if let actionSuperheader = viewModel.actionSuperheader {
|
||||
HStack {
|
||||
Spacer()
|
||||
Image(systemName: actionSuperheader.iconName)
|
||||
.font(.subheadline)
|
||||
.bold()
|
||||
.foregroundStyle(actionSuperheader.color)
|
||||
.frame(height: actionSuperheaderHeight)
|
||||
}
|
||||
}
|
||||
|
||||
switch iconStyle {
|
||||
case .icon:
|
||||
NotificationIconView(iconStyle)
|
||||
@ -653,10 +665,18 @@ struct NotificationRowView: View {
|
||||
}
|
||||
Spacer().frame(maxHeight: .infinity)
|
||||
}
|
||||
.fixedSize(horizontal: true, vertical: false)
|
||||
}
|
||||
|
||||
// VSTACK OF HEADER AND CONTENT COMPONENT VIEWS
|
||||
VStack(spacing: 4) {
|
||||
if let actionSuperheader = viewModel.actionSuperheader {
|
||||
componentView(.weightedText(actionSuperheader.text, .bold))
|
||||
.font(.subheadline)
|
||||
.foregroundColor(actionSuperheader.color)
|
||||
.frame(height: actionSuperheaderHeight)
|
||||
}
|
||||
|
||||
ForEach(viewModel.headerComponents) {
|
||||
componentView($0)
|
||||
}
|
||||
|
@ -2,9 +2,11 @@
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
import MastodonAsset
|
||||
import MastodonCore
|
||||
import MastodonLocalization
|
||||
import MastodonSDK
|
||||
import SwiftUICore
|
||||
|
||||
class NotificationRowViewModel: ObservableObject {
|
||||
let identifier: MastodonFeedItemIdentifier
|
||||
@ -16,7 +18,8 @@ class NotificationRowViewModel: ObservableObject {
|
||||
(SceneCoordinator.Scene, SceneCoordinator.Transition) -> Void
|
||||
let presentError: (Error) -> Void
|
||||
let defaultNavigation: (() -> Void)?
|
||||
public let iconStyle: GroupedNotificationType.MainIconStyle?
|
||||
let iconStyle: GroupedNotificationType.MainIconStyle?
|
||||
let actionSuperheader: (iconName: String, text: String, color: Color)?
|
||||
|
||||
@Published public var headerComponents: [NotificationViewComponent] = []
|
||||
public var contentComponents: [NotificationViewComponent] = []
|
||||
@ -59,9 +62,10 @@ class NotificationRowViewModel: ObservableObject {
|
||||
switch notificationInfo.groupedNotificationType {
|
||||
|
||||
case .follow, .followRequest:
|
||||
actionSuperheader = nil
|
||||
let avatarRowAdditionalElement: RelationshipElement
|
||||
if let account = notificationInfo.sourceAccounts
|
||||
.primaryAuthorAccount
|
||||
if notificationInfo.sourceAccounts
|
||||
.primaryAuthorAccount != nil
|
||||
{
|
||||
avatarRowAdditionalElement = .unfetched(
|
||||
notificationInfo.groupedNotificationType)
|
||||
@ -71,9 +75,9 @@ class NotificationRowViewModel: ObservableObject {
|
||||
avatarRow = .avatarRow(
|
||||
notificationInfo.sourceAccounts,
|
||||
avatarRowAdditionalElement)
|
||||
if let accountName = notificationInfo.sourceAccounts
|
||||
if (notificationInfo.sourceAccounts
|
||||
.primaryAuthorAccount?
|
||||
.displayNameWithFallback
|
||||
.displayNameWithFallback) != nil
|
||||
{
|
||||
headerTextComponents = [
|
||||
.text(
|
||||
@ -84,10 +88,10 @@ class NotificationRowViewModel: ObservableObject {
|
||||
}
|
||||
case .mention, .status:
|
||||
// TODO: eventually make this full status style, not inline
|
||||
// TODO: distinguish mentions from replies
|
||||
if let statusViewModel =
|
||||
notificationInfo.statusViewModel
|
||||
{
|
||||
actionSuperheader = NotificationRowViewModel.actionSuperheader(notificationInfo.groupedNotificationType, isReply: statusViewModel.isReply, isPrivateStatus: statusViewModel.visibility == .direct)
|
||||
headerTextComponents = [
|
||||
.text(
|
||||
notificationInfo.groupedNotificationType
|
||||
@ -96,9 +100,11 @@ class NotificationRowViewModel: ObservableObject {
|
||||
]
|
||||
contentComponents = [.status(statusViewModel)]
|
||||
} else {
|
||||
actionSuperheader = nil
|
||||
headerTextComponents = [._other("POST BY UNKNOWN ACCOUNT")]
|
||||
}
|
||||
case .reblog, .favourite:
|
||||
actionSuperheader = nil
|
||||
if let statusViewModel = notificationInfo.statusViewModel {
|
||||
avatarRow = .avatarRow(
|
||||
notificationInfo.sourceAccounts,
|
||||
@ -116,6 +122,7 @@ class NotificationRowViewModel: ObservableObject {
|
||||
]
|
||||
}
|
||||
case .poll, .update:
|
||||
actionSuperheader = nil
|
||||
if let statusViewModel =
|
||||
notificationInfo.statusViewModel
|
||||
{
|
||||
@ -132,6 +139,7 @@ class NotificationRowViewModel: ObservableObject {
|
||||
]
|
||||
}
|
||||
case .adminSignUp:
|
||||
actionSuperheader = nil
|
||||
avatarRow = .avatarRow(
|
||||
notificationInfo.sourceAccounts,
|
||||
.noneNeeded)
|
||||
@ -141,6 +149,7 @@ class NotificationRowViewModel: ObservableObject {
|
||||
notificationInfo.sourceAccounts) ?? "")
|
||||
]
|
||||
case .adminReport(let report):
|
||||
actionSuperheader = nil
|
||||
if let summary = report?.summary {
|
||||
headerTextComponents = [.text(summary)]
|
||||
}
|
||||
@ -150,6 +159,7 @@ class NotificationRowViewModel: ObservableObject {
|
||||
contentComponents = [.text(comment)]
|
||||
}
|
||||
case .severedRelationships(let severanceEvent):
|
||||
actionSuperheader = nil
|
||||
if let summary = severanceEvent?.summary(myDomain: myAccountDomain)
|
||||
{
|
||||
headerTextComponents = [.text(summary)]
|
||||
@ -168,6 +178,7 @@ class NotificationRowViewModel: ObservableObject {
|
||||
notificationID: notificationInfo.newestNotificationID))
|
||||
]
|
||||
case .moderationWarning(let accountWarning):
|
||||
actionSuperheader = nil
|
||||
headerTextComponents = [
|
||||
.weightedText(
|
||||
(accountWarning?.action ?? .none).actionDescription,
|
||||
@ -192,12 +203,32 @@ class NotificationRowViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
case ._other(let text):
|
||||
actionSuperheader = nil
|
||||
headerTextComponents = [
|
||||
._other("UNEXPECTED NOTIFICATION TYPE: \(text)")
|
||||
]
|
||||
}
|
||||
resetHeaderComponents()
|
||||
}
|
||||
|
||||
static func actionSuperheader(_ notificationType: GroupedNotificationType, isReply: Bool, isPrivateStatus: Bool?) -> (iconName: String, text: String, color: Color)? {
|
||||
guard let isPrivateStatus else { return nil }
|
||||
switch notificationType {
|
||||
case .mention:
|
||||
switch (isReply, isPrivateStatus) {
|
||||
case (true, false):
|
||||
return (iconName: "arrow.turn.up.left", text: L10n.Common.Controls.Status.reply, color: .gray)
|
||||
case (true, true):
|
||||
return (iconName: "arrow.turn.up.left", text: L10n.Common.Controls.Status.privateReply, color: Asset.Colors.accent.swiftUIColor)
|
||||
case (false, false):
|
||||
return (iconName: "at", text: L10n.Common.Controls.Status.mention, color: .gray)
|
||||
case (false, true):
|
||||
return (iconName: "at", text: L10n.Common.Controls.Status.privateMention, color: Asset.Colors.accent.swiftUIColor)
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func prepareForDisplay() {
|
||||
if let avatarRow {
|
||||
|
@ -347,10 +347,18 @@ public enum L10n {
|
||||
public static let loadEmbed = L10n.tr("Localizable", "Common.Controls.Status.LoadEmbed", fallback: "Load Embed")
|
||||
/// Tap anywhere to reveal
|
||||
public static let mediaContentWarning = L10n.tr("Localizable", "Common.Controls.Status.MediaContentWarning", fallback: "Tap anywhere to reveal")
|
||||
/// Mention
|
||||
public static let mention = L10n.tr("Localizable", "Common.Controls.Status.Mention", fallback: "Mention")
|
||||
/// %@ via %@
|
||||
public static func postedViaApplication(_ p1: Any, _ p2: Any) -> String {
|
||||
return L10n.tr("Localizable", "Common.Controls.Status.PostedViaApplication", String(describing: p1), String(describing: p2), fallback: "%@ via %@")
|
||||
}
|
||||
/// Private mention
|
||||
public static let privateMention = L10n.tr("Localizable", "Common.Controls.Status.PrivateMention", fallback: "Private mention")
|
||||
/// Private reply
|
||||
public static let privateReply = L10n.tr("Localizable", "Common.Controls.Status.PrivateReply", fallback: "Private reply")
|
||||
/// Reply
|
||||
public static let reply = L10n.tr("Localizable", "Common.Controls.Status.Reply", fallback: "Reply")
|
||||
/// Sensitive Content
|
||||
public static let sensitiveContent = L10n.tr("Localizable", "Common.Controls.Status.SensitiveContent", fallback: "Sensitive Content")
|
||||
/// Show Post
|
||||
|
@ -171,6 +171,10 @@ Please check your internet connection.";
|
||||
"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@";
|
||||
"Common.Controls.Status.Translation.UnknownLanguage" = "Unknown";
|
||||
"Common.Controls.Status.Translation.UnknownProvider" = "Unknown";
|
||||
"Common.Controls.Status.Reply" = "Reply";
|
||||
"Common.Controls.Status.PrivateReply" = "Private reply";
|
||||
"Common.Controls.Status.Mention" = "Mention";
|
||||
"Common.Controls.Status.PrivateMention" = "Private mention";
|
||||
"Common.Controls.Status.UserReblogged" = "%@ boosted";
|
||||
"Common.Controls.Status.UserRepliedTo" = "Replied to %@";
|
||||
"Common.Controls.Status.Visibility.Direct" = "Only mentioned user can see this post.";
|
||||
|
Loading…
x
Reference in New Issue
Block a user