diff --git a/Localizations/en.lproj/Localizable.strings b/Localizations/en.lproj/Localizable.strings index f7f6054..12c7a4d 100644 --- a/Localizations/en.lproj/Localizable.strings +++ b/Localizations/en.lproj/Localizable.strings @@ -253,6 +253,7 @@ "preferences.require-double-tap-to-favorite" = "Require double tap to favorite"; "preferences.show-reblog-and-favorite-counts" = "Show boost and favorite counts"; "preferences.status-word" = "Status word"; +"preferences.display-favorites-as" = "Display favorites as"; "filters.active" = "Active"; "filters.expired" = "Expired"; "filter.add-new" = "Add New Filter"; @@ -279,6 +280,7 @@ "notifications" = "Notifications"; "notifications.reblogged-your-status-%@" = "%@ boosted your status"; "notifications.favourited-your-status-%@" = "%@ favorited your status"; +"notifications.liked-your-status-%@" = "%@ liked your status"; "notifications.followed-you-%@" = "%@ followed you"; "notifications.poll-ended" = "A poll you have voted in has ended"; "notifications.your-poll-ended" = "Your poll has ended"; @@ -311,8 +313,11 @@ "status.delete.confirm.post" = "Are you sure you want to delete this post?"; "status.delete.confirm.toot" = "Are you sure you want to delete this toot?"; "status.delete-and-redraft" = "Delete & re-draft"; -"status.delete-and-redraft.confirm.post" = "Are you sure you want to delete this post and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned."; -"status.delete-and-redraft.confirm.toot" = "Are you sure you want to delete this toot and re-draft it? Favorites and boosts will be lost, and replies to the original toot will be orphaned."; +"status.delete-and-redraft.confirm.post-favorites" = "Are you sure you want to delete this post and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned."; +"status.delete-and-redraft.confirm.toot-favorites" = "Are you sure you want to delete this toot and re-draft it? Favorites and boosts will be lost, and replies to the original toot will be orphaned."; +"status.delete-and-redraft.confirm.post-likes" = "Are you sure you want to delete this post and re-draft it? Likes and boosts will be lost, and replies to the original post will be orphaned."; +"status.delete-and-redraft.confirm.toot-likes" = "Are you sure you want to delete this toot and re-draft it? Likes and boosts will be lost, and replies to the original toot will be orphaned."; + "status.mute" = "Mute conversation"; "status.new-items.post" = "New posts"; "status.new-items.toot" = "New toots"; @@ -331,6 +336,8 @@ "status.reblog-button.undo.accessibility-label" = "Unboost"; "status.favorite-button.accessibility-label" = "Favorite"; "status.favorite-button.undo.accessibility-label" = "Unfavorite"; +"status.like-button.accessibility-label" = "Like"; +"status.like-button.undo.accessibility-label" = "Unlike"; "status.show-more" = "Show More"; "status.show-more-all-button.accessibilty-label" = "Show more for all"; "status.show-less" = "Show Less"; @@ -356,3 +363,4 @@ "timelines.local" = "Local"; "timelines.federated" = "Federated"; "toot" = "Toot"; +"likes" = "Likes"; diff --git a/Localizations/en.lproj/Localizable.stringsdict b/Localizations/en.lproj/Localizable.stringsdict index 2cef6d1..05bc76f 100644 --- a/Localizations/en.lproj/Localizable.stringsdict +++ b/Localizations/en.lproj/Localizable.stringsdict @@ -66,6 +66,22 @@ %ld Favorites + status.likes-count-%ld + + NSStringLocalizedFormatKey + %#@likes@ + likes + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld Like + other + %ld Likes + + status.replies-count-%ld NSStringLocalizedFormatKey diff --git a/ServiceLayer/Sources/ServiceLayer/Utilities/AppPreferences.swift b/ServiceLayer/Sources/ServiceLayer/Utilities/AppPreferences.swift index 828fa03..c77b6b4 100644 --- a/ServiceLayer/Sources/ServiceLayer/Utilities/AppPreferences.swift +++ b/ServiceLayer/Sources/ServiceLayer/Utilities/AppPreferences.swift @@ -32,6 +32,13 @@ public extension AppPreferences { public var id: String { rawValue } } + enum DisplayFavoritesAs: String, CaseIterable, Identifiable { + case favorites + case likes + + public var id: String { rawValue } + } + enum AnimateAvatars: String, CaseIterable, Identifiable { case everywhere case profiles @@ -79,6 +86,18 @@ public extension AppPreferences { set { self[.statusWord] = newValue.rawValue } } + var displayFavoritesAs: DisplayFavoritesAs { + get { + if let rawValue = self[.displayFavoritesAs] as String?, + let value = DisplayFavoritesAs(rawValue: rawValue) { + return value + } + + return .favorites + } + set { self[.displayFavoritesAs] = newValue.rawValue } + } + var animateAvatars: AnimateAvatars { get { if let rawValue = self[.animateAvatars] as String?, @@ -207,6 +226,7 @@ private extension AppPreferences { enum Item: String { case colorScheme case statusWord + case displayFavoritesAs case requireDoubleTapToReblog case requireDoubleTapToFavorite case animateAvatars diff --git a/View Controllers/TableViewController.swift b/View Controllers/TableViewController.swift index 0759127..44e2d80 100644 --- a/View Controllers/TableViewController.swift +++ b/View Controllers/TableViewController.swift @@ -687,12 +687,17 @@ private extension TableViewController { let deleteAndRedraftConfirmMessage: String let deleteConfirmMessage: String + let favoritesNoun = viewModel.identityContext.appPreferences.displayFavoritesAs.rawValue + switch viewModel.identityContext.appPreferences.statusWord { case .toot: - deleteAndRedraftConfirmMessage = NSLocalizedString("status.delete-and-redraft.confirm.toot", comment: "") + deleteAndRedraftConfirmMessage = NSLocalizedString( + "status.delete-and-redraft.confirm.toot-".appending(favoritesNoun), + comment: "") deleteConfirmMessage = NSLocalizedString("status.delete.confirm.toot", comment: "") case .post: - deleteAndRedraftConfirmMessage = NSLocalizedString("status.delete-and-redraft.confirm.post", comment: "") + deleteAndRedraftConfirmMessage = NSLocalizedString( + "status.delete-and-redraft.confirm.post-".appending(favoritesNoun), comment: "") deleteConfirmMessage = NSLocalizedString("status.delete.confirm.post", comment: "") } diff --git a/Views/SwiftUI/PreferencesView.swift b/Views/SwiftUI/PreferencesView.swift index 296e58d..24b1dfd 100644 --- a/Views/SwiftUI/PreferencesView.swift +++ b/Views/SwiftUI/PreferencesView.swift @@ -101,6 +101,12 @@ struct PreferencesView: View { Text(option.localizedStringKey).tag(option) } } + Picker("preferences.display-favorites-as", + selection: $identityContext.appPreferences.displayFavoritesAs) { + ForEach(AppPreferences.DisplayFavoritesAs.allCases) { option in + Text(option.localizedStringKey).tag(option) + } + } Toggle("preferences.show-reblog-and-favorite-counts", isOn: $identityContext.appPreferences.showReblogAndFavoriteCounts) Toggle("preferences.require-double-tap-to-reblog", @@ -182,6 +188,17 @@ extension AppPreferences.StatusWord { } } +extension AppPreferences.DisplayFavoritesAs { + var localizedStringKey: LocalizedStringKey { + switch self { + case .favorites: + return "favorites" + case .likes: + return "likes" + } + } +} + extension AppPreferences.AnimateAvatars { var localizedStringKey: LocalizedStringKey { switch self { diff --git a/Views/UIKit/Content Views/NotificationView.swift b/Views/UIKit/Content Views/NotificationView.swift index 0e29641..6b854dd 100644 --- a/Views/UIKit/Content Views/NotificationView.swift +++ b/Views/UIKit/Content Views/NotificationView.swift @@ -163,9 +163,36 @@ private extension NotificationView { isAccessibilityElement = true } + var colorForFavoriteView: UIColor { + switch notificationConfiguration.viewModel.identityContext.appPreferences.displayFavoritesAs { + case .favorites: + return .systemYellow + case .likes: + return .systemRed + } + } + + var labelForFavoriteView: String { + switch notificationConfiguration.viewModel.identityContext.appPreferences.displayFavoritesAs { + case .favorites: + return "notifications.favourited-your-status-%@" + case .likes: + return "notifications.liked-your-status-%@" + } + } + + var imageNameForFavoriteView: String { + switch notificationConfiguration.viewModel.identityContext.appPreferences.displayFavoritesAs { + case .favorites: + return "star.fill" + case .likes: + return "heart.fill" + } + } + func applyNotificationConfiguration() { let viewModel = notificationConfiguration.viewModel - + var imageName = viewModel.type.systemImageName avatarImageView.sd_setImage(with: viewModel.accountViewModel.avatarURL()) switch viewModel.type { @@ -184,12 +211,13 @@ private extension NotificationView { identityContext: viewModel.identityContext) iconImageView.tintColor = .systemGreen case .favourite: - typeLabel.attributedText = "notifications.favourited-your-status-%@".localizedBolding( + imageName = imageNameForFavoriteView + typeLabel.attributedText = labelForFavoriteView.localizedBolding( displayName: viewModel.accountViewModel.displayName, emojis: viewModel.accountViewModel.emojis, label: typeLabel, identityContext: viewModel.identityContext) - iconImageView.tintColor = .systemYellow + iconImageView.tintColor = colorForFavoriteView case .poll: typeLabel.text = NSLocalizedString( viewModel.accountViewModel.isSelf @@ -229,7 +257,7 @@ private extension NotificationView { timeLabel.accessibilityLabel = viewModel.accessibilityTime iconImageView.image = UIImage( - systemName: viewModel.type.systemImageName, + systemName: imageName, withConfiguration: UIImage.SymbolConfiguration(scale: .medium)) let accessibilityAttributedLabel = NSMutableAttributedString(string: "") diff --git a/Views/UIKit/Content Views/StatusView.swift b/Views/UIKit/Content Views/StatusView.swift index 89a103e..9eb5c34 100644 --- a/Views/UIKit/Content Views/StatusView.swift +++ b/Views/UIKit/Content Views/StatusView.swift @@ -451,6 +451,15 @@ private extension StatusView { .store(in: &cancellables) } + var favoriteCountLabel: String { + switch statusConfiguration.viewModel.identityContext.appPreferences.displayFavoritesAs { + case .favorites: + return "status.favorites-count-%ld" + case .likes: + return "status.likes-count-%ld" + } + } + func applyStatusConfiguration() { let viewModel = statusConfiguration.viewModel let isContextParent = viewModel.configuration.isContextParent @@ -593,8 +602,9 @@ private extension StatusView { localizationKey: "status.reblogs-count-%ld", count: viewModel.reblogsCount) rebloggedByButton.isHidden = noReblogs + favoritedByButton.setAttributedLocalizedTitle( - localizationKey: "status.favorites-count-%ld", + localizationKey: favoriteCountLabel, count: viewModel.favoritesCount) favoritedByButton.isHidden = noFavorites @@ -822,7 +832,7 @@ private extension StatusView { if statusConfiguration.viewModel.favoritesCount > 0 { accessibilityAttributedLabel.appendWithSeparator( String.localizedStringWithFormat( - NSLocalizedString("status.favorites-count-%ld", comment: ""), + NSLocalizedString(favoriteCountLabel, comment: ""), statusConfiguration.viewModel.favoritesCount)) } } @@ -835,10 +845,24 @@ private extension StatusView { return accessibilityAttributedLabel } + var favoriteIcon: String { + switch statusConfiguration.viewModel.identityContext.appPreferences.displayFavoritesAs { + case .favorites: return "star" + case .likes: return "heart" + } + } + + var favoritedIcon: String { + switch statusConfiguration.viewModel.identityContext.appPreferences.displayFavoritesAs { + case .favorites: return "star.fill" + case .likes: return "heart.fill" + } + } + func setButtonImages(font: UIFont) { let visibility = statusConfiguration.viewModel.visibility let reblogSystemImageName: String - + let isFavorited = statusConfiguration.viewModel.favorited if statusConfiguration.viewModel.configuration.isContextParent { reblogSystemImageName = "arrow.2.squarepath" } else { @@ -858,7 +882,7 @@ private extension StatusView { pointSize: font.pointSize, weight: statusConfiguration.viewModel.reblogged ? .bold : .regular)), for: .normal) - favoriteButton.setImage(UIImage(systemName: statusConfiguration.viewModel.favorited ? "star.fill" : "star", + favoriteButton.setImage(UIImage(systemName: isFavorited ? favoritedIcon : favoriteIcon, withConfiguration: UIImage.SymbolConfiguration(pointSize: font.pointSize)), for: .normal) shareButton.setImage(UIImage(systemName: "square.and.arrow.up", @@ -905,17 +929,29 @@ private extension StatusView { } func setFavoriteButtonColor(favorited: Bool) { - let favoriteColor: UIColor = favorited ? .systemYellow : .secondaryLabel + var favoriteColor: UIColor + var favoriteLabel: String + var undoFavoriteLabel: String + switch statusConfiguration.viewModel.identityContext.appPreferences.displayFavoritesAs { + case .favorites: + favoriteColor = favorited ? .systemYellow : .secondaryLabel + favoriteLabel = "status.favorite-button.undo.accessibility-label" + undoFavoriteLabel = "status.favorite-button.accessibility-label" + case .likes: + favoriteColor = favorited ? .systemRed : .secondaryLabel + favoriteLabel = "status.like-button.undo.accessibility-label" + undoFavoriteLabel = "status.like-button.accessibility-label" + } favoriteButton.tintColor = favoriteColor favoriteButton.setTitleColor(favoriteColor, for: .normal) if favorited { favoriteButton.accessibilityLabel = - NSLocalizedString("status.favorite-button.undo.accessibility-label", comment: "") + NSLocalizedString(undoFavoriteLabel, comment: "") } else { favoriteButton.accessibilityLabel = - NSLocalizedString("status.favorite-button.accessibility-label", comment: "") + NSLocalizedString(favoriteLabel, comment: "") } } @@ -938,20 +974,47 @@ private extension StatusView { statusConfiguration.viewModel.toggleReblogged() } + func animateFavorite() { + switch statusConfiguration.viewModel.identityContext.appPreferences.displayFavoritesAs { + + case .favorites: + self.animateAsFavorite() + case .likes: + self.animateAsLike() + } + } + + func animateAsFavorite() { + UIViewPropertyAnimator.runningPropertyAnimator( + withDuration: .defaultAnimationDuration, + delay: 0, + options: .curveLinear) { + self.setFavoriteButtonColor(favorited: !self.statusConfiguration.viewModel.favorited) + self.favoriteButton.imageView?.transform = + self.favoriteButton.imageView?.transform.rotated(by: .pi) ?? .identity + } completion: { _ in + self.favoriteButton.imageView?.transform = .identity + } + } + + func animateAsLike() { + UIViewPropertyAnimator.runningPropertyAnimator( + withDuration: .defaultAnimationDuration, + delay: 0, + options: .curveEaseInOut) { + self.setFavoriteButtonColor(favorited: !self.statusConfiguration.viewModel.favorited) + self.favoriteButton.imageView?.transform = + self.favoriteButton.imageView?.transform.scaledBy(x: 0.7, y: 0.7) ?? .identity + } completion: { _ in + self.favoriteButton.imageView?.transform = .identity + } + } + func favorite() { UIImpactFeedbackGenerator(style: .medium).impactOccurred() if !UIAccessibility.isReduceMotionEnabled { - UIViewPropertyAnimator.runningPropertyAnimator( - withDuration: .defaultAnimationDuration, - delay: 0, - options: .curveLinear) { - self.setFavoriteButtonColor(favorited: !self.statusConfiguration.viewModel.favorited) - self.favoriteButton.imageView?.transform = - self.favoriteButton.imageView?.transform.rotated(by: .pi) ?? .identity - } completion: { _ in - self.favoriteButton.imageView?.transform = .identity - } + self.animateFavorite() } statusConfiguration.viewModel.toggleFavorited()