From 8c57da25222f957b212118a89c6212e38433c5cb Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 18 Oct 2021 08:07:55 +0200 Subject: [PATCH 01/96] New translations Localizable.stringsdict (Catalan) --- .../input/ca_ES/Localizable.stringsdict | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Localization/StringsConvertor/input/ca_ES/Localizable.stringsdict b/Localization/StringsConvertor/input/ca_ES/Localizable.stringsdict index cc731293..140185ba 100644 --- a/Localization/StringsConvertor/input/ca_ES/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/ca_ES/Localizable.stringsdict @@ -21,7 +21,7 @@ a11y.plural.count.input_limit_exceeds NSStringLocalizedFormatKey - El límit d’entrada supera a %#@character_count@ + El límit de la entrada supera a %#@character_count@ character_count NSStringFormatSpecTypeKey @@ -37,7 +37,7 @@ a11y.plural.count.input_limit_remains NSStringLocalizedFormatKey - El límit d’entrada continua sent %#@character_count@ + El límit de la entrada continua sent %#@character_count@ character_count NSStringFormatSpecTypeKey @@ -111,7 +111,7 @@ one 1 impuls other - %ld impuls + %ld impulsos plural.count.vote @@ -301,9 +301,9 @@ NSStringFormatValueTypeKey ld one - fa 1a + fa 1 any other - fa %ldy anys + fa %ld anys date.month.ago.abbr @@ -317,9 +317,9 @@ NSStringFormatValueTypeKey ld one - fa 1M + fa 1 mes other - fa %ldM mesos + fa %ld mesos date.day.ago.abbr @@ -333,9 +333,9 @@ NSStringFormatValueTypeKey ld one - fa 1d + fa 1 día other - fa %ldd dies + fa %ld dies date.hour.ago.abbr @@ -351,7 +351,7 @@ one fa 1h other - fa %ldh hores + fa %ld hores date.minute.ago.abbr @@ -365,9 +365,9 @@ NSStringFormatValueTypeKey ld one - fa 1m + fa 1 minut other - fa %ldm minuts + fa %ld minuts date.second.ago.abbr @@ -381,9 +381,9 @@ NSStringFormatValueTypeKey ld one - fa 1s + fa 1 segon other - fa %lds seg + fa %ld segons From faff2004c1110c0710560e6fec652add7000132d Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 18 Oct 2021 12:20:39 +0200 Subject: [PATCH 02/96] New translations app.json (Kurmanji (Kurdish)) --- .../StringsConvertor/input/kmr_TR/app.json | 549 ++++++++++++++++++ 1 file changed, 549 insertions(+) create mode 100644 Localization/StringsConvertor/input/kmr_TR/app.json diff --git a/Localization/StringsConvertor/input/kmr_TR/app.json b/Localization/StringsConvertor/input/kmr_TR/app.json new file mode 100644 index 00000000..3ec77cf1 --- /dev/null +++ b/Localization/StringsConvertor/input/kmr_TR/app.json @@ -0,0 +1,549 @@ +{ + "common": { + "alerts": { + "common": { + "please_try_again": "Please try again.", + "please_try_again_later": "Please try again later." + }, + "sign_up_failure": { + "title": "Sign Up Failure" + }, + "server_error": { + "title": "Server Error" + }, + "vote_failure": { + "title": "Vote Failure", + "poll_ended": "The poll has ended" + }, + "discard_post_content": { + "title": "Discard Draft", + "message": "Confirm to discard composed post content." + }, + "publish_post_failure": { + "title": "Publish Failure", + "message": "Failed to publish the post.\nPlease check your internet connection.", + "attachments_message": { + "video_attach_with_photo": "Cannot attach a video to a post that already contains images.", + "more_than_one_video": "Cannot attach more than one video." + } + }, + "edit_profile_failure": { + "title": "Edit Profile Error", + "message": "Cannot edit profile. Please try again." + }, + "sign_out": { + "title": "Sign Out", + "message": "Are you sure you want to sign out?", + "confirm": "Sign Out" + }, + "block_domain": { + "title": "Are you really, really sure you want to block the entire %s? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain and any of your followers from that domain will be removed.", + "block_entire_domain": "Block Domain" + }, + "save_photo_failure": { + "title": "Save Photo Failure", + "message": "Please enable the photo library access permission to save the photo." + }, + "delete_post": { + "title": "Are you sure you want to delete this post?", + "delete": "Delete" + }, + "clean_cache": { + "title": "Clean Cache", + "message": "Successfully cleaned %s cache." + } + }, + "controls": { + "actions": { + "back": "Back", + "next": "Next", + "previous": "Previous", + "open": "Open", + "add": "Add", + "remove": "Remove", + "edit": "Edit", + "save": "Save", + "ok": "OK", + "done": "Done", + "confirm": "Confirm", + "continue": "Continue", + "cancel": "Cancel", + "discard": "Discard", + "try_again": "Try Again", + "take_photo": "Take Photo", + "save_photo": "Save Photo", + "copy_photo": "Copy Photo", + "sign_in": "Sign In", + "sign_up": "Sign Up", + "see_more": "See More", + "preview": "Preview", + "share": "Share", + "share_user": "Share %s", + "share_post": "Share Post", + "open_in_safari": "Open in Safari", + "find_people": "Find people to follow", + "manually_search": "Manually search instead", + "skip": "Skip", + "reply": "Reply", + "report_user": "Report %s", + "block_domain": "Block %s", + "unblock_domain": "Unblock %s", + "settings": "Settings", + "delete": "Delete" + }, + "tabs": { + "home": "Home", + "search": "Search", + "notification": "Notification", + "profile": "Profile" + }, + "keyboard": { + "common": { + "switch_to_tab": "Switch to %s", + "compose_new_post": "Compose New Post", + "show_favorites": "Show Favorites", + "open_settings": "Open Settings" + }, + "timeline": { + "previous_status": "Previous Post", + "next_status": "Next Post", + "open_status": "Open Post", + "open_author_profile": "Open Author's Profile", + "open_reblogger_profile": "Open Reblogger's Profile", + "reply_status": "Reply to Post", + "toggle_reblog": "Toggle Reblog on Post", + "toggle_favorite": "Toggle Favorite on Post", + "toggle_content_warning": "Toggle Content Warning", + "preview_image": "Preview Image" + }, + "segmented_control": { + "previous_section": "Previous Section", + "next_section": "Next Section" + } + }, + "status": { + "user_reblogged": "%s reblogged", + "user_replied_to": "Replied to %s", + "show_post": "Show Post", + "show_user_profile": "Show user profile", + "content_warning": "Content Warning", + "media_content_warning": "Tap anywhere to reveal", + "poll": { + "vote": "Vote", + "closed": "Closed" + }, + "actions": { + "reply": "Reply", + "reblog": "Reblog", + "unreblog": "Undo reblog", + "favorite": "Favorite", + "unfavorite": "Unfavorite", + "menu": "Menu" + }, + "tag": { + "url": "URL", + "mention": "Mention", + "link": "Link", + "hashtag": "Hashtag", + "email": "Email", + "emoji": "Emoji" + } + }, + "friendship": { + "follow": "Follow", + "following": "Following", + "request": "Request", + "pending": "Pending", + "block": "Block", + "block_user": "Block %s", + "block_domain": "Block %s", + "unblock": "Unblock", + "unblock_user": "Unblock %s", + "blocked": "Blocked", + "mute": "Mute", + "mute_user": "Mute %s", + "unmute": "Unmute", + "unmute_user": "Unmute %s", + "muted": "Muted", + "edit_info": "Edit Info" + }, + "timeline": { + "filtered": "Filtered", + "timestamp": { + "now": "Now" + }, + "loader": { + "load_missing_posts": "Load missing posts", + "loading_missing_posts": "Loading missing posts...", + "show_more_replies": "Show more replies" + }, + "header": { + "no_status_found": "No Post Found", + "blocking_warning": "You can’t view this user's profile\nuntil you unblock them.\nYour profile looks like this to them.", + "user_blocking_warning": "You can’t view %s’s profile\nuntil you unblock them.\nYour profile looks like this to them.", + "blocked_warning": "You can’t view this user’s profile\nuntil they unblock you.", + "user_blocked_warning": "You can’t view %s’s profile\nuntil they unblock you.", + "suspended_warning": "This user has been suspended.", + "user_suspended_warning": "%s’s account has been suspended." + } + } + } + }, + "scene": { + "welcome": { + "slogan": "Social networking\nback in your hands." + }, + "server_picker": { + "title": "Pick a server,\nany server.", + "button": { + "category": { + "all": "All", + "all_accessiblity_description": "Category: All", + "academia": "academia", + "activism": "activism", + "food": "food", + "furry": "furry", + "games": "games", + "general": "general", + "journalism": "journalism", + "lgbt": "lgbt", + "regional": "regional", + "art": "art", + "music": "music", + "tech": "tech" + }, + "see_less": "See Less", + "see_more": "See More" + }, + "label": { + "language": "LANGUAGE", + "users": "USERS", + "category": "CATEGORY" + }, + "input": { + "placeholder": "Find a server or join your own..." + }, + "empty_state": { + "finding_servers": "Finding available servers...", + "bad_network": "Something went wrong while loading the data. Check your internet connection.", + "no_results": "No results" + } + }, + "register": { + "title": "Tell us about you.", + "input": { + "avatar": { + "delete": "Delete" + }, + "username": { + "placeholder": "username", + "duplicate_prompt": "This username is taken." + }, + "display_name": { + "placeholder": "display name" + }, + "email": { + "placeholder": "email" + }, + "password": { + "placeholder": "password", + "hint": "Your password needs at least eight characters" + }, + "invite": { + "registration_user_invite_request": "Why do you want to join?" + } + }, + "error": { + "item": { + "username": "Username", + "email": "Email", + "password": "Password", + "agreement": "Agreement", + "locale": "Locale", + "reason": "Reason" + }, + "reason": { + "blocked": "%s contains a disallowed email provider", + "unreachable": "%s does not seem to exist", + "taken": "%s is already in use", + "reserved": "%s is a reserved keyword", + "accepted": "%s must be accepted", + "blank": "%s is required", + "invalid": "%s is invalid", + "too_long": "%s is too long", + "too_short": "%s is too short", + "inclusion": "%s is not a supported value" + }, + "special": { + "username_invalid": "Username must only contain alphanumeric characters and underscores", + "username_too_long": "Username is too long (can’t be longer than 30 characters)", + "email_invalid": "This is not a valid email address", + "password_too_short": "Password is too short (must be at least 8 characters)" + } + } + }, + "server_rules": { + "title": "Some ground rules.", + "subtitle": "These rules are set by the admins of %s.", + "prompt": "By continuing, you’re subject to the terms of service and privacy policy for %s.", + "terms_of_service": "terms of service", + "privacy_policy": "privacy policy", + "button": { + "confirm": "I Agree" + } + }, + "confirm_email": { + "title": "One last thing.", + "subtitle": "We just sent an email to %s,\ntap the link to confirm your account.", + "button": { + "open_email_app": "Open Email App", + "dont_receive_email": "I never got an email" + }, + "dont_receive_email": { + "title": "Check your email", + "description": "Check if your email address is correct as well as your junk folder if you haven’t.", + "resend_email": "Resend Email" + }, + "open_email_app": { + "title": "Check your inbox.", + "description": "We just sent you an email. Check your junk folder if you haven’t.", + "mail": "Mail", + "open_email_client": "Open Email Client" + } + }, + "home_timeline": { + "title": "Home", + "navigation_bar_state": { + "offline": "Offline", + "new_posts": "See new posts", + "published": "Published!", + "Publishing": "Publishing post..." + } + }, + "suggestion_account": { + "title": "Find People to Follow", + "follow_explain": "When you follow someone, you’ll see their posts in your home feed." + }, + "compose": { + "title": { + "new_post": "New Post", + "new_reply": "New Reply" + }, + "media_selection": { + "camera": "Take Photo", + "photo_library": "Photo Library", + "browse": "Browse" + }, + "content_input_placeholder": "Type or paste what’s on your mind", + "compose_action": "Publish", + "replying_to_user": "replying to %s", + "attachment": { + "photo": "photo", + "video": "video", + "attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.", + "description_photo": "Describe the photo for the visually-impaired...", + "description_video": "Describe the video for the visually-impaired..." + }, + "poll": { + "duration_time": "Duration: %s", + "thirty_minutes": "30 minutes", + "one_hour": "1 Hour", + "six_hours": "6 Hours", + "one_day": "1 Day", + "three_days": "3 Days", + "seven_days": "7 Days", + "option_number": "Option %ld" + }, + "content_warning": { + "placeholder": "Write an accurate warning here..." + }, + "visibility": { + "public": "Public", + "unlisted": "Unlisted", + "private": "Followers only", + "direct": "Only people I mention" + }, + "auto_complete": { + "space_to_add": "Space to add" + }, + "accessibility": { + "append_attachment": "Add Attachment", + "append_poll": "Add Poll", + "remove_poll": "Remove Poll", + "custom_emoji_picker": "Custom Emoji Picker", + "enable_content_warning": "Enable Content Warning", + "disable_content_warning": "Disable Content Warning", + "post_visibility_menu": "Post Visibility Menu" + }, + "keyboard": { + "discard_post": "Discard Post", + "publish_post": "Publish Post", + "toggle_poll": "Toggle Poll", + "toggle_content_warning": "Toggle Content Warning", + "append_attachment_entry": "Add Attachment - %s", + "select_visibility_entry": "Select Visibility - %s" + } + }, + "profile": { + "dashboard": { + "posts": "posts", + "following": "following", + "followers": "followers" + }, + "fields": { + "add_row": "Add Row", + "placeholder": { + "label": "Label", + "content": "Content" + } + }, + "segmented_control": { + "posts": "Posts", + "replies": "Replies", + "media": "Media" + }, + "relationship_action_alert": { + "confirm_unmute_user": { + "title": "Unmute Account", + "message": "Confirm to unmute %s" + }, + "confirm_unblock_usre": { + "title": "Unblock Account", + "message": "Confirm to unblock %s" + } + } + }, + "search": { + "title": "Search", + "search_bar": { + "placeholder": "Search hashtags and users", + "cancel": "Cancel" + }, + "recommend": { + "button_text": "See All", + "hash_tag": { + "title": "Trending on Mastodon", + "description": "Hashtags that are getting quite a bit of attention", + "people_talking": "%s people are talking" + }, + "accounts": { + "title": "Accounts you might like", + "description": "You may like to follow these accounts", + "follow": "Follow" + } + }, + "searching": { + "segment": { + "all": "All", + "people": "People", + "hashtags": "Hashtags", + "posts": "Posts" + }, + "empty_state": { + "no_results": "No results" + }, + "recent_search": "Recent searches", + "clear": "Clear" + } + }, + "favorite": { + "title": "Your Favorites" + }, + "notification": { + "title": { + "Everything": "Everything", + "Mentions": "Mentions" + }, + "user_followed_you": "%s followed you", + "user_favorited your post": "%s favorited your post", + "user_reblogged_your_post": "%s reblogged your post", + "user_mentioned_you": "%s mentioned you", + "user_requested_to_follow_you": "%s requested to follow you", + "user_your_poll_has_ended": "%s Your poll has ended", + "keyobard": { + "show_everything": "Show Everything", + "show_mentions": "Show Mentions" + } + }, + "thread": { + "back_title": "Post", + "title": "Post from %s" + }, + "settings": { + "title": "Settings", + "section": { + "appearance": { + "title": "Appearance", + "automatic": "Automatic", + "light": "Always Light", + "dark": "Always Dark" + }, + "notifications": { + "title": "Notifications", + "favorites": "Favorites my post", + "follows": "Follows me", + "boosts": "Reblogs my post", + "mentions": "Mentions me", + "trigger": { + "anyone": "anyone", + "follower": "a follower", + "follow": "anyone I follow", + "noone": "no one", + "title": "Notify me when" + } + }, + "preference": { + "title": "Preferences", + "true_black_dark_mode": "True black dark mode", + "disable_avatar_animation": "Disable animated avatars", + "disable_emoji_animation": "Disable animated emojis", + "using_default_browser": "Use default browser to open links" + }, + "boring_zone": { + "title": "The Boring Zone", + "account_settings": "Account Settings", + "terms": "Terms of Service", + "privacy": "Privacy Policy" + }, + "spicy_zone": { + "title": "The Spicy Zone", + "clear": "Clear Media Cache", + "signout": "Sign Out" + } + }, + "footer": { + "mastodon_description": "Mastodon is open source software. You can report issues on GitHub at %s (%s)" + }, + "keyboard": { + "close_settings_window": "Close Settings Window" + } + }, + "report": { + "title": "Report %s", + "step1": "Step 1 of 2", + "step2": "Step 2 of 2", + "content1": "Are there any other posts you’d like to add to the report?", + "content2": "Is there anything the moderators should know about this report?", + "send": "Send Report", + "skip_to_send": "Send without comment", + "text_placeholder": "Type or paste additional comments" + }, + "preview": { + "keyboard": { + "close_preview": "Close Preview", + "show_next": "Show Next", + "show_previous": "Show Previous" + } + }, + "account_list": { + "tab_bar_hint": "Current selected profile: %s. Double tap then hold to show account switcher", + "dismiss_account_switcher": "Dismiss Account Switcher", + "add_account": "Add Account" + }, + "wizard": { + "new_in_mastodon": "New in Mastodon", + "multiple_account_switch_intro_description": "Switch between multiple accounts by holding the profile button.", + "accessibility_hint": "Double tap to dismiss this wizard" + } + } +} \ No newline at end of file From 9c5e01149d78703be62c0eefb8a31e0989d71756 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 18 Oct 2021 12:20:40 +0200 Subject: [PATCH 03/96] New translations ios-infoPlist.json (Kurmanji (Kurdish)) --- .../StringsConvertor/input/kmr_TR/ios-infoPlist.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 Localization/StringsConvertor/input/kmr_TR/ios-infoPlist.json diff --git a/Localization/StringsConvertor/input/kmr_TR/ios-infoPlist.json b/Localization/StringsConvertor/input/kmr_TR/ios-infoPlist.json new file mode 100644 index 00000000..c6db73de --- /dev/null +++ b/Localization/StringsConvertor/input/kmr_TR/ios-infoPlist.json @@ -0,0 +1,6 @@ +{ + "NSCameraUsageDescription": "Used to take photo for post status", + "NSPhotoLibraryAddUsageDescription": "Used to save photo into the Photo Library", + "NewPostShortcutItemTitle": "New Post", + "SearchShortcutItemTitle": "Search" +} From 5dddeb318255bb4a1050823fad6b3e0319d3b0a2 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 18 Oct 2021 12:20:41 +0200 Subject: [PATCH 04/96] New translations Localizable.stringsdict (Kurmanji (Kurdish)) --- .../input/kmr_TR/Localizable.stringsdict | 390 ++++++++++++++++++ 1 file changed, 390 insertions(+) create mode 100644 Localization/StringsConvertor/input/kmr_TR/Localizable.stringsdict diff --git a/Localization/StringsConvertor/input/kmr_TR/Localizable.stringsdict b/Localization/StringsConvertor/input/kmr_TR/Localizable.stringsdict new file mode 100644 index 00000000..730e2902 --- /dev/null +++ b/Localization/StringsConvertor/input/kmr_TR/Localizable.stringsdict @@ -0,0 +1,390 @@ + + + + + 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 notification + + + 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 + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@ %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + post + other + posts + + + 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.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 + + + + From 7aa634cce8faf9eac3de98040cf56da4a5a68166 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 18 Oct 2021 12:20:42 +0200 Subject: [PATCH 05/96] New translations Intents.strings (Kurmanji (Kurdish)) --- .../Intents/input/kmr_TR/Intents.strings | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 Localization/StringsConvertor/Intents/input/kmr_TR/Intents.strings diff --git a/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.strings b/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.strings new file mode 100644 index 00000000..6877490b --- /dev/null +++ b/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.strings @@ -0,0 +1,51 @@ +"16wxgf" = "Post on Mastodon"; + +"751xkl" = "Text Content"; + +"CsR7G2" = "Post on Mastodon"; + +"HZSGTr" = "What content to post?"; + +"HdGikU" = "Posting failed"; + +"KDNTJ4" = "Failure Reason"; + +"RHxKOw" = "Send Post with text content"; + +"RxSqsb" = "Post"; + +"WCIR3D" = "Post ${content} on Mastodon"; + +"ZKJSNu" = "Post"; + +"ZS1XaK" = "${content}"; + +"ZbSjzC" = "Visibility"; + +"Zo4jgJ" = "Post Visibility"; + +"apSxMG-dYQ5NN" = "There are ${count} options matching ‘Public’."; + +"apSxMG-ehFLjY" = "There are ${count} options matching ‘Followers Only’."; + +"ayoYEb-dYQ5NN" = "${content}, Public"; + +"ayoYEb-ehFLjY" = "${content}, Followers Only"; + +"dUyuGg" = "Post on Mastodon"; + +"dYQ5NN" = "Public"; + +"ehFLjY" = "Followers Only"; + +"gfePDu" = "Posting failed. ${failureReason}"; + +"k7dbKQ" = "Post was sent successfully."; + +"oGiqmY-dYQ5NN" = "Just to confirm, you wanted ‘Public’?"; + +"oGiqmY-ehFLjY" = "Just to confirm, you wanted ‘Followers Only’?"; + +"rM6dvp" = "URL"; + +"ryJLwG" = "Post was sent successfully. "; From 86926a360bd172126822313139ad546a26d8ac53 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 18 Oct 2021 12:20:43 +0200 Subject: [PATCH 06/96] New translations Intents.stringsdict (Kurmanji (Kurdish)) --- .../Intents/input/kmr_TR/Intents.stringsdict | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Localization/StringsConvertor/Intents/input/kmr_TR/Intents.stringsdict diff --git a/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.stringsdict b/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.stringsdict new file mode 100644 index 00000000..18422c77 --- /dev/null +++ b/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.stringsdict @@ -0,0 +1,38 @@ + + + + + There are ${count} options matching ‘${content}’. - 2 + + NSStringLocalizedFormatKey + There are %#@count_option@ matching ‘${content}’. + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + one + 1 option + other + %ld options + + + There are ${count} options matching ‘${visibility}’. + + NSStringLocalizedFormatKey + There are %#@count_option@ matching ‘${visibility}’. + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + one + 1 option + other + %ld options + + + + From 107e6437174738c8e5b125790dfd73ce409c8254 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 18 Oct 2021 13:33:27 +0200 Subject: [PATCH 07/96] New translations Intents.strings (Kurmanji (Kurdish)) --- .../Intents/input/kmr_TR/Intents.strings | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.strings b/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.strings index 6877490b..944f91c4 100644 --- a/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.strings +++ b/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.strings @@ -1,8 +1,8 @@ -"16wxgf" = "Post on Mastodon"; +"16wxgf" = "Di Mastodon de biweşîne"; -"751xkl" = "Text Content"; +"751xkl" = "Naveroka nivîsê"; -"CsR7G2" = "Post on Mastodon"; +"CsR7G2" = "Di Mastodon de biweşîne"; "HZSGTr" = "What content to post?"; @@ -12,17 +12,17 @@ "RHxKOw" = "Send Post with text content"; -"RxSqsb" = "Post"; +"RxSqsb" = "Şandî"; -"WCIR3D" = "Post ${content} on Mastodon"; +"WCIR3D" = "${content} biweşîne di Mastodon de"; -"ZKJSNu" = "Post"; +"ZKJSNu" = "Şandî"; "ZS1XaK" = "${content}"; -"ZbSjzC" = "Visibility"; +"ZbSjzC" = "Xuyanî"; -"Zo4jgJ" = "Post Visibility"; +"Zo4jgJ" = "Xuyaniya şandiyê"; "apSxMG-dYQ5NN" = "There are ${count} options matching ‘Public’."; From cda40437e053ce0f819b8d56da8cc3b82224cd8c Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 18 Oct 2021 13:33:28 +0200 Subject: [PATCH 08/96] New translations Intents.stringsdict (Kurmanji (Kurdish)) --- .../StringsConvertor/Intents/input/kmr_TR/Intents.stringsdict | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.stringsdict b/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.stringsdict index 18422c77..fb10126c 100644 --- a/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.stringsdict +++ b/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.stringsdict @@ -13,9 +13,9 @@ NSStringFormatValueTypeKey %ld one - 1 option + 1 vebijêrk other - %ld options + %ld vebijêrk There are ${count} options matching ‘${visibility}’. From 78a50049984ff2e34561e3e1e100b69ef6f15e5e Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 19 Oct 2021 03:24:14 +0200 Subject: [PATCH 09/96] New translations Localizable.stringsdict (Kurmanji (Kurdish)) --- .../input/kmr_TR/Localizable.stringsdict | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/Localization/StringsConvertor/input/kmr_TR/Localizable.stringsdict b/Localization/StringsConvertor/input/kmr_TR/Localizable.stringsdict index 730e2902..8efdda09 100644 --- a/Localization/StringsConvertor/input/kmr_TR/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/kmr_TR/Localizable.stringsdict @@ -13,15 +13,15 @@ NSStringFormatValueTypeKey ld one - 1 unread notification + 1 agahdariya nexwendî other - %ld unread notification + %ld agahdariyên nexwendî a11y.plural.count.input_limit_exceeds NSStringLocalizedFormatKey - Input limit exceeds %#@character_count@ + Sînorê têketinê derbas kir %#@character_count@ character_count NSStringFormatSpecTypeKey @@ -61,9 +61,9 @@ NSStringFormatValueTypeKey ld one - post + şandî other - posts + şandî plural.count.post @@ -77,9 +77,9 @@ NSStringFormatValueTypeKey ld one - 1 post + 1 şandî other - %ld posts + %ld şandî plural.count.favorite @@ -93,9 +93,9 @@ NSStringFormatValueTypeKey ld one - 1 favorite + 1 hezkirin other - %ld favorites + %ld hezkirin plural.count.reblog @@ -125,9 +125,9 @@ NSStringFormatValueTypeKey ld one - 1 vote + 1 deng other - %ld votes + %ld deng plural.count.voter @@ -141,9 +141,9 @@ NSStringFormatValueTypeKey ld one - 1 voter + 1 hilbijêr other - %ld voters + %ld hilbijêr plural.people_talking @@ -301,9 +301,9 @@ NSStringFormatValueTypeKey ld one - 1y ago + 1 sal berê other - %ldy ago + %ld sal berê date.month.ago.abbr @@ -317,9 +317,9 @@ NSStringFormatValueTypeKey ld one - 1M ago + 1 xulek berê other - %ldM ago + %ld xulek berê date.day.ago.abbr @@ -333,9 +333,9 @@ NSStringFormatValueTypeKey ld one - 1d ago + 1 roj berê other - %ldd ago + %ld roj berê date.hour.ago.abbr @@ -349,9 +349,9 @@ NSStringFormatValueTypeKey ld one - 1h ago + 1 demjimêr berê other - %ldh ago + %ld demjimêr berê date.minute.ago.abbr @@ -365,9 +365,9 @@ NSStringFormatValueTypeKey ld one - 1m ago + 1 xulek berê other - %ldm ago + %ld xulek berê date.second.ago.abbr @@ -381,9 +381,9 @@ NSStringFormatValueTypeKey ld one - 1s ago + 1 çirke berê other - %lds ago + %ld çirke berê From 35af570bdbdccf5fc524e96643e57cae350bc0cd Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 19 Oct 2021 07:10:52 +0200 Subject: [PATCH 10/96] New translations ios-infoPlist.json (Kurmanji (Kurdish)) --- .../StringsConvertor/input/kmr_TR/ios-infoPlist.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Localization/StringsConvertor/input/kmr_TR/ios-infoPlist.json b/Localization/StringsConvertor/input/kmr_TR/ios-infoPlist.json index c6db73de..cdb286c0 100644 --- a/Localization/StringsConvertor/input/kmr_TR/ios-infoPlist.json +++ b/Localization/StringsConvertor/input/kmr_TR/ios-infoPlist.json @@ -1,6 +1,6 @@ { - "NSCameraUsageDescription": "Used to take photo for post status", - "NSPhotoLibraryAddUsageDescription": "Used to save photo into the Photo Library", - "NewPostShortcutItemTitle": "New Post", - "SearchShortcutItemTitle": "Search" + "NSCameraUsageDescription": "Bo kişandina wêneyê ji bo rewşa şandiyan tê bikaranîn", + "NSPhotoLibraryAddUsageDescription": "Ji bo tomarkirina wêneyê di pirtûkxaneya wêneyan de tê bikaranîn", + "NewPostShortcutItemTitle": "Şandiya nû", + "SearchShortcutItemTitle": "Bigere" } From ce23fa4b78f83e2f9e9260257d39e9a7d55b0766 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 19 Oct 2021 07:10:54 +0200 Subject: [PATCH 11/96] New translations Localizable.stringsdict (Kurmanji (Kurdish)) --- .../input/kmr_TR/Localizable.stringsdict | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Localization/StringsConvertor/input/kmr_TR/Localizable.stringsdict b/Localization/StringsConvertor/input/kmr_TR/Localizable.stringsdict index 8efdda09..064b8bf2 100644 --- a/Localization/StringsConvertor/input/kmr_TR/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/kmr_TR/Localizable.stringsdict @@ -29,15 +29,15 @@ NSStringFormatValueTypeKey ld one - 1 character + 1 tîp other - %ld characters + %ld tîp a11y.plural.count.input_limit_remains NSStringLocalizedFormatKey - Input limit remains %#@character_count@ + Sînorê têketinê %#@character_count@ maye character_count NSStringFormatSpecTypeKey @@ -45,9 +45,9 @@ NSStringFormatValueTypeKey ld one - 1 character + 1 tîp other - %ld characters + %ld tîp plural.count.metric_formatted.post @@ -157,9 +157,9 @@ NSStringFormatValueTypeKey ld one - 1 people talking + 1 mirov diaxive other - %ld people talking + %ld mirov diaxive plural.count.following @@ -173,9 +173,9 @@ NSStringFormatValueTypeKey ld one - 1 following + 1 dişopîne other - %ld following + %ld dişopîne plural.count.follower @@ -189,9 +189,9 @@ NSStringFormatValueTypeKey ld one - 1 follower + 1 şopîner other - %ld followers + %ld şopîner date.year.left @@ -205,9 +205,9 @@ NSStringFormatValueTypeKey ld one - 1 year left + 1 sal berê other - %ld years left + %ld sal berê date.month.left @@ -221,9 +221,9 @@ NSStringFormatValueTypeKey ld one - 1 months left + 1 meh berê other - %ld months left + %ld meh berê date.day.left @@ -237,9 +237,9 @@ NSStringFormatValueTypeKey ld one - 1 day left + 1 roj berê other - %ld days left + %ld roj berê date.hour.left @@ -253,9 +253,9 @@ NSStringFormatValueTypeKey ld one - 1 hour left + 1 demjimêr berê other - %ld hours left + %ld demjimêr berê date.minute.left @@ -269,9 +269,9 @@ NSStringFormatValueTypeKey ld one - 1 minute left + 1 xulek berê other - %ld minutes left + %ld xulek berê date.second.left @@ -285,9 +285,9 @@ NSStringFormatValueTypeKey ld one - 1 second left + 1 çirke berê other - %ld seconds left + %ld çirke berê date.year.ago.abbr From 4e3275120ebafe0541ea0adf31ef164f3d06c5c4 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 19 Oct 2021 19:19:48 +0200 Subject: [PATCH 12/96] New translations app.json (Kurmanji (Kurdish)) --- .../StringsConvertor/input/kmr_TR/app.json | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Localization/StringsConvertor/input/kmr_TR/app.json b/Localization/StringsConvertor/input/kmr_TR/app.json index 3ec77cf1..6dffb38b 100644 --- a/Localization/StringsConvertor/input/kmr_TR/app.json +++ b/Localization/StringsConvertor/input/kmr_TR/app.json @@ -46,7 +46,7 @@ }, "delete_post": { "title": "Are you sure you want to delete this post?", - "delete": "Delete" + "delete": "Jê bibe" }, "clean_cache": { "title": "Clean Cache", @@ -55,31 +55,31 @@ }, "controls": { "actions": { - "back": "Back", - "next": "Next", - "previous": "Previous", - "open": "Open", - "add": "Add", - "remove": "Remove", - "edit": "Edit", - "save": "Save", - "ok": "OK", - "done": "Done", - "confirm": "Confirm", - "continue": "Continue", - "cancel": "Cancel", - "discard": "Discard", - "try_again": "Try Again", + "back": "Vegere", + "next": "Pêş", + "previous": "Paş", + "open": "Veke", + "add": "Tevlî bike", + "remove": "Rake", + "edit": "Serrast bike", + "save": "Tomar bike", + "ok": "BAŞ E", + "done": "Qediya", + "confirm": "Bipejirîne", + "continue": "Bidomîne", + "cancel": "Dev jê berde", + "discard": "Biavêje", + "try_again": "Dîsa biceribîne", "take_photo": "Take Photo", "save_photo": "Save Photo", "copy_photo": "Copy Photo", - "sign_in": "Sign In", - "sign_up": "Sign Up", - "see_more": "See More", - "preview": "Preview", - "share": "Share", - "share_user": "Share %s", - "share_post": "Share Post", + "sign_in": "Têkeve", + "sign_up": "Tomar bibe", + "see_more": "Bêtir bibîne", + "preview": "Pêşdîtin", + "share": "Parve bike", + "share_user": "%s parve bike", + "share_post": "Şandiyê parve bike", "open_in_safari": "Open in Safari", "find_people": "Find people to follow", "manually_search": "Manually search instead", @@ -110,7 +110,7 @@ "open_status": "Open Post", "open_author_profile": "Open Author's Profile", "open_reblogger_profile": "Open Reblogger's Profile", - "reply_status": "Reply to Post", + "reply_status": "Bersivê bide şandiyê", "toggle_reblog": "Toggle Reblog on Post", "toggle_favorite": "Toggle Favorite on Post", "toggle_content_warning": "Toggle Content Warning", From a6a782517668a7a43645a13a457b2c28746b4e86 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 19 Oct 2021 19:19:49 +0200 Subject: [PATCH 13/96] New translations Intents.strings (Kurmanji (Kurdish)) --- .../StringsConvertor/Intents/input/kmr_TR/Intents.strings | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.strings b/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.strings index 944f91c4..22b6300d 100644 --- a/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.strings +++ b/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.strings @@ -34,9 +34,9 @@ "dUyuGg" = "Post on Mastodon"; -"dYQ5NN" = "Public"; +"dYQ5NN" = "Gelemperî"; -"ehFLjY" = "Followers Only"; +"ehFLjY" = "Tenê şopîneran"; "gfePDu" = "Posting failed. ${failureReason}"; @@ -46,6 +46,6 @@ "oGiqmY-ehFLjY" = "Just to confirm, you wanted ‘Followers Only’?"; -"rM6dvp" = "URL"; +"rM6dvp" = "Girêdan"; "ryJLwG" = "Post was sent successfully. "; From 301a53f2228276478bdc6c9e118e7bd3b0c9fba2 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 19 Oct 2021 20:20:38 +0200 Subject: [PATCH 14/96] New translations app.json (Kurmanji (Kurdish)) --- .../StringsConvertor/input/kmr_TR/app.json | 130 +++++++++--------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/Localization/StringsConvertor/input/kmr_TR/app.json b/Localization/StringsConvertor/input/kmr_TR/app.json index 6dffb38b..09f99259 100644 --- a/Localization/StringsConvertor/input/kmr_TR/app.json +++ b/Localization/StringsConvertor/input/kmr_TR/app.json @@ -2,50 +2,50 @@ "common": { "alerts": { "common": { - "please_try_again": "Please try again.", - "please_try_again_later": "Please try again later." + "please_try_again": "Ji kerema xwe dîsa biceribîne.", + "please_try_again_later": "Ji kerema xwe paşê dîsa biceribîne." }, "sign_up_failure": { - "title": "Sign Up Failure" + "title": "Tomarkirin têkçû" }, "server_error": { - "title": "Server Error" + "title": "Çewtiya rajekar" }, "vote_failure": { - "title": "Vote Failure", - "poll_ended": "The poll has ended" + "title": "Dengdayîn têkçû", + "poll_ended": "Rapirsîya qediya" }, "discard_post_content": { "title": "Discard Draft", "message": "Confirm to discard composed post content." }, "publish_post_failure": { - "title": "Publish Failure", - "message": "Failed to publish the post.\nPlease check your internet connection.", + "title": "Weşandin têkçû", + "message": "Weşandina şandiyê têkçû.\nJkx girêdana înternetê xwe kontrol bike.", "attachments_message": { - "video_attach_with_photo": "Cannot attach a video to a post that already contains images.", - "more_than_one_video": "Cannot attach more than one video." + "video_attach_with_photo": "Nikare vîdyoyekê tevlî şandiyê ku berê wêne tê de heye bike.", + "more_than_one_video": "Nikare ji bêtirî yek vîdyoyekê tevlî şandiyê bike." } }, "edit_profile_failure": { "title": "Edit Profile Error", - "message": "Cannot edit profile. Please try again." + "message": "Nikare profîlê serrast bike. Jkx dîsa biceribîne." }, "sign_out": { - "title": "Sign Out", - "message": "Are you sure you want to sign out?", - "confirm": "Sign Out" + "title": "Derkeve", + "message": "Ma tu dixwazî ku derkevî?", + "confirm": "Derkeve" }, "block_domain": { - "title": "Are you really, really sure you want to block the entire %s? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain and any of your followers from that domain will be removed.", - "block_entire_domain": "Block Domain" + "title": "Tu ji xwe bawerî, bi rastî tu dixwazî hemû %s asteng bikî? Di gelek rewşan de asteng kirin an jî bêdeng kirin têrê dike û tê tercîh kirin. Tu nikarî naveroka vê navperê di demnameyê an jî agahdariyên xwe de bibînî. Şopînerên te yê di vê navperê were jêbirin.", + "block_entire_domain": "Navperê asteng bike" }, "save_photo_failure": { - "title": "Save Photo Failure", + "title": "Tomarkirina wêneyê têkçû", "message": "Please enable the photo library access permission to save the photo." }, "delete_post": { - "title": "Are you sure you want to delete this post?", + "title": "Ma tu dixwazî vê şandiyê jê bibî?", "delete": "Jê bibe" }, "clean_cache": { @@ -80,35 +80,35 @@ "share": "Parve bike", "share_user": "%s parve bike", "share_post": "Şandiyê parve bike", - "open_in_safari": "Open in Safari", + "open_in_safari": "Di Safariyê de veke", "find_people": "Find people to follow", - "manually_search": "Manually search instead", - "skip": "Skip", - "reply": "Reply", - "report_user": "Report %s", - "block_domain": "Block %s", - "unblock_domain": "Unblock %s", - "settings": "Settings", - "delete": "Delete" + "manually_search": "Ji devlê i destan lêgerînê bike", + "skip": "Derbas bike", + "reply": "Bersivê bide", + "report_user": "%s ragihîne", + "block_domain": "%s asteng bike", + "unblock_domain": "%s asteng neke", + "settings": "Sazkarî", + "delete": "Jê bibe" }, "tabs": { - "home": "Home", - "search": "Search", - "notification": "Notification", - "profile": "Profile" + "home": "Serrûpel", + "search": "Bigere", + "notification": "Agahdarî", + "profile": "Profîl" }, "keyboard": { "common": { - "switch_to_tab": "Switch to %s", - "compose_new_post": "Compose New Post", - "show_favorites": "Show Favorites", - "open_settings": "Open Settings" + "switch_to_tab": "Biguherîne bo %s", + "compose_new_post": "Şandiyeke nû binivsîne", + "show_favorites": "Bijarteyan nîşan bide", + "open_settings": "Sazkariyan Veke" }, "timeline": { - "previous_status": "Previous Post", - "next_status": "Next Post", - "open_status": "Open Post", - "open_author_profile": "Open Author's Profile", + "previous_status": "Şandeya paş", + "next_status": "Şandiya pêş", + "open_status": "Şandiyê veke", + "open_author_profile": "Profîla nivîskaran veke", "open_reblogger_profile": "Open Reblogger's Profile", "reply_status": "Bersivê bide şandiyê", "toggle_reblog": "Toggle Reblog on Post", @@ -124,8 +124,8 @@ "status": { "user_reblogged": "%s reblogged", "user_replied_to": "Replied to %s", - "show_post": "Show Post", - "show_user_profile": "Show user profile", + "show_post": "Şandiyê nîşan bide", + "show_user_profile": "Profîla bikarhêner nîşan bide", "content_warning": "Content Warning", "media_content_warning": "Tap anywhere to reveal", "poll": { @@ -152,33 +152,33 @@ "friendship": { "follow": "Follow", "following": "Following", - "request": "Request", - "pending": "Pending", - "block": "Block", - "block_user": "Block %s", - "block_domain": "Block %s", - "unblock": "Unblock", - "unblock_user": "Unblock %s", - "blocked": "Blocked", - "mute": "Mute", - "mute_user": "Mute %s", - "unmute": "Unmute", - "unmute_user": "Unmute %s", + "request": "Daxwazên şopandinê", + "pending": "Tê nirxandin", + "block": "Asteng bike", + "block_user": "%s asteng bike", + "block_domain": "%s asteng bike", + "unblock": "Astengiyê rake", + "unblock_user": "%s asteng neke", + "blocked": "Astengkirî", + "mute": "Bêdeng bike", + "mute_user": "%s bêdeng bike", + "unmute": "Bêdeng neke", + "unmute_user": "%s bêdeng neke", "muted": "Muted", - "edit_info": "Edit Info" + "edit_info": "Zanyariyan serrast bike" }, "timeline": { "filtered": "Filtered", "timestamp": { - "now": "Now" + "now": "Niha" }, "loader": { "load_missing_posts": "Load missing posts", "loading_missing_posts": "Loading missing posts...", - "show_more_replies": "Show more replies" + "show_more_replies": "Bêtir bersivan nîşan bide" }, "header": { - "no_status_found": "No Post Found", + "no_status_found": "Şandî nehate dîtin", "blocking_warning": "You can’t view this user's profile\nuntil you unblock them.\nYour profile looks like this to them.", "user_blocking_warning": "You can’t view %s’s profile\nuntil you unblock them.\nYour profile looks like this to them.", "blocked_warning": "You can’t view this user’s profile\nuntil they unblock you.", @@ -197,15 +197,15 @@ "title": "Pick a server,\nany server.", "button": { "category": { - "all": "All", - "all_accessiblity_description": "Category: All", - "academia": "academia", - "activism": "activism", - "food": "food", + "all": "Hemû", + "all_accessiblity_description": "Beş: Hemû", + "academia": "akademî", + "activism": "çalakî", + "food": "xwarin", "furry": "furry", - "games": "games", - "general": "general", - "journalism": "journalism", + "games": "lîsk", + "general": "giştî", + "journalism": "rojnamevanî", "lgbt": "lgbt", "regional": "regional", "art": "art", From 86e83a3f31a89b7712b2dd5286d7530c405fb537 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 19 Oct 2021 22:09:00 +0200 Subject: [PATCH 15/96] New translations app.json (Kurmanji (Kurdish)) --- .../StringsConvertor/input/kmr_TR/app.json | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Localization/StringsConvertor/input/kmr_TR/app.json b/Localization/StringsConvertor/input/kmr_TR/app.json index 09f99259..0c465cb2 100644 --- a/Localization/StringsConvertor/input/kmr_TR/app.json +++ b/Localization/StringsConvertor/input/kmr_TR/app.json @@ -16,8 +16,8 @@ "poll_ended": "Rapirsîya qediya" }, "discard_post_content": { - "title": "Discard Draft", - "message": "Confirm to discard composed post content." + "title": "Reşnivîs jêbibe", + "message": "Piştrast bikin ku naveroka posteyê ya hatîye nivîsandin jê bibin." }, "publish_post_failure": { "title": "Weşandin têkçû", @@ -28,7 +28,7 @@ } }, "edit_profile_failure": { - "title": "Edit Profile Error", + "title": "Çewtiya profîlê biguherîne", "message": "Nikare profîlê serrast bike. Jkx dîsa biceribîne." }, "sign_out": { @@ -42,15 +42,15 @@ }, "save_photo_failure": { "title": "Tomarkirina wêneyê têkçû", - "message": "Please enable the photo library access permission to save the photo." + "message": "Ji kerema xwe destûra gihîştina pirtûkxaneya wêneyê çalak bikin da ku wêneyê hilînin." }, "delete_post": { "title": "Ma tu dixwazî vê şandiyê jê bibî?", "delete": "Jê bibe" }, "clean_cache": { - "title": "Clean Cache", - "message": "Successfully cleaned %s cache." + "title": "Pêşbîrê paqij bike", + "message": "Pêşbîra %s biserketî hate paqijkirin." } }, "controls": { @@ -70,9 +70,9 @@ "cancel": "Dev jê berde", "discard": "Biavêje", "try_again": "Dîsa biceribîne", - "take_photo": "Take Photo", - "save_photo": "Save Photo", - "copy_photo": "Copy Photo", + "take_photo": "Wêne bikişîne", + "save_photo": "Wêneyê hilîne", + "copy_photo": "Wêne kopî bikin", "sign_in": "Têkeve", "sign_up": "Tomar bibe", "see_more": "Bêtir bibîne", @@ -81,7 +81,7 @@ "share_user": "%s parve bike", "share_post": "Şandiyê parve bike", "open_in_safari": "Di Safariyê de veke", - "find_people": "Find people to follow", + "find_people": "Kesên ku bişopînin bibînin", "manually_search": "Ji devlê i destan lêgerînê bike", "skip": "Derbas bike", "reply": "Bersivê bide", @@ -109,24 +109,24 @@ "next_status": "Şandiya pêş", "open_status": "Şandiyê veke", "open_author_profile": "Profîla nivîskaran veke", - "open_reblogger_profile": "Open Reblogger's Profile", + "open_reblogger_profile": "Profîla nivîskaran veke", "reply_status": "Bersivê bide şandiyê", "toggle_reblog": "Toggle Reblog on Post", - "toggle_favorite": "Toggle Favorite on Post", - "toggle_content_warning": "Toggle Content Warning", - "preview_image": "Preview Image" + "toggle_favorite": "Di postê da Bijartin veke/bigire", + "toggle_content_warning": "Hişyariya naverokê veke/bigire", + "preview_image": "Wêneya pêşdîtinê" }, "segmented_control": { - "previous_section": "Previous Section", - "next_section": "Next Section" + "previous_section": "Beşa berê", + "next_section": "Beşa paşê" } }, "status": { - "user_reblogged": "%s reblogged", - "user_replied_to": "Replied to %s", + "user_reblogged": "%s ji nû ve hat blogkirin", + "user_replied_to": "Bersiv da %s", "show_post": "Şandiyê nîşan bide", "show_user_profile": "Profîla bikarhêner nîşan bide", - "content_warning": "Content Warning", + "content_warning": "Hişyariya naverokê", "media_content_warning": "Tap anywhere to reveal", "poll": { "vote": "Vote", From 0fb360aca88d4f27b7715789469a4f168e69f88e Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 19 Oct 2021 23:06:46 +0200 Subject: [PATCH 16/96] New translations app.json (Kurmanji (Kurdish)) --- .../StringsConvertor/input/kmr_TR/app.json | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/Localization/StringsConvertor/input/kmr_TR/app.json b/Localization/StringsConvertor/input/kmr_TR/app.json index 0c465cb2..cc2fdd32 100644 --- a/Localization/StringsConvertor/input/kmr_TR/app.json +++ b/Localization/StringsConvertor/input/kmr_TR/app.json @@ -127,31 +127,31 @@ "show_post": "Şandiyê nîşan bide", "show_user_profile": "Profîla bikarhêner nîşan bide", "content_warning": "Hişyariya naverokê", - "media_content_warning": "Tap anywhere to reveal", + "media_content_warning": "Ji bo aşkerakirinê derekî bitikîne", "poll": { - "vote": "Vote", - "closed": "Closed" + "vote": "Deng", + "closed": "Girtî" }, "actions": { - "reply": "Reply", - "reblog": "Reblog", - "unreblog": "Undo reblog", - "favorite": "Favorite", - "unfavorite": "Unfavorite", - "menu": "Menu" + "reply": "Bersivê bide", + "reblog": "Ji nû ve blog", + "unreblog": "Ji nû ve blogkirin betal bikin", + "favorite": "Bijartî", + "unfavorite": "Nebijare", + "menu": "Menû" }, "tag": { "url": "URL", - "mention": "Mention", - "link": "Link", - "hashtag": "Hashtag", - "email": "Email", - "emoji": "Emoji" + "mention": "Behs", + "link": "Girêdan", + "hashtag": "Etîket", + "email": "E-name", + "emoji": "E-name" } }, "friendship": { - "follow": "Follow", - "following": "Following", + "follow": "Bişopîne", + "following": "Dişopîne", "request": "Daxwazên şopandinê", "pending": "Tê nirxandin", "block": "Asteng bike", @@ -164,37 +164,37 @@ "mute_user": "%s bêdeng bike", "unmute": "Bêdeng neke", "unmute_user": "%s bêdeng neke", - "muted": "Muted", + "muted": "Bêdengkirî", "edit_info": "Zanyariyan serrast bike" }, "timeline": { - "filtered": "Filtered", + "filtered": "Parzûnkirî", "timestamp": { "now": "Niha" }, "loader": { - "load_missing_posts": "Load missing posts", - "loading_missing_posts": "Loading missing posts...", + "load_missing_posts": "Barkirina posteyên kêm", + "loading_missing_posts": "Barkirina posteyên kêm...", "show_more_replies": "Bêtir bersivan nîşan bide" }, "header": { "no_status_found": "Şandî nehate dîtin", - "blocking_warning": "You can’t view this user's profile\nuntil you unblock them.\nYour profile looks like this to them.", - "user_blocking_warning": "You can’t view %s’s profile\nuntil you unblock them.\nYour profile looks like this to them.", - "blocked_warning": "You can’t view this user’s profile\nuntil they unblock you.", - "user_blocked_warning": "You can’t view %s’s profile\nuntil they unblock you.", - "suspended_warning": "This user has been suspended.", - "user_suspended_warning": "%s’s account has been suspended." + "blocking_warning": "Tu nikarî profîla vî bikarhênerî bibînî\nHeta ku tu wan asteng bikî.\nProfîla te ji wan ra wiha xuya dike.", + "user_blocking_warning": "Tu nikarî profîla %s bibînî\nHeta ku tu wan asteng bikî.\nProfîla te ji wan ra wiha xuya dike.", + "blocked_warning": "Tu nikarî profîla vî bikarhênerî bibînî\nheta ku astengîya te rakin.", + "user_blocked_warning": "Tu nikarî profîla %s bibînî\nHeta ku astengîya te rakin.", + "suspended_warning": "Ev bikarhêner hat sekinandin.", + "user_suspended_warning": "Hesaba %s hat sekinandin." } } } }, "scene": { "welcome": { - "slogan": "Social networking\nback in your hands." + "slogan": "Tevna civakî\nvegerrîna di destê te da." }, "server_picker": { - "title": "Pick a server,\nany server.", + "title": "Pêşkêşkarekê hilbijêre,\nher pêşkêşvanek.", "button": { "category": { "all": "Hemû", From 084a41b5039664a30f4726ee1a063641d613ba37 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 21 Oct 2021 04:36:43 +0200 Subject: [PATCH 17/96] New translations app.json (Kurmanji (Kurdish)) --- Localization/StringsConvertor/input/kmr_TR/app.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Localization/StringsConvertor/input/kmr_TR/app.json b/Localization/StringsConvertor/input/kmr_TR/app.json index cc2fdd32..eaa584de 100644 --- a/Localization/StringsConvertor/input/kmr_TR/app.json +++ b/Localization/StringsConvertor/input/kmr_TR/app.json @@ -194,7 +194,7 @@ "slogan": "Tevna civakî\nvegerrîna di destê te da." }, "server_picker": { - "title": "Pêşkêşkarekê hilbijêre,\nher pêşkêşvanek.", + "title": "Rajekarekê hilbijêre,\nHer kîjan rajekar be.", "button": { "category": { "all": "Hemû", From d640be9cbe4c805ba27f02b447efa1b3d6a5f878 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 21 Oct 2021 05:38:59 +0200 Subject: [PATCH 18/96] New translations app.json (Kurmanji (Kurdish)) --- Localization/StringsConvertor/input/kmr_TR/app.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Localization/StringsConvertor/input/kmr_TR/app.json b/Localization/StringsConvertor/input/kmr_TR/app.json index eaa584de..b2db1adb 100644 --- a/Localization/StringsConvertor/input/kmr_TR/app.json +++ b/Localization/StringsConvertor/input/kmr_TR/app.json @@ -191,7 +191,7 @@ }, "scene": { "welcome": { - "slogan": "Tevna civakî\nvegerrîna di destê te da." + "slogan": "Torên civakî\ndi destên te de." }, "server_picker": { "title": "Rajekarekê hilbijêre,\nHer kîjan rajekar be.", From 2b1a7e7d399b6a72857f3cce27a2aa63d5e652a7 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 21 Oct 2021 05:39:00 +0200 Subject: [PATCH 19/96] New translations Intents.strings (Kurmanji (Kurdish)) --- .../StringsConvertor/Intents/input/kmr_TR/Intents.strings | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.strings b/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.strings index 22b6300d..687cac69 100644 --- a/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.strings +++ b/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.strings @@ -4,13 +4,13 @@ "CsR7G2" = "Di Mastodon de biweşîne"; -"HZSGTr" = "What content to post?"; +"HZSGTr" = "Kîjan naverok bila bê şandin?"; -"HdGikU" = "Posting failed"; +"HdGikU" = "Şandin têkçû"; -"KDNTJ4" = "Failure Reason"; +"KDNTJ4" = "Sedema têkçûnê"; -"RHxKOw" = "Send Post with text content"; +"RHxKOw" = "Bi naveroka nivîsî şandiyan bişîne"; "RxSqsb" = "Şandî"; From 87722f172e03fdc0a653780348557a0de038b9c7 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 21 Oct 2021 05:39:01 +0200 Subject: [PATCH 20/96] New translations Intents.stringsdict (Kurmanji (Kurdish)) --- .../Intents/input/kmr_TR/Intents.stringsdict | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.stringsdict b/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.stringsdict index fb10126c..2f001aaa 100644 --- a/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.stringsdict +++ b/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.stringsdict @@ -5,7 +5,7 @@ There are ${count} options matching ‘${content}’. - 2 NSStringLocalizedFormatKey - There are %#@count_option@ matching ‘${content}’. + %#@count_option@ heye ku bi ‘${content}’ re têkildar e. count_option NSStringFormatSpecTypeKey @@ -21,7 +21,7 @@ There are ${count} options matching ‘${visibility}’. NSStringLocalizedFormatKey - There are %#@count_option@ matching ‘${visibility}’. + %#@count_option@ heye ku bi ‘${visibility}’ re têkildar e. count_option NSStringFormatSpecTypeKey @@ -29,9 +29,9 @@ NSStringFormatValueTypeKey %ld one - 1 option + 1 vebijêrk other - %ld options + %ld vebijêrk From 532e10e466f7a673af73f8c1c0b7c14dbcc3959d Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 25 Oct 2021 10:40:39 +0200 Subject: [PATCH 21/96] New translations app.json (Japanese) --- Localization/StringsConvertor/input/ja_JP/app.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Localization/StringsConvertor/input/ja_JP/app.json b/Localization/StringsConvertor/input/ja_JP/app.json index 2f1aec4e..1c7d408f 100644 --- a/Localization/StringsConvertor/input/ja_JP/app.json +++ b/Localization/StringsConvertor/input/ja_JP/app.json @@ -191,7 +191,7 @@ }, "scene": { "welcome": { - "slogan": "Social networking\nback in your hands." + "slogan": "ソーシャルネットワーキングを、あなたの手の中に." }, "server_picker": { "title": "サーバーを選択", @@ -538,11 +538,11 @@ "account_list": { "tab_bar_hint": "Current selected profile: %s. Double tap then hold to show account switcher", "dismiss_account_switcher": "Dismiss Account Switcher", - "add_account": "Add Account" + "add_account": "アカウントを追加" }, "wizard": { - "new_in_mastodon": "New in Mastodon", - "multiple_account_switch_intro_description": "Switch between multiple accounts by holding the profile button.", + "new_in_mastodon": "Mastodon の新機能", + "multiple_account_switch_intro_description": "プロフィールボタンを押して複数のアカウントを切り替えます。", "accessibility_hint": "Double tap to dismiss this wizard" } } From 30110d1560b594aab1069dbd4bc6cbcd8eb7f593 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 25 Oct 2021 10:40:40 +0200 Subject: [PATCH 22/96] New translations Localizable.stringsdict (Japanese) --- .../input/ja_JP/Localizable.stringsdict | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Localization/StringsConvertor/input/ja_JP/Localizable.stringsdict b/Localization/StringsConvertor/input/ja_JP/Localizable.stringsdict index 0300d9dc..c51a9a29 100644 --- a/Localization/StringsConvertor/input/ja_JP/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/ja_JP/Localizable.stringsdict @@ -13,7 +13,7 @@ NSStringFormatValueTypeKey ld other - %ld unread notification + %ld 件の未読通知 a11y.plural.count.input_limit_exceeds @@ -27,7 +27,7 @@ NSStringFormatValueTypeKey ld other - %ld characters + %ld 文字 a11y.plural.count.input_limit_remains @@ -41,7 +41,7 @@ NSStringFormatValueTypeKey ld other - %ld characters + %ld 文字 plural.count.metric_formatted.post @@ -111,7 +111,7 @@ NSStringFormatValueTypeKey ld other - %ld votes + %ld票 plural.count.voter @@ -195,7 +195,7 @@ NSStringFormatValueTypeKey ld other - %ld months left + %ldか月前 date.day.left @@ -279,7 +279,7 @@ NSStringFormatValueTypeKey ld other - %ldM ago + %ld分前 date.day.ago.abbr From 4640ac80eb2851c1a47641e7bc990bbfcb8a87d3 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 25 Oct 2021 23:36:20 +0200 Subject: [PATCH 23/96] New translations app.json (German) --- Localization/StringsConvertor/input/de_DE/app.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Localization/StringsConvertor/input/de_DE/app.json b/Localization/StringsConvertor/input/de_DE/app.json index ff29e8d6..43d8ed70 100644 --- a/Localization/StringsConvertor/input/de_DE/app.json +++ b/Localization/StringsConvertor/input/de_DE/app.json @@ -536,13 +536,13 @@ } }, "account_list": { - "tab_bar_hint": "Current selected profile: %s. Double tap then hold to show account switcher", + "tab_bar_hint": "Aktuell ausgewähltes Profil: %s. Doppeltippen dann gedrückt halten, um den Kontoschalter anzuzeigen", "dismiss_account_switcher": "Dismiss Account Switcher", "add_account": "Konto hinzufügen" }, "wizard": { "new_in_mastodon": "Neu in Mastodon", - "multiple_account_switch_intro_description": "Switch between multiple accounts by holding the profile button.", + "multiple_account_switch_intro_description": "Wechsel zwischen mehreren Konten durch drücken der Profil-Schaltfläche.", "accessibility_hint": "Doppeltippen, um diesen Assistenten zu schließen" } } From dfd3516f013d9ef8925186d8160759f9ef876037 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 26 Oct 2021 05:47:36 +0200 Subject: [PATCH 24/96] New translations Intents.strings (Arabic) --- .../StringsConvertor/Intents/input/ar_SA/Intents.strings | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Localization/StringsConvertor/Intents/input/ar_SA/Intents.strings b/Localization/StringsConvertor/Intents/input/ar_SA/Intents.strings index bf3e77ed..220b144a 100644 --- a/Localization/StringsConvertor/Intents/input/ar_SA/Intents.strings +++ b/Localization/StringsConvertor/Intents/input/ar_SA/Intents.strings @@ -1,10 +1,10 @@ -"16wxgf" = "Post on Mastodon"; +"16wxgf" = "النَشر على ماستودون"; "751xkl" = "محتوى نصي"; "CsR7G2" = "انشر على ماستدون"; -"HZSGTr" = "What content to post?"; +"HZSGTr" = "ما المُحتوى المُراد نشره؟"; "HdGikU" = "Posting failed"; @@ -12,11 +12,11 @@ "RHxKOw" = "Send Post with text content"; -"RxSqsb" = "Post"; +"RxSqsb" = "مَنشور"; "WCIR3D" = "Post ${content} on Mastodon"; -"ZKJSNu" = "Post"; +"ZKJSNu" = "مَنشور"; "ZS1XaK" = "${content}"; From 831b19aedd1dc4fdeaa7746faa7e27b13b1aa8d3 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 26 Oct 2021 06:52:24 +0200 Subject: [PATCH 25/96] New translations Intents.strings (Arabic) --- .../Intents/input/ar_SA/Intents.strings | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Localization/StringsConvertor/Intents/input/ar_SA/Intents.strings b/Localization/StringsConvertor/Intents/input/ar_SA/Intents.strings index 220b144a..cde27dc9 100644 --- a/Localization/StringsConvertor/Intents/input/ar_SA/Intents.strings +++ b/Localization/StringsConvertor/Intents/input/ar_SA/Intents.strings @@ -6,15 +6,15 @@ "HZSGTr" = "ما المُحتوى المُراد نشره؟"; -"HdGikU" = "Posting failed"; +"HdGikU" = "فَشَلَ النشر"; "KDNTJ4" = "سبب الإخفاق"; -"RHxKOw" = "Send Post with text content"; +"RHxKOw" = "إرسال مَنشور يَحوي نص"; "RxSqsb" = "مَنشور"; -"WCIR3D" = "Post ${content} on Mastodon"; +"WCIR3D" = "نَشر ${content} على ماستودون"; "ZKJSNu" = "مَنشور"; @@ -24,13 +24,13 @@ "Zo4jgJ" = "مدى ظهور المنشور"; -"apSxMG-dYQ5NN" = "There are ${count} options matching ‘Public’."; +"apSxMG-dYQ5NN" = "هُناك عدد ${count} خِيار مُطابق لِـ\"عام\"."; -"apSxMG-ehFLjY" = "There are ${count} options matching ‘Followers Only’."; +"apSxMG-ehFLjY" = "هُناك عدد ${count} خِيار مُطابق لِـ\"المُتابِعُون فقط\"."; -"ayoYEb-dYQ5NN" = "${content}, Public"; +"ayoYEb-dYQ5NN" = "${content}، عام"; -"ayoYEb-ehFLjY" = "${content}, Followers Only"; +"ayoYEb-ehFLjY" = "${content}، المُتابِعُون فقط"; "dUyuGg" = "النشر على ماستدون"; @@ -38,13 +38,13 @@ "ehFLjY" = "لمتابعيك فقط"; -"gfePDu" = "Posting failed. ${failureReason}"; +"gfePDu" = "فَشَلَ النشر، ${failureReason}"; -"k7dbKQ" = "Post was sent successfully."; +"k7dbKQ" = "تمَّ إرسال المنشور بِنجاح."; -"oGiqmY-dYQ5NN" = "Just to confirm, you wanted ‘Public’?"; +"oGiqmY-dYQ5NN" = "للتأكيد، هل تَريد \"عام\"؟"; -"oGiqmY-ehFLjY" = "Just to confirm, you wanted ‘Followers Only’?"; +"oGiqmY-ehFLjY" = "للتأكيد، هل تُريد \"للمُتابِعين فقط\"؟"; "rM6dvp" = "عنوان URL"; From 478004d3cd7a55073068fe69c3409791c5a638da Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 26 Oct 2021 06:52:25 +0200 Subject: [PATCH 26/96] New translations app.json (Arabic) --- Localization/StringsConvertor/input/ar_SA/app.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Localization/StringsConvertor/input/ar_SA/app.json b/Localization/StringsConvertor/input/ar_SA/app.json index ef009776..4bf55d91 100644 --- a/Localization/StringsConvertor/input/ar_SA/app.json +++ b/Localization/StringsConvertor/input/ar_SA/app.json @@ -28,7 +28,7 @@ } }, "edit_profile_failure": { - "title": "Edit Profile Error", + "title": "خطأ في تَحرير الملف الشخصي", "message": "لا يمكن تعديل الملف الشخصي. يُرجى المحاولة مرة أُخرى." }, "sign_out": { @@ -194,12 +194,12 @@ "slogan": "Social networking\nback in your hands." }, "server_picker": { - "title": "Pick a server,\nany server.", + "title": "اِختر خادِم،\nأي خادِم.", "button": { "category": { "all": "الكل", "all_accessiblity_description": "الفئة: الكل", - "academia": "academia", + "academia": "أكاديمي", "activism": "للنشطاء", "food": "الطعام", "furry": "فروي", From 8d6d075140c660f2efa93acceba67f31ac5ed1c3 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 26 Oct 2021 18:44:41 +0200 Subject: [PATCH 27/96] New translations app.json (Kurmanji (Kurdish)) --- .../StringsConvertor/input/kmr_TR/app.json | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/Localization/StringsConvertor/input/kmr_TR/app.json b/Localization/StringsConvertor/input/kmr_TR/app.json index b2db1adb..1514332e 100644 --- a/Localization/StringsConvertor/input/kmr_TR/app.json +++ b/Localization/StringsConvertor/input/kmr_TR/app.json @@ -207,72 +207,72 @@ "general": "giştî", "journalism": "rojnamevanî", "lgbt": "lgbt", - "regional": "regional", - "art": "art", - "music": "music", - "tech": "tech" + "regional": "herêmî", + "art": "huner", + "music": "muzîk", + "tech": "teknolojî" }, - "see_less": "See Less", - "see_more": "See More" + "see_less": "Kêmtir bibîne", + "see_more": "Bêtir bibîne" }, "label": { - "language": "LANGUAGE", - "users": "USERS", - "category": "CATEGORY" + "language": "ZIMAN", + "users": "BIKARHÊNER", + "category": "KATEGORÎ" }, "input": { - "placeholder": "Find a server or join your own..." + "placeholder": "Serverek bibînin an jî beşdarî ya xwe bibin..." }, "empty_state": { - "finding_servers": "Finding available servers...", - "bad_network": "Something went wrong while loading the data. Check your internet connection.", - "no_results": "No results" + "finding_servers": "Dîtina serverên berdest...", + "bad_network": "Di dema barkirina daneyan da tiştek xelet derket. Girêdana xwe ya înternetê kontrol bike.", + "no_results": "Encam nade" } }, "register": { - "title": "Tell us about you.", + "title": "Ji me re hinekî qala xwe bike.", "input": { "avatar": { - "delete": "Delete" + "delete": "Jê bibe" }, "username": { - "placeholder": "username", - "duplicate_prompt": "This username is taken." + "placeholder": "navê bikarhêner", + "duplicate_prompt": "Navê vê bikarhêner tê girtin." }, "display_name": { - "placeholder": "display name" + "placeholder": "navê nîşanê" }, "email": { - "placeholder": "email" + "placeholder": "e-name" }, "password": { - "placeholder": "password", - "hint": "Your password needs at least eight characters" + "placeholder": "şîfre", + "hint": "Şîfreya we herî kêm heşt tîpan hewce dike" }, "invite": { - "registration_user_invite_request": "Why do you want to join?" + "registration_user_invite_request": "Tu çima dixwazî beşdar bibî?" } }, "error": { "item": { - "username": "Username", - "email": "Email", - "password": "Password", - "agreement": "Agreement", - "locale": "Locale", - "reason": "Reason" + "username": "Navê bikarhêner", + "email": "E-name", + "password": "Şîfre", + "agreement": "Lihevhatin", + "locale": "Herêm", + "reason": "Sedem" }, "reason": { - "blocked": "%s contains a disallowed email provider", - "unreachable": "%s does not seem to exist", - "taken": "%s is already in use", - "reserved": "%s is a reserved keyword", - "accepted": "%s must be accepted", - "blank": "%s is required", - "invalid": "%s is invalid", - "too_long": "%s is too long", - "too_short": "%s is too short", - "inclusion": "%s is not a supported value" + "blocked": "%s peydekerê e-nameya bêdestûr dihewîne", + "unreachable": "%s xuya nake", + "taken": "%s jixwe tê bikaranîn", + "reserved": "%s peyveke mifteya veqetandî ye", + "accepted": "%s divê were qebûlkirin", + "blank": "%s pêwist e", + "invalid": "%s ne derbasdar e", + "too_long": "%s gelekî dirêj e", + "too_short": "%s pir kurt e", + "inclusion": "%s nirxeke ku tê destekirin nîn e" }, "special": { "username_invalid": "Username must only contain alphanumeric characters and underscores", From 4c893a168d6c73c1dc7e31ea758a6ca0d7abb84b Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 26 Oct 2021 19:55:32 +0200 Subject: [PATCH 28/96] New translations app.json (Kurmanji (Kurdish)) --- .../StringsConvertor/input/kmr_TR/app.json | 122 +++++++++--------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/Localization/StringsConvertor/input/kmr_TR/app.json b/Localization/StringsConvertor/input/kmr_TR/app.json index 1514332e..92600262 100644 --- a/Localization/StringsConvertor/input/kmr_TR/app.json +++ b/Localization/StringsConvertor/input/kmr_TR/app.json @@ -275,25 +275,25 @@ "inclusion": "%s nirxeke ku tê destekirin nîn e" }, "special": { - "username_invalid": "Username must only contain alphanumeric characters and underscores", - "username_too_long": "Username is too long (can’t be longer than 30 characters)", - "email_invalid": "This is not a valid email address", - "password_too_short": "Password is too short (must be at least 8 characters)" + "username_invalid": "Navê bikarhêner divê tenê tîpên alfanumerîk û binxet hebe", + "username_too_long": "Navê bikarhêner pir dirêj e (ji 30 tîpan dirêjtir nabe)", + "email_invalid": "Ev ne navnîşana e-nameyek derbasdar e", + "password_too_short": "Şîfre pir kurt e (divê herî kêm 8 tîpan be)" } } }, "server_rules": { - "title": "Some ground rules.", - "subtitle": "These rules are set by the admins of %s.", - "prompt": "By continuing, you’re subject to the terms of service and privacy policy for %s.", - "terms_of_service": "terms of service", - "privacy_policy": "privacy policy", + "title": "Hin qaîdeyên bingehîn.", + "subtitle": "Ev rêzik ji aliyê rêvebirên %s ve tên sazkirin.", + "prompt": "Bi berdewamî, hûn ji bo %s di bin şertên polîtîkaya xizmet û nepenîtiyê da ne.", + "terms_of_service": "şert û mercên xizmetê", + "privacy_policy": "polîtîkaya nepenîtiyê", "button": { - "confirm": "I Agree" + "confirm": "Ez tev dibim" } }, "confirm_email": { - "title": "One last thing.", + "title": "Tiştekî dawî.", "subtitle": "We just sent an email to %s,\ntap the link to confirm your account.", "button": { "open_email_app": "Open Email App", @@ -372,101 +372,101 @@ "remove_poll": "Remove Poll", "custom_emoji_picker": "Custom Emoji Picker", "enable_content_warning": "Enable Content Warning", - "disable_content_warning": "Disable Content Warning", - "post_visibility_menu": "Post Visibility Menu" + "disable_content_warning": "Hişyariya naverokê neçalak bike", + "post_visibility_menu": "Menuya Xuyabûna Şandiyê" }, "keyboard": { - "discard_post": "Discard Post", - "publish_post": "Publish Post", - "toggle_poll": "Toggle Poll", - "toggle_content_warning": "Toggle Content Warning", - "append_attachment_entry": "Add Attachment - %s", - "select_visibility_entry": "Select Visibility - %s" + "discard_post": "Şandî bihelîne", + "publish_post": "Şandiye bide weşan", + "toggle_poll": "Anketê veke/bigire", + "toggle_content_warning": "Hişyariya naverokê veke/bigire", + "append_attachment_entry": "Pêvek lê zêde bike - %s", + "select_visibility_entry": "Xuyanîbûn hilbijêre - %s" } }, "profile": { "dashboard": { - "posts": "posts", - "following": "following", - "followers": "followers" + "posts": "şandîyan", + "following": "dişopîne", + "followers": "şopîneran" }, "fields": { - "add_row": "Add Row", + "add_row": "Rêzê lê zêde bike", "placeholder": { - "label": "Label", - "content": "Content" + "label": "Nîşan", + "content": "Naverok" } }, "segmented_control": { - "posts": "Posts", - "replies": "Replies", - "media": "Media" + "posts": "Şandîyan", + "replies": "Bersivan", + "media": "Medya" }, "relationship_action_alert": { "confirm_unmute_user": { - "title": "Unmute Account", - "message": "Confirm to unmute %s" + "title": "Hesabê ji bê deng rake", + "message": "Ji bo vekirina bê dengkirinê bipejirin %s" }, "confirm_unblock_usre": { - "title": "Unblock Account", - "message": "Confirm to unblock %s" + "title": "Hesabê ji bloke rake", + "message": "Ji bo rakirina blokê bipejirin %s" } } }, "search": { - "title": "Search", + "title": "Bigere", "search_bar": { - "placeholder": "Search hashtags and users", - "cancel": "Cancel" + "placeholder": "Li etîketan û bikarhêneran bigerin", + "cancel": "Betal kirin" }, "recommend": { - "button_text": "See All", + "button_text": "Hemûyé bibîne", "hash_tag": { - "title": "Trending on Mastodon", - "description": "Hashtags that are getting quite a bit of attention", - "people_talking": "%s people are talking" + "title": "Trend li ser Mastodon", + "description": "Etîketên ku pir balê dikişînin", + "people_talking": "%s kes diaxivin" }, "accounts": { - "title": "Accounts you might like", - "description": "You may like to follow these accounts", - "follow": "Follow" + "title": "Hesabên ku hûn dikarin hez bikin", + "description": "Dibe ku tu bixwazî van hesaban bişopînî", + "follow": "Bişopîne" } }, "searching": { "segment": { - "all": "All", - "people": "People", - "hashtags": "Hashtags", - "posts": "Posts" + "all": "Hemû", + "people": "Mirov", + "hashtags": "Etîketan", + "posts": "Şandîyan" }, "empty_state": { - "no_results": "No results" + "no_results": "Encam tune" }, - "recent_search": "Recent searches", - "clear": "Clear" + "recent_search": "Lêgerînên dawî", + "clear": "Paqij bike" } }, "favorite": { - "title": "Your Favorites" + "title": "Bijareyên te" }, "notification": { "title": { - "Everything": "Everything", - "Mentions": "Mentions" + "Everything": "Her tişt", + "Mentions": "Behs" }, - "user_followed_you": "%s followed you", - "user_favorited your post": "%s favorited your post", - "user_reblogged_your_post": "%s reblogged your post", - "user_mentioned_you": "%s mentioned you", - "user_requested_to_follow_you": "%s requested to follow you", - "user_your_poll_has_ended": "%s Your poll has ended", + "user_followed_you": "%s te şopand", + "user_favorited your post": "%s posta we bijarte", + "user_reblogged_your_post": "%s posta we ji nû ve tomar kir", + "user_mentioned_you": "%s behsa te kir", + "user_requested_to_follow_you": "%s daxwaza şopandina te kir", + "user_your_poll_has_ended": "%s Anketa te qediya", "keyobard": { - "show_everything": "Show Everything", - "show_mentions": "Show Mentions" + "show_everything": "Her tiştî nîşan bide", + "show_mentions": "Behskirîya nîşan bike" } }, "thread": { - "back_title": "Post", + "back_title": "Şandî", "title": "Post from %s" }, "settings": { From 49955a638625ae65f9989726246af226e72b3822 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 26 Oct 2021 19:55:33 +0200 Subject: [PATCH 29/96] New translations Intents.strings (Kurmanji (Kurdish)) --- .../Intents/input/kmr_TR/Intents.strings | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.strings b/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.strings index 687cac69..3e1c69fc 100644 --- a/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.strings +++ b/Localization/StringsConvertor/Intents/input/kmr_TR/Intents.strings @@ -24,28 +24,28 @@ "Zo4jgJ" = "Xuyaniya şandiyê"; -"apSxMG-dYQ5NN" = "There are ${count} options matching ‘Public’."; +"apSxMG-dYQ5NN" = "Vebijarkên ${count} hene ku li gorî 'Giştî' ne."; -"apSxMG-ehFLjY" = "There are ${count} options matching ‘Followers Only’."; +"apSxMG-ehFLjY" = "Vebijarkên ${count} hene ku li gorî 'Tenê Şopandin' hene."; -"ayoYEb-dYQ5NN" = "${content}, Public"; +"ayoYEb-dYQ5NN" = "${content}, Giştî"; -"ayoYEb-ehFLjY" = "${content}, Followers Only"; +"ayoYEb-ehFLjY" = "${content}, Tenê şopînêr"; -"dUyuGg" = "Post on Mastodon"; +"dUyuGg" = "Li ser Mastodon bişînin"; "dYQ5NN" = "Gelemperî"; "ehFLjY" = "Tenê şopîneran"; -"gfePDu" = "Posting failed. ${failureReason}"; +"gfePDu" = "Weşandin bi ser neket. ${failureReason}"; -"k7dbKQ" = "Post was sent successfully."; +"k7dbKQ" = "Şandî bi serkeftî hate şandin."; -"oGiqmY-dYQ5NN" = "Just to confirm, you wanted ‘Public’?"; +"oGiqmY-dYQ5NN" = "Tenê ji bo pejirandinê, we 'Giştî' dixwest?"; -"oGiqmY-ehFLjY" = "Just to confirm, you wanted ‘Followers Only’?"; +"oGiqmY-ehFLjY" = "Tenê ji bo piştrastkirinê, we 'Tenê Şopdarên' dixwest?"; "rM6dvp" = "Girêdan"; -"ryJLwG" = "Post was sent successfully. "; +"ryJLwG" = "Bi serkeftî hat şandin. "; From 8d9ac28d810cd2d3b2906883ac1e62b004086063 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 27 Oct 2021 12:17:22 +0200 Subject: [PATCH 30/96] New translations app.json (Scottish Gaelic) --- Localization/StringsConvertor/input/gd_GB/app.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Localization/StringsConvertor/input/gd_GB/app.json b/Localization/StringsConvertor/input/gd_GB/app.json index 35f551fe..a73925bb 100644 --- a/Localization/StringsConvertor/input/gd_GB/app.json +++ b/Localization/StringsConvertor/input/gd_GB/app.json @@ -536,14 +536,14 @@ } }, "account_list": { - "tab_bar_hint": "Current selected profile: %s. Double tap then hold to show account switcher", - "dismiss_account_switcher": "Dismiss Account Switcher", - "add_account": "Add Account" + "tab_bar_hint": "A’ phròifil air a taghadh: %s. Thoir gnogag dhùbailte is cùm sìos a ghearradh leum gu cunntas eile", + "dismiss_account_switcher": "Leig seachad taghadh a’ chunntais", + "add_account": "Cuir cunntas ris" }, "wizard": { - "new_in_mastodon": "New in Mastodon", - "multiple_account_switch_intro_description": "Switch between multiple accounts by holding the profile button.", - "accessibility_hint": "Double tap to dismiss this wizard" + "new_in_mastodon": "Na tha ùr ann am Mastodon", + "multiple_account_switch_intro_description": "Geàrr leum eadar iomadh cunntas le cumail sìos putan na pròifil.", + "accessibility_hint": "Thoir gnogag dhùbailte a’ leigeil seachad an draoidh seo" } } } \ No newline at end of file From c13d1e2c0d0fe9041f590d8ec09f778b185a5c87 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 27 Oct 2021 12:17:24 +0200 Subject: [PATCH 31/96] New translations Localizable.stringsdict (Scottish Gaelic) --- .../StringsConvertor/input/gd_GB/Localizable.stringsdict | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Localization/StringsConvertor/input/gd_GB/Localizable.stringsdict b/Localization/StringsConvertor/input/gd_GB/Localizable.stringsdict index 41e592a5..7a54f553 100644 --- a/Localization/StringsConvertor/input/gd_GB/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/gd_GB/Localizable.stringsdict @@ -13,13 +13,13 @@ NSStringFormatValueTypeKey ld one - 1 unread notification + %ld bhrath nach deach a leughadh two - %ld unread notification + %ld bhrath nach deach a leughadh few - %ld unread notification + %ld brathan nach deach a leughadh other - %ld unread notification + %ld brath nach deach a leughadh a11y.plural.count.input_limit_exceeds From f778d37ea20f2b4ba31323c15b2c9eaeb298332a Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 27 Oct 2021 16:26:41 +0200 Subject: [PATCH 32/96] New translations app.json (Kurmanji (Kurdish)) --- Localization/StringsConvertor/input/kmr_TR/app.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Localization/StringsConvertor/input/kmr_TR/app.json b/Localization/StringsConvertor/input/kmr_TR/app.json index 92600262..a2b99012 100644 --- a/Localization/StringsConvertor/input/kmr_TR/app.json +++ b/Localization/StringsConvertor/input/kmr_TR/app.json @@ -296,13 +296,13 @@ "title": "Tiştekî dawî.", "subtitle": "We just sent an email to %s,\ntap the link to confirm your account.", "button": { - "open_email_app": "Open Email App", - "dont_receive_email": "I never got an email" + "open_email_app": "Sepana e-nameyê veke", + "dont_receive_email": "Min hîç e-nameyeke nesitand" }, "dont_receive_email": { - "title": "Check your email", - "description": "Check if your email address is correct as well as your junk folder if you haven’t.", - "resend_email": "Resend Email" + "title": "E-nameyê xwe kontrol bike", + "description": "Kontrol bike ka navnîşana e-nameya te rast e û her wiha peldanka xwe ya spam.", + "resend_email": "E-namyê yê dîsa bişîne" }, "open_email_app": { "title": "Check your inbox.", From 62a932825e1958f984623c224d6dcc73251f9695 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 27 Oct 2021 17:23:26 +0200 Subject: [PATCH 33/96] New translations app.json (Kurmanji (Kurdish)) --- .../StringsConvertor/input/kmr_TR/app.json | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/Localization/StringsConvertor/input/kmr_TR/app.json b/Localization/StringsConvertor/input/kmr_TR/app.json index a2b99012..45c224fa 100644 --- a/Localization/StringsConvertor/input/kmr_TR/app.json +++ b/Localization/StringsConvertor/input/kmr_TR/app.json @@ -307,69 +307,69 @@ "open_email_app": { "title": "Check your inbox.", "description": "We just sent you an email. Check your junk folder if you haven’t.", - "mail": "Mail", - "open_email_client": "Open Email Client" + "mail": "E-name", + "open_email_client": "Rajegirê e-nameyê veke" } }, "home_timeline": { - "title": "Home", + "title": "Serrûpel", "navigation_bar_state": { - "offline": "Offline", - "new_posts": "See new posts", - "published": "Published!", - "Publishing": "Publishing post..." + "offline": "Derhêl", + "new_posts": "Şandiyên nû bibîne", + "published": "Hate weşandin!", + "Publishing": "Şandî tê weşandin..." } }, "suggestion_account": { - "title": "Find People to Follow", - "follow_explain": "When you follow someone, you’ll see their posts in your home feed." + "title": "Kesên bo ku bişopînî bibîne", + "follow_explain": "Gava tu kesekî dişopînî, tu yê şandiyê wan di serrûpelê de bibîne." }, "compose": { "title": { - "new_post": "New Post", - "new_reply": "New Reply" + "new_post": "Şandiya nû", + "new_reply": "Bersiva nû" }, "media_selection": { - "camera": "Take Photo", - "photo_library": "Photo Library", - "browse": "Browse" + "camera": "Wêne bikişîne", + "photo_library": "Wênegeh", + "browse": "Bigere" }, "content_input_placeholder": "Type or paste what’s on your mind", - "compose_action": "Publish", - "replying_to_user": "replying to %s", + "compose_action": "Biweşîne", + "replying_to_user": "bersiv bide %s", "attachment": { - "photo": "photo", - "video": "video", - "attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.", + "photo": "wêne", + "video": "vîdyo", + "attachment_broken": "Ev %s naxebite û nayê barkirin\n li ser Mastodon.", "description_photo": "Describe the photo for the visually-impaired...", "description_video": "Describe the video for the visually-impaired..." }, "poll": { - "duration_time": "Duration: %s", - "thirty_minutes": "30 minutes", - "one_hour": "1 Hour", - "six_hours": "6 Hours", - "one_day": "1 Day", - "three_days": "3 Days", - "seven_days": "7 Days", - "option_number": "Option %ld" + "duration_time": "Dirêjî: %s", + "thirty_minutes": "30 xulek", + "one_hour": "1 Demjimêr", + "six_hours": "6 Demjimêr", + "one_day": "1 Roj", + "three_days": "3 Roj", + "seven_days": "7 Roj", + "option_number": "Vebijêrk %ld" }, "content_warning": { "placeholder": "Write an accurate warning here..." }, "visibility": { - "public": "Public", - "unlisted": "Unlisted", - "private": "Followers only", - "direct": "Only people I mention" + "public": "Gelemperî", + "unlisted": "Nerêzokkirî", + "private": "Tenê şopîneran", + "direct": "Tenê mirovên ku min qalkirî" }, "auto_complete": { "space_to_add": "Space to add" }, "accessibility": { - "append_attachment": "Add Attachment", - "append_poll": "Add Poll", - "remove_poll": "Remove Poll", + "append_attachment": "Pêvek tevlî bike", + "append_poll": "Rapirsî tevlî bike", + "remove_poll": "Rapirsî rake", "custom_emoji_picker": "Custom Emoji Picker", "enable_content_warning": "Enable Content Warning", "disable_content_warning": "Hişyariya naverokê neçalak bike", From 59faa09fe2268dd20ff378d110c4e7bab26f298d Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 27 Oct 2021 19:02:52 +0200 Subject: [PATCH 34/96] New translations app.json (Kurmanji (Kurdish)) --- .../StringsConvertor/input/kmr_TR/app.json | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Localization/StringsConvertor/input/kmr_TR/app.json b/Localization/StringsConvertor/input/kmr_TR/app.json index 45c224fa..5821049b 100644 --- a/Localization/StringsConvertor/input/kmr_TR/app.json +++ b/Localization/StringsConvertor/input/kmr_TR/app.json @@ -470,30 +470,30 @@ "title": "Post from %s" }, "settings": { - "title": "Settings", + "title": "Sazkarî", "section": { "appearance": { - "title": "Appearance", - "automatic": "Automatic", - "light": "Always Light", - "dark": "Always Dark" + "title": "Xuyang", + "automatic": "Xweber", + "light": "Her dem ronî", + "dark": "Her dem tarî" }, "notifications": { - "title": "Notifications", - "favorites": "Favorites my post", - "follows": "Follows me", + "title": "Agahdarî", + "favorites": "Şandiyên min hez kir", + "follows": "Min şopand", "boosts": "Reblogs my post", - "mentions": "Mentions me", + "mentions": "Qale min kir", "trigger": { - "anyone": "anyone", - "follower": "a follower", - "follow": "anyone I follow", - "noone": "no one", - "title": "Notify me when" + "anyone": "her kes", + "follower": "şopînerek", + "follow": "her kesê ku dişopînim", + "noone": "ne yek", + "title": "Min agahdar bike gava" } }, "preference": { - "title": "Preferences", + "title": "Hilbijarte", "true_black_dark_mode": "True black dark mode", "disable_avatar_animation": "Disable animated avatars", "disable_emoji_animation": "Disable animated emojis", From bc10d782870450f67b437fa7d8673dd53bc910ee Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 27 Oct 2021 20:16:43 +0200 Subject: [PATCH 35/96] New translations app.json (Kurmanji (Kurdish)) --- .../StringsConvertor/input/kmr_TR/app.json | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Localization/StringsConvertor/input/kmr_TR/app.json b/Localization/StringsConvertor/input/kmr_TR/app.json index 5821049b..9798c86c 100644 --- a/Localization/StringsConvertor/input/kmr_TR/app.json +++ b/Localization/StringsConvertor/input/kmr_TR/app.json @@ -519,30 +519,30 @@ } }, "report": { - "title": "Report %s", - "step1": "Step 1 of 2", - "step2": "Step 2 of 2", + "title": "%s ragihîne", + "step1": "Gav 1 ji 2", + "step2": "Gav 2 ji 2", "content1": "Are there any other posts you’d like to add to the report?", "content2": "Is there anything the moderators should know about this report?", - "send": "Send Report", - "skip_to_send": "Send without comment", + "send": "Ragihandinê bişîne", + "skip_to_send": "Bêyî şirove bişîne", "text_placeholder": "Type or paste additional comments" }, "preview": { "keyboard": { - "close_preview": "Close Preview", - "show_next": "Show Next", - "show_previous": "Show Previous" + "close_preview": "Pêşdîtin bigire", + "show_next": "A pêş nîşan bide", + "show_previous": "A paş nîşan bide" } }, "account_list": { - "tab_bar_hint": "Current selected profile: %s. Double tap then hold to show account switcher", + "tab_bar_hint": "Profîla hilbijartî ya niha: %s. Du caran bitikîne û paşê dest bide ser da ku guhêrbara ajimêr were nîşandan", "dismiss_account_switcher": "Dismiss Account Switcher", - "add_account": "Add Account" + "add_account": "Ajimêr tevlî bike" }, "wizard": { - "new_in_mastodon": "New in Mastodon", - "multiple_account_switch_intro_description": "Switch between multiple accounts by holding the profile button.", + "new_in_mastodon": "Nû di Mastodon de", + "multiple_account_switch_intro_description": "Dest bide ser bişkoja profîlê da ku di navbera gelek ajimêrann de biguherînî.", "accessibility_hint": "Double tap to dismiss this wizard" } } From b1f3729f56450fde3a56b99d7341c488b63f63d9 Mon Sep 17 00:00:00 2001 From: CMK Date: Thu, 28 Oct 2021 19:33:29 +0800 Subject: [PATCH 36/96] chore: update version to 1.2.0 (80) --- AppShared/Info.plist | 2 +- CoreDataStack/Info.plist | 2 +- CoreDataStackTests/Info.plist | 2 +- Mastodon.xcodeproj/project.pbxproj | 64 +++++++++---------- .../xcschemes/xcschememanagement.plist | 8 +-- .../xcshareddata/swiftpm/Package.resolved | 9 --- Mastodon/Info.plist | 2 +- MastodonIntent/Info.plist | 2 +- MastodonTests/Info.plist | 2 +- MastodonUITests/Info.plist | 2 +- NotificationService/Info.plist | 2 +- ShareActionExtension/Info.plist | 2 +- 12 files changed, 45 insertions(+), 54 deletions(-) diff --git a/AppShared/Info.plist b/AppShared/Info.plist index 7f509a3f..cf50d3ac 100644 --- a/AppShared/Info.plist +++ b/AppShared/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 79 + 80 diff --git a/CoreDataStack/Info.plist b/CoreDataStack/Info.plist index 7f509a3f..cf50d3ac 100644 --- a/CoreDataStack/Info.plist +++ b/CoreDataStack/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 79 + 80 diff --git a/CoreDataStackTests/Info.plist b/CoreDataStackTests/Info.plist index 7f509a3f..cf50d3ac 100644 --- a/CoreDataStackTests/Info.plist +++ b/CoreDataStackTests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 79 + 80 diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 1ab4b255..f7b373f5 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -4800,7 +4800,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4829,7 +4829,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4937,11 +4937,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 79; + DYLIB_CURRENT_VERSION = 80; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -4968,11 +4968,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 79; + DYLIB_CURRENT_VERSION = 80; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -4997,11 +4997,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 79; + DYLIB_CURRENT_VERSION = 80; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = CoreDataStack/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5027,11 +5027,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 79; + DYLIB_CURRENT_VERSION = 80; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = CoreDataStack/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5094,7 +5094,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5119,7 +5119,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5144,7 +5144,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5169,7 +5169,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5194,7 +5194,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5219,7 +5219,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5244,7 +5244,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5269,7 +5269,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5360,7 +5360,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -5427,11 +5427,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 79; + DYLIB_CURRENT_VERSION = 80; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = CoreDataStack/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5476,7 +5476,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5501,11 +5501,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 79; + DYLIB_CURRENT_VERSION = 80; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5597,7 +5597,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -5664,11 +5664,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 79; + DYLIB_CURRENT_VERSION = 80; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = CoreDataStack/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5713,7 +5713,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5738,11 +5738,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 79; + DYLIB_CURRENT_VERSION = 80; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5768,7 +5768,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5792,7 +5792,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 79; + CURRENT_PROJECT_VERSION = 80; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index e6093a21..cb88c396 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ AppShared.xcscheme_^#shared#^_ orderHint - 35 + 42 CoreDataStack.xcscheme_^#shared#^_ orderHint - 38 + 43 Mastodon - ASDK.xcscheme_^#shared#^_ @@ -97,7 +97,7 @@ MastodonIntent.xcscheme_^#shared#^_ orderHint - 36 + 44 MastodonIntents.xcscheme_^#shared#^_ @@ -117,7 +117,7 @@ ShareActionExtension.xcscheme_^#shared#^_ orderHint - 37 + 41 SuppressBuildableAutocreation diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved index 26ba58d5..b305c815 100644 --- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -216,15 +216,6 @@ "revision": "dad97167bf1be16aeecd109130900995dd01c515", "version": "2.6.0" } - }, - { - "package": "UITextView+Placeholder", - "repositoryURL": "https://github.com/MainasuK/UITextView-Placeholder", - "state": { - "branch": null, - "revision": "20f513ded04a040cdf5467f0891849b1763ede3b", - "version": "1.4.1" - } } ] }, diff --git a/Mastodon/Info.plist b/Mastodon/Info.plist index 43ae9e6d..1c89f12a 100644 --- a/Mastodon/Info.plist +++ b/Mastodon/Info.plist @@ -30,7 +30,7 @@ CFBundleVersion - 79 + 80 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/MastodonIntent/Info.plist b/MastodonIntent/Info.plist index 714ba310..062a4bf1 100644 --- a/MastodonIntent/Info.plist +++ b/MastodonIntent/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 79 + 80 NSExtension NSExtensionAttributes diff --git a/MastodonTests/Info.plist b/MastodonTests/Info.plist index 7f509a3f..cf50d3ac 100644 --- a/MastodonTests/Info.plist +++ b/MastodonTests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 79 + 80 diff --git a/MastodonUITests/Info.plist b/MastodonUITests/Info.plist index 7f509a3f..cf50d3ac 100644 --- a/MastodonUITests/Info.plist +++ b/MastodonUITests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 79 + 80 diff --git a/NotificationService/Info.plist b/NotificationService/Info.plist index b5ee7a30..fb52c9dc 100644 --- a/NotificationService/Info.plist +++ b/NotificationService/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 79 + 80 NSExtension NSExtensionPointIdentifier diff --git a/ShareActionExtension/Info.plist b/ShareActionExtension/Info.plist index 5643d878..8870b320 100644 --- a/ShareActionExtension/Info.plist +++ b/ShareActionExtension/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 79 + 80 NSExtension NSExtensionAttributes From c6fc5cc09dd48f43017cad33f00c1dee87af51df Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 29 Oct 2021 14:58:09 +0800 Subject: [PATCH 37/96] feat: update to new UI tint style --- .../Scene/Compose/View/ComposeToolbarView.swift | 2 +- .../HashtagTimelineViewController.swift | 2 +- .../HomeTimeline/HomeTimelineViewController.swift | 4 ++-- Mastodon/Scene/Report/ReportViewController.swift | 2 +- .../View/SidebarListCollectionViewCell.swift | 10 ++-------- .../Sidebar/View/SidebarListContentView.swift | 15 ++++++++------- .../Scene/Share/View/Content/StatusView.swift | 2 +- .../Scene/Share/View/Content/ThreadMetaView.swift | 8 ++++---- .../ThreadReplyLoaderTableViewCell.swift | 2 +- .../TimelineLoaderTableViewCell.swift | 4 ++-- Mastodon/Service/ThemeService/MastodonTheme.swift | 2 +- Mastodon/Service/ThemeService/SystemTheme.swift | 2 +- .../ThemeService/ThemeService+Appearance.swift | 6 +++--- Mastodon/Service/ThemeService/ThemeService.swift | 2 ++ Mastodon/Supporting Files/SceneDelegate.swift | 2 +- .../Scene/View/ComposeToolbarView.swift | 2 +- 16 files changed, 32 insertions(+), 35 deletions(-) diff --git a/Mastodon/Scene/Compose/View/ComposeToolbarView.swift b/Mastodon/Scene/Compose/View/ComposeToolbarView.swift index 373ce1a1..6b06973a 100644 --- a/Mastodon/Scene/Compose/View/ComposeToolbarView.swift +++ b/Mastodon/Scene/Compose/View/ComposeToolbarView.swift @@ -262,7 +262,7 @@ extension ComposeToolbarView { } private static func configureToolbarButtonAppearance(button: UIButton) { - button.tintColor = Asset.Colors.brandBlue.color + button.tintColor = ThemeService.tintColor button.setBackgroundImage(.placeholder(size: ComposeToolbarView.toolbarButtonSize, color: .systemFill), for: .highlighted) button.layer.masksToBounds = true button.layer.cornerRadius = 5 diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift index 9801d701..72f084fa 100644 --- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift +++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift @@ -24,7 +24,7 @@ class HashtagTimelineViewController: UIViewController, NeedsDependency, MediaPre let composeBarButtonItem: UIBarButtonItem = { let barButtonItem = UIBarButtonItem() - barButtonItem.tintColor = Asset.Colors.brandBlue.color + // barButtonItem.tintColor = Asset.Colors.brandBlue.color barButtonItem.image = UIImage(systemName: "square.and.pencil")?.withRenderingMode(.alwaysTemplate) return barButtonItem }() diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift index 55bf544c..6b547688 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift @@ -46,14 +46,14 @@ final class HomeTimelineViewController: UIViewController, NeedsDependency, Media let settingBarButtonItem: UIBarButtonItem = { let barButtonItem = UIBarButtonItem() - barButtonItem.tintColor = Asset.Colors.brandBlue.color + barButtonItem.tintColor = ThemeService.tintColor barButtonItem.image = UIImage(systemName: "gear")?.withRenderingMode(.alwaysTemplate) return barButtonItem }() let composeBarButtonItem: UIBarButtonItem = { let barButtonItem = UIBarButtonItem() - barButtonItem.tintColor = Asset.Colors.brandBlue.color + barButtonItem.tintColor = ThemeService.tintColor barButtonItem.image = UIImage(systemName: "square.and.pencil")?.withRenderingMode(.alwaysTemplate) return barButtonItem }() diff --git a/Mastodon/Scene/Report/ReportViewController.swift b/Mastodon/Scene/Report/ReportViewController.swift index efaa533e..6a7161c9 100644 --- a/Mastodon/Scene/Report/ReportViewController.swift +++ b/Mastodon/Scene/Report/ReportViewController.swift @@ -251,7 +251,7 @@ class ReportViewController: UIViewController, NeedsDependency { = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.cancel, target: self, action: #selector(doneButtonDidClick)) - navigationItem.rightBarButtonItem?.tintColor = Asset.Colors.brandBlue.color + navigationItem.rightBarButtonItem?.tintColor = ThemeService.tintColor // fetch old mastodon user let beReportedUser: MastodonUser? = { diff --git a/Mastodon/Scene/Root/Sidebar/View/SidebarListCollectionViewCell.swift b/Mastodon/Scene/Root/Sidebar/View/SidebarListCollectionViewCell.swift index dc71eadf..998d3f9e 100644 --- a/Mastodon/Scene/Root/Sidebar/View/SidebarListCollectionViewCell.swift +++ b/Mastodon/Scene/Root/Sidebar/View/SidebarListCollectionViewCell.swift @@ -51,15 +51,9 @@ extension SidebarListCollectionViewCell { newConfiguration.item = item contentConfiguration = newConfiguration + // remove background var newBackgroundConfiguration = UIBackgroundConfiguration.listSidebarCell().updated(for: state) - // Customize the background color to use the tint color when the cell is highlighted or selected. - if state.isSelected || state.isHighlighted { - newBackgroundConfiguration.backgroundColor = Asset.Colors.brandBlue.color - } - if state.isHighlighted { - newBackgroundConfiguration.backgroundColorTransformer = .init { $0.withAlphaComponent(0.8) } - } - + newBackgroundConfiguration.backgroundColor = .clear backgroundConfiguration = newBackgroundConfiguration } } diff --git a/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift b/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift index dedd47b8..8c013efd 100644 --- a/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift +++ b/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift @@ -71,8 +71,6 @@ extension SidebarListContentView { imageView.contentMode = .scaleAspectFit avatarButton.contentMode = .scaleAspectFit - imageView.tintColor = Asset.Colors.brandBlue.color - avatarButton.tintColor = Asset.Colors.brandBlue.color } private func apply(configuration: ContentConfiguration) { @@ -84,8 +82,9 @@ extension SidebarListContentView { guard let item = configuration.item else { return } // configure state - imageView.tintColor = item.isSelected ? .white : Asset.Colors.brandBlue.color - avatarButton.tintColor = item.isSelected ? .white : Asset.Colors.brandBlue.color + let tintColor = item.isHighlighted ? ThemeService.tintColor.withAlphaComponent(0.5) : ThemeService.tintColor + imageView.tintColor = tintColor + avatarButton.tintColor = tintColor // configure model imageView.isHidden = item.imageURL != nil @@ -96,9 +95,6 @@ extension SidebarListContentView { placeholder: avatarButton.avatarImageView.image ?? .placeholder(color: .systemFill), // reuse to avoid blink scaleToSize: nil ) - avatarButton.layer.masksToBounds = true - avatarButton.layer.cornerCurve = .continuous - avatarButton.layer.cornerRadius = 4 } } @@ -106,6 +102,7 @@ extension SidebarListContentView { struct Item: Hashable { // state var isSelected: Bool = false + var isHighlighted: Bool = false // model let title: String @@ -114,6 +111,7 @@ extension SidebarListContentView { static func == (lhs: SidebarListContentView.Item, rhs: SidebarListContentView.Item) -> Bool { return lhs.isSelected == rhs.isSelected + && lhs.isHighlighted == rhs.isHighlighted && lhs.title == rhs.title && lhs.image == rhs.image && lhs.imageURL == rhs.imageURL @@ -121,6 +119,7 @@ extension SidebarListContentView { func hash(into hasher: inout Hasher) { hasher.combine(isSelected) + hasher.combine(isHighlighted) hasher.combine(title) hasher.combine(image) imageURL.flatMap { hasher.combine($0) } @@ -143,9 +142,11 @@ extension SidebarListContentView { if let state = state as? UICellConfigurationState { updatedConfiguration.item?.isSelected = state.isHighlighted || state.isSelected + updatedConfiguration.item?.isHighlighted = state.isHighlighted } else { assertionFailure() updatedConfiguration.item?.isSelected = false + updatedConfiguration.item?.isHighlighted = false } return updatedConfiguration diff --git a/Mastodon/Scene/Share/View/Content/StatusView.swift b/Mastodon/Scene/Share/View/Content/StatusView.swift index a2788f52..957764fa 100644 --- a/Mastodon/Scene/Share/View/Content/StatusView.swift +++ b/Mastodon/Scene/Share/View/Content/StatusView.swift @@ -126,7 +126,7 @@ final class StatusView: UIView { let revealContentWarningButton: UIButton = { let button = HighlightDimmableButton() button.setImage(UIImage(systemName: "eye", withConfiguration: UIImage.SymbolConfiguration(pointSize: 17, weight: .medium)), for: .normal) - button.tintColor = Asset.Colors.brandBlue.color + // button.tintColor = Asset.Colors.brandBlue.color return button }() diff --git a/Mastodon/Scene/Share/View/Content/ThreadMetaView.swift b/Mastodon/Scene/Share/View/Content/ThreadMetaView.swift index 4bda525a..c339654f 100644 --- a/Mastodon/Scene/Share/View/Content/ThreadMetaView.swift +++ b/Mastodon/Scene/Share/View/Content/ThreadMetaView.swift @@ -23,8 +23,8 @@ final class ThreadMetaView: UIView { let button = UIButton() button.titleLabel?.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold)) button.setTitle("0 reblog", for: .normal) - button.setTitleColor(Asset.Colors.brandBlue.color, for: .normal) - button.setTitleColor(Asset.Colors.brandBlue.color.withAlphaComponent(0.5), for: .highlighted) + button.setTitleColor(ThemeService.tintColor, for: .normal) + button.setTitleColor(ThemeService.tintColor.withAlphaComponent(0.5), for: .highlighted) return button }() @@ -32,8 +32,8 @@ final class ThreadMetaView: UIView { let button = UIButton() button.titleLabel?.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold)) button.setTitle("0 favorite", for: .normal) - button.setTitleColor(Asset.Colors.brandBlue.color, for: .normal) - button.setTitleColor(Asset.Colors.brandBlue.color.withAlphaComponent(0.5), for: .highlighted) + button.setTitleColor(ThemeService.tintColor, for: .normal) + button.setTitleColor(ThemeService.tintColor.withAlphaComponent(0.5), for: .highlighted) return button }() diff --git a/Mastodon/Scene/Share/View/TableviewCell/ThreadReplyLoaderTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/ThreadReplyLoaderTableViewCell.swift index 5e5ac88d..a819f301 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/ThreadReplyLoaderTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/ThreadReplyLoaderTableViewCell.swift @@ -23,7 +23,7 @@ final class ThreadReplyLoaderTableViewCell: UITableViewCell { let loadMoreButton: UIButton = { let button = HighlightDimmableButton() button.titleLabel?.font = TimelineLoaderTableViewCell.labelFont - button.setTitleColor(Asset.Colors.brandBlue.color, for: .normal) + button.setTitleColor(ThemeService.tintColor, for: .normal) button.setTitle(L10n.Common.Controls.Timeline.Loader.showMoreReplies, for: .normal) return button }() diff --git a/Mastodon/Scene/Share/View/TableviewCell/TimelineLoaderTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/TimelineLoaderTableViewCell.swift index 8c329d31..da0b80fb 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/TimelineLoaderTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/TimelineLoaderTableViewCell.swift @@ -24,7 +24,7 @@ class TimelineLoaderTableViewCell: UITableViewCell { let loadMoreButton: UIButton = { let button = HighlightDimmableButton() button.titleLabel?.font = TimelineLoaderTableViewCell.labelFont - button.setTitleColor(Asset.Colors.brandBlue.color, for: .normal) + button.setTitleColor(ThemeService.tintColor, for: .normal) button.setTitle(L10n.Common.Controls.Timeline.Loader.loadMissingPosts, for: .normal) button.setTitle("", for: .disabled) return button @@ -68,7 +68,7 @@ class TimelineLoaderTableViewCell: UITableViewCell { func stopAnimating() { activityIndicatorView.stopAnimating() self.loadMoreButton.isEnabled = true - self.loadMoreLabel.textColor = Asset.Colors.brandBlue.color + self.loadMoreLabel.textColor = ThemeService.tintColor self.loadMoreLabel.text = "" } diff --git a/Mastodon/Service/ThemeService/MastodonTheme.swift b/Mastodon/Service/ThemeService/MastodonTheme.swift index 85b0d42d..1f0fd4e3 100644 --- a/Mastodon/Service/ThemeService/MastodonTheme.swift +++ b/Mastodon/Service/ThemeService/MastodonTheme.swift @@ -26,7 +26,7 @@ struct MastodonTheme: Theme { let sidebarBackgroundColor = Asset.Theme.Mastodon.sidebarBackground.color let tabBarBackgroundColor = Asset.Theme.Mastodon.tabBarBackground.color - let tabBarItemSelectedIconColor = Asset.Colors.brandBlue.color + let tabBarItemSelectedIconColor = ThemeService.tintColor let tabBarItemFocusedIconColor = Asset.Theme.Mastodon.tabBarItemInactiveIconColor.color let tabBarItemNormalIconColor = Asset.Theme.Mastodon.tabBarItemInactiveIconColor.color let tabBarItemDisabledIconColor = Asset.Theme.Mastodon.tabBarItemInactiveIconColor.color diff --git a/Mastodon/Service/ThemeService/SystemTheme.swift b/Mastodon/Service/ThemeService/SystemTheme.swift index 1ea92397..26673d57 100644 --- a/Mastodon/Service/ThemeService/SystemTheme.swift +++ b/Mastodon/Service/ThemeService/SystemTheme.swift @@ -26,7 +26,7 @@ struct SystemTheme: Theme { let sidebarBackgroundColor = Asset.Theme.System.sidebarBackground.color let tabBarBackgroundColor = Asset.Theme.System.tabBarBackground.color - let tabBarItemSelectedIconColor = Asset.Colors.brandBlue.color + let tabBarItemSelectedIconColor = ThemeService.tintColor let tabBarItemFocusedIconColor = Asset.Theme.System.tabBarItemInactiveIconColor.color let tabBarItemNormalIconColor = Asset.Theme.System.tabBarItemInactiveIconColor.color let tabBarItemDisabledIconColor = Asset.Theme.System.tabBarItemInactiveIconColor.color diff --git a/Mastodon/Service/ThemeService/ThemeService+Appearance.swift b/Mastodon/Service/ThemeService/ThemeService+Appearance.swift index cffa45c7..896ed888 100644 --- a/Mastodon/Service/ThemeService/ThemeService+Appearance.swift +++ b/Mastodon/Service/ThemeService/ThemeService+Appearance.swift @@ -46,7 +46,7 @@ extension ThemeService { tabBarAppearance.compactInlineLayoutAppearance = tabBarItemAppearance tabBarAppearance.backgroundColor = theme.tabBarBackgroundColor - tabBarAppearance.selectionIndicatorTintColor = Asset.Colors.brandBlue.color + tabBarAppearance.selectionIndicatorTintColor = ThemeService.tintColor UITabBar.appearance().standardAppearance = tabBarAppearance if #available(iOS 15.0, *) { UITabBar.appearance().scrollEdgeAppearance = tabBarAppearance @@ -61,9 +61,9 @@ extension ThemeService { UITableViewCell.appearance().selectionColor = theme.tableViewCellSelectionBackgroundColor // set search bar appearance - UISearchBar.appearance().tintColor = Asset.Colors.brandBlue.color + UISearchBar.appearance().tintColor = ThemeService.tintColor UISearchBar.appearance().barTintColor = theme.navigationBarBackgroundColor - let cancelButtonAttributes: [NSAttributedString.Key : Any] = [NSAttributedString.Key.foregroundColor: Asset.Colors.brandBlue.color] + let cancelButtonAttributes: [NSAttributedString.Key : Any] = [NSAttributedString.Key.foregroundColor: ThemeService.tintColor] UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).setTitleTextAttributes(cancelButtonAttributes, for: .normal) } } diff --git a/Mastodon/Service/ThemeService/ThemeService.swift b/Mastodon/Service/ThemeService/ThemeService.swift index 35d5b349..e3bd7c4a 100644 --- a/Mastodon/Service/ThemeService/ThemeService.swift +++ b/Mastodon/Service/ThemeService/ThemeService.swift @@ -10,6 +10,8 @@ import Combine // ref: https://zamzam.io/protocol-oriented-themes-for-ios-apps/ final class ThemeService { + + static let tintColor: UIColor = .label // MARK: - Singleton public static let shared = ThemeService() diff --git a/Mastodon/Supporting Files/SceneDelegate.swift b/Mastodon/Supporting Files/SceneDelegate.swift index 6c5752c4..4809fe5f 100644 --- a/Mastodon/Supporting Files/SceneDelegate.swift +++ b/Mastodon/Supporting Files/SceneDelegate.swift @@ -37,7 +37,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { self.window = window // set tint color - window.tintColor = Asset.Colors.brandBlue.color + window.tintColor = UIColor.label ThemeService.shared.currentTheme .receive(on: RunLoop.main) diff --git a/ShareActionExtension/Scene/View/ComposeToolbarView.swift b/ShareActionExtension/Scene/View/ComposeToolbarView.swift index e6842c74..d88bb018 100644 --- a/ShareActionExtension/Scene/View/ComposeToolbarView.swift +++ b/ShareActionExtension/Scene/View/ComposeToolbarView.swift @@ -190,7 +190,7 @@ extension ComposeToolbarView { extension ComposeToolbarView { private static func configureToolbarButtonAppearance(button: UIButton) { - button.tintColor = Asset.Colors.brandBlue.color + button.tintColor = ThemeService.tintColor button.setBackgroundImage(.placeholder(size: ComposeToolbarView.toolbarButtonSize, color: .systemFill), for: .highlighted) button.layer.masksToBounds = true button.layer.cornerRadius = 5 From 0ec20c6c88784028549829171dec410823f693ad Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 29 Oct 2021 14:58:59 +0800 Subject: [PATCH 38/96] feat: add split view layout update handler --- .../Root/ContentSplitViewController.swift | 2 +- .../Scene/Root/RootSplitViewController.swift | 133 +++++++----------- 2 files changed, 54 insertions(+), 81 deletions(-) diff --git a/Mastodon/Scene/Root/ContentSplitViewController.swift b/Mastodon/Scene/Root/ContentSplitViewController.swift index 42ce3afc..6f5e294c 100644 --- a/Mastodon/Scene/Root/ContentSplitViewController.swift +++ b/Mastodon/Scene/Root/ContentSplitViewController.swift @@ -47,7 +47,7 @@ extension ContentSplitViewController { navigationController?.setNavigationBarHidden(true, animated: false) addChild(sidebarViewController) - sidebarViewController.view.translatesAutoresizingMaskIntoConstraints = false + sidebarViewController.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(sidebarViewController.view) sidebarViewController.didMove(toParent: self) NSLayoutConstraint.activate([ diff --git a/Mastodon/Scene/Root/RootSplitViewController.swift b/Mastodon/Scene/Root/RootSplitViewController.swift index 4f818ea9..fc1e7a4f 100644 --- a/Mastodon/Scene/Root/RootSplitViewController.swift +++ b/Mastodon/Scene/Root/RootSplitViewController.swift @@ -26,6 +26,8 @@ final class RootSplitViewController: UISplitViewController, NeedsDependency { return contentSplitViewController }() + lazy var compactMainTabBarViewController = MainTabBarController(context: context, coordinator: coordinator) + init(context: AppContext, coordinator: SceneCoordinator) { self.context = context self.coordinator = coordinator @@ -48,6 +50,7 @@ final class RootSplitViewController: UISplitViewController, NeedsDependency { setViewController(UIViewController(), for: .primary) setViewController(contentSplitViewController, for: .secondary) + setViewController(compactMainTabBarViewController, for: .compact) contentSplitViewController.sidebarViewController.view.layer.zPosition = 100 contentSplitViewController.mainTabBarController.view.layer.zPosition = 90 @@ -108,85 +111,55 @@ extension RootSplitViewController { // MARK: - UISplitViewControllerDelegate extension RootSplitViewController: UISplitViewControllerDelegate { -// // .regular to .compact -// // move navigation stack from .supplementary & .secondary to .compact -// func splitViewController( -// _ svc: UISplitViewController, -// topColumnForCollapsingToProposedTopColumn proposedTopColumn: UISplitViewController.Column -// ) -> UISplitViewController.Column { -// switch proposedTopColumn { -// case .compact: -// guard let index = MainTabBarController.Tab.allCases.firstIndex(of: currentSupplementaryTab) else { -// assertionFailure() -// break -// } -// mainTabBarController.selectedIndex = index -// mainTabBarController.currentTab.value = currentSupplementaryTab -// -// guard let navigationController = mainTabBarController.selectedViewController as? UINavigationController else { break } -// navigationController.popToRootViewController(animated: false) -// var viewControllers = navigationController.viewControllers // init navigation stack with topMost -// -// if let supplementaryNavigationController = viewController(for: .supplementary) as? UINavigationController { -// // append supplementary -// viewControllers.append(contentsOf: supplementaryNavigationController.popToRootViewController(animated: true) ?? []) -// } -// if let secondaryNavigationController = viewController(for: .secondary) as? UINavigationController { -// // append secondary -// viewControllers.append(contentsOf: secondaryNavigationController.popToRootViewController(animated: true) ?? []) -// } -// // set navigation stack -// navigationController.setViewControllers(viewControllers, animated: false) -// -// default: -// assertionFailure() -// } -// -// return proposedTopColumn -// } -// -// // .compact to .regular -// // restore navigation stack to .supplementary & .secondary -// func splitViewController( -// _ svc: UISplitViewController, -// displayModeForExpandingToProposedDisplayMode proposedDisplayMode: UISplitViewController.DisplayMode -// ) -> UISplitViewController.DisplayMode { -// let compactNavigationController = mainTabBarController.selectedViewController as? UINavigationController -// -// if let topMost = compactNavigationController?.topMost, -// topMost is AccountListViewController { -// topMost.dismiss(animated: false, completion: nil) -// } -// -// let viewControllers = compactNavigationController?.popToRootViewController(animated: true) ?? [] -// -// var supplementaryViewControllers: [UIViewController] = [] -// var secondaryViewControllers: [UIViewController] = [] -// for viewController in viewControllers { -// if coordinator.secondaryStackHashValues.contains(viewController.hashValue) { -// secondaryViewControllers.append(viewController) -// } else { -// supplementaryViewControllers.append(viewController) -// } -// -// } -// if let supplementary = viewController(for: .supplementary) as? UINavigationController { -// supplementary.setViewControllers(supplementary.viewControllers + supplementaryViewControllers, animated: false) -// } -// if let secondaryNavigationController = viewController(for: .secondary) as? UINavigationController { -// secondaryNavigationController.setViewControllers(secondaryNavigationController.viewControllers + secondaryViewControllers, animated: false) -// } -// -// return proposedDisplayMode -// } + private static func transform(from: UITabBarController, to: UITabBarController) { + let sourceNavigationControllers = from.viewControllers ?? [] + let targetNavigationControllers = to.viewControllers ?? [] + + for (source, target) in zip(sourceNavigationControllers, targetNavigationControllers) { + guard let source = source as? UINavigationController, + let target = target as? UINavigationController + else { continue } + let viewControllers = source.popToRootViewController(animated: false) ?? [] + _ = target.popToRootViewController(animated: false) + target.viewControllers.append(contentsOf: viewControllers) + } + + to.selectedIndex = from.selectedIndex + } + + // .regular to .compact + func splitViewController( + _ svc: UISplitViewController, + topColumnForCollapsingToProposedTopColumn proposedTopColumn: UISplitViewController.Column + ) -> UISplitViewController.Column { + switch proposedTopColumn { + case .compact: + RootSplitViewController.transform(from: contentSplitViewController.mainTabBarController, to: compactMainTabBarViewController) + compactMainTabBarViewController.currentTab.value = contentSplitViewController.currentSupplementaryTab + + default: + assertionFailure() + } + + return proposedTopColumn + } + + // .compact to .regular + func splitViewController( + _ svc: UISplitViewController, + displayModeForExpandingToProposedDisplayMode proposedDisplayMode: UISplitViewController.DisplayMode + ) -> UISplitViewController.DisplayMode { + let compactNavigationController = compactMainTabBarViewController.selectedViewController as? UINavigationController + + if let topMost = compactNavigationController?.topMost, + topMost is AccountListViewController { + topMost.dismiss(animated: false, completion: nil) + } + + RootSplitViewController.transform(from: compactMainTabBarViewController, to: contentSplitViewController.mainTabBarController) + contentSplitViewController.currentSupplementaryTab = compactMainTabBarViewController.currentTab.value + + return proposedDisplayMode + } } - -//extension UIView { -// func setNeedsLayoutForSubviews() { -// self.subviews.forEach({ -// $0.setNeedsLayout() -// $0.setNeedsLayoutForSubviews() -// }) -// } -//} From bcddcf226b8e6527e30cd2b45600c3b89a4f36fc Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 29 Oct 2021 17:26:26 +0800 Subject: [PATCH 39/96] feat: add account switcher for iPad sidebar --- .../xcschemes/xcschememanagement.plist | 8 ++-- Mastodon/Coordinator/SceneCoordinator.swift | 6 ++- .../Cell/AddAccountTableViewCell.swift | 26 ++++++++++- .../Root/ContentSplitViewController.swift | 18 +++++++- .../Scene/Root/RootSplitViewController.swift | 33 +++++++++++--- .../Root/Sidebar/SidebarViewController.swift | 44 ++++++++++--------- .../Sidebar/View/SidebarListContentView.swift | 4 ++ 7 files changed, 106 insertions(+), 33 deletions(-) diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index cb88c396..879d9105 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ AppShared.xcscheme_^#shared#^_ orderHint - 42 + 35 CoreDataStack.xcscheme_^#shared#^_ orderHint - 43 + 36 Mastodon - ASDK.xcscheme_^#shared#^_ @@ -97,7 +97,7 @@ MastodonIntent.xcscheme_^#shared#^_ orderHint - 44 + 37 MastodonIntents.xcscheme_^#shared#^_ @@ -117,7 +117,7 @@ ShareActionExtension.xcscheme_^#shared#^_ orderHint - 41 + 38 SuppressBuildableAutocreation diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index 23c2a6f4..696b211b 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -139,6 +139,7 @@ extension SceneCoordinator { case show // push case showDetail // replace case modal(animated: Bool, completion: (() -> Void)? = nil) + case popover(sourceView: UIView) case panModal case custom(transitioningDelegate: UIViewControllerTransitioningDelegate) case customPush @@ -326,7 +327,10 @@ extension SceneCoordinator { panModalPresentable.transitioningDelegate = PanModalPresentationDelegate.default presentingViewController.present(panModalPresentable, animated: true, completion: nil) //presentingViewController.presentPanModal(panModalPresentable) - + case .popover(let sourceView): + viewController.modalPresentationStyle = .popover + viewController.popoverPresentationController?.sourceView = sourceView + (splitViewController ?? presentingViewController)?.present(viewController, animated: true, completion: nil) case .custom(let transitioningDelegate): viewController.modalPresentationStyle = .custom viewController.transitioningDelegate = transitioningDelegate diff --git a/Mastodon/Scene/Account/Cell/AddAccountTableViewCell.swift b/Mastodon/Scene/Account/Cell/AddAccountTableViewCell.swift index 743ad1dc..72289664 100644 --- a/Mastodon/Scene/Account/Cell/AddAccountTableViewCell.swift +++ b/Mastodon/Scene/Account/Cell/AddAccountTableViewCell.swift @@ -9,7 +9,7 @@ import UIKit import MetaTextKit final class AddAccountTableViewCell: UITableViewCell { - + let iconImageView: UIImageView = { let image = UIImage(systemName: "plus.circle.fill")! let imageView = UIImageView(image: image) @@ -51,6 +51,28 @@ extension AddAccountTableViewCell { ]) iconImageView.setContentHuggingPriority(.defaultLow, for: .horizontal) iconImageView.setContentHuggingPriority(.defaultLow, for: .vertical) + + // layout the same placeholder UI from `AccountListTableViewCell` + let placeholderLabelContainerStackView = UIStackView() + placeholderLabelContainerStackView.axis = .vertical + placeholderLabelContainerStackView.distribution = .equalCentering + placeholderLabelContainerStackView.spacing = 2 + placeholderLabelContainerStackView.distribution = .fillProportionally + placeholderLabelContainerStackView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(placeholderLabelContainerStackView) + NSLayoutConstraint.activate([ + placeholderLabelContainerStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8), + placeholderLabelContainerStackView.leadingAnchor.constraint(equalTo: iconImageView.trailingAnchor, constant: 10), + contentView.bottomAnchor.constraint(equalTo: placeholderLabelContainerStackView.bottomAnchor, constant: 10), + iconImageView.heightAnchor.constraint(equalTo: placeholderLabelContainerStackView.heightAnchor, multiplier: 0.8).priority(.required - 10), + ]) + let _nameLabel = MetaLabel(style: .accountListName) + _nameLabel.configure(content: PlaintextMetaContent(string: " ")) + let _usernameLabel = MetaLabel(style: .accountListUsername) + _usernameLabel.configure(content: PlaintextMetaContent(string: " ")) + placeholderLabelContainerStackView.addArrangedSubview(_nameLabel) + placeholderLabelContainerStackView.addArrangedSubview(_usernameLabel) + placeholderLabelContainerStackView.isHidden = true titleLabel.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(titleLabel) @@ -58,7 +80,7 @@ extension AddAccountTableViewCell { titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 15), titleLabel.leadingAnchor.constraint(equalTo: iconImageView.trailingAnchor, constant: 10), contentView.bottomAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 15), - iconImageView.heightAnchor.constraint(equalTo: titleLabel.heightAnchor, multiplier: 1.0).priority(.required - 10), + // iconImageView.heightAnchor.constraint(equalTo: titleLabel.heightAnchor, multiplier: 1.0).priority(.required - 10), titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), ]) diff --git a/Mastodon/Scene/Root/ContentSplitViewController.swift b/Mastodon/Scene/Root/ContentSplitViewController.swift index 6f5e294c..6e595f2e 100644 --- a/Mastodon/Scene/Root/ContentSplitViewController.swift +++ b/Mastodon/Scene/Root/ContentSplitViewController.swift @@ -63,7 +63,7 @@ extension ContentSplitViewController { sidebarViewController.didMove(toParent: self) NSLayoutConstraint.activate([ mainTabBarController.view.topAnchor.constraint(equalTo: view.topAnchor), - mainTabBarController.view.leadingAnchor.constraint(equalTo: sidebarViewController.view.trailingAnchor), + mainTabBarController.view.leadingAnchor.constraint(equalTo: sidebarViewController.view.trailingAnchor, constant: UIView.separatorLineHeight(of: view)), mainTabBarController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), mainTabBarController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) @@ -87,6 +87,22 @@ extension ContentSplitViewController: SidebarViewControllerDelegate { assertionFailure() return } + let previousTab = currentSupplementaryTab currentSupplementaryTab = tab + + if previousTab == tab, + let navigationController = mainTabBarController.selectedViewController as? UINavigationController + { + navigationController.popToRootViewController(animated: true) + } } + + func sidebarViewController(_ sidebarViewController: SidebarViewController, didLongPressItem item: SidebarViewModel.Item, sourceView: UIView) { + guard case let .tab(tab) = item, tab == .me else { return } + + let accountListViewController = coordinator.present(scene: .accountList, from: nil, transition: .popover(sourceView: sourceView)) as! AccountListViewController + accountListViewController.dragIndicatorView.barView.isHidden = true + accountListViewController.preferredContentSize = CGSize(width: 300, height: 320) + } + } diff --git a/Mastodon/Scene/Root/RootSplitViewController.swift b/Mastodon/Scene/Root/RootSplitViewController.swift index fc1e7a4f..22354751 100644 --- a/Mastodon/Scene/Root/RootSplitViewController.swift +++ b/Mastodon/Scene/Root/RootSplitViewController.swift @@ -26,8 +26,17 @@ final class RootSplitViewController: UISplitViewController, NeedsDependency { return contentSplitViewController }() + private(set) lazy var searchViewController: SearchViewController = { + let searchViewController = SearchViewController() + searchViewController.context = context + searchViewController.coordinator = coordinator + return searchViewController + }() + lazy var compactMainTabBarViewController = MainTabBarController(context: context, coordinator: coordinator) + let separatorLine = UIView.separatorLine + init(context: AppContext, coordinator: SceneCoordinator) { self.context = context self.coordinator = coordinator @@ -48,13 +57,9 @@ final class RootSplitViewController: UISplitViewController, NeedsDependency { // Fallback on earlier versions } - setViewController(UIViewController(), for: .primary) + setViewController(searchViewController, for: .primary) setViewController(contentSplitViewController, for: .secondary) setViewController(compactMainTabBarViewController, for: .compact) - - contentSplitViewController.sidebarViewController.view.layer.zPosition = 100 - contentSplitViewController.mainTabBarController.view.layer.zPosition = 90 - view.layer.zPosition = 80 } required init?(coder: NSCoder) { @@ -80,6 +85,15 @@ extension RootSplitViewController { self.updateBehavior(size: self.view.frame.size) } .store(in: &disposeBag) + + setupBackground(theme: ThemeService.shared.currentTheme.value) + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.main) + .sink { [weak self] theme in + guard let self = self else { return } + self.setupBackground(theme: theme) + } + .store(in: &disposeBag) } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { @@ -108,6 +122,15 @@ extension RootSplitViewController { } +extension RootSplitViewController { + + private func setupBackground(theme: Theme) { + // this set column separator line color + view.backgroundColor = theme.separator + } + +} + // MARK: - UISplitViewControllerDelegate extension RootSplitViewController: UISplitViewControllerDelegate { diff --git a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift index 06009192..7958c508 100644 --- a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift +++ b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift @@ -12,10 +12,13 @@ import CoreDataStack protocol SidebarViewControllerDelegate: AnyObject { func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab) + func sidebarViewController(_ sidebarViewController: SidebarViewController, didLongPressItem item: SidebarViewModel.Item, sourceView: UIView) } final class SidebarViewController: UIViewController, NeedsDependency { + let logger = Logger(subsystem: "SidebarViewController", category: "ViewController") + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } @@ -127,6 +130,10 @@ extension SidebarViewController { self.collectionView.contentInset.bottom = height } .store(in: &observations) + + let sidebarLongPressGestureRecognizer = UILongPressGestureRecognizer() + sidebarLongPressGestureRecognizer.addTarget(self, action: #selector(SidebarViewController.sidebarLongPressGestureRecognizerHandler(_:))) + collectionView.addGestureRecognizer(sidebarLongPressGestureRecognizer) } private func setupBackground(theme: Theme) { @@ -148,6 +155,23 @@ extension SidebarViewController { } +extension SidebarViewController { + @objc private func sidebarLongPressGestureRecognizerHandler(_ sender: UILongPressGestureRecognizer) { + guard sender.state == .began else { return } + + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + assert(sender.view === collectionView) + + let position = sender.location(in: collectionView) + guard let indexPath = collectionView.indexPathForItem(at: position) else { return } + guard let diffableDataSource = viewModel.diffableDataSource else { return } + guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } + guard let cell = collectionView.cellForItem(at: indexPath) else { return } + delegate?.sidebarViewController(self, didLongPressItem: item, sourceView: cell) + } + +} + // MARK: - UICollectionViewDelegate extension SidebarViewController: UICollectionViewDelegate { @@ -179,25 +203,5 @@ extension SidebarViewController: UICollectionViewDelegate { default: assertionFailure() } -// switch item { -// case .tab(let tab): -// delegate?.sidebarViewController(self, didSelectTab: tab) -// case .searchHistory(let viewModel): -// delegate?.sidebarViewController(self, didSelectSearchHistory: viewModel) -// case .header: -// break -// case .account(let viewModel): -// assert(Thread.isMainThread) -// let authentication = context.managedObjectContext.object(with: viewModel.authenticationObjectID) as! MastodonAuthentication -// context.authenticationService.activeMastodonUser(domain: authentication.domain, userID: authentication.userID) -// .receive(on: DispatchQueue.main) -// .sink { [weak self] result in -// guard let self = self else { return } -// self.coordinator.setup() -// } -// .store(in: &disposeBag) -// case .addAccount: -// coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil)) -// } } } diff --git a/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift b/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift index 8c013efd..d85d3a8b 100644 --- a/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift +++ b/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift @@ -18,6 +18,7 @@ final class SidebarListContentView: UIView, UIContentView { let avatarButton: CircleAvatarButton = { let button = CircleAvatarButton() button.borderWidth = 2 + button.borderColor = UIColor.label.cgColor return button }() @@ -71,6 +72,9 @@ extension SidebarListContentView { imageView.contentMode = .scaleAspectFit avatarButton.contentMode = .scaleAspectFit + + imageView.isUserInteractionEnabled = false + avatarButton.isUserInteractionEnabled = false } private func apply(configuration: ContentConfiguration) { From e8e655ff5c7225657ac53e96b26f45199dca297b Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 29 Oct 2021 18:08:47 +0800 Subject: [PATCH 40/96] feat: add accessibility supports for iPad sidebar --- Localization/app.json | 1 + Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Localization/app.json b/Localization/app.json index 3ec77cf1..3d0e36d0 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -67,6 +67,7 @@ "done": "Done", "confirm": "Confirm", "continue": "Continue", + "compose": "Compose", "cancel": "Cancel", "discard": "Discard", "try_again": "Try Again", diff --git a/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift b/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift index 6dd46d08..83abf4e6 100644 --- a/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift +++ b/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift @@ -84,6 +84,8 @@ extension SidebarViewModel { imageURL: imageURL ) cell.setNeedsUpdateConfiguration() + cell.isAccessibilityElement = true + cell.accessibilityLabel = item.title switch item { case .notification: @@ -103,6 +105,10 @@ extension SidebarViewModel { cell._contentView?.imageView.image = image } .store(in: &cell.disposeBag) + case .me: + guard let authentication = self.context.authenticationService.activeMastodonAuthentication.value else { break } + let currentUserDisplayName = authentication.user.displayNameWithFallback ?? "no user" + cell.accessibilityHint = L10n.Scene.AccountList.tabBarHint(currentUserDisplayName) default: break } @@ -112,6 +118,8 @@ extension SidebarViewModel { guard let self = self else { return } cell.item = item cell.setNeedsUpdateConfiguration() + cell.isAccessibilityElement = true + cell.accessibilityLabel = item.title } // header @@ -132,7 +140,7 @@ extension SidebarViewModel { return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item) case .compose: let item = SidebarListContentView.Item( - title: "Compose", // FIXME: + title: "Compose", // TODO: update i18n image: UIImage(systemName: "square.and.pencil")!, imageURL: nil ) From bb79e574124fa772859a5362c5eae0def750b189 Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 29 Oct 2021 18:14:51 +0800 Subject: [PATCH 41/96] chore: set Home page navigation item hidden on iPad --- Mastodon/Scene/Root/ContentSplitViewController.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Mastodon/Scene/Root/ContentSplitViewController.swift b/Mastodon/Scene/Root/ContentSplitViewController.swift index 6e595f2e..577f8d6a 100644 --- a/Mastodon/Scene/Root/ContentSplitViewController.swift +++ b/Mastodon/Scene/Root/ContentSplitViewController.swift @@ -31,6 +31,10 @@ final class ContentSplitViewController: UIViewController, NeedsDependency { @Published var currentSupplementaryTab: MainTabBarController.Tab = .home private(set) lazy var mainTabBarController: MainTabBarController = { let mainTabBarController = MainTabBarController(context: context, coordinator: coordinator) + if let homeTimelineViewController = mainTabBarController.viewController(of: HomeTimelineViewController.self) { + homeTimelineViewController.viewModel.displayComposeBarButtonItem.value = false + homeTimelineViewController.viewModel.displaySettingBarButtonItem.value = false + } return mainTabBarController }() From 2f0b321fd9023a6806d1ce5f3953e30a0489ad57 Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 29 Oct 2021 18:35:02 +0800 Subject: [PATCH 42/96] chore: update tab bar inactive color --- .../Contents.json | 12 ++++++------ .../Contents.json | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Mastodon/Resources/Assets.xcassets/Theme/Mastodon/tab.bar.item.inactive.icon.color.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Theme/Mastodon/tab.bar.item.inactive.icon.color.colorset/Contents.json index 1accfacd..bfc2a11b 100644 --- a/Mastodon/Resources/Assets.xcassets/Theme/Mastodon/tab.bar.item.inactive.icon.color.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Theme/Mastodon/tab.bar.item.inactive.icon.color.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.549", - "green" : "0.510", - "red" : "0.431" + "blue" : "0x99", + "green" : "0x99", + "red" : "0x99" } }, "idiom" : "universal" @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "200", - "green" : "174", - "red" : "155" + "blue" : "0x99", + "green" : "0x99", + "red" : "0x99" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Assets.xcassets/Theme/system/tab.bar.item.inactive.icon.color.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Theme/system/tab.bar.item.inactive.icon.color.colorset/Contents.json index ece9000a..bfc2a11b 100644 --- a/Mastodon/Resources/Assets.xcassets/Theme/system/tab.bar.item.inactive.icon.color.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Theme/system/tab.bar.item.inactive.icon.color.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.549", - "green" : "0.510", - "red" : "0.431" + "blue" : "0x99", + "green" : "0x99", + "red" : "0x99" } }, "idiom" : "universal" @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "140", - "green" : "130", - "red" : "110" + "blue" : "0x99", + "green" : "0x99", + "red" : "0x99" } }, "idiom" : "universal" From e669d17d4922452dfda65ce0ce5161f60fd42d72 Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 29 Oct 2021 18:56:58 +0800 Subject: [PATCH 43/96] feat: make notification coordinator works --- Mastodon/Coordinator/SceneCoordinator.swift | 94 +++++++++---------- .../Header/ProfileHeaderViewController.swift | 23 +++-- .../Scene/Profile/ProfileViewController.swift | 3 + 3 files changed, 62 insertions(+), 58 deletions(-) diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index 696b211b..10d6fb84 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -90,45 +90,44 @@ final public class SceneCoordinator { // Delay in next run loop -// DispatchQueue.main.async { [weak self] in -// guard let self = self else { return } -// -// // Note: -// // show (push) on phone or pad (compact) -// // showDetail in .secondary in UISplitViewController on pad (expand) -// let from: UIViewController? = { -// if let splitViewController = self.splitViewController { -// if splitViewController.mainTabBarController.topMost?.view.window != nil { -// // compact -// return splitViewController.mainTabBarController.topMost -// } else { -// // expand -// return splitViewController.viewController(for: .supplementary) -// } -// } else { -// return self.tabBarController.topMost -// } -// }() -// -// // show notification related content -// guard let type = Mastodon.Entity.Notification.NotificationType(rawValue: pushNotification.notificationType) else { return } -// let notificationID = String(pushNotification.notificationID) -// -// switch type { -// case .follow: -// let profileViewModel = RemoteProfileViewModel(context: appContext, notificationID: notificationID) -// self.present(scene: .profile(viewModel: profileViewModel), from: from, transition: .show) -// case .followRequest: -// // do nothing -// break -// case .mention, .reblog, .favourite, .poll, .status: -// let threadViewModel = RemoteThreadViewModel(context: appContext, notificationID: notificationID) -// self.present(scene: .thread(viewModel: threadViewModel), from: from, transition: .show) -// case ._other: -// assertionFailure() -// break -// } -// } // end DispatchQueue.main.async + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + // Note: + // show (push) on phone and pad + let from: UIViewController? = { + if let splitViewController = self.splitViewController { + if splitViewController.compactMainTabBarViewController.topMost?.view.window != nil { + // compact + return splitViewController.compactMainTabBarViewController.topMost + } else { + // expand + return splitViewController.contentSplitViewController.mainTabBarController.topMost + } + } else { + return self.tabBarController.topMost + } + }() + + // show notification related content + guard let type = Mastodon.Entity.Notification.NotificationType(rawValue: pushNotification.notificationType) else { return } + let notificationID = String(pushNotification.notificationID) + + switch type { + case .follow: + let profileViewModel = RemoteProfileViewModel(context: appContext, notificationID: notificationID) + self.present(scene: .profile(viewModel: profileViewModel), from: from, transition: .show) + case .followRequest: + // do nothing + break + case .mention, .reblog, .favourite, .poll, .status: + let threadViewModel = RemoteThreadViewModel(context: appContext, notificationID: notificationID) + self.present(scene: .thread(viewModel: threadViewModel), from: from, transition: .show) + case ._other: + assertionFailure() + break + } + } // end DispatchQueue.main.async } .store(in: &disposeBag) } @@ -283,18 +282,6 @@ extension SceneCoordinator { switch transition { case .show: -// if let splitViewController = splitViewController, !splitViewController.isCollapsed, -// let supplementaryViewController = splitViewController.viewController(for: .supplementary) as? UINavigationController, -// (supplementaryViewController === presentingViewController || supplementaryViewController.viewControllers.contains(presentingViewController)) || -// (presentingViewController is UserTimelineViewController && presentingViewController.view.isDescendant(of: supplementaryViewController.view)) -// { -// fallthrough -// } else { -// if secondaryStackHashValues.contains(presentingViewController.hashValue) { -// secondaryStackHashValues.insert(viewController.hashValue) -// } -// presentingViewController.show(viewController, sender: sender) -// } presentingViewController.show(viewController, sender: sender) case .showDetail: secondaryStackHashValues.insert(viewController.hashValue) @@ -362,6 +349,11 @@ extension SceneCoordinator { } func switchToTabBar(tab: MainTabBarController.Tab) { + splitViewController?.contentSplitViewController.currentSupplementaryTab = tab + + splitViewController?.compactMainTabBarViewController.selectedIndex = tab.rawValue + splitViewController?.compactMainTabBarViewController.currentTab.value = tab + tabBarController.selectedIndex = tab.rawValue tabBarController.currentTab.value = tab } diff --git a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift index 716b6230..34716dde 100644 --- a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift +++ b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift @@ -44,10 +44,11 @@ final class ProfileHeaderViewController: UIViewController { let profileHeaderView = ProfileHeaderView() let pageSegmentedControl: UISegmentedControl = { - let segmenetedControl = UISegmentedControl(items: ["A", "B"]) - segmenetedControl.selectedSegmentIndex = 0 - return segmenetedControl + let segmentedControl = UISegmentedControl(items: ["A", "B"]) + segmentedControl.selectedSegmentIndex = 0 + return segmentedControl }() + var pageSegmentedControlLeadingLayoutConstraint: NSLayoutConstraint! private var isBannerPinned = false private var bottomShadowAlpha: CGFloat = 0.0 @@ -118,9 +119,10 @@ extension ProfileHeaderViewController { pageSegmentedControl.translatesAutoresizingMaskIntoConstraints = false view.addSubview(pageSegmentedControl) + pageSegmentedControlLeadingLayoutConstraint = pageSegmentedControl.leadingAnchor.constraint(equalTo: view.leadingAnchor) NSLayoutConstraint.activate([ pageSegmentedControl.topAnchor.constraint(equalTo: profileHeaderView.bottomAnchor, constant: ProfileHeaderViewController.segmentedControlMarginHeight), - pageSegmentedControl.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor), + pageSegmentedControlLeadingLayoutConstraint, // Fix iPad layout issue pageSegmentedControl.trailingAnchor.constraint(equalTo: view.readableContentGuide.trailingAnchor), view.bottomAnchor.constraint(equalTo: pageSegmentedControl.bottomAnchor, constant: ProfileHeaderViewController.segmentedControlMarginHeight), pageSegmentedControl.heightAnchor.constraint(equalToConstant: ProfileHeaderViewController.segmentedControlHeight).priority(.defaultHigh), @@ -133,10 +135,10 @@ extension ProfileHeaderViewController { viewModel.isTitleViewContentOffsetSet.eraseToAnyPublisher() ) .receive(on: DispatchQueue.main) - .sink { [weak self] viewDidAppear, isTitleViewContentOffsetDidSetted in + .sink { [weak self] viewDidAppear, isTitleViewContentOffsetDidSet in guard let self = self else { return } - self.titleView.titleLabel.alpha = viewDidAppear && isTitleViewContentOffsetDidSetted ? 1 : 0 - self.titleView.subtitleLabel.alpha = viewDidAppear && isTitleViewContentOffsetDidSetted ? 1 : 0 + self.titleView.titleLabel.alpha = viewDidAppear && isTitleViewContentOffsetDidSet ? 1 : 0 + self.titleView.subtitleLabel.alpha = viewDidAppear && isTitleViewContentOffsetDidSet ? 1 : 0 } .store(in: &disposeBag) @@ -283,6 +285,13 @@ extension ProfileHeaderViewController { setupBottomShadow() } + override func viewLayoutMarginsDidChange() { + super.viewLayoutMarginsDidChange() + + let margin = view.frame.maxX - view.readableContentGuide.layoutFrame.maxX + pageSegmentedControlLeadingLayoutConstraint.constant = margin + } + } extension ProfileHeaderViewController { diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index 434836ab..b864fc94 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -517,6 +517,7 @@ extension ProfileViewController { .assign(to: \.value, on: profileHeaderViewController.viewModel.displayProfileInfo.note) .store(in: &disposeBag) viewModel.statusesCount + .receive(on: DispatchQueue.main) .sink { [weak self] count in guard let self = self else { return } let text = count.flatMap { MastodonMetricFormatter().string(from: $0) } ?? "-" @@ -526,6 +527,7 @@ extension ProfileViewController { } .store(in: &disposeBag) viewModel.followingCount + .receive(on: DispatchQueue.main) .sink { [weak self] count in guard let self = self else { return } let text = count.flatMap { MastodonMetricFormatter().string(from: $0) } ?? "-" @@ -535,6 +537,7 @@ extension ProfileViewController { } .store(in: &disposeBag) viewModel.followersCount + .receive(on: DispatchQueue.main) .sink { [weak self] count in guard let self = self else { return } let text = count.flatMap { MastodonMetricFormatter().string(from: $0) } ?? "-" From 9e2566e2a7e8c172c187f0e8b049827ddcef1dc3 Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 29 Oct 2021 19:00:23 +0800 Subject: [PATCH 44/96] chore: update i18n resources --- .../Resources/ar.lproj/Localizable.strings | 8 ++--- .../ca.lproj/Localizable.stringsdict | 28 ++++++++--------- .../Resources/de.lproj/Localizable.strings | 4 +-- .../Resources/gd-GB.lproj/Localizable.strings | 12 ++++---- .../gd-GB.lproj/Localizable.stringsdict | 8 ++--- .../Resources/ja.lproj/Localizable.strings | 9 +++--- .../ja.lproj/Localizable.stringsdict | 12 ++++---- MastodonIntent/ar.lproj/Intents.strings | 30 +++++++++---------- 8 files changed, 55 insertions(+), 56 deletions(-) diff --git a/Mastodon/Resources/ar.lproj/Localizable.strings b/Mastodon/Resources/ar.lproj/Localizable.strings index 98d8c09c..5950546a 100644 --- a/Mastodon/Resources/ar.lproj/Localizable.strings +++ b/Mastodon/Resources/ar.lproj/Localizable.strings @@ -9,7 +9,7 @@ "Common.Alerts.DiscardPostContent.Message" = "Confirm to discard composed post content."; "Common.Alerts.DiscardPostContent.Title" = "تجاهل المسودة"; "Common.Alerts.EditProfileFailure.Message" = "لا يمكن تعديل الملف الشخصي. يُرجى المحاولة مرة أُخرى."; -"Common.Alerts.EditProfileFailure.Title" = "Edit Profile Error"; +"Common.Alerts.EditProfileFailure.Title" = "خطأ في تَحرير الملف الشخصي"; "Common.Alerts.PublishPostFailure.AttachmentsMessage.MoreThanOneVideo" = "Cannot attach more than one video."; "Common.Alerts.PublishPostFailure.AttachmentsMessage.VideoAttachWithPhoto" = "Cannot attach a video to a post that already contains images."; "Common.Alerts.PublishPostFailure.Message" = "Failed to publish the post. @@ -275,7 +275,7 @@ uploaded to Mastodon."; "Scene.Search.Searching.Segment.People" = "الأشخاص"; "Scene.Search.Searching.Segment.Posts" = "المنشورات"; "Scene.Search.Title" = "بحث"; -"Scene.ServerPicker.Button.Category.Academia" = "academia"; +"Scene.ServerPicker.Button.Category.Academia" = "أكاديمي"; "Scene.ServerPicker.Button.Category.Activism" = "للنشطاء"; "Scene.ServerPicker.Button.Category.All" = "الكل"; "Scene.ServerPicker.Button.Category.AllAccessiblityDescription" = "الفئة: الكل"; @@ -298,8 +298,8 @@ uploaded to Mastodon."; "Scene.ServerPicker.Label.Category" = "الفئة"; "Scene.ServerPicker.Label.Language" = "اللغة"; "Scene.ServerPicker.Label.Users" = "مستخدمون·ات"; -"Scene.ServerPicker.Title" = "Pick a server, -any server."; +"Scene.ServerPicker.Title" = "اِختر خادِم، +أي خادِم."; "Scene.ServerRules.Button.Confirm" = "انا أوافق"; "Scene.ServerRules.PrivacyPolicy" = "سياسة الخصوصية"; "Scene.ServerRules.Prompt" = "إن اخترت المواصلة، فإنك تخضع لشروط الخدمة وسياسة الخصوصية لـ %@."; diff --git a/Mastodon/Resources/ca.lproj/Localizable.stringsdict b/Mastodon/Resources/ca.lproj/Localizable.stringsdict index cc731293..140185ba 100644 --- a/Mastodon/Resources/ca.lproj/Localizable.stringsdict +++ b/Mastodon/Resources/ca.lproj/Localizable.stringsdict @@ -21,7 +21,7 @@ a11y.plural.count.input_limit_exceeds NSStringLocalizedFormatKey - El límit d’entrada supera a %#@character_count@ + El límit de la entrada supera a %#@character_count@ character_count NSStringFormatSpecTypeKey @@ -37,7 +37,7 @@ a11y.plural.count.input_limit_remains NSStringLocalizedFormatKey - El límit d’entrada continua sent %#@character_count@ + El límit de la entrada continua sent %#@character_count@ character_count NSStringFormatSpecTypeKey @@ -111,7 +111,7 @@ one 1 impuls other - %ld impuls + %ld impulsos plural.count.vote @@ -301,9 +301,9 @@ NSStringFormatValueTypeKey ld one - fa 1a + fa 1 any other - fa %ldy anys + fa %ld anys date.month.ago.abbr @@ -317,9 +317,9 @@ NSStringFormatValueTypeKey ld one - fa 1M + fa 1 mes other - fa %ldM mesos + fa %ld mesos date.day.ago.abbr @@ -333,9 +333,9 @@ NSStringFormatValueTypeKey ld one - fa 1d + fa 1 día other - fa %ldd dies + fa %ld dies date.hour.ago.abbr @@ -351,7 +351,7 @@ one fa 1h other - fa %ldh hores + fa %ld hores date.minute.ago.abbr @@ -365,9 +365,9 @@ NSStringFormatValueTypeKey ld one - fa 1m + fa 1 minut other - fa %ldm minuts + fa %ld minuts date.second.ago.abbr @@ -381,9 +381,9 @@ NSStringFormatValueTypeKey ld one - fa 1s + fa 1 segon other - fa %lds seg + fa %ld segons diff --git a/Mastodon/Resources/de.lproj/Localizable.strings b/Mastodon/Resources/de.lproj/Localizable.strings index 51028d7a..2780723e 100644 --- a/Mastodon/Resources/de.lproj/Localizable.strings +++ b/Mastodon/Resources/de.lproj/Localizable.strings @@ -135,7 +135,7 @@ Dein Profil sieht für diesen Benutzer auch so aus."; "Common.Controls.Timeline.Timestamp.Now" = "Gerade"; "Scene.AccountList.AddAccount" = "Konto hinzufügen"; "Scene.AccountList.DismissAccountSwitcher" = "Dismiss Account Switcher"; -"Scene.AccountList.TabBarHint" = "Current selected profile: %@. Double tap then hold to show account switcher"; +"Scene.AccountList.TabBarHint" = "Aktuell ausgewähltes Profil: %@. Doppeltippen dann gedrückt halten, um den Kontoschalter anzuzeigen"; "Scene.Compose.Accessibility.AppendAttachment" = "Anhang hinzufügen"; "Scene.Compose.Accessibility.AppendPoll" = "Umfrage hinzufügen"; "Scene.Compose.Accessibility.CustomEmojiPicker" = "Benutzerdefinierter Emojiwähler"; @@ -341,5 +341,5 @@ beliebigen Server."; "Scene.Thread.Title" = "Beitrag von %@"; "Scene.Welcome.Slogan" = "Soziale Netzwerke wieder in deinen Händen."; "Scene.Wizard.AccessibilityHint" = "Doppeltippen, um diesen Assistenten zu schließen"; -"Scene.Wizard.MultipleAccountSwitchIntroDescription" = "Switch between multiple accounts by holding the profile button."; +"Scene.Wizard.MultipleAccountSwitchIntroDescription" = "Wechsel zwischen mehreren Konten durch drücken der Profil-Schaltfläche."; "Scene.Wizard.NewInMastodon" = "Neu in Mastodon"; \ No newline at end of file diff --git a/Mastodon/Resources/gd-GB.lproj/Localizable.strings b/Mastodon/Resources/gd-GB.lproj/Localizable.strings index f24bd24e..6c01adb0 100644 --- a/Mastodon/Resources/gd-GB.lproj/Localizable.strings +++ b/Mastodon/Resources/gd-GB.lproj/Localizable.strings @@ -133,9 +133,9 @@ Seo an coltas a th’ air a’ phròifil agad dhaibh-san."; "Common.Controls.Timeline.Loader.LoadingMissingPosts" = "A’ luchdadh nam post a tha a dhìth…"; "Common.Controls.Timeline.Loader.ShowMoreReplies" = "Seall barrachd freagairtean"; "Common.Controls.Timeline.Timestamp.Now" = "An-dràsta"; -"Scene.AccountList.AddAccount" = "Add Account"; -"Scene.AccountList.DismissAccountSwitcher" = "Dismiss Account Switcher"; -"Scene.AccountList.TabBarHint" = "Current selected profile: %@. Double tap then hold to show account switcher"; +"Scene.AccountList.AddAccount" = "Cuir cunntas ris"; +"Scene.AccountList.DismissAccountSwitcher" = "Leig seachad taghadh a’ chunntais"; +"Scene.AccountList.TabBarHint" = "A’ phròifil air a taghadh: %@. Thoir gnogag dhùbailte is cùm sìos a ghearradh leum gu cunntas eile"; "Scene.Compose.Accessibility.AppendAttachment" = "Cuir ceanglachan ris"; "Scene.Compose.Accessibility.AppendPoll" = "Cuir cunntas-bheachd ris"; "Scene.Compose.Accessibility.CustomEmojiPicker" = "Roghnaichear nan Emoji gnàthaichte"; @@ -340,6 +340,6 @@ thoir gnogag air a’ chunntas a dhearbhadh a’ chunntais agad."; "Scene.Thread.Title" = "Post le %@"; "Scene.Welcome.Slogan" = "A’ cur nan lìonraidhean sòisealta ’nad làmhan fhèin."; -"Scene.Wizard.AccessibilityHint" = "Double tap to dismiss this wizard"; -"Scene.Wizard.MultipleAccountSwitchIntroDescription" = "Switch between multiple accounts by holding the profile button."; -"Scene.Wizard.NewInMastodon" = "New in Mastodon"; \ No newline at end of file +"Scene.Wizard.AccessibilityHint" = "Thoir gnogag dhùbailte a’ leigeil seachad an draoidh seo"; +"Scene.Wizard.MultipleAccountSwitchIntroDescription" = "Geàrr leum eadar iomadh cunntas le cumail sìos putan na pròifil."; +"Scene.Wizard.NewInMastodon" = "Na tha ùr ann am Mastodon"; \ No newline at end of file diff --git a/Mastodon/Resources/gd-GB.lproj/Localizable.stringsdict b/Mastodon/Resources/gd-GB.lproj/Localizable.stringsdict index 41e592a5..7a54f553 100644 --- a/Mastodon/Resources/gd-GB.lproj/Localizable.stringsdict +++ b/Mastodon/Resources/gd-GB.lproj/Localizable.stringsdict @@ -13,13 +13,13 @@ NSStringFormatValueTypeKey ld one - 1 unread notification + %ld bhrath nach deach a leughadh two - %ld unread notification + %ld bhrath nach deach a leughadh few - %ld unread notification + %ld brathan nach deach a leughadh other - %ld unread notification + %ld brath nach deach a leughadh a11y.plural.count.input_limit_exceeds diff --git a/Mastodon/Resources/ja.lproj/Localizable.strings b/Mastodon/Resources/ja.lproj/Localizable.strings index e83278e3..beadccf2 100644 --- a/Mastodon/Resources/ja.lproj/Localizable.strings +++ b/Mastodon/Resources/ja.lproj/Localizable.strings @@ -129,7 +129,7 @@ "Common.Controls.Timeline.Loader.LoadingMissingPosts" = "読込中..."; "Common.Controls.Timeline.Loader.ShowMoreReplies" = "リプライをもっとみる"; "Common.Controls.Timeline.Timestamp.Now" = "今"; -"Scene.AccountList.AddAccount" = "Add Account"; +"Scene.AccountList.AddAccount" = "アカウントを追加"; "Scene.AccountList.DismissAccountSwitcher" = "Dismiss Account Switcher"; "Scene.AccountList.TabBarHint" = "Current selected profile: %@. Double tap then hold to show account switcher"; "Scene.Compose.Accessibility.AppendAttachment" = "アタッチメントの追加"; @@ -332,8 +332,7 @@ "Scene.SuggestionAccount.Title" = "フォローする人を探す"; "Scene.Thread.BackTitle" = "投稿"; "Scene.Thread.Title" = "%@の投稿"; -"Scene.Welcome.Slogan" = "Social networking -back in your hands."; +"Scene.Welcome.Slogan" = "ソーシャルネットワーキングを、あなたの手の中に."; "Scene.Wizard.AccessibilityHint" = "Double tap to dismiss this wizard"; -"Scene.Wizard.MultipleAccountSwitchIntroDescription" = "Switch between multiple accounts by holding the profile button."; -"Scene.Wizard.NewInMastodon" = "New in Mastodon"; \ No newline at end of file +"Scene.Wizard.MultipleAccountSwitchIntroDescription" = "プロフィールボタンを押して複数のアカウントを切り替えます。"; +"Scene.Wizard.NewInMastodon" = "Mastodon の新機能"; \ No newline at end of file diff --git a/Mastodon/Resources/ja.lproj/Localizable.stringsdict b/Mastodon/Resources/ja.lproj/Localizable.stringsdict index 0300d9dc..c51a9a29 100644 --- a/Mastodon/Resources/ja.lproj/Localizable.stringsdict +++ b/Mastodon/Resources/ja.lproj/Localizable.stringsdict @@ -13,7 +13,7 @@ NSStringFormatValueTypeKey ld other - %ld unread notification + %ld 件の未読通知 a11y.plural.count.input_limit_exceeds @@ -27,7 +27,7 @@ NSStringFormatValueTypeKey ld other - %ld characters + %ld 文字 a11y.plural.count.input_limit_remains @@ -41,7 +41,7 @@ NSStringFormatValueTypeKey ld other - %ld characters + %ld 文字 plural.count.metric_formatted.post @@ -111,7 +111,7 @@ NSStringFormatValueTypeKey ld other - %ld votes + %ld票 plural.count.voter @@ -195,7 +195,7 @@ NSStringFormatValueTypeKey ld other - %ld months left + %ldか月前 date.day.left @@ -279,7 +279,7 @@ NSStringFormatValueTypeKey ld other - %ldM ago + %ld分前 date.day.ago.abbr diff --git a/MastodonIntent/ar.lproj/Intents.strings b/MastodonIntent/ar.lproj/Intents.strings index bf3e77ed..cde27dc9 100644 --- a/MastodonIntent/ar.lproj/Intents.strings +++ b/MastodonIntent/ar.lproj/Intents.strings @@ -1,22 +1,22 @@ -"16wxgf" = "Post on Mastodon"; +"16wxgf" = "النَشر على ماستودون"; "751xkl" = "محتوى نصي"; "CsR7G2" = "انشر على ماستدون"; -"HZSGTr" = "What content to post?"; +"HZSGTr" = "ما المُحتوى المُراد نشره؟"; -"HdGikU" = "Posting failed"; +"HdGikU" = "فَشَلَ النشر"; "KDNTJ4" = "سبب الإخفاق"; -"RHxKOw" = "Send Post with text content"; +"RHxKOw" = "إرسال مَنشور يَحوي نص"; -"RxSqsb" = "Post"; +"RxSqsb" = "مَنشور"; -"WCIR3D" = "Post ${content} on Mastodon"; +"WCIR3D" = "نَشر ${content} على ماستودون"; -"ZKJSNu" = "Post"; +"ZKJSNu" = "مَنشور"; "ZS1XaK" = "${content}"; @@ -24,13 +24,13 @@ "Zo4jgJ" = "مدى ظهور المنشور"; -"apSxMG-dYQ5NN" = "There are ${count} options matching ‘Public’."; +"apSxMG-dYQ5NN" = "هُناك عدد ${count} خِيار مُطابق لِـ\"عام\"."; -"apSxMG-ehFLjY" = "There are ${count} options matching ‘Followers Only’."; +"apSxMG-ehFLjY" = "هُناك عدد ${count} خِيار مُطابق لِـ\"المُتابِعُون فقط\"."; -"ayoYEb-dYQ5NN" = "${content}, Public"; +"ayoYEb-dYQ5NN" = "${content}، عام"; -"ayoYEb-ehFLjY" = "${content}, Followers Only"; +"ayoYEb-ehFLjY" = "${content}، المُتابِعُون فقط"; "dUyuGg" = "النشر على ماستدون"; @@ -38,13 +38,13 @@ "ehFLjY" = "لمتابعيك فقط"; -"gfePDu" = "Posting failed. ${failureReason}"; +"gfePDu" = "فَشَلَ النشر، ${failureReason}"; -"k7dbKQ" = "Post was sent successfully."; +"k7dbKQ" = "تمَّ إرسال المنشور بِنجاح."; -"oGiqmY-dYQ5NN" = "Just to confirm, you wanted ‘Public’?"; +"oGiqmY-dYQ5NN" = "للتأكيد، هل تَريد \"عام\"؟"; -"oGiqmY-ehFLjY" = "Just to confirm, you wanted ‘Followers Only’?"; +"oGiqmY-ehFLjY" = "للتأكيد، هل تُريد \"للمُتابِعين فقط\"؟"; "rM6dvp" = "عنوان URL"; From b4c240967f0bf04e68ae89997449c518c9f92328 Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 29 Oct 2021 19:29:58 +0800 Subject: [PATCH 45/96] feat: add Kurmanji (Kurdish) i18n strings from Crowdin --- .../Sources/StringsConvertor/main.swift | 1 + Mastodon.xcodeproj/project.pbxproj | 11 + .../xcschemes/xcschememanagement.plist | 6 +- .../Resources/ku-TR.lproj/InfoPlist.strings | 4 + .../Resources/ku-TR.lproj/Localizable.strings | 346 ++++++++++++++++ .../ku-TR.lproj/Localizable.stringsdict | 390 ++++++++++++++++++ MastodonIntent/ku-TR.lproj/Intents.strings | 51 +++ .../ku-TR.lproj/Intents.stringsdict | 54 +++ 8 files changed, 860 insertions(+), 3 deletions(-) create mode 100644 Mastodon/Resources/ku-TR.lproj/InfoPlist.strings create mode 100644 Mastodon/Resources/ku-TR.lproj/Localizable.strings create mode 100644 Mastodon/Resources/ku-TR.lproj/Localizable.stringsdict create mode 100644 MastodonIntent/ku-TR.lproj/Intents.strings create mode 100644 MastodonIntent/ku-TR.lproj/Intents.stringsdict diff --git a/Localization/StringsConvertor/Sources/StringsConvertor/main.swift b/Localization/StringsConvertor/Sources/StringsConvertor/main.swift index 124612e5..6507986b 100644 --- a/Localization/StringsConvertor/Sources/StringsConvertor/main.swift +++ b/Localization/StringsConvertor/Sources/StringsConvertor/main.swift @@ -51,6 +51,7 @@ private func map(language: String) -> String? { case "fr_FR": return "fr" // French case "de_DE": return "de" // German case "ja_JP": return "ja" // Japanese + case "kmr_TR": return "ku-TR" // Kurmanji (Kurdish) case "ru_RU": return "ru" // Russian case "gd_GB": return "gd-GB" // Scottish Gaelic case "es_ES": return "es" // Spanish diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index f7b373f5..cc98e398 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -1365,6 +1365,11 @@ DBD376AB2692ECDB007FEC24 /* ThemePreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemePreference.swift; sourceTree = ""; }; DBD376B1269302A4007FEC24 /* UITableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITableViewCell.swift; sourceTree = ""; }; DBD9148F25DF6D8D00903DFD /* APIService+Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Onboarding.swift"; sourceTree = ""; }; + DBDC1CF9272C0FD600055C3D /* ku-TR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ku-TR"; path = "ku-TR.lproj/Intents.strings"; sourceTree = ""; }; + DBDC1CFA272C0FD600055C3D /* ku-TR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "ku-TR"; path = "ku-TR.lproj/Localizable.stringsdict"; sourceTree = ""; }; + DBDC1CFB272C0FD600055C3D /* ku-TR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ku-TR"; path = "ku-TR.lproj/Localizable.strings"; sourceTree = ""; }; + DBDC1CFC272C0FD600055C3D /* ku-TR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ku-TR"; path = "ku-TR.lproj/InfoPlist.strings"; sourceTree = ""; }; + DBDC1CFD272C0FD600055C3D /* ku-TR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "ku-TR"; path = "ku-TR.lproj/Intents.stringsdict"; sourceTree = ""; }; DBE0821425CD382600FD6BBD /* MastodonRegisterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterViewController.swift; sourceTree = ""; }; DBE0822325CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterViewModel.swift; sourceTree = ""; }; DBE3CDBA261C427900430CC6 /* TimelineHeaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineHeaderTableViewCell.swift; sourceTree = ""; }; @@ -3530,6 +3535,7 @@ ru, "gd-GB", th, + "ku-TR", ); mainGroup = DB427DC925BAA00100D1B89D; packageReferences = ( @@ -4560,6 +4566,7 @@ DB4B777F26CA4EFA00B087B3 /* ru */, DB4B778426CA500E00B087B3 /* gd-GB */, DB4B779226CA50BA00B087B3 /* th */, + DBDC1CF9272C0FD600055C3D /* ku-TR */, ); name = Intents.intentdefinition; sourceTree = ""; @@ -4580,6 +4587,7 @@ DB4B778226CA4EFA00B087B3 /* ru */, DB4B778726CA500E00B087B3 /* gd-GB */, DB4B779526CA50BA00B087B3 /* th */, + DBDC1CFC272C0FD600055C3D /* ku-TR */, ); name = InfoPlist.strings; sourceTree = ""; @@ -4600,6 +4608,7 @@ DB4B778126CA4EFA00B087B3 /* ru */, DB4B778626CA500E00B087B3 /* gd-GB */, DB4B779426CA50BA00B087B3 /* th */, + DBDC1CFB272C0FD600055C3D /* ku-TR */, ); name = Localizable.strings; sourceTree = ""; @@ -4636,6 +4645,7 @@ DB4B778026CA4EFA00B087B3 /* ru */, DB4B778526CA500E00B087B3 /* gd-GB */, DB4B779326CA50BA00B087B3 /* th */, + DBDC1CFA272C0FD600055C3D /* ku-TR */, ); name = Localizable.stringsdict; sourceTree = ""; @@ -4656,6 +4666,7 @@ DB4B779026CA504900B087B3 /* fr */, DB4B779126CA504A00B087B3 /* ja */, DB4B779626CA50BA00B087B3 /* th */, + DBDC1CFD272C0FD600055C3D /* ku-TR */, ); name = Intents.stringsdict; sourceTree = ""; diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index 879d9105..e0afdf7f 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ AppShared.xcscheme_^#shared#^_ orderHint - 35 + 36 CoreDataStack.xcscheme_^#shared#^_ orderHint - 36 + 35 Mastodon - ASDK.xcscheme_^#shared#^_ @@ -97,7 +97,7 @@ MastodonIntent.xcscheme_^#shared#^_ orderHint - 37 + 40 MastodonIntents.xcscheme_^#shared#^_ diff --git a/Mastodon/Resources/ku-TR.lproj/InfoPlist.strings b/Mastodon/Resources/ku-TR.lproj/InfoPlist.strings new file mode 100644 index 00000000..669ecfac --- /dev/null +++ b/Mastodon/Resources/ku-TR.lproj/InfoPlist.strings @@ -0,0 +1,4 @@ +"NSCameraUsageDescription" = "Bo kişandina wêneyê ji bo rewşa şandiyan tê bikaranîn"; +"NSPhotoLibraryAddUsageDescription" = "Ji bo tomarkirina wêneyê di pirtûkxaneya wêneyan de tê bikaranîn"; +"NewPostShortcutItemTitle" = "Şandiya nû"; +"SearchShortcutItemTitle" = "Bigere"; \ No newline at end of file diff --git a/Mastodon/Resources/ku-TR.lproj/Localizable.strings b/Mastodon/Resources/ku-TR.lproj/Localizable.strings new file mode 100644 index 00000000..345f10cf --- /dev/null +++ b/Mastodon/Resources/ku-TR.lproj/Localizable.strings @@ -0,0 +1,346 @@ +"Common.Alerts.BlockDomain.BlockEntireDomain" = "Navperê asteng bike"; +"Common.Alerts.BlockDomain.Title" = "Tu ji xwe bawerî, bi rastî tu dixwazî hemû %@ asteng bikî? Di gelek rewşan de asteng kirin an jî bêdeng kirin têrê dike û tê tercîh kirin. Tu nikarî naveroka vê navperê di demnameyê an jî agahdariyên xwe de bibînî. Şopînerên te yê di vê navperê were jêbirin."; +"Common.Alerts.CleanCache.Message" = "Pêşbîra %@ biserketî hate paqijkirin."; +"Common.Alerts.CleanCache.Title" = "Pêşbîrê paqij bike"; +"Common.Alerts.Common.PleaseTryAgain" = "Ji kerema xwe dîsa biceribîne."; +"Common.Alerts.Common.PleaseTryAgainLater" = "Ji kerema xwe paşê dîsa biceribîne."; +"Common.Alerts.DeletePost.Delete" = "Jê bibe"; +"Common.Alerts.DeletePost.Title" = "Ma tu dixwazî vê şandiyê jê bibî?"; +"Common.Alerts.DiscardPostContent.Message" = "Piştrast bikin ku naveroka posteyê ya hatîye nivîsandin jê bibin."; +"Common.Alerts.DiscardPostContent.Title" = "Reşnivîs jêbibe"; +"Common.Alerts.EditProfileFailure.Message" = "Nikare profîlê serrast bike. Jkx dîsa biceribîne."; +"Common.Alerts.EditProfileFailure.Title" = "Çewtiya profîlê biguherîne"; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.MoreThanOneVideo" = "Nikare ji bêtirî yek vîdyoyekê tevlî şandiyê bike."; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.VideoAttachWithPhoto" = "Nikare vîdyoyekê tevlî şandiyê ku berê wêne tê de heye bike."; +"Common.Alerts.PublishPostFailure.Message" = "Weşandina şandiyê têkçû. +Jkx girêdana înternetê xwe kontrol bike."; +"Common.Alerts.PublishPostFailure.Title" = "Weşandin têkçû"; +"Common.Alerts.SavePhotoFailure.Message" = "Ji kerema xwe destûra gihîştina pirtûkxaneya wêneyê çalak bikin da ku wêneyê hilînin."; +"Common.Alerts.SavePhotoFailure.Title" = "Tomarkirina wêneyê têkçû"; +"Common.Alerts.ServerError.Title" = "Çewtiya rajekar"; +"Common.Alerts.SignOut.Confirm" = "Derkeve"; +"Common.Alerts.SignOut.Message" = "Ma tu dixwazî ku derkevî?"; +"Common.Alerts.SignOut.Title" = "Derkeve"; +"Common.Alerts.SignUpFailure.Title" = "Tomarkirin têkçû"; +"Common.Alerts.VoteFailure.PollEnded" = "Rapirsîya qediya"; +"Common.Alerts.VoteFailure.Title" = "Dengdayîn têkçû"; +"Common.Controls.Actions.Add" = "Tevlî bike"; +"Common.Controls.Actions.Back" = "Vegere"; +"Common.Controls.Actions.BlockDomain" = "%@ asteng bike"; +"Common.Controls.Actions.Cancel" = "Dev jê berde"; +"Common.Controls.Actions.Confirm" = "Bipejirîne"; +"Common.Controls.Actions.Continue" = "Bidomîne"; +"Common.Controls.Actions.CopyPhoto" = "Wêne kopî bikin"; +"Common.Controls.Actions.Delete" = "Jê bibe"; +"Common.Controls.Actions.Discard" = "Biavêje"; +"Common.Controls.Actions.Done" = "Qediya"; +"Common.Controls.Actions.Edit" = "Serrast bike"; +"Common.Controls.Actions.FindPeople" = "Kesên ku bişopînin bibînin"; +"Common.Controls.Actions.ManuallySearch" = "Ji devlê i destan lêgerînê bike"; +"Common.Controls.Actions.Next" = "Pêş"; +"Common.Controls.Actions.Ok" = "BAŞ E"; +"Common.Controls.Actions.Open" = "Veke"; +"Common.Controls.Actions.OpenInSafari" = "Di Safariyê de veke"; +"Common.Controls.Actions.Preview" = "Pêşdîtin"; +"Common.Controls.Actions.Previous" = "Paş"; +"Common.Controls.Actions.Remove" = "Rake"; +"Common.Controls.Actions.Reply" = "Bersivê bide"; +"Common.Controls.Actions.ReportUser" = "%@ ragihîne"; +"Common.Controls.Actions.Save" = "Tomar bike"; +"Common.Controls.Actions.SavePhoto" = "Wêneyê hilîne"; +"Common.Controls.Actions.SeeMore" = "Bêtir bibîne"; +"Common.Controls.Actions.Settings" = "Sazkarî"; +"Common.Controls.Actions.Share" = "Parve bike"; +"Common.Controls.Actions.SharePost" = "Şandiyê parve bike"; +"Common.Controls.Actions.ShareUser" = "%@ parve bike"; +"Common.Controls.Actions.SignIn" = "Têkeve"; +"Common.Controls.Actions.SignUp" = "Tomar bibe"; +"Common.Controls.Actions.Skip" = "Derbas bike"; +"Common.Controls.Actions.TakePhoto" = "Wêne bikişîne"; +"Common.Controls.Actions.TryAgain" = "Dîsa biceribîne"; +"Common.Controls.Actions.UnblockDomain" = "%@ asteng neke"; +"Common.Controls.Friendship.Block" = "Asteng bike"; +"Common.Controls.Friendship.BlockDomain" = "%@ asteng bike"; +"Common.Controls.Friendship.BlockUser" = "%@ asteng bike"; +"Common.Controls.Friendship.Blocked" = "Astengkirî"; +"Common.Controls.Friendship.EditInfo" = "Zanyariyan serrast bike"; +"Common.Controls.Friendship.Follow" = "Bişopîne"; +"Common.Controls.Friendship.Following" = "Dişopîne"; +"Common.Controls.Friendship.Mute" = "Bêdeng bike"; +"Common.Controls.Friendship.MuteUser" = "%@ bêdeng bike"; +"Common.Controls.Friendship.Muted" = "Bêdengkirî"; +"Common.Controls.Friendship.Pending" = "Tê nirxandin"; +"Common.Controls.Friendship.Request" = "Daxwazên şopandinê"; +"Common.Controls.Friendship.Unblock" = "Astengiyê rake"; +"Common.Controls.Friendship.UnblockUser" = "%@ asteng neke"; +"Common.Controls.Friendship.Unmute" = "Bêdeng neke"; +"Common.Controls.Friendship.UnmuteUser" = "%@ bêdeng neke"; +"Common.Controls.Keyboard.Common.ComposeNewPost" = "Şandiyeke nû binivsîne"; +"Common.Controls.Keyboard.Common.OpenSettings" = "Sazkariyan Veke"; +"Common.Controls.Keyboard.Common.ShowFavorites" = "Bijarteyan nîşan bide"; +"Common.Controls.Keyboard.Common.SwitchToTab" = "Biguherîne bo %@"; +"Common.Controls.Keyboard.SegmentedControl.NextSection" = "Beşa paşê"; +"Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "Beşa berê"; +"Common.Controls.Keyboard.Timeline.NextStatus" = "Şandiya pêş"; +"Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "Profîla nivîskaran veke"; +"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "Profîla nivîskaran veke"; +"Common.Controls.Keyboard.Timeline.OpenStatus" = "Şandiyê veke"; +"Common.Controls.Keyboard.Timeline.PreviewImage" = "Wêneya pêşdîtinê"; +"Common.Controls.Keyboard.Timeline.PreviousStatus" = "Şandeya paş"; +"Common.Controls.Keyboard.Timeline.ReplyStatus" = "Bersivê bide şandiyê"; +"Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "Hişyariya naverokê veke/bigire"; +"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Di postê da Bijartin veke/bigire"; +"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Toggle Reblog on Post"; +"Common.Controls.Status.Actions.Favorite" = "Bijartî"; +"Common.Controls.Status.Actions.Menu" = "Menû"; +"Common.Controls.Status.Actions.Reblog" = "Ji nû ve blog"; +"Common.Controls.Status.Actions.Reply" = "Bersivê bide"; +"Common.Controls.Status.Actions.Unfavorite" = "Nebijare"; +"Common.Controls.Status.Actions.Unreblog" = "Ji nû ve blogkirin betal bikin"; +"Common.Controls.Status.ContentWarning" = "Hişyariya naverokê"; +"Common.Controls.Status.MediaContentWarning" = "Ji bo aşkerakirinê derekî bitikîne"; +"Common.Controls.Status.Poll.Closed" = "Girtî"; +"Common.Controls.Status.Poll.Vote" = "Deng"; +"Common.Controls.Status.ShowPost" = "Şandiyê nîşan bide"; +"Common.Controls.Status.ShowUserProfile" = "Profîla bikarhêner nîşan bide"; +"Common.Controls.Status.Tag.Email" = "E-name"; +"Common.Controls.Status.Tag.Emoji" = "E-name"; +"Common.Controls.Status.Tag.Hashtag" = "Etîket"; +"Common.Controls.Status.Tag.Link" = "Girêdan"; +"Common.Controls.Status.Tag.Mention" = "Behs"; +"Common.Controls.Status.Tag.Url" = "URL"; +"Common.Controls.Status.UserReblogged" = "%@ ji nû ve hat blogkirin"; +"Common.Controls.Status.UserRepliedTo" = "Bersiv da %@"; +"Common.Controls.Tabs.Home" = "Serrûpel"; +"Common.Controls.Tabs.Notification" = "Agahdarî"; +"Common.Controls.Tabs.Profile" = "Profîl"; +"Common.Controls.Tabs.Search" = "Bigere"; +"Common.Controls.Timeline.Filtered" = "Parzûnkirî"; +"Common.Controls.Timeline.Header.BlockedWarning" = "Tu nikarî profîla vî bikarhênerî bibînî +heta ku astengîya te rakin."; +"Common.Controls.Timeline.Header.BlockingWarning" = "Tu nikarî profîla vî bikarhênerî bibînî +Heta ku tu wan asteng bikî. +Profîla te ji wan ra wiha xuya dike."; +"Common.Controls.Timeline.Header.NoStatusFound" = "Şandî nehate dîtin"; +"Common.Controls.Timeline.Header.SuspendedWarning" = "Ev bikarhêner hat sekinandin."; +"Common.Controls.Timeline.Header.UserBlockedWarning" = "Tu nikarî profîla %@ bibînî +Heta ku astengîya te rakin."; +"Common.Controls.Timeline.Header.UserBlockingWarning" = "Tu nikarî profîla %@ bibînî +Heta ku tu wan asteng bikî. +Profîla te ji wan ra wiha xuya dike."; +"Common.Controls.Timeline.Header.UserSuspendedWarning" = "Hesaba %@ hat sekinandin."; +"Common.Controls.Timeline.Loader.LoadMissingPosts" = "Barkirina posteyên kêm"; +"Common.Controls.Timeline.Loader.LoadingMissingPosts" = "Barkirina posteyên kêm..."; +"Common.Controls.Timeline.Loader.ShowMoreReplies" = "Bêtir bersivan nîşan bide"; +"Common.Controls.Timeline.Timestamp.Now" = "Niha"; +"Scene.AccountList.AddAccount" = "Ajimêr tevlî bike"; +"Scene.AccountList.DismissAccountSwitcher" = "Dismiss Account Switcher"; +"Scene.AccountList.TabBarHint" = "Profîla hilbijartî ya niha: %@. Du caran bitikîne û paşê dest bide ser da ku guhêrbara ajimêr were nîşandan"; +"Scene.Compose.Accessibility.AppendAttachment" = "Pêvek tevlî bike"; +"Scene.Compose.Accessibility.AppendPoll" = "Rapirsî tevlî bike"; +"Scene.Compose.Accessibility.CustomEmojiPicker" = "Custom Emoji Picker"; +"Scene.Compose.Accessibility.DisableContentWarning" = "Hişyariya naverokê neçalak bike"; +"Scene.Compose.Accessibility.EnableContentWarning" = "Enable Content Warning"; +"Scene.Compose.Accessibility.PostVisibilityMenu" = "Menuya Xuyabûna Şandiyê"; +"Scene.Compose.Accessibility.RemovePoll" = "Rapirsî rake"; +"Scene.Compose.Attachment.AttachmentBroken" = "Ev %@ naxebite û nayê barkirin + li ser Mastodon."; +"Scene.Compose.Attachment.DescriptionPhoto" = "Describe the photo for the visually-impaired..."; +"Scene.Compose.Attachment.DescriptionVideo" = "Describe the video for the visually-impaired..."; +"Scene.Compose.Attachment.Photo" = "wêne"; +"Scene.Compose.Attachment.Video" = "vîdyo"; +"Scene.Compose.AutoComplete.SpaceToAdd" = "Space to add"; +"Scene.Compose.ComposeAction" = "Biweşîne"; +"Scene.Compose.ContentInputPlaceholder" = "Type or paste what’s on your mind"; +"Scene.Compose.ContentWarning.Placeholder" = "Write an accurate warning here..."; +"Scene.Compose.Keyboard.AppendAttachmentEntry" = "Pêvek lê zêde bike - %@"; +"Scene.Compose.Keyboard.DiscardPost" = "Şandî bihelîne"; +"Scene.Compose.Keyboard.PublishPost" = "Şandiye bide weşan"; +"Scene.Compose.Keyboard.SelectVisibilityEntry" = "Xuyanîbûn hilbijêre - %@"; +"Scene.Compose.Keyboard.ToggleContentWarning" = "Hişyariya naverokê veke/bigire"; +"Scene.Compose.Keyboard.TogglePoll" = "Anketê veke/bigire"; +"Scene.Compose.MediaSelection.Browse" = "Bigere"; +"Scene.Compose.MediaSelection.Camera" = "Wêne bikişîne"; +"Scene.Compose.MediaSelection.PhotoLibrary" = "Wênegeh"; +"Scene.Compose.Poll.DurationTime" = "Dirêjî: %@"; +"Scene.Compose.Poll.OneDay" = "1 Roj"; +"Scene.Compose.Poll.OneHour" = "1 Demjimêr"; +"Scene.Compose.Poll.OptionNumber" = "Vebijêrk %ld"; +"Scene.Compose.Poll.SevenDays" = "7 Roj"; +"Scene.Compose.Poll.SixHours" = "6 Demjimêr"; +"Scene.Compose.Poll.ThirtyMinutes" = "30 xulek"; +"Scene.Compose.Poll.ThreeDays" = "3 Roj"; +"Scene.Compose.ReplyingToUser" = "bersiv bide %@"; +"Scene.Compose.Title.NewPost" = "Şandiya nû"; +"Scene.Compose.Title.NewReply" = "Bersiva nû"; +"Scene.Compose.Visibility.Direct" = "Tenê mirovên ku min qalkirî"; +"Scene.Compose.Visibility.Private" = "Tenê şopîneran"; +"Scene.Compose.Visibility.Public" = "Gelemperî"; +"Scene.Compose.Visibility.Unlisted" = "Nerêzokkirî"; +"Scene.ConfirmEmail.Button.DontReceiveEmail" = "Min hîç e-nameyeke nesitand"; +"Scene.ConfirmEmail.Button.OpenEmailApp" = "Sepana e-nameyê veke"; +"Scene.ConfirmEmail.DontReceiveEmail.Description" = "Kontrol bike ka navnîşana e-nameya te rast e û her wiha peldanka xwe ya spam."; +"Scene.ConfirmEmail.DontReceiveEmail.ResendEmail" = "E-namyê yê dîsa bişîne"; +"Scene.ConfirmEmail.DontReceiveEmail.Title" = "E-nameyê xwe kontrol bike"; +"Scene.ConfirmEmail.OpenEmailApp.Description" = "We just sent you an email. Check your junk folder if you haven’t."; +"Scene.ConfirmEmail.OpenEmailApp.Mail" = "E-name"; +"Scene.ConfirmEmail.OpenEmailApp.OpenEmailClient" = "Rajegirê e-nameyê veke"; +"Scene.ConfirmEmail.OpenEmailApp.Title" = "Check your inbox."; +"Scene.ConfirmEmail.Subtitle" = "We just sent an email to %@, +tap the link to confirm your account."; +"Scene.ConfirmEmail.Title" = "Tiştekî dawî."; +"Scene.Favorite.Title" = "Bijareyên te"; +"Scene.HomeTimeline.NavigationBarState.NewPosts" = "Şandiyên nû bibîne"; +"Scene.HomeTimeline.NavigationBarState.Offline" = "Derhêl"; +"Scene.HomeTimeline.NavigationBarState.Published" = "Hate weşandin!"; +"Scene.HomeTimeline.NavigationBarState.Publishing" = "Şandî tê weşandin..."; +"Scene.HomeTimeline.Title" = "Serrûpel"; +"Scene.Notification.Keyobard.ShowEverything" = "Her tiştî nîşan bide"; +"Scene.Notification.Keyobard.ShowMentions" = "Behskirîya nîşan bike"; +"Scene.Notification.Title.Everything" = "Her tişt"; +"Scene.Notification.Title.Mentions" = "Behs"; +"Scene.Notification.UserFavorited Your Post" = "%@ posta we bijarte"; +"Scene.Notification.UserFollowedYou" = "%@ te şopand"; +"Scene.Notification.UserMentionedYou" = "%@ behsa te kir"; +"Scene.Notification.UserRebloggedYourPost" = "%@ posta we ji nû ve tomar kir"; +"Scene.Notification.UserRequestedToFollowYou" = "%@ daxwaza şopandina te kir"; +"Scene.Notification.UserYourPollHasEnded" = "%@ Anketa te qediya"; +"Scene.Preview.Keyboard.ClosePreview" = "Pêşdîtin bigire"; +"Scene.Preview.Keyboard.ShowNext" = "A pêş nîşan bide"; +"Scene.Preview.Keyboard.ShowPrevious" = "A paş nîşan bide"; +"Scene.Profile.Dashboard.Followers" = "şopîneran"; +"Scene.Profile.Dashboard.Following" = "dişopîne"; +"Scene.Profile.Dashboard.Posts" = "şandîyan"; +"Scene.Profile.Fields.AddRow" = "Rêzê lê zêde bike"; +"Scene.Profile.Fields.Placeholder.Content" = "Naverok"; +"Scene.Profile.Fields.Placeholder.Label" = "Nîşan"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUsre.Message" = "Ji bo rakirina blokê bipejirin %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUsre.Title" = "Hesabê ji bloke rake"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "Ji bo vekirina bê dengkirinê bipejirin %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Title" = "Hesabê ji bê deng rake"; +"Scene.Profile.SegmentedControl.Media" = "Medya"; +"Scene.Profile.SegmentedControl.Posts" = "Şandîyan"; +"Scene.Profile.SegmentedControl.Replies" = "Bersivan"; +"Scene.Register.Error.Item.Agreement" = "Lihevhatin"; +"Scene.Register.Error.Item.Email" = "E-name"; +"Scene.Register.Error.Item.Locale" = "Herêm"; +"Scene.Register.Error.Item.Password" = "Şîfre"; +"Scene.Register.Error.Item.Reason" = "Sedem"; +"Scene.Register.Error.Item.Username" = "Navê bikarhêner"; +"Scene.Register.Error.Reason.Accepted" = "%@ divê were qebûlkirin"; +"Scene.Register.Error.Reason.Blank" = "%@ pêwist e"; +"Scene.Register.Error.Reason.Blocked" = "%@ peydekerê e-nameya bêdestûr dihewîne"; +"Scene.Register.Error.Reason.Inclusion" = "%@ nirxeke ku tê destekirin nîn e"; +"Scene.Register.Error.Reason.Invalid" = "%@ ne derbasdar e"; +"Scene.Register.Error.Reason.Reserved" = "%@ peyveke mifteya veqetandî ye"; +"Scene.Register.Error.Reason.Taken" = "%@ jixwe tê bikaranîn"; +"Scene.Register.Error.Reason.TooLong" = "%@ gelekî dirêj e"; +"Scene.Register.Error.Reason.TooShort" = "%@ pir kurt e"; +"Scene.Register.Error.Reason.Unreachable" = "%@ xuya nake"; +"Scene.Register.Error.Special.EmailInvalid" = "Ev ne navnîşana e-nameyek derbasdar e"; +"Scene.Register.Error.Special.PasswordTooShort" = "Şîfre pir kurt e (divê herî kêm 8 tîpan be)"; +"Scene.Register.Error.Special.UsernameInvalid" = "Navê bikarhêner divê tenê tîpên alfanumerîk û binxet hebe"; +"Scene.Register.Error.Special.UsernameTooLong" = "Navê bikarhêner pir dirêj e (ji 30 tîpan dirêjtir nabe)"; +"Scene.Register.Input.Avatar.Delete" = "Jê bibe"; +"Scene.Register.Input.DisplayName.Placeholder" = "navê nîşanê"; +"Scene.Register.Input.Email.Placeholder" = "e-name"; +"Scene.Register.Input.Invite.RegistrationUserInviteRequest" = "Tu çima dixwazî beşdar bibî?"; +"Scene.Register.Input.Password.Hint" = "Şîfreya we herî kêm heşt tîpan hewce dike"; +"Scene.Register.Input.Password.Placeholder" = "şîfre"; +"Scene.Register.Input.Username.DuplicatePrompt" = "Navê vê bikarhêner tê girtin."; +"Scene.Register.Input.Username.Placeholder" = "navê bikarhêner"; +"Scene.Register.Title" = "Ji me re hinekî qala xwe bike."; +"Scene.Report.Content1" = "Are there any other posts you’d like to add to the report?"; +"Scene.Report.Content2" = "Is there anything the moderators should know about this report?"; +"Scene.Report.Send" = "Ragihandinê bişîne"; +"Scene.Report.SkipToSend" = "Bêyî şirove bişîne"; +"Scene.Report.Step1" = "Gav 1 ji 2"; +"Scene.Report.Step2" = "Gav 2 ji 2"; +"Scene.Report.TextPlaceholder" = "Type or paste additional comments"; +"Scene.Report.Title" = "%@ ragihîne"; +"Scene.Search.Recommend.Accounts.Description" = "Dibe ku tu bixwazî van hesaban bişopînî"; +"Scene.Search.Recommend.Accounts.Follow" = "Bişopîne"; +"Scene.Search.Recommend.Accounts.Title" = "Hesabên ku hûn dikarin hez bikin"; +"Scene.Search.Recommend.ButtonText" = "Hemûyé bibîne"; +"Scene.Search.Recommend.HashTag.Description" = "Etîketên ku pir balê dikişînin"; +"Scene.Search.Recommend.HashTag.PeopleTalking" = "%@ kes diaxivin"; +"Scene.Search.Recommend.HashTag.Title" = "Trend li ser Mastodon"; +"Scene.Search.SearchBar.Cancel" = "Betal kirin"; +"Scene.Search.SearchBar.Placeholder" = "Li etîketan û bikarhêneran bigerin"; +"Scene.Search.Searching.Clear" = "Paqij bike"; +"Scene.Search.Searching.EmptyState.NoResults" = "Encam tune"; +"Scene.Search.Searching.RecentSearch" = "Lêgerînên dawî"; +"Scene.Search.Searching.Segment.All" = "Hemû"; +"Scene.Search.Searching.Segment.Hashtags" = "Etîketan"; +"Scene.Search.Searching.Segment.People" = "Mirov"; +"Scene.Search.Searching.Segment.Posts" = "Şandîyan"; +"Scene.Search.Title" = "Bigere"; +"Scene.ServerPicker.Button.Category.Academia" = "akademî"; +"Scene.ServerPicker.Button.Category.Activism" = "çalakî"; +"Scene.ServerPicker.Button.Category.All" = "Hemû"; +"Scene.ServerPicker.Button.Category.AllAccessiblityDescription" = "Beş: Hemû"; +"Scene.ServerPicker.Button.Category.Art" = "huner"; +"Scene.ServerPicker.Button.Category.Food" = "xwarin"; +"Scene.ServerPicker.Button.Category.Furry" = "furry"; +"Scene.ServerPicker.Button.Category.Games" = "lîsk"; +"Scene.ServerPicker.Button.Category.General" = "giştî"; +"Scene.ServerPicker.Button.Category.Journalism" = "rojnamevanî"; +"Scene.ServerPicker.Button.Category.Lgbt" = "lgbt"; +"Scene.ServerPicker.Button.Category.Music" = "muzîk"; +"Scene.ServerPicker.Button.Category.Regional" = "herêmî"; +"Scene.ServerPicker.Button.Category.Tech" = "teknolojî"; +"Scene.ServerPicker.Button.SeeLess" = "Kêmtir bibîne"; +"Scene.ServerPicker.Button.SeeMore" = "Bêtir bibîne"; +"Scene.ServerPicker.EmptyState.BadNetwork" = "Di dema barkirina daneyan da tiştek xelet derket. Girêdana xwe ya înternetê kontrol bike."; +"Scene.ServerPicker.EmptyState.FindingServers" = "Dîtina serverên berdest..."; +"Scene.ServerPicker.EmptyState.NoResults" = "Encam nade"; +"Scene.ServerPicker.Input.Placeholder" = "Serverek bibînin an jî beşdarî ya xwe bibin..."; +"Scene.ServerPicker.Label.Category" = "KATEGORÎ"; +"Scene.ServerPicker.Label.Language" = "ZIMAN"; +"Scene.ServerPicker.Label.Users" = "BIKARHÊNER"; +"Scene.ServerPicker.Title" = "Rajekarekê hilbijêre, +Her kîjan rajekar be."; +"Scene.ServerRules.Button.Confirm" = "Ez tev dibim"; +"Scene.ServerRules.PrivacyPolicy" = "polîtîkaya nepenîtiyê"; +"Scene.ServerRules.Prompt" = "Bi berdewamî, hûn ji bo %@ di bin şertên polîtîkaya xizmet û nepenîtiyê da ne."; +"Scene.ServerRules.Subtitle" = "Ev rêzik ji aliyê rêvebirên %@ ve tên sazkirin."; +"Scene.ServerRules.TermsOfService" = "şert û mercên xizmetê"; +"Scene.ServerRules.Title" = "Hin qaîdeyên bingehîn."; +"Scene.Settings.Footer.MastodonDescription" = "Mastodon is open source software. You can report issues on GitHub at %@ (%@)"; +"Scene.Settings.Keyboard.CloseSettingsWindow" = "Close Settings Window"; +"Scene.Settings.Section.Appearance.Automatic" = "Xweber"; +"Scene.Settings.Section.Appearance.Dark" = "Her dem tarî"; +"Scene.Settings.Section.Appearance.Light" = "Her dem ronî"; +"Scene.Settings.Section.Appearance.Title" = "Xuyang"; +"Scene.Settings.Section.BoringZone.AccountSettings" = "Account Settings"; +"Scene.Settings.Section.BoringZone.Privacy" = "Privacy Policy"; +"Scene.Settings.Section.BoringZone.Terms" = "Terms of Service"; +"Scene.Settings.Section.BoringZone.Title" = "The Boring Zone"; +"Scene.Settings.Section.Notifications.Boosts" = "Reblogs my post"; +"Scene.Settings.Section.Notifications.Favorites" = "Şandiyên min hez kir"; +"Scene.Settings.Section.Notifications.Follows" = "Min şopand"; +"Scene.Settings.Section.Notifications.Mentions" = "Qale min kir"; +"Scene.Settings.Section.Notifications.Title" = "Agahdarî"; +"Scene.Settings.Section.Notifications.Trigger.Anyone" = "her kes"; +"Scene.Settings.Section.Notifications.Trigger.Follow" = "her kesê ku dişopînim"; +"Scene.Settings.Section.Notifications.Trigger.Follower" = "şopînerek"; +"Scene.Settings.Section.Notifications.Trigger.Noone" = "ne yek"; +"Scene.Settings.Section.Notifications.Trigger.Title" = "Min agahdar bike gava"; +"Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Disable animated avatars"; +"Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Disable animated emojis"; +"Scene.Settings.Section.Preference.Title" = "Hilbijarte"; +"Scene.Settings.Section.Preference.TrueBlackDarkMode" = "True black dark mode"; +"Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Use default browser to open links"; +"Scene.Settings.Section.SpicyZone.Clear" = "Clear Media Cache"; +"Scene.Settings.Section.SpicyZone.Signout" = "Sign Out"; +"Scene.Settings.Section.SpicyZone.Title" = "The Spicy Zone"; +"Scene.Settings.Title" = "Sazkarî"; +"Scene.SuggestionAccount.FollowExplain" = "Gava tu kesekî dişopînî, tu yê şandiyê wan di serrûpelê de bibîne."; +"Scene.SuggestionAccount.Title" = "Kesên bo ku bişopînî bibîne"; +"Scene.Thread.BackTitle" = "Şandî"; +"Scene.Thread.Title" = "Post from %@"; +"Scene.Welcome.Slogan" = "Torên civakî +di destên te de."; +"Scene.Wizard.AccessibilityHint" = "Double tap to dismiss this wizard"; +"Scene.Wizard.MultipleAccountSwitchIntroDescription" = "Dest bide ser bişkoja profîlê da ku di navbera gelek ajimêrann de biguherînî."; +"Scene.Wizard.NewInMastodon" = "Nû di Mastodon de"; \ No newline at end of file diff --git a/Mastodon/Resources/ku-TR.lproj/Localizable.stringsdict b/Mastodon/Resources/ku-TR.lproj/Localizable.stringsdict new file mode 100644 index 00000000..064b8bf2 --- /dev/null +++ b/Mastodon/Resources/ku-TR.lproj/Localizable.stringsdict @@ -0,0 +1,390 @@ + + + + + a11y.plural.count.unread.notification + + NSStringLocalizedFormatKey + %#@notification_count_unread_notification@ + notification_count_unread_notification + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 agahdariya nexwendî + other + %ld agahdariyên nexwendî + + + a11y.plural.count.input_limit_exceeds + + NSStringLocalizedFormatKey + Sînorê têketinê derbas kir %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 tîp + other + %ld tîp + + + a11y.plural.count.input_limit_remains + + NSStringLocalizedFormatKey + Sînorê têketinê %#@character_count@ maye + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 tîp + other + %ld tîp + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@ %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + şandî + other + şandî + + + plural.count.post + + NSStringLocalizedFormatKey + %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 şandî + other + %ld şandî + + + plural.count.favorite + + NSStringLocalizedFormatKey + %#@favorite_count@ + favorite_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 hezkirin + other + %ld hezkirin + + + plural.count.reblog + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reblog + other + %ld reblogs + + + plural.count.vote + + NSStringLocalizedFormatKey + %#@vote_count@ + vote_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 deng + other + %ld deng + + + plural.count.voter + + NSStringLocalizedFormatKey + %#@voter_count@ + voter_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 hilbijêr + other + %ld hilbijêr + + + plural.people_talking + + NSStringLocalizedFormatKey + %#@count_people_talking@ + count_people_talking + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 mirov diaxive + other + %ld mirov diaxive + + + plural.count.following + + NSStringLocalizedFormatKey + %#@count_following@ + count_following + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 dişopîne + other + %ld dişopîne + + + plural.count.follower + + NSStringLocalizedFormatKey + %#@count_follower@ + count_follower + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 şopîner + other + %ld şopîner + + + date.year.left + + NSStringLocalizedFormatKey + %#@count_year_left@ + count_year_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 sal berê + other + %ld sal berê + + + date.month.left + + NSStringLocalizedFormatKey + %#@count_month_left@ + count_month_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 meh berê + other + %ld meh berê + + + date.day.left + + NSStringLocalizedFormatKey + %#@count_day_left@ + count_day_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 roj berê + other + %ld roj berê + + + date.hour.left + + NSStringLocalizedFormatKey + %#@count_hour_left@ + count_hour_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 demjimêr berê + other + %ld demjimêr berê + + + date.minute.left + + NSStringLocalizedFormatKey + %#@count_minute_left@ + count_minute_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 xulek berê + other + %ld xulek berê + + + date.second.left + + NSStringLocalizedFormatKey + %#@count_second_left@ + count_second_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 çirke berê + other + %ld çirke berê + + + date.year.ago.abbr + + NSStringLocalizedFormatKey + %#@count_year_ago_abbr@ + count_year_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 sal berê + other + %ld sal berê + + + date.month.ago.abbr + + NSStringLocalizedFormatKey + %#@count_month_ago_abbr@ + count_month_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 xulek berê + other + %ld xulek berê + + + date.day.ago.abbr + + NSStringLocalizedFormatKey + %#@count_day_ago_abbr@ + count_day_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 roj berê + other + %ld roj berê + + + date.hour.ago.abbr + + NSStringLocalizedFormatKey + %#@count_hour_ago_abbr@ + count_hour_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 demjimêr berê + other + %ld demjimêr berê + + + date.minute.ago.abbr + + NSStringLocalizedFormatKey + %#@count_minute_ago_abbr@ + count_minute_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 xulek berê + other + %ld xulek berê + + + date.second.ago.abbr + + NSStringLocalizedFormatKey + %#@count_second_ago_abbr@ + count_second_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 çirke berê + other + %ld çirke berê + + + + diff --git a/MastodonIntent/ku-TR.lproj/Intents.strings b/MastodonIntent/ku-TR.lproj/Intents.strings new file mode 100644 index 00000000..3e1c69fc --- /dev/null +++ b/MastodonIntent/ku-TR.lproj/Intents.strings @@ -0,0 +1,51 @@ +"16wxgf" = "Di Mastodon de biweşîne"; + +"751xkl" = "Naveroka nivîsê"; + +"CsR7G2" = "Di Mastodon de biweşîne"; + +"HZSGTr" = "Kîjan naverok bila bê şandin?"; + +"HdGikU" = "Şandin têkçû"; + +"KDNTJ4" = "Sedema têkçûnê"; + +"RHxKOw" = "Bi naveroka nivîsî şandiyan bişîne"; + +"RxSqsb" = "Şandî"; + +"WCIR3D" = "${content} biweşîne di Mastodon de"; + +"ZKJSNu" = "Şandî"; + +"ZS1XaK" = "${content}"; + +"ZbSjzC" = "Xuyanî"; + +"Zo4jgJ" = "Xuyaniya şandiyê"; + +"apSxMG-dYQ5NN" = "Vebijarkên ${count} hene ku li gorî 'Giştî' ne."; + +"apSxMG-ehFLjY" = "Vebijarkên ${count} hene ku li gorî 'Tenê Şopandin' hene."; + +"ayoYEb-dYQ5NN" = "${content}, Giştî"; + +"ayoYEb-ehFLjY" = "${content}, Tenê şopînêr"; + +"dUyuGg" = "Li ser Mastodon bişînin"; + +"dYQ5NN" = "Gelemperî"; + +"ehFLjY" = "Tenê şopîneran"; + +"gfePDu" = "Weşandin bi ser neket. ${failureReason}"; + +"k7dbKQ" = "Şandî bi serkeftî hate şandin."; + +"oGiqmY-dYQ5NN" = "Tenê ji bo pejirandinê, we 'Giştî' dixwest?"; + +"oGiqmY-ehFLjY" = "Tenê ji bo piştrastkirinê, we 'Tenê Şopdarên' dixwest?"; + +"rM6dvp" = "Girêdan"; + +"ryJLwG" = "Bi serkeftî hat şandin. "; diff --git a/MastodonIntent/ku-TR.lproj/Intents.stringsdict b/MastodonIntent/ku-TR.lproj/Intents.stringsdict new file mode 100644 index 00000000..5a39d5e6 --- /dev/null +++ b/MastodonIntent/ku-TR.lproj/Intents.stringsdict @@ -0,0 +1,54 @@ + + + + + There are ${count} options matching ‘${content}’. - 2 + + NSStringLocalizedFormatKey + There are %#@count_option@ matching ‘${content}’. + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + zero + 0 options + one + 1 option + two + 2 options + few + %ld options + many + %ld options + other + %ld options + + + There are ${count} options matching ‘${visibility}’. + + NSStringLocalizedFormatKey + There are %#@count_option@ matching ‘${visibility}’. + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + zero + 0 options + one + 1 option + two + 2 options + few + %ld options + many + %ld options + other + %ld options + + + + From d66dfccad0a253201b5861f08a1396240f550a05 Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 29 Oct 2021 19:38:21 +0800 Subject: [PATCH 46/96] chore: update to version 1.2.0 (81) --- AppShared/Info.plist | 2 +- CoreDataStack/Info.plist | 2 +- CoreDataStackTests/Info.plist | 2 +- Mastodon.xcodeproj/project.pbxproj | 64 +++++++++---------- .../xcschemes/xcschememanagement.plist | 8 +-- Mastodon/Info.plist | 2 +- MastodonIntent/Info.plist | 2 +- MastodonTests/Info.plist | 2 +- MastodonUITests/Info.plist | 2 +- NotificationService/Info.plist | 2 +- ShareActionExtension/Info.plist | 2 +- 11 files changed, 45 insertions(+), 45 deletions(-) diff --git a/AppShared/Info.plist b/AppShared/Info.plist index cf50d3ac..9da0855b 100644 --- a/AppShared/Info.plist +++ b/AppShared/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 80 + 81 diff --git a/CoreDataStack/Info.plist b/CoreDataStack/Info.plist index cf50d3ac..9da0855b 100644 --- a/CoreDataStack/Info.plist +++ b/CoreDataStack/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 80 + 81 diff --git a/CoreDataStackTests/Info.plist b/CoreDataStackTests/Info.plist index cf50d3ac..9da0855b 100644 --- a/CoreDataStackTests/Info.plist +++ b/CoreDataStackTests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 80 + 81 diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index cc98e398..1e5021a3 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -4811,7 +4811,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4840,7 +4840,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4948,11 +4948,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 80; + DYLIB_CURRENT_VERSION = 81; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -4979,11 +4979,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 80; + DYLIB_CURRENT_VERSION = 81; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5008,11 +5008,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 80; + DYLIB_CURRENT_VERSION = 81; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = CoreDataStack/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5038,11 +5038,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 80; + DYLIB_CURRENT_VERSION = 81; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = CoreDataStack/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5105,7 +5105,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5130,7 +5130,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5155,7 +5155,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5180,7 +5180,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5205,7 +5205,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5230,7 +5230,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5255,7 +5255,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5280,7 +5280,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5371,7 +5371,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -5438,11 +5438,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 80; + DYLIB_CURRENT_VERSION = 81; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = CoreDataStack/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5487,7 +5487,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5512,11 +5512,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 80; + DYLIB_CURRENT_VERSION = 81; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5608,7 +5608,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -5675,11 +5675,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 80; + DYLIB_CURRENT_VERSION = 81; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = CoreDataStack/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5724,7 +5724,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5749,11 +5749,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 80; + DYLIB_CURRENT_VERSION = 81; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5779,7 +5779,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5803,7 +5803,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 80; + CURRENT_PROJECT_VERSION = 81; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index e0afdf7f..1e918639 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ AppShared.xcscheme_^#shared#^_ orderHint - 36 + 43 CoreDataStack.xcscheme_^#shared#^_ orderHint - 35 + 42 Mastodon - ASDK.xcscheme_^#shared#^_ @@ -97,7 +97,7 @@ MastodonIntent.xcscheme_^#shared#^_ orderHint - 40 + 41 MastodonIntents.xcscheme_^#shared#^_ @@ -117,7 +117,7 @@ ShareActionExtension.xcscheme_^#shared#^_ orderHint - 38 + 44 SuppressBuildableAutocreation diff --git a/Mastodon/Info.plist b/Mastodon/Info.plist index 1c89f12a..dc3e8619 100644 --- a/Mastodon/Info.plist +++ b/Mastodon/Info.plist @@ -30,7 +30,7 @@ CFBundleVersion - 80 + 81 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/MastodonIntent/Info.plist b/MastodonIntent/Info.plist index 062a4bf1..6fabe37e 100644 --- a/MastodonIntent/Info.plist +++ b/MastodonIntent/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 80 + 81 NSExtension NSExtensionAttributes diff --git a/MastodonTests/Info.plist b/MastodonTests/Info.plist index cf50d3ac..9da0855b 100644 --- a/MastodonTests/Info.plist +++ b/MastodonTests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 80 + 81 diff --git a/MastodonUITests/Info.plist b/MastodonUITests/Info.plist index cf50d3ac..9da0855b 100644 --- a/MastodonUITests/Info.plist +++ b/MastodonUITests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 80 + 81 diff --git a/NotificationService/Info.plist b/NotificationService/Info.plist index fb52c9dc..43c0dff3 100644 --- a/NotificationService/Info.plist +++ b/NotificationService/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 80 + 81 NSExtension NSExtensionPointIdentifier diff --git a/ShareActionExtension/Info.plist b/ShareActionExtension/Info.plist index 8870b320..73d43d81 100644 --- a/ShareActionExtension/Info.plist +++ b/ShareActionExtension/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 80 + 81 NSExtension NSExtensionAttributes From 90a24445e3e076890fb2078a1f429139776fc9da Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 29 Oct 2021 21:45:10 +0200 Subject: [PATCH 47/96] New translations app.json (Kurmanji (Kurdish)) --- .../StringsConvertor/input/kmr_TR/app.json | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/Localization/StringsConvertor/input/kmr_TR/app.json b/Localization/StringsConvertor/input/kmr_TR/app.json index 9798c86c..5330ea48 100644 --- a/Localization/StringsConvertor/input/kmr_TR/app.json +++ b/Localization/StringsConvertor/input/kmr_TR/app.json @@ -111,7 +111,7 @@ "open_author_profile": "Profîla nivîskaran veke", "open_reblogger_profile": "Profîla nivîskaran veke", "reply_status": "Bersivê bide şandiyê", - "toggle_reblog": "Toggle Reblog on Post", + "toggle_reblog": "Ji vû nivîsandin di şandiyê de biguherîne", "toggle_favorite": "Di postê da Bijartin veke/bigire", "toggle_content_warning": "Hişyariya naverokê veke/bigire", "preview_image": "Wêneya pêşdîtinê" @@ -294,7 +294,7 @@ }, "confirm_email": { "title": "Tiştekî dawî.", - "subtitle": "We just sent an email to %s,\ntap the link to confirm your account.", + "subtitle": "Me tenê e-nameyek ji %s re şand,\ngirêdanê bitikne da ku ajimêra xwe bidî piştrastkirin.", "button": { "open_email_app": "Sepana e-nameyê veke", "dont_receive_email": "Min hîç e-nameyeke nesitand" @@ -305,8 +305,8 @@ "resend_email": "E-namyê yê dîsa bişîne" }, "open_email_app": { - "title": "Check your inbox.", - "description": "We just sent you an email. Check your junk folder if you haven’t.", + "title": "Nameyên xwe yên wergirtî kontrol bike.", + "description": "Me tenê ji te re e-nameyek şand. Heke nehatiye peldanka xwe ya spamê kontrol bike.", "mail": "E-name", "open_email_client": "Rajegirê e-nameyê veke" } @@ -334,15 +334,15 @@ "photo_library": "Wênegeh", "browse": "Bigere" }, - "content_input_placeholder": "Type or paste what’s on your mind", + "content_input_placeholder": "Tiştê ku di hişê te de ye binivîsin an jî pêve bike", "compose_action": "Biweşîne", "replying_to_user": "bersiv bide %s", "attachment": { "photo": "wêne", "video": "vîdyo", "attachment_broken": "Ev %s naxebite û nayê barkirin\n li ser Mastodon.", - "description_photo": "Describe the photo for the visually-impaired...", - "description_video": "Describe the video for the visually-impaired..." + "description_photo": "Wêneyê ji bo kêmbînên dîtbar bide nasîn...", + "description_video": "Vîdyoyê ji bo kêmbînên dîtbar bide nasîn..." }, "poll": { "duration_time": "Dirêjî: %s", @@ -355,7 +355,7 @@ "option_number": "Vebijêrk %ld" }, "content_warning": { - "placeholder": "Write an accurate warning here..." + "placeholder": "Li vir hişyariyek hûrgilî binivîsine..." }, "visibility": { "public": "Gelemperî", @@ -364,14 +364,14 @@ "direct": "Tenê mirovên ku min qalkirî" }, "auto_complete": { - "space_to_add": "Space to add" + "space_to_add": "Bicîhkirinê tevlî bike" }, "accessibility": { "append_attachment": "Pêvek tevlî bike", "append_poll": "Rapirsî tevlî bike", "remove_poll": "Rapirsî rake", - "custom_emoji_picker": "Custom Emoji Picker", - "enable_content_warning": "Enable Content Warning", + "custom_emoji_picker": "Hilbijêrê emojî yên kesanekirî", + "enable_content_warning": "Hişyariya naverokê neçalak bike", "disable_content_warning": "Hişyariya naverokê neçalak bike", "post_visibility_menu": "Menuya Xuyabûna Şandiyê" }, @@ -467,7 +467,7 @@ }, "thread": { "back_title": "Şandî", - "title": "Post from %s" + "title": "Şandî ji %s" }, "settings": { "title": "Sazkarî", @@ -482,7 +482,7 @@ "title": "Agahdarî", "favorites": "Şandiyên min hez kir", "follows": "Min şopand", - "boosts": "Reblogs my post", + "boosts": "Şandiya min ji nû ve binivîsine", "mentions": "Qale min kir", "trigger": { "anyone": "her kes", @@ -494,39 +494,39 @@ }, "preference": { "title": "Hilbijarte", - "true_black_dark_mode": "True black dark mode", - "disable_avatar_animation": "Disable animated avatars", - "disable_emoji_animation": "Disable animated emojis", - "using_default_browser": "Use default browser to open links" + "true_black_dark_mode": "Moda tarî ya reş a rastîn", + "disable_avatar_animation": "Avatarên anîmasyonî neçalak bike", + "disable_emoji_animation": "Emojiyên anîmasyonî neçalak bike", + "using_default_browser": "Ji bo vekirina girêdanan geroka berdest bi kar bîne" }, "boring_zone": { - "title": "The Boring Zone", - "account_settings": "Account Settings", - "terms": "Terms of Service", - "privacy": "Privacy Policy" + "title": "Devera acizker", + "account_settings": "Sazkariyên ajimêr", + "terms": "Mercên bikaranînê", + "privacy": "Polîtikaya nihêniyê" }, "spicy_zone": { - "title": "The Spicy Zone", - "clear": "Clear Media Cache", - "signout": "Sign Out" + "title": "Devera germ", + "clear": "Pêşbîra medyayê pak bike", + "signout": "Derkeve" } }, "footer": { - "mastodon_description": "Mastodon is open source software. You can report issues on GitHub at %s (%s)" + "mastodon_description": "Mastodon nermalava çavkaniya vekirî ye. Tu dikarî pirsgirêkan li ser GitHub-ê ragihînî di %s (%s) de" }, "keyboard": { - "close_settings_window": "Close Settings Window" + "close_settings_window": "Sazkariyên çarçoveyê bigire" } }, "report": { "title": "%s ragihîne", "step1": "Gav 1 ji 2", "step2": "Gav 2 ji 2", - "content1": "Are there any other posts you’d like to add to the report?", - "content2": "Is there anything the moderators should know about this report?", + "content1": "Şandiyên din hene ku tu dixwazî tevlî ragihandinê bikî?", + "content2": "Derbarê vê ragihandinê de tiştek heye ku divê çavdêr bizanin?", "send": "Ragihandinê bişîne", "skip_to_send": "Bêyî şirove bişîne", - "text_placeholder": "Type or paste additional comments" + "text_placeholder": "Şiroveyên daxwazkirê binivîsine an jî pê ve bike" }, "preview": { "keyboard": { @@ -537,13 +537,13 @@ }, "account_list": { "tab_bar_hint": "Profîla hilbijartî ya niha: %s. Du caran bitikîne û paşê dest bide ser da ku guhêrbara ajimêr were nîşandan", - "dismiss_account_switcher": "Dismiss Account Switcher", + "dismiss_account_switcher": "Guherkera ajimêrê paş guh bike", "add_account": "Ajimêr tevlî bike" }, "wizard": { "new_in_mastodon": "Nû di Mastodon de", "multiple_account_switch_intro_description": "Dest bide ser bişkoja profîlê da ku di navbera gelek ajimêrann de biguherînî.", - "accessibility_hint": "Double tap to dismiss this wizard" + "accessibility_hint": "Du caran bitikîne da ku çarçoveyahilpekok ji holê rakî" } } } \ No newline at end of file From 061ed1b4b197614b4efb15db5ebd7f5d62b9c959 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 29 Oct 2021 21:45:11 +0200 Subject: [PATCH 48/96] New translations Localizable.stringsdict (Kurmanji (Kurdish)) --- .../StringsConvertor/input/kmr_TR/Localizable.stringsdict | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Localization/StringsConvertor/input/kmr_TR/Localizable.stringsdict b/Localization/StringsConvertor/input/kmr_TR/Localizable.stringsdict index 064b8bf2..8ae1b812 100644 --- a/Localization/StringsConvertor/input/kmr_TR/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/kmr_TR/Localizable.stringsdict @@ -109,9 +109,9 @@ NSStringFormatValueTypeKey ld one - 1 reblog + 1 ji nû ve nivîsandin other - %ld reblogs + %ld ji nû ve nivîsandin plural.count.vote From f0a570ea0cb6683eaa635515ede8376ec0159019 Mon Sep 17 00:00:00 2001 From: CMK Date: Mon, 1 Nov 2021 19:54:07 +0800 Subject: [PATCH 49/96] feat: add follower list for user --- Mastodon.xcodeproj/project.pbxproj | 52 +++++ .../xcschemes/xcschememanagement.plist | 8 +- Mastodon/Coordinator/SceneCoordinator.swift | 5 + Mastodon/Diffiable/Item/UserItem.swift | 15 ++ Mastodon/Diffiable/Section/UserSection.swift | 63 ++++++ ...erProviderFacade+UITableViewDelegate.swift | 22 ++ .../UserProvider/UserProviderFacade.swift | 22 ++ .../FollowerListViewController+Provider.swift | 50 +++++ .../Follower/FollowerListViewController.swift | 111 ++++++++++ .../FollowerListViewModel+Diffable.swift | 58 ++++++ .../FollowerListViewModel+State.swift | 196 ++++++++++++++++++ .../Follower/FollowerListViewModel.swift | 53 +++++ .../Header/View/ProfileHeaderView.swift | 19 +- .../View/ProfileStatusDashboardView.swift | 28 ++- .../Scene/Profile/ProfileViewController.swift | 33 ++- .../Timeline/UserTimelineViewController.swift | 1 - .../TimelineFooterTableViewCell.swift | 51 +++++ .../TableviewCell/UserTableViewCell.swift | 131 ++++++++++++ .../APIService/APIService+Follower.swift | 65 ++++++ .../Mastodon+API+Account+FollowRequest.swift | 6 +- .../API/Mastodon+API+Account+Followers.swift | 81 ++++++++ 21 files changed, 1028 insertions(+), 42 deletions(-) create mode 100644 Mastodon/Diffiable/Item/UserItem.swift create mode 100644 Mastodon/Diffiable/Section/UserSection.swift create mode 100644 Mastodon/Protocol/UserProvider/UserProviderFacade+UITableViewDelegate.swift create mode 100644 Mastodon/Scene/Profile/Follower/FollowerListViewController+Provider.swift create mode 100644 Mastodon/Scene/Profile/Follower/FollowerListViewController.swift create mode 100644 Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift create mode 100644 Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift create mode 100644 Mastodon/Scene/Profile/Follower/FollowerListViewModel.swift create mode 100644 Mastodon/Scene/Share/View/TableviewCell/TimelineFooterTableViewCell.swift create mode 100644 Mastodon/Scene/Share/View/TableviewCell/UserTableViewCell.swift create mode 100644 Mastodon/Service/APIService/APIService+Follower.swift create mode 100644 MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+Followers.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 1e5021a3..11914fe7 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -333,6 +333,17 @@ DB68A06325E905E000CFDF14 /* UIApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68A06225E905E000CFDF14 /* UIApplication.swift */; }; DB6B35182601FA3400DC1E11 /* MastodonAttachmentService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B35172601FA3400DC1E11 /* MastodonAttachmentService.swift */; }; DB6B351E2601FAEE00DC1E11 /* ComposeStatusAttachmentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B351D2601FAEE00DC1E11 /* ComposeStatusAttachmentCollectionViewCell.swift */; }; + DB6B74EF272FB55000C70B6E /* FollowerListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B74EE272FB55000C70B6E /* FollowerListViewController.swift */; }; + DB6B74F2272FB67600C70B6E /* FollowerListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B74F1272FB67600C70B6E /* FollowerListViewModel.swift */; }; + DB6B74F4272FBAE700C70B6E /* FollowerListViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B74F3272FBAE700C70B6E /* FollowerListViewModel+Diffable.swift */; }; + DB6B74F6272FBCDB00C70B6E /* FollowerListViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B74F5272FBCDB00C70B6E /* FollowerListViewModel+State.swift */; }; + DB6B74F8272FBFB100C70B6E /* FollowerListViewController+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B74F7272FBFB100C70B6E /* FollowerListViewController+Provider.swift */; }; + DB6B74FA272FC2B500C70B6E /* APIService+Follower.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B74F9272FC2B500C70B6E /* APIService+Follower.swift */; }; + DB6B74FC272FF55800C70B6E /* UserSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B74FB272FF55800C70B6E /* UserSection.swift */; }; + DB6B74FE272FF59000C70B6E /* UserItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B74FD272FF59000C70B6E /* UserItem.swift */; }; + DB6B7500272FF73800C70B6E /* UserTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B74FF272FF73800C70B6E /* UserTableViewCell.swift */; }; + DB6B75022730060700C70B6E /* UserProviderFacade+UITableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B75012730060700C70B6E /* UserProviderFacade+UITableViewDelegate.swift */; }; + DB6B750427300B4000C70B6E /* TimelineFooterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B750327300B4000C70B6E /* TimelineFooterTableViewCell.swift */; }; DB6C8C0F25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6C8C0E25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift */; }; DB6D1B3D2636857500ACB481 /* AppearancePreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B3C2636857500ACB481 /* AppearancePreference.swift */; }; DB6D1B44263691CF00ACB481 /* Mastodon+API+Subscriptions+Policy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B43263691CF00ACB481 /* Mastodon+API+Subscriptions+Policy.swift */; }; @@ -1132,6 +1143,17 @@ DB68A06225E905E000CFDF14 /* UIApplication.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIApplication.swift; sourceTree = ""; }; DB6B35172601FA3400DC1E11 /* MastodonAttachmentService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonAttachmentService.swift; sourceTree = ""; }; DB6B351D2601FAEE00DC1E11 /* ComposeStatusAttachmentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusAttachmentCollectionViewCell.swift; sourceTree = ""; }; + DB6B74EE272FB55000C70B6E /* FollowerListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowerListViewController.swift; sourceTree = ""; }; + DB6B74F1272FB67600C70B6E /* FollowerListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowerListViewModel.swift; sourceTree = ""; }; + DB6B74F3272FBAE700C70B6E /* FollowerListViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FollowerListViewModel+Diffable.swift"; sourceTree = ""; }; + DB6B74F5272FBCDB00C70B6E /* FollowerListViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FollowerListViewModel+State.swift"; sourceTree = ""; }; + DB6B74F7272FBFB100C70B6E /* FollowerListViewController+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FollowerListViewController+Provider.swift"; sourceTree = ""; }; + DB6B74F9272FC2B500C70B6E /* APIService+Follower.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Follower.swift"; sourceTree = ""; }; + DB6B74FB272FF55800C70B6E /* UserSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSection.swift; sourceTree = ""; }; + DB6B74FD272FF59000C70B6E /* UserItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserItem.swift; sourceTree = ""; }; + DB6B74FF272FF73800C70B6E /* UserTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTableViewCell.swift; sourceTree = ""; }; + DB6B75012730060700C70B6E /* UserProviderFacade+UITableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserProviderFacade+UITableViewDelegate.swift"; sourceTree = ""; }; + DB6B750327300B4000C70B6E /* TimelineFooterTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineFooterTableViewCell.swift; sourceTree = ""; }; DB6C8C0E25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+Error.swift"; sourceTree = ""; }; DB6D1B3C2636857500ACB481 /* AppearancePreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearancePreference.swift; sourceTree = ""; }; DB6D1B43263691CF00ACB481 /* Mastodon+API+Subscriptions+Policy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+API+Subscriptions+Policy.swift"; sourceTree = ""; }; @@ -1866,6 +1888,7 @@ 2D4AD8A126316CD200613EFC /* SelectedAccountSection.swift */, DB6D9F7C26358ED4008423CD /* SettingsSection.swift */, DBA94433265CBB5300C537E1 /* ProfileFieldSection.swift */, + DB6B74FB272FF55800C70B6E /* UserSection.swift */, ); path = Section; sourceTree = ""; @@ -1909,8 +1932,10 @@ 2DA7D04925CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift */, 2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */, DBE3CDBA261C427900430CC6 /* TimelineHeaderTableViewCell.swift */, + DB6B750327300B4000C70B6E /* TimelineFooterTableViewCell.swift */, DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */, DB92CF7125E7BB98002C1017 /* PollOptionTableViewCell.swift */, + DB6B74FF272FF73800C70B6E /* UserTableViewCell.swift */, ); path = TableviewCell; sourceTree = ""; @@ -1919,6 +1944,7 @@ isa = PBXGroup; children = ( 2D7631B225C159F700929FB9 /* Item.swift */, + DB6B74FD272FF59000C70B6E /* UserItem.swift */, 2D198642261BF09500F0B013 /* SearchResultItem.swift */, DB4F097C26A03A5B00D62E92 /* SearchHistoryItem.swift */, 2D4AD8A726316D3500613EFC /* SelectedAccountItem.swift */, @@ -2289,6 +2315,7 @@ 2D34D9D026148D9E0081BFC0 /* APIService+Recommend.swift */, 2D34D9DA261494120081BFC0 /* APIService+Search.swift */, 0F202212261351F5000C64BF /* APIService+HashtagTimeline.swift */, + DB6B74F9272FC2B500C70B6E /* APIService+Follower.swift */, DBCC3B9426157E6E0045B23D /* APIService+Relationship.swift */, 5B24BBE1262DB19100A9381B /* APIService+Report.swift */, DBAE3F932616E28B004B8251 /* APIService+Follow.swift */, @@ -2508,6 +2535,18 @@ path = NavigationController; sourceTree = ""; }; + DB6B74F0272FB55400C70B6E /* Follower */ = { + isa = PBXGroup; + children = ( + DB6B74EE272FB55000C70B6E /* FollowerListViewController.swift */, + DB6B74F7272FBFB100C70B6E /* FollowerListViewController+Provider.swift */, + DB6B74F1272FB67600C70B6E /* FollowerListViewModel.swift */, + DB6B74F3272FBAE700C70B6E /* FollowerListViewModel+Diffable.swift */, + DB6B74F5272FBCDB00C70B6E /* FollowerListViewModel+State.swift */, + ); + path = Follower; + sourceTree = ""; + }; DB6C8C0525F0921200AAA452 /* MastodonSDK */ = { isa = PBXGroup; children = ( @@ -2862,6 +2901,7 @@ DBB525462611ED57002F1F29 /* Header */, DBB5253B2611ECF5002F1F29 /* Timeline */, DBE3CDF1261C6B3100430CC6 /* Favorite */, + DB6B74F0272FB55400C70B6E /* Follower */, DB9D6BFE25E4F5940051B173 /* ProfileViewController.swift */, DBAE3F812615DDA3004B8251 /* ProfileViewController+UserProvider.swift */, DBB5255D2611F07A002F1F29 /* ProfileViewModel.swift */, @@ -2982,6 +3022,7 @@ children = ( DBAE3F672615DD60004B8251 /* UserProvider.swift */, DBAE3F872615DDF4004B8251 /* UserProviderFacade.swift */, + DB6B75012730060700C70B6E /* UserProviderFacade+UITableViewDelegate.swift */, ); path = UserProvider; sourceTree = ""; @@ -3965,6 +4006,7 @@ DB75BF1E263C1C1B00EDBF1F /* CustomScheduler.swift in Sources */, 0FAA102725E1126A0017CCDE /* MastodonPickServerViewController.swift in Sources */, DB59F0FE25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift in Sources */, + DB6B74FE272FF59000C70B6E /* UserItem.swift in Sources */, DB68586425E619B700F0A850 /* NSKeyValueObservation.swift in Sources */, DBE3CE07261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift in Sources */, 2D61335825C188A000CAE157 /* APIService+Persist+Status.swift in Sources */, @@ -4094,6 +4136,9 @@ DB9A486C26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift in Sources */, 5D0393902612D259007FE196 /* WebViewController.swift in Sources */, DB4481CC25EE2AFE00BEFB67 /* PollItem.swift in Sources */, + DB6B74FA272FC2B500C70B6E /* APIService+Follower.swift in Sources */, + DB6B74F4272FBAE700C70B6E /* FollowerListViewModel+Diffable.swift in Sources */, + DB6B74F2272FB67600C70B6E /* FollowerListViewModel.swift in Sources */, DB44767B260B3B8C00B66B82 /* CustomEmojiPickerInputView.swift in Sources */, 0F20222D261457EE000C64BF /* HashtagTimelineViewModel+LoadOldestState.swift in Sources */, DB0009A626AEE5DC009B9D2D /* Intents.intentdefinition in Sources */, @@ -4106,6 +4151,7 @@ DB35FC1F2612F1D9006193C9 /* ProfileRelationshipActionButton.swift in Sources */, DBC7A672260C897100E57475 /* StatusContentWarningEditorView.swift in Sources */, DB3667A6268AE2620027D07F /* ComposeStatusPollSection.swift in Sources */, + DB6B750427300B4000C70B6E /* TimelineFooterTableViewCell.swift in Sources */, DB59F10E25EF724F001F1DAB /* APIService+Poll.swift in Sources */, DB852D1F26FB037800FC9D81 /* SidebarViewModel.swift in Sources */, DB47229725F9EFAD00DA7F53 /* NSManagedObjectContext.swift in Sources */, @@ -4122,6 +4168,7 @@ DBF156E42702DB3F00EC00B7 /* HandleTapAction.swift in Sources */, DB023295267F0AB800031745 /* ASMetaEditableTextNode.swift in Sources */, 2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */, + DB6B74F6272FBCDB00C70B6E /* FollowerListViewModel+State.swift in Sources */, DB4F096C269EFA2000D62E92 /* SearchResultViewController+StatusProvider.swift in Sources */, DB87D4452609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift in Sources */, DB9F58EF26EF491E00E7BBE9 /* AccountListViewModel.swift in Sources */, @@ -4139,6 +4186,7 @@ DB040ED126538E3D00BEE9D8 /* Trie.swift in Sources */, DB73BF4B27140C0800781945 /* UITableViewDiffableDataSource.swift in Sources */, DBB525642612C988002F1F29 /* MeProfileViewModel.swift in Sources */, + DB6B74EF272FB55000C70B6E /* FollowerListViewController.swift in Sources */, 5BB04FE9262EFC300043BFF6 /* ReportedStatusTableviewCell.swift in Sources */, DBAE3F822615DDA3004B8251 /* ProfileViewController+UserProvider.swift in Sources */, DBBC24C426A544B900398BB9 /* Theme.swift in Sources */, @@ -4180,6 +4228,7 @@ DB49A63D25FF609300B98345 /* PlayerContainerView+MediaTypeIndicotorView.swift in Sources */, DB6180F826391D660018D199 /* MediaPreviewingViewController.swift in Sources */, DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */, + DB6B75022730060700C70B6E /* UserProviderFacade+UITableViewDelegate.swift in Sources */, DB44384F25E8C1FA008912A2 /* CALayer.swift in Sources */, 2D34D9CB261489930081BFC0 /* SearchViewController+Recommend.swift in Sources */, DB71C7CB271D5A0300BE3819 /* LineChartView.swift in Sources */, @@ -4245,6 +4294,7 @@ DBE54AC62636C89F004E7C0B /* NotificationPreference.swift in Sources */, 2D5A3D2825CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift in Sources */, DB98339C25C96DE600AD9700 /* APIService+Account.swift in Sources */, + DB6B74FC272FF55800C70B6E /* UserSection.swift in Sources */, 2DF75BA725D10E1000694EC8 /* APIService+Favorite.swift in Sources */, DB9D6C3825E508BE0051B173 /* Attachment.swift in Sources */, 5DFC35DF262068D20045711D /* SearchViewController+Follow.swift in Sources */, @@ -4261,6 +4311,7 @@ 0F202227261411BB000C64BF /* HashtagTimelineViewController+Provider.swift in Sources */, 2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */, 2D76316525C14BD100929FB9 /* PublicTimelineViewController.swift in Sources */, + DB6B7500272FF73800C70B6E /* UserTableViewCell.swift in Sources */, DB1D842E26552C4D000346B3 /* StatusTableViewControllerNavigateable.swift in Sources */, DB938F1F2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift in Sources */, 2D69CFF425CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift in Sources */, @@ -4291,6 +4342,7 @@ DB647C5726F1E97300F7F82C /* MainTabBarController+Wizard.swift in Sources */, DB6F5E38264E994A009108F4 /* AutoCompleteTopChevronView.swift in Sources */, DBB525412611ED54002F1F29 /* ProfileHeaderViewController.swift in Sources */, + DB6B74F8272FBFB100C70B6E /* FollowerListViewController+Provider.swift in Sources */, DB9D6BFF25E4F5940051B173 /* ProfileViewController.swift in Sources */, DB4932B326F2054200EF46D4 /* CircleAvatarButton.swift in Sources */, 0FB3D30825E524C600AAD544 /* PickServerCategoriesCell.swift in Sources */, diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index 1e918639..05869c6a 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ AppShared.xcscheme_^#shared#^_ orderHint - 43 + 35 CoreDataStack.xcscheme_^#shared#^_ orderHint - 42 + 39 Mastodon - ASDK.xcscheme_^#shared#^_ @@ -97,7 +97,7 @@ MastodonIntent.xcscheme_^#shared#^_ orderHint - 41 + 36 MastodonIntents.xcscheme_^#shared#^_ @@ -117,7 +117,7 @@ ShareActionExtension.xcscheme_^#shared#^_ orderHint - 44 + 37 SuppressBuildableAutocreation diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index 10d6fb84..cda20255 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -178,6 +178,7 @@ extension SceneCoordinator { case accountList case profile(viewModel: ProfileViewModel) case favorite(viewModel: FavoriteViewModel) + case follower(viewModel: FollowerListViewModel) // setting case settings(viewModel: SettingsViewModel) @@ -424,6 +425,10 @@ private extension SceneCoordinator { let _viewController = FavoriteViewController() _viewController.viewModel = viewModel viewController = _viewController + case .follower(let viewModel): + let _viewController = FollowerListViewController() + _viewController.viewModel = viewModel + viewController = _viewController case .suggestionAccount(let viewModel): let _viewController = SuggestionAccountViewController() _viewController.viewModel = viewModel diff --git a/Mastodon/Diffiable/Item/UserItem.swift b/Mastodon/Diffiable/Item/UserItem.swift new file mode 100644 index 00000000..6f3c591b --- /dev/null +++ b/Mastodon/Diffiable/Item/UserItem.swift @@ -0,0 +1,15 @@ +// +// UserItem.swift +// Mastodon +// +// Created by Cirno MainasuK on 2021-11-1. +// + +import Foundation +import CoreData + +enum UserItem: Hashable { + case follower(objectID: NSManagedObjectID) + case bottomLoader + case bottomHeader(text: String) +} diff --git a/Mastodon/Diffiable/Section/UserSection.swift b/Mastodon/Diffiable/Section/UserSection.swift new file mode 100644 index 00000000..58e80c6e --- /dev/null +++ b/Mastodon/Diffiable/Section/UserSection.swift @@ -0,0 +1,63 @@ +// +// UserSection.swift +// Mastodon +// +// Created by Cirno MainasuK on 2021-11-1. +// + +import os.log +import UIKit +import CoreData +import CoreDataStack +import MetaTextKit +import MastodonMeta + +enum UserSection: Hashable { + case main +} + +extension UserSection { + + static let logger = Logger(subsystem: "StatusSection", category: "logic") + + static func tableViewDiffableDataSource( + for tableView: UITableView, + dependency: NeedsDependency, + managedObjectContext: NSManagedObjectContext + ) -> UITableViewDiffableDataSource { + UITableViewDiffableDataSource(tableView: tableView) { [ + weak dependency + ] tableView, indexPath, item -> UITableViewCell? in + guard let dependency = dependency else { return UITableViewCell() } + switch item { + case .follower(let objectID): + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: UserTableViewCell.self), for: indexPath) as! UserTableViewCell + managedObjectContext.performAndWait { + let user = managedObjectContext.object(with: objectID) as! MastodonUser + configure(cell: cell, user: user) + } + return cell + case .bottomLoader: + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell + cell.startAnimating() + return cell + case .bottomHeader(let text): + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineFooterTableViewCell.self), for: indexPath) as! TimelineFooterTableViewCell + cell.messageLabel.text = text + return cell + } // end switch + } // end UITableViewDiffableDataSource + } // end static func tableViewDiffableDataSource { … } + +} + +extension UserSection { + + static func configure( + cell: UserTableViewCell, + user: MastodonUser + ) { + cell.configure(user: user) + } + +} diff --git a/Mastodon/Protocol/UserProvider/UserProviderFacade+UITableViewDelegate.swift b/Mastodon/Protocol/UserProvider/UserProviderFacade+UITableViewDelegate.swift new file mode 100644 index 00000000..a6e3cf21 --- /dev/null +++ b/Mastodon/Protocol/UserProvider/UserProviderFacade+UITableViewDelegate.swift @@ -0,0 +1,22 @@ +// +// UserProviderFacade+UITableViewDelegate.swift +// Mastodon +// +// Created by Cirno MainasuK on 2021-11-1. +// + +import Combine +import CoreDataStack +import MastodonSDK +import os.log +import UIKit + +extension UserTableViewCellDelegate where Self: UserProvider { + + func handleTableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let cell = tableView.cellForRow(at: indexPath) else { return } + let user = self.mastodonUser(for: cell) + UserProviderFacade.coordinatorToUserProfileScene(provider: self, user: user) + } + +} diff --git a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift index f8588194..edbe311c 100644 --- a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift +++ b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift @@ -440,3 +440,25 @@ extension UserProviderFacade { return activityViewController } } + +extension UserProviderFacade { + static func coordinatorToUserProfileScene(provider: UserProvider, user: Future) { + user + .sink { [weak provider] mastodonUser in + guard let provider = provider else { return } + guard let mastodonUser = mastodonUser else { return } + let profileViewModel = CachedProfileViewModel(context: provider.context, mastodonUser: mastodonUser) + DispatchQueue.main.async { + if provider.navigationController == nil { + let from = provider.presentingViewController ?? provider + provider.dismiss(animated: true) { + provider.coordinator.present(scene: .profile(viewModel: profileViewModel), from: from, transition: .show) + } + } else { + provider.coordinator.present(scene: .profile(viewModel: profileViewModel), from: provider, transition: .show) + } + } + } + .store(in: &provider.disposeBag) + } +} diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewController+Provider.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewController+Provider.swift new file mode 100644 index 00000000..627ed777 --- /dev/null +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewController+Provider.swift @@ -0,0 +1,50 @@ +// +// FollowerListViewController+Provider.swift +// Mastodon +// +// Created by Cirno MainasuK on 2021-11-1. +// + +import os.log +import UIKit +import Combine +import CoreData +import CoreDataStack + +extension FollowerListViewController: UserProvider { + + func mastodonUser() -> Future { + Future { promise in + promise(.success(nil)) + } + } + + func mastodonUser(for cell: UITableViewCell?) -> Future { + Future { [weak self] promise in + guard let self = self else { return } + guard let diffableDataSource = self.viewModel.diffableDataSource else { + assertionFailure() + promise(.success(nil)) + return + } + guard let cell = cell, + let indexPath = self.tableView.indexPath(for: cell), + let item = diffableDataSource.itemIdentifier(for: indexPath) else { + promise(.success(nil)) + return + } + + let managedObjectContext = self.viewModel.userFetchedResultsController.fetchedResultsController.managedObjectContext + + switch item { + case .follower(let objectID): + managedObjectContext.perform { + let user = managedObjectContext.object(with: objectID) as? MastodonUser + promise(.success(user)) + } + case .bottomLoader, .bottomHeader: + promise(.success(nil)) + } + } + } +} diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift new file mode 100644 index 00000000..42844866 --- /dev/null +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift @@ -0,0 +1,111 @@ +// +// FollowerListViewController.swift +// Mastodon +// +// Created by Cirno MainasuK on 2021-11-1. +// + +import os.log +import UIKit +import AVKit +import GameplayKit +import Combine + +final class FollowerListViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { + + var disposeBag = Set() + + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } + weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + + var viewModel: FollowerListViewModel! + + let mediaPreviewTransitionController = MediaPreviewTransitionController() + + lazy var tableView: UITableView = { + let tableView = UITableView() + tableView.register(UserTableViewCell.self, forCellReuseIdentifier: String(describing: UserTableViewCell.self)) + tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self)) + tableView.register(TimelineFooterTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineFooterTableViewCell.self)) + tableView.rowHeight = UITableView.automaticDimension + tableView.separatorStyle = .none + tableView.backgroundColor = .clear + return tableView + }() + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + +extension FollowerListViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor + ThemeService.shared.currentTheme + .receive(on: RunLoop.main) + .sink { [weak self] theme in + guard let self = self else { return } + self.view.backgroundColor = theme.secondarySystemBackgroundColor + } + .store(in: &disposeBag) + + tableView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(tableView) + NSLayoutConstraint.activate([ + tableView.topAnchor.constraint(equalTo: view.topAnchor), + tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + + tableView.delegate = self + viewModel.setupDiffableDataSource( + for: tableView, + dependency: self + ) + // TODO: add UserTableViewCellDelegate + + // trigger user timeline loading + Publishers.CombineLatest( + viewModel.domain.removeDuplicates().eraseToAnyPublisher(), + viewModel.userID.removeDuplicates().eraseToAnyPublisher() + ) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self = self else { return } + self.viewModel.stateMachine.enter(FollowerListViewModel.State.Reloading.self) + } + .store(in: &disposeBag) + } + +} + +// MARK: - LoadMoreConfigurableTableViewContainer +extension FollowerListViewController: LoadMoreConfigurableTableViewContainer { + typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell + typealias LoadingState = FollowerListViewModel.State.Loading + var loadMoreConfigurableTableView: UITableView { tableView } + var loadMoreConfigurableStateMachine: GKStateMachine { viewModel.stateMachine } +} + +// MARK: - UIScrollViewDelegate +extension FollowerListViewController { + func scrollViewDidScroll(_ scrollView: UIScrollView) { + handleScrollViewDidScroll(scrollView) + } +} + + +// MARK: - UITableViewDelegate +extension FollowerListViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + handleTableView(tableView, didSelectRowAt: indexPath) + } +} + +// MARK: - UserTableViewCellDelegate +extension FollowerListViewController: UserTableViewCellDelegate { } diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift new file mode 100644 index 00000000..90b9cb31 --- /dev/null +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift @@ -0,0 +1,58 @@ +// +// FollowerListViewModel+Diffable.swift +// Mastodon +// +// Created by Cirno MainasuK on 2021-11-1. +// + +import UIKit + +extension FollowerListViewModel { + func setupDiffableDataSource( + for tableView: UITableView, + dependency: NeedsDependency + ) { + diffableDataSource = UserSection.tableViewDiffableDataSource( + for: tableView, + dependency: dependency, + managedObjectContext: userFetchedResultsController.fetchedResultsController.managedObjectContext + ) + + // set empty section to make update animation top-to-bottom style + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + diffableDataSource?.apply(snapshot) + + // workaround to append loader wrong animation issue + snapshot.appendItems([.bottomLoader], toSection: .main) + diffableDataSource?.apply(snapshot) + + userFetchedResultsController.objectIDs.removeDuplicates() + .receive(on: DispatchQueue.main) + .sink { [weak self] objectIDs in + guard let self = self else { return } + guard let diffableDataSource = self.diffableDataSource else { return } + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + let items: [UserItem] = objectIDs.map { + UserItem.follower(objectID: $0) + } + snapshot.appendItems(items, toSection: .main) + + if let currentState = self.stateMachine.currentState { + switch currentState { + case is State.Idle, is State.Loading, is State.Fail: + snapshot.appendItems([.bottomLoader], toSection: .main) + case is State.NoMore: + break + default: + break + } + } + + diffableDataSource.apply(snapshot) + } + .store(in: &disposeBag) + } +} diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift new file mode 100644 index 00000000..b012a59b --- /dev/null +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift @@ -0,0 +1,196 @@ +// +// FollowerListViewModel+State.swift +// Mastodon +// +// Created by Cirno MainasuK on 2021-11-1. +// + +import os.log +import Foundation +import GameplayKit +import MastodonSDK + +extension FollowerListViewModel { + class State: GKState { + weak var viewModel: FollowerListViewModel? + + init(viewModel: FollowerListViewModel) { + self.viewModel = viewModel + } + + override func didEnter(from previousState: GKState?) { + os_log("%{public}s[%{public}ld], %{public}s: enter %s, previous: %s", ((#file as NSString).lastPathComponent), #line, #function, self.debugDescription, previousState.debugDescription) + } + } +} + +extension FollowerListViewModel.State { + class Initial: FollowerListViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + guard let viewModel = viewModel else { return false } + switch stateClass { + case is Reloading.Type: + return viewModel.userID.value != nil + default: + return false + } + } + } + + class Reloading: FollowerListViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Loading.Type: + return true + default: + return false + } + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + guard let viewModel = viewModel, let stateMachine = stateMachine else { return } + + // reset + viewModel.userFetchedResultsController.userIDs.value = [] + + stateMachine.enter(Loading.self) + } + } + + class Fail: FollowerListViewModel.State { + + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Loading.Type: + return true + default: + return false + } + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + guard let _ = viewModel, let stateMachine = stateMachine else { return } + + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: retry loading 3s later…", ((#file as NSString).lastPathComponent), #line, #function) + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: retry loading", ((#file as NSString).lastPathComponent), #line, #function) + stateMachine.enter(Loading.self) + } + } + } + + class Idle: FollowerListViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Reloading.Type, is Loading.Type: + return true + default: + return false + } + } + } + + class Loading: FollowerListViewModel.State { + + var maxID: String? + + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Fail.Type: + return true + case is Idle.Type: + return true + case is NoMore.Type: + return true + default: + return false + } + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + + if previousState is Reloading { + maxID = nil + } + + guard let viewModel = viewModel, let stateMachine = stateMachine else { return } + + guard let userID = viewModel.userID.value, !userID.isEmpty else { + stateMachine.enter(Fail.self) + return + } + + guard let activeMastodonAuthenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else { + stateMachine.enter(Fail.self) + return + } + + viewModel.context.apiService.followers( + userID: userID, + maxID: maxID, + authorizationBox: activeMastodonAuthenticationBox + ) + .receive(on: DispatchQueue.main) + .sink { completion in + switch completion { + case .failure(let error): + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: fetch user timeline fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription) + stateMachine.enter(Fail.self) + case .finished: + break + } + } receiveValue: { response in + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + + var hasNewAppend = false + var userIDs = viewModel.userFetchedResultsController.userIDs.value + for user in response.value { + guard !userIDs.contains(user.id) else { continue } + userIDs.append(user.id) + hasNewAppend = true + } + + let maxID = response.link?.maxID + + if hasNewAppend, maxID != nil { + stateMachine.enter(Idle.self) + } else { + stateMachine.enter(NoMore.self) + } + self.maxID = maxID + viewModel.userFetchedResultsController.userIDs.value = userIDs + } + .store(in: &viewModel.disposeBag) + } // end func didEnter + } + + class NoMore: FollowerListViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Reloading.Type: + return true + default: + return false + } + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + guard let viewModel = viewModel, let _ = stateMachine else { return } + guard let diffableDataSource = viewModel.diffableDataSource else { + assertionFailure() + return + } + DispatchQueue.main.async { + var snapshot = diffableDataSource.snapshot() + snapshot.deleteItems([.bottomLoader]) + let header = UserItem.bottomHeader(text: "Followers from other servers are not displayed") + snapshot.appendItems([header], toSection: .main) + diffableDataSource.apply(snapshot, animatingDifferences: false) + } + } + } +} diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewModel.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewModel.swift new file mode 100644 index 00000000..f62441cf --- /dev/null +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewModel.swift @@ -0,0 +1,53 @@ +// +// FollowerListViewModel.swift +// Mastodon +// +// Created by Cirno MainasuK on 2021-11-1. +// + +import Foundation +import Combine +import Combine +import CoreData +import CoreDataStack +import GameplayKit +import MastodonSDK + +final class FollowerListViewModel { + + var disposeBag = Set() + + // input + let context: AppContext + let domain: CurrentValueSubject + let userID: CurrentValueSubject + let userFetchedResultsController: UserFetchedResultsController + + // output + var diffableDataSource: UITableViewDiffableDataSource? + private(set) lazy var stateMachine: GKStateMachine = { + let stateMachine = GKStateMachine(states: [ + State.Initial(viewModel: self), + State.Reloading(viewModel: self), + State.Fail(viewModel: self), + State.Idle(viewModel: self), + State.Loading(viewModel: self), + State.NoMore(viewModel: self), + ]) + stateMachine.enter(State.Initial.self) + return stateMachine + }() + + init(context: AppContext, domain: String?, userID: String?) { + self.context = context + self.userFetchedResultsController = UserFetchedResultsController( + managedObjectContext: context.managedObjectContext, + domain: domain, + additionalTweetPredicate: nil + ) + self.domain = CurrentValueSubject(domain) + self.userID = CurrentValueSubject(userID) + // super.init() + + } +} diff --git a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift index 90f2e7a1..016b31a1 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift +++ b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift @@ -17,9 +17,7 @@ protocol ProfileHeaderViewDelegate: AnyObject { func profileHeaderView(_ profileHeaderView: ProfileHeaderView, relationshipButtonDidPressed button: ProfileRelationshipActionButton) func profileHeaderView(_ profileHeaderView: ProfileHeaderView, metaTextView: MetaTextView, metaDidPressed meta: Meta) - func profileHeaderView(_ profileHeaderView: ProfileHeaderView, profileStatusDashboardView: ProfileStatusDashboardView, postDashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView) - func profileHeaderView(_ profileHeaderView: ProfileHeaderView, profileStatusDashboardView: ProfileStatusDashboardView, followingDashboardMeterViewDidPressed followingDashboardMeterView: ProfileStatusDashboardMeterView) - func profileHeaderView(_ profileHeaderView: ProfileHeaderView, profileStatusDashboardView: ProfileStatusDashboardView, followersDashboardMeterViewDidPressed followersDashboardMeterView: ProfileStatusDashboardMeterView) + func profileHeaderView(_ profileHeaderView: ProfileHeaderView, profileStatusDashboardView dashboardView: ProfileStatusDashboardView, dashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView, meter: ProfileStatusDashboardView.Meter) } final class ProfileHeaderView: UIView { @@ -443,6 +441,7 @@ extension ProfileHeaderView { bringSubviewToFront(bannerContainerView) bringSubviewToFront(nameContainerStackView) + statusDashboardView.delegate = self bioMetaText.textView.delegate = self bioMetaText.textView.linkDelegate = self @@ -549,19 +548,9 @@ extension ProfileHeaderView: MetaTextViewDelegate { // MARK: - ProfileStatusDashboardViewDelegate extension ProfileHeaderView: ProfileStatusDashboardViewDelegate { - - func profileStatusDashboardView(_ dashboardView: ProfileStatusDashboardView, postDashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView) { - delegate?.profileHeaderView(self, profileStatusDashboardView: dashboardView, postDashboardMeterViewDidPressed: dashboardMeterView) + func profileStatusDashboardView(_ dashboardView: ProfileStatusDashboardView, dashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView, meter: ProfileStatusDashboardView.Meter) { + delegate?.profileHeaderView(self, profileStatusDashboardView: dashboardView, dashboardMeterViewDidPressed: dashboardMeterView, meter: meter) } - - func profileStatusDashboardView(_ dashboardView: ProfileStatusDashboardView, followingDashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView) { - delegate?.profileHeaderView(self, profileStatusDashboardView: dashboardView, followingDashboardMeterViewDidPressed: dashboardMeterView) - } - - func profileStatusDashboardView(_ dashboardView: ProfileStatusDashboardView, followersDashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView) { - delegate?.profileHeaderView(self, profileStatusDashboardView: dashboardView, followersDashboardMeterViewDidPressed: dashboardMeterView) - } - } // MARK: - AvatarConfigurableView diff --git a/Mastodon/Scene/Profile/Header/View/ProfileStatusDashboardView.swift b/Mastodon/Scene/Profile/Header/View/ProfileStatusDashboardView.swift index 0360421a..c21703c0 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileStatusDashboardView.swift +++ b/Mastodon/Scene/Profile/Header/View/ProfileStatusDashboardView.swift @@ -9,9 +9,7 @@ import os.log import UIKit protocol ProfileStatusDashboardViewDelegate: AnyObject { - func profileStatusDashboardView(_ dashboardView: ProfileStatusDashboardView, postDashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView) - func profileStatusDashboardView(_ dashboardView: ProfileStatusDashboardView, followingDashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView) - func profileStatusDashboardView(_ dashboardView: ProfileStatusDashboardView, followersDashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView) + func profileStatusDashboardView(_ dashboardView: ProfileStatusDashboardView, dashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView, meter: ProfileStatusDashboardView.Meter) } final class ProfileStatusDashboardView: UIView { @@ -34,6 +32,14 @@ final class ProfileStatusDashboardView: UIView { } +extension ProfileStatusDashboardView { + enum Meter: Hashable { + case post + case following + case follower + } +} + extension ProfileStatusDashboardView { private func _init() { let containerStackView = UIStackView() @@ -67,7 +73,6 @@ extension ProfileStatusDashboardView { tapGestureRecognizer.addTarget(self, action: #selector(ProfileStatusDashboardView.tapGestureRecognizerHandler(_:))) meterView.addGestureRecognizer(tapGestureRecognizer) } - } } @@ -78,12 +83,15 @@ extension ProfileStatusDashboardView { assertionFailure() return } - if sourceView === postDashboardMeterView { - delegate?.profileStatusDashboardView(self, postDashboardMeterViewDidPressed: sourceView) - } else if sourceView === followingDashboardMeterView { - delegate?.profileStatusDashboardView(self, followingDashboardMeterViewDidPressed: sourceView) - } else if sourceView === followersDashboardMeterView { - delegate?.profileStatusDashboardView(self, followersDashboardMeterViewDidPressed: sourceView) + switch sourceView { + case postDashboardMeterView: + delegate?.profileStatusDashboardView(self, dashboardMeterViewDidPressed: sourceView, meter: .post) + case followingDashboardMeterView: + delegate?.profileStatusDashboardView(self, dashboardMeterViewDidPressed: sourceView, meter: .following) + case followersDashboardMeterView: + delegate?.profileStatusDashboardView(self, dashboardMeterViewDidPressed: sourceView, meter: .follower) + default: + assertionFailure() } } } diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index b864fc94..04d58231 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -769,7 +769,6 @@ extension ProfileViewController: ProfilePagingViewControllerDelegate { // MARK: - ProfileHeaderViewDelegate extension ProfileViewController: ProfileHeaderViewDelegate { - func profileHeaderView(_ profileHeaderView: ProfileHeaderView, avatarImageViewDidPressed imageView: UIImageView) { guard let mastodonUser = viewModel.mastodonUser.value else { return } guard let avatar = imageView.image else { return } @@ -982,15 +981,29 @@ extension ProfileViewController: ProfileHeaderViewDelegate { } } - func profileHeaderView(_ profileHeaderView: ProfileHeaderView, profileStatusDashboardView: ProfileStatusDashboardView, postDashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView) { - - } - - func profileHeaderView(_ profileHeaderView: ProfileHeaderView, profileStatusDashboardView: ProfileStatusDashboardView, followingDashboardMeterViewDidPressed followingDashboardMeterView: ProfileStatusDashboardMeterView) { - - } - - func profileHeaderView(_ profileHeaderView: ProfileHeaderView, profileStatusDashboardView: ProfileStatusDashboardView, followersDashboardMeterViewDidPressed followersDashboardMeterView: ProfileStatusDashboardMeterView) { + func profileHeaderView(_ profileHeaderView: ProfileHeaderView, profileStatusDashboardView dashboardView: ProfileStatusDashboardView, dashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView, meter: ProfileStatusDashboardView.Meter) { + switch meter { + case .post: + // do nothing + break + case .follower: + guard let domain = viewModel.domain.value, + let userID = viewModel.userID.value + else { return } + let followerListViewModel = FollowerListViewModel( + context: context, + domain: domain, + userID: userID + ) + coordinator.present( + scene: .follower(viewModel: followerListViewModel), + from: self, + transition: .show + ) + case .following: + // TODO: + break + } } } diff --git a/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift b/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift index f3803da0..42e9376c 100644 --- a/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift +++ b/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift @@ -12,7 +12,6 @@ import Combine import CoreDataStack import GameplayKit -// TODO: adopt MediaPreviewableViewController final class UserTimelineViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } diff --git a/Mastodon/Scene/Share/View/TableviewCell/TimelineFooterTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/TimelineFooterTableViewCell.swift new file mode 100644 index 00000000..43dd2c6f --- /dev/null +++ b/Mastodon/Scene/Share/View/TableviewCell/TimelineFooterTableViewCell.swift @@ -0,0 +1,51 @@ +// +// TimelineFooterTableViewCell.swift +// Mastodon +// +// Created by Cirno MainasuK on 2021-11-1. +// + +import UIKit + +final class TimelineFooterTableViewCell: UITableViewCell { + + let messageLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 17) + label.textAlignment = .center + label.textColor = Asset.Colors.Label.secondary.color + label.text = "info" + label.numberOfLines = 0 + return label + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + _init() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension TimelineFooterTableViewCell { + + private func _init() { + selectionStyle = .none + backgroundColor = .clear + + messageLabel.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(messageLabel) + NSLayoutConstraint.activate([ + messageLabel.topAnchor.constraint(equalTo: contentView.topAnchor), + messageLabel.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor), + messageLabel.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor), + messageLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + messageLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 68).priority(.required - 1), // same height to bottom loader + ]) + } + +} diff --git a/Mastodon/Scene/Share/View/TableviewCell/UserTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/UserTableViewCell.swift new file mode 100644 index 00000000..29e28415 --- /dev/null +++ b/Mastodon/Scene/Share/View/TableviewCell/UserTableViewCell.swift @@ -0,0 +1,131 @@ +// +// UserTableViewCell.swift +// Mastodon +// +// Created by Cirno MainasuK on 2021-11-1. +// + +import CoreData +import CoreDataStack +import MastodonSDK +import UIKit +import MetaTextKit +import MastodonMeta +import FLAnimatedImage + +protocol UserTableViewCellDelegate: AnyObject { } + +final class UserTableViewCell: UITableViewCell { + + weak var delegate: UserTableViewCellDelegate? + + let avatarImageView: AvatarImageView = { + let imageView = AvatarImageView() + imageView.tintColor = Asset.Colors.Label.primary.color + imageView.layer.cornerRadius = 4 + imageView.clipsToBounds = true + return imageView + }() + + let nameLabel = MetaLabel(style: .statusName) + + let usernameLabel: UILabel = { + let label = UILabel() + label.textColor = Asset.Colors.Label.secondary.color + label.font = .preferredFont(forTextStyle: .body) + return label + }() + + let separatorLine = UIView.separatorLine + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + _init() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension UserTableViewCell { + + private func _init() { + let containerStackView = UIStackView() + containerStackView.axis = .horizontal + containerStackView.distribution = .fill + containerStackView.spacing = 12 + containerStackView.layoutMargins = UIEdgeInsets(top: 12, left: 0, bottom: 12, right: 0) + containerStackView.isLayoutMarginsRelativeArrangement = true + containerStackView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(containerStackView) + NSLayoutConstraint.activate([ + containerStackView.topAnchor.constraint(equalTo: contentView.topAnchor), + containerStackView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor), + containerStackView.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor), + containerStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + ]) + + avatarImageView.translatesAutoresizingMaskIntoConstraints = false + containerStackView.addArrangedSubview(avatarImageView) + NSLayoutConstraint.activate([ + avatarImageView.widthAnchor.constraint(equalToConstant: 42).priority(.required - 1), + avatarImageView.heightAnchor.constraint(equalToConstant: 42).priority(.required - 1), + ]) + + let textStackView = UIStackView() + textStackView.axis = .vertical + textStackView.distribution = .fill + textStackView.translatesAutoresizingMaskIntoConstraints = false + nameLabel.translatesAutoresizingMaskIntoConstraints = false + textStackView.addArrangedSubview(nameLabel) + usernameLabel.translatesAutoresizingMaskIntoConstraints = false + textStackView.addArrangedSubview(usernameLabel) + usernameLabel.setContentHuggingPriority(.defaultLow - 1, for: .vertical) + + containerStackView.addArrangedSubview(textStackView) + + separatorLine.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(separatorLine) + NSLayoutConstraint.activate([ + separatorLine.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor), + separatorLine.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor), + separatorLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)), + ]) + + + nameLabel.isUserInteractionEnabled = false + usernameLabel.isUserInteractionEnabled = false + avatarImageView.isUserInteractionEnabled = false + } + +} + +// MARK: - AvatarStackedImageView +extension UserTableViewCell: AvatarConfigurableView { + static var configurableAvatarImageSize: CGSize { CGSize(width: 42, height: 42) } + static var configurableAvatarImageCornerRadius: CGFloat { 4 } + var configurableAvatarImageView: FLAnimatedImageView? { avatarImageView } +} + +extension UserTableViewCell { + func configure(user: MastodonUser) { + // avatar + configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: user.avatarImageURL())) + // name + let name = user.displayNameWithFallback + do { + let mastodonContent = MastodonContent(content: name, emojis: user.emojiMeta) + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + nameLabel.configure(content: metaContent) + } catch { + let metaContent = PlaintextMetaContent(string: name) + nameLabel.configure(content: metaContent) + } + // username + usernameLabel.text = "@" + user.acct + } +} diff --git a/Mastodon/Service/APIService/APIService+Follower.swift b/Mastodon/Service/APIService/APIService+Follower.swift new file mode 100644 index 00000000..db29a0a2 --- /dev/null +++ b/Mastodon/Service/APIService/APIService+Follower.swift @@ -0,0 +1,65 @@ +// +// APIService+Follower.swift +// Mastodon +// +// Created by Cirno MainasuK on 2021-11-1. +// + +import UIKit +import Combine +import CoreData +import CoreDataStack +import CommonOSLog +import MastodonSDK + +extension APIService { + + func followers( + userID: Mastodon.Entity.Account.ID, + maxID: String?, + authorizationBox: MastodonAuthenticationBox + ) -> AnyPublisher, Error> { + let domain = authorizationBox.domain + let authorization = authorizationBox.userAuthorization + let requestMastodonUserID = authorizationBox.userID + + return Mastodon.API.Account.followers( + session: session, + domain: domain, + userID: userID, + authorization: authorization + ) + .flatMap { response -> AnyPublisher, Error> in + let managedObjectContext = self.backgroundManagedObjectContext + return managedObjectContext.performChanges { + let requestMastodonUserRequest = MastodonUser.sortedFetchRequest + requestMastodonUserRequest.predicate = MastodonUser.predicate(domain: domain, id: requestMastodonUserID) + requestMastodonUserRequest.fetchLimit = 1 + guard let requestMastodonUser = managedObjectContext.safeFetch(requestMastodonUserRequest).first else { return } + + for entity in response.value { + _ = APIService.CoreData.createOrMergeMastodonUser( + into: managedObjectContext, + for: requestMastodonUser, + in: domain, + entity: entity, + userCache: nil, + networkDate: response.networkDate, + log: .api + ) + } + } + .tryMap { result -> Mastodon.Response.Content<[Mastodon.Entity.Account]> in + switch result { + case .success: + return response + case .failure(let error): + throw error + } + } + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } + +} diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+FollowRequest.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+FollowRequest.swift index 87c879ea..7adbcdef 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+FollowRequest.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+FollowRequest.swift @@ -12,13 +12,15 @@ import Combine extension Mastodon.API.Account { static func acceptFollowRequestEndpointURL(domain: String, userID: Mastodon.Entity.Account.ID) -> URL { - return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("follow_requests") + return Mastodon.API.endpointURL(domain: domain) + .appendingPathComponent("follow_requests") .appendingPathComponent(userID) .appendingPathComponent("authorize") } static func rejectFollowRequestEndpointURL(domain: String, userID: Mastodon.Entity.Account.ID) -> URL { - return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("follow_requests") + return Mastodon.API.endpointURL(domain: domain) + .appendingPathComponent("follow_requests") .appendingPathComponent(userID) .appendingPathComponent("reject") } diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+Followers.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+Followers.swift new file mode 100644 index 00000000..b09a5f07 --- /dev/null +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+Followers.swift @@ -0,0 +1,81 @@ +// +// Mastodon+API+Account+Followers.swift +// +// +// Created by Cirno MainasuK on 2021-11-1. +// + +import Foundation +import Combine + +extension Mastodon.API.Account { + + static func followersEndpointURL(domain: String, userID: Mastodon.Entity.Account.ID) -> URL { + return Mastodon.API.endpointURL(domain: domain) + .appendingPathComponent("accounts") + .appendingPathComponent(userID) + .appendingPathComponent("followers") + } + + /// Followers + /// + /// Accounts which follow the given account, if network is not hidden by the account owner. + /// + /// - Since: 0.0.0 + /// - Version: 3.4.1 + /// # Reference + /// [Document](https://docs.joinmastodon.org/methods/accounts/) + /// - Parameters: + /// - session: `URLSession` + /// - domain: Mastodon instance domain. e.g. "example.com" + /// - userID: ID of the account in the database + /// - authorization: User token + /// - Returns: `AnyPublisher` contains `[Account]` nested in the response + public static func followers( + session: URLSession, + domain: String, + userID: Mastodon.Entity.Account.ID, + authorization: Mastodon.API.OAuth.Authorization + ) -> AnyPublisher, Error> { + let request = Mastodon.API.get( + url: followersEndpointURL(domain: domain, userID: userID), + query: nil, + authorization: authorization + ) + return session.dataTaskPublisher(for: request) + .tryMap { data, response in + let value = try Mastodon.API.decode(type: [Mastodon.Entity.Account].self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + .eraseToAnyPublisher() + } + + public struct FollowerQuery: Codable, GetQuery { + + public let maxID: String? + public let limit: Int? // default 40 + + enum CodingKeys: String, CodingKey { + case maxID = "max_id" + case limit + } + + public init( + maxID: String?, + limit: Int? + ) { + self.maxID = maxID + self.limit = limit + } + + var queryItems: [URLQueryItem]? { + var items: [URLQueryItem] = [] + maxID.flatMap { items.append(URLQueryItem(name: "max_id", value: $0)) } + limit.flatMap { items.append(URLQueryItem(name: "limit", value: String($0))) } + guard !items.isEmpty else { return nil } + return items + } + + } + +} From 14236c27b8c58791e2cfc34d8ecbc2ed66aa22af Mon Sep 17 00:00:00 2001 From: CMK Date: Mon, 1 Nov 2021 20:09:30 +0800 Subject: [PATCH 50/96] feat: coordinator sidebar search tab to trigger searching --- .../Root/ContentSplitViewController.swift | 19 ++++----- .../Scene/Root/RootSplitViewController.swift | 39 ++++++++++++++++++- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/Mastodon/Scene/Root/ContentSplitViewController.swift b/Mastodon/Scene/Root/ContentSplitViewController.swift index 577f8d6a..850b1429 100644 --- a/Mastodon/Scene/Root/ContentSplitViewController.swift +++ b/Mastodon/Scene/Root/ContentSplitViewController.swift @@ -10,6 +10,10 @@ import UIKit import Combine import CoreDataStack +protocol ContentSplitViewControllerDelegate: AnyObject { + func contentSplitViewController(_ contentSplitViewController: ContentSplitViewController, sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab) +} + final class ContentSplitViewController: UIViewController, NeedsDependency { var disposeBag = Set() @@ -19,6 +23,8 @@ final class ContentSplitViewController: UIViewController, NeedsDependency { weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + weak var delegate: ContentSplitViewControllerDelegate? + private(set) lazy var sidebarViewController: SidebarViewController = { let sidebarViewController = SidebarViewController() sidebarViewController.context = context @@ -87,18 +93,7 @@ extension ContentSplitViewController { extension ContentSplitViewController: SidebarViewControllerDelegate { func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab) { - guard let _ = MainTabBarController.Tab.allCases.firstIndex(of: tab) else { - assertionFailure() - return - } - let previousTab = currentSupplementaryTab - currentSupplementaryTab = tab - - if previousTab == tab, - let navigationController = mainTabBarController.selectedViewController as? UINavigationController - { - navigationController.popToRootViewController(animated: true) - } + delegate?.contentSplitViewController(self, sidebarViewController: sidebarViewController, didSelectTab: tab) } func sidebarViewController(_ sidebarViewController: SidebarViewController, didLongPressItem item: SidebarViewModel.Item, sourceView: UIView) { diff --git a/Mastodon/Scene/Root/RootSplitViewController.swift b/Mastodon/Scene/Root/RootSplitViewController.swift index 22354751..7c03287f 100644 --- a/Mastodon/Scene/Root/RootSplitViewController.swift +++ b/Mastodon/Scene/Root/RootSplitViewController.swift @@ -23,6 +23,7 @@ final class RootSplitViewController: UISplitViewController, NeedsDependency { let contentSplitViewController = ContentSplitViewController() contentSplitViewController.context = context contentSplitViewController.coordinator = coordinator + contentSplitViewController.delegate = self return contentSplitViewController }() @@ -131,6 +132,36 @@ extension RootSplitViewController { } +// MARK: - ContentSplitViewControllerDelegate +extension RootSplitViewController: ContentSplitViewControllerDelegate { + func contentSplitViewController(_ contentSplitViewController: ContentSplitViewController, sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab) { + guard let _ = MainTabBarController.Tab.allCases.firstIndex(of: tab) else { + assertionFailure() + return + } + switch tab { + case .search: + guard let navigationController = searchViewController.navigationController else { return } + if navigationController.viewControllers.count == 1 { + searchViewController.searchBarTapPublisher.send() + } else { + navigationController.popToRootViewController(animated: true) + } + + default: + let previousTab = contentSplitViewController.currentSupplementaryTab + contentSplitViewController.currentSupplementaryTab = tab + + if previousTab == tab, + let navigationController = contentSplitViewController.mainTabBarController.selectedViewController as? UINavigationController + { + navigationController.popToRootViewController(animated: true) + } + + } + } +} + // MARK: - UISplitViewControllerDelegate extension RootSplitViewController: UISplitViewControllerDelegate { @@ -180,7 +211,13 @@ extension RootSplitViewController: UISplitViewControllerDelegate { } RootSplitViewController.transform(from: compactMainTabBarViewController, to: contentSplitViewController.mainTabBarController) - contentSplitViewController.currentSupplementaryTab = compactMainTabBarViewController.currentTab.value + + let tab = compactMainTabBarViewController.currentTab.value + if tab == .search { + contentSplitViewController.currentSupplementaryTab = .home + } else { + contentSplitViewController.currentSupplementaryTab = compactMainTabBarViewController.currentTab.value + } return proposedDisplayMode } From 0eba513d5c57e44b29e0123bd42dcdcb2c292f7e Mon Sep 17 00:00:00 2001 From: CMK Date: Mon, 1 Nov 2021 20:11:18 +0800 Subject: [PATCH 51/96] chore: update version to 1.2.0 (82) --- AppShared/Info.plist | 2 +- CoreDataStack/Info.plist | 2 +- CoreDataStackTests/Info.plist | 2 +- Mastodon.xcodeproj/project.pbxproj | 64 +++++++++---------- .../xcschemes/xcschememanagement.plist | 8 +-- Mastodon/Info.plist | 2 +- MastodonIntent/Info.plist | 2 +- MastodonTests/Info.plist | 2 +- MastodonUITests/Info.plist | 2 +- NotificationService/Info.plist | 2 +- ShareActionExtension/Info.plist | 2 +- 11 files changed, 45 insertions(+), 45 deletions(-) diff --git a/AppShared/Info.plist b/AppShared/Info.plist index 9da0855b..16c084ce 100644 --- a/AppShared/Info.plist +++ b/AppShared/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 81 + 82 diff --git a/CoreDataStack/Info.plist b/CoreDataStack/Info.plist index 9da0855b..16c084ce 100644 --- a/CoreDataStack/Info.plist +++ b/CoreDataStack/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 81 + 82 diff --git a/CoreDataStackTests/Info.plist b/CoreDataStackTests/Info.plist index 9da0855b..16c084ce 100644 --- a/CoreDataStackTests/Info.plist +++ b/CoreDataStackTests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 81 + 82 diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 11914fe7..60f0f5d7 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -4863,7 +4863,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4892,7 +4892,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -5000,11 +5000,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 81; + DYLIB_CURRENT_VERSION = 82; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5031,11 +5031,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 81; + DYLIB_CURRENT_VERSION = 82; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5060,11 +5060,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 81; + DYLIB_CURRENT_VERSION = 82; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = CoreDataStack/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5090,11 +5090,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 81; + DYLIB_CURRENT_VERSION = 82; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = CoreDataStack/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5157,7 +5157,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5182,7 +5182,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5207,7 +5207,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5232,7 +5232,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5257,7 +5257,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5282,7 +5282,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5307,7 +5307,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5332,7 +5332,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5423,7 +5423,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -5490,11 +5490,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 81; + DYLIB_CURRENT_VERSION = 82; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = CoreDataStack/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5539,7 +5539,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5564,11 +5564,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 81; + DYLIB_CURRENT_VERSION = 82; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5660,7 +5660,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -5727,11 +5727,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 81; + DYLIB_CURRENT_VERSION = 82; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = CoreDataStack/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5776,7 +5776,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5801,11 +5801,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 81; + DYLIB_CURRENT_VERSION = 82; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5831,7 +5831,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5855,7 +5855,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 81; + CURRENT_PROJECT_VERSION = 82; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index 05869c6a..cb88c396 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ AppShared.xcscheme_^#shared#^_ orderHint - 35 + 42 CoreDataStack.xcscheme_^#shared#^_ orderHint - 39 + 43 Mastodon - ASDK.xcscheme_^#shared#^_ @@ -97,7 +97,7 @@ MastodonIntent.xcscheme_^#shared#^_ orderHint - 36 + 44 MastodonIntents.xcscheme_^#shared#^_ @@ -117,7 +117,7 @@ ShareActionExtension.xcscheme_^#shared#^_ orderHint - 37 + 41 SuppressBuildableAutocreation diff --git a/Mastodon/Info.plist b/Mastodon/Info.plist index dc3e8619..a75982e3 100644 --- a/Mastodon/Info.plist +++ b/Mastodon/Info.plist @@ -30,7 +30,7 @@ CFBundleVersion - 81 + 82 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/MastodonIntent/Info.plist b/MastodonIntent/Info.plist index 6fabe37e..ed243297 100644 --- a/MastodonIntent/Info.plist +++ b/MastodonIntent/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 81 + 82 NSExtension NSExtensionAttributes diff --git a/MastodonTests/Info.plist b/MastodonTests/Info.plist index 9da0855b..16c084ce 100644 --- a/MastodonTests/Info.plist +++ b/MastodonTests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 81 + 82 diff --git a/MastodonUITests/Info.plist b/MastodonUITests/Info.plist index 9da0855b..16c084ce 100644 --- a/MastodonUITests/Info.plist +++ b/MastodonUITests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 81 + 82 diff --git a/NotificationService/Info.plist b/NotificationService/Info.plist index 43c0dff3..89e56253 100644 --- a/NotificationService/Info.plist +++ b/NotificationService/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 81 + 82 NSExtension NSExtensionPointIdentifier diff --git a/ShareActionExtension/Info.plist b/ShareActionExtension/Info.plist index 73d43d81..79ba82ce 100644 --- a/ShareActionExtension/Info.plist +++ b/ShareActionExtension/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 81 + 82 NSExtension NSExtensionAttributes From 86d475fe5658fd37592ddad045d2c3e4b29dc148 Mon Sep 17 00:00:00 2001 From: CMK Date: Mon, 1 Nov 2021 20:14:26 +0800 Subject: [PATCH 52/96] feat: add i18n string for follower/following list footer --- Localization/app.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Localization/app.json b/Localization/app.json index 3d0e36d0..5c01ae7e 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -414,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "Search", "search_bar": { From 4f531a72d12369e0ebfc66f41d574641916156b9 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:12 +0100 Subject: [PATCH 53/96] New translations app.json (Kurmanji (Kurdish)) --- Localization/StringsConvertor/input/kmr_TR/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/kmr_TR/app.json b/Localization/StringsConvertor/input/kmr_TR/app.json index 5330ea48..4fdfd0e7 100644 --- a/Localization/StringsConvertor/input/kmr_TR/app.json +++ b/Localization/StringsConvertor/input/kmr_TR/app.json @@ -67,6 +67,7 @@ "done": "Qediya", "confirm": "Bipejirîne", "continue": "Bidomîne", + "compose": "Compose", "cancel": "Dev jê berde", "discard": "Biavêje", "try_again": "Dîsa biceribîne", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "Bigere", "search_bar": { From 9ffa6a2e6717f0887a4fe8ba0e8da8acb180cfce Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:13 +0100 Subject: [PATCH 54/96] New translations app.json (Russian) --- Localization/StringsConvertor/input/ru_RU/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/ru_RU/app.json b/Localization/StringsConvertor/input/ru_RU/app.json index a5c34023..c1ad3ee4 100644 --- a/Localization/StringsConvertor/input/ru_RU/app.json +++ b/Localization/StringsConvertor/input/ru_RU/app.json @@ -67,6 +67,7 @@ "done": "Готово", "confirm": "Подтвердить", "continue": "Продолжить", + "compose": "Compose", "cancel": "Отмена", "discard": "Отмена", "try_again": "Попробовать снова", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "Поиск", "search_bar": { From 9d265d9a18a091394ca43f2659df2fa78009d656 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:14 +0100 Subject: [PATCH 55/96] New translations app.json (Welsh) --- Localization/StringsConvertor/input/cy_GB/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/cy_GB/app.json b/Localization/StringsConvertor/input/cy_GB/app.json index 3ec77cf1..5c01ae7e 100644 --- a/Localization/StringsConvertor/input/cy_GB/app.json +++ b/Localization/StringsConvertor/input/cy_GB/app.json @@ -67,6 +67,7 @@ "done": "Done", "confirm": "Confirm", "continue": "Continue", + "compose": "Compose", "cancel": "Cancel", "discard": "Discard", "try_again": "Try Again", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "Search", "search_bar": { From 88909303360557a3f9908ddfe418a65e70568c66 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:15 +0100 Subject: [PATCH 56/96] New translations app.json (Hindi) --- Localization/StringsConvertor/input/hi_IN/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/hi_IN/app.json b/Localization/StringsConvertor/input/hi_IN/app.json index 3ec77cf1..5c01ae7e 100644 --- a/Localization/StringsConvertor/input/hi_IN/app.json +++ b/Localization/StringsConvertor/input/hi_IN/app.json @@ -67,6 +67,7 @@ "done": "Done", "confirm": "Confirm", "continue": "Continue", + "compose": "Compose", "cancel": "Cancel", "discard": "Discard", "try_again": "Try Again", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "Search", "search_bar": { From ede66ab8575014376d52c93e6a97e978b1292e35 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:16 +0100 Subject: [PATCH 57/96] New translations app.json (Thai) --- Localization/StringsConvertor/input/th_TH/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/th_TH/app.json b/Localization/StringsConvertor/input/th_TH/app.json index fb3024f2..f9e56214 100644 --- a/Localization/StringsConvertor/input/th_TH/app.json +++ b/Localization/StringsConvertor/input/th_TH/app.json @@ -67,6 +67,7 @@ "done": "เสร็จสิ้น", "confirm": "ยืนยัน", "continue": "ดำเนินการต่อ", + "compose": "Compose", "cancel": "ยกเลิก", "discard": "ละทิ้ง", "try_again": "ลองอีกครั้ง", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "ค้นหา", "search_bar": { From bf556d6e674c135ba291cfba35fb5894932d81c7 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:17 +0100 Subject: [PATCH 58/96] New translations app.json (Spanish, Argentina) --- Localization/StringsConvertor/input/es_AR/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/es_AR/app.json b/Localization/StringsConvertor/input/es_AR/app.json index 4ae70852..c0c884e2 100644 --- a/Localization/StringsConvertor/input/es_AR/app.json +++ b/Localization/StringsConvertor/input/es_AR/app.json @@ -67,6 +67,7 @@ "done": "Listo", "confirm": "Confirmar", "continue": "Continuar", + "compose": "Compose", "cancel": "Cancelar", "discard": "Descartar", "try_again": "Intentá de nuevo", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "Buscar", "search_bar": { From 9644a2332925756799601122b0643632151d3903 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:18 +0100 Subject: [PATCH 59/96] New translations app.json (Indonesian) --- Localization/StringsConvertor/input/id_ID/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/id_ID/app.json b/Localization/StringsConvertor/input/id_ID/app.json index 6ba29ad8..6f317125 100644 --- a/Localization/StringsConvertor/input/id_ID/app.json +++ b/Localization/StringsConvertor/input/id_ID/app.json @@ -67,6 +67,7 @@ "done": "Selesai", "confirm": "Konfirmasi", "continue": "Lanjut", + "compose": "Compose", "cancel": "Batal", "discard": "Discard", "try_again": "Coba Lagi", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "Cari", "search_bar": { From f983162838b7cc16cebe44529da3f521fb3f2d36 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:19 +0100 Subject: [PATCH 60/96] New translations app.json (Portuguese, Brazilian) --- Localization/StringsConvertor/input/pt_BR/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/pt_BR/app.json b/Localization/StringsConvertor/input/pt_BR/app.json index 3ec77cf1..5c01ae7e 100644 --- a/Localization/StringsConvertor/input/pt_BR/app.json +++ b/Localization/StringsConvertor/input/pt_BR/app.json @@ -67,6 +67,7 @@ "done": "Done", "confirm": "Confirm", "continue": "Continue", + "compose": "Compose", "cancel": "Cancel", "discard": "Discard", "try_again": "Try Again", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "Search", "search_bar": { From 157721384630e73ecbcebd18ce1107da9c905f85 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:20 +0100 Subject: [PATCH 61/96] New translations app.json (English) --- Localization/StringsConvertor/input/en_US/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/en_US/app.json b/Localization/StringsConvertor/input/en_US/app.json index 3ec77cf1..5c01ae7e 100644 --- a/Localization/StringsConvertor/input/en_US/app.json +++ b/Localization/StringsConvertor/input/en_US/app.json @@ -67,6 +67,7 @@ "done": "Done", "confirm": "Confirm", "continue": "Continue", + "compose": "Compose", "cancel": "Cancel", "discard": "Discard", "try_again": "Try Again", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "Search", "search_bar": { From 78e0b170d0713d79ceb146caf85b2aad87af617e Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:21 +0100 Subject: [PATCH 62/96] New translations app.json (Chinese Traditional) --- Localization/StringsConvertor/input/zh_TW/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/zh_TW/app.json b/Localization/StringsConvertor/input/zh_TW/app.json index 3ec77cf1..5c01ae7e 100644 --- a/Localization/StringsConvertor/input/zh_TW/app.json +++ b/Localization/StringsConvertor/input/zh_TW/app.json @@ -67,6 +67,7 @@ "done": "Done", "confirm": "Confirm", "continue": "Continue", + "compose": "Compose", "cancel": "Cancel", "discard": "Discard", "try_again": "Try Again", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "Search", "search_bar": { From 5fca21c7d9e3f262b11a44946c282c519f3b90d9 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:22 +0100 Subject: [PATCH 63/96] New translations app.json (Chinese Simplified) --- Localization/StringsConvertor/input/zh_CN/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/zh_CN/app.json b/Localization/StringsConvertor/input/zh_CN/app.json index b7728b60..b3a8866b 100644 --- a/Localization/StringsConvertor/input/zh_CN/app.json +++ b/Localization/StringsConvertor/input/zh_CN/app.json @@ -67,6 +67,7 @@ "done": "完成", "confirm": "确认", "continue": "继续", + "compose": "Compose", "cancel": "取消", "discard": "放弃", "try_again": "再试一次", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "搜索", "search_bar": { From 627c2b4999f039aa9b07094ee47ff923ac44c5ed Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:23 +0100 Subject: [PATCH 64/96] New translations app.json (Swedish) --- Localization/StringsConvertor/input/sv_SE/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/sv_SE/app.json b/Localization/StringsConvertor/input/sv_SE/app.json index d0a9ad79..7acf4875 100644 --- a/Localization/StringsConvertor/input/sv_SE/app.json +++ b/Localization/StringsConvertor/input/sv_SE/app.json @@ -67,6 +67,7 @@ "done": "Done", "confirm": "Confirm", "continue": "Fortsätt", + "compose": "Compose", "cancel": "Avbryt", "discard": "Discard", "try_again": "Försök igen", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "Search", "search_bar": { From 05ec22990e0632fa3345582ab9b6463635391407 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:24 +0100 Subject: [PATCH 65/96] New translations app.json (Portuguese) --- Localization/StringsConvertor/input/pt_PT/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/pt_PT/app.json b/Localization/StringsConvertor/input/pt_PT/app.json index 3ec77cf1..5c01ae7e 100644 --- a/Localization/StringsConvertor/input/pt_PT/app.json +++ b/Localization/StringsConvertor/input/pt_PT/app.json @@ -67,6 +67,7 @@ "done": "Done", "confirm": "Confirm", "continue": "Continue", + "compose": "Compose", "cancel": "Cancel", "discard": "Discard", "try_again": "Try Again", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "Search", "search_bar": { From b85bfb65d3d418de32763737b63870daa840ad75 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:25 +0100 Subject: [PATCH 66/96] New translations app.json (Japanese) --- Localization/StringsConvertor/input/ja_JP/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/ja_JP/app.json b/Localization/StringsConvertor/input/ja_JP/app.json index 1c7d408f..417ca3e3 100644 --- a/Localization/StringsConvertor/input/ja_JP/app.json +++ b/Localization/StringsConvertor/input/ja_JP/app.json @@ -67,6 +67,7 @@ "done": "完了", "confirm": "確認", "continue": "続ける", + "compose": "Compose", "cancel": "キャンセル", "discard": "破棄", "try_again": "再実行", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "検索", "search_bar": { From e4eada46f4d48c658d0013ae0f3f1cbd3de5fa54 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:26 +0100 Subject: [PATCH 67/96] New translations app.json (Dutch) --- Localization/StringsConvertor/input/nl_NL/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/nl_NL/app.json b/Localization/StringsConvertor/input/nl_NL/app.json index d57a38ef..d8ee1e57 100644 --- a/Localization/StringsConvertor/input/nl_NL/app.json +++ b/Localization/StringsConvertor/input/nl_NL/app.json @@ -67,6 +67,7 @@ "done": "Klaar", "confirm": "Bevestigen", "continue": "Doorgaan", + "compose": "Compose", "cancel": "Annuleren", "discard": "Weggooien", "try_again": "Probeer Opnieuw", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "Zoeken", "search_bar": { From 12c8198cb943ade95ca3897905da903b26f637e4 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:27 +0100 Subject: [PATCH 68/96] New translations app.json (Korean) --- Localization/StringsConvertor/input/ko_KR/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/ko_KR/app.json b/Localization/StringsConvertor/input/ko_KR/app.json index a9fb71ee..571b1465 100644 --- a/Localization/StringsConvertor/input/ko_KR/app.json +++ b/Localization/StringsConvertor/input/ko_KR/app.json @@ -67,6 +67,7 @@ "done": "완료", "confirm": "확인", "continue": "계속", + "compose": "Compose", "cancel": "취소", "discard": "버리기", "try_again": "다시 시도", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "검색", "search_bar": { From 54cfcc10aaaa40717203bdbfe02f80fd1a94d03e Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:28 +0100 Subject: [PATCH 69/96] New translations app.json (Danish) --- Localization/StringsConvertor/input/da_DK/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/da_DK/app.json b/Localization/StringsConvertor/input/da_DK/app.json index 3ec77cf1..5c01ae7e 100644 --- a/Localization/StringsConvertor/input/da_DK/app.json +++ b/Localization/StringsConvertor/input/da_DK/app.json @@ -67,6 +67,7 @@ "done": "Done", "confirm": "Confirm", "continue": "Continue", + "compose": "Compose", "cancel": "Cancel", "discard": "Discard", "try_again": "Try Again", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "Search", "search_bar": { From 5a5e9f60f77039e21aa7ee7b973a981e5d819a4c Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:30 +0100 Subject: [PATCH 70/96] New translations app.json (Catalan) --- Localization/StringsConvertor/input/ca_ES/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/ca_ES/app.json b/Localization/StringsConvertor/input/ca_ES/app.json index 9ffdc5be..41349483 100644 --- a/Localization/StringsConvertor/input/ca_ES/app.json +++ b/Localization/StringsConvertor/input/ca_ES/app.json @@ -67,6 +67,7 @@ "done": "Fet", "confirm": "Confirma", "continue": "Continua", + "compose": "Compose", "cancel": "Cancel·la", "discard": "Descarta", "try_again": "Torna a provar", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "Cerca", "search_bar": { From 0ed72e48be1ad9868ea6255bb8b7f5b8deb17ef4 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:31 +0100 Subject: [PATCH 71/96] New translations app.json (Spanish) --- Localization/StringsConvertor/input/es_ES/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/es_ES/app.json b/Localization/StringsConvertor/input/es_ES/app.json index 5e96daff..1710318b 100644 --- a/Localization/StringsConvertor/input/es_ES/app.json +++ b/Localization/StringsConvertor/input/es_ES/app.json @@ -67,6 +67,7 @@ "done": "Hecho", "confirm": "Confirmar", "continue": "Continuar", + "compose": "Compose", "cancel": "Cancelar", "discard": "Descartar", "try_again": "Inténtalo de nuevo", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "Buscar", "search_bar": { From a310737ee3720321b0274828d154c37ad40e0fb4 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:32 +0100 Subject: [PATCH 72/96] New translations app.json (French) --- Localization/StringsConvertor/input/fr_FR/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/fr_FR/app.json b/Localization/StringsConvertor/input/fr_FR/app.json index 5c101dbe..e27f097e 100644 --- a/Localization/StringsConvertor/input/fr_FR/app.json +++ b/Localization/StringsConvertor/input/fr_FR/app.json @@ -67,6 +67,7 @@ "done": "Terminé", "confirm": "Confirmer", "continue": "Continuer", + "compose": "Compose", "cancel": "Annuler", "discard": "Abandonner", "try_again": "Réessayer", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "Rechercher", "search_bar": { From 894d62ba231a69251a91941d769edf2d66f86dde Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:33 +0100 Subject: [PATCH 73/96] New translations app.json (Romanian) --- Localization/StringsConvertor/input/ro_RO/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/ro_RO/app.json b/Localization/StringsConvertor/input/ro_RO/app.json index ef819f8e..3927247e 100644 --- a/Localization/StringsConvertor/input/ro_RO/app.json +++ b/Localization/StringsConvertor/input/ro_RO/app.json @@ -67,6 +67,7 @@ "done": "Done", "confirm": "Confirm", "continue": "Continue", + "compose": "Compose", "cancel": "Cancel", "discard": "Discard", "try_again": "Try Again", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "Search", "search_bar": { From 2b70a770cf1aa63b784ef06fc7a939f1c62758d0 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:34 +0100 Subject: [PATCH 74/96] New translations app.json (Scottish Gaelic) --- Localization/StringsConvertor/input/gd_GB/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/gd_GB/app.json b/Localization/StringsConvertor/input/gd_GB/app.json index a73925bb..12d862a5 100644 --- a/Localization/StringsConvertor/input/gd_GB/app.json +++ b/Localization/StringsConvertor/input/gd_GB/app.json @@ -67,6 +67,7 @@ "done": "Deiseil", "confirm": "Dearbh", "continue": "Lean air adhart", + "compose": "Compose", "cancel": "Sguir dheth", "discard": "Tilg air falbh", "try_again": "Feuch ris a-rithist", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "Lorg", "search_bar": { From 5c0a07110c827fcf7fb4109f86e6cd67c683fbcd Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:35 +0100 Subject: [PATCH 75/96] New translations app.json (Arabic) --- Localization/StringsConvertor/input/ar_SA/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/ar_SA/app.json b/Localization/StringsConvertor/input/ar_SA/app.json index 4bf55d91..44aebc15 100644 --- a/Localization/StringsConvertor/input/ar_SA/app.json +++ b/Localization/StringsConvertor/input/ar_SA/app.json @@ -67,6 +67,7 @@ "done": "تمّ", "confirm": "تأكيد", "continue": "واصل", + "compose": "Compose", "cancel": "إلغاء", "discard": "تجاهل", "try_again": "المُحاولة مرة أُخرى", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "بحث", "search_bar": { From caadcf8eaa7ab3ec2335a5c04702c13ead2555bb Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:36 +0100 Subject: [PATCH 76/96] New translations app.json (German) --- Localization/StringsConvertor/input/de_DE/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/de_DE/app.json b/Localization/StringsConvertor/input/de_DE/app.json index 43d8ed70..dc8cdf8c 100644 --- a/Localization/StringsConvertor/input/de_DE/app.json +++ b/Localization/StringsConvertor/input/de_DE/app.json @@ -67,6 +67,7 @@ "done": "Fertig", "confirm": "Bestätigen", "continue": "Fortfahren", + "compose": "Compose", "cancel": "Abbrechen", "discard": "Verwerfen", "try_again": "Nochmals versuchen", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "Suche", "search_bar": { From 8ef001e02c999d3daedeeef88952a7987533e79c Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 13:44:37 +0100 Subject: [PATCH 77/96] New translations app.json (Swedish, Finland) --- Localization/StringsConvertor/input/sv_FI/app.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/sv_FI/app.json b/Localization/StringsConvertor/input/sv_FI/app.json index d0a9ad79..7acf4875 100644 --- a/Localization/StringsConvertor/input/sv_FI/app.json +++ b/Localization/StringsConvertor/input/sv_FI/app.json @@ -67,6 +67,7 @@ "done": "Done", "confirm": "Confirm", "continue": "Fortsätt", + "compose": "Compose", "cancel": "Avbryt", "discard": "Discard", "try_again": "Försök igen", @@ -413,6 +414,12 @@ } } }, + "follower": { + "footer": "Followers from other servers are not displayed." + }, + "following": { + "footer": "Follows from other servers are not displayed." + }, "search": { "title": "Search", "search_bar": { From 3f945a1cf8839f016d9173026a4e659c00a4c6bf Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 14:40:30 +0100 Subject: [PATCH 78/96] New translations app.json (Kurmanji (Kurdish)) --- Localization/StringsConvertor/input/kmr_TR/app.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Localization/StringsConvertor/input/kmr_TR/app.json b/Localization/StringsConvertor/input/kmr_TR/app.json index 4fdfd0e7..7534aede 100644 --- a/Localization/StringsConvertor/input/kmr_TR/app.json +++ b/Localization/StringsConvertor/input/kmr_TR/app.json @@ -67,7 +67,7 @@ "done": "Qediya", "confirm": "Bipejirîne", "continue": "Bidomîne", - "compose": "Compose", + "compose": "Binivîsîne", "cancel": "Dev jê berde", "discard": "Biavêje", "try_again": "Dîsa biceribîne", @@ -415,10 +415,10 @@ } }, "follower": { - "footer": "Followers from other servers are not displayed." + "footer": "Şopîner ji rajekerên din nayê dîtin." }, "following": { - "footer": "Follows from other servers are not displayed." + "footer": "Şopandin ji rajekerên din nayê dîtin." }, "search": { "title": "Bigere", From a487c19cba3c9121e64579a4b87f8df738cd81d0 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 14:40:32 +0100 Subject: [PATCH 79/96] New translations app.json (Scottish Gaelic) --- Localization/StringsConvertor/input/gd_GB/app.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Localization/StringsConvertor/input/gd_GB/app.json b/Localization/StringsConvertor/input/gd_GB/app.json index 12d862a5..b5c66f8f 100644 --- a/Localization/StringsConvertor/input/gd_GB/app.json +++ b/Localization/StringsConvertor/input/gd_GB/app.json @@ -67,7 +67,7 @@ "done": "Deiseil", "confirm": "Dearbh", "continue": "Lean air adhart", - "compose": "Compose", + "compose": "Sgrìobh", "cancel": "Sguir dheth", "discard": "Tilg air falbh", "try_again": "Feuch ris a-rithist", @@ -415,10 +415,10 @@ } }, "follower": { - "footer": "Followers from other servers are not displayed." + "footer": "Cha dèid luchd-leantainn o fhrithealaichean eile a shealltainn." }, "following": { - "footer": "Follows from other servers are not displayed." + "footer": "Cha dèid cò air a leanas tu air frithealaichean eile a shealltainn." }, "search": { "title": "Lorg", From 45ee6941ba3f95515589f484e790a57834d44d69 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 14:40:33 +0100 Subject: [PATCH 80/96] New translations app.json (Catalan) --- Localization/StringsConvertor/input/ca_ES/app.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Localization/StringsConvertor/input/ca_ES/app.json b/Localization/StringsConvertor/input/ca_ES/app.json index 41349483..2ecd587c 100644 --- a/Localization/StringsConvertor/input/ca_ES/app.json +++ b/Localization/StringsConvertor/input/ca_ES/app.json @@ -67,7 +67,7 @@ "done": "Fet", "confirm": "Confirma", "continue": "Continua", - "compose": "Compose", + "compose": "Composa", "cancel": "Cancel·la", "discard": "Descarta", "try_again": "Torna a provar", @@ -415,10 +415,10 @@ } }, "follower": { - "footer": "Followers from other servers are not displayed." + "footer": "Els seguidors d'altres servidors no son mostrats." }, "following": { - "footer": "Follows from other servers are not displayed." + "footer": "Els seguits d'altres servidors no son mostrats." }, "search": { "title": "Cerca", From 524f7c0cf6d4834f8136e8ca7a95b3e1fab050fb Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 1 Nov 2021 18:14:37 +0100 Subject: [PATCH 81/96] New translations Intents.stringsdict (Dutch) --- .../Intents/input/nl_NL/Intents.stringsdict | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Localization/StringsConvertor/Intents/input/nl_NL/Intents.stringsdict b/Localization/StringsConvertor/Intents/input/nl_NL/Intents.stringsdict index 18422c77..9043720d 100644 --- a/Localization/StringsConvertor/Intents/input/nl_NL/Intents.stringsdict +++ b/Localization/StringsConvertor/Intents/input/nl_NL/Intents.stringsdict @@ -5,7 +5,7 @@ There are ${count} options matching ‘${content}’. - 2 NSStringLocalizedFormatKey - There are %#@count_option@ matching ‘${content}’. + Er zijn %#@count_option@ die overeenkomen met ‘${content}’. count_option NSStringFormatSpecTypeKey @@ -13,7 +13,7 @@ NSStringFormatValueTypeKey %ld one - 1 option + 1 optie other %ld options @@ -21,7 +21,7 @@ There are ${count} options matching ‘${visibility}’. NSStringLocalizedFormatKey - There are %#@count_option@ matching ‘${visibility}’. + Er zijn %#@count_option@ die overeenkomen met ‘${visibility}’. count_option NSStringFormatSpecTypeKey @@ -29,7 +29,7 @@ NSStringFormatValueTypeKey %ld one - 1 option + 1 optie other %ld options From 0d39d061a1ee2be1a9f535d41372001c1324bfee Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 2 Nov 2021 14:28:21 +0800 Subject: [PATCH 82/96] feat: update the notification tab "Mentions" segment table UI --- .../Diffiable/Item/NotificationItem.swift | 8 +- .../Section/Status/NotificationSection.swift | 296 ++++++++++-------- ...icationViewController+StatusProvider.swift | 17 +- .../NotificationViewController.swift | 134 +++++--- .../NotificationViewModel+Diffable.swift | 26 +- ...otificationViewModel+LoadOldestState.swift | 4 +- .../Notification/NotificationViewModel.swift | 8 +- .../Scene/Share/View/Content/StatusView.swift | 10 + 8 files changed, 319 insertions(+), 184 deletions(-) diff --git a/Mastodon/Diffiable/Item/NotificationItem.swift b/Mastodon/Diffiable/Item/NotificationItem.swift index 22949b3a..fc7d0e0d 100644 --- a/Mastodon/Diffiable/Item/NotificationItem.swift +++ b/Mastodon/Diffiable/Item/NotificationItem.swift @@ -10,7 +10,7 @@ import Foundation enum NotificationItem { case notification(objectID: NSManagedObjectID, attribute: Item.StatusAttribute) - + case notificationStatus(objectID: NSManagedObjectID, attribute: Item.StatusAttribute) // display notification status without card wrapper case bottomLoader } @@ -19,6 +19,8 @@ extension NotificationItem: Equatable { switch (lhs, rhs) { case (.notification(let idLeft, _), .notification(let idRight, _)): return idLeft == idRight + case (.notificationStatus(let idLeft, _), .notificationStatus(let idRight, _)): + return idLeft == idRight case (.bottomLoader, .bottomLoader): return true default: @@ -32,6 +34,8 @@ extension NotificationItem: Hashable { switch self { case .notification(let id, _): hasher.combine(id) + case .notificationStatus(let id, _): + hasher.combine(id) case .bottomLoader: hasher.combine(String(describing: NotificationItem.bottomLoader.self)) } @@ -43,6 +47,8 @@ extension NotificationItem { switch self { case .notification(let objectID, _): return .mastodonNotification(objectID: objectID) + case .notificationStatus(let objectID, _): + return .mastodonNotification(objectID: objectID) case .bottomLoader: return nil } diff --git a/Mastodon/Diffiable/Section/Status/NotificationSection.swift b/Mastodon/Diffiable/Section/Status/NotificationSection.swift index 215ba67c..22283a47 100644 --- a/Mastodon/Diffiable/Section/Status/NotificationSection.swift +++ b/Mastodon/Diffiable/Section/Status/NotificationSection.swift @@ -21,9 +21,10 @@ enum NotificationSection: Equatable, Hashable { extension NotificationSection { static func tableViewDiffableDataSource( for tableView: UITableView, + dependency: NeedsDependency, managedObjectContext: NSManagedObjectContext, delegate: NotificationTableViewCellDelegate, - dependency: NeedsDependency + statusTableViewCellDelegate: StatusTableViewCellDelegate ) -> UITableViewDiffableDataSource { UITableViewDiffableDataSource(tableView: tableView) { [weak delegate, weak dependency] @@ -32,137 +33,45 @@ extension NotificationSection { switch notificationItem { case .notification(let objectID, let attribute): guard let notification = try? managedObjectContext.existingObject(with: objectID) as? MastodonNotification, - !notification.isDeleted else { - return UITableViewCell() - } + !notification.isDeleted + else { return UITableViewCell() } let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NotificationStatusTableViewCell.self), for: indexPath) as! NotificationStatusTableViewCell - cell.delegate = delegate - - // configure author - cell.configure( - with: AvatarConfigurableViewConfiguration( - avatarImageURL: notification.account.avatarImageURL() - ) + configure( + tableView: tableView, + cell: cell, + notification: notification, + dependency: dependency, + attribute: attribute ) + cell.delegate = delegate + return cell - func createActionImage() -> UIImage? { - return UIImage( - systemName: notification.notificationType.actionImageName, - withConfiguration: UIImage.SymbolConfiguration( - pointSize: 12, weight: .semibold - ) - )? - .withTintColor(.systemBackground) - .af.imageAspectScaled(toFit: CGSize(width: 14, height: 14)) - } + case .notificationStatus(objectID: let objectID, attribute: let attribute): + guard let notification = try? managedObjectContext.existingObject(with: objectID) as? MastodonNotification, + !notification.isDeleted, + let status = notification.status, + let requestUserID = dependency.context.authenticationService.activeMastodonAuthenticationBox.value?.userID + else { return UITableViewCell() } + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell - cell.avatarButton.badgeImageView.backgroundColor = notification.notificationType.color - cell.avatarButton.badgeImageView.image = createActionImage() - cell.traitCollectionDidChange - .receive(on: DispatchQueue.main) - .sink { [weak cell] in - guard let cell = cell else { return } - cell.avatarButton.badgeImageView.image = createActionImage() - } - .store(in: &cell.disposeBag) - - // configure author name, notification description, timestamp - let nameText = notification.account.displayNameWithFallback - let titleLabelText: String = { - switch notification.notificationType { - case .favourite: return L10n.Scene.Notification.userFavoritedYourPost(nameText) - case .follow: return L10n.Scene.Notification.userFollowedYou(nameText) - case .followRequest: return L10n.Scene.Notification.userRequestedToFollowYou(nameText) - case .mention: return L10n.Scene.Notification.userMentionedYou(nameText) - case .poll: return L10n.Scene.Notification.userYourPollHasEnded(nameText) - case .reblog: return L10n.Scene.Notification.userRebloggedYourPost(nameText) - default: return "" - } - }() - - do { - let nameContent = MastodonContent(content: nameText, emojis: notification.account.emojiMeta) - let nameMetaContent = try MastodonMetaContent.convert(document: nameContent) - - let mastodonContent = MastodonContent(content: titleLabelText, emojis: notification.account.emojiMeta) - let metaContent = try MastodonMetaContent.convert(document: mastodonContent) - - cell.titleLabel.configure(content: metaContent) - - if let nameRange = metaContent.string.range(of: nameMetaContent.string) { - let nsRange = NSRange(nameRange, in: metaContent.string) - cell.titleLabel.textStorage.addAttributes([ - .font: UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold), maximumPointSize: 20), - .foregroundColor: Asset.Colors.brandBlue.color, - ], range: nsRange) - } - - } catch { - let metaContent = PlaintextMetaContent(string: titleLabelText) - cell.titleLabel.configure(content: metaContent) - } - - let createAt = notification.createAt - cell.timestampLabel.text = createAt.localizedSlowedTimeAgoSinceNow - AppContext.shared.timestampUpdatePublisher - .receive(on: DispatchQueue.main) - .sink { [weak cell] _ in - guard let cell = cell else { return } - cell.timestampLabel.text = createAt.localizedSlowedTimeAgoSinceNow - } - .store(in: &cell.disposeBag) - - // configure follow request (if exist) - if case .followRequest = notification.notificationType { - cell.acceptButton.publisher(for: .touchUpInside) - .sink { [weak cell] _ in - guard let cell = cell else { return } - cell.delegate?.notificationTableViewCell(cell, notification: notification, acceptButtonDidPressed: cell.acceptButton) - } - .store(in: &cell.disposeBag) - cell.rejectButton.publisher(for: .touchUpInside) - .sink { [weak cell] _ in - guard let cell = cell else { return } - cell.delegate?.notificationTableViewCell(cell, notification: notification, rejectButtonDidPressed: cell.rejectButton) - } - .store(in: &cell.disposeBag) - cell.buttonStackView.isHidden = false - } else { - cell.buttonStackView.isHidden = true - } - - // configure status (if exist) - if let status = notification.status { - let frame = CGRect( - x: 0, - y: 0, - width: tableView.readableContentGuide.layoutFrame.width - NotificationStatusTableViewCell.statusPadding.left - NotificationStatusTableViewCell.statusPadding.right, - height: tableView.readableContentGuide.layoutFrame.height - ) - StatusSection.configure( - cell: cell, - tableView: tableView, - timelineContext: .notifications, - dependency: dependency, - readableLayoutFrame: frame, - status: status, - requestUserID: notification.userID, - statusItemAttribute: attribute - ) - cell.statusContainerView.isHidden = false - cell.containerStackView.alignment = .top - cell.containerStackViewBottomLayoutConstraint.constant = 0 - } else { - if case .followRequest = notification.notificationType { - cell.containerStackView.alignment = .top - } else { - cell.containerStackView.alignment = .center - } - cell.statusContainerView.isHidden = true - cell.containerStackViewBottomLayoutConstraint.constant = 5 // 5pt margin when no status view - } - + // configure cell + StatusSection.configureStatusTableViewCell( + cell: cell, + tableView: tableView, + timelineContext: .notifications, + dependency: dependency, + readableLayoutFrame: tableView.readableContentGuide.layoutFrame, + status: status, + requestUserID: requestUserID, + statusItemAttribute: attribute + ) + cell.statusView.headerContainerView.isHidden = true // set header hide + cell.statusView.actionToolbarContainer.isHidden = true // set toolbar hide + cell.statusView.actionToolbarPlaceholderPaddingView.isHidden = false + cell.delegate = statusTableViewCellDelegate + cell.isAccessibilityElement = true + StatusSection.configureStatusAccessibilityLabel(cell: cell) return cell case .bottomLoader: @@ -174,3 +83,136 @@ extension NotificationSection { } } +extension NotificationSection { + static func configure( + tableView: UITableView, + cell: NotificationStatusTableViewCell, + notification: MastodonNotification, + dependency: NeedsDependency, + attribute: Item.StatusAttribute + ) { + // configure author + cell.configure( + with: AvatarConfigurableViewConfiguration( + avatarImageURL: notification.account.avatarImageURL() + ) + ) + + func createActionImage() -> UIImage? { + return UIImage( + systemName: notification.notificationType.actionImageName, + withConfiguration: UIImage.SymbolConfiguration( + pointSize: 12, weight: .semibold + ) + )? + .withTintColor(.systemBackground) + .af.imageAspectScaled(toFit: CGSize(width: 14, height: 14)) + } + + cell.avatarButton.badgeImageView.backgroundColor = notification.notificationType.color + cell.avatarButton.badgeImageView.image = createActionImage() + cell.traitCollectionDidChange + .receive(on: DispatchQueue.main) + .sink { [weak cell] in + guard let cell = cell else { return } + cell.avatarButton.badgeImageView.image = createActionImage() + } + .store(in: &cell.disposeBag) + + // configure author name, notification description, timestamp + let nameText = notification.account.displayNameWithFallback + let titleLabelText: String = { + switch notification.notificationType { + case .favourite: return L10n.Scene.Notification.userFavoritedYourPost(nameText) + case .follow: return L10n.Scene.Notification.userFollowedYou(nameText) + case .followRequest: return L10n.Scene.Notification.userRequestedToFollowYou(nameText) + case .mention: return L10n.Scene.Notification.userMentionedYou(nameText) + case .poll: return L10n.Scene.Notification.userYourPollHasEnded(nameText) + case .reblog: return L10n.Scene.Notification.userRebloggedYourPost(nameText) + default: return "" + } + }() + + do { + let nameContent = MastodonContent(content: nameText, emojis: notification.account.emojiMeta) + let nameMetaContent = try MastodonMetaContent.convert(document: nameContent) + + let mastodonContent = MastodonContent(content: titleLabelText, emojis: notification.account.emojiMeta) + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + + cell.titleLabel.configure(content: metaContent) + + if let nameRange = metaContent.string.range(of: nameMetaContent.string) { + let nsRange = NSRange(nameRange, in: metaContent.string) + cell.titleLabel.textStorage.addAttributes([ + .font: UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold), maximumPointSize: 20), + .foregroundColor: Asset.Colors.brandBlue.color, + ], range: nsRange) + } + + } catch { + let metaContent = PlaintextMetaContent(string: titleLabelText) + cell.titleLabel.configure(content: metaContent) + } + + let createAt = notification.createAt + cell.timestampLabel.text = createAt.localizedSlowedTimeAgoSinceNow + AppContext.shared.timestampUpdatePublisher + .receive(on: DispatchQueue.main) + .sink { [weak cell] _ in + guard let cell = cell else { return } + cell.timestampLabel.text = createAt.localizedSlowedTimeAgoSinceNow + } + .store(in: &cell.disposeBag) + + // configure follow request (if exist) + if case .followRequest = notification.notificationType { + cell.acceptButton.publisher(for: .touchUpInside) + .sink { [weak cell] _ in + guard let cell = cell else { return } + cell.delegate?.notificationTableViewCell(cell, notification: notification, acceptButtonDidPressed: cell.acceptButton) + } + .store(in: &cell.disposeBag) + cell.rejectButton.publisher(for: .touchUpInside) + .sink { [weak cell] _ in + guard let cell = cell else { return } + cell.delegate?.notificationTableViewCell(cell, notification: notification, rejectButtonDidPressed: cell.rejectButton) + } + .store(in: &cell.disposeBag) + cell.buttonStackView.isHidden = false + } else { + cell.buttonStackView.isHidden = true + } + + // configure status (if exist) + if let status = notification.status { + let frame = CGRect( + x: 0, + y: 0, + width: tableView.readableContentGuide.layoutFrame.width - NotificationStatusTableViewCell.statusPadding.left - NotificationStatusTableViewCell.statusPadding.right, + height: tableView.readableContentGuide.layoutFrame.height + ) + StatusSection.configure( + cell: cell, + tableView: tableView, + timelineContext: .notifications, + dependency: dependency, + readableLayoutFrame: frame, + status: status, + requestUserID: notification.userID, + statusItemAttribute: attribute + ) + cell.statusContainerView.isHidden = false + cell.containerStackView.alignment = .top + cell.containerStackViewBottomLayoutConstraint.constant = 0 + } else { + if case .followRequest = notification.notificationType { + cell.containerStackView.alignment = .top + } else { + cell.containerStackView.alignment = .center + } + cell.statusContainerView.isHidden = true + cell.containerStackViewBottomLayoutConstraint.constant = 5 // 5pt margin when no status view + } + } +} diff --git a/Mastodon/Scene/Notification/NotificationViewController+StatusProvider.swift b/Mastodon/Scene/Notification/NotificationViewController+StatusProvider.swift index 127cca1b..57272404 100644 --- a/Mastodon/Scene/Notification/NotificationViewController+StatusProvider.swift +++ b/Mastodon/Scene/Notification/NotificationViewController+StatusProvider.swift @@ -19,21 +19,25 @@ extension NotificationViewController: StatusProvider { func status(for cell: UITableViewCell?, indexPath: IndexPath?) -> Future { return Future { promise in - guard let cell = cell, - let diffableDataSource = self.viewModel.diffableDataSource, - let indexPath = self.tableView.indexPath(for: cell), + guard let diffableDataSource = self.viewModel.diffableDataSource else { + assertionFailure() + promise(.success(nil)) + return + } + guard let indexPath = indexPath ?? cell.flatMap({ self.tableView.indexPath(for: $0) }), let item = diffableDataSource.itemIdentifier(for: indexPath) else { promise(.success(nil)) return } switch item { - case .notification(let objectID, _): + case .notification(let objectID, _), + .notificationStatus(let objectID, _): self.viewModel.fetchedResultsController.managedObjectContext.perform { let notification = self.viewModel.fetchedResultsController.managedObjectContext.object(with: objectID) as! MastodonNotification promise(.success(notification.status)) } - default: + case .bottomLoader: promise(.success(nil)) } } @@ -68,3 +72,6 @@ extension NotificationViewController: StatusProvider { } } + +// MARK: - UserProvider +extension NotificationViewController: UserProvider { } diff --git a/Mastodon/Scene/Notification/NotificationViewController.swift b/Mastodon/Scene/Notification/NotificationViewController.swift index 55b4504d..0567d04d 100644 --- a/Mastodon/Scene/Notification/NotificationViewController.swift +++ b/Mastodon/Scene/Notification/NotificationViewController.swift @@ -14,8 +14,10 @@ import OSLog import UIKit import Meta import MetaTextKit +import AVKit -final class NotificationViewController: UIViewController, NeedsDependency { +final class NotificationViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } @@ -23,15 +25,18 @@ final class NotificationViewController: UIViewController, NeedsDependency { var observations = Set() private(set) lazy var viewModel = NotificationViewModel(context: context) + + let mediaPreviewTransitionController = MediaPreviewTransitionController() let segmentControl: UISegmentedControl = { let control = UISegmentedControl(items: [L10n.Scene.Notification.Title.everything, L10n.Scene.Notification.Title.mentions]) - control.selectedSegmentIndex = NotificationViewModel.NotificationSegment.EveryThing.rawValue + control.selectedSegmentIndex = NotificationViewModel.NotificationSegment.everyThing.rawValue return control }() let tableView: UITableView = { let tableView = ControlContainableTableView() + tableView.register(StatusTableViewCell.self, forCellReuseIdentifier: String(describing: StatusTableViewCell.self)) tableView.register(NotificationStatusTableViewCell.self, forCellReuseIdentifier: String(describing: NotificationStatusTableViewCell.self)) tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self)) tableView.estimatedRowHeight = UITableView.automaticDimension @@ -82,7 +87,12 @@ extension NotificationViewController { tableView.delegate = self viewModel.tableView = tableView viewModel.contentOffsetAdjustableTimelineViewControllerDelegate = self - viewModel.setupDiffableDataSource(for: tableView, delegate: self, dependency: self) + viewModel.setupDiffableDataSource( + for: tableView, + dependency: self, + delegate: self, + statusTableViewCellDelegate: self + ) viewModel.viewDidLoad.send() // bind refresh control @@ -128,9 +138,9 @@ extension NotificationViewController { self.viewModel.needsScrollToTopAfterDataSourceUpdate = true switch segment { - case .EveryThing: + case .everyThing: self.viewModel.notificationPredicate.value = MastodonNotification.predicate(domain: domain, userID: userID) - case .Mentions: + case .mentions: self.viewModel.notificationPredicate.value = MastodonNotification.predicate(domain: domain, userID: userID, typeRaw: Mastodon.Entity.Notification.NotificationType.mention.rawValue) } } @@ -148,8 +158,8 @@ extension NotificationViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - - tableView.deselectRow(with: transitionCoordinator, animated: 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 { @@ -181,6 +191,12 @@ extension NotificationViewController { // reset notification count context.notificationService.clearNotificationCountForActiveUser() } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + aspectViewDidDisappear(animated) + } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) @@ -208,33 +224,34 @@ extension NotificationViewController { } } -// MARK: - StatusTableViewControllerAspect -extension NotificationViewController: StatusTableViewControllerAspect { } - -extension NotificationViewController { - +// MARK: - TableViewCellHeightCacheableContainer +extension NotificationViewController: TableViewCellHeightCacheableContainer { + var cellFrameCache: NSCache { return viewModel.cellFrameCache } + func cacheTableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { guard let diffableDataSource = viewModel.diffableDataSource else { return } guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } switch item { - case .notification(let objectID, _): + case .notification(let objectID, _), + .notificationStatus(let objectID, _): guard let object = try? viewModel.fetchedResultsController.managedObjectContext.existingObject(with: objectID) as? MastodonNotification else { return } - let key = object.id as NSString + let key = object.objectID.hashValue let frame = cell.frame - viewModel.cellFrameCache.setObject(NSValue(cgRect: frame), forKey: key) + viewModel.cellFrameCache.setObject(NSValue(cgRect: frame), forKey: NSNumber(value: key)) case .bottomLoader: break } } - + func handleTableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { guard let diffableDataSource = viewModel.diffableDataSource else { return UITableView.automaticDimension } guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return UITableView.automaticDimension } switch item { - case .notification(let objectID, _): + case .notification(let objectID, _), + .notificationStatus(let objectID, _): guard let object = try? viewModel.fetchedResultsController.managedObjectContext.existingObject(with: objectID) as? MastodonNotification else { return UITableView.automaticDimension } - let key = object.id as NSString - guard let frame = viewModel.cellFrameCache.object(forKey: key)?.cgRectValue else { return UITableView.automaticDimension } + let key = object.objectID.hashValue + guard let frame = viewModel.cellFrameCache.object(forKey: NSNumber(value: key))?.cgRectValue else { return UITableView.automaticDimension } return frame.height case .bottomLoader: return TimelineLoaderTableViewCell.cellHeight @@ -242,22 +259,55 @@ extension NotificationViewController { } } + +// MARK: - StatusTableViewControllerAspect +extension NotificationViewController: StatusTableViewControllerAspect { } + // MARK: - UITableViewDelegate extension NotificationViewController: UITableViewDelegate { - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { + aspectTableView(tableView, estimatedHeightForRowAt: indexPath) + } + + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { guard let diffableDataSource = viewModel.diffableDataSource else { return } guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } - open(item: item) + switch item { + case .notificationStatus: + aspectTableView(tableView, willDisplay: cell, forRowAt: indexPath) + case .bottomLoader: + if !tableView.isDragging, !tableView.isDecelerating { + viewModel.loadOldestStateMachine.enter(NotificationViewModel.LoadOldestState.Loading.self) + } + default: + break + } } func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { - cacheTableView(tableView, didEndDisplaying: cell, forRowAt: indexPath) + aspectTableView(tableView, didEndDisplaying: cell, forRowAt: indexPath) + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + aspectTableView(tableView, didSelectRowAt: indexPath) } - func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { - return handleTableView(tableView, estimatedHeightForRowAt: indexPath) + func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + return aspectTableView(tableView, contextMenuConfigurationForRowAt: indexPath, point: point) + } + + func tableView(_ tableView: UITableView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { + return aspectTableView(tableView, previewForHighlightingContextMenuWithConfiguration: configuration) + } + + func tableView(_ tableView: UITableView, previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { + return aspectTableView(tableView, previewForDismissingContextMenuWithConfiguration: configuration) + } + + func tableView(_ tableView: UITableView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) { + aspectTableView(tableView, willPerformPreviewActionForMenuWith: configuration, animator: animator) } } @@ -278,19 +328,6 @@ extension NotificationViewController { break } } - - func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { - guard let diffableDataSource = viewModel.diffableDataSource else { return } - guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } - switch item { - case .bottomLoader: - if !tableView.isDragging, !tableView.isDecelerating { - viewModel.loadOldestStateMachine.enter(NotificationViewModel.LoadOldestState.Loading.self) - } - default: - break - } - } } // MARK: - ContentOffsetAdjustableTimelineViewControllerDelegate @@ -388,6 +425,7 @@ extension NotificationViewController: ScrollViewContainer { } } +// MARK: - LoadMoreConfigurableTableViewContainer extension NotificationViewController: LoadMoreConfigurableTableViewContainer { typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell typealias LoadingState = NotificationViewModel.LoadOldestState.Loading @@ -395,6 +433,24 @@ extension NotificationViewController: LoadMoreConfigurableTableViewContainer { var loadMoreConfigurableStateMachine: GKStateMachine { viewModel.loadOldestStateMachine } } +// MARK: - AVPlayerViewControllerDelegate +extension NotificationViewController: AVPlayerViewControllerDelegate { + func playerViewController(_ playerViewController: AVPlayerViewController, willBeginFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) { + handlePlayerViewController(playerViewController, willBeginFullScreenPresentationWithAnimationCoordinator: coordinator) + } + + func playerViewController(_ playerViewController: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) { + handlePlayerViewController(playerViewController, willEndFullScreenPresentationWithAnimationCoordinator: coordinator) + } +} + +// MARK: - statusTableViewCellDelegate +extension NotificationViewController: StatusTableViewCellDelegate { + var playerViewControllerDelegate: AVPlayerViewControllerDelegate? { + return self + } +} + extension NotificationViewController { enum CategorySwitch: String, CaseIterable { @@ -452,9 +508,9 @@ extension NotificationViewController { switch category { case .showEverything: - viewModel.selectedIndex.value = .EveryThing + viewModel.selectedIndex.value = .everyThing case .showMentions: - viewModel.selectedIndex.value = .Mentions + viewModel.selectedIndex.value = .mentions } } diff --git a/Mastodon/Scene/Notification/NotificationViewModel+Diffable.swift b/Mastodon/Scene/Notification/NotificationViewModel+Diffable.swift index 6a542bf2..6c7a70e4 100644 --- a/Mastodon/Scene/Notification/NotificationViewModel+Diffable.swift +++ b/Mastodon/Scene/Notification/NotificationViewModel+Diffable.swift @@ -14,14 +14,16 @@ import MastodonSDK extension NotificationViewModel { func setupDiffableDataSource( for tableView: UITableView, + dependency: NeedsDependency, delegate: NotificationTableViewCellDelegate, - dependency: NeedsDependency + statusTableViewCellDelegate: StatusTableViewCellDelegate ) { diffableDataSource = NotificationSection.tableViewDiffableDataSource( for: tableView, + dependency: dependency, managedObjectContext: fetchedResultsController.managedObjectContext, delegate: delegate, - dependency: dependency + statusTableViewCellDelegate: statusTableViewCellDelegate ) var snapshot = NSDiffableDataSourceSnapshot() @@ -81,11 +83,23 @@ extension NotificationViewModel: NSFetchedResultsControllerDelegate { } var newSnapshot = NSDiffableDataSourceSnapshot() newSnapshot.appendSections([.main]) - let items: [NotificationItem] = notifications.map { notification in - let attribute: Item.StatusAttribute = oldSnapshotAttributeDict[notification.objectID] ?? Item.StatusAttribute() - return NotificationItem.notification(objectID: notification.objectID, attribute: attribute) + + let segment = self.selectedIndex.value + switch segment { + case .everyThing: + let items: [NotificationItem] = notifications.map { notification in + let attribute: Item.StatusAttribute = oldSnapshotAttributeDict[notification.objectID] ?? Item.StatusAttribute() + return NotificationItem.notification(objectID: notification.objectID, attribute: attribute) + } + newSnapshot.appendItems(items, toSection: .main) + case .mentions: + let items: [NotificationItem] = notifications.map { notification in + let attribute: Item.StatusAttribute = oldSnapshotAttributeDict[notification.objectID] ?? Item.StatusAttribute() + return NotificationItem.notificationStatus(objectID: notification.objectID, attribute: attribute) + } + newSnapshot.appendItems(items, toSection: .main) } - newSnapshot.appendItems(items, toSection: .main) + if !notifications.isEmpty, self.noMoreNotification.value == false { newSnapshot.appendItems([.bottomLoader], toSection: .main) } diff --git a/Mastodon/Scene/Notification/NotificationViewModel+LoadOldestState.swift b/Mastodon/Scene/Notification/NotificationViewModel+LoadOldestState.swift index 9567d6cb..bf2c0317 100644 --- a/Mastodon/Scene/Notification/NotificationViewModel+LoadOldestState.swift +++ b/Mastodon/Scene/Notification/NotificationViewModel+LoadOldestState.swift @@ -92,13 +92,13 @@ extension NotificationViewModel.LoadOldestState { } receiveValue: { [weak viewModel] response in guard let viewModel = viewModel else { return } switch viewModel.selectedIndex.value { - case .EveryThing: + case .everyThing: if response.value.isEmpty { stateMachine.enter(NoMore.self) } else { stateMachine.enter(Idle.self) } - case .Mentions: + case .mentions: viewModel.noMoreNotification.value = response.value.isEmpty let list = response.value.filter { $0.type == Mastodon.Entity.Notification.NotificationType.mention } if list.isEmpty { diff --git a/Mastodon/Scene/Notification/NotificationViewModel.swift b/Mastodon/Scene/Notification/NotificationViewModel.swift index 71238091..98b7deec 100644 --- a/Mastodon/Scene/Notification/NotificationViewModel.swift +++ b/Mastodon/Scene/Notification/NotificationViewModel.swift @@ -23,13 +23,13 @@ final class NotificationViewModel: NSObject { weak var contentOffsetAdjustableTimelineViewControllerDelegate: ContentOffsetAdjustableTimelineViewControllerDelegate? let viewDidLoad = PassthroughSubject() - let selectedIndex = CurrentValueSubject(.EveryThing) + let selectedIndex = CurrentValueSubject(.everyThing) let noMoreNotification = CurrentValueSubject(false) let activeMastodonAuthenticationBox: CurrentValueSubject let fetchedResultsController: NSFetchedResultsController! let notificationPredicate = CurrentValueSubject(nil) - let cellFrameCache = NSCache() + let cellFrameCache = NSCache() var needsScrollToTopAfterDataSourceUpdate = false let dataSourceDidUpdated = PassthroughSubject() @@ -161,7 +161,7 @@ final class NotificationViewModel: NSObject { extension NotificationViewModel { enum NotificationSegment: Int { - case EveryThing - case Mentions + case everyThing + case mentions } } diff --git a/Mastodon/Scene/Share/View/Content/StatusView.swift b/Mastodon/Scene/Share/View/Content/StatusView.swift index 957764fa..62eb3d6b 100644 --- a/Mastodon/Scene/Share/View/Content/StatusView.swift +++ b/Mastodon/Scene/Share/View/Content/StatusView.swift @@ -203,6 +203,9 @@ final class StatusView: UIView { return actionToolbarContainer }() + // set display when needs bottom padding + let actionToolbarPlaceholderPaddingView = UIView() + let contentMetaText: MetaText = { let metaText = MetaText() metaText.textView.backgroundColor = .clear @@ -451,6 +454,13 @@ extension StatusView { containerStackView.sendSubviewToBack(actionToolbarContainer) actionToolbarContainer.setContentCompressionResistancePriority(.defaultHigh, for: .vertical) actionToolbarContainer.setContentHuggingPriority(.required - 1, for: .vertical) + + actionToolbarPlaceholderPaddingView.translatesAutoresizingMaskIntoConstraints = false + containerStackView.addArrangedSubview(actionToolbarPlaceholderPaddingView) + NSLayoutConstraint.activate([ + actionToolbarPlaceholderPaddingView.heightAnchor.constraint(equalToConstant: 12).priority(.required - 1), + ]) + actionToolbarPlaceholderPaddingView.isHidden = true headerContainerView.isHidden = true statusMosaicImageViewContainer.isHidden = true From 8ebb2e5347460b6a112992411cdad4d76f0a4f3d Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 2 Nov 2021 14:56:42 +0800 Subject: [PATCH 83/96] feat: add following list --- Mastodon.xcodeproj/project.pbxproj | 28 +++ .../xcschemes/xcschememanagement.plist | 8 +- Mastodon/Coordinator/SceneCoordinator.swift | 5 + Mastodon/Diffiable/Item/UserItem.swift | 1 + Mastodon/Diffiable/Section/UserSection.swift | 3 +- .../FollowerListViewController+Provider.swift | 3 +- .../Follower/FollowerListViewController.swift | 7 +- .../FollowerListViewModel+Diffable.swift | 16 +- .../FollowerListViewModel+State.swift | 12 -- ...FollowingListViewController+Provider.swift | 51 +++++ .../FollowingListViewController.swift | 108 ++++++++++ .../FollowingListViewModel+Diffable.swift | 61 ++++++ .../FollowingListViewModel+State.swift | 196 ++++++++++++++++++ .../Following/FollowingListViewModel.swift | 53 +++++ .../Scene/Profile/ProfileViewController.swift | 15 +- 15 files changed, 536 insertions(+), 31 deletions(-) create mode 100644 Mastodon/Scene/Profile/Following/FollowingListViewController+Provider.swift create mode 100644 Mastodon/Scene/Profile/Following/FollowingListViewController.swift create mode 100644 Mastodon/Scene/Profile/Following/FollowingListViewModel+Diffable.swift create mode 100644 Mastodon/Scene/Profile/Following/FollowingListViewModel+State.swift create mode 100644 Mastodon/Scene/Profile/Following/FollowingListViewModel.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 60f0f5d7..8408f3ac 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -296,6 +296,11 @@ DB59F10425EF5EBC001F1DAB /* TableViewCellHeightCacheableContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F10325EF5EBC001F1DAB /* TableViewCellHeightCacheableContainer.swift */; }; DB59F10E25EF724F001F1DAB /* APIService+Poll.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F10D25EF724F001F1DAB /* APIService+Poll.swift */; }; DB59F11825EFA35B001F1DAB /* StripProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F11725EFA35B001F1DAB /* StripProgressView.swift */; }; + DB5B7295273112B100081888 /* FollowingListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5B7294273112B100081888 /* FollowingListViewController.swift */; }; + DB5B7298273112C800081888 /* FollowingListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5B7297273112C800081888 /* FollowingListViewModel.swift */; }; + DB5B729A2731137900081888 /* FollowingListViewController+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5B72992731137900081888 /* FollowingListViewController+Provider.swift */; }; + DB5B729C273113C200081888 /* FollowingListViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5B729B273113C200081888 /* FollowingListViewModel+Diffable.swift */; }; + DB5B729E273113F300081888 /* FollowingListViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5B729D273113F300081888 /* FollowingListViewModel+State.swift */; }; DB6180DD263918E30018D199 /* MediaPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180DC263918E30018D199 /* MediaPreviewViewController.swift */; }; DB6180E02639194B0018D199 /* MediaPreviewPagingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180DF2639194B0018D199 /* MediaPreviewPagingViewController.swift */; }; DB6180E326391A4C0018D199 /* ViewControllerAnimatedTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180E226391A4C0018D199 /* ViewControllerAnimatedTransitioning.swift */; }; @@ -1110,6 +1115,11 @@ DB59F10325EF5EBC001F1DAB /* TableViewCellHeightCacheableContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewCellHeightCacheableContainer.swift; sourceTree = ""; }; DB59F10D25EF724F001F1DAB /* APIService+Poll.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Poll.swift"; sourceTree = ""; }; DB59F11725EFA35B001F1DAB /* StripProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripProgressView.swift; sourceTree = ""; }; + DB5B7294273112B100081888 /* FollowingListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowingListViewController.swift; sourceTree = ""; }; + DB5B7297273112C800081888 /* FollowingListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowingListViewModel.swift; sourceTree = ""; }; + DB5B72992731137900081888 /* FollowingListViewController+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FollowingListViewController+Provider.swift"; sourceTree = ""; }; + DB5B729B273113C200081888 /* FollowingListViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FollowingListViewModel+Diffable.swift"; sourceTree = ""; }; + DB5B729D273113F300081888 /* FollowingListViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FollowingListViewModel+State.swift"; sourceTree = ""; }; DB6180DC263918E30018D199 /* MediaPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewViewController.swift; sourceTree = ""; }; DB6180DF2639194B0018D199 /* MediaPreviewPagingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewPagingViewController.swift; sourceTree = ""; }; DB6180E226391A4C0018D199 /* ViewControllerAnimatedTransitioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerAnimatedTransitioning.swift; sourceTree = ""; }; @@ -2453,6 +2463,18 @@ path = View; sourceTree = ""; }; + DB5B7296273112B400081888 /* Following */ = { + isa = PBXGroup; + children = ( + DB5B7294273112B100081888 /* FollowingListViewController.swift */, + DB5B72992731137900081888 /* FollowingListViewController+Provider.swift */, + DB5B7297273112C800081888 /* FollowingListViewModel.swift */, + DB5B729B273113C200081888 /* FollowingListViewModel+Diffable.swift */, + DB5B729D273113F300081888 /* FollowingListViewModel+State.swift */, + ); + path = Following; + sourceTree = ""; + }; DB6180DE263919350018D199 /* MediaPreview */ = { isa = PBXGroup; children = ( @@ -2902,6 +2924,7 @@ DBB5253B2611ECF5002F1F29 /* Timeline */, DBE3CDF1261C6B3100430CC6 /* Favorite */, DB6B74F0272FB55400C70B6E /* Follower */, + DB5B7296273112B400081888 /* Following */, DB9D6BFE25E4F5940051B173 /* ProfileViewController.swift */, DBAE3F812615DDA3004B8251 /* ProfileViewController+UserProvider.swift */, DBB5255D2611F07A002F1F29 /* ProfileViewModel.swift */, @@ -3918,6 +3941,7 @@ DBAE3F8E2616E0B1004B8251 /* APIService+Block.swift in Sources */, 5DF1057F25F88A4100D6C0D4 /* TouchBlockingView.swift in Sources */, DB1D843426579931000346B3 /* TableViewControllerNavigateable.swift in Sources */, + DB5B729A2731137900081888 /* FollowingListViewController+Provider.swift in Sources */, 0FAA0FDF25E0B57E0017CCDE /* WelcomeViewController.swift in Sources */, 2D206B8C25F6015000143C56 /* AudioPlaybackService.swift in Sources */, 2D59819B25E4A581000FB903 /* MastodonConfirmEmailViewController.swift in Sources */, @@ -3929,6 +3953,7 @@ DB03A793272A7E5700EE37C5 /* SidebarListHeaderView.swift in Sources */, DB4FFC2B269EC39600D62E92 /* SearchToSearchDetailViewControllerAnimatedTransitioning.swift in Sources */, DBCC3B9526157E6E0045B23D /* APIService+Relationship.swift in Sources */, + DB5B7298273112C800081888 /* FollowingListViewModel.swift in Sources */, 2D7631B325C159F700929FB9 /* Item.swift in Sources */, 5DF1054125F886D400D6C0D4 /* VideoPlaybackService.swift in Sources */, DB6B35182601FA3400DC1E11 /* MastodonAttachmentService.swift in Sources */, @@ -4065,6 +4090,7 @@ DBE3CE01261D623D00430CC6 /* FavoriteViewModel+State.swift in Sources */, 2D82BA0525E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift in Sources */, 2D38F1EB25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift in Sources */, + DB5B7295273112B100081888 /* FollowingListViewController.swift in Sources */, 0F202201261326E6000C64BF /* HashtagTimelineViewModel.swift in Sources */, DB6D9F9726367249008423CD /* SettingsViewController.swift in Sources */, DB4F097F26A03DA600D62E92 /* SearchHistoryFetchedResultController.swift in Sources */, @@ -4075,8 +4101,10 @@ DB73BF47271199CA00781945 /* Instance.swift in Sources */, DB0F8150264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift in Sources */, DB98337F25C9452D00AD9700 /* APIService+APIError.swift in Sources */, + DB5B729C273113C200081888 /* FollowingListViewModel+Diffable.swift in Sources */, DB9E0D6F25EE008500CFDD76 /* UIInterpolatingMotionEffect.swift in Sources */, DBB9759C262462E1004620BD /* ThreadMetaView.swift in Sources */, + DB5B729E273113F300081888 /* FollowingListViewModel+State.swift in Sources */, 2DF123A725C3B0210020F248 /* ActiveLabel.swift in Sources */, 5DDDF1A92617489F00311060 /* Mastodon+Entity+History.swift in Sources */, DB59F11825EFA35B001F1DAB /* StripProgressView.swift in Sources */, diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index cb88c396..2d2dd193 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ AppShared.xcscheme_^#shared#^_ orderHint - 42 + 36 CoreDataStack.xcscheme_^#shared#^_ orderHint - 43 + 35 Mastodon - ASDK.xcscheme_^#shared#^_ @@ -97,7 +97,7 @@ MastodonIntent.xcscheme_^#shared#^_ orderHint - 44 + 38 MastodonIntents.xcscheme_^#shared#^_ @@ -117,7 +117,7 @@ ShareActionExtension.xcscheme_^#shared#^_ orderHint - 41 + 37 SuppressBuildableAutocreation diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index cda20255..e7611056 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -179,6 +179,7 @@ extension SceneCoordinator { case profile(viewModel: ProfileViewModel) case favorite(viewModel: FavoriteViewModel) case follower(viewModel: FollowerListViewModel) + case following(viewModel: FollowingListViewModel) // setting case settings(viewModel: SettingsViewModel) @@ -429,6 +430,10 @@ private extension SceneCoordinator { let _viewController = FollowerListViewController() _viewController.viewModel = viewModel viewController = _viewController + case .following(let viewModel): + let _viewController = FollowingListViewController() + _viewController.viewModel = viewModel + viewController = _viewController case .suggestionAccount(let viewModel): let _viewController = SuggestionAccountViewController() _viewController.viewModel = viewModel diff --git a/Mastodon/Diffiable/Item/UserItem.swift b/Mastodon/Diffiable/Item/UserItem.swift index 6f3c591b..bd15f35e 100644 --- a/Mastodon/Diffiable/Item/UserItem.swift +++ b/Mastodon/Diffiable/Item/UserItem.swift @@ -10,6 +10,7 @@ import CoreData enum UserItem: Hashable { case follower(objectID: NSManagedObjectID) + case following(objectID: NSManagedObjectID) case bottomLoader case bottomHeader(text: String) } diff --git a/Mastodon/Diffiable/Section/UserSection.swift b/Mastodon/Diffiable/Section/UserSection.swift index 58e80c6e..9c7e2f21 100644 --- a/Mastodon/Diffiable/Section/UserSection.swift +++ b/Mastodon/Diffiable/Section/UserSection.swift @@ -30,7 +30,8 @@ extension UserSection { ] tableView, indexPath, item -> UITableViewCell? in guard let dependency = dependency else { return UITableViewCell() } switch item { - case .follower(let objectID): + case .follower(let objectID), + .following(let objectID): let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: UserTableViewCell.self), for: indexPath) as! UserTableViewCell managedObjectContext.performAndWait { let user = managedObjectContext.object(with: objectID) as! MastodonUser diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewController+Provider.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewController+Provider.swift index 627ed777..25e10284 100644 --- a/Mastodon/Scene/Profile/Follower/FollowerListViewController+Provider.swift +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewController+Provider.swift @@ -37,7 +37,8 @@ extension FollowerListViewController: UserProvider { let managedObjectContext = self.viewModel.userFetchedResultsController.fetchedResultsController.managedObjectContext switch item { - case .follower(let objectID): + case .follower(let objectID), + .following(let objectID): managedObjectContext.perform { let user = managedObjectContext.object(with: objectID) as? MastodonUser promise(.success(user)) diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift index 42844866..97e62ea8 100644 --- a/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift @@ -7,11 +7,10 @@ import os.log import UIKit -import AVKit import GameplayKit import Combine -final class FollowerListViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { +final class FollowerListViewController: UIViewController, NeedsDependency { var disposeBag = Set() @@ -19,9 +18,7 @@ final class FollowerListViewController: UIViewController, NeedsDependency, Media weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } var viewModel: FollowerListViewModel! - - let mediaPreviewTransitionController = MediaPreviewTransitionController() - + lazy var tableView: UITableView = { let tableView = UITableView() tableView.register(UserTableViewCell.self, forCellReuseIdentifier: String(describing: UserTableViewCell.self)) diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift index 90b9cb31..c048e172 100644 --- a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift @@ -18,16 +18,19 @@ extension FollowerListViewModel { managedObjectContext: userFetchedResultsController.fetchedResultsController.managedObjectContext ) + // workaround to append loader wrong animation issue // set empty section to make update animation top-to-bottom style var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) - diffableDataSource?.apply(snapshot) - - // workaround to append loader wrong animation issue snapshot.appendItems([.bottomLoader], toSection: .main) - diffableDataSource?.apply(snapshot) + if #available(iOS 15.0, *) { + diffableDataSource?.applySnapshotUsingReloadData(snapshot, completion: nil) + } else { + // Fallback on earlier versions + diffableDataSource?.apply(snapshot, animatingDifferences: false) + } - userFetchedResultsController.objectIDs.removeDuplicates() + userFetchedResultsController.objectIDs .receive(on: DispatchQueue.main) .sink { [weak self] objectIDs in guard let self = self else { return } @@ -45,7 +48,8 @@ extension FollowerListViewModel { case is State.Idle, is State.Loading, is State.Fail: snapshot.appendItems([.bottomLoader], toSection: .main) case is State.NoMore: - break + let text = "Followers from other servers are not displayed." + snapshot.appendItems([.bottomHeader(text: text)], toSection: .main) default: break } diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift index b012a59b..87693318 100644 --- a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift @@ -179,18 +179,6 @@ extension FollowerListViewModel.State { override func didEnter(from previousState: GKState?) { super.didEnter(from: previousState) - guard let viewModel = viewModel, let _ = stateMachine else { return } - guard let diffableDataSource = viewModel.diffableDataSource else { - assertionFailure() - return - } - DispatchQueue.main.async { - var snapshot = diffableDataSource.snapshot() - snapshot.deleteItems([.bottomLoader]) - let header = UserItem.bottomHeader(text: "Followers from other servers are not displayed") - snapshot.appendItems([header], toSection: .main) - diffableDataSource.apply(snapshot, animatingDifferences: false) - } } } } diff --git a/Mastodon/Scene/Profile/Following/FollowingListViewController+Provider.swift b/Mastodon/Scene/Profile/Following/FollowingListViewController+Provider.swift new file mode 100644 index 00000000..aaeb5232 --- /dev/null +++ b/Mastodon/Scene/Profile/Following/FollowingListViewController+Provider.swift @@ -0,0 +1,51 @@ +// +// FollowingListViewController+Provider.swift +// Mastodon +// +// Created by Cirno MainasuK on 2021-11-2. +// + +import os.log +import UIKit +import Combine +import CoreData +import CoreDataStack + +extension FollowingListViewController: UserProvider { + + func mastodonUser() -> Future { + Future { promise in + promise(.success(nil)) + } + } + + func mastodonUser(for cell: UITableViewCell?) -> Future { + Future { [weak self] promise in + guard let self = self else { return } + guard let diffableDataSource = self.viewModel.diffableDataSource else { + assertionFailure() + promise(.success(nil)) + return + } + guard let cell = cell, + let indexPath = self.tableView.indexPath(for: cell), + let item = diffableDataSource.itemIdentifier(for: indexPath) else { + promise(.success(nil)) + return + } + + let managedObjectContext = self.viewModel.userFetchedResultsController.fetchedResultsController.managedObjectContext + + switch item { + case .follower(let objectID), + .following(let objectID): + managedObjectContext.perform { + let user = managedObjectContext.object(with: objectID) as? MastodonUser + promise(.success(user)) + } + case .bottomLoader, .bottomHeader: + promise(.success(nil)) + } + } + } +} diff --git a/Mastodon/Scene/Profile/Following/FollowingListViewController.swift b/Mastodon/Scene/Profile/Following/FollowingListViewController.swift new file mode 100644 index 00000000..35691b82 --- /dev/null +++ b/Mastodon/Scene/Profile/Following/FollowingListViewController.swift @@ -0,0 +1,108 @@ +// +// FollowingListViewController.swift +// Mastodon +// +// Created by Cirno MainasuK on 2021-11-2. +// + +import os.log +import UIKit +import GameplayKit +import Combine + +final class FollowingListViewController: UIViewController, NeedsDependency { + + var disposeBag = Set() + + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } + weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + + var viewModel: FollowingListViewModel! + + lazy var tableView: UITableView = { + let tableView = UITableView() + tableView.register(UserTableViewCell.self, forCellReuseIdentifier: String(describing: UserTableViewCell.self)) + tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self)) + tableView.register(TimelineFooterTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineFooterTableViewCell.self)) + tableView.rowHeight = UITableView.automaticDimension + tableView.separatorStyle = .none + tableView.backgroundColor = .clear + return tableView + }() + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + +extension FollowingListViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor + ThemeService.shared.currentTheme + .receive(on: RunLoop.main) + .sink { [weak self] theme in + guard let self = self else { return } + self.view.backgroundColor = theme.secondarySystemBackgroundColor + } + .store(in: &disposeBag) + + tableView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(tableView) + NSLayoutConstraint.activate([ + tableView.topAnchor.constraint(equalTo: view.topAnchor), + tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + + tableView.delegate = self + viewModel.setupDiffableDataSource( + for: tableView, + dependency: self + ) + // TODO: add UserTableViewCellDelegate + + // trigger user timeline loading + Publishers.CombineLatest( + viewModel.domain.removeDuplicates().eraseToAnyPublisher(), + viewModel.userID.removeDuplicates().eraseToAnyPublisher() + ) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self = self else { return } + self.viewModel.stateMachine.enter(FollowingListViewModel.State.Reloading.self) + } + .store(in: &disposeBag) + } + +} + +// MARK: - LoadMoreConfigurableTableViewContainer +extension FollowingListViewController: LoadMoreConfigurableTableViewContainer { + typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell + typealias LoadingState = FollowingListViewModel.State.Loading + var loadMoreConfigurableTableView: UITableView { tableView } + var loadMoreConfigurableStateMachine: GKStateMachine { viewModel.stateMachine } +} + +// MARK: - UIScrollViewDelegate +extension FollowingListViewController { + func scrollViewDidScroll(_ scrollView: UIScrollView) { + handleScrollViewDidScroll(scrollView) + } +} + + +// MARK: - UITableViewDelegate +extension FollowingListViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + handleTableView(tableView, didSelectRowAt: indexPath) + } +} + +// MARK: - UserTableViewCellDelegate +extension FollowingListViewController: UserTableViewCellDelegate { } diff --git a/Mastodon/Scene/Profile/Following/FollowingListViewModel+Diffable.swift b/Mastodon/Scene/Profile/Following/FollowingListViewModel+Diffable.swift new file mode 100644 index 00000000..58af276d --- /dev/null +++ b/Mastodon/Scene/Profile/Following/FollowingListViewModel+Diffable.swift @@ -0,0 +1,61 @@ +// +// FollowingListViewModel+Diffable.swift +// Mastodon +// +// Created by Cirno MainasuK on 2021-11-2. +// + +import UIKit + +extension FollowingListViewModel { + func setupDiffableDataSource( + for tableView: UITableView, + dependency: NeedsDependency + ) { + diffableDataSource = UserSection.tableViewDiffableDataSource( + for: tableView, + dependency: dependency, + managedObjectContext: userFetchedResultsController.fetchedResultsController.managedObjectContext + ) + + // workaround to append loader wrong animation issue + // set empty section to make update animation top-to-bottom style + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + snapshot.appendItems([.bottomLoader], toSection: .main) + if #available(iOS 15.0, *) { + diffableDataSource?.applySnapshotUsingReloadData(snapshot, completion: nil) + } else { + // Fallback on earlier versions + diffableDataSource?.apply(snapshot, animatingDifferences: false) + } + + userFetchedResultsController.objectIDs + .receive(on: DispatchQueue.main) + .sink { [weak self] objectIDs in + guard let self = self else { return } + guard let diffableDataSource = self.diffableDataSource else { return } + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + let items: [UserItem] = objectIDs.map { + UserItem.following(objectID: $0) + } + snapshot.appendItems(items, toSection: .main) + + if let currentState = self.stateMachine.currentState { + switch currentState { + case is State.Idle, is State.Loading, is State.Fail: + snapshot.appendItems([.bottomLoader], toSection: .main) + case is State.NoMore: + break + default: + break + } + } + + diffableDataSource.apply(snapshot) + } + .store(in: &disposeBag) + } +} diff --git a/Mastodon/Scene/Profile/Following/FollowingListViewModel+State.swift b/Mastodon/Scene/Profile/Following/FollowingListViewModel+State.swift new file mode 100644 index 00000000..d5c30b43 --- /dev/null +++ b/Mastodon/Scene/Profile/Following/FollowingListViewModel+State.swift @@ -0,0 +1,196 @@ +// +// FollowingListViewModel+State.swift +// Mastodon +// +// Created by Cirno MainasuK on 2021-11-2. +// + +import os.log +import Foundation +import GameplayKit +import MastodonSDK + +extension FollowingListViewModel { + class State: GKState { + weak var viewModel: FollowingListViewModel? + + init(viewModel: FollowingListViewModel) { + self.viewModel = viewModel + } + + override func didEnter(from previousState: GKState?) { + os_log("%{public}s[%{public}ld], %{public}s: enter %s, previous: %s", ((#file as NSString).lastPathComponent), #line, #function, self.debugDescription, previousState.debugDescription) + } + } +} + +extension FollowingListViewModel.State { + class Initial: FollowingListViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + guard let viewModel = viewModel else { return false } + switch stateClass { + case is Reloading.Type: + return viewModel.userID.value != nil + default: + return false + } + } + } + + class Reloading: FollowingListViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Loading.Type: + return true + default: + return false + } + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + guard let viewModel = viewModel, let stateMachine = stateMachine else { return } + + // reset + viewModel.userFetchedResultsController.userIDs.value = [] + + stateMachine.enter(Loading.self) + } + } + + class Fail: FollowingListViewModel.State { + + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Loading.Type: + return true + default: + return false + } + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + guard let _ = viewModel, let stateMachine = stateMachine else { return } + + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: retry loading 3s later…", ((#file as NSString).lastPathComponent), #line, #function) + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: retry loading", ((#file as NSString).lastPathComponent), #line, #function) + stateMachine.enter(Loading.self) + } + } + } + + class Idle: FollowingListViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Reloading.Type, is Loading.Type: + return true + default: + return false + } + } + } + + class Loading: FollowingListViewModel.State { + + var maxID: String? + + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Fail.Type: + return true + case is Idle.Type: + return true + case is NoMore.Type: + return true + default: + return false + } + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + + if previousState is Reloading { + maxID = nil + } + + guard let viewModel = viewModel, let stateMachine = stateMachine else { return } + + guard let userID = viewModel.userID.value, !userID.isEmpty else { + stateMachine.enter(Fail.self) + return + } + + guard let activeMastodonAuthenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else { + stateMachine.enter(Fail.self) + return + } + + viewModel.context.apiService.followers( + userID: userID, + maxID: maxID, + authorizationBox: activeMastodonAuthenticationBox + ) + .receive(on: DispatchQueue.main) + .sink { completion in + switch completion { + case .failure(let error): + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: fetch user timeline fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription) + stateMachine.enter(Fail.self) + case .finished: + break + } + } receiveValue: { response in + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + + var hasNewAppend = false + var userIDs = viewModel.userFetchedResultsController.userIDs.value + for user in response.value { + guard !userIDs.contains(user.id) else { continue } + userIDs.append(user.id) + hasNewAppend = true + } + + let maxID = response.link?.maxID + + if hasNewAppend, maxID != nil { + stateMachine.enter(Idle.self) + } else { + stateMachine.enter(NoMore.self) + } + self.maxID = maxID + viewModel.userFetchedResultsController.userIDs.value = userIDs + } + .store(in: &viewModel.disposeBag) + } // end func didEnter + } + + class NoMore: FollowingListViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Reloading.Type: + return true + default: + return false + } + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + guard let viewModel = viewModel, let _ = stateMachine else { return } + guard let diffableDataSource = viewModel.diffableDataSource else { + assertionFailure() + return + } + DispatchQueue.main.async { + var snapshot = diffableDataSource.snapshot() + snapshot.deleteItems([.bottomLoader]) + let header = UserItem.bottomHeader(text: "Followers from other servers are not displayed") + snapshot.appendItems([header], toSection: .main) + diffableDataSource.apply(snapshot, animatingDifferences: false) + } + } + } +} diff --git a/Mastodon/Scene/Profile/Following/FollowingListViewModel.swift b/Mastodon/Scene/Profile/Following/FollowingListViewModel.swift new file mode 100644 index 00000000..0677f6cb --- /dev/null +++ b/Mastodon/Scene/Profile/Following/FollowingListViewModel.swift @@ -0,0 +1,53 @@ +// +// FollowingListViewModel.swift +// Mastodon +// +// Created by Cirno MainasuK on 2021-11-2. +// + +import Foundation +import Combine +import Combine +import CoreData +import CoreDataStack +import GameplayKit +import MastodonSDK + +final class FollowingListViewModel { + + var disposeBag = Set() + + // input + let context: AppContext + let domain: CurrentValueSubject + let userID: CurrentValueSubject + let userFetchedResultsController: UserFetchedResultsController + + // output + var diffableDataSource: UITableViewDiffableDataSource? + private(set) lazy var stateMachine: GKStateMachine = { + let stateMachine = GKStateMachine(states: [ + State.Initial(viewModel: self), + State.Reloading(viewModel: self), + State.Fail(viewModel: self), + State.Idle(viewModel: self), + State.Loading(viewModel: self), + State.NoMore(viewModel: self), + ]) + stateMachine.enter(State.Initial.self) + return stateMachine + }() + + init(context: AppContext, domain: String?, userID: String?) { + self.context = context + self.userFetchedResultsController = UserFetchedResultsController( + managedObjectContext: context.managedObjectContext, + domain: domain, + additionalTweetPredicate: nil + ) + self.domain = CurrentValueSubject(domain) + self.userID = CurrentValueSubject(userID) + // super.init() + + } +} diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index 04d58231..5ff71ba9 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -1001,8 +1001,19 @@ extension ProfileViewController: ProfileHeaderViewDelegate { transition: .show ) case .following: - // TODO: - break + guard let domain = viewModel.domain.value, + let userID = viewModel.userID.value + else { return } + let followingListViewModel = FollowingListViewModel( + context: context, + domain: domain, + userID: userID + ) + coordinator.present( + scene: .following(viewModel: followingListViewModel), + from: self, + transition: .show + ) } } From 613916e0406d9c8a4823d089382e3f8b34e7bebf Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 2 Nov 2021 08:05:08 +0100 Subject: [PATCH 84/96] New translations app.json (Chinese Simplified) --- Localization/StringsConvertor/input/zh_CN/app.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Localization/StringsConvertor/input/zh_CN/app.json b/Localization/StringsConvertor/input/zh_CN/app.json index b3a8866b..905afdd8 100644 --- a/Localization/StringsConvertor/input/zh_CN/app.json +++ b/Localization/StringsConvertor/input/zh_CN/app.json @@ -67,7 +67,7 @@ "done": "完成", "confirm": "确认", "continue": "继续", - "compose": "Compose", + "compose": "撰写", "cancel": "取消", "discard": "放弃", "try_again": "再试一次", @@ -415,10 +415,10 @@ } }, "follower": { - "footer": "Followers from other servers are not displayed." + "footer": "不会显示来自其它服务器的关注者" }, "following": { - "footer": "Follows from other servers are not displayed." + "footer": "不会显示来自其它服务器的关注" }, "search": { "title": "搜索", From dfb84fdb5bad9258b37246908cf618e2bc42ae48 Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 2 Nov 2021 15:07:45 +0800 Subject: [PATCH 85/96] chore: update i18n resources --- Mastodon/Generated/Strings.swift | 10 +++ .../Resources/ar.lproj/Localizable.strings | 3 + .../Resources/ca.lproj/Localizable.strings | 3 + .../Resources/de.lproj/Localizable.strings | 3 + .../Resources/en.lproj/Localizable.strings | 3 + .../es-419.lproj/Localizable.strings | 3 + .../Resources/es.lproj/Localizable.strings | 3 + .../Resources/fr.lproj/Localizable.strings | 3 + .../Resources/gd-GB.lproj/Localizable.strings | 3 + .../Resources/ja.lproj/Localizable.strings | 3 + .../Resources/ku-TR.lproj/Localizable.strings | 67 ++++++++++--------- .../ku-TR.lproj/Localizable.stringsdict | 4 +- .../Resources/nl.lproj/Localizable.strings | 3 + .../Resources/ru.lproj/Localizable.strings | 3 + .../Resources/th.lproj/Localizable.strings | 3 + .../zh-Hans.lproj/Localizable.strings | 3 + 16 files changed, 86 insertions(+), 34 deletions(-) diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index 93cc4ca3..ebf9869c 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -106,6 +106,8 @@ internal enum L10n { } /// Cancel internal static let cancel = L10n.tr("Localizable", "Common.Controls.Actions.Cancel") + /// Compose + internal static let compose = L10n.tr("Localizable", "Common.Controls.Actions.Compose") /// Confirm internal static let confirm = L10n.tr("Localizable", "Common.Controls.Actions.Confirm") /// Continue @@ -523,6 +525,14 @@ internal enum L10n { /// Your Favorites internal static let title = L10n.tr("Localizable", "Scene.Favorite.Title") } + internal enum Follower { + /// Followers from other servers are not displayed. + internal static let footer = L10n.tr("Localizable", "Scene.Follower.Footer") + } + internal enum Following { + /// Follows from other servers are not displayed. + internal static let footer = L10n.tr("Localizable", "Scene.Following.Footer") + } internal enum HomeTimeline { /// Home internal static let title = L10n.tr("Localizable", "Scene.HomeTimeline.Title") diff --git a/Mastodon/Resources/ar.lproj/Localizable.strings b/Mastodon/Resources/ar.lproj/Localizable.strings index 5950546a..8681e584 100644 --- a/Mastodon/Resources/ar.lproj/Localizable.strings +++ b/Mastodon/Resources/ar.lproj/Localizable.strings @@ -28,6 +28,7 @@ Please check your internet connection."; "Common.Controls.Actions.Back" = "العودة"; "Common.Controls.Actions.BlockDomain" = "حظر %@"; "Common.Controls.Actions.Cancel" = "إلغاء"; +"Common.Controls.Actions.Compose" = "Compose"; "Common.Controls.Actions.Confirm" = "تأكيد"; "Common.Controls.Actions.Continue" = "واصل"; "Common.Controls.Actions.CopyPhoto" = "نسخ الصورة"; @@ -190,6 +191,8 @@ uploaded to Mastodon."; اضغط على الرابط لتأكيد حسابك."; "Scene.ConfirmEmail.Title" = "شيء واحد أخير."; "Scene.Favorite.Title" = "مفضلتك"; +"Scene.Follower.Footer" = "Followers from other servers are not displayed."; +"Scene.Following.Footer" = "Follows from other servers are not displayed."; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "إظهار منشورات جديدة"; "Scene.HomeTimeline.NavigationBarState.Offline" = "غير متصل"; "Scene.HomeTimeline.NavigationBarState.Published" = "تم نشره!"; diff --git a/Mastodon/Resources/ca.lproj/Localizable.strings b/Mastodon/Resources/ca.lproj/Localizable.strings index 71eb62b0..1642fc8a 100644 --- a/Mastodon/Resources/ca.lproj/Localizable.strings +++ b/Mastodon/Resources/ca.lproj/Localizable.strings @@ -28,6 +28,7 @@ Comprova la teva connexió a Internet."; "Common.Controls.Actions.Back" = "Enrere"; "Common.Controls.Actions.BlockDomain" = "Bloqueja %@"; "Common.Controls.Actions.Cancel" = "Cancel·la"; +"Common.Controls.Actions.Compose" = "Composa"; "Common.Controls.Actions.Confirm" = "Confirma"; "Common.Controls.Actions.Continue" = "Continua"; "Common.Controls.Actions.CopyPhoto" = "Copia la foto"; @@ -190,6 +191,8 @@ carregat a Mastodon."; toca l'enllaç per a confirmar el teu compte."; "Scene.ConfirmEmail.Title" = "Una última cosa."; "Scene.Favorite.Title" = "Els teus Favorits"; +"Scene.Follower.Footer" = "Els seguidors d'altres servidors no son mostrats."; +"Scene.Following.Footer" = "Els seguits d'altres servidors no son mostrats."; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Veure noves publicacions"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Fora de línia"; "Scene.HomeTimeline.NavigationBarState.Published" = "Publicat!"; diff --git a/Mastodon/Resources/de.lproj/Localizable.strings b/Mastodon/Resources/de.lproj/Localizable.strings index 2780723e..12fba538 100644 --- a/Mastodon/Resources/de.lproj/Localizable.strings +++ b/Mastodon/Resources/de.lproj/Localizable.strings @@ -28,6 +28,7 @@ Bitte überprüfe deine Internetverbindung."; "Common.Controls.Actions.Back" = "Zurück"; "Common.Controls.Actions.BlockDomain" = "%@ blockieren"; "Common.Controls.Actions.Cancel" = "Abbrechen"; +"Common.Controls.Actions.Compose" = "Compose"; "Common.Controls.Actions.Confirm" = "Bestätigen"; "Common.Controls.Actions.Continue" = "Fortfahren"; "Common.Controls.Actions.CopyPhoto" = "Foto kopieren"; @@ -190,6 +191,8 @@ kann nicht auf Mastodon hochgeladen werden."; tippe darin auf den Link, um Dein Konto zu bestätigen."; "Scene.ConfirmEmail.Title" = "Noch eine letzte Sache."; "Scene.Favorite.Title" = "Deine Favoriten"; +"Scene.Follower.Footer" = "Followers from other servers are not displayed."; +"Scene.Following.Footer" = "Follows from other servers are not displayed."; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Neue Beiträge anzeigen"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Offline"; "Scene.HomeTimeline.NavigationBarState.Published" = "Veröffentlicht!"; diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index a8852675..0f3ed66a 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -28,6 +28,7 @@ Please check your internet connection."; "Common.Controls.Actions.Back" = "Back"; "Common.Controls.Actions.BlockDomain" = "Block %@"; "Common.Controls.Actions.Cancel" = "Cancel"; +"Common.Controls.Actions.Compose" = "Compose"; "Common.Controls.Actions.Confirm" = "Confirm"; "Common.Controls.Actions.Continue" = "Continue"; "Common.Controls.Actions.CopyPhoto" = "Copy Photo"; @@ -190,6 +191,8 @@ uploaded to Mastodon."; tap the link to confirm your account."; "Scene.ConfirmEmail.Title" = "One last thing."; "Scene.Favorite.Title" = "Your Favorites"; +"Scene.Follower.Footer" = "Followers from other servers are not displayed."; +"Scene.Following.Footer" = "Follows from other servers are not displayed."; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "See new posts"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Offline"; "Scene.HomeTimeline.NavigationBarState.Published" = "Published!"; diff --git a/Mastodon/Resources/es-419.lproj/Localizable.strings b/Mastodon/Resources/es-419.lproj/Localizable.strings index 209502b2..3ffd1204 100644 --- a/Mastodon/Resources/es-419.lproj/Localizable.strings +++ b/Mastodon/Resources/es-419.lproj/Localizable.strings @@ -28,6 +28,7 @@ Por favor, revisá tu conexión a Internet."; "Common.Controls.Actions.Back" = "Volver"; "Common.Controls.Actions.BlockDomain" = "Bloquear a %@"; "Common.Controls.Actions.Cancel" = "Cancelar"; +"Common.Controls.Actions.Compose" = "Compose"; "Common.Controls.Actions.Confirm" = "Confirmar"; "Common.Controls.Actions.Continue" = "Continuar"; "Common.Controls.Actions.CopyPhoto" = "Copiar foto"; @@ -190,6 +191,8 @@ y no se puede subir a Mastodon."; pulsá en el enlace para confirmar tu cuenta."; "Scene.ConfirmEmail.Title" = "Una última cosa."; "Scene.Favorite.Title" = "Tus favoritos"; +"Scene.Follower.Footer" = "Followers from other servers are not displayed."; +"Scene.Following.Footer" = "Follows from other servers are not displayed."; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Ver nuevos mensajes"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Desconectado"; "Scene.HomeTimeline.NavigationBarState.Published" = "¡Enviado!"; diff --git a/Mastodon/Resources/es.lproj/Localizable.strings b/Mastodon/Resources/es.lproj/Localizable.strings index 1fffc928..5e7f9afb 100644 --- a/Mastodon/Resources/es.lproj/Localizable.strings +++ b/Mastodon/Resources/es.lproj/Localizable.strings @@ -28,6 +28,7 @@ Por favor, revise su conexión a internet."; "Common.Controls.Actions.Back" = "Atrás"; "Common.Controls.Actions.BlockDomain" = "Bloquear %@"; "Common.Controls.Actions.Cancel" = "Cancelar"; +"Common.Controls.Actions.Compose" = "Compose"; "Common.Controls.Actions.Confirm" = "Confirmar"; "Common.Controls.Actions.Continue" = "Continuar"; "Common.Controls.Actions.CopyPhoto" = "Copiar foto"; @@ -190,6 +191,8 @@ subirse a Mastodon."; pulsa en el enlace para confirmar tu cuenta."; "Scene.ConfirmEmail.Title" = "Una última cosa."; "Scene.Favorite.Title" = "Tus Favoritos"; +"Scene.Follower.Footer" = "Followers from other servers are not displayed."; +"Scene.Following.Footer" = "Follows from other servers are not displayed."; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Ver nuevas publicaciones"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Sin Conexión"; "Scene.HomeTimeline.NavigationBarState.Published" = "¡Publicado!"; diff --git a/Mastodon/Resources/fr.lproj/Localizable.strings b/Mastodon/Resources/fr.lproj/Localizable.strings index 76df6b60..9514a7a5 100644 --- a/Mastodon/Resources/fr.lproj/Localizable.strings +++ b/Mastodon/Resources/fr.lproj/Localizable.strings @@ -28,6 +28,7 @@ Veuillez vérifier votre accès à Internet."; "Common.Controls.Actions.Back" = "Retour"; "Common.Controls.Actions.BlockDomain" = "Bloquer %@"; "Common.Controls.Actions.Cancel" = "Annuler"; +"Common.Controls.Actions.Compose" = "Compose"; "Common.Controls.Actions.Confirm" = "Confirmer"; "Common.Controls.Actions.Continue" = "Continuer"; "Common.Controls.Actions.CopyPhoto" = "Copier la photo"; @@ -190,6 +191,8 @@ téléversé sur Mastodon."; tapotez le lien pour confirmer votre compte."; "Scene.ConfirmEmail.Title" = "Une dernière chose."; "Scene.Favorite.Title" = "Vos favoris"; +"Scene.Follower.Footer" = "Followers from other servers are not displayed."; +"Scene.Following.Footer" = "Follows from other servers are not displayed."; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Voir les nouvelles publications"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Hors ligne"; "Scene.HomeTimeline.NavigationBarState.Published" = "Publié!"; diff --git a/Mastodon/Resources/gd-GB.lproj/Localizable.strings b/Mastodon/Resources/gd-GB.lproj/Localizable.strings index 6c01adb0..3f80f641 100644 --- a/Mastodon/Resources/gd-GB.lproj/Localizable.strings +++ b/Mastodon/Resources/gd-GB.lproj/Localizable.strings @@ -28,6 +28,7 @@ Thoir sùil air a’ cheangal agad ris an eadar-lìon."; "Common.Controls.Actions.Back" = "Air ais"; "Common.Controls.Actions.BlockDomain" = "Bac %@"; "Common.Controls.Actions.Cancel" = "Sguir dheth"; +"Common.Controls.Actions.Compose" = "Sgrìobh"; "Common.Controls.Actions.Confirm" = "Dearbh"; "Common.Controls.Actions.Continue" = "Lean air adhart"; "Common.Controls.Actions.CopyPhoto" = "Dèan lethbhreac dhen dealbh"; @@ -190,6 +191,8 @@ a luchdadh suas gu Mastodon."; thoir gnogag air a’ chunntas a dhearbhadh a’ chunntais agad."; "Scene.ConfirmEmail.Title" = "Aon rud eile."; "Scene.Favorite.Title" = "Na h-annsachdan agad"; +"Scene.Follower.Footer" = "Cha dèid luchd-leantainn o fhrithealaichean eile a shealltainn."; +"Scene.Following.Footer" = "Cha dèid cò air a leanas tu air frithealaichean eile a shealltainn."; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Seall na postaichean ùra"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Far loidhne"; "Scene.HomeTimeline.NavigationBarState.Published" = "Chaidh fhoillseachadh!"; diff --git a/Mastodon/Resources/ja.lproj/Localizable.strings b/Mastodon/Resources/ja.lproj/Localizable.strings index beadccf2..98bf7163 100644 --- a/Mastodon/Resources/ja.lproj/Localizable.strings +++ b/Mastodon/Resources/ja.lproj/Localizable.strings @@ -28,6 +28,7 @@ "Common.Controls.Actions.Back" = "戻る"; "Common.Controls.Actions.BlockDomain" = "%@をブロック"; "Common.Controls.Actions.Cancel" = "キャンセル"; +"Common.Controls.Actions.Compose" = "Compose"; "Common.Controls.Actions.Confirm" = "確認"; "Common.Controls.Actions.Continue" = "続ける"; "Common.Controls.Actions.CopyPhoto" = "写真をコピー"; @@ -184,6 +185,8 @@ "Scene.ConfirmEmail.Subtitle" = "先程 %@ にメールを送信しました。リンクをタップしてアカウントを確認してください。"; "Scene.ConfirmEmail.Title" = "さいごにもうひとつ。"; "Scene.Favorite.Title" = "お気に入り"; +"Scene.Follower.Footer" = "Followers from other servers are not displayed."; +"Scene.Following.Footer" = "Follows from other servers are not displayed."; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "新しい投稿を見る"; "Scene.HomeTimeline.NavigationBarState.Offline" = "オフライン"; "Scene.HomeTimeline.NavigationBarState.Published" = "投稿しました!"; diff --git a/Mastodon/Resources/ku-TR.lproj/Localizable.strings b/Mastodon/Resources/ku-TR.lproj/Localizable.strings index 345f10cf..7f84febe 100644 --- a/Mastodon/Resources/ku-TR.lproj/Localizable.strings +++ b/Mastodon/Resources/ku-TR.lproj/Localizable.strings @@ -28,6 +28,7 @@ Jkx girêdana înternetê xwe kontrol bike."; "Common.Controls.Actions.Back" = "Vegere"; "Common.Controls.Actions.BlockDomain" = "%@ asteng bike"; "Common.Controls.Actions.Cancel" = "Dev jê berde"; +"Common.Controls.Actions.Compose" = "Binivîsîne"; "Common.Controls.Actions.Confirm" = "Bipejirîne"; "Common.Controls.Actions.Continue" = "Bidomîne"; "Common.Controls.Actions.CopyPhoto" = "Wêne kopî bikin"; @@ -90,7 +91,7 @@ Jkx girêdana înternetê xwe kontrol bike."; "Common.Controls.Keyboard.Timeline.ReplyStatus" = "Bersivê bide şandiyê"; "Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "Hişyariya naverokê veke/bigire"; "Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Di postê da Bijartin veke/bigire"; -"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Toggle Reblog on Post"; +"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Ji vû nivîsandin di şandiyê de biguherîne"; "Common.Controls.Status.Actions.Favorite" = "Bijartî"; "Common.Controls.Status.Actions.Menu" = "Menû"; "Common.Controls.Status.Actions.Reblog" = "Ji nû ve blog"; @@ -134,25 +135,25 @@ Profîla te ji wan ra wiha xuya dike."; "Common.Controls.Timeline.Loader.ShowMoreReplies" = "Bêtir bersivan nîşan bide"; "Common.Controls.Timeline.Timestamp.Now" = "Niha"; "Scene.AccountList.AddAccount" = "Ajimêr tevlî bike"; -"Scene.AccountList.DismissAccountSwitcher" = "Dismiss Account Switcher"; +"Scene.AccountList.DismissAccountSwitcher" = "Guherkera ajimêrê paş guh bike"; "Scene.AccountList.TabBarHint" = "Profîla hilbijartî ya niha: %@. Du caran bitikîne û paşê dest bide ser da ku guhêrbara ajimêr were nîşandan"; "Scene.Compose.Accessibility.AppendAttachment" = "Pêvek tevlî bike"; "Scene.Compose.Accessibility.AppendPoll" = "Rapirsî tevlî bike"; -"Scene.Compose.Accessibility.CustomEmojiPicker" = "Custom Emoji Picker"; +"Scene.Compose.Accessibility.CustomEmojiPicker" = "Hilbijêrê emojî yên kesanekirî"; "Scene.Compose.Accessibility.DisableContentWarning" = "Hişyariya naverokê neçalak bike"; -"Scene.Compose.Accessibility.EnableContentWarning" = "Enable Content Warning"; +"Scene.Compose.Accessibility.EnableContentWarning" = "Hişyariya naverokê neçalak bike"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "Menuya Xuyabûna Şandiyê"; "Scene.Compose.Accessibility.RemovePoll" = "Rapirsî rake"; "Scene.Compose.Attachment.AttachmentBroken" = "Ev %@ naxebite û nayê barkirin li ser Mastodon."; -"Scene.Compose.Attachment.DescriptionPhoto" = "Describe the photo for the visually-impaired..."; -"Scene.Compose.Attachment.DescriptionVideo" = "Describe the video for the visually-impaired..."; +"Scene.Compose.Attachment.DescriptionPhoto" = "Wêneyê ji bo kêmbînên dîtbar bide nasîn..."; +"Scene.Compose.Attachment.DescriptionVideo" = "Vîdyoyê ji bo kêmbînên dîtbar bide nasîn..."; "Scene.Compose.Attachment.Photo" = "wêne"; "Scene.Compose.Attachment.Video" = "vîdyo"; -"Scene.Compose.AutoComplete.SpaceToAdd" = "Space to add"; +"Scene.Compose.AutoComplete.SpaceToAdd" = "Bicîhkirinê tevlî bike"; "Scene.Compose.ComposeAction" = "Biweşîne"; -"Scene.Compose.ContentInputPlaceholder" = "Type or paste what’s on your mind"; -"Scene.Compose.ContentWarning.Placeholder" = "Write an accurate warning here..."; +"Scene.Compose.ContentInputPlaceholder" = "Tiştê ku di hişê te de ye binivîsin an jî pêve bike"; +"Scene.Compose.ContentWarning.Placeholder" = "Li vir hişyariyek hûrgilî binivîsine..."; "Scene.Compose.Keyboard.AppendAttachmentEntry" = "Pêvek lê zêde bike - %@"; "Scene.Compose.Keyboard.DiscardPost" = "Şandî bihelîne"; "Scene.Compose.Keyboard.PublishPost" = "Şandiye bide weşan"; @@ -182,14 +183,16 @@ Profîla te ji wan ra wiha xuya dike."; "Scene.ConfirmEmail.DontReceiveEmail.Description" = "Kontrol bike ka navnîşana e-nameya te rast e û her wiha peldanka xwe ya spam."; "Scene.ConfirmEmail.DontReceiveEmail.ResendEmail" = "E-namyê yê dîsa bişîne"; "Scene.ConfirmEmail.DontReceiveEmail.Title" = "E-nameyê xwe kontrol bike"; -"Scene.ConfirmEmail.OpenEmailApp.Description" = "We just sent you an email. Check your junk folder if you haven’t."; +"Scene.ConfirmEmail.OpenEmailApp.Description" = "Me tenê ji te re e-nameyek şand. Heke nehatiye peldanka xwe ya spamê kontrol bike."; "Scene.ConfirmEmail.OpenEmailApp.Mail" = "E-name"; "Scene.ConfirmEmail.OpenEmailApp.OpenEmailClient" = "Rajegirê e-nameyê veke"; -"Scene.ConfirmEmail.OpenEmailApp.Title" = "Check your inbox."; -"Scene.ConfirmEmail.Subtitle" = "We just sent an email to %@, -tap the link to confirm your account."; +"Scene.ConfirmEmail.OpenEmailApp.Title" = "Nameyên xwe yên wergirtî kontrol bike."; +"Scene.ConfirmEmail.Subtitle" = "Me tenê e-nameyek ji %@ re şand, +girêdanê bitikne da ku ajimêra xwe bidî piştrastkirin."; "Scene.ConfirmEmail.Title" = "Tiştekî dawî."; "Scene.Favorite.Title" = "Bijareyên te"; +"Scene.Follower.Footer" = "Şopîner ji rajekerên din nayê dîtin."; +"Scene.Following.Footer" = "Şopandin ji rajekerên din nayê dîtin."; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Şandiyên nû bibîne"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Derhêl"; "Scene.HomeTimeline.NavigationBarState.Published" = "Hate weşandin!"; @@ -250,13 +253,13 @@ tap the link to confirm your account."; "Scene.Register.Input.Username.DuplicatePrompt" = "Navê vê bikarhêner tê girtin."; "Scene.Register.Input.Username.Placeholder" = "navê bikarhêner"; "Scene.Register.Title" = "Ji me re hinekî qala xwe bike."; -"Scene.Report.Content1" = "Are there any other posts you’d like to add to the report?"; -"Scene.Report.Content2" = "Is there anything the moderators should know about this report?"; +"Scene.Report.Content1" = "Şandiyên din hene ku tu dixwazî tevlî ragihandinê bikî?"; +"Scene.Report.Content2" = "Derbarê vê ragihandinê de tiştek heye ku divê çavdêr bizanin?"; "Scene.Report.Send" = "Ragihandinê bişîne"; "Scene.Report.SkipToSend" = "Bêyî şirove bişîne"; "Scene.Report.Step1" = "Gav 1 ji 2"; "Scene.Report.Step2" = "Gav 2 ji 2"; -"Scene.Report.TextPlaceholder" = "Type or paste additional comments"; +"Scene.Report.TextPlaceholder" = "Şiroveyên daxwazkirê binivîsine an jî pê ve bike"; "Scene.Report.Title" = "%@ ragihîne"; "Scene.Search.Recommend.Accounts.Description" = "Dibe ku tu bixwazî van hesaban bişopînî"; "Scene.Search.Recommend.Accounts.Follow" = "Bişopîne"; @@ -306,17 +309,17 @@ Her kîjan rajekar be."; "Scene.ServerRules.Subtitle" = "Ev rêzik ji aliyê rêvebirên %@ ve tên sazkirin."; "Scene.ServerRules.TermsOfService" = "şert û mercên xizmetê"; "Scene.ServerRules.Title" = "Hin qaîdeyên bingehîn."; -"Scene.Settings.Footer.MastodonDescription" = "Mastodon is open source software. You can report issues on GitHub at %@ (%@)"; -"Scene.Settings.Keyboard.CloseSettingsWindow" = "Close Settings Window"; +"Scene.Settings.Footer.MastodonDescription" = "Mastodon nermalava çavkaniya vekirî ye. Tu dikarî pirsgirêkan li ser GitHub-ê ragihînî di %@ (%@) de"; +"Scene.Settings.Keyboard.CloseSettingsWindow" = "Sazkariyên çarçoveyê bigire"; "Scene.Settings.Section.Appearance.Automatic" = "Xweber"; "Scene.Settings.Section.Appearance.Dark" = "Her dem tarî"; "Scene.Settings.Section.Appearance.Light" = "Her dem ronî"; "Scene.Settings.Section.Appearance.Title" = "Xuyang"; -"Scene.Settings.Section.BoringZone.AccountSettings" = "Account Settings"; -"Scene.Settings.Section.BoringZone.Privacy" = "Privacy Policy"; -"Scene.Settings.Section.BoringZone.Terms" = "Terms of Service"; -"Scene.Settings.Section.BoringZone.Title" = "The Boring Zone"; -"Scene.Settings.Section.Notifications.Boosts" = "Reblogs my post"; +"Scene.Settings.Section.BoringZone.AccountSettings" = "Sazkariyên ajimêr"; +"Scene.Settings.Section.BoringZone.Privacy" = "Polîtikaya nihêniyê"; +"Scene.Settings.Section.BoringZone.Terms" = "Mercên bikaranînê"; +"Scene.Settings.Section.BoringZone.Title" = "Devera acizker"; +"Scene.Settings.Section.Notifications.Boosts" = "Şandiya min ji nû ve binivîsine"; "Scene.Settings.Section.Notifications.Favorites" = "Şandiyên min hez kir"; "Scene.Settings.Section.Notifications.Follows" = "Min şopand"; "Scene.Settings.Section.Notifications.Mentions" = "Qale min kir"; @@ -326,21 +329,21 @@ Her kîjan rajekar be."; "Scene.Settings.Section.Notifications.Trigger.Follower" = "şopînerek"; "Scene.Settings.Section.Notifications.Trigger.Noone" = "ne yek"; "Scene.Settings.Section.Notifications.Trigger.Title" = "Min agahdar bike gava"; -"Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Disable animated avatars"; -"Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Disable animated emojis"; +"Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Avatarên anîmasyonî neçalak bike"; +"Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Emojiyên anîmasyonî neçalak bike"; "Scene.Settings.Section.Preference.Title" = "Hilbijarte"; -"Scene.Settings.Section.Preference.TrueBlackDarkMode" = "True black dark mode"; -"Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Use default browser to open links"; -"Scene.Settings.Section.SpicyZone.Clear" = "Clear Media Cache"; -"Scene.Settings.Section.SpicyZone.Signout" = "Sign Out"; -"Scene.Settings.Section.SpicyZone.Title" = "The Spicy Zone"; +"Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Moda tarî ya reş a rastîn"; +"Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Ji bo vekirina girêdanan geroka berdest bi kar bîne"; +"Scene.Settings.Section.SpicyZone.Clear" = "Pêşbîra medyayê pak bike"; +"Scene.Settings.Section.SpicyZone.Signout" = "Derkeve"; +"Scene.Settings.Section.SpicyZone.Title" = "Devera germ"; "Scene.Settings.Title" = "Sazkarî"; "Scene.SuggestionAccount.FollowExplain" = "Gava tu kesekî dişopînî, tu yê şandiyê wan di serrûpelê de bibîne."; "Scene.SuggestionAccount.Title" = "Kesên bo ku bişopînî bibîne"; "Scene.Thread.BackTitle" = "Şandî"; -"Scene.Thread.Title" = "Post from %@"; +"Scene.Thread.Title" = "Şandî ji %@"; "Scene.Welcome.Slogan" = "Torên civakî di destên te de."; -"Scene.Wizard.AccessibilityHint" = "Double tap to dismiss this wizard"; +"Scene.Wizard.AccessibilityHint" = "Du caran bitikîne da ku çarçoveyahilpekok ji holê rakî"; "Scene.Wizard.MultipleAccountSwitchIntroDescription" = "Dest bide ser bişkoja profîlê da ku di navbera gelek ajimêrann de biguherînî."; "Scene.Wizard.NewInMastodon" = "Nû di Mastodon de"; \ No newline at end of file diff --git a/Mastodon/Resources/ku-TR.lproj/Localizable.stringsdict b/Mastodon/Resources/ku-TR.lproj/Localizable.stringsdict index 064b8bf2..8ae1b812 100644 --- a/Mastodon/Resources/ku-TR.lproj/Localizable.stringsdict +++ b/Mastodon/Resources/ku-TR.lproj/Localizable.stringsdict @@ -109,9 +109,9 @@ NSStringFormatValueTypeKey ld one - 1 reblog + 1 ji nû ve nivîsandin other - %ld reblogs + %ld ji nû ve nivîsandin plural.count.vote diff --git a/Mastodon/Resources/nl.lproj/Localizable.strings b/Mastodon/Resources/nl.lproj/Localizable.strings index 1ebda117..9c84e138 100644 --- a/Mastodon/Resources/nl.lproj/Localizable.strings +++ b/Mastodon/Resources/nl.lproj/Localizable.strings @@ -27,6 +27,7 @@ "Common.Controls.Actions.Back" = "Terug"; "Common.Controls.Actions.BlockDomain" = "Blokkeer %@"; "Common.Controls.Actions.Cancel" = "Annuleren"; +"Common.Controls.Actions.Compose" = "Compose"; "Common.Controls.Actions.Confirm" = "Bevestigen"; "Common.Controls.Actions.Continue" = "Doorgaan"; "Common.Controls.Actions.CopyPhoto" = "Foto kopiëren"; @@ -184,6 +185,8 @@ Uw profiel ziet er zo uit voor hen."; klik op de link om uw account te bevestigen."; "Scene.ConfirmEmail.Title" = "Nog één ding."; "Scene.Favorite.Title" = "Uw favorieten"; +"Scene.Follower.Footer" = "Followers from other servers are not displayed."; +"Scene.Following.Footer" = "Follows from other servers are not displayed."; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Bekijk nieuwe berichten"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Offline"; "Scene.HomeTimeline.NavigationBarState.Published" = "Gepubliceerd!"; diff --git a/Mastodon/Resources/ru.lproj/Localizable.strings b/Mastodon/Resources/ru.lproj/Localizable.strings index 3a8adbba..1a4f92fc 100644 --- a/Mastodon/Resources/ru.lproj/Localizable.strings +++ b/Mastodon/Resources/ru.lproj/Localizable.strings @@ -28,6 +28,7 @@ "Common.Controls.Actions.Back" = "Назад"; "Common.Controls.Actions.BlockDomain" = "Заблокировать %@"; "Common.Controls.Actions.Cancel" = "Отмена"; +"Common.Controls.Actions.Compose" = "Compose"; "Common.Controls.Actions.Confirm" = "Подтвердить"; "Common.Controls.Actions.Continue" = "Продолжить"; "Common.Controls.Actions.CopyPhoto" = "Скопировать изображение"; @@ -200,6 +201,8 @@ подтвердить свою учётную запись."; "Scene.ConfirmEmail.Title" = "И ещё кое-что."; "Scene.Favorite.Title" = "Ваше избранное"; +"Scene.Follower.Footer" = "Followers from other servers are not displayed."; +"Scene.Following.Footer" = "Follows from other servers are not displayed."; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Показать новые"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Не в сети"; "Scene.HomeTimeline.NavigationBarState.Published" = "Опубликовано!"; diff --git a/Mastodon/Resources/th.lproj/Localizable.strings b/Mastodon/Resources/th.lproj/Localizable.strings index a61b1d15..d05bb583 100644 --- a/Mastodon/Resources/th.lproj/Localizable.strings +++ b/Mastodon/Resources/th.lproj/Localizable.strings @@ -28,6 +28,7 @@ "Common.Controls.Actions.Back" = "ย้อนกลับ"; "Common.Controls.Actions.BlockDomain" = "ปิดกั้น %@"; "Common.Controls.Actions.Cancel" = "ยกเลิก"; +"Common.Controls.Actions.Compose" = "Compose"; "Common.Controls.Actions.Confirm" = "ยืนยัน"; "Common.Controls.Actions.Continue" = "ดำเนินการต่อ"; "Common.Controls.Actions.CopyPhoto" = "คัดลอกรูปภาพ"; @@ -190,6 +191,8 @@ แตะที่ลิงก์เพื่อยืนยันบัญชีของคุณ"; "Scene.ConfirmEmail.Title" = "หนึ่งสิ่งสุดท้าย"; "Scene.Favorite.Title" = "รายการโปรดของคุณ"; +"Scene.Follower.Footer" = "Followers from other servers are not displayed."; +"Scene.Following.Footer" = "Follows from other servers are not displayed."; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "ดูโพสต์ใหม่"; "Scene.HomeTimeline.NavigationBarState.Offline" = "ออฟไลน์"; "Scene.HomeTimeline.NavigationBarState.Published" = "เผยแพร่แล้ว!"; diff --git a/Mastodon/Resources/zh-Hans.lproj/Localizable.strings b/Mastodon/Resources/zh-Hans.lproj/Localizable.strings index 21c41a15..7a6b0203 100644 --- a/Mastodon/Resources/zh-Hans.lproj/Localizable.strings +++ b/Mastodon/Resources/zh-Hans.lproj/Localizable.strings @@ -28,6 +28,7 @@ "Common.Controls.Actions.Back" = "返回"; "Common.Controls.Actions.BlockDomain" = "屏蔽 %@"; "Common.Controls.Actions.Cancel" = "取消"; +"Common.Controls.Actions.Compose" = "撰写"; "Common.Controls.Actions.Confirm" = "确认"; "Common.Controls.Actions.Continue" = "继续"; "Common.Controls.Actions.CopyPhoto" = "拷贝照片"; @@ -190,6 +191,8 @@ 点击链接确认你的帐户。"; "Scene.ConfirmEmail.Title" = "最后一件事。"; "Scene.Favorite.Title" = "你的喜欢"; +"Scene.Follower.Footer" = "不会显示来自其它服务器的关注者"; +"Scene.Following.Footer" = "不会显示来自其它服务器的关注"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "查看新帖子"; "Scene.HomeTimeline.NavigationBarState.Offline" = "离线"; "Scene.HomeTimeline.NavigationBarState.Published" = "已发送"; From 206beb6d19d46fc71f4b62ee8b79eedb37dd655d Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 2 Nov 2021 16:00:42 +0800 Subject: [PATCH 86/96] chore: update following/follower list footer display logic --- .../Follower/FollowerListViewModel+Diffable.swift | 6 +++++- .../Follower/FollowerListViewModel+State.swift | 2 +- .../Following/FollowingListViewModel+Diffable.swift | 7 ++++++- .../Following/FollowingListViewModel+State.swift | 12 ------------ 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift index c048e172..fc9f3177 100644 --- a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift @@ -48,7 +48,11 @@ extension FollowerListViewModel { case is State.Idle, is State.Loading, is State.Fail: snapshot.appendItems([.bottomLoader], toSection: .main) case is State.NoMore: - let text = "Followers from other servers are not displayed." + guard let activeMastodonAuthenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value, + let userID = self.userID.value, + userID != activeMastodonAuthenticationBox.userID + else { break } + let text = L10n.Scene.Follower.footer snapshot.appendItems([.bottomHeader(text: text)], toSection: .main) default: break diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift index 87693318..30621f6a 100644 --- a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift @@ -155,7 +155,7 @@ extension FollowerListViewModel.State { let maxID = response.link?.maxID - if hasNewAppend, maxID != nil { + if maxID != nil { stateMachine.enter(Idle.self) } else { stateMachine.enter(NoMore.self) diff --git a/Mastodon/Scene/Profile/Following/FollowingListViewModel+Diffable.swift b/Mastodon/Scene/Profile/Following/FollowingListViewModel+Diffable.swift index 58af276d..dc6f1f6f 100644 --- a/Mastodon/Scene/Profile/Following/FollowingListViewModel+Diffable.swift +++ b/Mastodon/Scene/Profile/Following/FollowingListViewModel+Diffable.swift @@ -48,7 +48,12 @@ extension FollowingListViewModel { case is State.Idle, is State.Loading, is State.Fail: snapshot.appendItems([.bottomLoader], toSection: .main) case is State.NoMore: - break + guard let activeMastodonAuthenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value, + let userID = self.userID.value, + userID != activeMastodonAuthenticationBox.userID + else { break } + let text = L10n.Scene.Following.footer + snapshot.appendItems([.bottomHeader(text: text)], toSection: .main) default: break } diff --git a/Mastodon/Scene/Profile/Following/FollowingListViewModel+State.swift b/Mastodon/Scene/Profile/Following/FollowingListViewModel+State.swift index d5c30b43..e6fadf7f 100644 --- a/Mastodon/Scene/Profile/Following/FollowingListViewModel+State.swift +++ b/Mastodon/Scene/Profile/Following/FollowingListViewModel+State.swift @@ -179,18 +179,6 @@ extension FollowingListViewModel.State { override func didEnter(from previousState: GKState?) { super.didEnter(from: previousState) - guard let viewModel = viewModel, let _ = stateMachine else { return } - guard let diffableDataSource = viewModel.diffableDataSource else { - assertionFailure() - return - } - DispatchQueue.main.async { - var snapshot = diffableDataSource.snapshot() - snapshot.deleteItems([.bottomLoader]) - let header = UserItem.bottomHeader(text: "Followers from other servers are not displayed") - snapshot.appendItems([header], toSection: .main) - diffableDataSource.apply(snapshot, animatingDifferences: false) - } } } } From c9c0aaf148034d46c1218a85067461d90579f50b Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 2 Nov 2021 16:01:22 +0800 Subject: [PATCH 87/96] fix: follower list pagination not works issue --- Mastodon/Service/APIService/APIService+Follower.swift | 5 +++++ .../MastodonSDK/API/Mastodon+API+Account+Followers.swift | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Mastodon/Service/APIService/APIService+Follower.swift b/Mastodon/Service/APIService/APIService+Follower.swift index db29a0a2..f75d2420 100644 --- a/Mastodon/Service/APIService/APIService+Follower.swift +++ b/Mastodon/Service/APIService/APIService+Follower.swift @@ -23,10 +23,15 @@ extension APIService { let authorization = authorizationBox.userAuthorization let requestMastodonUserID = authorizationBox.userID + let query = Mastodon.API.Account.FollowerQuery( + maxID: maxID, + limit: nil + ) return Mastodon.API.Account.followers( session: session, domain: domain, userID: userID, + query: query, authorization: authorization ) .flatMap { response -> AnyPublisher, Error> in diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+Followers.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+Followers.swift index b09a5f07..a900a1c1 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+Followers.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+Followers.swift @@ -35,11 +35,12 @@ extension Mastodon.API.Account { session: URLSession, domain: String, userID: Mastodon.Entity.Account.ID, + query: FollowerQuery, authorization: Mastodon.API.OAuth.Authorization ) -> AnyPublisher, Error> { let request = Mastodon.API.get( url: followersEndpointURL(domain: domain, userID: userID), - query: nil, + query: query, authorization: authorization ) return session.dataTaskPublisher(for: request) From 6d7de85f6272faab92a7725a5527d5bd780a7341 Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 2 Nov 2021 16:02:46 +0800 Subject: [PATCH 88/96] fix: account switcher popover too small issue --- Mastodon/Scene/Root/ContentSplitViewController.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mastodon/Scene/Root/ContentSplitViewController.swift b/Mastodon/Scene/Root/ContentSplitViewController.swift index 850b1429..8ca59787 100644 --- a/Mastodon/Scene/Root/ContentSplitViewController.swift +++ b/Mastodon/Scene/Root/ContentSplitViewController.swift @@ -101,7 +101,8 @@ extension ContentSplitViewController: SidebarViewControllerDelegate { let accountListViewController = coordinator.present(scene: .accountList, from: nil, transition: .popover(sourceView: sourceView)) as! AccountListViewController accountListViewController.dragIndicatorView.barView.isHidden = true - accountListViewController.preferredContentSize = CGSize(width: 300, height: 320) + // content width needs > 300 to make checkmark display + accountListViewController.preferredContentSize = CGSize(width: 375, height: 400) } } From 07eab320f40b7e643d0465ad45b4633a2c4fb594 Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 2 Nov 2021 16:03:34 +0800 Subject: [PATCH 89/96] fix: iPad layout update not take care of search tab selection logic issue --- .../Root/MainTab/MainTabBarController.swift | 4 ++ .../Scene/Root/RootSplitViewController.swift | 50 +++++++++++++------ 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift index d34c8553..e76db6b3 100644 --- a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift +++ b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift @@ -351,6 +351,10 @@ extension MainTabBarController { return viewController(of: NotificationViewController.self) } + var searchViewController: SearchViewController? { + return viewController(of: SearchViewController.self) + } + } // MARK: - UITabBarControllerDelegate diff --git a/Mastodon/Scene/Root/RootSplitViewController.swift b/Mastodon/Scene/Root/RootSplitViewController.swift index 7c03287f..13df6f88 100644 --- a/Mastodon/Scene/Root/RootSplitViewController.swift +++ b/Mastodon/Scene/Root/RootSplitViewController.swift @@ -19,6 +19,8 @@ final class RootSplitViewController: UISplitViewController, NeedsDependency { weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + private var isPrimaryDisplay = false + private(set) lazy var contentSplitViewController: ContentSplitViewController = { let contentSplitViewController = ContentSplitViewController() contentSplitViewController.context = context @@ -79,13 +81,6 @@ extension RootSplitViewController { super.viewDidLoad() updateBehavior(size: view.frame.size) - contentSplitViewController.$currentSupplementaryTab - .receive(on: DispatchQueue.main) - .sink { [weak self] _ in - guard let self = self else { return } - self.updateBehavior(size: self.view.frame.size) - } - .store(in: &disposeBag) setupBackground(theme: ThemeService.shared.currentTheme.value) ThemeService.shared.currentTheme @@ -97,6 +92,12 @@ extension RootSplitViewController { .store(in: &disposeBag) } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + updateBehavior(size: view.frame.size) + } + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) @@ -109,15 +110,23 @@ extension RootSplitViewController { } private func updateBehavior(size: CGSize) { - switch contentSplitViewController.currentSupplementaryTab { - case .search: + if size.width > 960 { + show(.primary) + isPrimaryDisplay = true + + } else { hide(.primary) + isPrimaryDisplay = false + } + + switch (contentSplitViewController.currentSupplementaryTab, isPrimaryDisplay) { + case (.search, true): + // needs switch to other tab when primary display + // use FIFO queue save tab history + contentSplitViewController.currentSupplementaryTab = .home default: - if size.width > 960 { - show(.primary) - } else { - hide(.primary) - } + // do nothing + break } } @@ -140,7 +149,11 @@ extension RootSplitViewController: ContentSplitViewControllerDelegate { return } switch tab { - case .search: + case .search: + guard isPrimaryDisplay else { + // only control search tab behavior when primary display + fallthrough + } guard let navigationController = searchViewController.navigationController else { return } if navigationController.viewControllers.count == 1 { searchViewController.searchBarTapPublisher.send() @@ -165,7 +178,7 @@ extension RootSplitViewController: ContentSplitViewControllerDelegate { // MARK: - UISplitViewControllerDelegate extension RootSplitViewController: UISplitViewControllerDelegate { - private static func transform(from: UITabBarController, to: UITabBarController) { + private static func transform(from: UITabBarController, to: UITabBarController) { let sourceNavigationControllers = from.viewControllers ?? [] let targetNavigationControllers = to.viewControllers ?? [] @@ -181,6 +194,11 @@ extension RootSplitViewController: UISplitViewControllerDelegate { to.selectedIndex = from.selectedIndex } + private static func transform(from: UINavigationController, to: UINavigationController) { + let viewControllers = from.popToRootViewController(animated: false) ?? [] + to.viewControllers.append(contentsOf: viewControllers) + } + // .regular to .compact func splitViewController( _ svc: UISplitViewController, From 30b2a35b84d685f2def2b1c8cfbb2181bd6918bf Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 2 Nov 2021 16:12:20 +0800 Subject: [PATCH 90/96] feat: implement following list --- Mastodon.xcodeproj/project.pbxproj | 4 + .../xcschemes/xcschememanagement.plist | 8 +- .../FollowerListViewModel+State.swift | 2 +- .../FollowingListViewModel+State.swift | 2 +- .../APIService/APIService+Following.swift | 70 ++++++++++++++++ .../API/Mastodon+API+Account+Following.swift | 82 +++++++++++++++++++ 6 files changed, 162 insertions(+), 6 deletions(-) create mode 100644 Mastodon/Service/APIService/APIService+Following.swift create mode 100644 MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+Following.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 8408f3ac..98cff7ec 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -320,6 +320,7 @@ DB66728C25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */; }; DB66729625F9F91600D60309 /* ComposeStatusSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729525F9F91600D60309 /* ComposeStatusSection.swift */; }; DB66729C25F9F91F00D60309 /* ComposeStatusItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */; }; + DB67D08427312970006A36CF /* APIService+Following.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB67D08327312970006A36CF /* APIService+Following.swift */; }; DB68045B2636DC6A00430867 /* MastodonNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68045A2636DC6A00430867 /* MastodonNotification.swift */; }; DB6804662636DC9000430867 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AB425EDD8A90076FA61 /* String.swift */; }; DB68046C2636DC9E00430867 /* MastodonNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68045A2636DC6A00430867 /* MastodonNotification.swift */; }; @@ -1139,6 +1140,7 @@ DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+DataSource.swift"; sourceTree = ""; }; DB66729525F9F91600D60309 /* ComposeStatusSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusSection.swift; sourceTree = ""; }; DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusItem.swift; sourceTree = ""; }; + DB67D08327312970006A36CF /* APIService+Following.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Following.swift"; sourceTree = ""; }; DB68045A2636DC6A00430867 /* MastodonNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonNotification.swift; sourceTree = ""; }; DB68047F2637CD4C00430867 /* AppShared.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppShared.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DB6804812637CD4C00430867 /* AppShared.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppShared.h; sourceTree = ""; }; @@ -2326,6 +2328,7 @@ 2D34D9DA261494120081BFC0 /* APIService+Search.swift */, 0F202212261351F5000C64BF /* APIService+HashtagTimeline.swift */, DB6B74F9272FC2B500C70B6E /* APIService+Follower.swift */, + DB67D08327312970006A36CF /* APIService+Following.swift */, DBCC3B9426157E6E0045B23D /* APIService+Relationship.swift */, 5B24BBE1262DB19100A9381B /* APIService+Report.swift */, DBAE3F932616E28B004B8251 /* APIService+Follow.swift */, @@ -4125,6 +4128,7 @@ 2D82B9FF25E7863200E36F0F /* OnboardingViewControllerAppearance.swift in Sources */, 5DF1054725F8870E00D6C0D4 /* VideoPlayerViewModel.swift in Sources */, DB73BF43271192BB00781945 /* InstanceService.swift in Sources */, + DB67D08427312970006A36CF /* APIService+Following.swift in Sources */, DBA9443A265CC0FC00C537E1 /* Fields.swift in Sources */, 2DE0FAC12615F04D00CDF649 /* RecommendHashTagSection.swift in Sources */, DBA5E7A5263BD28C004598BB /* ContextMenuImagePreviewViewModel.swift in Sources */, diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index 2d2dd193..5a541891 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ AppShared.xcscheme_^#shared#^_ orderHint - 36 + 37 CoreDataStack.xcscheme_^#shared#^_ orderHint - 35 + 36 Mastodon - ASDK.xcscheme_^#shared#^_ @@ -97,7 +97,7 @@ MastodonIntent.xcscheme_^#shared#^_ orderHint - 38 + 35 MastodonIntents.xcscheme_^#shared#^_ @@ -117,7 +117,7 @@ ShareActionExtension.xcscheme_^#shared#^_ orderHint - 37 + 38 SuppressBuildableAutocreation diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift index 30621f6a..43e53267 100644 --- a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift @@ -155,7 +155,7 @@ extension FollowerListViewModel.State { let maxID = response.link?.maxID - if maxID != nil { + if hasNewAppend && maxID != nil { stateMachine.enter(Idle.self) } else { stateMachine.enter(NoMore.self) diff --git a/Mastodon/Scene/Profile/Following/FollowingListViewModel+State.swift b/Mastodon/Scene/Profile/Following/FollowingListViewModel+State.swift index e6fadf7f..0ec3d626 100644 --- a/Mastodon/Scene/Profile/Following/FollowingListViewModel+State.swift +++ b/Mastodon/Scene/Profile/Following/FollowingListViewModel+State.swift @@ -128,7 +128,7 @@ extension FollowingListViewModel.State { return } - viewModel.context.apiService.followers( + viewModel.context.apiService.following( userID: userID, maxID: maxID, authorizationBox: activeMastodonAuthenticationBox diff --git a/Mastodon/Service/APIService/APIService+Following.swift b/Mastodon/Service/APIService/APIService+Following.swift new file mode 100644 index 00000000..8f477d6e --- /dev/null +++ b/Mastodon/Service/APIService/APIService+Following.swift @@ -0,0 +1,70 @@ +// +// APIService+Following.swift +// Mastodon +// +// Created by Cirno MainasuK on 2021-11-2. +// + +import UIKit +import Combine +import CoreData +import CoreDataStack +import CommonOSLog +import MastodonSDK + +extension APIService { + + func following( + userID: Mastodon.Entity.Account.ID, + maxID: String?, + authorizationBox: MastodonAuthenticationBox + ) -> AnyPublisher, Error> { + let domain = authorizationBox.domain + let authorization = authorizationBox.userAuthorization + let requestMastodonUserID = authorizationBox.userID + + let query = Mastodon.API.Account.FollowingQuery( + maxID: maxID, + limit: nil + ) + return Mastodon.API.Account.following( + session: session, + domain: domain, + userID: userID, + query: query, + authorization: authorization + ) + .flatMap { response -> AnyPublisher, Error> in + let managedObjectContext = self.backgroundManagedObjectContext + return managedObjectContext.performChanges { + let requestMastodonUserRequest = MastodonUser.sortedFetchRequest + requestMastodonUserRequest.predicate = MastodonUser.predicate(domain: domain, id: requestMastodonUserID) + requestMastodonUserRequest.fetchLimit = 1 + guard let requestMastodonUser = managedObjectContext.safeFetch(requestMastodonUserRequest).first else { return } + + for entity in response.value { + _ = APIService.CoreData.createOrMergeMastodonUser( + into: managedObjectContext, + for: requestMastodonUser, + in: domain, + entity: entity, + userCache: nil, + networkDate: response.networkDate, + log: .api + ) + } + } + .tryMap { result -> Mastodon.Response.Content<[Mastodon.Entity.Account]> in + switch result { + case .success: + return response + case .failure(let error): + throw error + } + } + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } + +} diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+Following.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+Following.swift new file mode 100644 index 00000000..c992c758 --- /dev/null +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+Following.swift @@ -0,0 +1,82 @@ +// +// Mastodon+API+Account+Following.swift +// +// +// Created by Cirno MainasuK on 2021-11-2. +// + +import Foundation +import Combine + +extension Mastodon.API.Account { + + static func followingEndpointURL(domain: String, userID: Mastodon.Entity.Account.ID) -> URL { + return Mastodon.API.endpointURL(domain: domain) + .appendingPathComponent("accounts") + .appendingPathComponent(userID) + .appendingPathComponent("following") + } + + /// Following + /// + /// Accounts which the given account is following, if network is not hidden by the account owner. + /// + /// - Since: 0.0.0 + /// - Version: 3.4.1 + /// # Reference + /// [Document](https://docs.joinmastodon.org/methods/accounts/) + /// - Parameters: + /// - session: `URLSession` + /// - domain: Mastodon instance domain. e.g. "example.com" + /// - userID: ID of the account in the database + /// - authorization: User token + /// - Returns: `AnyPublisher` contains `[Account]` nested in the response + public static func following( + session: URLSession, + domain: String, + userID: Mastodon.Entity.Account.ID, + query: FollowingQuery, + authorization: Mastodon.API.OAuth.Authorization + ) -> AnyPublisher, Error> { + let request = Mastodon.API.get( + url: followingEndpointURL(domain: domain, userID: userID), + query: query, + authorization: authorization + ) + return session.dataTaskPublisher(for: request) + .tryMap { data, response in + let value = try Mastodon.API.decode(type: [Mastodon.Entity.Account].self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + .eraseToAnyPublisher() + } + + public struct FollowingQuery: Codable, GetQuery { + + public let maxID: String? + public let limit: Int? // default 40 + + enum CodingKeys: String, CodingKey { + case maxID = "max_id" + case limit + } + + public init( + maxID: String?, + limit: Int? + ) { + self.maxID = maxID + self.limit = limit + } + + var queryItems: [URLQueryItem]? { + var items: [URLQueryItem] = [] + maxID.flatMap { items.append(URLQueryItem(name: "max_id", value: $0)) } + limit.flatMap { items.append(URLQueryItem(name: "limit", value: String($0))) } + guard !items.isEmpty else { return nil } + return items + } + + } + +} From 865718351d3230a447618a85ea855690c8fd5a17 Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 2 Nov 2021 19:15:46 +0800 Subject: [PATCH 91/96] feat: update wizard for new iPad design --- Mastodon.xcodeproj/project.pbxproj | 16 +- Mastodon/Coordinator/SceneCoordinator.swift | 24 +- .../Welcome/View/WizardCardView.swift | 7 + .../MainTab/MainTabBarController+Wizard.swift | 137 ------------ .../Root/MainTab/MainTabBarController.swift | 47 ++-- .../Scene/Root/RootSplitViewController.swift | 87 ++++++++ .../Root/Sidebar/SidebarViewController.swift | 1 + .../Scene/Root/Sidebar/SidebarViewModel.swift | 9 +- .../Scene/Wizard/WizardViewController.swift | 211 ++++++++++++++++++ 9 files changed, 371 insertions(+), 168 deletions(-) delete mode 100644 Mastodon/Scene/Root/MainTab/MainTabBarController+Wizard.swift create mode 100644 Mastodon/Scene/Wizard/WizardViewController.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 98cff7ec..619baa12 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -315,12 +315,12 @@ DB6180F826391D660018D199 /* MediaPreviewingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180F726391D660018D199 /* MediaPreviewingViewController.swift */; }; DB6180FA26391F2E0018D199 /* MediaPreviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180F926391F2E0018D199 /* MediaPreviewViewModel.swift */; }; DB63BE7F268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB63BE7E268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift */; }; - DB647C5726F1E97300F7F82C /* MainTabBarController+Wizard.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB647C5626F1E97300F7F82C /* MainTabBarController+Wizard.swift */; }; DB647C5926F1EA2700F7F82C /* WizardPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB647C5826F1EA2700F7F82C /* WizardPreference.swift */; }; DB66728C25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */; }; DB66729625F9F91600D60309 /* ComposeStatusSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729525F9F91600D60309 /* ComposeStatusSection.swift */; }; DB66729C25F9F91F00D60309 /* ComposeStatusItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */; }; DB67D08427312970006A36CF /* APIService+Following.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB67D08327312970006A36CF /* APIService+Following.swift */; }; + DB67D08627312E67006A36CF /* WizardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB67D08527312E67006A36CF /* WizardViewController.swift */; }; DB68045B2636DC6A00430867 /* MastodonNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68045A2636DC6A00430867 /* MastodonNotification.swift */; }; DB6804662636DC9000430867 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AB425EDD8A90076FA61 /* String.swift */; }; DB68046C2636DC9E00430867 /* MastodonNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68045A2636DC6A00430867 /* MastodonNotification.swift */; }; @@ -1135,12 +1135,12 @@ DB6180F726391D660018D199 /* MediaPreviewingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewingViewController.swift; sourceTree = ""; }; DB6180F926391F2E0018D199 /* MediaPreviewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewViewModel.swift; sourceTree = ""; }; DB63BE7E268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationViewController+StatusProvider.swift"; sourceTree = ""; }; - DB647C5626F1E97300F7F82C /* MainTabBarController+Wizard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainTabBarController+Wizard.swift"; sourceTree = ""; }; DB647C5826F1EA2700F7F82C /* WizardPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardPreference.swift; sourceTree = ""; }; DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+DataSource.swift"; sourceTree = ""; }; DB66729525F9F91600D60309 /* ComposeStatusSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusSection.swift; sourceTree = ""; }; DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusItem.swift; sourceTree = ""; }; DB67D08327312970006A36CF /* APIService+Following.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Following.swift"; sourceTree = ""; }; + DB67D08527312E67006A36CF /* WizardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardViewController.swift; sourceTree = ""; }; DB68045A2636DC6A00430867 /* MastodonNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonNotification.swift; sourceTree = ""; }; DB68047F2637CD4C00430867 /* AppShared.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppShared.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DB6804812637CD4C00430867 /* AppShared.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppShared.h; sourceTree = ""; }; @@ -2529,6 +2529,14 @@ path = Image; sourceTree = ""; }; + DB67D08727312E6A006A36CF /* Wizard */ = { + isa = PBXGroup; + children = ( + DB67D08527312E67006A36CF /* WizardViewController.swift */, + ); + path = Wizard; + sourceTree = ""; + }; DB6804802637CD4C00430867 /* AppShared */ = { isa = PBXGroup; children = ( @@ -2770,7 +2778,6 @@ isa = PBXGroup; children = ( DB8AF54F25C13703002E6C99 /* MainTabBarController.swift */, - DB647C5626F1E97300F7F82C /* MainTabBarController+Wizard.swift */, ); path = MainTab; sourceTree = ""; @@ -2782,6 +2789,7 @@ DB6180E426391A500018D199 /* Transition */, DB852D1D26FB021900FC9D81 /* Root */, DB01409B25C40BB600F9F3CF /* Onboarding */, + DB67D08727312E6A006A36CF /* Wizard */, DB9F58ED26EF435800E7BBE9 /* Account */, 2D38F1D325CD463600561493 /* HomeTimeline */, 2D76316325C14BAC00929FB9 /* PublicTimeline */, @@ -4358,6 +4366,7 @@ DB71FD2C25F86A5100512AE1 /* AvatarStackContainerButton.swift in Sources */, DB87D4512609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift in Sources */, DBBC24C026A5443100398BB9 /* MastodonTheme.swift in Sources */, + DB67D08627312E67006A36CF /* WizardViewController.swift in Sources */, DBBC24B526A540AE00398BB9 /* AvatarConfigurableView.swift in Sources */, DB9A489026035963008B817C /* APIService+Media.swift in Sources */, DBFEF07726A691FB006D7ED1 /* MastodonAuthenticationBox.swift in Sources */, @@ -4371,7 +4380,6 @@ DB49A62525FF334C00B98345 /* EmojiService+CustomEmojiViewModel+LoadState.swift in Sources */, DB4924E226312AB200E9DB22 /* NotificationService.swift in Sources */, DB6D9F6F2635807F008423CD /* Setting.swift in Sources */, - DB647C5726F1E97300F7F82C /* MainTabBarController+Wizard.swift in Sources */, DB6F5E38264E994A009108F4 /* AutoCompleteTopChevronView.swift in Sources */, DBB525412611ED54002F1F29 /* ProfileHeaderViewController.swift in Sources */, DB6B74F8272FBFB100C70B6E /* FollowerListViewController+Provider.swift in Sources */, diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index e7611056..9fbb2b77 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -23,6 +23,7 @@ final public class SceneCoordinator { private(set) weak var tabBarController: MainTabBarController! private(set) weak var splitViewController: RootSplitViewController? + private(set) var wizardViewController: WizardViewController? private(set) var secondaryStackHashValues = Set() @@ -221,17 +222,34 @@ extension SceneCoordinator { extension SceneCoordinator { func setup() { + let rootViewController: UIViewController switch UIDevice.current.userInterfaceIdiom { case .phone: let viewController = MainTabBarController(context: appContext, coordinator: self) - sceneDelegate.window?.rootViewController = viewController - tabBarController = viewController + self.splitViewController = nil + self.tabBarController = viewController + rootViewController = viewController default: let splitViewController = RootSplitViewController(context: appContext, coordinator: self) self.splitViewController = splitViewController self.tabBarController = splitViewController.contentSplitViewController.mainTabBarController - sceneDelegate.window?.rootViewController = splitViewController + rootViewController = splitViewController } + + let wizardViewController = WizardViewController() + if !wizardViewController.items.isEmpty, + let delegate = rootViewController as? WizardViewControllerDelegate + { + // do not add as child view controller. + // otherwise, the tab bar controller will add as a new tab + wizardViewController.delegate = delegate + wizardViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] + wizardViewController.view.frame = rootViewController.view.bounds + rootViewController.view.addSubview(wizardViewController.view) + self.wizardViewController = wizardViewController + } + + sceneDelegate.window?.rootViewController = rootViewController } func setupOnboardingIfNeeds(animated: Bool) { diff --git a/Mastodon/Scene/Onboarding/Welcome/View/WizardCardView.swift b/Mastodon/Scene/Onboarding/Welcome/View/WizardCardView.swift index 024fb205..6f18afc9 100644 --- a/Mastodon/Scene/Onboarding/Welcome/View/WizardCardView.swift +++ b/Mastodon/Scene/Onboarding/Welcome/View/WizardCardView.swift @@ -96,6 +96,13 @@ extension WizardCardView { path.addArc(withCenter: CGPoint(x: rect.minX, y: rect.minY), radius: radius, startAngle: -.pi / 2, endAngle: -.pi, clockwise: false) path.addLine(to: CGPoint(x: rect.minX - radius, y: rect.maxY + radius + WizardCardView.bubbleArrowHeight)) path.close() + case .allCorners: // no arrow + path.move(to: CGPoint(x: rect.maxX, y: rect.maxY + radius)) + path.addArc(withCenter: CGPoint(x: rect.minX, y: rect.maxY), radius: radius, startAngle: .pi / 2, endAngle: .pi, clockwise: true) + path.addArc(withCenter: CGPoint(x: rect.minX, y: rect.minY), radius: radius, startAngle: .pi, endAngle: .pi / 2 * 3, clockwise: true) + path.addArc(withCenter: CGPoint(x: rect.maxX, y: rect.minY), radius: radius, startAngle: .pi / 2 * 3, endAngle: .pi * 2, clockwise: true) + path.addArc(withCenter: CGPoint(x: rect.maxX, y: rect.maxY), radius: radius, startAngle: .pi * 2, endAngle: .pi / 2 * 5, clockwise: true) + path.close() default: assertionFailure("FIXME") } diff --git a/Mastodon/Scene/Root/MainTab/MainTabBarController+Wizard.swift b/Mastodon/Scene/Root/MainTab/MainTabBarController+Wizard.swift deleted file mode 100644 index b69a6b78..00000000 --- a/Mastodon/Scene/Root/MainTab/MainTabBarController+Wizard.swift +++ /dev/null @@ -1,137 +0,0 @@ -// -// MainTabBarController+Wizard.swift -// Mastodon -// -// Created by Cirno MainasuK on 2021-9-15. -// - -import os.log -import UIKit - -protocol WizardDelegate: AnyObject { - func spotlight(item: MainTabBarController.Wizard.Item) -> UIBezierPath - func layoutWizardCard(_ wizard: MainTabBarController.Wizard, item: MainTabBarController.Wizard.Item) -} - -extension MainTabBarController { - class Wizard { - - let logger = Logger(subsystem: "Wizard", category: "UI") - - weak var delegate: WizardDelegate? - - private(set) var items: [Item] - - let backgroundView: UIView = { - let view = UIView() - view.backgroundColor = UIColor.black.withAlphaComponent(0.7) - return view - }() - - init() { - var items: [Item] = [] - if !UserDefaults.shared.didShowMultipleAccountSwitchWizard { - items.append(.multipleAccountSwitch) - } - self.items = items - - let backgroundTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer - backgroundTapGestureRecognizer.addTarget(self, action: #selector(MainTabBarController.Wizard.backgroundTapGestureRecognizerHandler(_:))) - backgroundView.addGestureRecognizer(backgroundTapGestureRecognizer) - } - } -} - -extension MainTabBarController.Wizard { - enum Item { - case multipleAccountSwitch - - var title: String { - return L10n.Scene.Wizard.newInMastodon - } - - var description: String { - switch self { - case .multipleAccountSwitch: - return L10n.Scene.Wizard.multipleAccountSwitchIntroDescription - } - } - - func markAsRead() { - switch self { - case .multipleAccountSwitch: - UserDefaults.shared.didShowMultipleAccountSwitchWizard = true - } - } - } -} - -extension MainTabBarController.Wizard { - - func setup(in view: UIView) { - assert(delegate != nil, "need set delegate before use") - - guard !items.isEmpty else { return } - - backgroundView.frame = view.bounds - backgroundView.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(backgroundView) - NSLayoutConstraint.activate([ - backgroundView.topAnchor.constraint(equalTo: view.topAnchor), - backgroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - backgroundView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - backgroundView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) - } - - func consume() { - guard !items.isEmpty else { - backgroundView.removeFromSuperview() - return - } - let item = items.removeFirst() - perform(item: item) - } - - private func perform(item: Item) { - guard let delegate = delegate else { - assertionFailure() - return - } - - // prepare for reuse - prepareForReuse() - - // set wizard item read - item.markAsRead() - - // add spotlight - let spotlight = delegate.spotlight(item: item) - let maskLayer = CAShapeLayer() - let path = UIBezierPath(rect: backgroundView.bounds) - path.append(spotlight) - maskLayer.fillRule = .evenOdd - maskLayer.path = path.cgPath - backgroundView.layer.mask = maskLayer - - // layout wizard card - delegate.layoutWizardCard(self, item: item) - } - - private func prepareForReuse() { - backgroundView.subviews.forEach { subview in - subview.removeFromSuperview() - } - backgroundView.mask = nil - backgroundView.layer.mask = nil - } - -} - -extension MainTabBarController.Wizard { - @objc private func backgroundTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) { - logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") - - consume() - } -} diff --git a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift index e76db6b3..2cb96427 100644 --- a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift +++ b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift @@ -21,8 +21,6 @@ class MainTabBarController: UITabBarController { static let avatarButtonSize = CGSize(width: 25, height: 25) let avatarButton = CircleAvatarButton() - - let wizard = Wizard() var currentTab = CurrentValueSubject(.home) @@ -108,6 +106,8 @@ class MainTabBarController: UITabBarController { var _viewControllers: [UIViewController] = [] + private(set) var isReadyForWizardAvatarButton = false + init(context: AppContext, coordinator: SceneCoordinator) { self.context = context self.coordinator = coordinator @@ -247,9 +247,6 @@ extension MainTabBarController { profileTabItem.accessibilityHint = L10n.Scene.AccountList.tabBarHint(currentUserDisplayName) } .store(in: &disposeBag) - - wizard.delegate = self - wizard.setup(in: view) let tabBarLongPressGestureRecognizer = UILongPressGestureRecognizer() tabBarLongPressGestureRecognizer.addTarget(self, action: #selector(MainTabBarController.tabBarLongPressGestureRecognizerHandler(_:))) @@ -265,7 +262,7 @@ extension MainTabBarController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - wizard.consume() + isReadyForWizardAvatarButton = true } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { @@ -377,51 +374,57 @@ extension MainTabBarController: UITabBarControllerDelegate { } } -// MARK: - WizardDataSource -extension MainTabBarController: WizardDelegate { - func spotlight(item: Wizard.Item) -> UIBezierPath { +// MARK: - WizardViewControllerDelegate +extension MainTabBarController: WizardViewControllerDelegate { + func readyToLayoutItem(_ wizardViewController: WizardViewController, item: WizardViewController.Item) -> Bool { switch item { case .multipleAccountSwitch: - guard let avatarButtonFrameInView = avatarButtonFrameInView() else { - return UIBezierPath() - } - return UIBezierPath(ovalIn: avatarButtonFrameInView) - + return isReadyForWizardAvatarButton } } - func layoutWizardCard(_ wizard: MainTabBarController.Wizard, item: Wizard.Item) { + func layoutSpotlight(_ wizardViewController: WizardViewController, item: WizardViewController.Item) -> UIBezierPath { switch item { case .multipleAccountSwitch: - guard let avatarButtonFrameInView = avatarButtonFrameInView() else { + guard let avatarButtonFrameInView = avatarButtonFrameInWizardView(wizardView: wizardViewController.view) else { + return UIBezierPath() + } + return UIBezierPath(ovalIn: avatarButtonFrameInView) + } + } + + func layoutWizardCard(_ wizardViewController: WizardViewController, item: WizardViewController.Item) { + switch item { + case .multipleAccountSwitch: + guard let avatarButtonFrameInView = avatarButtonFrameInWizardView(wizardView: wizardViewController.view) else { return } let anchorView = UIView() anchorView.frame = avatarButtonFrameInView - wizard.backgroundView.addSubview(anchorView) + wizardViewController.backgroundView.addSubview(anchorView) let wizardCardView = WizardCardView() wizardCardView.arrowRectCorner = view.traitCollection.layoutDirection == .leftToRight ? .bottomRight : .bottomLeft wizardCardView.titleLabel.text = item.title wizardCardView.descriptionLabel.text = item.description - + wizardCardView.translatesAutoresizingMaskIntoConstraints = false - wizard.backgroundView.addSubview(wizardCardView) + wizardViewController.backgroundView.addSubview(wizardCardView) NSLayoutConstraint.activate([ anchorView.topAnchor.constraint(equalTo: wizardCardView.bottomAnchor, constant: 13), // 13pt spacing wizardCardView.trailingAnchor.constraint(equalTo: anchorView.centerXAnchor), - wizardCardView.widthAnchor.constraint(equalTo: wizard.backgroundView.widthAnchor, multiplier: 2.0/3.0).priority(.required - 1), + wizardCardView.widthAnchor.constraint(equalTo: wizardViewController.view.widthAnchor, multiplier: 2.0/3.0).priority(.required - 1), ]) wizardCardView.setContentHuggingPriority(.defaultLow, for: .vertical) } } - private func avatarButtonFrameInView() -> CGRect? { + private func avatarButtonFrameInWizardView(wizardView: UIView) -> CGRect? { guard let superview = avatarButton.superview else { assertionFailure() return nil } - return superview.convert(avatarButton.frame, to: view) + return superview.convert(avatarButton.frame, to: wizardView) } } diff --git a/Mastodon/Scene/Root/RootSplitViewController.swift b/Mastodon/Scene/Root/RootSplitViewController.swift index 13df6f88..2b3c858e 100644 --- a/Mastodon/Scene/Root/RootSplitViewController.swift +++ b/Mastodon/Scene/Root/RootSplitViewController.swift @@ -241,3 +241,90 @@ extension RootSplitViewController: UISplitViewControllerDelegate { } } + +// MARK: - WizardViewControllerDelegate +extension RootSplitViewController: WizardViewControllerDelegate { + + func readyToLayoutItem(_ wizardViewController: WizardViewController, item: WizardViewController.Item) -> Bool { + guard traitCollection.horizontalSizeClass != .compact else { + return compactMainTabBarViewController.readyToLayoutItem(wizardViewController, item: item) + } + + switch item { + case .multipleAccountSwitch: + return contentSplitViewController.sidebarViewController.viewModel.isReadyForWizardAvatarButton + } + } + + + func layoutSpotlight(_ wizardViewController: WizardViewController, item: WizardViewController.Item) -> UIBezierPath { + guard traitCollection.horizontalSizeClass != .compact else { + return compactMainTabBarViewController.layoutSpotlight(wizardViewController, item: item) + } + + switch item { + case .multipleAccountSwitch: + guard let frame = avatarButtonFrameInWizardView(wizardView: wizardViewController.view) + else { + assertionFailure() + return UIBezierPath() + } + return UIBezierPath(ovalIn: frame) + } + } + + func layoutWizardCard(_ wizardViewController: WizardViewController, item: WizardViewController.Item) { + guard traitCollection.horizontalSizeClass != .compact else { + return compactMainTabBarViewController.layoutWizardCard(wizardViewController, item: item) + } + + guard let frame = avatarButtonFrameInWizardView(wizardView: wizardViewController.view) else { + return + } + + let anchorView = UIView() + anchorView.frame = frame + wizardViewController.backgroundView.addSubview(anchorView) + + let wizardCardView = WizardCardView() + wizardCardView.arrowRectCorner = .allCorners // no arrow + wizardCardView.titleLabel.text = item.title + wizardCardView.descriptionLabel.text = item.description + + wizardCardView.translatesAutoresizingMaskIntoConstraints = false + wizardViewController.backgroundView.addSubview(wizardCardView) + NSLayoutConstraint.activate([ + wizardCardView.centerYAnchor.constraint(equalTo: anchorView.centerYAnchor), + wizardCardView.leadingAnchor.constraint(equalTo: anchorView.trailingAnchor, constant: 20), // 20pt spacing + wizardCardView.widthAnchor.constraint(equalToConstant: 320), + ]) + wizardCardView.setContentHuggingPriority(.defaultLow, for: .vertical) + } + + private func avatarButtonFrameInWizardView(wizardView: UIView) -> CGRect? { + guard let diffableDataSource = contentSplitViewController.sidebarViewController.viewModel.diffableDataSource, + let indexPath = diffableDataSource.indexPath(for: .tab(.me)), + let cell = contentSplitViewController.sidebarViewController.collectionView.cellForItem(at: indexPath) as? SidebarListCollectionViewCell, + let contentView = cell._contentView, + let frame = sourceViewFrameInTargetView( + sourceView: contentView.avatarButton, + targetView: wizardView + ) + else { + assertionFailure() + return nil + } + return frame + } + + private func sourceViewFrameInTargetView( + sourceView: UIView, + targetView: UIView + ) -> CGRect? { + guard let superview = sourceView.superview else { + assertionFailure() + return nil + } + return superview.convert(sourceView.frame, to: targetView) + } +} diff --git a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift index 7958c508..d2dae247 100644 --- a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift +++ b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift @@ -125,6 +125,7 @@ extension SidebarViewController { secondaryCollectionView.observe(\.contentSize, options: [.initial, .new]) { [weak self] secondaryCollectionView, _ in guard let self = self else { return } + self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): secondaryCollectionView contentSize: \(secondaryCollectionView.contentSize.debugDescription)") let height = secondaryCollectionView.contentSize.height self.secondaryCollectionViewHeightLayoutConstraint.constant = height self.collectionView.contentInset.bottom = height diff --git a/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift b/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift index 83abf4e6..1f982429 100644 --- a/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift +++ b/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift @@ -23,6 +23,7 @@ final class SidebarViewModel { // output var diffableDataSource: UICollectionViewDiffableDataSource? var secondaryDiffableDataSource: UICollectionViewDiffableDataSource? + private(set) var isReadyForWizardAvatarButton = false let activeMastodonAuthenticationObjectID = CurrentValueSubject(nil) @@ -170,8 +171,12 @@ extension SidebarViewModel { .setting, ] sectionSnapshot.append(items, to: nil) - _diffableDataSource.apply(sectionSnapshot, to: .main) - + // animatingDifferences must to be `true` + // otherwise the UI layout will infinity loop + _diffableDataSource.apply(sectionSnapshot, to: .main, animatingDifferences: true) { [weak self] in + guard let self = self else { return } + self.isReadyForWizardAvatarButton = true + } // secondary let _secondaryDiffableDataSource = UICollectionViewDiffableDataSource(collectionView: secondaryCollectionView) { collectionView, indexPath, item in diff --git a/Mastodon/Scene/Wizard/WizardViewController.swift b/Mastodon/Scene/Wizard/WizardViewController.swift new file mode 100644 index 00000000..2678c712 --- /dev/null +++ b/Mastodon/Scene/Wizard/WizardViewController.swift @@ -0,0 +1,211 @@ +// +// WizardViewController.swift +// Mastodon +// +// Created by Cirno MainasuK on 2021-11-2. +// + +import os.log +import UIKit +import Combine + +protocol WizardViewControllerDelegate: AnyObject { + func readyToLayoutItem(_ wizardViewController: WizardViewController, item: WizardViewController.Item) -> Bool + func layoutSpotlight(_ wizardViewController: WizardViewController, item: WizardViewController.Item) -> UIBezierPath + func layoutWizardCard(_ wizardViewController: WizardViewController, item: WizardViewController.Item) +} + +class WizardViewController: UIViewController { + + let logger = Logger(subsystem: "Wizard", category: "UI") + + var disposeBag = Set() + weak var delegate: WizardViewControllerDelegate? + + private(set) var items: [Item] = { + var items: [Item] = [] + if !UserDefaults.shared.didShowMultipleAccountSwitchWizard { + items.append(.multipleAccountSwitch) + } + return items + }() + + let pendingItem = CurrentValueSubject(nil) + let currentItem = CurrentValueSubject(nil) + + let backgroundView: UIView = { + let view = UIView() + view.backgroundColor = UIColor.black.withAlphaComponent(0.7) + return view + }() + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + +extension WizardViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + setup() + + let backgroundTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer + backgroundTapGestureRecognizer.addTarget(self, action: #selector(WizardViewController.backgroundTapGestureRecognizerHandler(_:))) + backgroundView.addGestureRecognizer(backgroundTapGestureRecognizer) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + // Create a timer to consume pending item + Timer.publish(every: 0.5, on: .main, in: .default) + .autoconnect() + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self = self else { return } + guard self.pendingItem.value != nil else { return } + self.consume() + } + .store(in: &disposeBag) + + consume() + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + invalidLayoutForCurrentItem() + } + + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + + coordinator.animate { context in + + } completion: { [weak self] context in + guard let self = self else { return } + self.invalidLayoutForCurrentItem() + } + + } + +} + +extension WizardViewController { + enum Item { + case multipleAccountSwitch + + var title: String { + return L10n.Scene.Wizard.newInMastodon + } + + var description: String { + switch self { + case .multipleAccountSwitch: + return L10n.Scene.Wizard.multipleAccountSwitchIntroDescription + } + } + + func markAsRead() { + switch self { + case .multipleAccountSwitch: + UserDefaults.shared.didShowMultipleAccountSwitchWizard = true + } + } + } +} + +extension WizardViewController { + + func setup() { + assert(delegate != nil, "need set delegate before use") + + guard !items.isEmpty else { return } + + backgroundView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + backgroundView.frame = view.bounds + view.addSubview(backgroundView) + } + + func destroy() { + view.removeFromSuperview() + } + + func consume() { + guard !items.isEmpty else { + destroy() + return + } + + guard let first = items.first else { return } + guard delegate?.readyToLayoutItem(self, item: first) == true else { + pendingItem.value = first + return + } + pendingItem.value = nil + currentItem.value = nil + + let item = items.removeFirst() + perform(item: item) + } + + private func perform(item: Item) { + guard let delegate = delegate else { + assertionFailure() + return + } + + // prepare for reuse + prepareForReuse() + + // set wizard item read + item.markAsRead() + + // add spotlight + let spotlight = delegate.layoutSpotlight(self, item: item) + let maskLayer = CAShapeLayer() + // expand rect to make sure view always fill the screen when device rotate + let expandRect: CGRect = { + var rect = backgroundView.bounds + rect.size.width *= 2 + rect.size.height *= 2 + return rect + }() + let path = UIBezierPath(rect: expandRect) + path.append(spotlight) + maskLayer.fillRule = .evenOdd + maskLayer.path = path.cgPath + backgroundView.layer.mask = maskLayer + + // layout wizard card + delegate.layoutWizardCard(self, item: item) + + currentItem.value = item + } + + private func prepareForReuse() { + backgroundView.subviews.forEach { subview in + subview.removeFromSuperview() + } + backgroundView.mask = nil + backgroundView.layer.mask = nil + } + + private func invalidLayoutForCurrentItem() { + if let item = currentItem.value { + perform(item: item) + } + } + +} + +extension WizardViewController { + @objc private func backgroundTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + + consume() + } +} From 1aea75fb830f01a3a76d90f0f435dbb03e1b6cd3 Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 2 Nov 2021 19:16:34 +0800 Subject: [PATCH 92/96] chore: update version to 1.2.0 (83) --- AppShared/Info.plist | 2 +- CoreDataStack/Info.plist | 2 +- CoreDataStackTests/Info.plist | 2 +- Mastodon.xcodeproj/project.pbxproj | 64 +++++++++---------- .../xcschemes/xcschememanagement.plist | 8 +-- Mastodon/Info.plist | 2 +- MastodonIntent/Info.plist | 2 +- MastodonTests/Info.plist | 2 +- MastodonUITests/Info.plist | 2 +- NotificationService/Info.plist | 2 +- ShareActionExtension/Info.plist | 2 +- 11 files changed, 45 insertions(+), 45 deletions(-) diff --git a/AppShared/Info.plist b/AppShared/Info.plist index 16c084ce..be737c93 100644 --- a/AppShared/Info.plist +++ b/AppShared/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 82 + 83 diff --git a/CoreDataStack/Info.plist b/CoreDataStack/Info.plist index 16c084ce..be737c93 100644 --- a/CoreDataStack/Info.plist +++ b/CoreDataStack/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 82 + 83 diff --git a/CoreDataStackTests/Info.plist b/CoreDataStackTests/Info.plist index 16c084ce..be737c93 100644 --- a/CoreDataStackTests/Info.plist +++ b/CoreDataStackTests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 82 + 83 diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 619baa12..a2a357c8 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -4903,7 +4903,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4932,7 +4932,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -5040,11 +5040,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 82; + DYLIB_CURRENT_VERSION = 83; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5071,11 +5071,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 82; + DYLIB_CURRENT_VERSION = 83; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5100,11 +5100,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 82; + DYLIB_CURRENT_VERSION = 83; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = CoreDataStack/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5130,11 +5130,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 82; + DYLIB_CURRENT_VERSION = 83; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = CoreDataStack/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5197,7 +5197,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5222,7 +5222,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5247,7 +5247,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5272,7 +5272,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5297,7 +5297,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5322,7 +5322,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5347,7 +5347,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5372,7 +5372,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5463,7 +5463,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -5530,11 +5530,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 82; + DYLIB_CURRENT_VERSION = 83; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = CoreDataStack/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5579,7 +5579,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5604,11 +5604,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 82; + DYLIB_CURRENT_VERSION = 83; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5700,7 +5700,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -5767,11 +5767,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 82; + DYLIB_CURRENT_VERSION = 83; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = CoreDataStack/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5816,7 +5816,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5841,11 +5841,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 82; + DYLIB_CURRENT_VERSION = 83; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5871,7 +5871,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5895,7 +5895,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index 5a541891..fce31178 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ AppShared.xcscheme_^#shared#^_ orderHint - 37 + 44 CoreDataStack.xcscheme_^#shared#^_ orderHint - 36 + 43 Mastodon - ASDK.xcscheme_^#shared#^_ @@ -97,7 +97,7 @@ MastodonIntent.xcscheme_^#shared#^_ orderHint - 35 + 41 MastodonIntents.xcscheme_^#shared#^_ @@ -117,7 +117,7 @@ ShareActionExtension.xcscheme_^#shared#^_ orderHint - 38 + 42 SuppressBuildableAutocreation diff --git a/Mastodon/Info.plist b/Mastodon/Info.plist index a75982e3..f5b56d6d 100644 --- a/Mastodon/Info.plist +++ b/Mastodon/Info.plist @@ -30,7 +30,7 @@ CFBundleVersion - 82 + 83 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/MastodonIntent/Info.plist b/MastodonIntent/Info.plist index ed243297..a79ce28b 100644 --- a/MastodonIntent/Info.plist +++ b/MastodonIntent/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 82 + 83 NSExtension NSExtensionAttributes diff --git a/MastodonTests/Info.plist b/MastodonTests/Info.plist index 16c084ce..be737c93 100644 --- a/MastodonTests/Info.plist +++ b/MastodonTests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 82 + 83 diff --git a/MastodonUITests/Info.plist b/MastodonUITests/Info.plist index 16c084ce..be737c93 100644 --- a/MastodonUITests/Info.plist +++ b/MastodonUITests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 82 + 83 diff --git a/NotificationService/Info.plist b/NotificationService/Info.plist index 89e56253..7f407c80 100644 --- a/NotificationService/Info.plist +++ b/NotificationService/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 82 + 83 NSExtension NSExtensionPointIdentifier diff --git a/ShareActionExtension/Info.plist b/ShareActionExtension/Info.plist index 79ba82ce..fd2a138c 100644 --- a/ShareActionExtension/Info.plist +++ b/ShareActionExtension/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.2.0 CFBundleVersion - 82 + 83 NSExtension NSExtensionAttributes From 716326351e5f0206b8f12c5fcfef01eaf75aabc9 Mon Sep 17 00:00:00 2001 From: CMK Date: Wed, 3 Nov 2021 14:24:52 +0800 Subject: [PATCH 93/96] feat: add store rating supports --- Mastodon.xcodeproj/project.pbxproj | 4 +++ .../xcschemes/xcschememanagement.plist | 8 +++--- .../Preference/StoreReviewPreference.swift | 26 +++++++++++++++++++ ...meTimelineViewController+DebugAction.swift | 6 +++++ .../HomeTimelineViewController.swift | 16 ++++++++++++ Mastodon/Service/StatusPublishService.swift | 2 +- Mastodon/Supporting Files/AppDelegate.swift | 5 ++++ 7 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 Mastodon/Preference/StoreReviewPreference.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index a2a357c8..5e23b578 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -321,6 +321,7 @@ DB66729C25F9F91F00D60309 /* ComposeStatusItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */; }; DB67D08427312970006A36CF /* APIService+Following.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB67D08327312970006A36CF /* APIService+Following.swift */; }; DB67D08627312E67006A36CF /* WizardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB67D08527312E67006A36CF /* WizardViewController.swift */; }; + DB67D089273256D7006A36CF /* StoreReviewPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB67D088273256D7006A36CF /* StoreReviewPreference.swift */; }; DB68045B2636DC6A00430867 /* MastodonNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68045A2636DC6A00430867 /* MastodonNotification.swift */; }; DB6804662636DC9000430867 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AB425EDD8A90076FA61 /* String.swift */; }; DB68046C2636DC9E00430867 /* MastodonNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68045A2636DC6A00430867 /* MastodonNotification.swift */; }; @@ -1141,6 +1142,7 @@ DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusItem.swift; sourceTree = ""; }; DB67D08327312970006A36CF /* APIService+Following.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Following.swift"; sourceTree = ""; }; DB67D08527312E67006A36CF /* WizardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardViewController.swift; sourceTree = ""; }; + DB67D088273256D7006A36CF /* StoreReviewPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreReviewPreference.swift; sourceTree = ""; }; DB68045A2636DC6A00430867 /* MastodonNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonNotification.swift; sourceTree = ""; }; DB68047F2637CD4C00430867 /* AppShared.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppShared.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DB6804812637CD4C00430867 /* AppShared.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppShared.h; sourceTree = ""; }; @@ -2447,6 +2449,7 @@ DB1D842F26566512000346B3 /* KeyboardPreference.swift */, DBCBCC0C2680B908000F5B51 /* HomeTimelinePreference.swift */, DBD376AB2692ECDB007FEC24 /* ThemePreference.swift */, + DB67D088273256D7006A36CF /* StoreReviewPreference.swift */, ); path = Preference; sourceTree = ""; @@ -4101,6 +4104,7 @@ DBE3CE01261D623D00430CC6 /* FavoriteViewModel+State.swift in Sources */, 2D82BA0525E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift in Sources */, 2D38F1EB25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift in Sources */, + DB67D089273256D7006A36CF /* StoreReviewPreference.swift in Sources */, DB5B7295273112B100081888 /* FollowingListViewController.swift in Sources */, 0F202201261326E6000C64BF /* HashtagTimelineViewModel.swift in Sources */, DB6D9F9726367249008423CD /* SettingsViewController.swift in Sources */, diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index fce31178..e6093a21 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ AppShared.xcscheme_^#shared#^_ orderHint - 44 + 35 CoreDataStack.xcscheme_^#shared#^_ orderHint - 43 + 38 Mastodon - ASDK.xcscheme_^#shared#^_ @@ -97,7 +97,7 @@ MastodonIntent.xcscheme_^#shared#^_ orderHint - 41 + 36 MastodonIntents.xcscheme_^#shared#^_ @@ -117,7 +117,7 @@ ShareActionExtension.xcscheme_^#shared#^_ orderHint - 42 + 37 SuppressBuildableAutocreation diff --git a/Mastodon/Preference/StoreReviewPreference.swift b/Mastodon/Preference/StoreReviewPreference.swift new file mode 100644 index 00000000..e3a403f6 --- /dev/null +++ b/Mastodon/Preference/StoreReviewPreference.swift @@ -0,0 +1,26 @@ +// +// StoreReviewPreference.swift +// Mastodon +// +// Created by Cirno MainasuK on 2021-11-3. +// + +import Foundation + +extension UserDefaults { + + @objc dynamic var processCompletedCount: Int { + get { + return integer(forKey: #function) + } + set { self[#function] = newValue } + } + + @objc dynamic var lastVersionPromptedForReview: String? { + get { + return string(forKey: #function) + } + set { self[#function] = newValue } + } + +} diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift index 6d79d060..6e75a17e 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift @@ -15,6 +15,7 @@ import FLEX import SwiftUI import MastodonUI import MastodonSDK +import StoreKit extension HomeTimelineViewController { var debugMenu: UIMenu { @@ -77,6 +78,11 @@ extension HomeTimelineViewController { guard let self = self else { return } self.showThreadAction(action) }, + UIAction(title: "Store Rating", image: UIImage(systemName: "star.fill"), attributes: []) { [weak self] action in + guard let self = self else { return } + guard let windowScene = self.view.window?.windowScene else { return } + SKStoreReviewController.requestReview(in: windowScene) + }, ] ) } diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift index 6b547688..62695f21 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift @@ -14,6 +14,7 @@ import CoreDataStack import GameplayKit import MastodonSDK import AlamofireImage +import StoreKit final class HomeTimelineViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { @@ -144,6 +145,21 @@ extension HomeTimelineViewController { } .store(in: &disposeBag) + viewModel.homeTimelineNavigationBarTitleViewModel.state + .removeDuplicates() + .filter { $0 == .publishedButton } + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self = self else { return } + guard UserDefaults.shared.lastVersionPromptedForReview == nil else { return } + guard UserDefaults.shared.processCompletedCount > 3 else { return } + guard let windowScene = self.view.window?.windowScene else { return } + let version = UIApplication.appVersion() + UserDefaults.shared.lastVersionPromptedForReview = version + SKStoreReviewController.requestReview(in: windowScene) + } + .store(in: &disposeBag) + tableView.refreshControl = refreshControl refreshControl.addTarget(self, action: #selector(HomeTimelineViewController.refreshControlValueChanged(_:)), for: .valueChanged) diff --git a/Mastodon/Service/StatusPublishService.swift b/Mastodon/Service/StatusPublishService.swift index ed894f93..f5c4cb2d 100644 --- a/Mastodon/Service/StatusPublishService.swift +++ b/Mastodon/Service/StatusPublishService.swift @@ -12,6 +12,7 @@ import Combine import CoreData import CoreDataStack import MastodonSDK +import UIKit final class StatusPublishService { @@ -72,7 +73,6 @@ extension StatusPublishService { self.viewModels.value = viewModels os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: composeViewModel removed", ((#file as NSString).lastPathComponent), #line, #function) - } } diff --git a/Mastodon/Supporting Files/AppDelegate.swift b/Mastodon/Supporting Files/AppDelegate.swift index 87d16241..e2cb7c41 100644 --- a/Mastodon/Supporting Files/AppDelegate.swift +++ b/Mastodon/Supporting Files/AppDelegate.swift @@ -36,6 +36,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { UNUserNotificationCenter.current().delegate = self application.registerForRemoteNotifications() + // increase app process count + var count = UserDefaults.shared.processCompletedCount + count += 1 // Int64. could ignore overflow here + UserDefaults.shared.processCompletedCount = count + #if ASDK && DEBUG // PerformanceMonitor.shared().start() // ASDisplayNode.shouldShowRangeDebugOverlay = true From bab09c36fde9a6cbaf587e842277c8bb13b6a8bb Mon Sep 17 00:00:00 2001 From: CMK Date: Wed, 3 Nov 2021 15:15:55 +0800 Subject: [PATCH 94/96] fix: AutoLayout may enter infinity layout loop issue --- .../Scene/Root/Sidebar/SidebarViewController.swift | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift index d2dae247..b5f67e76 100644 --- a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift +++ b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift @@ -125,10 +125,16 @@ extension SidebarViewController { secondaryCollectionView.observe(\.contentSize, options: [.initial, .new]) { [weak self] secondaryCollectionView, _ in guard let self = self else { return } + + let contentHeight = secondaryCollectionView.contentSize.height + guard contentHeight > 0 else { return } self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): secondaryCollectionView contentSize: \(secondaryCollectionView.contentSize.debugDescription)") - let height = secondaryCollectionView.contentSize.height - self.secondaryCollectionViewHeightLayoutConstraint.constant = height - self.collectionView.contentInset.bottom = height + + let currentFrameHeight = secondaryCollectionView.frame.height + guard currentFrameHeight < contentHeight else { return } + + self.secondaryCollectionViewHeightLayoutConstraint.constant = contentHeight + self.collectionView.contentInset.bottom = contentHeight } .store(in: &observations) @@ -147,7 +153,6 @@ extension SidebarViewController { coordinator.animate { context in self.collectionView.collectionViewLayout.invalidateLayout() -// // do nothing } completion: { [weak self] context in // guard let self = self else { return } } From 757578e97e7d3c0f9439b70e9d759f5a8ff5416a Mon Sep 17 00:00:00 2001 From: CMK Date: Wed, 3 Nov 2021 15:16:17 +0800 Subject: [PATCH 95/96] fix: image transitioning clip invisible tab bar issue --- ...viewViewControllerAnimatedTransitioning.swift | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift b/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift index 9fbbbd88..ec4ac35a 100644 --- a/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift +++ b/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift @@ -184,9 +184,13 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { let maskLayerToPath = maskLayerToRect.flatMap { UIBezierPath(rect: $0) }?.cgPath let maskLayerToFinalRect: CGRect? = { guard case .mosaic = transitionItem.source else { return nil } - guard let tabBarController = toVC.tabBarController, let tabBarSuperView = tabBarController.tabBar.superview else { return nil } - let tabBarFrameInWindow = tabBarSuperView.convert(tabBarController.tabBar.frame, to: nil) var rect = maskLayerToRect ?? transitionMaskView.frame + // clip tabBar when bar visible + guard let tabBarController = toVC.tabBarController, + !tabBarController.tabBar.isHidden, + let tabBarSuperView = tabBarController.tabBar.superview + else { return rect } + let tabBarFrameInWindow = tabBarSuperView.convert(tabBarController.tabBar.frame, to: nil) let offset = rect.maxY - tabBarFrameInWindow.minY guard offset > 0 else { return rect } rect.size.height -= offset @@ -473,9 +477,13 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { let maskLayerToFinalRect: CGRect? = { guard case .mosaic = transitionItem.source else { return nil } - guard let tabBarController = toVC.tabBarController, let tabBarSuperView = tabBarController.tabBar.superview else { return nil } - let tabBarFrameInWindow = tabBarSuperView.convert(tabBarController.tabBar.frame, to: nil) var rect = maskLayerToRect ?? transitionMaskView.frame + // clip rect bottom when tabBar visible + guard let tabBarController = toVC.tabBarController, + !tabBarController.tabBar.isHidden, + let tabBarSuperView = tabBarController.tabBar.superview + else { return rect } + let tabBarFrameInWindow = tabBarSuperView.convert(tabBarController.tabBar.frame, to: nil) let offset = rect.maxY - tabBarFrameInWindow.minY guard offset > 0 else { return rect } rect.size.height -= offset From e15e373f3db4e74c1625124c3bfdd5e35590bc0f Mon Sep 17 00:00:00 2001 From: CMK Date: Wed, 3 Nov 2021 15:29:25 +0800 Subject: [PATCH 96/96] fix: search controller cannot trigger become first responder issue --- Mastodon/Scene/Root/RootSplitViewController.swift | 4 ++-- .../Search/SearchDetail/SearchDetailViewController.swift | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Mastodon/Scene/Root/RootSplitViewController.swift b/Mastodon/Scene/Root/RootSplitViewController.swift index 2b3c858e..f13383de 100644 --- a/Mastodon/Scene/Root/RootSplitViewController.swift +++ b/Mastodon/Scene/Root/RootSplitViewController.swift @@ -307,8 +307,8 @@ extension RootSplitViewController: WizardViewControllerDelegate { let cell = contentSplitViewController.sidebarViewController.collectionView.cellForItem(at: indexPath) as? SidebarListCollectionViewCell, let contentView = cell._contentView, let frame = sourceViewFrameInTargetView( - sourceView: contentView.avatarButton, - targetView: wizardView + sourceView: contentView.avatarButton, + targetView: wizardView ) else { assertionFailure() diff --git a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift index b401e795..486a3b48 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift @@ -245,7 +245,9 @@ extension SearchDetailViewController { searchBar.becomeFirstResponder() } else { searchController.isActive = true - searchController.searchBar.becomeFirstResponder() + DispatchQueue.main.asyncAfter(deadline: .now() + 0.33) { + self.searchController.searchBar.becomeFirstResponder() + } } }