diff --git a/Localization/Localizable.stringsdict b/Localization/Localizable.stringsdict
index a56e4b1e64..1038e2ea85 100644
--- a/Localization/Localizable.stringsdict
+++ b/Localization/Localizable.stringsdict
@@ -2,72 +2,72 @@
- a11y.plural.count.unread.notification
-
- NSStringLocalizedFormatKey
- %#@notification_count_unread_notification@
- notification_count_unread_notification
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- no unread notifications
- one
- 1 unread notification
- few
- %ld unread notifications
- many
- %ld unread notifications
- other
- %ld unread notifications
-
-
- a11y.plural.count.input_limit_exceeds
-
- NSStringLocalizedFormatKey
- Input limit exceeds %#@character_count@
- character_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 characters
- one
- 1 character
- few
- %ld characters
- many
- %ld characters
- other
- %ld characters
-
-
- a11y.plural.count.input_limit_remains
-
- NSStringLocalizedFormatKey
- Input limit remains %#@character_count@
- character_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 characters
- one
- 1 character
- few
- %ld characters
- many
- %ld characters
- other
- %ld characters
-
-
+ a11y.plural.count.unread.notification
+
+ NSStringLocalizedFormatKey
+ %#@notification_count_unread_notification@
+ notification_count_unread_notification
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ no unread notifications
+ one
+ 1 unread notification
+ few
+ %ld unread notifications
+ many
+ %ld unread notifications
+ other
+ %ld unread notifications
+
+
+ a11y.plural.count.input_limit_exceeds
+
+ NSStringLocalizedFormatKey
+ Input limit exceeds %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 characters
+ one
+ 1 character
+ few
+ %ld characters
+ many
+ %ld characters
+ other
+ %ld characters
+
+
+ a11y.plural.count.input_limit_remains
+
+ NSStringLocalizedFormatKey
+ Input limit remains %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 characters
+ one
+ 1 character
+ few
+ %ld characters
+ many
+ %ld characters
+ other
+ %ld characters
+
+
a11y.plural.count.characters_left
NSStringLocalizedFormatKey
@@ -90,125 +90,125 @@
%ld characters left
- plural.count.followed_by_and_mutual
-
- NSStringLocalizedFormatKey
- %#@names@%#@count_mutual@
- names
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- other
-
-
- count_mutual
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- Followed by %1$@
- one
- Followed by %1$@, and another mutual
- few
- Followed by %1$@, and %ld mutuals
- many
- Followed by %1$@, and %ld mutuals
- other
- Followed by %1$@, and %ld mutuals
-
-
- plural.count.metric_formatted.post
-
- NSStringLocalizedFormatKey
- %@ %#@post_count@
- post_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- posts
- one
- post
- few
- posts
- many
- posts
- other
- posts
-
-
- plural.count.media
-
- NSStringLocalizedFormatKey
- %#@media_count@
- media_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 media
- one
- 1 media
- few
- %ld media
- many
- %ld media
- other
- %ld media
-
-
- plural.count.post
-
- NSStringLocalizedFormatKey
- %#@post_count@
- post_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 posts
- one
- 1 post
- few
- %ld posts
- many
- %ld posts
- other
- %ld posts
-
-
+ plural.count.followed_by_and_mutual
+
+ NSStringLocalizedFormatKey
+ %#@names@%#@count_mutual@
+ names
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+
+
+ count_mutual
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ Followed by %1$@
+ one
+ Followed by %1$@, and another mutual
+ few
+ Followed by %1$@, and %ld mutuals
+ many
+ Followed by %1$@, and %ld mutuals
+ other
+ Followed by %1$@, and %ld mutuals
+
+
+ plural.count.metric_formatted.post
+
+ NSStringLocalizedFormatKey
+ %@ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ posts
+ one
+ post
+ few
+ posts
+ many
+ posts
+ other
+ posts
+
+
+ plural.count.media
+
+ NSStringLocalizedFormatKey
+ %#@media_count@
+ media_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 media
+ one
+ 1 media
+ few
+ %ld media
+ many
+ %ld media
+ other
+ %ld media
+
+
+ plural.count.post
+
+ NSStringLocalizedFormatKey
+ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 posts
+ one
+ 1 post
+ few
+ %ld posts
+ many
+ %ld posts
+ other
+ %ld posts
+
+
plural.count.favorite
-
- NSStringLocalizedFormatKey
- %#@favorite_count@
- favorite_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 favorites
- one
- 1 favorite
- few
- %ld favorites
- many
- %ld favorites
- other
- %ld favorites
-
-
+
+ NSStringLocalizedFormatKey
+ %#@favorite_count@
+ favorite_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 favorites
+ one
+ 1 favorite
+ few
+ %ld favorites
+ many
+ %ld favorites
+ other
+ %ld favorites
+
+
plural.count.reblog
NSStringLocalizedFormatKey
@@ -231,29 +231,29 @@
%ld reblogs
- plural.count.reblog_a11y
-
- NSStringLocalizedFormatKey
- %#@reblog_count@
- reblog_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 re-blogs
- one
- 1 re-blog
- few
- %ld re-blogs
- many
- %ld re-blogs
- other
- %ld re-blogs
-
-
- plural.count.reply
+ plural.count.reblog_a11y
+
+ NSStringLocalizedFormatKey
+ %#@reblog_count@
+ reblog_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 re-blogs
+ one
+ 1 re-blog
+ few
+ %ld re-blogs
+ many
+ %ld re-blogs
+ other
+ %ld re-blogs
+
+
+ plural.count.reply
NSStringLocalizedFormatKey
%#@reply_count@
@@ -275,379 +275,395 @@
%ld replies
- plural.count.vote
-
- NSStringLocalizedFormatKey
- %#@vote_count@
- vote_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 votes
- one
- 1 vote
- few
- %ld votes
- many
- %ld votes
- other
- %ld votes
-
-
- plural.count.voter
-
- NSStringLocalizedFormatKey
- %#@voter_count@
- voter_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 voters
- one
- 1 voter
- few
- %ld voters
- many
- %ld voters
- other
- %ld voters
-
-
- plural.people_talking
-
- NSStringLocalizedFormatKey
- %#@count_people_talking@
- count_people_talking
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 people talking
- one
- 1 people talking
- few
- %ld people talking
- many
- %ld people talking
- other
- %ld people talking
-
-
- plural.count.following
-
- NSStringLocalizedFormatKey
- %#@count_following@
- count_following
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 following
- one
- 1 following
- few
- %ld following
- many
- %ld following
- other
- %ld following
-
-
- plural.count.follower
-
- NSStringLocalizedFormatKey
- %#@count_follower@
- count_follower
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 followers
- one
- 1 follower
- few
- %ld followers
- many
- %ld followers
- other
- %ld followers
-
-
- date.year.left
-
- NSStringLocalizedFormatKey
- %#@count_year_left@
- count_year_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 years left
- one
- 1 year left
- few
- %ld years left
- many
- %ld years left
- other
- %ld years left
-
-
- date.month.left
-
- NSStringLocalizedFormatKey
- %#@count_month_left@
- count_month_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 months left
- one
- 1 months left
- few
- %ld months left
- many
- %ld months left
- other
- %ld months left
-
-
- date.day.left
-
- NSStringLocalizedFormatKey
- %#@count_day_left@
- count_day_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 days left
- one
- 1 day left
- few
- %ld days left
- many
- %ld days left
- other
- %ld days left
-
-
- date.hour.left
-
- NSStringLocalizedFormatKey
- %#@count_hour_left@
- count_hour_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 hours left
- one
- 1 hour left
- few
- %ld hours left
- many
- %ld hours left
- other
- %ld hours left
-
-
- date.minute.left
-
- NSStringLocalizedFormatKey
- %#@count_minute_left@
- count_minute_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 minutes left
- one
- 1 minute left
- few
- %ld minutes left
- many
- %ld minutes left
- other
- %ld minutes left
-
-
- date.second.left
-
- NSStringLocalizedFormatKey
- %#@count_second_left@
- count_second_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 seconds left
- one
- 1 second left
- few
- %ld seconds left
- many
- %ld seconds left
- other
- %ld seconds left
-
-
- date.year.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_year_ago_abbr@
- count_year_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0y ago
- one
- 1y ago
- few
- %ldy ago
- many
- %ldy ago
- other
- %ldy ago
-
-
- date.month.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_month_ago_abbr@
- count_month_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0M ago
- one
- 1M ago
- few
- %ldM ago
- many
- %ldM ago
- other
- %ldM ago
-
-
- date.day.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_day_ago_abbr@
- count_day_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0d ago
- one
- 1d ago
- few
- %ldd ago
- many
- %ldd ago
- other
- %ldd ago
-
-
- date.hour.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_hour_ago_abbr@
- count_hour_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0h ago
- one
- 1h ago
- few
- %ldh ago
- many
- %ldh ago
- other
- %ldh ago
-
-
- date.minute.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_minute_ago_abbr@
- count_minute_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0m ago
- one
- 1m ago
- few
- %ldm ago
- many
- %ldm ago
- other
- %ldm ago
-
-
- date.second.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_second_ago_abbr@
- count_second_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0s ago
- one
- 1s ago
- few
- %lds ago
- many
- %lds ago
- other
- %lds ago
-
-
+ plural.count.vote
+
+ NSStringLocalizedFormatKey
+ %#@vote_count@
+ vote_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 votes
+ one
+ 1 vote
+ few
+ %ld votes
+ many
+ %ld votes
+ other
+ %ld votes
+
+
+ plural.count.voter
+
+ NSStringLocalizedFormatKey
+ %#@voter_count@
+ voter_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 voters
+ one
+ 1 voter
+ few
+ %ld voters
+ many
+ %ld voters
+ other
+ %ld voters
+
+
+ plural.people_talking
+
+ NSStringLocalizedFormatKey
+ %#@count_people_talking@
+ count_people_talking
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 people talking
+ one
+ 1 people talking
+ few
+ %ld people talking
+ many
+ %ld people talking
+ other
+ %ld people talking
+
+
+ plural.count.following
+
+ NSStringLocalizedFormatKey
+ %#@count_following@
+ count_following
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 following
+ one
+ 1 following
+ few
+ %ld following
+ many
+ %ld following
+ other
+ %ld following
+
+
+ plural.count.follower
+
+ NSStringLocalizedFormatKey
+ %#@count_follower@
+ count_follower
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 followers
+ one
+ 1 follower
+ few
+ %ld followers
+ many
+ %ld followers
+ other
+ %ld followers
+
+
+ date.year.left
+
+ NSStringLocalizedFormatKey
+ %#@count_year_left@
+ count_year_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 years left
+ one
+ 1 year left
+ few
+ %ld years left
+ many
+ %ld years left
+ other
+ %ld years left
+
+
+ date.month.left
+
+ NSStringLocalizedFormatKey
+ %#@count_month_left@
+ count_month_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 months left
+ one
+ 1 months left
+ few
+ %ld months left
+ many
+ %ld months left
+ other
+ %ld months left
+
+
+ date.day.left
+
+ NSStringLocalizedFormatKey
+ %#@count_day_left@
+ count_day_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 days left
+ one
+ 1 day left
+ few
+ %ld days left
+ many
+ %ld days left
+ other
+ %ld days left
+
+
+ date.hour.left
+
+ NSStringLocalizedFormatKey
+ %#@count_hour_left@
+ count_hour_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 hours left
+ one
+ 1 hour left
+ few
+ %ld hours left
+ many
+ %ld hours left
+ other
+ %ld hours left
+
+
+ date.minute.left
+
+ NSStringLocalizedFormatKey
+ %#@count_minute_left@
+ count_minute_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 minutes left
+ one
+ 1 minute left
+ few
+ %ld minutes left
+ many
+ %ld minutes left
+ other
+ %ld minutes left
+
+
+ date.second.left
+
+ NSStringLocalizedFormatKey
+ %#@count_second_left@
+ count_second_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 seconds left
+ one
+ 1 second left
+ few
+ %ld seconds left
+ many
+ %ld seconds left
+ other
+ %ld seconds left
+
+
+ date.year.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_year_ago_abbr@
+ count_year_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0y ago
+ one
+ 1y ago
+ few
+ %ldy ago
+ many
+ %ldy ago
+ other
+ %ldy ago
+
+
+ date.month.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_month_ago_abbr@
+ count_month_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0M ago
+ one
+ 1M ago
+ few
+ %ldM ago
+ many
+ %ldM ago
+ other
+ %ldM ago
+
+
+ date.day.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_day_ago_abbr@
+ count_day_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0d ago
+ one
+ 1d ago
+ few
+ %ldd ago
+ many
+ %ldd ago
+ other
+ %ldd ago
+
+
+ date.hour.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_hour_ago_abbr@
+ count_hour_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0h ago
+ one
+ 1h ago
+ few
+ %ldh ago
+ many
+ %ldh ago
+ other
+ %ldh ago
+
+
+ date.minute.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_minute_ago_abbr@
+ count_minute_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0m ago
+ one
+ 1m ago
+ few
+ %ldm ago
+ many
+ %ldm ago
+ other
+ %ldm ago
+
+
+ date.second.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_second_ago_abbr@
+ count_second_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0s ago
+ one
+ 1s ago
+ few
+ %lds ago
+ many
+ %lds ago
+ other
+ %lds ago
+
+
+ plural.filtered_notification_banner.subtitle
+
+ NSStringLocalizedFormatKey
+ %#@number_of_requests@
+ number_of_requests
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ One person you may know
+ other
+ %ld people you may know
+
+
diff --git a/Localization/StringsConvertor/input/Base.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/Base.lproj/Localizable.stringsdict
index a56e4b1e64..1038e2ea85 100644
--- a/Localization/StringsConvertor/input/Base.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/Base.lproj/Localizable.stringsdict
@@ -2,72 +2,72 @@
- a11y.plural.count.unread.notification
-
- NSStringLocalizedFormatKey
- %#@notification_count_unread_notification@
- notification_count_unread_notification
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- no unread notifications
- one
- 1 unread notification
- few
- %ld unread notifications
- many
- %ld unread notifications
- other
- %ld unread notifications
-
-
- a11y.plural.count.input_limit_exceeds
-
- NSStringLocalizedFormatKey
- Input limit exceeds %#@character_count@
- character_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 characters
- one
- 1 character
- few
- %ld characters
- many
- %ld characters
- other
- %ld characters
-
-
- a11y.plural.count.input_limit_remains
-
- NSStringLocalizedFormatKey
- Input limit remains %#@character_count@
- character_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 characters
- one
- 1 character
- few
- %ld characters
- many
- %ld characters
- other
- %ld characters
-
-
+ a11y.plural.count.unread.notification
+
+ NSStringLocalizedFormatKey
+ %#@notification_count_unread_notification@
+ notification_count_unread_notification
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ no unread notifications
+ one
+ 1 unread notification
+ few
+ %ld unread notifications
+ many
+ %ld unread notifications
+ other
+ %ld unread notifications
+
+
+ a11y.plural.count.input_limit_exceeds
+
+ NSStringLocalizedFormatKey
+ Input limit exceeds %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 characters
+ one
+ 1 character
+ few
+ %ld characters
+ many
+ %ld characters
+ other
+ %ld characters
+
+
+ a11y.plural.count.input_limit_remains
+
+ NSStringLocalizedFormatKey
+ Input limit remains %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 characters
+ one
+ 1 character
+ few
+ %ld characters
+ many
+ %ld characters
+ other
+ %ld characters
+
+
a11y.plural.count.characters_left
NSStringLocalizedFormatKey
@@ -90,125 +90,125 @@
%ld characters left
- plural.count.followed_by_and_mutual
-
- NSStringLocalizedFormatKey
- %#@names@%#@count_mutual@
- names
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- other
-
-
- count_mutual
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- Followed by %1$@
- one
- Followed by %1$@, and another mutual
- few
- Followed by %1$@, and %ld mutuals
- many
- Followed by %1$@, and %ld mutuals
- other
- Followed by %1$@, and %ld mutuals
-
-
- plural.count.metric_formatted.post
-
- NSStringLocalizedFormatKey
- %@ %#@post_count@
- post_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- posts
- one
- post
- few
- posts
- many
- posts
- other
- posts
-
-
- plural.count.media
-
- NSStringLocalizedFormatKey
- %#@media_count@
- media_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 media
- one
- 1 media
- few
- %ld media
- many
- %ld media
- other
- %ld media
-
-
- plural.count.post
-
- NSStringLocalizedFormatKey
- %#@post_count@
- post_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 posts
- one
- 1 post
- few
- %ld posts
- many
- %ld posts
- other
- %ld posts
-
-
+ plural.count.followed_by_and_mutual
+
+ NSStringLocalizedFormatKey
+ %#@names@%#@count_mutual@
+ names
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+
+
+ count_mutual
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ Followed by %1$@
+ one
+ Followed by %1$@, and another mutual
+ few
+ Followed by %1$@, and %ld mutuals
+ many
+ Followed by %1$@, and %ld mutuals
+ other
+ Followed by %1$@, and %ld mutuals
+
+
+ plural.count.metric_formatted.post
+
+ NSStringLocalizedFormatKey
+ %@ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ posts
+ one
+ post
+ few
+ posts
+ many
+ posts
+ other
+ posts
+
+
+ plural.count.media
+
+ NSStringLocalizedFormatKey
+ %#@media_count@
+ media_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 media
+ one
+ 1 media
+ few
+ %ld media
+ many
+ %ld media
+ other
+ %ld media
+
+
+ plural.count.post
+
+ NSStringLocalizedFormatKey
+ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 posts
+ one
+ 1 post
+ few
+ %ld posts
+ many
+ %ld posts
+ other
+ %ld posts
+
+
plural.count.favorite
-
- NSStringLocalizedFormatKey
- %#@favorite_count@
- favorite_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 favorites
- one
- 1 favorite
- few
- %ld favorites
- many
- %ld favorites
- other
- %ld favorites
-
-
+
+ NSStringLocalizedFormatKey
+ %#@favorite_count@
+ favorite_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 favorites
+ one
+ 1 favorite
+ few
+ %ld favorites
+ many
+ %ld favorites
+ other
+ %ld favorites
+
+
plural.count.reblog
NSStringLocalizedFormatKey
@@ -231,29 +231,29 @@
%ld reblogs
- plural.count.reblog_a11y
-
- NSStringLocalizedFormatKey
- %#@reblog_count@
- reblog_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 re-blogs
- one
- 1 re-blog
- few
- %ld re-blogs
- many
- %ld re-blogs
- other
- %ld re-blogs
-
-
- plural.count.reply
+ plural.count.reblog_a11y
+
+ NSStringLocalizedFormatKey
+ %#@reblog_count@
+ reblog_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 re-blogs
+ one
+ 1 re-blog
+ few
+ %ld re-blogs
+ many
+ %ld re-blogs
+ other
+ %ld re-blogs
+
+
+ plural.count.reply
NSStringLocalizedFormatKey
%#@reply_count@
@@ -275,379 +275,395 @@
%ld replies
- plural.count.vote
-
- NSStringLocalizedFormatKey
- %#@vote_count@
- vote_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 votes
- one
- 1 vote
- few
- %ld votes
- many
- %ld votes
- other
- %ld votes
-
-
- plural.count.voter
-
- NSStringLocalizedFormatKey
- %#@voter_count@
- voter_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 voters
- one
- 1 voter
- few
- %ld voters
- many
- %ld voters
- other
- %ld voters
-
-
- plural.people_talking
-
- NSStringLocalizedFormatKey
- %#@count_people_talking@
- count_people_talking
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 people talking
- one
- 1 people talking
- few
- %ld people talking
- many
- %ld people talking
- other
- %ld people talking
-
-
- plural.count.following
-
- NSStringLocalizedFormatKey
- %#@count_following@
- count_following
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 following
- one
- 1 following
- few
- %ld following
- many
- %ld following
- other
- %ld following
-
-
- plural.count.follower
-
- NSStringLocalizedFormatKey
- %#@count_follower@
- count_follower
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 followers
- one
- 1 follower
- few
- %ld followers
- many
- %ld followers
- other
- %ld followers
-
-
- date.year.left
-
- NSStringLocalizedFormatKey
- %#@count_year_left@
- count_year_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 years left
- one
- 1 year left
- few
- %ld years left
- many
- %ld years left
- other
- %ld years left
-
-
- date.month.left
-
- NSStringLocalizedFormatKey
- %#@count_month_left@
- count_month_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 months left
- one
- 1 months left
- few
- %ld months left
- many
- %ld months left
- other
- %ld months left
-
-
- date.day.left
-
- NSStringLocalizedFormatKey
- %#@count_day_left@
- count_day_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 days left
- one
- 1 day left
- few
- %ld days left
- many
- %ld days left
- other
- %ld days left
-
-
- date.hour.left
-
- NSStringLocalizedFormatKey
- %#@count_hour_left@
- count_hour_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 hours left
- one
- 1 hour left
- few
- %ld hours left
- many
- %ld hours left
- other
- %ld hours left
-
-
- date.minute.left
-
- NSStringLocalizedFormatKey
- %#@count_minute_left@
- count_minute_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 minutes left
- one
- 1 minute left
- few
- %ld minutes left
- many
- %ld minutes left
- other
- %ld minutes left
-
-
- date.second.left
-
- NSStringLocalizedFormatKey
- %#@count_second_left@
- count_second_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 seconds left
- one
- 1 second left
- few
- %ld seconds left
- many
- %ld seconds left
- other
- %ld seconds left
-
-
- date.year.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_year_ago_abbr@
- count_year_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0y ago
- one
- 1y ago
- few
- %ldy ago
- many
- %ldy ago
- other
- %ldy ago
-
-
- date.month.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_month_ago_abbr@
- count_month_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0M ago
- one
- 1M ago
- few
- %ldM ago
- many
- %ldM ago
- other
- %ldM ago
-
-
- date.day.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_day_ago_abbr@
- count_day_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0d ago
- one
- 1d ago
- few
- %ldd ago
- many
- %ldd ago
- other
- %ldd ago
-
-
- date.hour.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_hour_ago_abbr@
- count_hour_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0h ago
- one
- 1h ago
- few
- %ldh ago
- many
- %ldh ago
- other
- %ldh ago
-
-
- date.minute.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_minute_ago_abbr@
- count_minute_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0m ago
- one
- 1m ago
- few
- %ldm ago
- many
- %ldm ago
- other
- %ldm ago
-
-
- date.second.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_second_ago_abbr@
- count_second_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0s ago
- one
- 1s ago
- few
- %lds ago
- many
- %lds ago
- other
- %lds ago
-
-
+ plural.count.vote
+
+ NSStringLocalizedFormatKey
+ %#@vote_count@
+ vote_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 votes
+ one
+ 1 vote
+ few
+ %ld votes
+ many
+ %ld votes
+ other
+ %ld votes
+
+
+ plural.count.voter
+
+ NSStringLocalizedFormatKey
+ %#@voter_count@
+ voter_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 voters
+ one
+ 1 voter
+ few
+ %ld voters
+ many
+ %ld voters
+ other
+ %ld voters
+
+
+ plural.people_talking
+
+ NSStringLocalizedFormatKey
+ %#@count_people_talking@
+ count_people_talking
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 people talking
+ one
+ 1 people talking
+ few
+ %ld people talking
+ many
+ %ld people talking
+ other
+ %ld people talking
+
+
+ plural.count.following
+
+ NSStringLocalizedFormatKey
+ %#@count_following@
+ count_following
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 following
+ one
+ 1 following
+ few
+ %ld following
+ many
+ %ld following
+ other
+ %ld following
+
+
+ plural.count.follower
+
+ NSStringLocalizedFormatKey
+ %#@count_follower@
+ count_follower
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 followers
+ one
+ 1 follower
+ few
+ %ld followers
+ many
+ %ld followers
+ other
+ %ld followers
+
+
+ date.year.left
+
+ NSStringLocalizedFormatKey
+ %#@count_year_left@
+ count_year_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 years left
+ one
+ 1 year left
+ few
+ %ld years left
+ many
+ %ld years left
+ other
+ %ld years left
+
+
+ date.month.left
+
+ NSStringLocalizedFormatKey
+ %#@count_month_left@
+ count_month_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 months left
+ one
+ 1 months left
+ few
+ %ld months left
+ many
+ %ld months left
+ other
+ %ld months left
+
+
+ date.day.left
+
+ NSStringLocalizedFormatKey
+ %#@count_day_left@
+ count_day_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 days left
+ one
+ 1 day left
+ few
+ %ld days left
+ many
+ %ld days left
+ other
+ %ld days left
+
+
+ date.hour.left
+
+ NSStringLocalizedFormatKey
+ %#@count_hour_left@
+ count_hour_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 hours left
+ one
+ 1 hour left
+ few
+ %ld hours left
+ many
+ %ld hours left
+ other
+ %ld hours left
+
+
+ date.minute.left
+
+ NSStringLocalizedFormatKey
+ %#@count_minute_left@
+ count_minute_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 minutes left
+ one
+ 1 minute left
+ few
+ %ld minutes left
+ many
+ %ld minutes left
+ other
+ %ld minutes left
+
+
+ date.second.left
+
+ NSStringLocalizedFormatKey
+ %#@count_second_left@
+ count_second_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 seconds left
+ one
+ 1 second left
+ few
+ %ld seconds left
+ many
+ %ld seconds left
+ other
+ %ld seconds left
+
+
+ date.year.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_year_ago_abbr@
+ count_year_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0y ago
+ one
+ 1y ago
+ few
+ %ldy ago
+ many
+ %ldy ago
+ other
+ %ldy ago
+
+
+ date.month.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_month_ago_abbr@
+ count_month_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0M ago
+ one
+ 1M ago
+ few
+ %ldM ago
+ many
+ %ldM ago
+ other
+ %ldM ago
+
+
+ date.day.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_day_ago_abbr@
+ count_day_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0d ago
+ one
+ 1d ago
+ few
+ %ldd ago
+ many
+ %ldd ago
+ other
+ %ldd ago
+
+
+ date.hour.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_hour_ago_abbr@
+ count_hour_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0h ago
+ one
+ 1h ago
+ few
+ %ldh ago
+ many
+ %ldh ago
+ other
+ %ldh ago
+
+
+ date.minute.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_minute_ago_abbr@
+ count_minute_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0m ago
+ one
+ 1m ago
+ few
+ %ldm ago
+ many
+ %ldm ago
+ other
+ %ldm ago
+
+
+ date.second.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_second_ago_abbr@
+ count_second_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0s ago
+ one
+ 1s ago
+ few
+ %lds ago
+ many
+ %lds ago
+ other
+ %lds ago
+
+
+ plural.filtered_notification_banner.subtitle
+
+ NSStringLocalizedFormatKey
+ %#@number_of_requests@
+ number_of_requests
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ One person you may know
+ other
+ %ld people you may know
+
+
diff --git a/Localization/StringsConvertor/input/Base.lproj/app.json b/Localization/StringsConvertor/input/Base.lproj/app.json
index f11b057a6a..a799483709 100644
--- a/Localization/StringsConvertor/input/Base.lproj/app.json
+++ b/Localization/StringsConvertor/input/Base.lproj/app.json
@@ -477,7 +477,15 @@
"title": "Home",
"timeline_menu": {
"following": "Following",
- "local_community": "Local"
+ "local_community": "Local",
+ "lists": {
+ "title": "Lists",
+ "empty_message": "You don't have any Lists"
+ },
+ "hashtags": {
+ "title": "Followed Hashtags",
+ "empty_message": "You don't follow any Hashtags"
+ }
},
"timeline_pill": {
"offline": "Offline",
@@ -744,6 +752,30 @@
"silence": "Your account has been limited.",
"suspend": "Your account has been suspended.",
"learn_more": "Learn More"
+ },
+ "filtered_notification": {
+ "title": "Filtered Notifications",
+ "accept": "Accept",
+ "dismiss": "Dismiss",
+ },
+ "policy": {
+ "title": "Filter Notifications from…",
+ "not_following": {
+ "title": "People you don't follow",
+ "subtitle": "Until you manually approve them"
+ },
+ "no_follower": {
+ "title": "People not following you",
+ "subtitle": "Including people who have been following you fewer than 3 days"
+ },
+ "new_account": {
+ "title": "New accounts",
+ "subtitle": "Created within the past 30 days"
+ },
+ "private_mentions": {
+ "title": "Unsolicited private mentions",
+ "subtitle": "Filtered unless it’s in reply to your own mention or if you follow the sender"
+ }
}
},
"thread": {
diff --git a/Localization/app.json b/Localization/app.json
index c84499439c..a799483709 100644
--- a/Localization/app.json
+++ b/Localization/app.json
@@ -752,6 +752,30 @@
"silence": "Your account has been limited.",
"suspend": "Your account has been suspended.",
"learn_more": "Learn More"
+ },
+ "filtered_notification": {
+ "title": "Filtered Notifications",
+ "accept": "Accept",
+ "dismiss": "Dismiss",
+ },
+ "policy": {
+ "title": "Filter Notifications from…",
+ "not_following": {
+ "title": "People you don't follow",
+ "subtitle": "Until you manually approve them"
+ },
+ "no_follower": {
+ "title": "People not following you",
+ "subtitle": "Including people who have been following you fewer than 3 days"
+ },
+ "new_account": {
+ "title": "New accounts",
+ "subtitle": "Created within the past 30 days"
+ },
+ "private_mentions": {
+ "title": "Unsolicited private mentions",
+ "subtitle": "Filtered unless it’s in reply to your own mention or if you follow the sender"
+ }
}
},
"thread": {
diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj
index ab6ac29406..8db2635d6d 100644
--- a/Mastodon.xcodeproj/project.pbxproj
+++ b/Mastodon.xcodeproj/project.pbxproj
@@ -157,10 +157,20 @@
D8318A882A4468D300C0FB73 /* NotificationSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8318A872A4468D300C0FB73 /* NotificationSettingsViewController.swift */; };
D8318A8A2A4468DC00C0FB73 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8318A892A4468DC00C0FB73 /* AboutViewController.swift */; };
D8363B1629469CE200A74079 /* OnboardingNextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8363B1529469CE200A74079 /* OnboardingNextView.swift */; };
+ D83B54F82C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D83B54F72C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift */; };
D84738D42BBD9ABE00ECD52B /* TimelineStatusPill.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84738D32BBD9ABE00ECD52B /* TimelineStatusPill.swift */; };
D84FA0932AE6915800987F47 /* MBProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = D84FA0922AE6915800987F47 /* MBProgressHUD */; };
D852C23C2AC5D02C00309232 /* AboutInstanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852C23B2AC5D02C00309232 /* AboutInstanceViewController.swift */; };
D852C23E2AC5D03300309232 /* InstanceRulesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852C23D2AC5D03300309232 /* InstanceRulesViewController.swift */; };
+ D85DF96B2C481AF700A01408 /* NotificationPolicyFilterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF9682C481AF400A01408 /* NotificationPolicyFilterTableViewCell.swift */; };
+ D85DF96C2C481AF700A01408 /* NotificationPolicyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF9692C481AF700A01408 /* NotificationPolicyViewController.swift */; };
+ D85DF96D2C481AF700A01408 /* NotificationPolicyHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF96A2C481AF700A01408 /* NotificationPolicyHeaderView.swift */; };
+ D85DF9712C481B1100A01408 /* NotificationRequestsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF96E2C481B1100A01408 /* NotificationRequestsTableViewController.swift */; };
+ D85DF9722C481B1100A01408 /* NotificationRequestTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF96F2C481B1100A01408 /* NotificationRequestTableViewCell.swift */; };
+ D85DF9742C481B3500A01408 /* DataSourceFacade+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF9732C481B3500A01408 /* DataSourceFacade+Notifications.swift */; };
+ D85DF9762C4965A900A01408 /* NotificationRequestsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF9752C4965A900A01408 /* NotificationRequestsViewModel.swift */; };
+ D85DF97A2C4E49A400A01408 /* NotificationRequestCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF9792C4E49A400A01408 /* NotificationRequestCountView.swift */; };
+ D85DF97E2C50EFA700A01408 /* AccountNotificationTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF97D2C50EFA700A01408 /* AccountNotificationTimelineViewController.swift */; };
D87364F92AE28DB500C8F919 /* Kanna in Frameworks */ = {isa = PBXBuildFile; productRef = D87364F82AE28DB500C8F919 /* Kanna */; };
D87BFC8B291D5C6B00FEE264 /* MastodonLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */; };
D87BFC8D291EB81200FEE264 /* MastodonLoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */; };
@@ -789,6 +799,7 @@
D8318A872A4468D300C0FB73 /* NotificationSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsViewController.swift; sourceTree = ""; };
D8318A892A4468DC00C0FB73 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; };
D8363B1529469CE200A74079 /* OnboardingNextView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = OnboardingNextView.swift; sourceTree = ""; tabWidth = 4; };
+ D83B54F72C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationFilteringBannerTableViewCell.swift; sourceTree = ""; };
D84738D32BBD9ABE00ECD52B /* TimelineStatusPill.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStatusPill.swift; sourceTree = ""; };
D84C099D2B0F9E33009E685E /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
D84C099F2B0F9E41009E685E /* Setup.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Setup.md; sourceTree = ""; };
@@ -799,6 +810,15 @@
D84C09A42B0F9E41009E685E /* Acknowledgments.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Acknowledgments.md; sourceTree = ""; };
D852C23B2AC5D02C00309232 /* AboutInstanceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutInstanceViewController.swift; sourceTree = ""; };
D852C23D2AC5D03300309232 /* InstanceRulesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceRulesViewController.swift; sourceTree = ""; };
+ D85DF9682C481AF400A01408 /* NotificationPolicyFilterTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NotificationPolicyFilterTableViewCell.swift; path = Policy/NotificationPolicyFilterTableViewCell.swift; sourceTree = ""; };
+ D85DF9692C481AF700A01408 /* NotificationPolicyViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NotificationPolicyViewController.swift; path = Policy/NotificationPolicyViewController.swift; sourceTree = ""; };
+ D85DF96A2C481AF700A01408 /* NotificationPolicyHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NotificationPolicyHeaderView.swift; path = Policy/NotificationPolicyHeaderView.swift; sourceTree = ""; };
+ D85DF96E2C481B1100A01408 /* NotificationRequestsTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationRequestsTableViewController.swift; sourceTree = ""; };
+ D85DF96F2C481B1100A01408 /* NotificationRequestTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationRequestTableViewCell.swift; sourceTree = ""; };
+ D85DF9732C481B3500A01408 /* DataSourceFacade+Notifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Notifications.swift"; sourceTree = ""; };
+ D85DF9752C4965A900A01408 /* NotificationRequestsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRequestsViewModel.swift; sourceTree = ""; };
+ D85DF9792C4E49A400A01408 /* NotificationRequestCountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRequestCountView.swift; sourceTree = ""; };
+ D85DF97D2C50EFA700A01408 /* AccountNotificationTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountNotificationTimelineViewController.swift; sourceTree = ""; };
D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginView.swift; sourceTree = ""; };
D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginViewModel.swift; sourceTree = ""; };
D87BFC8E291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginServerTableViewCell.swift; sourceTree = ""; };
@@ -1510,6 +1530,7 @@
DB63F76E279A7D1100455B82 /* NotificationTableViewCell.swift */,
DB63F774279A997D00455B82 /* NotificationTableViewCell+ViewModel.swift */,
D8A0729C2BEBA8D7001A4C7C /* AccountWarningNotificationCell.swift */,
+ D83B54F72C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift */,
);
path = Cell;
sourceTree = "";
@@ -1758,6 +1779,16 @@
path = Privacy;
sourceTree = "";
};
+ D80EC2602C2978CB009724A5 /* Notification Filtering */ = {
+ isa = PBXGroup;
+ children = (
+ D85DF9682C481AF400A01408 /* NotificationPolicyFilterTableViewCell.swift */,
+ D85DF96A2C481AF700A01408 /* NotificationPolicyHeaderView.swift */,
+ D85DF9692C481AF700A01408 /* NotificationPolicyViewController.swift */,
+ );
+ path = "Notification Filtering";
+ sourceTree = "";
+ };
D80F627E2B5C32E400877059 /* NotificationView */ = {
isa = PBXGroup;
children = (
@@ -1820,6 +1851,27 @@
path = Documentation;
sourceTree = "";
};
+ D85DF9702C481B1100A01408 /* Requests */ = {
+ isa = PBXGroup;
+ children = (
+ D85DF97C2C50EF8700A01408 /* Account Notifications */,
+ D85DF96E2C481B1100A01408 /* NotificationRequestsTableViewController.swift */,
+ D85DF96F2C481B1100A01408 /* NotificationRequestTableViewCell.swift */,
+ D85DF9752C4965A900A01408 /* NotificationRequestsViewModel.swift */,
+ D85DF9792C4E49A400A01408 /* NotificationRequestCountView.swift */,
+ );
+ name = Requests;
+ path = "Notification Filtering/Requests";
+ sourceTree = "";
+ };
+ D85DF97C2C50EF8700A01408 /* Account Notifications */ = {
+ isa = PBXGroup;
+ children = (
+ D85DF97D2C50EFA700A01408 /* AccountNotificationTimelineViewController.swift */,
+ );
+ path = "Account Notifications";
+ sourceTree = "";
+ };
D8A6AB68291C50F3003AB663 /* Login */ = {
isa = PBXGroup;
children = (
@@ -2397,6 +2449,7 @@
DB697DDC278F521D004EF2F7 /* DataSourceFacade.swift */,
6213AF5D2893A8B200BCADB6 /* DataSourceFacade+Bookmark.swift */,
DB697DE0278F5296004EF2F7 /* DataSourceFacade+Model.swift */,
+ D85DF9732C481B3500A01408 /* DataSourceFacade+Notifications.swift */,
DB697DDE278F524F004EF2F7 /* DataSourceFacade+Profile.swift */,
DB8F7075279E954700E1225B /* DataSourceFacade+Follow.swift */,
DB603110279EB38500A935FE /* DataSourceFacade+Mute.swift */,
@@ -2667,6 +2720,8 @@
DB9D6BFD25E4F57B0051B173 /* Notification */ = {
isa = PBXGroup;
children = (
+ D85DF9702C481B1100A01408 /* Requests */,
+ D80EC2602C2978CB009724A5 /* Notification Filtering */,
DB63F765279A5E5600455B82 /* NotificationTimeline */,
2D35237F26256F470031AF25 /* Cell */,
D80F627E2B5C32E400877059 /* NotificationView */,
@@ -3497,6 +3552,7 @@
DBDFF1902805543100557A48 /* DiscoveryPostsViewController.swift in Sources */,
DB697DD9278F4CED004EF2F7 /* HomeTimelineViewController+DataSourceProvider.swift in Sources */,
DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */,
+ D85DF9712C481B1100A01408 /* NotificationRequestsTableViewController.swift in Sources */,
D8FAAE432AD047B200DC1832 /* AboutInstanceTableFooterView.swift in Sources */,
D808B94E296EFBBA0031EB1E /* StatusEditHistoryTableViewCell.swift in Sources */,
D852C23E2AC5D03300309232 /* InstanceRulesViewController.swift in Sources */,
@@ -3535,6 +3591,7 @@
DB938F0926240F3C00E5B6C1 /* RemoteThreadViewModel.swift in Sources */,
DB5B54A62833BE0000DEF8B2 /* UserListViewModel+State.swift in Sources */,
DB0617ED277F02C50030EE79 /* OnboardingNavigationController.swift in Sources */,
+ D85DF9722C481B1100A01408 /* NotificationRequestTableViewCell.swift in Sources */,
D808B94C296ECFDC0031EB1E /* StatusEditHistoryViewModel.swift in Sources */,
DB0617F527855AB90030EE79 /* ServerRuleSection.swift in Sources */,
DB75BF1E263C1C1B00EDBF1F /* CustomScheduler.swift in Sources */,
@@ -3557,6 +3614,7 @@
DB6180DD263918E30018D199 /* MediaPreviewViewController.swift in Sources */,
DBE3CDEC261C6B2900430CC6 /* FavoriteViewController.swift in Sources */,
DB938EE62623F50700E5B6C1 /* ThreadViewController.swift in Sources */,
+ D85DF9762C4965A900A01408 /* NotificationRequestsViewModel.swift in Sources */,
DB6180F426391D110018D199 /* MediaPreviewImageView.swift in Sources */,
DBF9814A265E24F500E4BA07 /* ProfileFieldCollectionViewHeaderFooterView.swift in Sources */,
2D939AB525EDD8A90076FA61 /* String.swift in Sources */,
@@ -3581,6 +3639,7 @@
2D38F1EB25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift in Sources */,
DB5B7295273112B100081888 /* FollowingListViewController.swift in Sources */,
0F202201261326E6000C64BF /* HashtagTimelineViewModel.swift in Sources */,
+ D85DF96C2C481AF700A01408 /* NotificationPolicyViewController.swift in Sources */,
D81A94172B07A1D30067A19D /* ProfileCardView+Configuration.swift in Sources */,
DB63F7452799056400455B82 /* HashtagTableViewCell.swift in Sources */,
D82BD7552ABC73AF009A374A /* NotificationPolicyTableViewCell.swift in Sources */,
@@ -3601,6 +3660,7 @@
2DAC9E38262FC2320062E1A6 /* SuggestionAccountViewController.swift in Sources */,
DB6180E02639194B0018D199 /* MediaPreviewPagingViewController.swift in Sources */,
D8E5C349296DB8A3007E76A7 /* StatusEditHistoryViewController.swift in Sources */,
+ D85DF96B2C481AF700A01408 /* NotificationPolicyFilterTableViewCell.swift in Sources */,
DBE0822425CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift in Sources */,
D8318A882A4468D300C0FB73 /* NotificationSettingsViewController.swift in Sources */,
2D82B9FF25E7863200E36F0F /* OnboardingViewControllerAppearance.swift in Sources */,
@@ -3753,6 +3813,7 @@
DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */,
DB0FCB822796AC78006C02E2 /* UserTimelineViewController+DataSourceProvider.swift in Sources */,
DB0EF72E26FDB24F00347686 /* SidebarListContentView.swift in Sources */,
+ D85DF96D2C481AF700A01408 /* NotificationPolicyHeaderView.swift in Sources */,
DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */,
2D7867192625B77500211898 /* NotificationItem.swift in Sources */,
DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */,
@@ -3766,9 +3827,11 @@
DB1D84382657B275000346B3 /* SegmentedControlNavigateable.swift in Sources */,
0F20220726134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift in Sources */,
DB7A9F932818F33C0016AF98 /* MastodonServerRulesViewController+Debug.swift in Sources */,
+ D85DF97E2C50EFA700A01408 /* AccountNotificationTimelineViewController.swift in Sources */,
2D5A3D2825CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift in Sources */,
DB6B74FC272FF55800C70B6E /* UserSection.swift in Sources */,
DB0FCB862796BDA1006C02E2 /* SearchSection.swift in Sources */,
+ D83B54F82C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift in Sources */,
DB1D61CF26F1B33600DA8662 /* WelcomeViewModel.swift in Sources */,
D84738D42BBD9ABE00ECD52B /* TimelineStatusPill.swift in Sources */,
D8B5E4F42A4ED0240008970C /* NotificationSettingsViewModel.swift in Sources */,
@@ -3777,6 +3840,7 @@
D852C23C2AC5D02C00309232 /* AboutInstanceViewController.swift in Sources */,
D8F917142A4D74C3008A5370 /* GeneralSettingsDiffableTableViewDataSource.swift in Sources */,
2A5242772C199EC2005B9E22 /* PrivacySafetySettingPreset.swift in Sources */,
+ D85DF97A2C4E49A400A01408 /* NotificationRequestCountView.swift in Sources */,
DB6180FA26391F2E0018D199 /* MediaPreviewViewModel.swift in Sources */,
2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */,
DB6B7500272FF73800C70B6E /* UserTableViewCell.swift in Sources */,
@@ -3854,6 +3918,7 @@
D886FBD329DF710F00272017 /* WelcomeSeparatorView.swift in Sources */,
DB0617FD27855BFE0030EE79 /* ServerRuleItem.swift in Sources */,
5BB04FD5262E7AFF0043BFF6 /* ReportViewController.swift in Sources */,
+ D85DF9742C481B3500A01408 /* DataSourceFacade+Notifications.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift
index acf985feef..4ca70d8ec9 100644
--- a/Mastodon/Coordinator/SceneCoordinator.swift
+++ b/Mastodon/Coordinator/SceneCoordinator.swift
@@ -205,7 +205,12 @@ extension SceneCoordinator {
// setting
case settings(setting: Setting)
-
+
+ // Notifications
+ case notificationPolicy(viewModel: NotificationFilterViewModel)
+ case notificationRequests(viewModel: NotificationRequestsViewModel)
+ case accountNotificationTimeline(viewModel: NotificationTimelineViewModel, request: Mastodon.Entity.NotificationRequest)
+
// report
case report(viewModel: ReportViewModel)
case reportServerRules(viewModel: ReportServerRulesViewModel)
@@ -558,6 +563,12 @@ private extension SceneCoordinator {
case .editStatus(let viewModel):
let composeViewController = ComposeViewController(viewModel: viewModel)
viewController = composeViewController
+ case .notificationRequests(let viewModel):
+ viewController = NotificationRequestsTableViewController(viewModel: viewModel)
+ case .notificationPolicy(let viewModel):
+ viewController = NotificationPolicyViewController(viewModel: viewModel)
+ case .accountNotificationTimeline(let viewModel, let request):
+ viewController = AccountNotificationTimelineViewController(viewModel: viewModel, context: appContext, coordinator: self, notificationRequest: request)
}
setupDependency(for: viewController as? NeedsDependency)
diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift
new file mode 100644
index 0000000000..7abca3694d
--- /dev/null
+++ b/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift
@@ -0,0 +1,52 @@
+// Copyright © 2024 Mastodon gGmbH. All rights reserved.
+
+import Foundation
+import MastodonCore
+import MastodonSDK
+
+extension DataSourceFacade {
+ @MainActor
+ static func coordinateToNotificationRequests(
+ provider: DataSourceProvider & AuthContextProvider
+ ) async {
+ provider.coordinator.showLoading()
+
+ do {
+ let notificationRequests = try await provider.context.apiService.notificationRequests(authenticationBox: provider.authContext.mastodonAuthenticationBox).value
+ let viewModel = NotificationRequestsViewModel(appContext: provider.context, authContext: provider.authContext, coordinator: provider.coordinator, requests: notificationRequests)
+
+ provider.coordinator.hideLoading()
+
+ let transition: SceneCoordinator.Transition
+
+ if provider.traitCollection.userInterfaceIdiom == .phone {
+ transition = .show
+ } else {
+ transition = .modal(animated: true)
+ }
+
+ provider.coordinator.present(scene: .notificationRequests(viewModel: viewModel), transition: transition)
+ } catch {
+ //TODO: Error Handling
+ provider.coordinator.hideLoading()
+ }
+ }
+
+ @MainActor
+ static func coordinateToNotificationRequest(
+ request: Mastodon.Entity.NotificationRequest,
+ provider: ViewControllerWithDependencies & AuthContextProvider
+ ) async -> AccountNotificationTimelineViewController? {
+ provider.coordinator.showLoading()
+
+ let notificationTimelineViewModel = NotificationTimelineViewModel(context: provider.context, authContext: provider.authContext, scope: .fromAccount(request.account))
+
+ provider.coordinator.hideLoading()
+
+ guard let viewController = provider.coordinator.present(scene: .accountNotificationTimeline(viewModel: notificationTimelineViewModel, request: request), transition: .show) as? AccountNotificationTimelineViewController else { return nil }
+
+ return viewController
+
+ }
+
+}
diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift b/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift
index 76b19fa106..63ccabbf9e 100644
--- a/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift
+++ b/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift
@@ -17,32 +17,30 @@ extension DataSourceFacade {
item: DataSourceItem
) async {
switch item {
- case .account(account: let account, relationship: _):
- let now = Date()
- let userID = provider.authContext.mastodonAuthenticationBox.userID
- let searchEntry = Persistence.SearchHistory.Item(
- updatedAt: now,
- userID: userID,
- account: account,
- hashtag: nil
- )
+ case .account(account: let account, relationship: _):
+ let now = Date()
+ let userID = provider.authContext.mastodonAuthenticationBox.userID
+ let searchEntry = Persistence.SearchHistory.Item(
+ updatedAt: now,
+ userID: userID,
+ account: account,
+ hashtag: nil
+ )
- try? FileManager.default.addSearchItem(searchEntry, for: provider.authContext.mastodonAuthenticationBox)
- case .hashtag(let tag):
+ try? FileManager.default.addSearchItem(searchEntry, for: provider.authContext.mastodonAuthenticationBox)
+ case .hashtag(let tag):
- let now = Date()
- let userID = provider.authContext.mastodonAuthenticationBox.userID
- let searchEntry = Persistence.SearchHistory.Item(
- updatedAt: now,
- userID: userID,
- account: nil,
- hashtag: tag
- )
+ let now = Date()
+ let userID = provider.authContext.mastodonAuthenticationBox.userID
+ let searchEntry = Persistence.SearchHistory.Item(
+ updatedAt: now,
+ userID: userID,
+ account: nil,
+ hashtag: tag
+ )
- try? FileManager.default.addSearchItem(searchEntry, for: provider.authContext.mastodonAuthenticationBox)
- case .status:
- break
- case .notification:
+ try? FileManager.default.addSearchItem(searchEntry, for: provider.authContext.mastodonAuthenticationBox)
+ case .status, .notification, .notificationBanner(_):
break
}
diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift
index aa71991d3b..1290953f72 100644
--- a/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift
+++ b/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift
@@ -514,10 +514,9 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut
)
case .account(let account, _):
await DataSourceFacade.coordinateToProfileScene(provider: self, account: account)
- case .notification:
- assertionFailure("TODO")
- case .hashtag(_):
- assertionFailure("TODO")
+ case .notification, .hashtag(_), .notificationBanner(_):
+ // not supposed to happen
+ break
}
} // end Task
}
diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift
index 4381498907..9db7ac73d7 100644
--- a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift
+++ b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift
@@ -618,10 +618,9 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
provider: self,
account: account
)
- case .notification:
- assertionFailure("TODO")
- case .hashtag(_):
- assertionFailure("TODO")
+ case .notification, .hashtag(_), .notificationBanner(_):
+ // not supposed to happen
+ break
}
}
}
diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift
index d5e654dcad..bea5118e4e 100644
--- a/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift
+++ b/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift
@@ -22,42 +22,44 @@ extension UITableViewDelegate where Self: DataSourceProvider & AuthContextProvid
return
}
switch item {
- case .account(let account, relationship: _):
- await DataSourceFacade.coordinateToProfileScene(provider: self, account: account)
- case .status(let status):
+ case .account(let account, relationship: _):
+ await DataSourceFacade.coordinateToProfileScene(provider: self, account: account)
+ case .status(let status):
+ await DataSourceFacade.coordinateToStatusThreadScene(
+ provider: self,
+ target: .status, // remove reblog wrapper
+ status: status
+ )
+ case .hashtag(let tag):
+ await DataSourceFacade.coordinateToHashtagScene(
+ provider: self,
+ tag: tag
+ )
+ case .notification(let notification):
+ let _status: MastodonStatus? = notification.status
+ if let status = _status {
await DataSourceFacade.coordinateToStatusThreadScene(
provider: self,
target: .status, // remove reblog wrapper
status: status
)
- case .hashtag(let tag):
- await DataSourceFacade.coordinateToHashtagScene(
- provider: self,
- tag: tag
+ } else if let accountWarning = notification.entity.accountWarning {
+ let url = Mastodon.API.disputesEndpoint(domain: authContext.mastodonAuthenticationBox.domain, strikeId: accountWarning.id)
+ _ = coordinator.present(
+ scene: .safari(url: url),
+ from: self,
+ transition: .safariPresent(animated: true, completion: nil)
)
- case .notification(let notification):
- let _status: MastodonStatus? = notification.status
- if let status = _status {
- await DataSourceFacade.coordinateToStatusThreadScene(
- provider: self,
- target: .status, // remove reblog wrapper
- status: status
- )
- } else if let accountWarning = notification.entity.accountWarning {
- let url = Mastodon.API.disputesEndpoint(domain: authContext.mastodonAuthenticationBox.domain, strikeId: accountWarning.id)
- _ = coordinator.present(
- scene: .safari(url: url),
- from: self,
- transition: .safariPresent(animated: true, completion: nil)
- )
- } else {
- await DataSourceFacade.coordinateToProfileScene(
- provider: self,
- account: notification.entity.account
- )
- } // end Task
- } // end func
+ } else {
+ await DataSourceFacade.coordinateToProfileScene(
+ provider: self,
+ account: notification.entity.account
+ )
+ }
+ case .notificationBanner(let policy):
+ await DataSourceFacade.coordinateToNotificationRequests(provider: self)
+ }
}
}
}
diff --git a/Mastodon/Protocol/Provider/DataSourceProvider.swift b/Mastodon/Protocol/Provider/DataSourceProvider.swift
index fe886800e1..b6e1134fb3 100644
--- a/Mastodon/Protocol/Provider/DataSourceProvider.swift
+++ b/Mastodon/Protocol/Provider/DataSourceProvider.swift
@@ -14,6 +14,7 @@ enum DataSourceItem: Hashable {
case status(record: MastodonStatus)
case hashtag(tag: Mastodon.Entity.Tag)
case notification(record: MastodonNotification)
+ case notificationBanner(policy: Mastodon.Entity.NotificationPolicy)
case account(account: Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?)
}
diff --git a/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift b/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift
new file mode 100644
index 0000000000..2fd02878d7
--- /dev/null
+++ b/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift
@@ -0,0 +1,88 @@
+// Copyright © 2024 Mastodon gGmbH. All rights reserved.
+
+import UIKit
+import MastodonSDK
+import MastodonUI
+import MastodonLocalization
+
+class NotificationFilteringBannerTableViewCell: UITableViewCell {
+
+ static let reuseIdentifier = "NotificationFilteringBannerTableViewCell"
+
+ let iconImageView: UIImageView
+ let iconImageWrapperView: UIView
+
+ let titleLabel: UILabel
+ let subtitleLabel: UILabel
+ private let contentStackView: UIStackView
+ private let labelStackView: UIStackView
+ let separatorLine: UIView
+
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+
+ let iconConfiguration = UIImage.SymbolConfiguration(scale: .large)
+ let icon = UIImage(systemName: "archivebox", withConfiguration: iconConfiguration)
+ iconImageView = UIImageView(image: icon)
+ iconImageView.translatesAutoresizingMaskIntoConstraints = false
+
+ iconImageWrapperView = UIView()
+ iconImageWrapperView.translatesAutoresizingMaskIntoConstraints = false
+ iconImageWrapperView.addSubview(iconImageView)
+
+ titleLabel = UILabel()
+ titleLabel.text = L10n.Scene.Notification.FilteredNotification.title
+ titleLabel.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular))
+
+ subtitleLabel = UILabel()
+ subtitleLabel.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular))
+ subtitleLabel.textColor = .secondaryLabel
+
+ labelStackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
+ labelStackView.translatesAutoresizingMaskIntoConstraints = false
+ labelStackView.alignment = .leading
+ labelStackView.axis = .vertical
+
+ contentStackView = UIStackView(arrangedSubviews: [iconImageWrapperView, labelStackView])
+ contentStackView.translatesAutoresizingMaskIntoConstraints = false
+ contentStackView.alignment = .center
+ contentStackView.axis = .horizontal
+ contentStackView.spacing = 12
+
+ separatorLine = UIView.separatorLine
+ separatorLine.translatesAutoresizingMaskIntoConstraints = false
+
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+
+ contentView.addSubview(contentStackView)
+ contentView.addSubview(separatorLine)
+
+ setupConstraints()
+ }
+
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
+
+ private func setupConstraints() {
+ let constraints = [
+ iconImageWrapperView.widthAnchor.constraint(equalToConstant: CGSize.authorAvatarButtonSize.width),
+ iconImageWrapperView.heightAnchor.constraint(equalToConstant: CGSize.authorAvatarButtonSize.height).priority(.defaultHigh),
+ iconImageView.centerXAnchor.constraint(equalTo: iconImageWrapperView.centerXAnchor),
+ iconImageView.centerYAnchor.constraint(equalTo: iconImageWrapperView.centerYAnchor),
+
+ contentStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
+ contentStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
+ contentView.trailingAnchor.constraint(equalTo: contentStackView.trailingAnchor, constant: 16),
+ separatorLine.topAnchor.constraint(equalTo: contentStackView.bottomAnchor, constant: 7),
+
+ separatorLine.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
+ separatorLine.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
+ separatorLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
+ separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView))
+ ]
+
+ NSLayoutConstraint.activate(constraints)
+ }
+
+ func configure(with policy: Mastodon.Entity.NotificationPolicy) {
+ subtitleLabel.text = L10n.Plural.FilteredNotificationBanner.subtitle(policy.summary.pendingRequestsCount)
+ }
+}
diff --git a/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyFilterTableViewCell.swift b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyFilterTableViewCell.swift
new file mode 100644
index 0000000000..76950c2139
--- /dev/null
+++ b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyFilterTableViewCell.swift
@@ -0,0 +1,54 @@
+// Copyright © 2024 Mastodon gGmbH. All rights reserved.
+
+import UIKit
+
+protocol NotificationPolicyFilterTableViewCellDelegate: AnyObject {
+ func toggleValueChanged(_ tableViewCell: NotificationPolicyFilterTableViewCell, filterItem: NotificationFilterItem, newValue: Bool)
+}
+
+class NotificationPolicyFilterTableViewCell: ToggleTableViewCell {
+ override class var reuseIdentifier: String {
+ return "NotificationPolicyFilterTableViewCell"
+ }
+
+ var filterItem: NotificationFilterItem?
+ weak var delegate: NotificationPolicyFilterTableViewCellDelegate?
+
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+
+ label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular))
+ subtitleLabel.textColor = .secondaryLabel
+ subtitleLabel.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 15, weight: .regular))
+
+ toggle.addTarget(self, action: #selector(NotificationPolicyFilterTableViewCell.toggleValueChanged(_:)), for: .valueChanged)
+ }
+
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
+
+ public func configure(with filterItem: NotificationFilterItem, viewModel: NotificationFilterViewModel) {
+ label.text = filterItem.title
+ subtitleLabel.text = filterItem.subtitle
+ self.filterItem = filterItem
+
+ let toggleIsOn: Bool
+ switch filterItem {
+ case .notFollowing:
+ toggleIsOn = viewModel.notFollowing
+ case .noFollower:
+ toggleIsOn = viewModel.noFollower
+ case .newAccount:
+ toggleIsOn = viewModel.newAccount
+ case .privateMentions:
+ toggleIsOn = viewModel.privateMentions
+ }
+
+ toggle.isOn = toggleIsOn
+ }
+
+ @objc func toggleValueChanged(_ sender: UISwitch) {
+ guard let filterItem, let delegate else { return }
+
+ delegate.toggleValueChanged(self, filterItem: filterItem, newValue: sender.isOn)
+ }
+}
diff --git a/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyHeaderView.swift b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyHeaderView.swift
new file mode 100644
index 0000000000..84f2a07bb0
--- /dev/null
+++ b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyHeaderView.swift
@@ -0,0 +1,53 @@
+// Copyright © 2024 Mastodon gGmbH. All rights reserved.
+
+import UIKit
+import MastodonLocalization
+
+class NotificationPolicyHeaderView: UIView {
+ let titleLabel: UILabel
+ let closeButton: UIButton
+
+ override init(frame: CGRect) {
+
+ titleLabel = UILabel()
+ titleLabel.translatesAutoresizingMaskIntoConstraints = false
+ titleLabel.font = UIFontMetrics(forTextStyle: .title3).scaledFont(for: .systemFont(ofSize: 20, weight: .bold))
+ titleLabel.text = L10n.Scene.Notification.Policy.title
+
+
+ let buttonImageConfiguration = UIImage
+ .SymbolConfiguration(pointSize: 30)
+ .applying(UIImage.SymbolConfiguration(paletteColors: [.secondaryLabel, .quaternarySystemFill]))
+ let buttonImage = UIImage(systemName: "xmark.circle.fill", withConfiguration: buttonImageConfiguration)
+ var buttonConfiguration = UIButton.Configuration.plain()
+ buttonConfiguration.image = buttonImage
+ buttonConfiguration.contentInsets = .init(top: 0, leading: 10, bottom: 0, trailing: 0)
+
+ closeButton = UIButton(configuration: buttonConfiguration)
+ closeButton.translatesAutoresizingMaskIntoConstraints = false
+ closeButton.contentMode = .center
+
+ super.init(frame: frame)
+ addSubview(titleLabel)
+ addSubview(closeButton)
+
+ setupConstraints()
+ }
+
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
+
+ private func setupConstraints() {
+ let constraints = [
+ titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
+ titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 10),
+ closeButton.leadingAnchor.constraint(greaterThanOrEqualTo: titleLabel.trailingAnchor, constant: 8),
+ bottomAnchor.constraint(equalTo: titleLabel.bottomAnchor),
+
+ closeButton.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor),
+ trailingAnchor.constraint(equalTo: closeButton.trailingAnchor, constant: 20),
+ bottomAnchor.constraint(greaterThanOrEqualTo: closeButton.bottomAnchor)
+ ]
+
+ NSLayoutConstraint.activate(constraints)
+ }
+}
diff --git a/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift
new file mode 100644
index 0000000000..22f77332bf
--- /dev/null
+++ b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift
@@ -0,0 +1,212 @@
+// Copyright © 2024 Mastodon gGmbH. All rights reserved.
+
+import UIKit
+import MastodonLocalization
+import MastodonAsset
+import MastodonCore
+import MastodonSDK
+
+enum NotificationFilterSection: Hashable {
+ case main
+}
+
+enum NotificationFilterItem: Hashable, CaseIterable {
+ case notFollowing
+ case noFollower
+ case newAccount
+ case privateMentions
+
+ var title: String {
+ switch self {
+ case .notFollowing:
+ return L10n.Scene.Notification.Policy.NotFollowing.title
+ case .noFollower:
+ return L10n.Scene.Notification.Policy.NoFollower.title
+ case .newAccount:
+ return L10n.Scene.Notification.Policy.NewAccount.title
+ case .privateMentions:
+ return L10n.Scene.Notification.Policy.PrivateMentions.title
+ }
+ }
+
+ var subtitle: String {
+ switch self {
+ case .notFollowing:
+ return L10n.Scene.Notification.Policy.NotFollowing.subtitle
+ case .noFollower:
+ return L10n.Scene.Notification.Policy.NoFollower.subtitle
+ case .newAccount:
+ return L10n.Scene.Notification.Policy.NewAccount.subtitle
+ case .privateMentions:
+ return L10n.Scene.Notification.Policy.PrivateMentions.subtitle
+ }
+ }
+}
+
+struct NotificationFilterViewModel {
+ var notFollowing: Bool
+ var noFollower: Bool
+ var newAccount: Bool
+ var privateMentions: Bool
+
+ let appContext: AppContext
+
+ init(appContext: AppContext, notFollowing: Bool, noFollower: Bool, newAccount: Bool, privateMentions: Bool) {
+ self.appContext = appContext
+ self.notFollowing = notFollowing
+ self.noFollower = noFollower
+ self.newAccount = newAccount
+ self.privateMentions = privateMentions
+ }
+}
+
+protocol NotificationPolicyViewControllerDelegate: AnyObject {
+ func policyUpdated(_ viewController: NotificationPolicyViewController, newPolicy: Mastodon.Entity.NotificationPolicy)
+}
+
+class NotificationPolicyViewController: UIViewController {
+
+ let tableView: UITableView
+ let headerBar: NotificationPolicyHeaderView
+ var saveItem: UIBarButtonItem?
+ var dataSource: UITableViewDiffableDataSource?
+ let items: [NotificationFilterItem]
+ var viewModel: NotificationFilterViewModel
+ weak var delegate: NotificationPolicyViewControllerDelegate?
+
+ init(viewModel: NotificationFilterViewModel) {
+ self.viewModel = viewModel
+ items = NotificationFilterItem.allCases
+
+ headerBar = NotificationPolicyHeaderView()
+ headerBar.translatesAutoresizingMaskIntoConstraints = false
+
+ tableView = UITableView(frame: .zero, style: .insetGrouped)
+ tableView.translatesAutoresizingMaskIntoConstraints = false
+ tableView.register(NotificationPolicyFilterTableViewCell.self, forCellReuseIdentifier: NotificationPolicyFilterTableViewCell.reuseIdentifier)
+ tableView.contentInset.top = -20
+
+ super.init(nibName: nil, bundle: nil)
+
+ let dataSource = UITableViewDiffableDataSource(tableView: tableView) { [weak self] tableView, indexPath, itemIdentifier in
+ guard let self, let cell = tableView.dequeueReusableCell(withIdentifier: NotificationPolicyFilterTableViewCell.reuseIdentifier, for: indexPath) as? NotificationPolicyFilterTableViewCell else {
+ fatalError("No NotificationPolicyFilterTableViewCell")
+ }
+
+ let item = items[indexPath.row]
+ cell.configure(with: item, viewModel: self.viewModel)
+ cell.delegate = self
+
+ return cell
+ }
+
+ tableView.dataSource = dataSource
+ tableView.delegate = self
+
+ self.dataSource = dataSource
+ view.addSubview(headerBar)
+ view.addSubview(tableView)
+ view.backgroundColor = .systemGroupedBackground
+ headerBar.closeButton.addTarget(self, action: #selector(NotificationPolicyViewController.save(_:)), for: .touchUpInside)
+
+ setupConstraints()
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ var snapshot = NSDiffableDataSourceSnapshot()
+
+ snapshot.appendSections([.main])
+ snapshot.appendItems(items)
+
+ dataSource?.apply(snapshot, animatingDifferences: false)
+ }
+
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
+
+ private func setupConstraints() {
+ let constraints = [
+ headerBar.topAnchor.constraint(equalTo: view.topAnchor),
+ headerBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ view.trailingAnchor.constraint(equalTo: headerBar.trailingAnchor),
+
+ tableView.topAnchor.constraint(equalTo: headerBar.bottomAnchor),
+ tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ view.trailingAnchor.constraint(equalTo: tableView.trailingAnchor),
+ view.bottomAnchor.constraint(equalTo: tableView.bottomAnchor),
+ ]
+
+ NSLayoutConstraint.activate(constraints)
+ }
+
+ // MARK: - Action
+
+ @objc private func save(_ sender: UIButton) {
+ guard let authenticationBox = viewModel.appContext.authenticationService.mastodonAuthenticationBoxes.first else { return }
+
+ Task { [weak self] in
+ guard let self else { return }
+
+ do {
+ let updatedPolicy = try await viewModel.appContext.apiService.updateNotificationPolicy(
+ authenticationBox: authenticationBox,
+ filterNotFollowing: viewModel.notFollowing,
+ filterNotFollowers: viewModel.noFollower,
+ filterNewAccounts: viewModel.newAccount,
+ filterPrivateMentions: viewModel.privateMentions
+ ).value
+
+ delegate?.policyUpdated(self, newPolicy: updatedPolicy)
+
+ NotificationCenter.default.post(name: .notificationFilteringChanged, object: nil)
+
+ } catch {}
+ }
+
+ dismiss(animated:true)
+ }
+
+ @objc private func cancel(_ sender: UIBarButtonItem) {
+ dismiss(animated: true)
+ }
+}
+
+//MARK: - UITableViewDelegate
+
+extension NotificationPolicyViewController: UITableViewDelegate {
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+ tableView.deselectRow(at: indexPath, animated: true)
+
+ let filterItem = items[indexPath.row]
+ switch filterItem {
+ case .notFollowing:
+ viewModel.notFollowing.toggle()
+ case .noFollower:
+ viewModel.noFollower.toggle()
+ case .newAccount:
+ viewModel.newAccount.toggle()
+ case .privateMentions:
+ viewModel.privateMentions.toggle()
+ }
+
+ if let snapshot = dataSource?.snapshot() {
+ dataSource?.applySnapshotUsingReloadData(snapshot)
+ }
+ }
+}
+
+extension NotificationPolicyViewController: NotificationPolicyFilterTableViewCellDelegate {
+ func toggleValueChanged(_ tableViewCell: NotificationPolicyFilterTableViewCell, filterItem: NotificationFilterItem, newValue: Bool) {
+ switch filterItem {
+ case .notFollowing:
+ viewModel.notFollowing = newValue
+ case .noFollower:
+ viewModel.noFollower = newValue
+ case .newAccount:
+ viewModel.newAccount = newValue
+ case .privateMentions:
+ viewModel.privateMentions = newValue
+ }
+ }
+}
diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/Account Notifications/AccountNotificationTimelineViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/Account Notifications/AccountNotificationTimelineViewController.swift
new file mode 100644
index 0000000000..e53886a4a5
--- /dev/null
+++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/Account Notifications/AccountNotificationTimelineViewController.swift
@@ -0,0 +1,52 @@
+// Copyright © 2024 Mastodon gGmbH. All rights reserved.
+
+import UIKit
+import MastodonCore
+import MastodonSDK
+import MastodonLocalization
+
+protocol AccountNotificationTimelineViewControllerDelegate: AnyObject {
+ func acceptRequest(_ viewController: AccountNotificationTimelineViewController, request: Mastodon.Entity.NotificationRequest)
+ func dismissRequest(_ viewController: AccountNotificationTimelineViewController, request: Mastodon.Entity.NotificationRequest)
+}
+
+class AccountNotificationTimelineViewController: NotificationTimelineViewController {
+
+ let request: Mastodon.Entity.NotificationRequest
+ weak var delegate: AccountNotificationTimelineViewControllerDelegate?
+
+ init(viewModel: NotificationTimelineViewModel, context: AppContext, coordinator: SceneCoordinator, notificationRequest: Mastodon.Entity.NotificationRequest) {
+ self.request = notificationRequest
+
+ super.init(viewModel: viewModel, context: context, coordinator: coordinator)
+
+ navigationItem.rightBarButtonItem = UIBarButtonItem(title: nil, image: UIImage(systemName: "ellipsis.circle"), target: nil, action: nil, menu: menu())
+ }
+
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
+
+ // MARK: - Actions
+
+ func menu() -> UIMenu {
+ let menu = UIMenu(children: [
+ UIAction(title: L10n.Scene.Notification.FilteredNotification.accept, image: UIImage(systemName: "checkmark")) { [weak self] _ in
+ guard let self else { return }
+
+ coordinator.showLoading()
+ self.navigationController?.popViewController(animated: true)
+ self.delegate?.acceptRequest(self, request: request)
+ coordinator.hideLoading()
+ },
+ UIAction(title: L10n.Scene.Notification.FilteredNotification.dismiss, image: UIImage(systemName: "speaker.slash")) { [weak self] _ in
+ guard let self else { return }
+
+ coordinator.showLoading()
+ self.navigationController?.popViewController(animated: true)
+ self.delegate?.dismissRequest(self, request: request)
+ coordinator.hideLoading()
+ }
+ ])
+
+ return menu
+ }
+}
diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestCountView.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestCountView.swift
new file mode 100644
index 0000000000..4fbac07339
--- /dev/null
+++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestCountView.swift
@@ -0,0 +1,44 @@
+// Copyright © 2024 Mastodon gGmbH. All rights reserved.
+
+import UIKit
+import MastodonAsset
+
+class NotificationRequestCountView: UIView {
+
+ let countLabel: UILabel
+
+ init() {
+ countLabel = UILabel()
+ countLabel.translatesAutoresizingMaskIntoConstraints = false
+ countLabel.textColor = .white
+ countLabel.font = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: .systemFont(ofSize: 13, weight: .regular))
+ countLabel.textAlignment = .center
+
+ super.init(frame: .zero)
+
+ addSubview(countLabel)
+
+ backgroundColor = Asset.Colors.Brand.blurple.color
+ layer.borderWidth = 2.0
+ layer.borderColor = UIColor.white.cgColor
+ applyCornerRadius(radius: 10)
+
+ setupConstraints()
+ }
+
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
+
+ private func setupConstraints() {
+ let constraints = [
+ countLabel.topAnchor.constraint(equalTo: topAnchor, constant: 2),
+ countLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 5),
+ trailingAnchor.constraint(equalTo: countLabel.trailingAnchor, constant: 5),
+ bottomAnchor.constraint(equalTo: countLabel.bottomAnchor, constant: 2),
+
+ widthAnchor.constraint(greaterThanOrEqualToConstant: 20),
+ heightAnchor.constraint(greaterThanOrEqualToConstant: 20)
+ ]
+
+ NSLayoutConstraint.activate(constraints)
+ }
+}
diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift
new file mode 100644
index 0000000000..e84574820b
--- /dev/null
+++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift
@@ -0,0 +1,215 @@
+// Copyright © 2024 Mastodon gGmbH. All rights reserved.
+
+import UIKit
+import MastodonSDK
+import MetaTextKit
+import MastodonMeta
+import MastodonUI
+import MastodonCore
+import MastodonLocalization
+import MastodonAsset
+
+protocol NotificationRequestTableViewCellDelegate: AnyObject {
+ func acceptNotificationRequest(_ cell: NotificationRequestTableViewCell, notificationRequest: Mastodon.Entity.NotificationRequest)
+ func rejectNotificationRequest(_ cell: NotificationRequestTableViewCell, notificationRequest: Mastodon.Entity.NotificationRequest)
+}
+
+class NotificationRequestTableViewCell: UITableViewCell {
+ static let reuseIdentifier = "NotificationRequestTableViewCell"
+
+ var notificationRequest: Mastodon.Entity.NotificationRequest?
+ weak var delegate: NotificationRequestTableViewCellDelegate?
+
+ let nameLabel: MetaLabel
+ let usernameLabel: MetaLabel
+ let avatarButton: AvatarButton
+ let chevronImageView: UIImageView
+
+ private let labelStackView: UIStackView
+ private let avatarStackView: UIStackView
+ private let contentStackView: UIStackView
+
+ let acceptNotificationRequestButtonShadowBackgroundContainer = ShadowBackgroundContainer()
+ let acceptNotificationRequestButton: HighlightDimmableButton
+ let acceptNotificationRequestActivityIndicatorView: UIActivityIndicatorView
+
+ let rejectNotificationRequestButtonShadowBackgroundContainer = ShadowBackgroundContainer()
+ let rejectNotificationRequestActivityIndicatorView: UIActivityIndicatorView
+ let rejectNotificationRequestButton: HighlightDimmableButton
+
+ let requestCountView: NotificationRequestCountView
+
+ private let buttonStackView: UIStackView
+
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ nameLabel = MetaLabel(style: .statusName)
+ usernameLabel = MetaLabel(style: .statusUsername)
+ avatarButton = AvatarButton()
+ avatarButton.translatesAutoresizingMaskIntoConstraints = false
+ avatarButton.size = CGSize.authorAvatarButtonSize
+ avatarButton.avatarImageView.imageViewSize = CGSize.authorAvatarButtonSize
+
+ labelStackView = UIStackView(arrangedSubviews: [nameLabel, usernameLabel])
+ labelStackView.axis = .vertical
+ labelStackView.alignment = .leading
+ labelStackView.spacing = 4
+
+ acceptNotificationRequestButton = HighlightDimmableButton()
+ acceptNotificationRequestButton.translatesAutoresizingMaskIntoConstraints = false
+ acceptNotificationRequestButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold)
+ acceptNotificationRequestButton.setTitleColor(.white, for: .normal)
+ acceptNotificationRequestButton.setTitle(L10n.Scene.Notification.FilteredNotification.accept, for: .normal)
+ acceptNotificationRequestButton.setImage(UIImage(systemName: "checkmark"), for: .normal)
+ acceptNotificationRequestButton.imageView?.contentMode = .scaleAspectFit
+ acceptNotificationRequestButton.setBackgroundImage(.placeholder(color: Asset.Scene.Notification.confirmFollowRequestButtonBackground.color), for: .normal)
+ acceptNotificationRequestButton.setInsets(forContentPadding: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8), imageTitlePadding: 8)
+ acceptNotificationRequestButton.tintColor = .white
+ acceptNotificationRequestButton.layer.masksToBounds = true
+ acceptNotificationRequestButton.layer.cornerCurve = .continuous
+ acceptNotificationRequestButton.layer.cornerRadius = 10
+ acceptNotificationRequestButton.accessibilityLabel = L10n.Scene.Notification.FollowRequest.accept
+ acceptNotificationRequestButtonShadowBackgroundContainer.cornerRadius = 10
+ acceptNotificationRequestButtonShadowBackgroundContainer.shadowAlpha = 0.1
+ acceptNotificationRequestButtonShadowBackgroundContainer.addSubview(acceptNotificationRequestButton)
+
+ acceptNotificationRequestActivityIndicatorView = UIActivityIndicatorView(style: .medium)
+ acceptNotificationRequestActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
+ acceptNotificationRequestActivityIndicatorView.color = .white
+ acceptNotificationRequestActivityIndicatorView.hidesWhenStopped = true
+ acceptNotificationRequestActivityIndicatorView.stopAnimating()
+ acceptNotificationRequestButton.addSubview(acceptNotificationRequestActivityIndicatorView)
+
+ rejectNotificationRequestButton = HighlightDimmableButton()
+ rejectNotificationRequestButton.translatesAutoresizingMaskIntoConstraints = false
+ rejectNotificationRequestButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold)
+ rejectNotificationRequestButton.setTitleColor(.black, for: .normal)
+ rejectNotificationRequestButton.setTitle(L10n.Scene.Notification.FilteredNotification.dismiss, for: .normal)
+ rejectNotificationRequestButton.setImage(UIImage(systemName: "speaker.slash"), for: .normal)
+ rejectNotificationRequestButton.imageView?.contentMode = .scaleAspectFit
+ rejectNotificationRequestButton.setInsets(forContentPadding: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8), imageTitlePadding: 8)
+ rejectNotificationRequestButton.setBackgroundImage(.placeholder(color: Asset.Scene.Notification.deleteFollowRequestButtonBackground.color), for: .normal)
+ rejectNotificationRequestButton.tintColor = .black
+ rejectNotificationRequestButton.layer.masksToBounds = true
+ rejectNotificationRequestButton.layer.cornerCurve = .continuous
+ rejectNotificationRequestButton.layer.cornerRadius = 10
+ rejectNotificationRequestButton.accessibilityLabel = L10n.Scene.Notification.FollowRequest.reject
+ rejectNotificationRequestButtonShadowBackgroundContainer.cornerRadius = 10
+ rejectNotificationRequestButtonShadowBackgroundContainer.shadowAlpha = 0.1
+ rejectNotificationRequestButtonShadowBackgroundContainer.addSubview(rejectNotificationRequestButton)
+
+ rejectNotificationRequestActivityIndicatorView = UIActivityIndicatorView(style: .medium)
+ rejectNotificationRequestActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
+ rejectNotificationRequestActivityIndicatorView.color = .black
+ rejectNotificationRequestActivityIndicatorView.hidesWhenStopped = true
+ rejectNotificationRequestActivityIndicatorView.stopAnimating()
+ rejectNotificationRequestButton.addSubview(rejectNotificationRequestActivityIndicatorView)
+
+ buttonStackView = UIStackView(arrangedSubviews: [rejectNotificationRequestButtonShadowBackgroundContainer, acceptNotificationRequestButtonShadowBackgroundContainer])
+ buttonStackView.axis = .horizontal
+ buttonStackView.distribution = .fillEqually
+ buttonStackView.spacing = 16
+ buttonStackView.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 16, right: 0) // set bottom padding
+
+ chevronImageView = UIImageView(image: UIImage(systemName: "chevron.right"))
+ chevronImageView.tintColor = .tertiaryLabel
+
+ avatarStackView = UIStackView(arrangedSubviews: [avatarButton, labelStackView, UIView(), chevronImageView])
+ avatarStackView.axis = .horizontal
+ avatarStackView.alignment = .center
+ avatarStackView.spacing = 12
+
+ contentStackView = UIStackView(arrangedSubviews: [avatarStackView, buttonStackView])
+ contentStackView.translatesAutoresizingMaskIntoConstraints = false
+ contentStackView.spacing = 16
+ contentStackView.axis = .vertical
+ contentStackView.alignment = .leading
+
+ requestCountView = NotificationRequestCountView()
+ requestCountView.translatesAutoresizingMaskIntoConstraints = false
+
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+
+ acceptNotificationRequestButton.addTarget(self, action: #selector(NotificationRequestTableViewCell.acceptNotificationRequest(_:)), for: .touchUpInside)
+ rejectNotificationRequestButton.addTarget(self, action: #selector(NotificationRequestTableViewCell.rejectNotificationRequest(_:)), for: .touchUpInside)
+
+ contentView.addSubview(contentStackView)
+ contentView.addSubview(requestCountView)
+ setupConstraints()
+ }
+
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
+
+ private func setupConstraints() {
+ let constraints = [
+
+ contentStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16),
+ contentStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
+ contentView.trailingAnchor.constraint(equalTo: contentStackView.trailingAnchor, constant: 16),
+ contentView.bottomAnchor.constraint(equalTo: contentStackView.bottomAnchor, constant: 16),
+
+ buttonStackView.widthAnchor.constraint(equalTo: contentStackView.widthAnchor),
+ avatarStackView.widthAnchor.constraint(equalTo: contentStackView.widthAnchor),
+
+ avatarButton.widthAnchor.constraint(equalToConstant: CGSize.authorAvatarButtonSize.width).priority(.required - 1),
+ avatarButton.heightAnchor.constraint(equalToConstant: CGSize.authorAvatarButtonSize.height).priority(.required - 1),
+
+ acceptNotificationRequestActivityIndicatorView.centerXAnchor.constraint(equalTo: acceptNotificationRequestButton.centerXAnchor),
+ acceptNotificationRequestActivityIndicatorView.centerYAnchor.constraint(equalTo: acceptNotificationRequestButton.centerYAnchor),
+ rejectNotificationRequestActivityIndicatorView.centerXAnchor.constraint(equalTo: rejectNotificationRequestButton.centerXAnchor),
+ rejectNotificationRequestActivityIndicatorView.centerYAnchor.constraint(equalTo: rejectNotificationRequestButton.centerYAnchor),
+
+ requestCountView.trailingAnchor.constraint(equalTo: avatarButton.trailingAnchor, constant: 2),
+ requestCountView.bottomAnchor.constraint(equalTo: avatarButton.bottomAnchor, constant: 2),
+
+ ]
+ NSLayoutConstraint.activate(constraints)
+
+ acceptNotificationRequestButton.pinToParent()
+ rejectNotificationRequestButton.pinToParent()
+ }
+
+ override func prepareForReuse() {
+ avatarButton.avatarImageView.image = nil
+ avatarButton.avatarImageView.cancelTask()
+ }
+
+ func configure(with request: Mastodon.Entity.NotificationRequest) {
+ let account = request.account
+
+ avatarButton.avatarImageView.configure(with: account.avatarImageURL())
+ avatarButton.avatarImageView.configure(cornerConfiguration: .init(corner: .fixed(radius: 12)))
+
+ // author name
+ let metaAccountName: MetaContent
+ do {
+ let content = MastodonContent(content: account.displayNameWithFallback, emojis: account.emojis.asDictionary)
+ metaAccountName = try MastodonMetaContent.convert(document: content)
+ } catch {
+ assertionFailure(error.localizedDescription)
+ metaAccountName = PlaintextMetaContent(string: account.displayNameWithFallback)
+ }
+ nameLabel.configure(content: metaAccountName)
+
+ let metaUsername = PlaintextMetaContent(string: "@\(account.acct)")
+ usernameLabel.configure(content: metaUsername)
+
+ requestCountView.countLabel.text = request.notificationsCount
+ requestCountView.setNeedsLayout()
+ requestCountView.layoutIfNeeded()
+
+ self.notificationRequest = request
+ }
+
+ // MARK: - Actions
+ @objc private func acceptNotificationRequest(_ sender: UIButton) {
+ guard let notificationRequest, let delegate else { return }
+
+ delegate.acceptNotificationRequest(self, notificationRequest: notificationRequest)
+ }
+ @objc private func rejectNotificationRequest(_ sender: UIButton) {
+ guard let notificationRequest, let delegate else { return }
+
+ delegate.rejectNotificationRequest(self, notificationRequest: notificationRequest)
+ }
+
+}
diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift
new file mode 100644
index 0000000000..8befab7c75
--- /dev/null
+++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift
@@ -0,0 +1,222 @@
+// Copyright © 2024 Mastodon gGmbH. All rights reserved.
+
+import UIKit
+import MastodonSDK
+import MastodonCore
+import MastodonAsset
+import MastodonLocalization
+
+enum NotificationRequestsSection: Hashable {
+ case main
+}
+
+enum NotificationRequestItem: Hashable {
+ case item(Mastodon.Entity.NotificationRequest)
+}
+
+protocol NotificationRequestsTableViewControllerDelegate: AnyObject {
+ func notificationRequestsUpdated(_ viewController: NotificationRequestsTableViewController)
+}
+
+class NotificationRequestsTableViewController: UIViewController, NeedsDependency {
+ var context: AppContext!
+ var coordinator: SceneCoordinator!
+ weak var delegate: NotificationRequestsTableViewControllerDelegate?
+
+ let tableView: UITableView
+ var viewModel: NotificationRequestsViewModel
+ var dataSource: UITableViewDiffableDataSource?
+
+ init(viewModel: NotificationRequestsViewModel) {
+
+ self.viewModel = viewModel
+ self.context = viewModel.appContext
+ self.coordinator = viewModel.coordinator
+
+ tableView = UITableView(frame: .zero)
+ tableView.translatesAutoresizingMaskIntoConstraints = false
+ tableView.backgroundColor = .secondarySystemBackground
+ tableView.register(NotificationRequestTableViewCell.self, forCellReuseIdentifier: NotificationRequestTableViewCell.reuseIdentifier)
+
+ super.init(nibName: nil, bundle: nil)
+
+ view.addSubview(tableView)
+ tableView.pinToParent()
+
+ let dataSource = UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, itemIdentifier in
+ guard let cell = tableView.dequeueReusableCell(withIdentifier: NotificationRequestTableViewCell.reuseIdentifier, for: indexPath) as? NotificationRequestTableViewCell else {
+ fatalError("No NotificationRequestTableViewCell")
+ }
+
+ let request = viewModel.requests[indexPath.row]
+ cell.configure(with: request)
+ cell.delegate = self
+
+ return cell
+ }
+
+ tableView.dataSource = dataSource
+ tableView.delegate = self
+ self.dataSource = dataSource
+
+ title = L10n.Scene.Notification.FilteredNotification.title
+ }
+
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
+
+ override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+ var snapshot = NSDiffableDataSourceSnapshot()
+ snapshot.appendSections([.main])
+ snapshot.appendItems(viewModel.requests.compactMap { NotificationRequestItem.item($0) } )
+
+ dataSource?.apply(snapshot)
+ }
+}
+
+// MARK: - UITableViewDelegate
+extension NotificationRequestsTableViewController: UITableViewDelegate {
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+ tableView.deselectRow(at: indexPath, animated: true)
+
+ let request = viewModel.requests[indexPath.row]
+
+ Task { [weak self] in
+ guard let self else { return }
+
+ let viewController = await DataSourceFacade.coordinateToNotificationRequest(request: request, provider: self)
+ viewController?.delegate = self
+ }
+ }
+
+ func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
+ let dismissAction = UIContextualAction(style: .normal, title: "Dismiss") { [weak self] action, view, completion in
+ guard let request = self?.viewModel.requests[indexPath.row], let cell = tableView.cellForRow(at: indexPath) as? NotificationRequestTableViewCell else { return completion(false) }
+
+ self?.rejectNotificationRequest(cell, notificationRequest: request)
+ completion(true)
+ }
+
+ dismissAction.image = UIImage(systemName: "speaker.slash")
+
+ let swipeAction = UISwipeActionsConfiguration(actions: [dismissAction])
+ swipeAction.performsFirstActionWithFullSwipe = true
+ return swipeAction
+
+ }
+}
+
+// MARK: - AuthContextProvider
+extension NotificationRequestsTableViewController: AuthContextProvider {
+ var authContext: AuthContext { viewModel.authContext }
+}
+
+extension NotificationRequestsTableViewController: NotificationRequestTableViewCellDelegate {
+ func acceptNotificationRequest(_ cell: NotificationRequestTableViewCell, notificationRequest: MastodonSDK.Mastodon.Entity.NotificationRequest) {
+
+ cell.acceptNotificationRequestActivityIndicatorView.isHidden = false
+ cell.acceptNotificationRequestActivityIndicatorView.startAnimating()
+ cell.acceptNotificationRequestButton.tintColor = .clear
+ cell.acceptNotificationRequestButton.setTitleColor(.clear, for: .normal)
+ cell.rejectNotificationRequestButton.isUserInteractionEnabled = false
+ cell.acceptNotificationRequestButton.isUserInteractionEnabled = false
+
+ Task { [weak self] in
+ guard let self else { return }
+ do {
+ try await acceptNotificationRequest(notificationRequest)
+ } catch {
+ cell.acceptNotificationRequestActivityIndicatorView.stopAnimating()
+ cell.acceptNotificationRequestButton.tintColor = .white
+ cell.acceptNotificationRequestButton.setTitleColor(.white, for: .normal)
+ cell.rejectNotificationRequestButton.isUserInteractionEnabled = true
+ cell.acceptNotificationRequestButton.isUserInteractionEnabled = true
+ }
+ }
+ }
+
+ private func acceptNotificationRequest(_ notificationRequest: MastodonSDK.Mastodon.Entity.NotificationRequest) async throws {
+ _ = try await context.apiService.acceptNotificationRequests(authenticationBox: authContext.mastodonAuthenticationBox,
+ id: notificationRequest.id)
+
+ let requests = try await context.apiService.notificationRequests(authenticationBox: authContext.mastodonAuthenticationBox).value
+
+ NotificationCenter.default.post(name: .notificationFilteringChanged, object: nil)
+
+ await MainActor.run { [weak self] in
+ guard let self else { return }
+
+ if requests.count > 0 {
+ self.viewModel.requests = requests
+ var snapshot = NSDiffableDataSourceSnapshot()
+ snapshot.appendSections([.main])
+ snapshot.appendItems(self.viewModel.requests.compactMap { NotificationRequestItem.item($0) } )
+
+ self.dataSource?.apply(snapshot)
+ } else {
+ _ = self.navigationController?.popViewController(animated: true)
+ }
+ }
+ }
+
+ func rejectNotificationRequest(_ cell: NotificationRequestTableViewCell, notificationRequest: MastodonSDK.Mastodon.Entity.NotificationRequest) {
+
+ cell.rejectNotificationRequestActivityIndicatorView.isHidden = false
+ cell.rejectNotificationRequestActivityIndicatorView.startAnimating()
+ cell.rejectNotificationRequestButton.tintColor = .clear
+ cell.rejectNotificationRequestButton.setTitleColor(.clear, for: .normal)
+ cell.rejectNotificationRequestButton.isUserInteractionEnabled = false
+ cell.acceptNotificationRequestButton.isUserInteractionEnabled = false
+
+ Task { [weak self] in
+ guard let self else { return }
+ do {
+ try await rejectNotificationRequest(notificationRequest)
+ } catch {
+ cell.rejectNotificationRequestActivityIndicatorView.stopAnimating()
+ cell.rejectNotificationRequestButton.tintColor = .black
+ cell.rejectNotificationRequestButton.setTitleColor(.black, for: .normal)
+ cell.rejectNotificationRequestButton.isUserInteractionEnabled = true
+ cell.acceptNotificationRequestButton.isUserInteractionEnabled = true
+ }
+ }
+ }
+
+ private func rejectNotificationRequest(_ notificationRequest: MastodonSDK.Mastodon.Entity.NotificationRequest) async throws {
+ _ = try await context.apiService.rejectNotificationRequests(authenticationBox: authContext.mastodonAuthenticationBox,
+ id: notificationRequest.id)
+
+ let requests = try await context.apiService.notificationRequests(authenticationBox: authContext.mastodonAuthenticationBox).value
+
+ NotificationCenter.default.post(name: .notificationFilteringChanged, object: nil)
+
+ await MainActor.run { [weak self] in
+ guard let self else { return }
+
+ if requests.count > 0 {
+ self.viewModel.requests = requests
+ var snapshot = NSDiffableDataSourceSnapshot()
+ snapshot.appendSections([.main])
+ snapshot.appendItems(self.viewModel.requests.compactMap { NotificationRequestItem.item($0) } )
+
+ self.dataSource?.apply(snapshot)
+ } else {
+ _ = self.navigationController?.popViewController(animated: true)
+ }
+ }
+ }
+}
+
+extension NotificationRequestsTableViewController: AccountNotificationTimelineViewControllerDelegate {
+ func acceptRequest(_ viewController: AccountNotificationTimelineViewController, request: MastodonSDK.Mastodon.Entity.NotificationRequest) {
+ Task {
+ try? await acceptNotificationRequest(request)
+ }
+ }
+
+ func dismissRequest(_ viewController: AccountNotificationTimelineViewController, request: MastodonSDK.Mastodon.Entity.NotificationRequest) {
+ Task {
+ try? await rejectNotificationRequest(request)
+ }
+ }
+}
diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsViewModel.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsViewModel.swift
new file mode 100644
index 0000000000..4ffe3be116
--- /dev/null
+++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsViewModel.swift
@@ -0,0 +1,20 @@
+// Copyright © 2024 Mastodon gGmbH. All rights reserved.
+
+import Foundation
+import MastodonSDK
+import MastodonCore
+
+struct NotificationRequestsViewModel {
+ let appContext: AppContext
+ let authContext: AuthContext
+ let coordinator: SceneCoordinator
+
+ var requests: [Mastodon.Entity.NotificationRequest]
+
+ init(appContext: AppContext, authContext: AuthContext, coordinator: SceneCoordinator, requests: [Mastodon.Entity.NotificationRequest]) {
+ self.appContext = appContext
+ self.authContext = authContext
+ self.coordinator = coordinator
+ self.requests = requests
+ }
+}
diff --git a/Mastodon/Scene/Notification/NotificationItem.swift b/Mastodon/Scene/Notification/NotificationItem.swift
index d5727e813e..72ca3d8454 100644
--- a/Mastodon/Scene/Notification/NotificationItem.swift
+++ b/Mastodon/Scene/Notification/NotificationItem.swift
@@ -10,6 +10,7 @@ import Foundation
import MastodonSDK
enum NotificationItem: Hashable {
+ case filteredNotifications(policy: Mastodon.Entity.NotificationPolicy)
case feed(record: MastodonFeed)
case feedLoader(record: MastodonFeed)
case bottomLoader
diff --git a/Mastodon/Scene/Notification/NotificationSection.swift b/Mastodon/Scene/Notification/NotificationSection.swift
index ac80faf974..cb630b904e 100644
--- a/Mastodon/Scene/Notification/NotificationSection.swift
+++ b/Mastodon/Scene/Notification/NotificationSection.swift
@@ -39,6 +39,7 @@ extension NotificationSection {
tableView.register(NotificationTableViewCell.self, forCellReuseIdentifier: String(describing: NotificationTableViewCell.self))
tableView.register(AccountWarningNotificationCell.self, forCellReuseIdentifier: AccountWarningNotificationCell.reuseIdentifier)
tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self))
+ tableView.register(NotificationFilteringBannerTableViewCell.self, forCellReuseIdentifier: NotificationFilteringBannerTableViewCell.reuseIdentifier)
return UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item -> UITableViewCell? in
switch item {
@@ -67,6 +68,12 @@ extension NotificationSection {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell
cell.activityIndicatorView.startAnimating()
return cell
+
+ case .filteredNotifications(let policy):
+ let cell = tableView.dequeueReusableCell(withIdentifier: NotificationFilteringBannerTableViewCell.reuseIdentifier, for: indexPath) as! NotificationFilteringBannerTableViewCell
+ cell.configure(with: policy)
+
+ return cell
}
}
}
diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController+DataSourceProvider.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController+DataSourceProvider.swift
index e4bcc8a125..04a8820a4e 100644
--- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController+DataSourceProvider.swift
+++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController+DataSourceProvider.swift
@@ -33,7 +33,9 @@ extension NotificationTimelineViewController: DataSourceProvider {
}
}()
return item
- default:
+ case .filteredNotifications(let policy):
+ return DataSourceItem.notificationBanner(policy: policy)
+ case .bottomLoader, .feedLoader(_):
return nil
}
}
diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift
index cd8a3158e9..60906a15a4 100644
--- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift
+++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift
@@ -11,18 +11,18 @@ import CoreDataStack
import MastodonCore
import MastodonLocalization
-final class NotificationTimelineViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
+class NotificationTimelineViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
- weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
- weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
+ weak var context: AppContext!
+ weak var coordinator: SceneCoordinator!
let mediaPreviewTransitionController = MediaPreviewTransitionController()
var disposeBag = Set()
var observations = Set()
- var viewModel: NotificationTimelineViewModel!
-
+ let viewModel: NotificationTimelineViewModel
+
private(set) lazy var refreshControl: RefreshControl = {
let refreshControl = RefreshControl()
refreshControl.addTarget(self, action: #selector(NotificationTimelineViewController.refreshControlValueChanged(_:)), for: .valueChanged)
@@ -31,13 +31,26 @@ final class NotificationTimelineViewController: UIViewController, NeedsDependenc
private(set) lazy var tableView: UITableView = {
let tableView = UITableView()
- tableView.backgroundColor = .clear
+ tableView.backgroundColor = .secondarySystemBackground
tableView.rowHeight = UITableView.automaticDimension
tableView.separatorStyle = .none
return tableView
}()
let cellFrameCache = NSCache()
+
+ init(viewModel: NotificationTimelineViewModel, context: AppContext, coordinator: SceneCoordinator) {
+ self.viewModel = viewModel
+ self.context = context
+ self.coordinator = coordinator
+
+ super.init(nibName: nil, bundle: nil)
+
+ title = viewModel.scope.title
+ view.backgroundColor = .secondarySystemBackground
+ }
+
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
}
extension NotificationTimelineViewController {
@@ -113,6 +126,9 @@ extension NotificationTimelineViewController {
@objc private func refreshControlValueChanged(_ sender: RefreshControl) {
Task {
+ let policy = try? await context.apiService.notificationPolicy(authenticationBox: authContext.mastodonAuthenticationBox)
+ viewModel.notificationPolicy = policy?.value
+
await viewModel.loadLatest()
}
}
diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift
index 9c5bf2949c..6070f48d41 100644
--- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift
+++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift
@@ -33,7 +33,7 @@ extension NotificationTimelineViewModel {
dataController.$records
.receive(on: DispatchQueue.main)
.sink { [weak self] records in
- guard let self = self else { return }
+ guard let self else { return }
guard let diffableDataSource = self.diffableDataSource else { return }
Task {
@@ -44,6 +44,9 @@ extension NotificationTimelineViewModel {
}
var snapshot = NSDiffableDataSourceSnapshot()
snapshot.appendSections([.main])
+ if self.scope == .everything, let notificationPolicy = self.notificationPolicy, notificationPolicy.summary.pendingRequestsCount > 0 {
+ snapshot.appendItems([.filteredNotifications(policy: notificationPolicy)])
+ }
snapshot.appendItems(newItems.removingDuplicates(), toSection: .main)
return snapshot
}()
diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+LoadOldestState.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+LoadOldestState.swift
index 0999618569..7c6d8a347e 100644
--- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+LoadOldestState.swift
+++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+LoadOldestState.swift
@@ -9,6 +9,7 @@ import CoreDataStack
import Foundation
import GameplayKit
import MastodonSDK
+import MastodonCore
extension NotificationTimelineViewModel {
class LoadOldestState: GKState {
@@ -51,8 +52,22 @@ extension NotificationTimelineViewModel.LoadOldestState {
stateMachine.enter(Fail.self)
return
}
- let scope = viewModel.scope
+ let scope: APIService.MastodonNotificationScope?
+ let accountID: String?
+
+ switch viewModel.scope {
+ case .everything:
+ scope = .everything
+ accountID = nil
+ case .mentions:
+ scope = .mentions
+ accountID = nil
+ case .fromAccount(let account):
+ scope = nil
+ accountID = account.id
+ }
+
Task {
let _maxID: Mastodon.Entity.Notification.ID? = lastFeedRecord.notification?.id
@@ -64,6 +79,7 @@ extension NotificationTimelineViewModel.LoadOldestState {
do {
let response = try await viewModel.context.apiService.notifications(
maxID: maxID,
+ accountID: accountID,
scope: scope,
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
)
diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift
index 0df34434a4..2f8d9a6b5a 100644
--- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift
+++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift
@@ -11,6 +11,7 @@ import CoreDataStack
import GameplayKit
import MastodonSDK
import MastodonCore
+import MastodonLocalization
final class NotificationTimelineViewModel {
@@ -20,6 +21,7 @@ final class NotificationTimelineViewModel {
let context: AppContext
let authContext: AuthContext
let scope: Scope
+ var notificationPolicy: Mastodon.Entity.NotificationPolicy?
let dataController: FeedDataController
@Published var isLoadingLatest = false
@Published var lastAutomaticFetchTimestamp: Date?
@@ -46,12 +48,14 @@ final class NotificationTimelineViewModel {
init(
context: AppContext,
authContext: AuthContext,
- scope: Scope
+ scope: Scope,
+ notificationPolicy: Mastodon.Entity.NotificationPolicy? = nil
) {
self.context = context
self.authContext = authContext
self.scope = scope
self.dataController = FeedDataController(context: context, authContext: authContext)
+ self.notificationPolicy = notificationPolicy
switch scope {
case .everything:
@@ -62,6 +66,8 @@ final class NotificationTimelineViewModel {
self.dataController.records = (try? FileManager.default.cachedNotificationsMentions(for: authContext.mastodonAuthenticationBox))?.map({ notification in
MastodonFeed.fromNotification(notification, relationship: nil, kind: .notificationMentions)
}) ?? []
+ case .fromAccount(_):
+ self.dataController.records = []
}
self.dataController.$records
@@ -77,18 +83,47 @@ final class NotificationTimelineViewModel {
FileManager.default.cacheNotificationsAll(items: items, for: authContext.mastodonAuthenticationBox)
case .mentions:
FileManager.default.cacheNotificationsMentions(items: items, for: authContext.mastodonAuthenticationBox)
+ case .fromAccount(_):
+ //NOTE: we don't persist these
+ break
}
})
.store(in: &disposeBag)
+
+ NotificationCenter.default.addObserver(self, selector: #selector(Self.notificationFilteringChanged(_:)), name: .notificationFilteringChanged, object: nil)
+ }
+
+ //MARK: - Notifications
+
+ @objc func notificationFilteringChanged(_ notification: Notification) {
+ Task { [weak self] in
+ guard let self else { return }
+
+ let policy = try await self.context.apiService.notificationPolicy(authenticationBox: self.authContext.mastodonAuthenticationBox)
+ self.notificationPolicy = policy.value
+
+ await self.loadLatest()
+ }
}
-
-
}
extension NotificationTimelineViewModel {
+ enum Scope: Hashable {
+ case everything
+ case mentions
+ case fromAccount(Mastodon.Entity.Account)
- typealias Scope = APIService.MastodonNotificationScope
-
+ var title: String {
+ switch self {
+ case .everything:
+ return L10n.Scene.Notification.Title.everything
+ case .mentions:
+ return L10n.Scene.Notification.Title.mentions
+ case .fromAccount(let account):
+ return "Notifications from \(account.displayName)"
+ }
+ }
+ }
}
extension NotificationTimelineViewModel {
@@ -103,6 +138,8 @@ extension NotificationTimelineViewModel {
dataController.loadInitial(kind: .notificationAll)
case .mentions:
dataController.loadInitial(kind: .notificationMentions)
+ case .fromAccount(let account):
+ dataController.loadInitial(kind: .notificationAccount(account.id))
}
didLoadLatest.send()
@@ -115,6 +152,8 @@ extension NotificationTimelineViewModel {
dataController.loadNext(kind: .notificationAll)
case .mentions:
dataController.loadNext(kind: .notificationMentions)
+ case .fromAccount(let account):
+ dataController.loadNext(kind: .notificationAccount(account.id))
}
}
}
diff --git a/Mastodon/Scene/Notification/NotificationView/NotificationView+Configuration.swift b/Mastodon/Scene/Notification/NotificationView/NotificationView+Configuration.swift
index 01ae0ab10f..b296bcd902 100644
--- a/Mastodon/Scene/Notification/NotificationView/NotificationView+Configuration.swift
+++ b/Mastodon/Scene/Notification/NotificationView/NotificationView+Configuration.swift
@@ -39,9 +39,9 @@ extension NotificationView {
switch notification.entity.type {
case .follow:
- setAuthorContainerBottomPaddingViewDisplay()
+ setAuthorContainerBottomPaddingViewDisplay(isHidden: true)
case .followRequest:
- setFollowRequestAdaptiveMarginContainerViewDisplay()
+ setFollowRequestAdaptiveMarginContainerViewDisplay(isHidden: true)
case .mention, .status:
if let status = notification.status {
statusView.configure(status: status)
diff --git a/Mastodon/Scene/Notification/NotificationView/NotificationView.swift b/Mastodon/Scene/Notification/NotificationView/NotificationView.swift
index 341dd6e725..4da3ab81d8 100644
--- a/Mastodon/Scene/Notification/NotificationView/NotificationView.swift
+++ b/Mastodon/Scene/Notification/NotificationView/NotificationView.swift
@@ -210,7 +210,7 @@ extension NotificationView {
containerStackView.topAnchor.constraint(equalTo: topAnchor),
containerStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
containerStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
- bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor),
+ bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor, constant: 8),
])
// author container: H - [ avatarButton | author meta container ]
@@ -327,7 +327,7 @@ extension NotificationView {
rejectFollowRequestActivityIndicatorView.centerYAnchor.constraint(equalTo: rejectFollowRequestButton.centerYAnchor),
])
rejectFollowRequestActivityIndicatorView.color = .black
- acceptFollowRequestActivityIndicatorView.hidesWhenStopped = true
+ rejectFollowRequestActivityIndicatorView.hidesWhenStopped = true
rejectFollowRequestActivityIndicatorView.stopAnimating()
// statusView
@@ -420,12 +420,12 @@ extension NotificationView {
extension NotificationView {
- public func setAuthorContainerBottomPaddingViewDisplay() {
- authorContainerViewBottomPaddingView.isHidden = false
+ public func setAuthorContainerBottomPaddingViewDisplay(isHidden: Bool = false) {
+ authorContainerViewBottomPaddingView.isHidden = isHidden
}
- public func setFollowRequestAdaptiveMarginContainerViewDisplay() {
- followRequestAdaptiveMarginContainerView.isHidden = false
+ public func setFollowRequestAdaptiveMarginContainerViewDisplay(isHidden: Bool = false) {
+ followRequestAdaptiveMarginContainerView.isHidden = isHidden
}
public func setStatusViewDisplay() {
diff --git a/Mastodon/Scene/Notification/NotificationViewController.swift b/Mastodon/Scene/Notification/NotificationViewController.swift
index df0b7d0fa5..5f55e15301 100644
--- a/Mastodon/Scene/Notification/NotificationViewController.swift
+++ b/Mastodon/Scene/Notification/NotificationViewController.swift
@@ -12,6 +12,7 @@ import MastodonLocalization
import Tabman
import Pageboy
import MastodonCore
+import MastodonSDK
final class NotificationViewController: TabmanViewController, NeedsDependency {
@@ -49,7 +50,7 @@ extension NotificationViewController {
view.backgroundColor = .secondarySystemBackground
- setupSegmentedControl(scopes: APIService.MastodonNotificationScope.allCases)
+ setupSegmentedControl(scopes: [.everything, .mentions])
pageSegmentedControl.translatesAutoresizingMaskIntoConstraints = false
navigationItem.titleView = pageSegmentedControl
NSLayoutConstraint.activate([
@@ -68,7 +69,7 @@ extension NotificationViewController {
}
.store(in: &disposeBag)
- viewModel?.viewControllers = APIService.MastodonNotificationScope.allCases.map { scope in
+ viewModel?.viewControllers = [NotificationTimelineViewModel.Scope.everything, .mentions].map { scope in
createViewController(for: scope)
}
@@ -86,14 +87,11 @@ extension NotificationViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
-// aspectViewWillAppear(animated)
-
- // fetch latest notification when scroll position is within half screen height to prevent list reload
-// if tableView.contentOffset.y < view.frame.height * 0.5 {
-// viewModel.loadLatestStateMachine.enter(NotificationViewModel.LoadLatestState.Loading.self)
-// }
+ // https://github.com/mastodon/documentation/pull/1447#issuecomment-2149225659
+ if let viewModel, viewModel.notificationPolicy != nil {
+ navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "line.3.horizontal.decrease.circle"), style: .plain, target: self, action: #selector(NotificationViewController.showNotificationPolicySettings(_:)))
+ }
-
// needs trigger manually after onboarding dismiss
setNeedsStatusBarAppearanceUpdate()
}
@@ -117,6 +115,24 @@ extension NotificationViewController {
// aspectViewDidDisappear(animated)
}
+
+ //MARK: - Actions
+
+ @objc private func showNotificationPolicySettings(_ sender: Any) {
+ guard let viewModel, let policy = viewModel.notificationPolicy else { return }
+
+ let policyViewModel = NotificationFilterViewModel(
+ appContext: viewModel.context,
+ notFollowing: policy.filterNotFollowing,
+ noFollower: policy.filterNotFollowers,
+ newAccount: policy.filterNewAccounts,
+ privateMentions: policy.filterPrivateMentions
+ )
+
+ guard let policyViewController = coordinator.present(scene: .notificationPolicy(viewModel: policyViewModel), transition: .formSheet) as? NotificationPolicyViewController else { return }
+
+ policyViewController.delegate = self
+ }
}
extension NotificationViewController {
@@ -134,17 +150,20 @@ extension NotificationViewController {
pageSegmentedControl.selectedSegmentIndex = 0
}
}
-
+
private func createViewController(for scope: NotificationTimelineViewModel.Scope) -> UIViewController {
- guard let authContext = viewModel?.authContext else { return UITableViewController() }
- let viewController = NotificationTimelineViewController()
- viewController.context = context
- viewController.coordinator = coordinator
- viewController.viewModel = NotificationTimelineViewModel(
+ guard let viewModel else { return UITableViewController() }
+
+ let viewController = NotificationTimelineViewController(
+ viewModel: NotificationTimelineViewModel(
+ context: context,
+ authContext: viewModel.authContext,
+ scope: scope, notificationPolicy: viewModel.notificationPolicy
+ ),
context: context,
- authContext: authContext,
- scope: scope
+ coordinator: coordinator
)
+
return viewController
}
}
@@ -234,3 +253,12 @@ extension NotificationViewController {
return categorySwitchKeyCommands
}
}
+
+
+//MARK: - NotificationPolicyViewControllerDelegate
+
+extension NotificationViewController: NotificationPolicyViewControllerDelegate {
+ func policyUpdated(_ viewController: NotificationPolicyViewController, newPolicy: Mastodon.Entity.NotificationPolicy) {
+ viewModel?.notificationPolicy = newPolicy
+ }
+}
diff --git a/Mastodon/Scene/Notification/NotificationViewModel.swift b/Mastodon/Scene/Notification/NotificationViewModel.swift
index b3a3316fd4..8b68367c19 100644
--- a/Mastodon/Scene/Notification/NotificationViewModel.swift
+++ b/Mastodon/Scene/Notification/NotificationViewModel.swift
@@ -11,6 +11,7 @@ import Pageboy
import MastodonAsset
import MastodonCore
import MastodonLocalization
+import MastodonSDK
final class NotificationViewModel {
@@ -19,6 +20,7 @@ final class NotificationViewModel {
// input
let context: AppContext
let authContext: AuthContext
+ var notificationPolicy: Mastodon.Entity.NotificationPolicy?
let viewDidLoad = PassthroughSubject()
// output
@@ -50,17 +52,15 @@ final class NotificationViewModel {
init(context: AppContext, authContext: AuthContext) {
self.context = context
self.authContext = authContext
+
// end init
- }
-}
-
-extension NotificationTimelineViewModel.Scope {
- var title: String {
- switch self {
- case .everything:
- return L10n.Scene.Notification.Title.everything
- case .mentions:
- return L10n.Scene.Notification.Title.mentions
+ Task {
+ do {
+ let policy = try await context.apiService.notificationPolicy(authenticationBox: authContext.mastodonAuthenticationBox)
+ self.notificationPolicy = policy.value
+ } catch {
+ // we won't show the filtering-options.
+ }
}
}
}
diff --git a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController+DataSourceProvider.swift b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController+DataSourceProvider.swift
index ff1eab32ba..4891b175b9 100644
--- a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController+DataSourceProvider.swift
+++ b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController+DataSourceProvider.swift
@@ -70,7 +70,7 @@ extension SearchResultViewController {
provider: self,
tag: tag
)
- case .notification:
+ case .notification, .notificationBanner(_):
assertionFailure()
} // end switch
diff --git a/Mastodon/Scene/Settings/General Settings/GeneralSettingToggleTableViewCell.swift b/Mastodon/Scene/Settings/General Settings/GeneralSettingToggleTableViewCell.swift
index 5775fdcf5f..ab74574215 100644
--- a/Mastodon/Scene/Settings/General Settings/GeneralSettingToggleTableViewCell.swift
+++ b/Mastodon/Scene/Settings/General Settings/GeneralSettingToggleTableViewCell.swift
@@ -17,6 +17,7 @@ class GeneralSettingToggleTableViewCell: ToggleTableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
+ subtitleLabel.isHidden = true
toggle.addTarget(self, action: #selector(GeneralSettingToggleTableViewCell.toggleValueChanged(_:)), for: .valueChanged)
}
diff --git a/Mastodon/Scene/Settings/Notification Settings/Cells/NotificationSettingTableViewToggleCell.swift b/Mastodon/Scene/Settings/Notification Settings/Cells/NotificationSettingTableViewToggleCell.swift
index 6a8dfb0631..5bb8cff1f2 100644
--- a/Mastodon/Scene/Settings/Notification Settings/Cells/NotificationSettingTableViewToggleCell.swift
+++ b/Mastodon/Scene/Settings/Notification Settings/Cells/NotificationSettingTableViewToggleCell.swift
@@ -18,6 +18,7 @@ class NotificationSettingTableViewToggleCell: ToggleTableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
+ subtitleLabel.isHidden = true
toggle.addTarget(self, action: #selector(NotificationSettingTableViewToggleCell.toggleValueChanged(_:)), for: .valueChanged)
}
diff --git a/Mastodon/Scene/Settings/Shared/ToggleTableViewCell.swift b/Mastodon/Scene/Settings/Shared/ToggleTableViewCell.swift
index 11454d9e63..db47a7f75f 100644
--- a/Mastodon/Scene/Settings/Shared/ToggleTableViewCell.swift
+++ b/Mastodon/Scene/Settings/Shared/ToggleTableViewCell.swift
@@ -9,22 +9,33 @@ class ToggleTableViewCell: UITableViewCell {
}
let label: UILabel
+ let subtitleLabel: UILabel
+ private let labelStackView: UIStackView
let toggle: UISwitch
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
label = UILabel()
- label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular))
label.numberOfLines = 0
-
+
+ subtitleLabel = UILabel()
+ subtitleLabel.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular))
+ subtitleLabel.numberOfLines = 0
+
+ labelStackView = UIStackView(arrangedSubviews: [label, subtitleLabel])
+ labelStackView.translatesAutoresizingMaskIntoConstraints = false
+ labelStackView.alignment = .leading
+ labelStackView.axis = .vertical
+ labelStackView.spacing = 4
+
toggle = UISwitch()
toggle.translatesAutoresizingMaskIntoConstraints = false
toggle.onTintColor = Asset.Colors.Brand.blurple.color
super.init(style: style, reuseIdentifier: reuseIdentifier)
- contentView.addSubview(label)
+ contentView.addSubview(labelStackView)
contentView.addSubview(toggle)
setupConstraints()
}
@@ -33,11 +44,11 @@ class ToggleTableViewCell: UITableViewCell {
private func setupConstraints() {
let constraints = [
- label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 11),
- label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
- contentView.bottomAnchor.constraint(equalTo: label.bottomAnchor, constant: 11),
-
- toggle.leadingAnchor.constraint(greaterThanOrEqualTo: label.trailingAnchor, constant: 16),
+ labelStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 11),
+ labelStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
+ contentView.bottomAnchor.constraint(equalTo: labelStackView.bottomAnchor, constant: 11),
+
+ toggle.leadingAnchor.constraint(greaterThanOrEqualTo: labelStackView.trailingAnchor, constant: 16),
toggle.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
contentView.trailingAnchor.constraint(equalTo: toggle.trailingAnchor, constant: 16)
diff --git a/MastodonSDK/Sources/MastodonCore/DataController/FeedDataController.swift b/MastodonSDK/Sources/MastodonCore/DataController/FeedDataController.swift
index beaa8d56b6..d91a98c28f 100644
--- a/MastodonSDK/Sources/MastodonCore/DataController/FeedDataController.swift
+++ b/MastodonSDK/Sources/MastodonCore/DataController/FeedDataController.swift
@@ -202,13 +202,14 @@ private extension FeedDataController {
return try await getFeeds(with: .everything)
case .notificationMentions:
return try await getFeeds(with: .mentions)
-
+ case .notificationAccount(let accountID):
+ return try await getFeeds(with: nil, accountID: accountID)
}
}
- private func getFeeds(with scope: APIService.MastodonNotificationScope) async throws -> [MastodonFeed] {
+ private func getFeeds(with scope: APIService.MastodonNotificationScope?, accountID: String? = nil) async throws -> [MastodonFeed] {
- let notifications = try await context.apiService.notifications(maxID: nil, scope: scope, authenticationBox: authContext.mastodonAuthenticationBox).value
+ let notifications = try await context.apiService.notifications(maxID: nil, accountID: accountID, scope: scope, authenticationBox: authContext.mastodonAuthenticationBox).value
let accounts = notifications.map { $0.account }
let relationships = try await context.apiService.relationship(forAccounts: accounts, authenticationBox: authContext.mastodonAuthenticationBox).value
diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift
index ff26e69b5c..9af896130d 100644
--- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift
+++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift
@@ -10,70 +10,42 @@ import CoreData
import CoreDataStack
import Foundation
import MastodonSDK
-import OSLog
extension APIService {
public enum MastodonNotificationScope: String, Hashable, CaseIterable {
case everything
case mentions
-
- public var includeTypes: [MastodonNotificationType]? {
- switch self {
- case .everything: return nil
- case .mentions: return [.mention, .status]
- }
- }
-
- public var excludeTypes: [MastodonNotificationType]? {
- switch self {
- case .everything: return nil
- case .mentions: return [.follow, .followRequest, .reblog, .favourite, .poll]
- }
- }
-
- public var _excludeTypes: [Mastodon.Entity.Notification.NotificationType]? {
- switch self {
- case .everything: return nil
- case .mentions: return [.follow, .followRequest, .reblog, .favourite, .poll]
- }
- }
}
-
+
public func notifications(
maxID: Mastodon.Entity.Status.ID?,
- scope: MastodonNotificationScope,
+ accountID: String? = nil,
+ scope: MastodonNotificationScope?,
authenticationBox: MastodonAuthenticationBox
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Notification]> {
let authorization = authenticationBox.userAuthorization
-
+
+ let types: [Mastodon.Entity.Notification.NotificationType]?
+ let excludedTypes: [Mastodon.Entity.Notification.NotificationType]?
+
+ switch scope {
+ case .everything:
+ types = [.follow, .followRequest, .mention, .reblog, .favourite, .poll, .status, .moderationWarning]
+ excludedTypes = nil
+ case .mentions:
+ types = [.mention]
+ excludedTypes = [.follow, .followRequest, .reblog, .favourite, .poll]
+ case nil:
+ types = nil
+ excludedTypes = nil
+ }
+
let query = Mastodon.API.Notifications.Query(
maxID: maxID,
- types: {
- switch scope {
- case .everything:
- return [
- .follow,
- .followRequest,
- .mention,
- .reblog,
- .favourite,
- .poll,
- .status,
- .moderationWarning
- ]
- case .mentions:
- return [.mention]
- }
- }(),
- excludeTypes: {
- switch scope {
- case .everything:
- return nil
- case .mentions:
- return [.follow, .followRequest, .reblog, .favourite, .poll]
- }
- }()
+ types: types,
+ excludeTypes: excludedTypes,
+ accountID: accountID
)
let response = try await Mastodon.API.Notifications.getNotifications(
@@ -107,3 +79,70 @@ extension APIService {
}
}
+
+//MARK: - Notification Policy
+
+extension APIService {
+ public func notificationPolicy(authenticationBox: MastodonAuthenticationBox) async throws -> Mastodon.Response.Content {
+ let domain = authenticationBox.domain
+ let authorization = authenticationBox.userAuthorization
+
+ let response = try await Mastodon.API.Notifications.getNotificationPolicy(session: session, domain: domain, authorization: authorization)
+
+ return response
+ }
+
+ public func updateNotificationPolicy(
+ authenticationBox: MastodonAuthenticationBox,
+ filterNotFollowing: Bool,
+ filterNotFollowers: Bool,
+ filterNewAccounts: Bool,
+ filterPrivateMentions: Bool
+ ) async throws -> Mastodon.Response.Content {
+ let domain = authenticationBox.domain
+ let authorization = authenticationBox.userAuthorization
+ let query = Mastodon.API.Notifications.UpdateNotificationPolicyQuery(filterNotFollowing: filterNotFollowing, filterNotFollowers: filterNotFollowers, filterNewAccounts: filterNewAccounts, filterPrivateMentions: filterPrivateMentions)
+
+ let response = try await Mastodon.API.Notifications.updateNotificationPolicy(
+ session: session,
+ domain: domain,
+ authorization: authorization,
+ query: query
+ )
+
+ return response
+ }
+}
+
+//MARK: - Notification Requests
+
+extension APIService {
+ public func notificationRequests(authenticationBox: MastodonAuthenticationBox) async throws -> Mastodon.Response.Content<[Mastodon.Entity.NotificationRequest]> {
+ let domain = authenticationBox.domain
+ let authorization = authenticationBox.userAuthorization
+
+ let response = try await Mastodon.API.Notifications.getNotificationRequests(session: session, domain: domain, authorization: authorization)
+
+ return response
+ }
+
+ public func acceptNotificationRequests(authenticationBox: MastodonAuthenticationBox, id: String) async throws -> Mastodon.Response.Content<[String: String]> {
+ let domain = authenticationBox.domain
+ let authorization = authenticationBox.userAuthorization
+
+ let response = try await Mastodon.API.Notifications.acceptNotificationRequest(id: id, session: session, domain: domain, authorization: authorization)
+ return response
+ }
+
+ public func rejectNotificationRequests(authenticationBox: MastodonAuthenticationBox, id: String) async throws -> Mastodon.Response.Content<[String: String]> {
+ let domain = authenticationBox.domain
+ let authorization = authenticationBox.userAuthorization
+
+ let response = try await Mastodon.API.Notifications.dismissNotificationRequest(id: id, session: session, domain: domain, authorization: authorization)
+ return response
+ }
+}
+
+extension Notification.Name {
+ public static let notificationFilteringChanged = Notification.Name(rawValue: "org.joinmastodon.app.notificationFilteringsChanged")
+}
diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift
index 3a8787b50b..ef5edbfdb2 100644
--- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift
+++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift
@@ -895,6 +895,14 @@ public enum L10n {
}
}
public enum Notification {
+ public enum FilteredNotification {
+ /// Accept
+ public static let accept = L10n.tr("Localizable", "Scene.Notification.FilteredNotification.Accept", fallback: "Accept")
+ /// Dismiss
+ public static let dismiss = L10n.tr("Localizable", "Scene.Notification.FilteredNotification.Dismiss", fallback: "Dismiss")
+ /// Filtered Notifications
+ public static let title = L10n.tr("Localizable", "Scene.Notification.FilteredNotification.Title", fallback: "Filtered Notifications")
+ }
public enum FollowRequest {
/// Accept
public static let accept = L10n.tr("Localizable", "Scene.Notification.FollowRequest.Accept", fallback: "Accept")
@@ -925,6 +933,34 @@ public enum L10n {
/// request to follow you
public static let requestToFollowYou = L10n.tr("Localizable", "Scene.Notification.NotificationDescription.RequestToFollowYou", fallback: "request to follow you")
}
+ public enum Policy {
+ /// Filter Notifications from…
+ public static let title = L10n.tr("Localizable", "Scene.Notification.Policy.Title", fallback: "Filter Notifications from…")
+ public enum NewAccount {
+ /// Created within the past 30 days
+ public static let subtitle = L10n.tr("Localizable", "Scene.Notification.Policy.NewAccount.Subtitle", fallback: "Created within the past 30 days")
+ /// New accounts
+ public static let title = L10n.tr("Localizable", "Scene.Notification.Policy.NewAccount.Title", fallback: "New accounts")
+ }
+ public enum NoFollower {
+ /// Including people who have been following you fewer than 3 days
+ public static let subtitle = L10n.tr("Localizable", "Scene.Notification.Policy.NoFollower.Subtitle", fallback: "Including people who have been following you fewer than 3 days")
+ /// People not following you
+ public static let title = L10n.tr("Localizable", "Scene.Notification.Policy.NoFollower.Title", fallback: "People not following you")
+ }
+ public enum NotFollowing {
+ /// Until you manually approve them
+ public static let subtitle = L10n.tr("Localizable", "Scene.Notification.Policy.NotFollowing.Subtitle", fallback: "Until you manually approve them")
+ /// People you don't follow
+ public static let title = L10n.tr("Localizable", "Scene.Notification.Policy.NotFollowing.Title", fallback: "People you don't follow")
+ }
+ public enum PrivateMentions {
+ /// Filtered unless it’s in reply to your own mention or if you follow the sender
+ public static let subtitle = L10n.tr("Localizable", "Scene.Notification.Policy.PrivateMentions.Subtitle", fallback: "Filtered unless it’s in reply to your own mention or if you follow the sender")
+ /// Unsolicited private mentions
+ public static let title = L10n.tr("Localizable", "Scene.Notification.Policy.PrivateMentions.Title", fallback: "Unsolicited private mentions")
+ }
+ }
public enum Title {
/// Everything
public static let everything = L10n.tr("Localizable", "Scene.Notification.Title.Everything", fallback: "Everything")
@@ -1950,6 +1986,12 @@ public enum L10n {
}
}
}
+ public enum FilteredNotificationBanner {
+ /// Plural format key: "%#@number_of_requests@"
+ public static func subtitle(_ p1: Int) -> String {
+ return L10n.tr("Localizable", "plural.filtered_notification_banner.subtitle", p1, fallback: "Plural format key: \"%#@number_of_requests@\"")
+ }
+ }
}
}
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings
index 5a6db9ba34..0e559e9395 100644
--- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings
+++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings
@@ -304,11 +304,11 @@ uploaded to Mastodon.";
"Scene.Following.Footer" = "Follows from other servers are not displayed.";
"Scene.Following.Title" = "following";
"Scene.HomeTimeline.TimelineMenu.Following" = "Following";
-"Scene.HomeTimeline.TimelineMenu.LocalCommunity" = "Local";
-"Scene.HomeTimeline.TimelineMenu.Lists.Title" = "Lists";
-"Scene.HomeTimeline.TimelineMenu.Lists.EmptyMessage" = "You don't have any Lists";
-"Scene.HomeTimeline.TimelineMenu.Hashtags.Title" = "Followed Hashtags";
"Scene.HomeTimeline.TimelineMenu.Hashtags.EmptyMessage" = "You don't follow any Hashtags";
+"Scene.HomeTimeline.TimelineMenu.Hashtags.Title" = "Followed Hashtags";
+"Scene.HomeTimeline.TimelineMenu.Lists.EmptyMessage" = "You don't have any Lists";
+"Scene.HomeTimeline.TimelineMenu.Lists.Title" = "Lists";
+"Scene.HomeTimeline.TimelineMenu.LocalCommunity" = "Local";
"Scene.HomeTimeline.TimelinePill.NewPosts" = "New Posts";
"Scene.HomeTimeline.TimelinePill.Offline" = "Offline";
"Scene.HomeTimeline.TimelinePill.PostSent" = "Post Sent";
@@ -316,6 +316,9 @@ uploaded to Mastodon.";
"Scene.Login.ServerSearchField.Placeholder" = "Enter URL or search for your server";
"Scene.Login.Subtitle" = "Log in with the server where you created your account.";
"Scene.Login.Title" = "Welcome Back";
+"Scene.Notification.FilteredNotification.Accept" = "Accept";
+"Scene.Notification.FilteredNotification.Dismiss" = "Dismiss";
+"Scene.Notification.FilteredNotification.Title" = "Filtered Notifications";
"Scene.Notification.FollowRequest.Accept" = "Accept";
"Scene.Notification.FollowRequest.Accepted" = "Accepted";
"Scene.Notification.FollowRequest.Reject" = "reject";
@@ -328,6 +331,15 @@ uploaded to Mastodon.";
"Scene.Notification.NotificationDescription.PollHasEnded" = "poll has ended";
"Scene.Notification.NotificationDescription.RebloggedYourPost" = "boosted your post";
"Scene.Notification.NotificationDescription.RequestToFollowYou" = "request to follow you";
+"Scene.Notification.Policy.NewAccount.Subtitle" = "Created within the past 30 days";
+"Scene.Notification.Policy.NewAccount.Title" = "New accounts";
+"Scene.Notification.Policy.NoFollower.Subtitle" = "Including people who have been following you fewer than 3 days";
+"Scene.Notification.Policy.NoFollower.Title" = "People not following you";
+"Scene.Notification.Policy.NotFollowing.Subtitle" = "Until you manually approve them";
+"Scene.Notification.Policy.NotFollowing.Title" = "People you don't follow";
+"Scene.Notification.Policy.PrivateMentions.Subtitle" = "Filtered unless it’s in reply to your own mention or if you follow the sender";
+"Scene.Notification.Policy.PrivateMentions.Title" = "Unsolicited private mentions";
+"Scene.Notification.Policy.Title" = "Filter Notifications from…";
"Scene.Notification.Title.Everything" = "Everything";
"Scene.Notification.Title.Mentions" = "Mentions";
"Scene.Notification.Warning.DeleteStatuses" = "Some of your posts have been removed.";
@@ -621,4 +633,4 @@ If you disagree with the policy for **%@**, you can go back and pick a different
"Widget.MultipleFollowers.ConfigurationDescription" = "Show number of followers for multiple accounts.";
"Widget.MultipleFollowers.ConfigurationDisplayName" = "Multiple followers";
"Widget.MultipleFollowers.MockUser.AccountName" = "another@follower.social";
-"Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower";
+"Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower";
\ No newline at end of file
diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.stringsdict
index a56e4b1e64..1038e2ea85 100644
--- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.stringsdict
+++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.stringsdict
@@ -2,72 +2,72 @@
- a11y.plural.count.unread.notification
-
- NSStringLocalizedFormatKey
- %#@notification_count_unread_notification@
- notification_count_unread_notification
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- no unread notifications
- one
- 1 unread notification
- few
- %ld unread notifications
- many
- %ld unread notifications
- other
- %ld unread notifications
-
-
- a11y.plural.count.input_limit_exceeds
-
- NSStringLocalizedFormatKey
- Input limit exceeds %#@character_count@
- character_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 characters
- one
- 1 character
- few
- %ld characters
- many
- %ld characters
- other
- %ld characters
-
-
- a11y.plural.count.input_limit_remains
-
- NSStringLocalizedFormatKey
- Input limit remains %#@character_count@
- character_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 characters
- one
- 1 character
- few
- %ld characters
- many
- %ld characters
- other
- %ld characters
-
-
+ a11y.plural.count.unread.notification
+
+ NSStringLocalizedFormatKey
+ %#@notification_count_unread_notification@
+ notification_count_unread_notification
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ no unread notifications
+ one
+ 1 unread notification
+ few
+ %ld unread notifications
+ many
+ %ld unread notifications
+ other
+ %ld unread notifications
+
+
+ a11y.plural.count.input_limit_exceeds
+
+ NSStringLocalizedFormatKey
+ Input limit exceeds %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 characters
+ one
+ 1 character
+ few
+ %ld characters
+ many
+ %ld characters
+ other
+ %ld characters
+
+
+ a11y.plural.count.input_limit_remains
+
+ NSStringLocalizedFormatKey
+ Input limit remains %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 characters
+ one
+ 1 character
+ few
+ %ld characters
+ many
+ %ld characters
+ other
+ %ld characters
+
+
a11y.plural.count.characters_left
NSStringLocalizedFormatKey
@@ -90,125 +90,125 @@
%ld characters left
- plural.count.followed_by_and_mutual
-
- NSStringLocalizedFormatKey
- %#@names@%#@count_mutual@
- names
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- other
-
-
- count_mutual
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- Followed by %1$@
- one
- Followed by %1$@, and another mutual
- few
- Followed by %1$@, and %ld mutuals
- many
- Followed by %1$@, and %ld mutuals
- other
- Followed by %1$@, and %ld mutuals
-
-
- plural.count.metric_formatted.post
-
- NSStringLocalizedFormatKey
- %@ %#@post_count@
- post_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- posts
- one
- post
- few
- posts
- many
- posts
- other
- posts
-
-
- plural.count.media
-
- NSStringLocalizedFormatKey
- %#@media_count@
- media_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 media
- one
- 1 media
- few
- %ld media
- many
- %ld media
- other
- %ld media
-
-
- plural.count.post
-
- NSStringLocalizedFormatKey
- %#@post_count@
- post_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 posts
- one
- 1 post
- few
- %ld posts
- many
- %ld posts
- other
- %ld posts
-
-
+ plural.count.followed_by_and_mutual
+
+ NSStringLocalizedFormatKey
+ %#@names@%#@count_mutual@
+ names
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+
+
+ count_mutual
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ Followed by %1$@
+ one
+ Followed by %1$@, and another mutual
+ few
+ Followed by %1$@, and %ld mutuals
+ many
+ Followed by %1$@, and %ld mutuals
+ other
+ Followed by %1$@, and %ld mutuals
+
+
+ plural.count.metric_formatted.post
+
+ NSStringLocalizedFormatKey
+ %@ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ posts
+ one
+ post
+ few
+ posts
+ many
+ posts
+ other
+ posts
+
+
+ plural.count.media
+
+ NSStringLocalizedFormatKey
+ %#@media_count@
+ media_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 media
+ one
+ 1 media
+ few
+ %ld media
+ many
+ %ld media
+ other
+ %ld media
+
+
+ plural.count.post
+
+ NSStringLocalizedFormatKey
+ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 posts
+ one
+ 1 post
+ few
+ %ld posts
+ many
+ %ld posts
+ other
+ %ld posts
+
+
plural.count.favorite
-
- NSStringLocalizedFormatKey
- %#@favorite_count@
- favorite_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 favorites
- one
- 1 favorite
- few
- %ld favorites
- many
- %ld favorites
- other
- %ld favorites
-
-
+
+ NSStringLocalizedFormatKey
+ %#@favorite_count@
+ favorite_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 favorites
+ one
+ 1 favorite
+ few
+ %ld favorites
+ many
+ %ld favorites
+ other
+ %ld favorites
+
+
plural.count.reblog
NSStringLocalizedFormatKey
@@ -231,29 +231,29 @@
%ld reblogs
- plural.count.reblog_a11y
-
- NSStringLocalizedFormatKey
- %#@reblog_count@
- reblog_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 re-blogs
- one
- 1 re-blog
- few
- %ld re-blogs
- many
- %ld re-blogs
- other
- %ld re-blogs
-
-
- plural.count.reply
+ plural.count.reblog_a11y
+
+ NSStringLocalizedFormatKey
+ %#@reblog_count@
+ reblog_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 re-blogs
+ one
+ 1 re-blog
+ few
+ %ld re-blogs
+ many
+ %ld re-blogs
+ other
+ %ld re-blogs
+
+
+ plural.count.reply
NSStringLocalizedFormatKey
%#@reply_count@
@@ -275,379 +275,395 @@
%ld replies
- plural.count.vote
-
- NSStringLocalizedFormatKey
- %#@vote_count@
- vote_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 votes
- one
- 1 vote
- few
- %ld votes
- many
- %ld votes
- other
- %ld votes
-
-
- plural.count.voter
-
- NSStringLocalizedFormatKey
- %#@voter_count@
- voter_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 voters
- one
- 1 voter
- few
- %ld voters
- many
- %ld voters
- other
- %ld voters
-
-
- plural.people_talking
-
- NSStringLocalizedFormatKey
- %#@count_people_talking@
- count_people_talking
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 people talking
- one
- 1 people talking
- few
- %ld people talking
- many
- %ld people talking
- other
- %ld people talking
-
-
- plural.count.following
-
- NSStringLocalizedFormatKey
- %#@count_following@
- count_following
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 following
- one
- 1 following
- few
- %ld following
- many
- %ld following
- other
- %ld following
-
-
- plural.count.follower
-
- NSStringLocalizedFormatKey
- %#@count_follower@
- count_follower
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 followers
- one
- 1 follower
- few
- %ld followers
- many
- %ld followers
- other
- %ld followers
-
-
- date.year.left
-
- NSStringLocalizedFormatKey
- %#@count_year_left@
- count_year_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 years left
- one
- 1 year left
- few
- %ld years left
- many
- %ld years left
- other
- %ld years left
-
-
- date.month.left
-
- NSStringLocalizedFormatKey
- %#@count_month_left@
- count_month_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 months left
- one
- 1 months left
- few
- %ld months left
- many
- %ld months left
- other
- %ld months left
-
-
- date.day.left
-
- NSStringLocalizedFormatKey
- %#@count_day_left@
- count_day_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 days left
- one
- 1 day left
- few
- %ld days left
- many
- %ld days left
- other
- %ld days left
-
-
- date.hour.left
-
- NSStringLocalizedFormatKey
- %#@count_hour_left@
- count_hour_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 hours left
- one
- 1 hour left
- few
- %ld hours left
- many
- %ld hours left
- other
- %ld hours left
-
-
- date.minute.left
-
- NSStringLocalizedFormatKey
- %#@count_minute_left@
- count_minute_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 minutes left
- one
- 1 minute left
- few
- %ld minutes left
- many
- %ld minutes left
- other
- %ld minutes left
-
-
- date.second.left
-
- NSStringLocalizedFormatKey
- %#@count_second_left@
- count_second_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0 seconds left
- one
- 1 second left
- few
- %ld seconds left
- many
- %ld seconds left
- other
- %ld seconds left
-
-
- date.year.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_year_ago_abbr@
- count_year_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0y ago
- one
- 1y ago
- few
- %ldy ago
- many
- %ldy ago
- other
- %ldy ago
-
-
- date.month.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_month_ago_abbr@
- count_month_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0M ago
- one
- 1M ago
- few
- %ldM ago
- many
- %ldM ago
- other
- %ldM ago
-
-
- date.day.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_day_ago_abbr@
- count_day_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0d ago
- one
- 1d ago
- few
- %ldd ago
- many
- %ldd ago
- other
- %ldd ago
-
-
- date.hour.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_hour_ago_abbr@
- count_hour_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0h ago
- one
- 1h ago
- few
- %ldh ago
- many
- %ldh ago
- other
- %ldh ago
-
-
- date.minute.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_minute_ago_abbr@
- count_minute_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0m ago
- one
- 1m ago
- few
- %ldm ago
- many
- %ldm ago
- other
- %ldm ago
-
-
- date.second.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_second_ago_abbr@
- count_second_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- zero
- 0s ago
- one
- 1s ago
- few
- %lds ago
- many
- %lds ago
- other
- %lds ago
-
-
+ plural.count.vote
+
+ NSStringLocalizedFormatKey
+ %#@vote_count@
+ vote_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 votes
+ one
+ 1 vote
+ few
+ %ld votes
+ many
+ %ld votes
+ other
+ %ld votes
+
+
+ plural.count.voter
+
+ NSStringLocalizedFormatKey
+ %#@voter_count@
+ voter_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 voters
+ one
+ 1 voter
+ few
+ %ld voters
+ many
+ %ld voters
+ other
+ %ld voters
+
+
+ plural.people_talking
+
+ NSStringLocalizedFormatKey
+ %#@count_people_talking@
+ count_people_talking
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 people talking
+ one
+ 1 people talking
+ few
+ %ld people talking
+ many
+ %ld people talking
+ other
+ %ld people talking
+
+
+ plural.count.following
+
+ NSStringLocalizedFormatKey
+ %#@count_following@
+ count_following
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 following
+ one
+ 1 following
+ few
+ %ld following
+ many
+ %ld following
+ other
+ %ld following
+
+
+ plural.count.follower
+
+ NSStringLocalizedFormatKey
+ %#@count_follower@
+ count_follower
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 followers
+ one
+ 1 follower
+ few
+ %ld followers
+ many
+ %ld followers
+ other
+ %ld followers
+
+
+ date.year.left
+
+ NSStringLocalizedFormatKey
+ %#@count_year_left@
+ count_year_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 years left
+ one
+ 1 year left
+ few
+ %ld years left
+ many
+ %ld years left
+ other
+ %ld years left
+
+
+ date.month.left
+
+ NSStringLocalizedFormatKey
+ %#@count_month_left@
+ count_month_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 months left
+ one
+ 1 months left
+ few
+ %ld months left
+ many
+ %ld months left
+ other
+ %ld months left
+
+
+ date.day.left
+
+ NSStringLocalizedFormatKey
+ %#@count_day_left@
+ count_day_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 days left
+ one
+ 1 day left
+ few
+ %ld days left
+ many
+ %ld days left
+ other
+ %ld days left
+
+
+ date.hour.left
+
+ NSStringLocalizedFormatKey
+ %#@count_hour_left@
+ count_hour_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 hours left
+ one
+ 1 hour left
+ few
+ %ld hours left
+ many
+ %ld hours left
+ other
+ %ld hours left
+
+
+ date.minute.left
+
+ NSStringLocalizedFormatKey
+ %#@count_minute_left@
+ count_minute_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 minutes left
+ one
+ 1 minute left
+ few
+ %ld minutes left
+ many
+ %ld minutes left
+ other
+ %ld minutes left
+
+
+ date.second.left
+
+ NSStringLocalizedFormatKey
+ %#@count_second_left@
+ count_second_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 seconds left
+ one
+ 1 second left
+ few
+ %ld seconds left
+ many
+ %ld seconds left
+ other
+ %ld seconds left
+
+
+ date.year.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_year_ago_abbr@
+ count_year_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0y ago
+ one
+ 1y ago
+ few
+ %ldy ago
+ many
+ %ldy ago
+ other
+ %ldy ago
+
+
+ date.month.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_month_ago_abbr@
+ count_month_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0M ago
+ one
+ 1M ago
+ few
+ %ldM ago
+ many
+ %ldM ago
+ other
+ %ldM ago
+
+
+ date.day.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_day_ago_abbr@
+ count_day_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0d ago
+ one
+ 1d ago
+ few
+ %ldd ago
+ many
+ %ldd ago
+ other
+ %ldd ago
+
+
+ date.hour.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_hour_ago_abbr@
+ count_hour_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0h ago
+ one
+ 1h ago
+ few
+ %ldh ago
+ many
+ %ldh ago
+ other
+ %ldh ago
+
+
+ date.minute.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_minute_ago_abbr@
+ count_minute_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0m ago
+ one
+ 1m ago
+ few
+ %ldm ago
+ many
+ %ldm ago
+ other
+ %ldm ago
+
+
+ date.second.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_second_ago_abbr@
+ count_second_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0s ago
+ one
+ 1s ago
+ few
+ %lds ago
+ many
+ %lds ago
+ other
+ %lds ago
+
+
+ plural.filtered_notification_banner.subtitle
+
+ NSStringLocalizedFormatKey
+ %#@number_of_requests@
+ number_of_requests
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ One person you may know
+ other
+ %ld people you may know
+
+
diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.stringsdict
index 2b09ee0040..c97236f967 100644
--- a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.stringsdict
+++ b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.stringsdict
@@ -1,481 +1,497 @@
-
+
-
- a11y.plural.count.unread.notification
-
- NSStringLocalizedFormatKey
- %#@notification_count_unread_notification@
- notification_count_unread_notification
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1 unread notification
- other
- %ld unread notifications
-
-
- a11y.plural.count.input_limit_exceeds
-
- NSStringLocalizedFormatKey
- Input limit exceeds %#@character_count@
- character_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1 character
- other
- %ld characters
-
-
- a11y.plural.count.input_limit_remains
-
- NSStringLocalizedFormatKey
- Input limit remains %#@character_count@
- character_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1 character
- other
- %ld characters
-
-
- a11y.plural.count.characters_left
-
- NSStringLocalizedFormatKey
- %#@character_count@
- character_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1 character left
- other
- %ld characters left
-
-
- plural.count.followed_by_and_mutual
-
- NSStringLocalizedFormatKey
- %#@names@%#@count_mutual@
- names
-
- one
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- other
-
-
- count_mutual
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- Followed by %1$@, and another mutual
- other
- Followed by %1$@, and %ld mutuals
-
-
- plural.count.metric_formatted.post
-
- NSStringLocalizedFormatKey
- %@ %#@post_count@
- post_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- post
- other
- posts
-
-
- plural.count.media
-
- NSStringLocalizedFormatKey
- %#@media_count@
- media_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1 media
- other
- %ld media
-
-
- plural.count.post
-
- NSStringLocalizedFormatKey
- %#@post_count@
- post_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1 post
- other
- %ld posts
-
-
- plural.count.favorite
-
- NSStringLocalizedFormatKey
- %#@favorite_count@
- favorite_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1 favorite
- other
- %ld favorites
-
-
- plural.count.reblog
-
- NSStringLocalizedFormatKey
- %#@reblog_count@
- reblog_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1 reblog
- other
- %ld reblogs
-
-
- plural.count.reblog_a11y
-
- NSStringLocalizedFormatKey
- %#@reblog_count@
- reblog_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1 re-blog
- other
- %ld re-blogs
-
-
- plural.count.reply
-
- NSStringLocalizedFormatKey
- %#@reply_count@
- reply_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1 reply
- other
- %ld replies
-
-
- plural.count.vote
-
- NSStringLocalizedFormatKey
- %#@vote_count@
- vote_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1 vote
- other
- %ld votes
-
-
- plural.count.voter
-
- NSStringLocalizedFormatKey
- %#@voter_count@
- voter_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1 voter
- other
- %ld voters
-
-
- plural.people_talking
-
- NSStringLocalizedFormatKey
- %#@count_people_talking@
- count_people_talking
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1 people talking
- other
- %ld people talking
-
-
- plural.count.following
-
- NSStringLocalizedFormatKey
- %#@count_following@
- count_following
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1 following
- other
- %ld following
-
-
- plural.count.follower
-
- NSStringLocalizedFormatKey
- %#@count_follower@
- count_follower
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1 follower
- other
- %ld followers
-
-
- date.year.left
-
- NSStringLocalizedFormatKey
- %#@count_year_left@
- count_year_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1 year left
- other
- %ld years left
-
-
- date.month.left
-
- NSStringLocalizedFormatKey
- %#@count_month_left@
- count_month_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1 months left
- other
- %ld months left
-
-
- date.day.left
-
- NSStringLocalizedFormatKey
- %#@count_day_left@
- count_day_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1 day left
- other
- %ld days left
-
-
- date.hour.left
-
- NSStringLocalizedFormatKey
- %#@count_hour_left@
- count_hour_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1 hour left
- other
- %ld hours left
-
-
- date.minute.left
-
- NSStringLocalizedFormatKey
- %#@count_minute_left@
- count_minute_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1 minute left
- other
- %ld minutes left
-
-
- date.second.left
-
- NSStringLocalizedFormatKey
- %#@count_second_left@
- count_second_left
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1 second left
- other
- %ld seconds left
-
-
- date.year.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_year_ago_abbr@
- count_year_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1y ago
- other
- %ldy ago
-
-
- date.month.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_month_ago_abbr@
- count_month_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1M ago
- other
- %ldM ago
-
-
- date.day.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_day_ago_abbr@
- count_day_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1d ago
- other
- %ldd ago
-
-
- date.hour.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_hour_ago_abbr@
- count_hour_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1h ago
- other
- %ldh ago
-
-
- date.minute.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_minute_ago_abbr@
- count_minute_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1m ago
- other
- %ldm ago
-
-
- date.second.ago.abbr
-
- NSStringLocalizedFormatKey
- %#@count_second_ago_abbr@
- count_second_ago_abbr
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- ld
- one
- 1s ago
- other
- %lds ago
-
-
-
+
+ a11y.plural.count.unread.notification
+
+ NSStringLocalizedFormatKey
+ %#@notification_count_unread_notification@
+ notification_count_unread_notification
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 unread notification
+ other
+ %ld unread notifications
+
+
+ a11y.plural.count.input_limit_exceeds
+
+ NSStringLocalizedFormatKey
+ Input limit exceeds %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character
+ other
+ %ld characters
+
+
+ a11y.plural.count.input_limit_remains
+
+ NSStringLocalizedFormatKey
+ Input limit remains %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character
+ other
+ %ld characters
+
+
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character left
+ other
+ %ld characters left
+
+
+ plural.count.followed_by_and_mutual
+
+ NSStringLocalizedFormatKey
+ %#@names@%#@count_mutual@
+ names
+
+ one
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+
+
+ count_mutual
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ Followed by %1$@, and another mutual
+ other
+ Followed by %1$@, and %ld mutuals
+
+
+ plural.count.metric_formatted.post
+
+ NSStringLocalizedFormatKey
+ %@ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ post
+ other
+ posts
+
+
+ plural.count.media
+
+ NSStringLocalizedFormatKey
+ %#@media_count@
+ media_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 media
+ other
+ %ld media
+
+
+ plural.count.post
+
+ NSStringLocalizedFormatKey
+ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 post
+ other
+ %ld posts
+
+
+ plural.count.favorite
+
+ NSStringLocalizedFormatKey
+ %#@favorite_count@
+ favorite_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 favorite
+ other
+ %ld favorites
+
+
+ plural.count.reblog
+
+ NSStringLocalizedFormatKey
+ %#@reblog_count@
+ reblog_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 reblog
+ other
+ %ld reblogs
+
+
+ plural.count.reblog_a11y
+
+ NSStringLocalizedFormatKey
+ %#@reblog_count@
+ reblog_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 re-blog
+ other
+ %ld re-blogs
+
+
+ plural.count.reply
+
+ NSStringLocalizedFormatKey
+ %#@reply_count@
+ reply_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 reply
+ other
+ %ld replies
+
+
+ plural.count.vote
+
+ NSStringLocalizedFormatKey
+ %#@vote_count@
+ vote_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 vote
+ other
+ %ld votes
+
+
+ plural.count.voter
+
+ NSStringLocalizedFormatKey
+ %#@voter_count@
+ voter_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 voter
+ other
+ %ld voters
+
+
+ plural.people_talking
+
+ NSStringLocalizedFormatKey
+ %#@count_people_talking@
+ count_people_talking
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 people talking
+ other
+ %ld people talking
+
+
+ plural.count.following
+
+ NSStringLocalizedFormatKey
+ %#@count_following@
+ count_following
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 following
+ other
+ %ld following
+
+
+ plural.count.follower
+
+ NSStringLocalizedFormatKey
+ %#@count_follower@
+ count_follower
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 follower
+ other
+ %ld followers
+
+
+ date.year.left
+
+ NSStringLocalizedFormatKey
+ %#@count_year_left@
+ count_year_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 year left
+ other
+ %ld years left
+
+
+ date.month.left
+
+ NSStringLocalizedFormatKey
+ %#@count_month_left@
+ count_month_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 months left
+ other
+ %ld months left
+
+
+ date.day.left
+
+ NSStringLocalizedFormatKey
+ %#@count_day_left@
+ count_day_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 day left
+ other
+ %ld days left
+
+
+ date.hour.left
+
+ NSStringLocalizedFormatKey
+ %#@count_hour_left@
+ count_hour_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 hour left
+ other
+ %ld hours left
+
+
+ date.minute.left
+
+ NSStringLocalizedFormatKey
+ %#@count_minute_left@
+ count_minute_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 minute left
+ other
+ %ld minutes left
+
+
+ date.second.left
+
+ NSStringLocalizedFormatKey
+ %#@count_second_left@
+ count_second_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 second left
+ other
+ %ld seconds left
+
+
+ date.year.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_year_ago_abbr@
+ count_year_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1y ago
+ other
+ %ldy ago
+
+
+ date.month.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_month_ago_abbr@
+ count_month_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1M ago
+ other
+ %ldM ago
+
+
+ date.day.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_day_ago_abbr@
+ count_day_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1d ago
+ other
+ %ldd ago
+
+
+ date.hour.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_hour_ago_abbr@
+ count_hour_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1h ago
+ other
+ %ldh ago
+
+
+ date.minute.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_minute_ago_abbr@
+ count_minute_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1m ago
+ other
+ %ldm ago
+
+
+ date.second.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_second_ago_abbr@
+ count_second_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1s ago
+ other
+ %lds ago
+
+
+ plural.filtered_notification_banner.subtitle
+
+ NSStringLocalizedFormatKey
+ %#@number_of_requests@
+ number_of_requests
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ One person you may know
+ other
+ %ld people you may know
+
+
+
diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift
index f70caaa1dc..7244c6e9c6 100644
--- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift
+++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift
@@ -134,3 +134,135 @@ extension Mastodon.API.Notifications {
}
}
}
+
+//MARK: - Notification Policy
+
+extension Mastodon.API.Notifications {
+ internal static func notificationPolicyEndpointURL(domain: String) -> URL {
+ notificationsEndpointURL(domain: domain).appendingPathComponent("policy")
+ }
+
+ public struct UpdateNotificationPolicyQuery: Codable, PatchQuery {
+ public let filterNotFollowing: Bool
+ public let filterNotFollowers: Bool
+ public let filterNewAccounts: Bool
+ public let filterPrivateMentions: Bool
+
+ enum CodingKeys: String, CodingKey {
+ case filterNotFollowing = "filter_not_following"
+ case filterNotFollowers = "filter_not_followers"
+ case filterNewAccounts = "filter_new_accounts"
+ case filterPrivateMentions = "filter_private_mentions"
+ }
+
+ public init(filterNotFollowing: Bool, filterNotFollowers: Bool, filterNewAccounts: Bool, filterPrivateMentions: Bool) {
+ self.filterNotFollowing = filterNotFollowing
+ self.filterNotFollowers = filterNotFollowers
+ self.filterNewAccounts = filterNewAccounts
+ self.filterPrivateMentions = filterPrivateMentions
+ }
+ }
+
+ public static func getNotificationPolicy(
+ session: URLSession,
+ domain: String,
+ authorization: Mastodon.API.OAuth.Authorization
+ ) async throws -> Mastodon.Response.Content {
+ let request = Mastodon.API.get(
+ url: notificationPolicyEndpointURL(domain: domain),
+ authorization: authorization
+ )
+
+ let (data, response) = try await session.data(for: request)
+
+ let value = try Mastodon.API.decode(type: Mastodon.Entity.NotificationPolicy.self, from: data, response: response)
+ return Mastodon.Response.Content(value: value, response: response)
+ }
+
+ public static func updateNotificationPolicy(
+ session: URLSession,
+ domain: String,
+ authorization: Mastodon.API.OAuth.Authorization,
+ query: Mastodon.API.Notifications.UpdateNotificationPolicyQuery
+ ) async throws -> Mastodon.Response.Content {
+ let request = Mastodon.API.patch(
+ url: notificationPolicyEndpointURL(domain: domain),
+ query: query,
+ authorization: authorization
+ )
+ let (data, response) = try await session.data(for: request)
+ let value = try Mastodon.API.decode(type: Mastodon.Entity.NotificationPolicy.self, from: data, response: response)
+
+ return Mastodon.Response.Content(value: value, response: response)
+ }
+}
+
+extension Mastodon.API.Notifications {
+ internal static func notificationRequestsEndpointURL(domain: String) -> URL {
+ notificationsEndpointURL(domain: domain).appendingPathComponent("requests")
+ }
+
+ internal static func notificationRequestEndpointURL(domain: String, id: String) -> URL {
+ notificationRequestsEndpointURL(domain: domain).appendingPathComponent(id)
+ }
+
+ internal static func acceptNotificationRequestEndpointURL(domain: String, id: String) -> URL {
+ notificationRequestEndpointURL(domain: domain, id: id).appendingPathComponent("accept")
+ }
+
+ internal static func dismissNotificationRequestEndpointURL(domain: String, id: String) -> URL {
+ notificationRequestEndpointURL(domain: domain, id: id).appendingPathComponent("dismiss")
+ }
+
+ public static func getNotificationRequests(
+ session: URLSession,
+ domain: String,
+ authorization: Mastodon.API.OAuth.Authorization
+ ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.NotificationRequest]> {
+ let request = Mastodon.API.get(
+ url: notificationRequestsEndpointURL(domain: domain),
+ authorization: authorization
+ )
+
+ let (data, response) = try await session.data(for: request)
+
+ let value = try Mastodon.API.decode(type: [Mastodon.Entity.NotificationRequest].self, from: data, response: response)
+ return Mastodon.Response.Content(value: value, response: response)
+ }
+
+ public static func acceptNotificationRequest(
+ id: String,
+ session: URLSession,
+ domain: String,
+ authorization: Mastodon.API.OAuth.Authorization
+ ) async throws -> Mastodon.Response.Content<[String: String]> {
+ let request = Mastodon.API.post(
+ url: acceptNotificationRequestEndpointURL(domain: domain, id: id),
+ authorization: authorization
+ )
+
+ let (data, response) = try await session.data(for: request)
+
+ // we expect an empty dictionary
+ let value = try Mastodon.API.decode(type: [String: String].self, from: data, response: response)
+ return Mastodon.Response.Content(value: value, response: response)
+ }
+
+ public static func dismissNotificationRequest(
+ id: String,
+ session: URLSession,
+ domain: String,
+ authorization: Mastodon.API.OAuth.Authorization
+ ) async throws -> Mastodon.Response.Content<[String: String]> {
+ let request = Mastodon.API.post(
+ url: dismissNotificationRequestEndpointURL(domain: domain, id: id),
+ authorization: authorization
+ )
+
+ let (data, response) = try await session.data(for: request)
+
+ // we expect an empty dictionary
+ let value = try Mastodon.API.decode(type: [String: String].self, from: data, response: response)
+ return Mastodon.Response.Content(value: value, response: response)
+ }
+}
diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift
index 1103322879..a733a07051 100644
--- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift
+++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift
@@ -149,7 +149,7 @@ extension Mastodon.API {
static func post(
url: URL,
- query: PostQuery?,
+ query: PostQuery? = nil,
authorization: OAuth.Authorization? = nil
) -> URLRequest {
return buildRequest(url: url, method: .POST, query: query, authorization: authorization)
diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationPolicy.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationPolicy.swift
new file mode 100644
index 0000000000..68755e9b60
--- /dev/null
+++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationPolicy.swift
@@ -0,0 +1,31 @@
+// Copyright © 2024 Mastodon gGmbH. All rights reserved.
+
+import Foundation
+
+extension Mastodon.Entity {
+ public struct NotificationPolicy: Codable, Hashable {
+ public let filterNotFollowing: Bool
+ public let filterNotFollowers: Bool
+ public let filterNewAccounts: Bool
+ public let filterPrivateMentions: Bool
+ public let summary: Summary
+
+ enum CodingKeys: String, CodingKey {
+ case filterNotFollowing = "filter_not_following"
+ case filterNotFollowers = "filter_not_followers"
+ case filterNewAccounts = "filter_new_accounts"
+ case filterPrivateMentions = "filter_private_mentions"
+ case summary
+ }
+
+ public struct Summary: Codable, Hashable {
+ public let pendingRequestsCount: Int
+ public let pendingNotificationsCount: Int
+
+ enum CodingKeys: String, CodingKey {
+ case pendingRequestsCount = "pending_requests_count"
+ case pendingNotificationsCount = "pending_notifications_count"
+ }
+ }
+ }
+}
diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationRequest.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationRequest.swift
new file mode 100644
index 0000000000..2f6188b7ea
--- /dev/null
+++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationRequest.swift
@@ -0,0 +1,23 @@
+// Copyright © 2024 Mastodon gGmbH. All rights reserved.
+
+import Foundation
+
+extension Mastodon.Entity {
+ public struct NotificationRequest: Codable, Hashable {
+ public let id: String
+ public let createdAt: Date
+ public let updatedAt: Date
+ public let account: Mastodon.Entity.Account
+ public let notificationsCount: String // contains an `Int`
+ public let lastStatus: Mastodon.Entity.Status?
+
+ enum CodingKeys: String, CodingKey {
+ case id = "id"
+ case createdAt = "created_at"
+ case updatedAt = "updated_at"
+ case account = "account"
+ case notificationsCount = "notifications_count"
+ case lastStatus = "last_status"
+ }
+ }
+}
diff --git a/MastodonSDK/Sources/MastodonSDK/MastodonFeed.swift b/MastodonSDK/Sources/MastodonSDK/MastodonFeed.swift
index 33208fcc66..df53767b38 100644
--- a/MastodonSDK/Sources/MastodonSDK/MastodonFeed.swift
+++ b/MastodonSDK/Sources/MastodonSDK/MastodonFeed.swift
@@ -10,6 +10,7 @@ public final class MastodonFeed {
case home(timeline: TimelineContext)
case notificationAll
case notificationMentions
+ case notificationAccount(String)
public enum TimelineContext: Equatable {
case home
diff --git a/MastodonSDK/Sources/MastodonSDK/Query/Query.swift b/MastodonSDK/Sources/MastodonSDK/Query/Query.swift
index f93d8af1b9..ba3ec63cd2 100644
--- a/MastodonSDK/Sources/MastodonSDK/Query/Query.swift
+++ b/MastodonSDK/Sources/MastodonSDK/Query/Query.swift
@@ -55,6 +55,11 @@ extension PostQuery {
// PATCH
protocol PatchQuery: RequestQuery { }
+extension PatchQuery {
+ // By default a `PatchQuery` does not have query items
+ var queryItems: [URLQueryItem]? { nil }
+}
+
// PUT
protocol PutQuery: RequestQuery { }
diff --git a/MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift b/MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift
index eba7e1672f..2de73dc5b5 100644
--- a/MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift
+++ b/MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift
@@ -290,19 +290,3 @@ extension ActionToolbarContainer {
set { }
}
}
-
-#if DEBUG
-import SwiftUI
-
-struct ActionToolbarContainer_Previews: PreviewProvider {
- static var previews: some View {
- Group {
- UIViewPreview(width: 300) {
- ActionToolbarContainer()
- }
- .previewLayout(.fixed(width: 300, height: 44))
- .previewDisplayName("Inline")
- }
- }
-}
-#endif