diff --git a/.github/scripts/setup.sh b/.github/scripts/setup.sh index 0c2612d51..845730f1f 100755 --- a/.github/scripts/setup.sh +++ b/.github/scripts/setup.sh @@ -1,9 +1,16 @@ #!/bin/bash -sudo gem install cocoapods-keys +# workaround https://github.com/CocoaPods/CocoaPods/issues/11355 +sed -i '' $'1s/^/source "https:\\/\\/github.com\\/CocoaPods\\/Specs.git"\\\n\\\n/' Podfile + +# Install Ruby Bundler +gem install bundler:2.3.11 + +# Install Ruby Gems +bundle install # stub keys. DO NOT use in production -pod keys set notification_endpoint "" -pod keys set notification_endpoint_debug "" +bundle exec pod keys set notification_endpoint "" +bundle exec pod keys set notification_endpoint_debug "" -pod install +bundle exec pod install diff --git a/AppShared/AppSecret.swift b/AppShared/AppSecret.swift index 1fc5495c7..9110f2490 100644 --- a/AppShared/AppSecret.swift +++ b/AppShared/AppSecret.swift @@ -10,10 +10,7 @@ import Foundation import CryptoKit import KeychainAccess import Keys - -enum AppName { - public static let groupID = "group.org.joinmastodon.app" -} +import MastodonCommon public final class AppSecret { diff --git a/AppShared/Info.plist b/AppShared/Info.plist index 73f11cd26..a187ca268 100644 --- a/AppShared/Info.plist +++ b/AppShared/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.3.0 + 1.4.2 CFBundleVersion - 109 + 127 diff --git a/Documentation/Setup.md b/Documentation/Setup.md index ede9d4862..1c2f0a6c5 100644 --- a/Documentation/Setup.md +++ b/Documentation/Setup.md @@ -12,12 +12,13 @@ Intell the latest version of Xcode from the App Store or Apple Developer Downloa This guide may not suit your machine and actually setup procedure may change in the future. Please file the issue or Pull Request if there are any problems. ## CocoaPods -The app use [CocoaPods]() and [CocoaPods-Keys](https://github.com/orta/cocoapods-keys). The M1 Mac needs virtual ruby env to workaround compatibility issues. +The app use [CocoaPods]() and [CocoaPods-Keys](https://github.com/orta/cocoapods-keys). Ruby Gems are managed through Bundler. The M1 Mac needs virtual ruby env to workaround compatibility issues. #### Intel Mac ```zsh -sudo gem install cocoapods cocoapods-keys +gem install bundler +bundle install ``` #### M1 Mac @@ -40,18 +41,19 @@ rbenv global 3.0.3 ruby --version # > ruby 3.0.3p157 (2021-11-24 revision 3fb7d2cadc) [arm64-darwin21] -sudo gem install cocoapods cocoapods-keys +gem install bundler +bundle install ``` ## Bootstrap ```zsh # make a clean build -sudo gem install cocoapods-clean -pod clean +bundle install +bundle exec pod clean # make install -pod install --repo-update +bundle exec pod install --repo-update # open workspace open Mastodon.xcworkspace diff --git a/Gemfile b/Gemfile new file mode 100644 index 000000000..48aae3d82 --- /dev/null +++ b/Gemfile @@ -0,0 +1,6 @@ +source "https://rubygems.org" + +gem "cocoapods" +gem "cocoapods-clean" +gem "cocoapods-keys" + diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 000000000..b27a44a97 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,109 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.5) + rexml + RubyInline (3.12.5) + ZenTest (~> 4.3) + ZenTest (4.12.1) + activesupport (6.1.5.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) + addressable (2.8.0) + public_suffix (>= 2.0.2, < 5.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) + atomos (0.1.3) + claide (1.1.0) + cocoapods (1.11.3) + addressable (~> 2.8) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.11.3) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 1.4.0, < 2.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.4.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.8.0) + nap (~> 1.0) + ruby-macho (>= 1.0, < 3.0) + xcodeproj (>= 1.21.0, < 2.0) + cocoapods-clean (0.0.1) + cocoapods-core (1.11.3) + activesupport (>= 5.0, < 7) + addressable (~> 2.8) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + netrc (~> 0.11) + public_suffix (~> 4.0) + typhoeus (~> 1.0) + cocoapods-deintegrate (1.0.5) + cocoapods-downloader (1.6.3) + cocoapods-keys (2.2.1) + dotenv + osx_keychain + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.1) + cocoapods-trunk (1.6.0) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + cocoapods-try (1.2.0) + colored2 (3.1.2) + concurrent-ruby (1.1.10) + dotenv (2.7.6) + escape (0.0.4) + ethon (0.15.0) + ffi (>= 1.15.0) + ffi (1.15.5) + fourflusher (2.3.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + httpclient (2.8.3) + i18n (1.10.0) + concurrent-ruby (~> 1.0) + json (2.6.1) + minitest (5.15.0) + molinillo (0.8.0) + nanaimo (0.3.0) + nap (1.1.0) + netrc (0.11.0) + osx_keychain (1.0.2) + RubyInline (~> 3) + public_suffix (4.0.7) + rexml (3.2.5) + ruby-macho (2.5.1) + typhoeus (1.4.0) + ethon (>= 0.9.0) + tzinfo (2.0.4) + concurrent-ruby (~> 1.0) + xcodeproj (1.21.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + rexml (~> 3.2.4) + zeitwerk (2.5.4) + +PLATFORMS + ruby + +DEPENDENCIES + cocoapods + cocoapods-clean + cocoapods-keys + +BUNDLED WITH + 2.3.11 diff --git a/Localization/Localizable.stringsdict b/Localization/Localizable.stringsdict index 4b9a12762..0b9a6ff60 100644 --- a/Localization/Localizable.stringsdict +++ b/Localization/Localizable.stringsdict @@ -90,6 +90,28 @@ posts + plural.count.media + + NSStringLocalizedFormatKey + %#@media_count@ + media_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 media + one + 1 media + few + %ld media + many + %ld media + other + %ld media + + plural.count.post NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/Sources/StringsConvertor/main.swift b/Localization/StringsConvertor/Sources/StringsConvertor/main.swift index 606a95200..e30cf56a5 100644 --- a/Localization/StringsConvertor/Sources/StringsConvertor/main.swift +++ b/Localization/StringsConvertor/Sources/StringsConvertor/main.swift @@ -51,19 +51,25 @@ private func map(language: String) -> String? { case "eu_ES": return "eu-ES" // Basque case "ca_ES": return "ca" // Catalan case "zh_CN": return "zh-Hans" // Chinese Simplified + case "zh_TW": return "zh-Hant" // Chinese Traditional case "nl_NL": return "nl" // Dutch case "en_US": return "en" case "fr_FR": return "fr" // French + case "gl_ES": return "gl" // Galician case "de_DE": return "de" // German + case "it_IT": return "it" // Italian case "ja_JP": return "ja" // Japanese case "kab_KAB": return "kab" // Kabyle case "kmr_TR": return "ku" // Kurmanji (Kurdish) case "ru_RU": return "ru" // Russian case "gd_GB": return "gd-GB" // Scottish Gaelic + case "ckb_IR": return "ckb" // Sorani (Kurdish) case "es_ES": return "es" // Spanish case "es_AR": return "es-419" // Spanish, Argentina + case "sv-SE": return "sv" // Swedish case "sv_FI": return "sv_FI" // Swedish, Finland case "th_TH": return "th" // Thai + case "tr_TR": return "tr" // Turkish case "vi_VN": return "vi" // Vietnamese default: return nil } diff --git a/Localization/app.json b/Localization/app.json index f0dc0ebf1..32eaea9d2 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -129,6 +129,7 @@ "show_post": "Show Post", "show_user_profile": "Show user profile", "content_warning": "Content Warning", + "sensitive_content": "Sensitive Content", "media_content_warning": "Tap anywhere to reveal", "tap_to_reveal": "Tap to reveal", "poll": { @@ -210,9 +211,9 @@ "log_in": "Log In" }, "server_picker": { - "title": "Mastodon is made of users in different communities.", - "subtitle": "Pick a community based on your interests, region, or a general purpose one.", - "subtitle_extend": "Pick a community based on your interests, region, or a general purpose one. Each community is operated by an entirely independent organization or individual.", + "title": "Mastodon is made of users in different servers.", + "subtitle": "Pick a server based on your interests, region, or a general purpose one.", + "subtitle_extend": "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual.", "button": { "category": { "all": "All", @@ -239,7 +240,8 @@ "category": "CATEGORY" }, "input": { - "placeholder": "Search communities" + "placeholder": "Search servers", + "search_servers_or_enter_url": "Search communities or enter URL" }, "empty_state": { "finding_servers": "Finding available servers...", @@ -249,6 +251,7 @@ }, "register": { "title": "Let’s get you set up on %s", + "lets_get_you_set_up_on_domain": "Let’s get you set up on %s", "input": { "avatar": { "delete": "Delete" @@ -319,6 +322,7 @@ "confirm_email": { "title": "One last thing.", "subtitle": "Tap the link we emailed to you to verify your account.", + "tap_the_link_we_emailed_to_you_to_verify_your_account": "Tap the link we emailed to you to verify your account", "button": { "open_email_app": "Open Email App", "resend": "Resend" @@ -341,7 +345,11 @@ "offline": "Offline", "new_posts": "See new posts", "published": "Published!", - "Publishing": "Publishing post..." + "Publishing": "Publishing post...", + "accessibility": { + "logo_label": "Logo Button", + "logo_hint": "Tap to scroll to top and tap again to previous location" + } } }, "suggestion_account": { @@ -492,6 +500,16 @@ "clear": "Clear" } }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "community": "Community", + "for_you": "For You" + }, + "intro": "These are the posts gaining traction in your corner of Mastodon." + }, "favorite": { "title": "Your Favorites" }, @@ -585,7 +603,49 @@ "send": "Send Report", "skip_to_send": "Send without comment", "text_placeholder": "Type or paste additional comments", - "reported": "REPORTED" + "reported": "REPORTED", + "step_one": { + "step_1_of_4": "Step 1 of 4", + "whats_wrong_with_this_post": "What's wrong with this post?", + "whats_wrong_with_this_account": "What's wrong with this account?", + "whats_wrong_with_this_username": "What's wrong with %s?", + "select_the_best_match": "Select the best match", + "i_dont_like_it": "I don’t like it", + "it_is_not_something_you_want_to_see": "It is not something you want to see", + "its_spam": "It’s spam", + "malicious_links_fake_engagement_or_repetetive_replies": "Malicious links, fake engagement, or repetetive replies", + "it_violates_server_rules": "It violates server rules", + "you_are_aware_that_it_breaks_specific_rules": "You are aware that it breaks specific rules", + "its_something_else": "It’s something else", + "the_issue_does_not_fit_into_other_categories": "The issue does not fit into other categories" + }, + "step_two": { + "step_2_of_4": "Step 2 of 4", + "which_rules_are_being_violated": "Which rules are being violated?", + "select_all_that_apply": "Select all that apply", + "i_just_don’t_like_it": "I just don’t like it" + }, + "step_three": { + "step_3_of_4": "Step 3 of 4", + "are_there_any_posts_that_back_up_this_report": "Are there any posts that back up this report?", + "select_all_that_apply": "Select all that apply" + }, + "step_four": { + "step_4_of_4": "Step 4 of 4", + "is_there_anything_else_we_should_know": "Is there anything else we should know?" + }, + "step_final": { + "dont_want_to_see_this": "Don’t want to see this?", + "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "When you see something you don’t like on Mastodon, you can remove the person from your experience.", + "unfollow": "Unfollow", + "unfollowed": "Unfollowed", + "unfollow_user": "Unfollow %s", + "mute_user": "Mute %s", + "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted.", + "block_user": "Block %s", + "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked.", + "while_we_review_this_you_can_take_action_against_user": "While we review this, you can take action against %s" + } }, "preview": { "keyboard": { diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index cbcd948b6..fb9e834cb 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -31,7 +31,6 @@ 2D206B9225F60EA700143C56 /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B9125F60EA700143C56 /* UIControl.swift */; }; 2D24E1232626ED9D00A59D4F /* UIView+Gesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D24E1222626ED9D00A59D4F /* UIView+Gesture.swift */; }; 2D32EAAC25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */; }; - 2D32EABA25CB9B0500C9ED86 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAB925CB9B0500C9ED86 /* UIView.swift */; }; 2D34D9D126148D9E0081BFC0 /* APIService+Recommend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D34D9D026148D9E0081BFC0 /* APIService+Recommend.swift */; }; 2D34D9DB261494120081BFC0 /* APIService+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D34D9DA261494120081BFC0 /* APIService+Search.swift */; }; 2D35237A26256D920031AF25 /* NotificationSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D35237926256D920031AF25 /* NotificationSection.swift */; }; @@ -87,9 +86,8 @@ 2DE0FACE2615F7AD00CDF649 /* RecommendAccountSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DE0FACD2615F7AD00CDF649 /* RecommendAccountSection.swift */; }; 2DF123A725C3B0210020F248 /* ActiveLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF123A625C3B0210020F248 /* ActiveLabel.swift */; }; 2DF75BA725D10E1000694EC8 /* APIService+Favorite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF75BA625D10E1000694EC8 /* APIService+Favorite.swift */; }; - 4278334D6033AEEE0A1C5155 /* Pods_ShareActionExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A32B0CACBF35F4CC3CFAA043 /* Pods_ShareActionExtension.framework */; }; 5B24BBDA262DB14800A9381B /* ReportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B24BBD7262DB14800A9381B /* ReportViewModel.swift */; }; - 5B24BBDB262DB14800A9381B /* ReportViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B24BBD8262DB14800A9381B /* ReportViewModel+Diffable.swift */; }; + 5B24BBDB262DB14800A9381B /* ReportStatusViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B24BBD8262DB14800A9381B /* ReportStatusViewModel+Diffable.swift */; }; 5B24BBE2262DB19100A9381B /* APIService+Report.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B24BBE1262DB19100A9381B /* APIService+Report.swift */; }; 5B90C45E262599800002E742 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B90C456262599800002E742 /* SettingsViewModel.swift */; }; 5B90C45F262599800002E742 /* SettingsToggleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B90C459262599800002E742 /* SettingsToggleTableViewCell.swift */; }; @@ -102,7 +100,6 @@ 5BB04FF5262F0E6D0043BFF6 /* ReportSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB04FF4262F0E6D0043BFF6 /* ReportSection.swift */; }; 5D0393902612D259007FE196 /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D03938F2612D259007FE196 /* WebViewController.swift */; }; 5D0393962612D266007FE196 /* WebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D0393952612D266007FE196 /* WebViewModel.swift */; }; - 5D526FE225BE9AC400460CB9 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 5D526FE125BE9AC400460CB9 /* MastodonSDK */; }; 5DA732CC2629CEF500A92342 /* UIView+Remove.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DA732CB2629CEF500A92342 /* UIView+Remove.swift */; }; 5DDDF1932617442700311060 /* Mastodon+Entity+Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DDDF1922617442700311060 /* Mastodon+Entity+Account.swift */; }; 5DDDF1992617447F00311060 /* Mastodon+Entity+Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DDDF1982617447F00311060 /* Mastodon+Entity+Tag.swift */; }; @@ -111,15 +108,9 @@ 5E0DEC05797A7E6933788DDB /* Pods_MastodonTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 452147B2903DF38070FE56A2 /* Pods_MastodonTests.framework */; }; 5E44BF88AD33646E64727BCF /* Pods_MastodonTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */; }; 87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */; }; - B914FC6B0B8AF18573C0B291 /* Pods_NotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 374AA339A20E0FAC75BCDA6D /* Pods_NotificationService.framework */; }; - BBAC710E327AF1EE1DB36A4E /* Pods_MastodonIntent.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F4C94BD75C96D0EFF5F6D961 /* Pods_MastodonIntent.framework */; }; DB0009A626AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; settings = {ATTRIBUTES = (no_codegen, ); }; }; DB0009A726AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; }; - DB00CA972632DDB600A54956 /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB00CA962632DDB600A54956 /* CommonOSLog */; }; - DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB0140BC25C40D7500F9F3CF /* CommonOSLog */; }; DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140CE25C42AEE00F9F3CF /* OSLog.swift */; }; - DB01E23326A98F0900C3965B /* MastodonMeta in Frameworks */ = {isa = PBXBuildFile; productRef = DB01E23226A98F0900C3965B /* MastodonMeta */; }; - DB01E23526A98F0900C3965B /* MetaTextKit in Frameworks */ = {isa = PBXBuildFile; productRef = DB01E23426A98F0900C3965B /* MetaTextKit */; }; DB023D26279FFB0A005AC798 /* ShareActivityProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB023D25279FFB0A005AC798 /* ShareActivityProvider.swift */; }; DB023D2827A0FABD005AC798 /* NotificationTableViewCellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB023D2727A0FABD005AC798 /* NotificationTableViewCellDelegate.swift */; }; DB023D2A27A0FE5C005AC798 /* DataSourceProvider+NotificationTableViewCellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB023D2927A0FE5C005AC798 /* DataSourceProvider+NotificationTableViewCellDelegate.swift */; }; @@ -131,6 +122,7 @@ DB029E95266A20430062874E /* MastodonAuthenticationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB029E94266A20430062874E /* MastodonAuthenticationController.swift */; }; DB02CDAB26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */; }; DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */; }; + DB02EA0B280D180D00E751C5 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = DB02EA0A280D180D00E751C5 /* KeychainAccess */; }; DB03A793272A7E5700EE37C5 /* SidebarListHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03A792272A7E5700EE37C5 /* SidebarListHeaderView.swift */; }; DB03A795272A981400EE37C5 /* ContentSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03A794272A981400EE37C5 /* ContentSplitViewController.swift */; }; DB03F7F32689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7F22689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift */; }; @@ -146,11 +138,9 @@ DB0618012785732C0030EE79 /* ServerRulesTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0618002785732C0030EE79 /* ServerRulesTableViewCell.swift */; }; DB0618032785A7100030EE79 /* RegisterSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0618022785A7100030EE79 /* RegisterSection.swift */; }; DB0618052785A73D0030EE79 /* RegisterItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0618042785A73D0030EE79 /* RegisterItem.swift */; }; - DB0618072785A8880030EE79 /* MastodonRegisterViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0618062785A8880030EE79 /* MastodonRegisterViewModel+Diffable.swift */; }; - DB06180A2785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0618092785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift */; }; DB084B5725CBC56C00F898ED /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB084B5625CBC56C00F898ED /* Status.swift */; }; + DB0A322E280EE9FD001729D2 /* DiscoveryIntroBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0A322D280EE9FD001729D2 /* DiscoveryIntroBannerView.swift */; }; DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */; }; - DB0C946526A6FD4D0088FB11 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB0C946426A6FD4D0088FB11 /* AlamofireImage */; }; DB0C947726A7FE840088FB11 /* NotificationAvatarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C947626A7FE840088FB11 /* NotificationAvatarButton.swift */; }; DB0EF72B26FDB1D200347686 /* SidebarListCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0EF72A26FDB1D200347686 /* SidebarListCollectionViewCell.swift */; }; DB0EF72E26FDB24F00347686 /* SidebarListContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0EF72D26FDB24F00347686 /* SidebarListContentView.swift */; }; @@ -182,7 +172,6 @@ DB0FCB9C27980AB6006C02E2 /* HashtagTimelineViewController+DataSourceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0FCB9B27980AB6006C02E2 /* HashtagTimelineViewController+DataSourceProvider.swift */; }; DB118A8225E4B6E600FAB162 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */; }; DB159C2B27A17BAC0068DC77 /* DataSourceFacade+Media.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB159C2A27A17BAC0068DC77 /* DataSourceFacade+Media.swift */; }; - DB179267278D5A4A00B71DEB /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = DB179266278D5A4A00B71DEB /* MastodonSDK */; }; DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */; }; DB1D61CF26F1B33600DA8662 /* WelcomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D61CE26F1B33600DA8662 /* WelcomeViewModel.swift */; }; DB1D842E26552C4D000346B3 /* StatusTableViewControllerNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D842D26552C4D000346B3 /* StatusTableViewControllerNavigateable.swift */; }; @@ -215,14 +204,45 @@ DB336F3F278E668C0031E64B /* StatusTableViewCell+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB336F3E278E668C0031E64B /* StatusTableViewCell+ViewModel.swift */; }; DB336F41278E68480031E64B /* StatusView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB336F40278E68480031E64B /* StatusView+Configuration.swift */; }; DB336F43278EB1690031E64B /* MediaView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB336F42278EB1680031E64B /* MediaView+Configuration.swift */; }; - DB35FC1F2612F1D9006193C9 /* ProfileRelationshipActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB35FC1E2612F1D9006193C9 /* ProfileRelationshipActionButton.swift */; }; DB36679D268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB36679C268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift */; }; DB36679F268ABAF20027D07F /* ComposeStatusAttachmentSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB36679E268ABAF20027D07F /* ComposeStatusAttachmentSection.swift */; }; DB3667A1268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667A0268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift */; }; DB3667A4268AE2370027D07F /* ComposeStatusPollTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667A3268AE2370027D07F /* ComposeStatusPollTableViewCell.swift */; }; DB3667A6268AE2620027D07F /* ComposeStatusPollSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667A5268AE2620027D07F /* ComposeStatusPollSection.swift */; }; DB3667A8268AE2900027D07F /* ComposeStatusPollItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667A7268AE2900027D07F /* ComposeStatusPollItem.swift */; }; - DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB3D0FF225BAA61700EAA174 /* AlamofireImage */; }; + DB3E6FDD2806A40F00B035AE /* DiscoveryHashtagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FDC2806A40F00B035AE /* DiscoveryHashtagsViewController.swift */; }; + DB3E6FE02806A4ED00B035AE /* DiscoveryHashtagsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FDF2806A4ED00B035AE /* DiscoveryHashtagsViewModel.swift */; }; + DB3E6FE22806A50100B035AE /* DiscoveryHashtagsViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FE12806A50100B035AE /* DiscoveryHashtagsViewModel+Diffable.swift */; }; + DB3E6FE42806A5B800B035AE /* DiscoverySection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FE32806A5B800B035AE /* DiscoverySection.swift */; }; + DB3E6FE72806A7A200B035AE /* DiscoveryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FE62806A7A200B035AE /* DiscoveryItem.swift */; }; + DB3E6FE92806BD2200B035AE /* ThemeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FE82806BD2200B035AE /* ThemeService.swift */; }; + DB3E6FEC2806D7F100B035AE /* DiscoveryNewsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FEB2806D7F100B035AE /* DiscoveryNewsViewController.swift */; }; + DB3E6FEF2806D82600B035AE /* DiscoveryNewsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FEE2806D82600B035AE /* DiscoveryNewsViewModel.swift */; }; + DB3E6FF12806D96900B035AE /* DiscoveryNewsViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FF02806D96900B035AE /* DiscoveryNewsViewModel+Diffable.swift */; }; + DB3E6FF32806D97400B035AE /* DiscoveryNewsViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FF22806D97400B035AE /* DiscoveryNewsViewModel+State.swift */; }; + DB3E6FF52807C40300B035AE /* DiscoveryForYouViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FF42807C40300B035AE /* DiscoveryForYouViewController.swift */; }; + DB3E6FF82807C45300B035AE /* DiscoveryForYouViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FF72807C45300B035AE /* DiscoveryForYouViewModel.swift */; }; + DB3E6FFA2807C47900B035AE /* DiscoveryForYouViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FF92807C47900B035AE /* DiscoveryForYouViewModel+Diffable.swift */; }; + DB3EA8E6281B79E200598866 /* DiscoveryCommunityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3EA8E5281B79E200598866 /* DiscoveryCommunityViewController.swift */; }; + DB3EA8E9281B7A3700598866 /* DiscoveryCommunityViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3EA8E8281B7A3700598866 /* DiscoveryCommunityViewModel.swift */; }; + DB3EA8EB281B7E0700598866 /* DiscoveryCommunityViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3EA8EA281B7E0700598866 /* DiscoveryCommunityViewModel+State.swift */; }; + DB3EA8ED281B810100598866 /* APIService+PublicTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3EA8EC281B810100598866 /* APIService+PublicTimeline.swift */; }; + DB3EA8EF281B837000598866 /* DiscoveryCommunityViewController+DataSourceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3EA8EE281B837000598866 /* DiscoveryCommunityViewController+DataSourceProvider.swift */; }; + DB3EA8F1281B9EF600598866 /* DiscoveryCommunityViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3EA8F0281B9EF600598866 /* DiscoveryCommunityViewModel+Diffable.swift */; }; + DB3EA8F5281BB65200598866 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = DB3EA8F4281BB65200598866 /* MastodonSDK */; }; + DB3EA8FC281BBAE100598866 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB3EA8FB281BBAE100598866 /* AlamofireImage */; }; + DB3EA8FE281BBAF200598866 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = DB3EA8FD281BBAF200598866 /* Alamofire */; }; + DB3EA900281BBB1D00598866 /* MetaTextKit in Frameworks */ = {isa = PBXBuildFile; productRef = DB3EA8FF281BBB1D00598866 /* MetaTextKit */; }; + DB3EA902281BBD5D00598866 /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB3EA901281BBD5D00598866 /* CommonOSLog */; }; + DB3EA904281BBD9400598866 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = DB3EA903281BBD9400598866 /* Introspect */; }; + DB3EA906281BBE8200598866 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB3EA905281BBE8200598866 /* AlamofireImage */; }; + DB3EA908281BBE8200598866 /* AlamofireNetworkActivityIndicator in Frameworks */ = {isa = PBXBuildFile; productRef = DB3EA907281BBE8200598866 /* AlamofireNetworkActivityIndicator */; }; + DB3EA90A281BBE8200598866 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = DB3EA909281BBE8200598866 /* Alamofire */; }; + DB3EA90C281BBE9600598866 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB3EA90B281BBE9600598866 /* AlamofireImage */; }; + DB3EA90E281BBE9600598866 /* AlamofireNetworkActivityIndicator in Frameworks */ = {isa = PBXBuildFile; productRef = DB3EA90D281BBE9600598866 /* AlamofireNetworkActivityIndicator */; }; + DB3EA910281BBE9600598866 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = DB3EA90F281BBE9600598866 /* Alamofire */; }; + DB3EA912281BBEA800598866 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB3EA911281BBEA800598866 /* AlamofireImage */; }; + DB3EA914281BBEA800598866 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = DB3EA913281BBEA800598866 /* Alamofire */; }; DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD525BAA00100D1B89D /* AppDelegate.swift */; }; DB427DD825BAA00100D1B89D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD725BAA00100D1B89D /* SceneDelegate.swift */; }; DB427DDD25BAA00100D1B89D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DB427DDB25BAA00100D1B89D /* Main.storyboard */; }; @@ -266,8 +286,6 @@ DB4F097F26A03DA600D62E92 /* SearchHistoryFetchedResultController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4F097E26A03DA600D62E92 /* SearchHistoryFetchedResultController.swift */; }; DB4FFC2B269EC39600D62E92 /* SearchToSearchDetailViewControllerAnimatedTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4FFC29269EC39600D62E92 /* SearchToSearchDetailViewControllerAnimatedTransitioning.swift */; }; DB4FFC2C269EC39600D62E92 /* SearchTransitionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4FFC2A269EC39600D62E92 /* SearchTransitionController.swift */; }; - DB51D172262832380062B7A1 /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB51D170262832380062B7A1 /* BlurHashDecode.swift */; }; - DB51D173262832380062B7A1 /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB51D171262832380062B7A1 /* BlurHashEncode.swift */; }; DB552D4F26BBD10C00E481F6 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = DB552D4E26BBD10C00E481F6 /* OrderedCollections */; }; DB564BD3269F3B35001E39A7 /* StatusFilterService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB564BD2269F3B35001E39A7 /* StatusFilterService.swift */; }; DB59F10E25EF724F001F1DAB /* APIService+Poll.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F10D25EF724F001F1DAB /* APIService+Poll.swift */; }; @@ -334,9 +352,7 @@ DB6804832637CD4C00430867 /* AppShared.h in Headers */ = {isa = PBXBuildFile; fileRef = DB6804812637CD4C00430867 /* AppShared.h */; settings = {ATTRIBUTES = (Public, ); }; }; DB6804862637CD4C00430867 /* AppShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB68047F2637CD4C00430867 /* AppShared.framework */; }; DB6804872637CD4C00430867 /* AppShared.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DB68047F2637CD4C00430867 /* AppShared.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - DB6804D12637CE4700430867 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6804D02637CE4700430867 /* UserDefaults.swift */; }; DB6804FD2637CFEC00430867 /* AppSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6804FC2637CFEC00430867 /* AppSecret.swift */; }; - DB6805102637D0F800430867 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = DB68050F2637D0F800430867 /* KeychainAccess */; }; DB68586425E619B700F0A850 /* NSKeyValueObservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */; }; DB68A04A25E9027700CFDF14 /* AdaptiveStatusBarStyleNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68A04925E9027700CFDF14 /* AdaptiveStatusBarStyleNavigationController.swift */; }; DB68A05D25E9055900CFDF14 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DB68A05C25E9055900CFDF14 /* Settings.bundle */; }; @@ -361,10 +377,8 @@ DB6B7500272FF73800C70B6E /* UserTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B74FF272FF73800C70B6E /* UserTableViewCell.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 */; }; DB6D9F3526351B7A008423CD /* NotificationService+Decrypt.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F3426351B7A008423CD /* NotificationService+Decrypt.swift */; }; - DB6D9F42263527CE008423CD /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB6D9F41263527CE008423CD /* AlamofireImage */; }; DB6D9F4926353FD7008423CD /* Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F4826353FD6008423CD /* Subscription.swift */; }; DB6D9F502635761F008423CD /* SubscriptionAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F4F2635761F008423CD /* SubscriptionAlerts.swift */; }; DB6D9F57263577D2008423CD /* APIService+CoreData+Setting.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F56263577D2008423CD /* APIService+CoreData+Setting.swift */; }; @@ -376,8 +390,6 @@ DB6D9F9726367249008423CD /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F9626367249008423CD /* SettingsViewController.swift */; }; DB6F5E35264E78E7009108F4 /* AutoCompleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6F5E34264E78E7009108F4 /* AutoCompleteViewController.swift */; }; DB6F5E38264E994A009108F4 /* AutoCompleteTopChevronView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6F5E37264E994A009108F4 /* AutoCompleteTopChevronView.swift */; }; - DB71C7CB271D5A0300BE3819 /* LineChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB71C7CA271D5A0300BE3819 /* LineChartView.swift */; }; - DB71C7CD271D7F4300BE3819 /* CurveAlgorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB71C7CC271D7F4300BE3819 /* CurveAlgorithm.swift */; }; DB71FD5225F8CCAA00512AE1 /* APIService+Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB71FD5125F8CCAA00512AE1 /* APIService+Status.swift */; }; DB72601C25E36A2100235243 /* MastodonServerRulesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */; }; DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */; }; @@ -392,16 +404,16 @@ DB75BF1E263C1C1B00EDBF1F /* CustomScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB75BF1D263C1C1B00EDBF1F /* CustomScheduler.swift */; }; DB789A0B25F9F2950071ACA0 /* ComposeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB789A0A25F9F2950071ACA0 /* ComposeViewController.swift */; }; DB789A1225F9F2CC0071ACA0 /* ComposeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */; }; + DB7A9F912818EAF10016AF98 /* MastodonRegisterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7A9F902818EAF10016AF98 /* MastodonRegisterView.swift */; }; + DB7A9F932818F33C0016AF98 /* MastodonServerRulesViewController+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7A9F922818F33C0016AF98 /* MastodonServerRulesViewController+Debug.swift */; }; DB7F48452620241000796008 /* ProfileHeaderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7F48442620241000796008 /* ProfileHeaderViewModel.swift */; }; DB8190C62601FF0400020C08 /* AttachmentContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */; }; - DB8481152788121200BBEABA /* MastodonRegisterTextFieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8481142788121200BBEABA /* MastodonRegisterTextFieldTableViewCell.swift */; }; - DB84811727883C2600BBEABA /* MastodonRegisterPasswordHintTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB84811627883C2600BBEABA /* MastodonRegisterPasswordHintTableViewCell.swift */; }; + DB848E33282B62A800A302CC /* ReportResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB848E32282B62A800A302CC /* ReportResultView.swift */; }; DB852D1926FAEB6B00FC9D81 /* SidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB852D1826FAEB6B00FC9D81 /* SidebarViewController.swift */; }; DB852D1C26FB021500FC9D81 /* RootSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB852D1B26FB021500FC9D81 /* RootSplitViewController.swift */; }; DB852D1F26FB037800FC9D81 /* SidebarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB852D1E26FB037800FC9D81 /* SidebarViewModel.swift */; }; DB87D4452609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB87D4442609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift */; }; DB87D4512609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB87D4502609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift */; }; - DB894CC427A5490600684B74 /* BlurhashImageCacheService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB894CC327A5490600684B74 /* BlurhashImageCacheService.swift */; }; DB8AF52E25C13561002E6C99 /* ViewStateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF52B25C13561002E6C99 /* ViewStateStore.swift */; }; DB8AF52F25C13561002E6C99 /* DocumentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF52C25C13561002E6C99 /* DocumentStore.swift */; }; DB8AF53025C13561002E6C99 /* AppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF52D25C13561002E6C99 /* AppContext.swift */; }; @@ -426,7 +438,7 @@ DB98337125C9443200AD9700 /* APIService+Authentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98337025C9443200AD9700 /* APIService+Authentication.swift */; }; DB98337F25C9452D00AD9700 /* APIService+APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98337E25C9452D00AD9700 /* APIService+APIError.swift */; }; DB98339C25C96DE600AD9700 /* APIService+Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98339B25C96DE600AD9700 /* APIService+Account.swift */; }; - DB98EB4727B0DFAA0082E365 /* ReportViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98EB4627B0DFAA0082E365 /* ReportViewModel+State.swift */; }; + DB98EB4727B0DFAA0082E365 /* ReportStatusViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98EB4627B0DFAA0082E365 /* ReportStatusViewModel+State.swift */; }; DB98EB4927B0F0CD0082E365 /* ReportStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98EB4827B0F0CD0082E365 /* ReportStatusTableViewCell.swift */; }; DB98EB4C27B0F2BC0082E365 /* ReportStatusTableViewCell+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98EB4B27B0F2BC0082E365 /* ReportStatusTableViewCell+ViewModel.swift */; }; DB98EB5327B0F9890082E365 /* ReportHeadlineTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98EB5227B0F9890082E365 /* ReportHeadlineTableViewCell.swift */; }; @@ -437,11 +449,9 @@ DB98EB6027B10E150082E365 /* ReportCommentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98EB5F27B10E150082E365 /* ReportCommentTableViewCell.swift */; }; DB98EB6227B215EB0082E365 /* ReportResultViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98EB6127B215EB0082E365 /* ReportResultViewController.swift */; }; DB98EB6527B216500082E365 /* ReportResultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98EB6427B216500082E365 /* ReportResultViewModel.swift */; }; - DB98EB6727B216560082E365 /* ReportResultViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98EB6627B216560082E365 /* ReportResultViewModel+Diffable.swift */; }; DB98EB6927B21A7C0082E365 /* ReportResultActionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98EB6827B21A7C0082E365 /* ReportResultActionTableViewCell.swift */; }; DB98EB6B27B243470082E365 /* SettingsAppearanceTableViewCell+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98EB6A27B243470082E365 /* SettingsAppearanceTableViewCell+ViewModel.swift */; }; DB9A486C26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */; }; - DB9A487E2603456B008B817C /* UITextView+Placeholder in Frameworks */ = {isa = PBXBuildFile; productRef = DB9A487D2603456B008B817C /* UITextView+Placeholder */; }; DB9A488A26034D40008B817C /* ComposeViewModel+PublishState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A488926034D40008B817C /* ComposeViewModel+PublishState.swift */; }; DB9A489026035963008B817C /* APIService+Media.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A488F26035963008B817C /* APIService+Media.swift */; }; DB9A48962603685D008B817C /* MastodonAttachmentService+UploadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A48952603685D008B817C /* MastodonAttachmentService+UploadState.swift */; }; @@ -495,32 +505,16 @@ DBB525562611EDCA002F1F29 /* UserTimelineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB525552611EDCA002F1F29 /* UserTimelineViewModel.swift */; }; DBB5255E2611F07A002F1F29 /* ProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB5255D2611F07A002F1F29 /* ProfileViewModel.swift */; }; DBB525642612C988002F1F29 /* MeProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB525632612C988002F1F29 /* MeProfileViewModel.swift */; }; - DBB5256E2612D5A1002F1F29 /* ProfileStatusDashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB5256D2612D5A1002F1F29 /* ProfileStatusDashboardView.swift */; }; - DBB525852612D6DD002F1F29 /* ProfileStatusDashboardMeterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB525842612D6DD002F1F29 /* ProfileStatusDashboardMeterView.swift */; }; DBB8AB4626AECDE200F6D281 /* SendPostIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB8AB4526AECDE200F6D281 /* SendPostIntentHandler.swift */; }; - DBB8AB4826AED09C00F6D281 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = DBB8AB4726AED09C00F6D281 /* MastodonSDK */; }; DBB8AB4A26AED0B500F6D281 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB8AB4926AED0B500F6D281 /* APIService.swift */; }; DBB8AB4C26AED11300F6D281 /* APIService+APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98337E25C9452D00AD9700 /* APIService+APIError.swift */; }; DBB8AB4F26AED13F00F6D281 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB427DDE25BAA00100D1B89D /* Assets.xcassets */; }; DBB8AB5226AED1B300F6D281 /* APIService+Status+Publish.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF07A26A6BCE8006D7ED1 /* APIService+Status+Publish.swift */; }; DBB9759C262462E1004620BD /* ThreadMetaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB9759B262462E1004620BD /* ThreadMetaView.swift */; }; DBBC24A826A52F9000398BB9 /* ComposeToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24A726A52F9000398BB9 /* ComposeToolbarView.swift */; }; - DBBC24AA26A5301B00398BB9 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24A926A5301B00398BB9 /* MastodonSDK */; }; DBBC24AC26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24AB26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift */; }; - DBBC24B826A5421800398BB9 /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24B726A5421800398BB9 /* CommonOSLog */; }; - DBBC24BC26A542F500398BB9 /* ThemeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BB26A542F500398BB9 /* ThemeService.swift */; }; - DBBC24C026A5443100398BB9 /* MastodonTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BE26A5443100398BB9 /* MastodonTheme.swift */; }; - DBBC24C126A5443100398BB9 /* SystemTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BF26A5443100398BB9 /* SystemTheme.swift */; }; - DBBC24C426A544B900398BB9 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24C326A544B900398BB9 /* Theme.swift */; }; - DBBC24C626A5456000398BB9 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24C326A544B900398BB9 /* Theme.swift */; }; - DBBC24C726A5456400398BB9 /* SystemTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BF26A5443100398BB9 /* SystemTheme.swift */; }; - DBBC24C826A5456400398BB9 /* ThemeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BB26A542F500398BB9 /* ThemeService.swift */; }; - DBBC24C926A5456400398BB9 /* MastodonTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BE26A5443100398BB9 /* MastodonTheme.swift */; }; DBBC24CB26A546C000398BB9 /* ThemePreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD376AB2692ECDB007FEC24 /* ThemePreference.swift */; }; - DBBC24CF26A547AE00398BB9 /* ThemeService+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24CE26A547AE00398BB9 /* ThemeService+Appearance.swift */; }; - DBBC24D126A5484F00398BB9 /* UITextView+Placeholder in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24D026A5484F00398BB9 /* UITextView+Placeholder */; }; DBBC24DC26A54BCB00398BB9 /* MastodonRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */; }; - DBBC24DE26A54BCB00398BB9 /* MastodonMetricFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */; }; DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */; }; DBBF1DBF2652401B00E5B703 /* AutoCompleteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DBE2652401B00E5B703 /* AutoCompleteViewModel.swift */; }; DBBF1DC226524D2900E5B703 /* AutoCompleteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DC126524D2900E5B703 /* AutoCompleteTableViewCell.swift */; }; @@ -535,12 +529,12 @@ DBC6462826A1736300B0E31B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB427DDE25BAA00100D1B89D /* Assets.xcassets */; }; DBC7A672260C897100E57475 /* StatusContentWarningEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC7A671260C897100E57475 /* StatusContentWarningEditorView.swift */; }; DBC7A67C260DFADE00E57475 /* StatusPublishService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC7A67B260DFADE00E57475 /* StatusPublishService.swift */; }; + DBCA0EBC282BB38A0029E2B0 /* PageboyNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCA0EBB282BB38A0029E2B0 /* PageboyNavigateable.swift */; }; DBCBCBF4267CB070000F5B51 /* Decode85.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBCBF3267CB070000F5B51 /* Decode85.swift */; }; DBCBCC0D2680B908000F5B51 /* HomeTimelinePreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBCC0C2680B908000F5B51 /* HomeTimelinePreference.swift */; }; DBCBED1726132DB500B49291 /* UserTimelineViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBED1626132DB500B49291 /* UserTimelineViewModel+Diffable.swift */; }; DBCBED1D26132E1A00B49291 /* StatusFetchedResultsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBED1C26132E1A00B49291 /* StatusFetchedResultsController.swift */; }; DBCC3B30261440A50045B23D /* UITabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCC3B2F261440A50045B23D /* UITabBarController.swift */; }; - DBCC3B36261440BA0045B23D /* UINavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCC3B35261440BA0045B23D /* UINavigationController.swift */; }; DBCC3B8F26148F7B0045B23D /* CachedProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCC3B8E26148F7B0045B23D /* CachedProfileViewModel.swift */; }; DBCC3B9526157E6E0045B23D /* APIService+Relationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCC3B9426157E6E0045B23D /* APIService+Relationship.swift */; }; DBCCC71E25F73297007E1AB6 /* APIService+Reblog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCCC71D25F73297007E1AB6 /* APIService+Reblog.swift */; }; @@ -550,6 +544,13 @@ DBD5B1F827BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD5B1F727BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift */; }; DBD5B1FA27BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD5B1F927BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift */; }; DBD9149025DF6D8D00903DFD /* APIService+Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD9148F25DF6D8D00903DFD /* APIService+Onboarding.swift */; }; + DBDFF1902805543100557A48 /* DiscoveryPostsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDFF18F2805543100557A48 /* DiscoveryPostsViewController.swift */; }; + DBDFF1932805554900557A48 /* DiscoveryPostsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDFF1922805554900557A48 /* DiscoveryPostsViewModel.swift */; }; + DBDFF1952805561700557A48 /* DiscoveryPostsViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDFF1942805561700557A48 /* DiscoveryPostsViewModel+Diffable.swift */; }; + DBDFF197280556D900557A48 /* DiscoveryPostsViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDFF196280556D900557A48 /* DiscoveryPostsViewModel+State.swift */; }; + DBDFF19A28055A1400557A48 /* DiscoveryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDFF19928055A1400557A48 /* DiscoveryViewController.swift */; }; + DBDFF19C28055BD600557A48 /* DiscoveryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDFF19B28055BD600557A48 /* DiscoveryViewModel.swift */; }; + DBDFF19E2805703700557A48 /* DiscoveryPostsViewController+DataSourceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDFF19D2805703700557A48 /* DiscoveryPostsViewController+DataSourceProvider.swift */; }; DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE0821425CD382600FD6BBD /* MastodonRegisterViewController.swift */; }; DBE0822425CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE0822325CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift */; }; DBE3CA6827A39CAB00AFE27B /* AppShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB68047F2637CD4C00430867 /* AppShared.framework */; }; @@ -563,6 +564,14 @@ DBE3CE07261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE06261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift */; }; DBE54AC62636C89F004E7C0B /* NotificationPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */; }; DBE54ACC2636C8FD004E7C0B /* NotificationPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */; }; + DBEFCD71282A12B200C0ABEA /* ReportReasonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBEFCD70282A12B200C0ABEA /* ReportReasonViewController.swift */; }; + DBEFCD74282A130400C0ABEA /* ReportReasonViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBEFCD73282A130400C0ABEA /* ReportReasonViewModel.swift */; }; + DBEFCD76282A143F00C0ABEA /* ReportStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBEFCD75282A143F00C0ABEA /* ReportStatusViewController.swift */; }; + DBEFCD79282A147000C0ABEA /* ReportStatusViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBEFCD78282A147000C0ABEA /* ReportStatusViewModel.swift */; }; + DBEFCD7B282A162400C0ABEA /* ReportReasonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBEFCD7A282A162400C0ABEA /* ReportReasonView.swift */; }; + DBEFCD7D282A2A3B00C0ABEA /* ReportServerRulesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBEFCD7C282A2A3B00C0ABEA /* ReportServerRulesViewController.swift */; }; + DBEFCD80282A2AA900C0ABEA /* ReportServerRulesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBEFCD7F282A2AA900C0ABEA /* ReportServerRulesViewModel.swift */; }; + DBEFCD82282A2AB100C0ABEA /* ReportServerRulesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBEFCD81282A2AB100C0ABEA /* ReportServerRulesView.swift */; }; DBF156DF2701B17600EC00B7 /* SidebarAddAccountCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF156DE2701B17600EC00B7 /* SidebarAddAccountCollectionViewCell.swift */; }; DBF156E22702DA6900EC00B7 /* UIStatusBarManager+HandleTapAction.m in Sources */ = {isa = PBXBuildFile; fileRef = DBF156E12702DA6900EC00B7 /* UIStatusBarManager+HandleTapAction.m */; }; DBF156E42702DB3F00EC00B7 /* HandleTapAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF156E32702DB3F00EC00B7 /* HandleTapAction.swift */; }; @@ -589,7 +598,6 @@ DBFEF05F26A57715006D7ED1 /* StatusAuthorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF05926A576EE006D7ED1 /* StatusAuthorView.swift */; }; DBFEF06026A57715006D7ED1 /* StatusAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF05A26A576EE006D7ED1 /* StatusAttachmentView.swift */; }; DBFEF06326A577F2006D7ED1 /* StatusAttachmentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF06226A577F2006D7ED1 /* StatusAttachmentViewModel.swift */; }; - DBFEF06926A67E45006D7ED1 /* AppearancePreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B3C2636857500ACB481 /* AppearancePreference.swift */; }; DBFEF06D26A67FB7006D7ED1 /* StatusAttachmentViewModel+UploadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF06C26A67FB7006D7ED1 /* StatusAttachmentViewModel+UploadState.swift */; }; DBFEF06F26A690C4006D7ED1 /* APIService+APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98337E25C9452D00AD9700 /* APIService+APIError.swift */; }; DBFEF07326A6913D006D7ED1 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF07226A6913D006D7ED1 /* APIService.swift */; }; @@ -718,17 +726,14 @@ 0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCategoryView.swift; sourceTree = ""; }; 0FB3D31D25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCategoryCollectionViewCell.swift; sourceTree = ""; }; 0FB3D33725E6401400AAD544 /* PickServerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCell.swift; sourceTree = ""; }; - 159AC43EFE0A1F95FCB358A4 /* Pods-MastodonIntent.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonIntent.release.xcconfig"; path = "Target Support Files/Pods-MastodonIntent/Pods-MastodonIntent.release.xcconfig"; sourceTree = ""; }; 164F0EBB267D4FE400249499 /* BoopSound.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = BoopSound.caf; sourceTree = ""; }; 1D6D967E77A5357E2C6110D9 /* Pods-Mastodon.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.asdk - debug.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.asdk - debug.xcconfig"; sourceTree = ""; }; - 2C12EB4B3699D5D597027962 /* Pods-MastodonIntent.release snapshot.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonIntent.release snapshot.xcconfig"; path = "Target Support Files/Pods-MastodonIntent/Pods-MastodonIntent.release snapshot.xcconfig"; sourceTree = ""; }; 2D198642261BF09500F0B013 /* SearchResultItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultItem.swift; sourceTree = ""; }; 2D198648261C0B8500F0B013 /* SearchResultSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultSection.swift; sourceTree = ""; }; 2D206B8525F5FB0900143C56 /* Double.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Double.swift; sourceTree = ""; }; 2D206B9125F60EA700143C56 /* UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControl.swift; sourceTree = ""; }; 2D24E1222626ED9D00A59D4F /* UIView+Gesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Gesture.swift"; sourceTree = ""; }; 2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMiddleLoaderTableViewCell.swift; sourceTree = ""; }; - 2D32EAB925CB9B0500C9ED86 /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = ""; }; 2D34D9D026148D9E0081BFC0 /* APIService+Recommend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Recommend.swift"; sourceTree = ""; }; 2D34D9DA261494120081BFC0 /* APIService+Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Search.swift"; sourceTree = ""; }; 2D35237926256D920031AF25 /* NotificationSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSection.swift; sourceTree = ""; }; @@ -783,7 +788,6 @@ 2DF123A625C3B0210020F248 /* ActiveLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveLabel.swift; sourceTree = ""; }; 2DF75BA625D10E1000694EC8 /* APIService+Favorite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Favorite.swift"; sourceTree = ""; }; 2E1F6A67FDF9771D3E064FDC /* Pods-Mastodon.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.debug.xcconfig"; sourceTree = ""; }; - 374AA339A20E0FAC75BCDA6D /* Pods_NotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3B7FD8F28DDA8FBCE5562B78 /* Pods-NotificationService.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.asdk - debug.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.asdk - debug.xcconfig"; sourceTree = ""; }; 3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Mastodon_MastodonUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3E08A432F40BA7B9CAA9DB68 /* Pods-AppShared.release snapshot.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppShared.release snapshot.xcconfig"; path = "Target Support Files/Pods-AppShared/Pods-AppShared.release snapshot.xcconfig"; sourceTree = ""; }; @@ -791,7 +795,7 @@ 459EA4F43058CAB47719E963 /* Pods-Mastodon-MastodonUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.debug.xcconfig"; sourceTree = ""; }; 46DAB0EBDDFB678347CD96FF /* Pods-MastodonTests.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.asdk - release.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.asdk - release.xcconfig"; sourceTree = ""; }; 5B24BBD7262DB14800A9381B /* ReportViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReportViewModel.swift; sourceTree = ""; }; - 5B24BBD8262DB14800A9381B /* ReportViewModel+Diffable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ReportViewModel+Diffable.swift"; sourceTree = ""; }; + 5B24BBD8262DB14800A9381B /* ReportStatusViewModel+Diffable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ReportStatusViewModel+Diffable.swift"; sourceTree = ""; }; 5B24BBE1262DB19100A9381B /* APIService+Report.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "APIService+Report.swift"; sourceTree = ""; }; 5B90C456262599800002E742 /* SettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; 5B90C459262599800002E742 /* SettingsToggleTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsToggleTableViewCell.swift; sourceTree = ""; }; @@ -812,22 +816,18 @@ 5DDDF1A82617489F00311060 /* Mastodon+Entity+History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+History.swift"; sourceTree = ""; }; 5DF1056325F887CB00D6C0D4 /* AVPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayer.swift; sourceTree = ""; }; 6130CBE4B26E3C976ACC1688 /* Pods-ShareActionExtension.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareActionExtension.asdk - debug.xcconfig"; path = "Target Support Files/Pods-ShareActionExtension/Pods-ShareActionExtension.asdk - debug.xcconfig"; sourceTree = ""; }; + 63EF9E6E5B575CD2A8B0475D /* Pods-AppShared.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppShared.profile.xcconfig"; path = "Target Support Files/Pods-AppShared/Pods-AppShared.profile.xcconfig"; sourceTree = ""; }; + 728DE51ADA27C395C6E1BAB5 /* Pods-Mastodon-MastodonUITests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.profile.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.profile.xcconfig"; sourceTree = ""; }; 75E3471C898DDD9631729B6E /* Pods-Mastodon.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.release.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.release.xcconfig"; sourceTree = ""; }; - 77EE917BC055E6621C0452B6 /* Pods-ShareActionExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareActionExtension.debug.xcconfig"; path = "Target Support Files/Pods-ShareActionExtension/Pods-ShareActionExtension.debug.xcconfig"; sourceTree = ""; }; + 7CB58D292DA7ACEF179A9050 /* Pods-Mastodon.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.profile.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.profile.xcconfig"; sourceTree = ""; }; 7CEFFAE9AF9284B13C0A758D /* Pods-MastodonTests.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.asdk - debug.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.asdk - debug.xcconfig"; sourceTree = ""; }; 819CEC9DCAD8E8E7BD85A7BB /* Pods-Mastodon.asdk.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.asdk.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.asdk.xcconfig"; sourceTree = ""; }; - 861BE60ED27430771CFD578D /* Pods-MastodonIntent.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonIntent.debug.xcconfig"; path = "Target Support Files/Pods-MastodonIntent/Pods-MastodonIntent.debug.xcconfig"; sourceTree = ""; }; 8850E70A1D5FF51432E43653 /* Pods-Mastodon-MastodonUITests.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.asdk - release.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.asdk - release.xcconfig"; sourceTree = ""; }; - 8ADD558BE5B8255E5764A54F /* Pods-NotificationService.release snapshot.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.release snapshot.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.release snapshot.xcconfig"; sourceTree = ""; }; 8E79CCBE51FBC3F7FE8CF49F /* Pods-MastodonTests.release snapshot.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.release snapshot.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.release snapshot.xcconfig"; sourceTree = ""; }; 8ED8C4B1F1BA2DCFF2926BB1 /* Pods-Mastodon-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-NotificationService/Pods-Mastodon-NotificationService.debug.xcconfig"; sourceTree = ""; }; - 9553C689FFA9EBC880CAB78D /* Pods-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.debug.xcconfig"; sourceTree = ""; }; - 95AD0663479892A2109EEFD0 /* Pods-ShareActionExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareActionExtension.release.xcconfig"; path = "Target Support Files/Pods-ShareActionExtension/Pods-ShareActionExtension.release.xcconfig"; sourceTree = ""; }; - 9776D7C4B79101CF70181127 /* Pods-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.release.xcconfig"; sourceTree = ""; }; 9780A4C98FFC65B32B50D1C0 /* Pods-MastodonTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.release.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.release.xcconfig"; sourceTree = ""; }; 9A0982D8F349244EB558CDFD /* Pods-AppShared.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppShared.debug.xcconfig"; path = "Target Support Files/Pods-AppShared/Pods-AppShared.debug.xcconfig"; sourceTree = ""; }; 9CFF58FD900AC059428700E7 /* Pods-NotificationService.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.asdk - release.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.asdk - release.xcconfig"; sourceTree = ""; }; - A32B0CACBF35F4CC3CFAA043 /* Pods_ShareActionExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ShareActionExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Mastodon.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A67FD038ECDA0E411AF8DB4D /* Pods-Mastodon-MastodonUITests.asdk.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.asdk.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.asdk.xcconfig"; sourceTree = ""; }; A9B1FB898DFD6063B044298C /* Pods-AppShared.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppShared.asdk - debug.xcconfig"; path = "Target Support Files/Pods-AppShared/Pods-AppShared.asdk - debug.xcconfig"; sourceTree = ""; }; @@ -870,6 +870,7 @@ DB0618062785A8880030EE79 /* MastodonRegisterViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonRegisterViewModel+Diffable.swift"; sourceTree = ""; }; DB0618092785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterAvatarTableViewCell.swift; sourceTree = ""; }; DB084B5625CBC56C00F898ED /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = ""; }; + DB0A322D280EE9FD001729D2 /* DiscoveryIntroBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryIntroBannerView.swift; sourceTree = ""; }; DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Instance.swift"; sourceTree = ""; }; DB0C947626A7FE840088FB11 /* NotificationAvatarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationAvatarButton.swift; sourceTree = ""; }; DB0EF72A26FDB1D200347686 /* SidebarListCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarListCollectionViewCell.swift; sourceTree = ""; }; @@ -941,7 +942,6 @@ DB336F3E278E668C0031E64B /* StatusTableViewCell+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusTableViewCell+ViewModel.swift"; sourceTree = ""; }; DB336F40278E68480031E64B /* StatusView+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusView+Configuration.swift"; sourceTree = ""; }; DB336F42278EB1680031E64B /* MediaView+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaView+Configuration.swift"; sourceTree = ""; }; - DB35FC1E2612F1D9006193C9 /* ProfileRelationshipActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileRelationshipActionButton.swift; sourceTree = ""; }; DB36679C268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusAttachmentTableViewCell.swift; sourceTree = ""; }; DB36679E268ABAF20027D07F /* ComposeStatusAttachmentSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusAttachmentSection.swift; sourceTree = ""; }; DB3667A0268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusAttachmentItem.swift; sourceTree = ""; }; @@ -949,6 +949,25 @@ DB3667A5268AE2620027D07F /* ComposeStatusPollSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollSection.swift; sourceTree = ""; }; DB3667A7268AE2900027D07F /* ComposeStatusPollItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollItem.swift; sourceTree = ""; }; DB3D0FED25BAA42200EAA174 /* MastodonSDK */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MastodonSDK; sourceTree = ""; }; + DB3E6FDC2806A40F00B035AE /* DiscoveryHashtagsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryHashtagsViewController.swift; sourceTree = ""; }; + DB3E6FDF2806A4ED00B035AE /* DiscoveryHashtagsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryHashtagsViewModel.swift; sourceTree = ""; }; + DB3E6FE12806A50100B035AE /* DiscoveryHashtagsViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiscoveryHashtagsViewModel+Diffable.swift"; sourceTree = ""; }; + DB3E6FE32806A5B800B035AE /* DiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoverySection.swift; sourceTree = ""; }; + DB3E6FE62806A7A200B035AE /* DiscoveryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryItem.swift; sourceTree = ""; }; + DB3E6FE82806BD2200B035AE /* ThemeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeService.swift; sourceTree = ""; }; + DB3E6FEB2806D7F100B035AE /* DiscoveryNewsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryNewsViewController.swift; sourceTree = ""; }; + DB3E6FEE2806D82600B035AE /* DiscoveryNewsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryNewsViewModel.swift; sourceTree = ""; }; + DB3E6FF02806D96900B035AE /* DiscoveryNewsViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiscoveryNewsViewModel+Diffable.swift"; sourceTree = ""; }; + DB3E6FF22806D97400B035AE /* DiscoveryNewsViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiscoveryNewsViewModel+State.swift"; sourceTree = ""; }; + DB3E6FF42807C40300B035AE /* DiscoveryForYouViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryForYouViewController.swift; sourceTree = ""; }; + DB3E6FF72807C45300B035AE /* DiscoveryForYouViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryForYouViewModel.swift; sourceTree = ""; }; + DB3E6FF92807C47900B035AE /* DiscoveryForYouViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiscoveryForYouViewModel+Diffable.swift"; sourceTree = ""; }; + DB3EA8E5281B79E200598866 /* DiscoveryCommunityViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryCommunityViewController.swift; sourceTree = ""; }; + DB3EA8E8281B7A3700598866 /* DiscoveryCommunityViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryCommunityViewModel.swift; sourceTree = ""; }; + DB3EA8EA281B7E0700598866 /* DiscoveryCommunityViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiscoveryCommunityViewModel+State.swift"; sourceTree = ""; }; + DB3EA8EC281B810100598866 /* APIService+PublicTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+PublicTimeline.swift"; sourceTree = ""; }; + DB3EA8EE281B837000598866 /* DiscoveryCommunityViewController+DataSourceProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiscoveryCommunityViewController+DataSourceProvider.swift"; sourceTree = ""; }; + DB3EA8F0281B9EF600598866 /* DiscoveryCommunityViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiscoveryCommunityViewModel+Diffable.swift"; sourceTree = ""; }; DB427DD225BAA00100D1B89D /* Mastodon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Mastodon.app; sourceTree = BUILT_PRODUCTS_DIR; }; DB427DD525BAA00100D1B89D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; DB427DD725BAA00100D1B89D /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -1016,8 +1035,21 @@ DB4F097E26A03DA600D62E92 /* SearchHistoryFetchedResultController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchHistoryFetchedResultController.swift; sourceTree = ""; }; DB4FFC29269EC39600D62E92 /* SearchToSearchDetailViewControllerAnimatedTransitioning.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchToSearchDetailViewControllerAnimatedTransitioning.swift; sourceTree = ""; }; DB4FFC2A269EC39600D62E92 /* SearchTransitionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchTransitionController.swift; sourceTree = ""; }; - DB51D170262832380062B7A1 /* BlurHashDecode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = ""; }; - DB51D171262832380062B7A1 /* BlurHashEncode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurHashEncode.swift; sourceTree = ""; }; + DB519B09281BCA2E00F0C99D /* ckb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ckb; path = ckb.lproj/Intents.strings; sourceTree = ""; }; + DB519B0A281BCA2E00F0C99D /* ckb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ckb; path = ckb.lproj/InfoPlist.strings; sourceTree = ""; }; + DB519B0B281BCA4300F0C99D /* ckb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ckb; path = ckb.lproj/Intents.stringsdict; sourceTree = ""; }; + DB519B0C281BCAC300F0C99D /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Intents.strings"; sourceTree = ""; }; + DB519B0D281BCAC300F0C99D /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/InfoPlist.strings"; sourceTree = ""; }; + DB519B0E281BCAC300F0C99D /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hant"; path = "zh-Hant.lproj/Intents.stringsdict"; sourceTree = ""; }; + DB519B0F281BCB3300F0C99D /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = gl.lproj/Intents.strings; sourceTree = ""; }; + DB519B10281BCB3300F0C99D /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = gl.lproj/InfoPlist.strings; sourceTree = ""; }; + DB519B11281BCB3400F0C99D /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = gl; path = gl.lproj/Intents.stringsdict; sourceTree = ""; }; + DB519B12281BCBC400F0C99D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Intents.strings; sourceTree = ""; }; + DB519B13281BCBC400F0C99D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; + DB519B14281BCBC400F0C99D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = it; path = it.lproj/Intents.stringsdict; sourceTree = ""; }; + DB519B15281BCC2F00F0C99D /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Intents.strings; sourceTree = ""; }; + DB519B16281BCC2F00F0C99D /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = ""; }; + DB519B17281BCC2F00F0C99D /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = tr; path = tr.lproj/Intents.stringsdict; sourceTree = ""; }; DB564BD2269F3B35001E39A7 /* StatusFilterService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusFilterService.swift; sourceTree = ""; }; DB59F10D25EF724F001F1DAB /* APIService+Poll.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Poll.swift"; sourceTree = ""; }; DB5B7294273112B100081888 /* FollowingListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowingListViewController.swift; sourceTree = ""; }; @@ -1078,7 +1110,6 @@ 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 = ""; }; DB6804822637CD4C00430867 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - DB6804D02637CE4700430867 /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = ""; }; DB6804FC2637CFEC00430867 /* AppSecret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSecret.swift; sourceTree = ""; }; DB68053E2638011000430867 /* NotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationService.entitlements; sourceTree = ""; }; DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSKeyValueObservation.swift; sourceTree = ""; }; @@ -1105,7 +1136,6 @@ DB6B74FF272FF73800C70B6E /* UserTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTableViewCell.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 = ""; }; DB6D9F3426351B7A008423CD /* NotificationService+Decrypt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationService+Decrypt.swift"; sourceTree = ""; }; DB6D9F4826353FD6008423CD /* Subscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = ""; }; @@ -1119,8 +1149,6 @@ DB6D9F9626367249008423CD /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; DB6F5E34264E78E7009108F4 /* AutoCompleteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteViewController.swift; sourceTree = ""; }; DB6F5E37264E994A009108F4 /* AutoCompleteTopChevronView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteTopChevronView.swift; sourceTree = ""; }; - DB71C7CA271D5A0300BE3819 /* LineChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChartView.swift; sourceTree = ""; }; - DB71C7CC271D7F4300BE3819 /* CurveAlgorithm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurveAlgorithm.swift; sourceTree = ""; }; DB71FD5125F8CCAA00512AE1 /* APIService+Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Status.swift"; sourceTree = ""; }; DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewController.swift; sourceTree = ""; }; DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewModel.swift; sourceTree = ""; }; @@ -1135,16 +1163,18 @@ DB75BF1D263C1C1B00EDBF1F /* CustomScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomScheduler.swift; sourceTree = ""; }; DB789A0A25F9F2950071ACA0 /* ComposeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewController.swift; sourceTree = ""; }; DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewModel.swift; sourceTree = ""; }; + DB7A9F902818EAF10016AF98 /* MastodonRegisterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterView.swift; sourceTree = ""; }; + DB7A9F922818F33C0016AF98 /* MastodonServerRulesViewController+Debug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonServerRulesViewController+Debug.swift"; sourceTree = ""; }; DB7F48442620241000796008 /* ProfileHeaderViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderViewModel.swift; sourceTree = ""; }; DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentContainerView.swift; sourceTree = ""; }; DB8481142788121200BBEABA /* MastodonRegisterTextFieldTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterTextFieldTableViewCell.swift; sourceTree = ""; }; DB84811627883C2600BBEABA /* MastodonRegisterPasswordHintTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterPasswordHintTableViewCell.swift; sourceTree = ""; }; + DB848E32282B62A800A302CC /* ReportResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportResultView.swift; sourceTree = ""; }; DB852D1826FAEB6B00FC9D81 /* SidebarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarViewController.swift; sourceTree = ""; }; DB852D1B26FB021500FC9D81 /* RootSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootSplitViewController.swift; sourceTree = ""; }; DB852D1E26FB037800FC9D81 /* SidebarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarViewModel.swift; sourceTree = ""; }; DB87D4442609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollOptionCollectionViewCell.swift; sourceTree = ""; }; DB87D4502609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollOptionAppendEntryCollectionViewCell.swift; sourceTree = ""; }; - DB894CC327A5490600684B74 /* BlurhashImageCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurhashImageCacheService.swift; sourceTree = ""; }; DB89BA1025C10FF5008580ED /* Mastodon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Mastodon.entitlements; sourceTree = ""; }; DB8AF52B25C13561002E6C99 /* ViewStateStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewStateStore.swift; sourceTree = ""; }; DB8AF52C25C13561002E6C99 /* DocumentStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentStore.swift; sourceTree = ""; }; @@ -1153,6 +1183,9 @@ DB8AF54325C13647002E6C99 /* NeedsDependency.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeedsDependency.swift; sourceTree = ""; }; DB8AF54F25C13703002E6C99 /* MainTabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainTabBarController.swift; sourceTree = ""; }; DB8AF55C25C138B7002E6C99 /* UIViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = ""; }; + DB8D8E3128196FA0009FD90F /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Intents.strings; sourceTree = ""; }; + DB8D8E3228196FA0009FD90F /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/InfoPlist.strings; sourceTree = ""; }; + DB8D8E3328196FA0009FD90F /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sv; path = sv.lproj/Intents.stringsdict; sourceTree = ""; }; DB8F7075279E954700E1225B /* DataSourceFacade+Follow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Follow.swift"; sourceTree = ""; }; DB8FAB9E26AEC3A2008E5AF4 /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; }; DB8FABA926AEC3A2008E5AF4 /* IntentsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IntentsUI.framework; path = System/Library/Frameworks/IntentsUI.framework; sourceTree = SDKROOT; }; @@ -1173,7 +1206,7 @@ DB98337025C9443200AD9700 /* APIService+Authentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Authentication.swift"; sourceTree = ""; }; DB98337E25C9452D00AD9700 /* APIService+APIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "APIService+APIError.swift"; sourceTree = ""; }; DB98339B25C96DE600AD9700 /* APIService+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Account.swift"; sourceTree = ""; }; - DB98EB4627B0DFAA0082E365 /* ReportViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReportViewModel+State.swift"; sourceTree = ""; }; + DB98EB4627B0DFAA0082E365 /* ReportStatusViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReportStatusViewModel+State.swift"; sourceTree = ""; }; DB98EB4827B0F0CD0082E365 /* ReportStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportStatusTableViewCell.swift; sourceTree = ""; }; DB98EB4B27B0F2BC0082E365 /* ReportStatusTableViewCell+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReportStatusTableViewCell+ViewModel.swift"; sourceTree = ""; }; DB98EB5227B0F9890082E365 /* ReportHeadlineTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportHeadlineTableViewCell.swift; sourceTree = ""; }; @@ -1184,7 +1217,6 @@ DB98EB5F27B10E150082E365 /* ReportCommentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportCommentTableViewCell.swift; sourceTree = ""; }; DB98EB6127B215EB0082E365 /* ReportResultViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportResultViewController.swift; sourceTree = ""; }; DB98EB6427B216500082E365 /* ReportResultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportResultViewModel.swift; sourceTree = ""; }; - DB98EB6627B216560082E365 /* ReportResultViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReportResultViewModel+Diffable.swift"; sourceTree = ""; }; DB98EB6827B21A7C0082E365 /* ReportResultActionTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportResultActionTableViewCell.swift; sourceTree = ""; }; DB98EB6A27B243470082E365 /* SettingsAppearanceTableViewCell+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsAppearanceTableViewCell+ViewModel.swift"; sourceTree = ""; }; DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttachmentContainerView+EmptyStateView.swift"; sourceTree = ""; }; @@ -1249,20 +1281,12 @@ DBB525552611EDCA002F1F29 /* UserTimelineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTimelineViewModel.swift; sourceTree = ""; }; DBB5255D2611F07A002F1F29 /* ProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewModel.swift; sourceTree = ""; }; DBB525632612C988002F1F29 /* MeProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeProfileViewModel.swift; sourceTree = ""; }; - DBB5256D2612D5A1002F1F29 /* ProfileStatusDashboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileStatusDashboardView.swift; sourceTree = ""; }; - DBB525842612D6DD002F1F29 /* ProfileStatusDashboardMeterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileStatusDashboardMeterView.swift; sourceTree = ""; }; DBB8AB4526AECDE200F6D281 /* SendPostIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendPostIntentHandler.swift; sourceTree = ""; }; DBB8AB4926AED0B500F6D281 /* APIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; }; DBB9759B262462E1004620BD /* ThreadMetaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadMetaView.swift; sourceTree = ""; }; DBBC24A726A52F9000398BB9 /* ComposeToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeToolbarView.swift; sourceTree = ""; }; DBBC24AB26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusContentTableViewCell.swift; sourceTree = ""; }; - DBBC24BB26A542F500398BB9 /* ThemeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeService.swift; sourceTree = ""; }; - DBBC24BE26A5443100398BB9 /* MastodonTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonTheme.swift; sourceTree = ""; }; - DBBC24BF26A5443100398BB9 /* SystemTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemTheme.swift; sourceTree = ""; }; - DBBC24C326A544B900398BB9 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; - DBBC24CE26A547AE00398BB9 /* ThemeService+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThemeService+Appearance.swift"; sourceTree = ""; }; DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonRegex.swift; sourceTree = ""; }; - DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonMetricFormatter.swift; sourceTree = ""; }; DBBC50C0278ED49200AF0CC6 /* MastodonAuthenticationBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonAuthenticationBox.swift; sourceTree = ""; }; DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerAppearance.swift; sourceTree = ""; }; DBBF1DBE2652401B00E5B703 /* AutoCompleteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteViewModel.swift; sourceTree = ""; }; @@ -1278,12 +1302,12 @@ DBC6462226A1712000B0E31B /* ShareViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShareViewModel.swift; sourceTree = ""; }; DBC7A671260C897100E57475 /* StatusContentWarningEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentWarningEditorView.swift; sourceTree = ""; }; DBC7A67B260DFADE00E57475 /* StatusPublishService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusPublishService.swift; sourceTree = ""; }; + DBCA0EBB282BB38A0029E2B0 /* PageboyNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageboyNavigateable.swift; sourceTree = ""; }; DBCBCBF3267CB070000F5B51 /* Decode85.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Decode85.swift; sourceTree = ""; }; DBCBCC0C2680B908000F5B51 /* HomeTimelinePreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelinePreference.swift; sourceTree = ""; }; DBCBED1626132DB500B49291 /* UserTimelineViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserTimelineViewModel+Diffable.swift"; sourceTree = ""; }; DBCBED1C26132E1A00B49291 /* StatusFetchedResultsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusFetchedResultsController.swift; sourceTree = ""; }; DBCC3B2F261440A50045B23D /* UITabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITabBarController.swift; sourceTree = ""; }; - DBCC3B35261440BA0045B23D /* UINavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UINavigationController.swift; sourceTree = ""; }; DBCC3B8E26148F7B0045B23D /* CachedProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedProfileViewModel.swift; sourceTree = ""; }; DBCC3B9426157E6E0045B23D /* APIService+Relationship.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Relationship.swift"; sourceTree = ""; }; DBCCC71D25F73297007E1AB6 /* APIService+Reblog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Reblog.swift"; sourceTree = ""; }; @@ -1293,6 +1317,13 @@ DBD5B1F727BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceProvider+TableViewControllerNavigateable.swift"; sourceTree = ""; }; DBD5B1F927BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceProvider+StatusTableViewControllerNavigateable.swift"; sourceTree = ""; }; DBD9148F25DF6D8D00903DFD /* APIService+Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Onboarding.swift"; sourceTree = ""; }; + DBDFF18F2805543100557A48 /* DiscoveryPostsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryPostsViewController.swift; sourceTree = ""; }; + DBDFF1922805554900557A48 /* DiscoveryPostsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryPostsViewModel.swift; sourceTree = ""; }; + DBDFF1942805561700557A48 /* DiscoveryPostsViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiscoveryPostsViewModel+Diffable.swift"; sourceTree = ""; }; + DBDFF196280556D900557A48 /* DiscoveryPostsViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiscoveryPostsViewModel+State.swift"; sourceTree = ""; }; + DBDFF19928055A1400557A48 /* DiscoveryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryViewController.swift; sourceTree = ""; }; + DBDFF19B28055BD600557A48 /* DiscoveryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryViewModel.swift; sourceTree = ""; }; + DBDFF19D2805703700557A48 /* DiscoveryPostsViewController+DataSourceProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiscoveryPostsViewController+DataSourceProvider.swift"; 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 = ""; }; @@ -1305,6 +1336,14 @@ DBEB19E927E4F37B00B0E80E /* ku */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ku; path = ku.lproj/Intents.strings; sourceTree = ""; }; DBEB19EA27E4F37B00B0E80E /* ku */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ku; path = ku.lproj/InfoPlist.strings; sourceTree = ""; }; DBEB19EB27E4F37B00B0E80E /* ku */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ku; path = ku.lproj/Intents.stringsdict; sourceTree = ""; }; + DBEFCD70282A12B200C0ABEA /* ReportReasonViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportReasonViewController.swift; sourceTree = ""; }; + DBEFCD73282A130400C0ABEA /* ReportReasonViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportReasonViewModel.swift; sourceTree = ""; }; + DBEFCD75282A143F00C0ABEA /* ReportStatusViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportStatusViewController.swift; sourceTree = ""; }; + DBEFCD78282A147000C0ABEA /* ReportStatusViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportStatusViewModel.swift; sourceTree = ""; }; + DBEFCD7A282A162400C0ABEA /* ReportReasonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportReasonView.swift; sourceTree = ""; }; + DBEFCD7C282A2A3B00C0ABEA /* ReportServerRulesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportServerRulesViewController.swift; sourceTree = ""; }; + DBEFCD7F282A2AA900C0ABEA /* ReportServerRulesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportServerRulesViewModel.swift; sourceTree = ""; }; + DBEFCD81282A2AB100C0ABEA /* ReportServerRulesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportServerRulesView.swift; sourceTree = ""; }; DBF156DE2701B17600EC00B7 /* SidebarAddAccountCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarAddAccountCollectionViewCell.swift; sourceTree = ""; }; DBF156E02702DA6800EC00B7 /* Mastodon-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Mastodon-Bridging-Header.h"; sourceTree = ""; }; DBF156E12702DA6900EC00B7 /* UIStatusBarManager+HandleTapAction.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIStatusBarManager+HandleTapAction.m"; sourceTree = ""; }; @@ -1345,6 +1384,7 @@ DBFEF07226A6913D006D7ED1 /* APIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; }; DBFEF07A26A6BCE8006D7ED1 /* APIService+Status+Publish.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Status+Publish.swift"; sourceTree = ""; }; DDB1B139FA8EA26F510D58B6 /* Pods-AppShared.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppShared.asdk - release.xcconfig"; path = "Target Support Files/Pods-AppShared/Pods-AppShared.asdk - release.xcconfig"; sourceTree = ""; }; + DF65937EC1FF64462BC002EE /* Pods-MastodonTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.profile.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.profile.xcconfig"; sourceTree = ""; }; E5C7236E58D14A0322FE00F2 /* Pods-Mastodon-MastodonUITests.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.asdk - debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.asdk - debug.xcconfig"; sourceTree = ""; }; E9AABD3D26B64B8C00E237DA /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Intents.strings; sourceTree = ""; }; E9AABD4026B64B8D00E237DA /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -1352,9 +1392,7 @@ ECA373ABA86BE3C2D7ED878E /* Pods-AppShared.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppShared.release.xcconfig"; path = "Target Support Files/Pods-AppShared/Pods-AppShared.release.xcconfig"; sourceTree = ""; }; EE13214BC0246BE5210CCC10 /* Pods-AppShared.asdk.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppShared.asdk.xcconfig"; path = "Target Support Files/Pods-AppShared/Pods-AppShared.asdk.xcconfig"; sourceTree = ""; }; F31E7502A7E3945B98C6CBAF /* Pods-NotificationService.asdk.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.asdk.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.asdk.xcconfig"; sourceTree = ""; }; - F43DF6E8AB8C87914A64FC48 /* Pods-ShareActionExtension.release snapshot.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareActionExtension.release snapshot.xcconfig"; path = "Target Support Files/Pods-ShareActionExtension/Pods-ShareActionExtension.release snapshot.xcconfig"; sourceTree = ""; }; F4A2A2D7000E477CA459ADA9 /* Pods_AppShared.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AppShared.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F4C94BD75C96D0EFF5F6D961 /* Pods_MastodonIntent.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MastodonIntent.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F920AD4EC23B0D00F5CCA58E /* Pods-MastodonIntent.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonIntent.asdk - release.xcconfig"; path = "Target Support Files/Pods-MastodonIntent/Pods-MastodonIntent.asdk - release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1363,25 +1401,21 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */, - DB9A487E2603456B008B817C /* UITextView+Placeholder in Frameworks */, + DB3EA914281BBEA800598866 /* Alamofire in Frameworks */, 2D939AC825EE14620076FA61 /* CropViewController in Frameworks */, DBB525082611EAC0002F1F29 /* Tabman in Frameworks */, - 5D526FE225BE9AC400460CB9 /* MastodonSDK in Frameworks */, DB6804862637CD4C00430867 /* AppShared.framework in Frameworks */, DBF96326262EC0A6001D8D25 /* AuthenticationServices.framework in Frameworks */, DBAC6483267D0B21007FE9FD /* DifferenceKit in Frameworks */, DB552D4F26BBD10C00E481F6 /* OrderedCollections in Frameworks */, 2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */, - DB01E23326A98F0900C3965B /* MastodonMeta in Frameworks */, DBAC64A1267E6D02007FE9FD /* Fuzi in Frameworks */, - DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */, DBAC649E267DFE43007FE9FD /* DiffableDataSources in Frameworks */, 2D5981BA25E4D7F8000FB903 /* ThirdPartyMailer in Frameworks */, 87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */, DBF7A0FC26830C33004176A2 /* FPSIndicator in Frameworks */, - DB01E23526A98F0900C3965B /* MetaTextKit in Frameworks */, DBA5A52F26F07ED800CACBAA /* PanModal in Frameworks */, + DB3EA912281BBEA800598866 /* AlamofireImage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1406,8 +1440,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DB6805102637D0F800430867 /* KeychainAccess in Frameworks */, + DB3EA8FE281BBAF200598866 /* Alamofire in Frameworks */, + DB3EA8F5281BB65200598866 /* MastodonSDK in Frameworks */, + DB3EA8FC281BBAE100598866 /* AlamofireImage in Frameworks */, + DB02EA0B280D180D00E751C5 /* KeychainAccess in Frameworks */, EE93E8E8F9E0C39EAAEBD92F /* Pods_AppShared.framework in Frameworks */, + DB3EA904281BBD9400598866 /* Introspect in Frameworks */, + DB3EA902281BBD5D00598866 /* CommonOSLog in Frameworks */, + DB3EA900281BBB1D00598866 /* MetaTextKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1416,8 +1456,6 @@ buildActionMask = 2147483647; files = ( DB8FABC726AEC7B2008E5AF4 /* Intents.framework in Frameworks */, - DBB8AB4826AED09C00F6D281 /* MastodonSDK in Frameworks */, - BBAC710E327AF1EE1DB36A4E /* Pods_MastodonIntent.framework in Frameworks */, DBE3CA6E27A39CB300AFE27B /* AppShared.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1426,12 +1464,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DBBC24B826A5421800398BB9 /* CommonOSLog in Frameworks */, - DBBC24D126A5484F00398BB9 /* UITextView+Placeholder in Frameworks */, - DBBC24AA26A5301B00398BB9 /* MastodonSDK in Frameworks */, + DB3EA90E281BBE9600598866 /* AlamofireNetworkActivityIndicator in Frameworks */, + DB3EA910281BBE9600598866 /* Alamofire in Frameworks */, DBE3CA6B27A39CAF00AFE27B /* AppShared.framework in Frameworks */, - DB0C946526A6FD4D0088FB11 /* AlamofireImage in Frameworks */, - 4278334D6033AEEE0A1C5155 /* Pods_ShareActionExtension.framework in Frameworks */, + DB3EA90C281BBE9600598866 /* AlamofireImage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1439,11 +1475,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DB00CA972632DDB600A54956 /* CommonOSLog in Frameworks */, - DB179267278D5A4A00B71DEB /* MastodonSDK in Frameworks */, - DB6D9F42263527CE008423CD /* AlamofireImage in Frameworks */, + DB3EA908281BBE8200598866 /* AlamofireNetworkActivityIndicator in Frameworks */, + DB3EA90A281BBE8200598866 /* Alamofire in Frameworks */, DBE3CA6827A39CAB00AFE27B /* AppShared.framework in Frameworks */, - B914FC6B0B8AF18573C0B291 /* Pods_NotificationService.framework in Frameworks */, + DB3EA906281BBE8200598866 /* AlamofireImage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1529,8 +1564,6 @@ B31D44635FCF6452F7E1B865 /* Pods-Mastodon-AppShared.release.xcconfig */, 9A0982D8F349244EB558CDFD /* Pods-AppShared.debug.xcconfig */, ECA373ABA86BE3C2D7ED878E /* Pods-AppShared.release.xcconfig */, - 9553C689FFA9EBC880CAB78D /* Pods-NotificationService.debug.xcconfig */, - 9776D7C4B79101CF70181127 /* Pods-NotificationService.release.xcconfig */, EE13214BC0246BE5210CCC10 /* Pods-AppShared.asdk.xcconfig */, 819CEC9DCAD8E8E7BD85A7BB /* Pods-Mastodon.asdk.xcconfig */, A67FD038ECDA0E411AF8DB4D /* Pods-Mastodon-MastodonUITests.asdk.xcconfig */, @@ -1546,21 +1579,18 @@ 46DAB0EBDDFB678347CD96FF /* Pods-MastodonTests.asdk - release.xcconfig */, 3B7FD8F28DDA8FBCE5562B78 /* Pods-NotificationService.asdk - debug.xcconfig */, 9CFF58FD900AC059428700E7 /* Pods-NotificationService.asdk - release.xcconfig */, - 77EE917BC055E6621C0452B6 /* Pods-ShareActionExtension.debug.xcconfig */, 6130CBE4B26E3C976ACC1688 /* Pods-ShareActionExtension.asdk - debug.xcconfig */, 5CE45680252519F42FEA2D13 /* Pods-ShareActionExtension.asdk - release.xcconfig */, - 95AD0663479892A2109EEFD0 /* Pods-ShareActionExtension.release.xcconfig */, - 861BE60ED27430771CFD578D /* Pods-MastodonIntent.debug.xcconfig */, C3789232A52F43529CA67E95 /* Pods-MastodonIntent.asdk - debug.xcconfig */, F920AD4EC23B0D00F5CCA58E /* Pods-MastodonIntent.asdk - release.xcconfig */, - 159AC43EFE0A1F95FCB358A4 /* Pods-MastodonIntent.release.xcconfig */, 3E08A432F40BA7B9CAA9DB68 /* Pods-AppShared.release snapshot.xcconfig */, 0655B257371274BEB7EB1C19 /* Pods-Mastodon.release snapshot.xcconfig */, 0827D1674B2523503E8605F6 /* Pods-Mastodon-MastodonUITests.release snapshot.xcconfig */, - 2C12EB4B3699D5D597027962 /* Pods-MastodonIntent.release snapshot.xcconfig */, 8E79CCBE51FBC3F7FE8CF49F /* Pods-MastodonTests.release snapshot.xcconfig */, - 8ADD558BE5B8255E5764A54F /* Pods-NotificationService.release snapshot.xcconfig */, - F43DF6E8AB8C87914A64FC48 /* Pods-ShareActionExtension.release snapshot.xcconfig */, + 63EF9E6E5B575CD2A8B0475D /* Pods-AppShared.profile.xcconfig */, + 7CB58D292DA7ACEF179A9050 /* Pods-Mastodon.profile.xcconfig */, + 728DE51ADA27C395C6E1BAB5 /* Pods-Mastodon-MastodonUITests.profile.xcconfig */, + DF65937EC1FF64462BC002EE /* Pods-MastodonTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -1654,10 +1684,7 @@ 2D5A3D0125CF8640002347D6 /* Vender */ = { isa = PBXGroup; children = ( - DB71C7CC271D7F4300BE3819 /* CurveAlgorithm.swift */, 2D5A3D0225CF8742002347D6 /* ControlContainableScrollViews.swift */, - DB51D170262832380062B7A1 /* BlurHashDecode.swift */, - DB51D171262832380062B7A1 /* BlurHashEncode.swift */, DB6180EC26391C6C0018D199 /* TransitioningMath.swift */, DB75BF1D263C1C1B00EDBF1F /* CustomScheduler.swift */, DBF156E32702DB3F00EC00B7 /* HandleTapAction.swift */, @@ -1673,7 +1700,6 @@ DB45FB0425CA87B4005A8AC7 /* APIService */, DB49A61925FF327D00B98345 /* EmojiService */, DB9A489B26036E19008B817C /* MastodonAttachmentService */, - DBBC24BD26A5441A00398BB9 /* ThemeService */, DB45FB0E25CA87D0005A8AC7 /* AuthenticationService.swift */, 2DA6054625F716A2006356F9 /* PlaybackState.swift */, DBC7A67B260DFADE00E57475 /* StatusPublishService.swift */, @@ -1684,7 +1710,6 @@ DB297B1A2679FAE200704C90 /* PlaceholderImageCacheService.swift */, DB564BD2269F3B35001E39A7 /* StatusFilterService.swift */, DB73BF42271192BB00781945 /* InstanceService.swift */, - DB894CC327A5490600684B74 /* BlurhashImageCacheService.swift */, ); path = Service; sourceTree = ""; @@ -1698,6 +1723,7 @@ DB4AA6B227BA34B6009EC082 /* CellFrameCacheContainer.swift */, 2D38F20725CD491300561493 /* DisposeBagCollectable.swift */, DB1D84372657B275000346B3 /* SegmentedControlNavigateable.swift */, + DBCA0EBB282BB38A0029E2B0 /* PageboyNavigateable.swift */, DB1D843326579931000346B3 /* TableViewControllerNavigateable.swift */, DB1D842D26552C4D000346B3 /* StatusTableViewControllerNavigateable.swift */, ); @@ -1717,6 +1743,7 @@ DB4F097626A0398000D62E92 /* Compose */, DB0617F727855B010030EE79 /* Notification */, DB4F097726A039A200D62E92 /* Search */, + DB3E6FE52806A5BA00B035AE /* Discovery */, DB0617FA27855B660030EE79 /* Settings */, DBCBED2226132E1D00B49291 /* FetchedResultsController */, ); @@ -1803,7 +1830,6 @@ isa = PBXGroup; children = ( 2DCB73FC2615C13900EC03D4 /* SearchRecommendCollectionHeader.swift */, - DB71C7CA271D5A0300BE3819 /* LineChartView.swift */, ); path = View; sourceTree = ""; @@ -1824,11 +1850,8 @@ 3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */, 452147B2903DF38070FE56A2 /* Pods_MastodonTests.framework */, F4A2A2D7000E477CA459ADA9 /* Pods_AppShared.framework */, - 374AA339A20E0FAC75BCDA6D /* Pods_NotificationService.framework */, - A32B0CACBF35F4CC3CFAA043 /* Pods_ShareActionExtension.framework */, DB8FAB9E26AEC3A2008E5AF4 /* Intents.framework */, DB8FABA926AEC3A2008E5AF4 /* IntentsUI.framework */, - F4C94BD75C96D0EFF5F6D961 /* Pods_MastodonIntent.framework */, ); name = Frameworks; sourceTree = ""; @@ -1837,6 +1860,9 @@ isa = PBXGroup; children = ( DB98EB5727B0FF1F0082E365 /* Share */, + DBEFCD77282A144D00C0ABEA /* Report */, + DBEFCD72282A12B900C0ABEA /* ReportReason */, + DBEFCD7E282A2A3D00C0ABEA /* ReportServerRules */, DB98EB4F27B0F9300082E365 /* ReportStatus */, DB98EB5A27B109900082E365 /* ReportSupplementary */, DB98EB6327B216490082E365 /* ReportResult */, @@ -2018,6 +2044,14 @@ path = CoreDataStack; sourceTree = ""; }; + DB0A322F280EEA00001729D2 /* View */ = { + isa = PBXGroup; + children = ( + DB0A322D280EE9FD001729D2 /* DiscoveryIntroBannerView.swift */, + ); + path = View; + sourceTree = ""; + }; DB0C947826A7FE950088FB11 /* Button */ = { isa = PBXGroup; children = ( @@ -2097,6 +2131,66 @@ path = Resources; sourceTree = ""; }; + DB3E6FDE2806A41200B035AE /* Hashtags */ = { + isa = PBXGroup; + children = ( + DB3E6FDC2806A40F00B035AE /* DiscoveryHashtagsViewController.swift */, + DB3E6FDF2806A4ED00B035AE /* DiscoveryHashtagsViewModel.swift */, + DB3E6FE12806A50100B035AE /* DiscoveryHashtagsViewModel+Diffable.swift */, + ); + path = Hashtags; + sourceTree = ""; + }; + DB3E6FE52806A5BA00B035AE /* Discovery */ = { + isa = PBXGroup; + children = ( + DB3E6FE32806A5B800B035AE /* DiscoverySection.swift */, + DB3E6FE62806A7A200B035AE /* DiscoveryItem.swift */, + ); + path = Discovery; + sourceTree = ""; + }; + DB3E6FEA2806BD2500B035AE /* MastodonUI */ = { + isa = PBXGroup; + children = ( + DB3E6FE82806BD2200B035AE /* ThemeService.swift */, + ); + path = MastodonUI; + sourceTree = ""; + }; + DB3E6FED2806D7FC00B035AE /* News */ = { + isa = PBXGroup; + children = ( + DB3E6FEB2806D7F100B035AE /* DiscoveryNewsViewController.swift */, + DB3E6FEE2806D82600B035AE /* DiscoveryNewsViewModel.swift */, + DB3E6FF02806D96900B035AE /* DiscoveryNewsViewModel+Diffable.swift */, + DB3E6FF22806D97400B035AE /* DiscoveryNewsViewModel+State.swift */, + ); + path = News; + sourceTree = ""; + }; + DB3E6FF62807C40500B035AE /* ForYou */ = { + isa = PBXGroup; + children = ( + DB3E6FF42807C40300B035AE /* DiscoveryForYouViewController.swift */, + DB3E6FF72807C45300B035AE /* DiscoveryForYouViewModel.swift */, + DB3E6FF92807C47900B035AE /* DiscoveryForYouViewModel+Diffable.swift */, + ); + path = ForYou; + sourceTree = ""; + }; + DB3EA8E7281B79E500598866 /* Community */ = { + isa = PBXGroup; + children = ( + DB3EA8E5281B79E200598866 /* DiscoveryCommunityViewController.swift */, + DB3EA8EE281B837000598866 /* DiscoveryCommunityViewController+DataSourceProvider.swift */, + DB3EA8E8281B7A3700598866 /* DiscoveryCommunityViewModel.swift */, + DB3EA8F0281B9EF600598866 /* DiscoveryCommunityViewModel+Diffable.swift */, + DB3EA8EA281B7E0700598866 /* DiscoveryCommunityViewModel+State.swift */, + ); + path = Community; + sourceTree = ""; + }; DB427DC925BAA00100D1B89D = { isa = PBXGroup; children = ( @@ -2135,8 +2229,8 @@ DB427DD425BAA00100D1B89D /* Mastodon */ = { isa = PBXGroup; children = ( - DB427DE325BAA00100D1B89D /* Info.plist */, DB89BA1025C10FF5008580ED /* Mastodon.entitlements */, + DB427DE325BAA00100D1B89D /* Info.plist */, 2D76319C25C151DE00929FB9 /* Diffiable */, DB8AF52A25C13561002E6C99 /* State */, 2D61335525C1886800CAE157 /* Service */, @@ -2190,6 +2284,7 @@ 2D9DB96A263A91D1007C1D71 /* APIService+DomainBlock.swift */, DB45FB1C25CA9D23005A8AC7 /* APIService+HomeTimeline.swift */, DB482A4A261340A7008AE74C /* APIService+UserTimeline.swift */, + DB3EA8EC281B810100598866 /* APIService+PublicTimeline.swift */, DBA465922696B495002B41DB /* APIService+WebFinger.swift */, DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */, DB59F10D25EF724F001F1DAB /* APIService+Poll.swift */, @@ -2328,7 +2423,6 @@ children = ( DBA465942696E387002B41DB /* AppPreference.swift */, DB647C5826F1EA2700F7F82C /* WizardPreference.swift */, - DB6D1B3C2636857500ACB481 /* AppearancePreference.swift */, DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */, DB1D842F26566512000346B3 /* KeyboardPreference.swift */, DBCBCC0C2680B908000F5B51 /* HomeTimelinePreference.swift */, @@ -2472,7 +2566,6 @@ DB6804812637CD4C00430867 /* AppShared.h */, DB6804822637CD4C00430867 /* Info.plist */, DB6804FC2637CFEC00430867 /* AppSecret.swift */, - DB6804D02637CE4700430867 /* UserDefaults.swift */, DB73BF3A2711885500781945 /* UserDefaults+Notification.swift */, ); path = AppShared; @@ -2571,6 +2664,7 @@ children = ( DB0618082785B2790030EE79 /* Cell */, DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */, + DB7A9F922818F33C0016AF98 /* MastodonServerRulesViewController+Debug.swift */, DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */, DB0617FE27855D6C0030EE79 /* MastodonServerRulesViewModel+Diffable.swift */, ); @@ -2682,6 +2776,7 @@ 2DAC9E36262FC20B0062E1A6 /* SuggestionAccount */, DB9D6C0825E4F5A60051B173 /* Profile */, DB9D6BEE25E4F5370051B173 /* Search */, + DBDFF1912805544800557A48 /* Discovery */, 5B90C455262599800002E742 /* Settings */, ); path = Scene; @@ -2691,6 +2786,7 @@ isa = PBXGroup; children = ( DB084B5125CBC56300F898ED /* CoreDataStack */, + DB3E6FEA2806BD2500B035AE /* MastodonUI */, DB6C8C0525F0921200AAA452 /* MastodonSDK */, 2DF123A625C3B0210020F248 /* ActiveLabel.swift */, 5DF1056325F887CB00D6C0D4 /* AVPlayer.swift */, @@ -2708,7 +2804,6 @@ DBD376B1269302A4007FEC24 /* UITableViewCell.swift */, 0FAA101B25E10E760017CCDE /* UIFont.swift */, 2D206B9125F60EA700143C56 /* UIControl.swift */, - 2D32EAB925CB9B0500C9ED86 /* UIView.swift */, 5DA732CB2629CEF500A92342 /* UIView+Remove.swift */, 2D24E1222626ED9D00A59D4F /* UIView+Gesture.swift */, DB8AF55C25C138B7002E6C99 /* UIViewController.swift */, @@ -2716,7 +2811,6 @@ 2D84350425FF858100EECE90 /* UIScrollView.swift */, DB9E0D6E25EE008500CFDD76 /* UIInterpolatingMotionEffect.swift */, DBCC3B2F261440A50045B23D /* UITabBarController.swift */, - DBCC3B35261440BA0045B23D /* UINavigationController.swift */, DB73BF4827140BA300781945 /* UICollectionViewDiffableDataSource.swift */, DB73BF4A27140C0800781945 /* UITableViewDiffableDataSource.swift */, ); @@ -2777,10 +2871,10 @@ DB98EB4F27B0F9300082E365 /* ReportStatus */ = { isa = PBXGroup; children = ( - 5BB04FD4262E7AFF0043BFF6 /* ReportViewController.swift */, - 5B24BBD7262DB14800A9381B /* ReportViewModel.swift */, - 5B24BBD8262DB14800A9381B /* ReportViewModel+Diffable.swift */, - DB98EB4627B0DFAA0082E365 /* ReportViewModel+State.swift */, + DBEFCD75282A143F00C0ABEA /* ReportStatusViewController.swift */, + DBEFCD78282A147000C0ABEA /* ReportStatusViewModel.swift */, + 5B24BBD8262DB14800A9381B /* ReportStatusViewModel+Diffable.swift */, + DB98EB4627B0DFAA0082E365 /* ReportStatusViewModel+State.swift */, ); path = ReportStatus; sourceTree = ""; @@ -2809,7 +2903,7 @@ children = ( DB98EB6127B215EB0082E365 /* ReportResultViewController.swift */, DB98EB6427B216500082E365 /* ReportResultViewModel.swift */, - DB98EB6627B216560082E365 /* ReportResultViewModel+Diffable.swift */, + DB848E32282B62A800A302CC /* ReportResultView.swift */, ); path = ReportResult; sourceTree = ""; @@ -2989,9 +3083,6 @@ isa = PBXGroup; children = ( DBB5254F2611ED6D002F1F29 /* ProfileHeaderView.swift */, - DBB5256D2612D5A1002F1F29 /* ProfileStatusDashboardView.swift */, - DBB525842612D6DD002F1F29 /* ProfileStatusDashboardMeterView.swift */, - DB35FC1E2612F1D9006193C9 /* ProfileRelationshipActionButton.swift */, DBF98149265E24F500E4BA07 /* ProfileFieldCollectionViewHeaderFooterView.swift */, ); path = View; @@ -3005,24 +3096,11 @@ path = Service; sourceTree = ""; }; - DBBC24BD26A5441A00398BB9 /* ThemeService */ = { - isa = PBXGroup; - children = ( - DBBC24C326A544B900398BB9 /* Theme.swift */, - DBBC24BE26A5443100398BB9 /* MastodonTheme.swift */, - DBBC24BF26A5443100398BB9 /* SystemTheme.swift */, - DBBC24BB26A542F500398BB9 /* ThemeService.swift */, - DBBC24CE26A547AE00398BB9 /* ThemeService+Appearance.swift */, - ); - path = ThemeService; - sourceTree = ""; - }; DBBC24D526A54BCB00398BB9 /* Helper */ = { isa = PBXGroup; children = ( DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */, DBBC50C0278ED49200AF0CC6 /* MastodonAuthenticationBox.swift */, - DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */, DBF3B7402733EB9400E21627 /* MastodonLocalCode.swift */, ); path = Helper; @@ -3068,6 +3146,33 @@ path = FetchedResultsController; sourceTree = ""; }; + DBDFF1912805544800557A48 /* Discovery */ = { + isa = PBXGroup; + children = ( + DB0A322F280EEA00001729D2 /* View */, + DBDFF19828055A0900557A48 /* Posts */, + DB3E6FDE2806A41200B035AE /* Hashtags */, + DB3E6FED2806D7FC00B035AE /* News */, + DB3EA8E7281B79E500598866 /* Community */, + DB3E6FF62807C40500B035AE /* ForYou */, + DBDFF19928055A1400557A48 /* DiscoveryViewController.swift */, + DBDFF19B28055BD600557A48 /* DiscoveryViewModel.swift */, + ); + path = Discovery; + sourceTree = ""; + }; + DBDFF19828055A0900557A48 /* Posts */ = { + isa = PBXGroup; + children = ( + DBDFF18F2805543100557A48 /* DiscoveryPostsViewController.swift */, + DBDFF19D2805703700557A48 /* DiscoveryPostsViewController+DataSourceProvider.swift */, + DBDFF1922805554900557A48 /* DiscoveryPostsViewModel.swift */, + DBDFF1942805561700557A48 /* DiscoveryPostsViewModel+Diffable.swift */, + DBDFF196280556D900557A48 /* DiscoveryPostsViewModel+State.swift */, + ); + path = Posts; + sourceTree = ""; + }; DBE0821A25CD382900FD6BBD /* Register */ = { isa = PBXGroup; children = ( @@ -3076,6 +3181,7 @@ 2D939AE725EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift */, DBE0822325CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift */, DB0618062785A8880030EE79 /* MastodonRegisterViewModel+Diffable.swift */, + DB7A9F902818EAF10016AF98 /* MastodonRegisterView.swift */, ); path = Register; sourceTree = ""; @@ -3092,6 +3198,35 @@ path = Favorite; sourceTree = ""; }; + DBEFCD72282A12B900C0ABEA /* ReportReason */ = { + isa = PBXGroup; + children = ( + DBEFCD70282A12B200C0ABEA /* ReportReasonViewController.swift */, + DBEFCD73282A130400C0ABEA /* ReportReasonViewModel.swift */, + DBEFCD7A282A162400C0ABEA /* ReportReasonView.swift */, + ); + path = ReportReason; + sourceTree = ""; + }; + DBEFCD77282A144D00C0ABEA /* Report */ = { + isa = PBXGroup; + children = ( + 5BB04FD4262E7AFF0043BFF6 /* ReportViewController.swift */, + 5B24BBD7262DB14800A9381B /* ReportViewModel.swift */, + ); + path = Report; + sourceTree = ""; + }; + DBEFCD7E282A2A3D00C0ABEA /* ReportServerRules */ = { + isa = PBXGroup; + children = ( + DBEFCD7C282A2A3B00C0ABEA /* ReportServerRulesViewController.swift */, + DBEFCD7F282A2AA900C0ABEA /* ReportServerRulesViewModel.swift */, + DBEFCD81282A2AB100C0ABEA /* ReportServerRulesView.swift */, + ); + path = ReportServerRules; + sourceTree = ""; + }; DBF1D24F269DAF6100C1C08A /* SearchDetail */ = { isa = PBXGroup; children = ( @@ -3236,22 +3371,18 @@ ); name = Mastodon; packageProductDependencies = ( - DB3D0FF225BAA61700EAA174 /* AlamofireImage */, - 5D526FE125BE9AC400460CB9 /* MastodonSDK */, 2D61336825C18A4F00CAE157 /* AlamofireNetworkActivityIndicator */, - DB0140BC25C40D7500F9F3CF /* CommonOSLog */, 2D5981B925E4D7F8000FB903 /* ThirdPartyMailer */, 2D939AC725EE14620076FA61 /* CropViewController */, - DB9A487D2603456B008B817C /* UITextView+Placeholder */, DBB525072611EAC0002F1F29 /* Tabman */, DBAC6482267D0B21007FE9FD /* DifferenceKit */, DBAC649D267DFE43007FE9FD /* DiffableDataSources */, DBAC64A0267E6D02007FE9FD /* Fuzi */, DBF7A0FB26830C33004176A2 /* FPSIndicator */, - DB01E23226A98F0900C3965B /* MastodonMeta */, - DB01E23426A98F0900C3965B /* MetaTextKit */, DB552D4E26BBD10C00E481F6 /* OrderedCollections */, DBA5A52E26F07ED800CACBAA /* PanModal */, + DB3EA911281BBEA800598866 /* AlamofireImage */, + DB3EA913281BBEA800598866 /* Alamofire */, ); productName = Mastodon; productReference = DB427DD225BAA00100D1B89D /* Mastodon.app */; @@ -3312,7 +3443,13 @@ ); name = AppShared; packageProductDependencies = ( - DB68050F2637D0F800430867 /* KeychainAccess */, + DB02EA0A280D180D00E751C5 /* KeychainAccess */, + DB3EA8F4281BB65200598866 /* MastodonSDK */, + DB3EA8FB281BBAE100598866 /* AlamofireImage */, + DB3EA8FD281BBAF200598866 /* Alamofire */, + DB3EA8FF281BBB1D00598866 /* MetaTextKit */, + DB3EA901281BBD5D00598866 /* CommonOSLog */, + DB3EA903281BBD9400598866 /* Introspect */, ); productName = AppShared; productReference = DB68047F2637CD4C00430867 /* AppShared.framework */; @@ -3322,7 +3459,6 @@ isa = PBXNativeTarget; buildConfigurationList = DB8FABCF26AEC7B2008E5AF4 /* Build configuration list for PBXNativeTarget "MastodonIntent" */; buildPhases = ( - 625308CC94CCF528BD373740 /* [CP] Check Pods Manifest.lock */, DB8FABC226AEC7B2008E5AF4 /* Sources */, DB8FABC326AEC7B2008E5AF4 /* Frameworks */, DB8FABC426AEC7B2008E5AF4 /* Resources */, @@ -3333,9 +3469,6 @@ DB8FABDA26AEC873008E5AF4 /* PBXTargetDependency */, ); name = MastodonIntent; - packageProductDependencies = ( - DBB8AB4726AED09C00F6D281 /* MastodonSDK */, - ); productName = MastodonIntent; productReference = DB8FABC626AEC7B2008E5AF4 /* MastodonIntent.appex */; productType = "com.apple.product-type.app-extension"; @@ -3344,7 +3477,6 @@ isa = PBXNativeTarget; buildConfigurationList = DBC6462126A170AB00B0E31B /* Build configuration list for PBXNativeTarget "ShareActionExtension" */; buildPhases = ( - 641DEA63EE5B048C9A551302 /* [CP] Check Pods Manifest.lock */, DBC6460E26A170AB00B0E31B /* Sources */, DBC6460F26A170AB00B0E31B /* Frameworks */, DBC6461026A170AB00B0E31B /* Resources */, @@ -3356,10 +3488,9 @@ ); name = ShareActionExtension; packageProductDependencies = ( - DBBC24A926A5301B00398BB9 /* MastodonSDK */, - DBBC24B726A5421800398BB9 /* CommonOSLog */, - DBBC24D026A5484F00398BB9 /* UITextView+Placeholder */, - DB0C946426A6FD4D0088FB11 /* AlamofireImage */, + DB3EA90B281BBE9600598866 /* AlamofireImage */, + DB3EA90D281BBE9600598866 /* AlamofireNetworkActivityIndicator */, + DB3EA90F281BBE9600598866 /* Alamofire */, ); productName = ShareActionExtension; productReference = DBC6461226A170AB00B0E31B /* ShareActionExtension.appex */; @@ -3369,7 +3500,6 @@ isa = PBXNativeTarget; buildConfigurationList = DBF8AE1E263293E400C9C23C /* Build configuration list for PBXNativeTarget "NotificationService" */; buildPhases = ( - 0DC740704503CA6BED56F5C8 /* [CP] Check Pods Manifest.lock */, DBF8AE0F263293E400C9C23C /* Sources */, DBF8AE10263293E400C9C23C /* Frameworks */, DBF8AE11263293E400C9C23C /* Resources */, @@ -3381,9 +3511,9 @@ ); name = NotificationService; packageProductDependencies = ( - DB00CA962632DDB600A54956 /* CommonOSLog */, - DB6D9F41263527CE008423CD /* AlamofireImage */, - DB179266278D5A4A00B71DEB /* MastodonSDK */, + DB3EA905281BBE8200598866 /* AlamofireImage */, + DB3EA907281BBE8200598866 /* AlamofireNetworkActivityIndicator */, + DB3EA909281BBE8200598866 /* Alamofire */, ); productName = NotificationService; productReference = DBF8AE13263293E400C9C23C /* NotificationService.appex */; @@ -3449,6 +3579,12 @@ ku, kab, vi, + sv, + ckb, + "zh-Hant", + gl, + it, + tr, ); mainGroup = DB427DC925BAA00100D1B89D; packageReferences = ( @@ -3457,7 +3593,6 @@ DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */, 2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */, 2D939AC625EE14620076FA61 /* XCRemoteSwiftPackageReference "TOCropViewController" */, - DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */, DBB525062611EAC0002F1F29 /* XCRemoteSwiftPackageReference "Tabman" */, DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */, DBAC6481267D0B21007FE9FD /* XCRemoteSwiftPackageReference "DifferenceKit" */, @@ -3468,6 +3603,8 @@ DB01E23126A98F0900C3965B /* XCRemoteSwiftPackageReference "MetaTextKit" */, DB552D4D26BBD10C00E481F6 /* XCRemoteSwiftPackageReference "swift-collections" */, DBA5A52D26F07ED800CACBAA /* XCRemoteSwiftPackageReference "PanModal" */, + DB8D8E2D28192EED009FD90F /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */, + DB3EA8F6281BBA4C00598866 /* XCRemoteSwiftPackageReference "Alamofire" */, ); productRefGroup = DB427DD325BAA00100D1B89D /* Products */; projectDirPath = ""; @@ -3550,28 +3687,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 0DC740704503CA6BED56F5C8 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-NotificationService-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; 5532CB85BBE168B25B20720B /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -3589,50 +3704,6 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Mastodon/Pods-Mastodon-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 625308CC94CCF528BD373740 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-MastodonIntent-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 641DEA63EE5B048C9A551302 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-ShareActionExtension-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; 6E033728B42BA1C0018B6131 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -3797,6 +3868,7 @@ buildActionMask = 2147483647; files = ( DBB525212611EBD6002F1F29 /* ProfilePagingViewController.swift in Sources */, + DBDFF19E2805703700557A48 /* DiscoveryPostsViewController+DataSourceProvider.swift in Sources */, DB6180EB26391C140018D199 /* MediaPreviewTransitionItem.swift in Sources */, DB63F74727990B0600455B82 /* DataSourceFacade+Hashtag.swift in Sources */, DB98337125C9443200AD9700 /* APIService+Authentication.swift in Sources */, @@ -3823,6 +3895,7 @@ DB336F43278EB1690031E64B /* MediaView+Configuration.swift in Sources */, DB66729625F9F91600D60309 /* ComposeStatusSection.swift in Sources */, DB482A3F261331E8008AE74C /* UserTimelineViewModel+State.swift in Sources */, + DB3E6FE02806A4ED00B035AE /* DiscoveryHashtagsViewModel.swift in Sources */, 2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */, DB447681260B3ED600B66B82 /* CustomEmojiPickerSection.swift in Sources */, DB0FCB7427956939006C02E2 /* DataSourceFacade+Status.swift in Sources */, @@ -3830,7 +3903,6 @@ DB63F75A279953F200455B82 /* SearchHistoryUserCollectionViewCell+ViewModel.swift in Sources */, DB023D26279FFB0A005AC798 /* ShareActivityProvider.swift in Sources */, DB71FD5225F8CCAA00512AE1 /* APIService+Status.swift in Sources */, - DB8481152788121200BBEABA /* MastodonRegisterTextFieldTableViewCell.swift in Sources */, 5D0393962612D266007FE196 /* WebViewModel.swift in Sources */, 5B24BBDA262DB14800A9381B /* ReportViewModel.swift in Sources */, 2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */, @@ -3851,6 +3923,7 @@ 2D364F7225E66D7500204FDC /* MastodonResendEmailViewController.swift in Sources */, DB68A06325E905E000CFDF14 /* UIApplication.swift in Sources */, DB02CDAB26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift in Sources */, + DBEFCD80282A2AA900C0ABEA /* ReportServerRulesViewModel.swift in Sources */, DB0617FF27855D6C0030EE79 /* MastodonServerRulesViewModel+Diffable.swift in Sources */, DBB5255E2611F07A002F1F29 /* ProfileViewModel.swift in Sources */, DB0FCB982797F6BF006C02E2 /* UserTableViewCell+ViewModel.swift in Sources */, @@ -3858,6 +3931,7 @@ DB697DD6278F4C29004EF2F7 /* DataSourceProvider.swift in Sources */, DB0FCB8E2796C0B7006C02E2 /* TrendCollectionViewCell.swift in Sources */, 0F1E2D0B2615C39400C38565 /* DoubleTitleLabelNavigationBarTitleView.swift in Sources */, + DBDFF1902805543100557A48 /* DiscoveryPostsViewController.swift in Sources */, DB697DD9278F4CED004EF2F7 /* HomeTimelineViewController+DataSourceProvider.swift in Sources */, DB9A488A26034D40008B817C /* ComposeViewModel+PublishState.swift in Sources */, DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */, @@ -3871,18 +3945,21 @@ 5DDDF1932617442700311060 /* Mastodon+Entity+Account.swift in Sources */, DB63F767279A5EB300455B82 /* NotificationTimelineViewModel.swift in Sources */, 2D607AD826242FC500B70763 /* NotificationViewModel.swift in Sources */, + DB848E33282B62A800A302CC /* ReportResultView.swift in Sources */, DBABE3EC25ECAC4B00879EE5 /* WelcomeIllustrationView.swift in Sources */, DB564BD3269F3B35001E39A7 /* StatusFilterService.swift in Sources */, DB0FCB9C27980AB6006C02E2 /* HashtagTimelineViewController+DataSourceProvider.swift in Sources */, DB63F76F279A7D1100455B82 /* NotificationTableViewCell.swift in Sources */, DB297B1B2679FAE200704C90 /* PlaceholderImageCacheService.swift in Sources */, DB0FCB8C2796BF8D006C02E2 /* SearchViewModel+Diffable.swift in Sources */, + DBEFCD76282A143F00C0ABEA /* ReportStatusViewController.swift in Sources */, 2D8FCA082637EABB00137F46 /* APIService+FollowRequest.swift in Sources */, + DBDFF1952805561700557A48 /* DiscoveryPostsViewModel+Diffable.swift in Sources */, DB03A795272A981400EE37C5 /* ContentSplitViewController.swift in Sources */, - DBBC24DE26A54BCB00398BB9 /* MastodonMetricFormatter.swift in Sources */, - DB06180A2785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift in Sources */, + DBDFF19C28055BD600557A48 /* DiscoveryViewModel.swift in Sources */, DBB45B6227B51112002DC5A7 /* SuggestionAccountViewModel+Diffable.swift in Sources */, DBB3BA2A26A81C020004F2D4 /* FLAnimatedImageView.swift in Sources */, + DB3E6FF32806D97400B035AE /* DiscoveryNewsViewModel+State.swift in Sources */, DB6746ED278F45F0008A6B94 /* AutoGenerateProtocolRelayDelegate.swift in Sources */, DB0618032785A7100030EE79 /* RegisterSection.swift in Sources */, DB63F76B279A5ED300455B82 /* NotificationTimelineViewModel+LoadOldestState.swift in Sources */, @@ -3905,12 +3982,12 @@ DBE3CE07261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift in Sources */, 2D34D9DB261494120081BFC0 /* APIService+Search.swift in Sources */, 5B90C48B26259C120002E742 /* APIService+CoreData+Subscriptions.swift in Sources */, - DBB5256E2612D5A1002F1F29 /* ProfileStatusDashboardView.swift in Sources */, DBA9443E265CFA6400C537E1 /* ProfileFieldCollectionViewCell.swift in Sources */, DB025B93278D6501002F581E /* Persistence.swift in Sources */, 2D24E1232626ED9D00A59D4F /* UIView+Gesture.swift in Sources */, DBFEEC9D279C12C1004F81DD /* ProfileFieldEditCollectionViewCell.swift in Sources */, DB45FAE325CA7181005A8AC7 /* MastodonUser.swift in Sources */, + DB3E6FEC2806D7F100B035AE /* DiscoveryNewsViewController.swift in Sources */, DBA088DF26958164003EB4B2 /* UserFetchedResultsController.swift in Sources */, DB2FF510260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift in Sources */, 0F202213261351F5000C64BF /* APIService+HashtagTimeline.swift in Sources */, @@ -3947,12 +4024,12 @@ DB36679F268ABAF20027D07F /* ComposeStatusAttachmentSection.swift in Sources */, 2DA7D04425CA52B200804E11 /* TimelineLoaderTableViewCell.swift in Sources */, DB63F7542799491600455B82 /* DataSourceFacade+SearchHistory.swift in Sources */, + DB7A9F912818EAF10016AF98 /* MastodonRegisterView.swift in Sources */, DBF1572F27046F1A00EC00B7 /* SecondaryPlaceholderViewController.swift in Sources */, DB03F7F32689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift in Sources */, 2D4AD8A826316D3500613EFC /* SelectedAccountItem.swift in Sources */, DBE3CDFB261C6CA500430CC6 /* FavoriteViewModel.swift in Sources */, DB8AF52F25C13561002E6C99 /* DocumentStore.swift in Sources */, - DBBC24C126A5443100398BB9 /* SystemTheme.swift in Sources */, DBE3CE01261D623D00430CC6 /* FavoriteViewModel+State.swift in Sources */, 2D82BA0525E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift in Sources */, 2D38F1EB25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift in Sources */, @@ -3964,6 +4041,7 @@ DB63F7452799056400455B82 /* HashtagTableViewCell.swift in Sources */, DBD9149025DF6D8D00903DFD /* APIService+Onboarding.swift in Sources */, DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */, + DB3EA8EB281B7E0700598866 /* DiscoveryCommunityViewModel+State.swift in Sources */, DB0FCB7227952986006C02E2 /* NamingState.swift in Sources */, DB73BF47271199CA00781945 /* Instance.swift in Sources */, DB0F8150264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift in Sources */, @@ -3979,6 +4057,7 @@ DB63F76227996B6600455B82 /* SearchHistoryViewController+DataSourceProvider.swift in Sources */, DB73BF4927140BA300781945 /* UICollectionViewDiffableDataSource.swift in Sources */, DBA5E7AB263BD3F5004598BB /* TimelineTableViewCellContextMenuConfiguration.swift in Sources */, + DB3E6FE92806BD2200B035AE /* ThemeService.swift in Sources */, DB73B490261F030A002E9E9F /* SafariActivity.swift in Sources */, DB63F7492799126300455B82 /* FollowerListViewController+DataSourceProvider.swift in Sources */, DB6D1B44263691CF00ACB481 /* Mastodon+API+Subscriptions+Policy.swift in Sources */, @@ -3996,27 +4075,30 @@ DB697DD4278F4927004EF2F7 /* StatusTableViewCellDelegate.swift in Sources */, DB0FCB902796C5EB006C02E2 /* APIService+Trend.swift in Sources */, DBA5E7A5263BD28C004598BB /* ContextMenuImagePreviewViewModel.swift in Sources */, + DB3E6FF52807C40300B035AE /* DiscoveryForYouViewController.swift in Sources */, DB9D7C21269824B80054B3DF /* APIService+Filter.swift in Sources */, 2D38F1E525CD46C100561493 /* HomeTimelineViewModel.swift in Sources */, DB0FCB842796B2A2006C02E2 /* FavoriteViewController+DataSourceProvider.swift in Sources */, - DBCC3B36261440BA0045B23D /* UINavigationController.swift in Sources */, - DBB525852612D6DD002F1F29 /* ProfileStatusDashboardMeterView.swift in Sources */, DB0FCB68279507EF006C02E2 /* DataSourceFacade+Meta.swift in Sources */, DB63F75C279956D000455B82 /* Persistence+Tag.swift in Sources */, 2D84350525FF858100EECE90 /* UIScrollView.swift in Sources */, DB49A61F25FF32AA00B98345 /* EmojiService+CustomEmojiViewModel.swift in Sources */, - 5B24BBDB262DB14800A9381B /* ReportViewModel+Diffable.swift in Sources */, + 5B24BBDB262DB14800A9381B /* ReportStatusViewModel+Diffable.swift in Sources */, DB4F0968269ED8AD00D62E92 /* SearchHistoryTableHeaderView.swift in Sources */, 0FB3D2FE25E4CB6400AAD544 /* OnboardingHeadlineTableViewCell.swift in Sources */, 5DA732CC2629CEF500A92342 /* UIView+Remove.swift in Sources */, DB1D843026566512000346B3 /* KeyboardPreference.swift in Sources */, DB852D1926FAEB6B00FC9D81 /* SidebarViewController.swift in Sources */, 2D206B9225F60EA700143C56 /* UIControl.swift in Sources */, + DBDFF1932805554900557A48 /* DiscoveryPostsViewModel.swift in Sources */, 2D9DB96B263A91D1007C1D71 /* APIService+DomainBlock.swift in Sources */, DBBF1DC92652538500E5B703 /* AutoCompleteSection.swift in Sources */, + DB3E6FE72806A7A200B035AE /* DiscoveryItem.swift in Sources */, DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */, + DBEFCD79282A147000C0ABEA /* ReportStatusViewModel.swift in Sources */, DB7F48452620241000796008 /* ProfileHeaderViewModel.swift in Sources */, DB647C5926F1EA2700F7F82C /* WizardPreference.swift in Sources */, + DB0A322E280EE9FD001729D2 /* DiscoveryIntroBannerView.swift in Sources */, 2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */, 5DDDF1992617447F00311060 /* Mastodon+Entity+Tag.swift in Sources */, 5B90C45F262599800002E742 /* SettingsToggleTableViewCell.swift in Sources */, @@ -4036,11 +4118,9 @@ 5B90C462262599800002E742 /* SettingsSectionHeader.swift in Sources */, DB44768B260B3F2100B66B82 /* CustomEmojiPickerItem.swift in Sources */, 5DF1056425F887CB00D6C0D4 /* AVPlayer.swift in Sources */, + DB3E6FEF2806D82600B035AE /* DiscoveryNewsViewModel.swift in Sources */, DBBF1DCB2652539E00E5B703 /* AutoCompleteItem.swift in Sources */, - DB84811727883C2600BBEABA /* MastodonRegisterPasswordHintTableViewCell.swift in Sources */, 2DA6054725F716A2006356F9 /* PlaybackState.swift in Sources */, - DB35FC1F2612F1D9006193C9 /* ProfileRelationshipActionButton.swift in Sources */, - DB98EB6727B216560082E365 /* ReportResultViewModel+Diffable.swift in Sources */, DBC7A672260C897100E57475 /* StatusContentWarningEditorView.swift in Sources */, DB025B95278D6530002F581E /* Persistence+MastodonUser.swift in Sources */, DB3667A6268AE2620027D07F /* ComposeStatusPollSection.swift in Sources */, @@ -4064,11 +4144,13 @@ DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */, DBA94434265CBB5300C537E1 /* ProfileFieldSection.swift in Sources */, DB336F28278D6EC70031E64B /* MastodonFieldContainer.swift in Sources */, + DBCA0EBC282BB38A0029E2B0 /* PageboyNavigateable.swift in Sources */, DBF156E42702DB3F00EC00B7 /* HandleTapAction.swift in Sources */, - DB98EB4727B0DFAA0082E365 /* ReportViewModel+State.swift in Sources */, + DB98EB4727B0DFAA0082E365 /* ReportStatusViewModel+State.swift in Sources */, 2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */, DB6B74F6272FBCDB00C70B6E /* FollowerListViewModel+State.swift in Sources */, DB87D4452609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift in Sources */, + DBDFF197280556D900557A48 /* DiscoveryPostsViewModel+State.swift in Sources */, DB336F2C278D6FC30031E64B /* Persistence+Status.swift in Sources */, DB336F2A278D6F2B0031E64B /* MastodonField.swift in Sources */, DB0FCB7A279576A2006C02E2 /* DataSourceFacade+Thread.swift in Sources */, @@ -4092,11 +4174,12 @@ DB040ED126538E3D00BEE9D8 /* Trie.swift in Sources */, DB73BF4B27140C0800781945 /* UITableViewDiffableDataSource.swift in Sources */, DBB525642612C988002F1F29 /* MeProfileViewModel.swift in Sources */, + DB3EA8EF281B837000598866 /* DiscoveryCommunityViewController+DataSourceProvider.swift in Sources */, DB6B74EF272FB55000C70B6E /* FollowerListViewController.swift in Sources */, DB4AA6B327BA34B6009EC082 /* CellFrameCacheContainer.swift in Sources */, DB0FCB942797E2B0006C02E2 /* SearchResultViewModel+Diffable.swift in Sources */, DB63F752279944AA00455B82 /* SearchHistorySectionHeaderCollectionReusableView.swift in Sources */, - DBBC24C426A544B900398BB9 /* Theme.swift in Sources */, + DB3E6FDD2806A40F00B035AE /* DiscoveryHashtagsViewController.swift in Sources */, DB938EED2623F79B00E5B6C1 /* ThreadViewModel.swift in Sources */, DBBC24AC26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift in Sources */, DBC7A67C260DFADE00E57475 /* StatusPublishService.swift in Sources */, @@ -4104,6 +4187,7 @@ DB852D1C26FB021500FC9D81 /* RootSplitViewController.swift in Sources */, DB697DD1278F4871004EF2F7 /* AutoGenerateTableViewDelegate.swift in Sources */, DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */, + DB3E6FFA2807C47900B035AE /* DiscoveryForYouViewModel+Diffable.swift in Sources */, DB1FD44425F26CCC004CFCFC /* PickServerSection.swift in Sources */, DBB45B6027B50A4F002DC5A7 /* RecommendAccountItem.swift in Sources */, 0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */, @@ -4115,12 +4199,12 @@ DBBF1DC5265251C300E5B703 /* AutoCompleteViewModel+Diffable.swift in Sources */, DB603111279EB38500A935FE /* DataSourceFacade+Mute.swift in Sources */, DB68A04A25E9027700CFDF14 /* AdaptiveStatusBarStyleNavigationController.swift in Sources */, - DBBC24BC26A542F500398BB9 /* ThemeService.swift in Sources */, DB336F38278D7AAF0031E64B /* Poll+Property.swift in Sources */, 0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */, DB6D9F8426358EEC008423CD /* SettingsItem.swift in Sources */, 2D364F7825E66D8300204FDC /* MastodonResendEmailViewModel.swift in Sources */, DBA465932696B495002B41DB /* APIService+WebFinger.swift in Sources */, + DBEFCD7B282A162400C0ABEA /* ReportReasonView.swift in Sources */, DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */, DB63F77B279ACAE500455B82 /* DataSourceFacade+Favorite.swift in Sources */, DB9D6BF825E4F5690051B173 /* NotificationViewController.swift in Sources */, @@ -4128,17 +4212,18 @@ DB4FFC2C269EC39600D62E92 /* SearchTransitionController.swift in Sources */, DBA5E7A9263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift in Sources */, DBF156E22702DA6900EC00B7 /* UIStatusBarManager+HandleTapAction.m in Sources */, - 2D32EABA25CB9B0500C9ED86 /* UIView.swift in Sources */, 2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */, DB6F5E35264E78E7009108F4 /* AutoCompleteViewController.swift in Sources */, DB697DE1278F5296004EF2F7 /* DataSourceFacade+Model.swift in Sources */, DBCC3B8F26148F7B0045B23D /* CachedProfileViewModel.swift in Sources */, DB4F097526A037F500D62E92 /* SearchHistoryViewModel.swift in Sources */, + DB3EA8E9281B7A3700598866 /* DiscoveryCommunityViewModel.swift in Sources */, DB6180F826391D660018D199 /* MediaPreviewingViewController.swift in Sources */, + DBEFCD71282A12B200C0ABEA /* ReportReasonViewController.swift in Sources */, DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */, - DB71C7CB271D5A0300BE3819 /* LineChartView.swift in Sources */, DB98EB5627B0FF1B0082E365 /* ReportViewControllerAppearance.swift in Sources */, DB938F1526241FDF00E5B6C1 /* APIService+Thread.swift in Sources */, + DB3EA8E6281B79E200598866 /* DiscoveryCommunityViewController.swift in Sources */, 2D206B8625F5FB0900143C56 /* Double.swift in Sources */, DB9F58F126EF512300E7BBE9 /* AccountListTableViewCell.swift in Sources */, 2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */, @@ -4158,6 +4243,7 @@ DBD376AC2692ECDB007FEC24 /* ThemePreference.swift in Sources */, DB4F097D26A03A5B00D62E92 /* SearchHistoryItem.swift in Sources */, DBD5B1FA27BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift in Sources */, + DB3E6FE22806A50100B035AE /* DiscoveryHashtagsViewModel+Diffable.swift in Sources */, DB68046C2636DC9E00430867 /* MastodonNotification.swift in Sources */, DBAE3F9E2616E308004B8251 /* APIService+Mute.swift in Sources */, DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */, @@ -4170,10 +4256,10 @@ 2D7867192625B77500211898 /* NotificationItem.swift in Sources */, DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */, DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */, + DBEFCD74282A130400C0ABEA /* ReportReasonViewModel.swift in Sources */, 2D5A3D0325CF8742002347D6 /* ControlContainableScrollViews.swift in Sources */, DB36679D268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift in Sources */, DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */, - DB894CC427A5490600684B74 /* BlurhashImageCacheService.swift in Sources */, DBFEF07B26A6BCE8006D7ED1 /* APIService+Status+Publish.swift in Sources */, DBA0A11325FB3FC10079C110 /* ComposeToolbarView.swift in Sources */, 5B90C48526259BF10002E742 /* APIService+Subscriptions.swift in Sources */, @@ -4188,23 +4274,23 @@ DB73BF45271195AC00781945 /* APIService+CoreData+Instance.swift in Sources */, DB336F21278D6D960031E64B /* MastodonEmoji.swift in Sources */, DB1D84382657B275000346B3 /* SegmentedControlNavigateable.swift in Sources */, + DB3EA8ED281B810100598866 /* APIService+PublicTimeline.swift in Sources */, DB447697260B439000B66B82 /* CustomEmojiPickerHeaderCollectionReusableView.swift in Sources */, DB025B97278D66D5002F581E /* MastodonUser+Property.swift in Sources */, DB45FAF925CA80A2005A8AC7 /* APIService+CoreData+MastodonAuthentication.swift in Sources */, DB0FCB6C27950E29006C02E2 /* MastodonMentionContainer.swift in Sources */, DB6D9F502635761F008423CD /* SubscriptionAlerts.swift in Sources */, 0F20220726134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift in Sources */, + DB7A9F932818F33C0016AF98 /* MastodonServerRulesViewController+Debug.swift in Sources */, DBE54AC62636C89F004E7C0B /* NotificationPreference.swift in Sources */, 2D5A3D2825CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift in Sources */, DB98339C25C96DE600AD9700 /* APIService+Account.swift in Sources */, - DB0618072785A8880030EE79 /* MastodonRegisterViewModel+Diffable.swift in Sources */, DB6B74FC272FF55800C70B6E /* UserSection.swift in Sources */, 2DF75BA725D10E1000694EC8 /* APIService+Favorite.swift in Sources */, DB0FCB862796BDA1006C02E2 /* SearchSection.swift in Sources */, DB8AF52E25C13561002E6C99 /* ViewStateStore.swift in Sources */, DB1D61CF26F1B33600DA8662 /* WelcomeViewModel.swift in Sources */, 2DA7D04A25CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift in Sources */, - DB71C7CD271D7F4300BE3819 /* CurveAlgorithm.swift in Sources */, DBD376B2269302A4007FEC24 /* UITableViewCell.swift in Sources */, DB4F0966269ED52200D62E92 /* SearchResultViewModel.swift in Sources */, DBBF1DBF2652401B00E5B703 /* AutoCompleteViewModel.swift in Sources */, @@ -4221,41 +4307,43 @@ 0F2021FB2613262F000C64BF /* HashtagTimelineViewController.swift in Sources */, DB697DDD278F521D004EF2F7 /* DataSourceFacade.swift in Sources */, DBCC3B30261440A50045B23D /* UITabBarController.swift in Sources */, + DB3E6FE42806A5B800B035AE /* DiscoverySection.swift in Sources */, DB8190C62601FF0400020C08 /* AttachmentContainerView.swift in Sources */, DB697DDB278F4DE3004EF2F7 /* DataSourceProvider+StatusTableViewCellDelegate.swift in Sources */, - DB51D173262832380062B7A1 /* BlurHashEncode.swift in Sources */, 2D32EAAC25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift in Sources */, DB87D4512609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift in Sources */, DBB45B5627B39FC9002DC5A7 /* MediaPreviewVideoViewController.swift in Sources */, - DBBC24C026A5443100398BB9 /* MastodonTheme.swift in Sources */, DB0FCB8027968F70006C02E2 /* MastodonStatusThreadViewModel.swift in Sources */, DB0FCB6E27950E6B006C02E2 /* MastodonMention.swift in Sources */, DB67D08627312E67006A36CF /* WizardViewController.swift in Sources */, DB6746EB278ED8B0008A6B94 /* PollOptionView+Configuration.swift in Sources */, DB9A489026035963008B817C /* APIService+Media.swift in Sources */, DBFEEC99279BDCDE004F81DD /* ProfileAboutViewModel.swift in Sources */, - DBBC24CF26A547AE00398BB9 /* ThemeService+Appearance.swift in Sources */, 2D198649261C0B8500F0B013 /* SearchResultSection.swift in Sources */, DB4F097B26A039FF00D62E92 /* SearchHistorySection.swift in Sources */, DBB525302611EBF3002F1F29 /* ProfilePagingViewModel.swift in Sources */, DB9F58EC26EF435000E7BBE9 /* AccountViewController.swift in Sources */, 2D5A3D6225CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift in Sources */, + DB3E6FF12806D96900B035AE /* DiscoveryNewsViewModel+Diffable.swift in Sources */, + DB3E6FF82807C45300B035AE /* DiscoveryForYouViewModel.swift in Sources */, DB49A62525FF334C00B98345 /* EmojiService+CustomEmojiViewModel+LoadState.swift in Sources */, DB4924E226312AB200E9DB22 /* NotificationService.swift in Sources */, DB6D9F6F2635807F008423CD /* Setting.swift in Sources */, DB6F5E38264E994A009108F4 /* AutoCompleteTopChevronView.swift in Sources */, DB6746F0278F463B008A6B94 /* AutoGenerateProtocolDelegate.swift in Sources */, DBB525412611ED54002F1F29 /* ProfileHeaderViewController.swift in Sources */, + DBDFF19A28055A1400557A48 /* DiscoveryViewController.swift in Sources */, DB9D6BFF25E4F5940051B173 /* ProfileViewController.swift in Sources */, DB63F756279949BD00455B82 /* Persistence+SearchHistory.swift in Sources */, 2D4AD8A226316CD200613EFC /* SelectedAccountSection.swift in Sources */, + DB3EA8F1281B9EF600598866 /* DiscoveryCommunityViewModel+Diffable.swift in Sources */, DB63F775279A997D00455B82 /* NotificationTableViewCell+ViewModel.swift in Sources */, DB98EB5927B109890082E365 /* ReportSupplementaryViewController.swift in Sources */, DB0617EB277EF3820030EE79 /* GradientBorderView.swift in Sources */, DB789A1225F9F2CC0071ACA0 /* ComposeViewModel.swift in Sources */, DB63F74B279914A000455B82 /* FollowingListViewController+DataSourceProvider.swift in Sources */, + DBEFCD7D282A2A3B00C0ABEA /* ReportServerRulesViewController.swift in Sources */, DBB525362611ECEB002F1F29 /* UserTimelineViewController.swift in Sources */, - DB6D1B3D2636857500ACB481 /* AppearancePreference.swift in Sources */, DB938F3326243D6200E5B6C1 /* TimelineTopLoaderTableViewCell.swift in Sources */, DB98EB4927B0F0CD0082E365 /* ReportStatusTableViewCell.swift in Sources */, DB3667A4268AE2370027D07F /* ComposeStatusPollTableViewCell.swift in Sources */, @@ -4265,6 +4353,7 @@ DB98EB6527B216500082E365 /* ReportResultViewModel.swift in Sources */, DB4F096A269EDAD200D62E92 /* SearchResultViewModel+State.swift in Sources */, 5BB04FF5262F0E6D0043BFF6 /* ReportSection.swift in Sources */, + DBEFCD82282A2AB100C0ABEA /* ReportServerRulesView.swift in Sources */, DBA94436265CBB7400C537E1 /* ProfileFieldItem.swift in Sources */, DB023D2A27A0FE5C005AC798 /* DataSourceProvider+NotificationTableViewCellDelegate.swift in Sources */, DB98EB6027B10E150082E365 /* ReportCommentTableViewCell.swift in Sources */, @@ -4280,7 +4369,6 @@ DB6180ED26391C6C0018D199 /* TransitioningMath.swift in Sources */, DB63F771279A858500455B82 /* Persistence+Notification.swift in Sources */, 2D6DE40026141DF600A63F6A /* SearchViewModel.swift in Sources */, - DB51D172262832380062B7A1 /* BlurHashDecode.swift in Sources */, DBCCC71E25F73297007E1AB6 /* APIService+Reblog.swift in Sources */, DB0617FD27855BFE0030EE79 /* ServerRuleItem.swift in Sources */, 5BB04FD5262E7AFF0043BFF6 /* ReportViewController.swift in Sources */, @@ -4309,7 +4397,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - DB6804D12637CE4700430867 /* UserDefaults.swift in Sources */, DB73BF3B2711885500781945 /* UserDefaults+Notification.swift in Sources */, DB4932B726F30F0700EF46D4 /* Array.swift in Sources */, DB6804FD2637CFEC00430867 /* AppSecret.swift in Sources */, @@ -4346,17 +4433,12 @@ DBFEF07526A69192006D7ED1 /* APIService+Media.swift in Sources */, DBFEF06F26A690C4006D7ED1 /* APIService+APIError.swift in Sources */, DBFEF05C26A57715006D7ED1 /* StatusEditorView.swift in Sources */, - DBBC24C726A5456400398BB9 /* SystemTheme.swift in Sources */, - DBBC24C826A5456400398BB9 /* ThemeService.swift in Sources */, - DBBC24C926A5456400398BB9 /* MastodonTheme.swift in Sources */, DBFEF07C26A6BD0A006D7ED1 /* APIService+Status+Publish.swift in Sources */, DBB3BA2B26A81D060004F2D4 /* FLAnimatedImageView.swift in Sources */, DB6746E8278ED639008A6B94 /* MastodonAuthenticationBox.swift in Sources */, DBBC24A826A52F9000398BB9 /* ComposeToolbarView.swift in Sources */, DBFEF05B26A57715006D7ED1 /* ComposeViewModel.swift in Sources */, - DBBC24C626A5456000398BB9 /* Theme.swift in Sources */, DBFEF06326A577F2006D7ED1 /* StatusAttachmentViewModel.swift in Sources */, - DBFEF06926A67E45006D7ED1 /* AppearancePreference.swift in Sources */, DBC6461526A170AB00B0E31B /* ShareViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4452,6 +4534,12 @@ DBEB19E927E4F37B00B0E80E /* ku */, DBF81C7427F68F5A00004A56 /* kab */, DBF81C7727F6913300004A56 /* vi */, + DB8D8E3128196FA0009FD90F /* sv */, + DB519B09281BCA2E00F0C99D /* ckb */, + DB519B0C281BCAC300F0C99D /* zh-Hant */, + DB519B0F281BCB3300F0C99D /* gl */, + DB519B12281BCBC400F0C99D /* it */, + DB519B15281BCC2F00F0C99D /* tr */, ); name = Intents.intentdefinition; sourceTree = ""; @@ -4477,6 +4565,12 @@ DBEB19EA27E4F37B00B0E80E /* ku */, DBF81C7527F68F5A00004A56 /* kab */, DBF81C7827F6913300004A56 /* vi */, + DB8D8E3228196FA0009FD90F /* sv */, + DB519B0A281BCA2E00F0C99D /* ckb */, + DB519B0D281BCAC300F0C99D /* zh-Hant */, + DB519B10281BCB3300F0C99D /* gl */, + DB519B13281BCBC400F0C99D /* it */, + DB519B16281BCC2F00F0C99D /* tr */, ); name = InfoPlist.strings; sourceTree = ""; @@ -4518,6 +4612,12 @@ DBEB19EB27E4F37B00B0E80E /* ku */, DBF81C7627F68F5A00004A56 /* kab */, DBF81C7927F6913300004A56 /* vi */, + DB8D8E3328196FA0009FD90F /* sv */, + DB519B0B281BCA4300F0C99D /* ckb */, + DB519B0E281BCAC300F0C99D /* zh-Hant */, + DB519B11281BCB3400F0C99D /* gl */, + DB519B14281BCBC400F0C99D /* it */, + DB519B17281BCC2F00F0C99D /* tr */, ); name = Intents.stringsdict; sourceTree = ""; @@ -4568,7 +4668,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -4661,8 +4761,9 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 127; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4690,8 +4791,9 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 127; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4799,11 +4901,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 127; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 109; + DYLIB_CURRENT_VERSION = 127; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -4830,11 +4932,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 127; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 109; + DYLIB_CURRENT_VERSION = 127; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -4853,13 +4955,247 @@ }; name = Release; }; - DB8FABD026AEC7B2008E5AF4 /* Debug */ = { + DB848E2A282B5E6300A302CC /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "PROFILE=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INTENTS_CODEGEN_LANGUAGE = Swift; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = PROFILE; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + DB848E2B282B5E6300A302CC /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7CB58D292DA7ACEF179A9050 /* Pods-Mastodon.profile.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 127; + DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; + DEVELOPMENT_TEAM = 5Z4GVSS33P; + INFOPLIST_FILE = Mastodon/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.7; + PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Mastodon/Vender/Mastodon-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + DB848E2C282B5E6300A302CC /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DF65937EC1FF64462BC002EE /* Pods-MastodonTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 5Z4GVSS33P; + INFOPLIST_FILE = MastodonTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.MastodonTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Mastodon.app/Mastodon"; + }; + name = Profile; + }; + DB848E2D282B5E6300A302CC /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 728DE51ADA27C395C6E1BAB5 /* Pods-Mastodon-MastodonUITests.profile.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 5Z4GVSS33P; + INFOPLIST_FILE = MastodonUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.MastodonUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = Mastodon; + }; + name = Profile; + }; + DB848E2E282B5E6300A302CC /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 63EF9E6E5B575CD2A8B0475D /* Pods-AppShared.profile.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 127; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 5Z4GVSS33P; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 127; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = AppShared/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.AppShared; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Profile; + }; + DB848E2F282B5E6300A302CC /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 127; + DEVELOPMENT_TEAM = 5Z4GVSS33P; + INFOPLIST_FILE = NotificationService/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0.7; + PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + DB848E30282B5E6300A302CC /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 127; + DEVELOPMENT_TEAM = 5Z4GVSS33P; + INFOPLIST_FILE = ShareActionExtension/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0.7; + PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + DB848E31282B5E6300A302CC /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 861BE60ED27430771CFD578D /* Pods-MastodonIntent.debug.xcconfig */; buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 127; + DEVELOPMENT_TEAM = 5Z4GVSS33P; + INFOPLIST_FILE = MastodonIntent/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0.7; + PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.MastodonIntent; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + DB8FABD026AEC7B2008E5AF4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 127; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4880,11 +5216,10 @@ }; DB8FABD326AEC7B2008E5AF4 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 159AC43EFE0A1F95FCB358A4 /* Pods-MastodonIntent.release.xcconfig */; buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 127; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4905,11 +5240,10 @@ }; DBC6461D26A170AB00B0E31B /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 77EE917BC055E6621C0452B6 /* Pods-ShareActionExtension.debug.xcconfig */; buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 127; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4930,11 +5264,10 @@ }; DBC6462026A170AB00B0E31B /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 95AD0663479892A2109EEFD0 /* Pods-ShareActionExtension.release.xcconfig */; buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 127; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5019,8 +5352,9 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 127; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -5087,11 +5421,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 127; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 109; + DYLIB_CURRENT_VERSION = 127; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5112,11 +5446,10 @@ }; DBEB19E627E4658E00B0E80E /* Release Snapshot */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 8ADD558BE5B8255E5764A54F /* Pods-NotificationService.release snapshot.xcconfig */; buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 127; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5136,11 +5469,10 @@ }; DBEB19E727E4658E00B0E80E /* Release Snapshot */ = { isa = XCBuildConfiguration; - baseConfigurationReference = F43DF6E8AB8C87914A64FC48 /* Pods-ShareActionExtension.release snapshot.xcconfig */; buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 127; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5161,11 +5493,10 @@ }; DBEB19E827E4658E00B0E80E /* Release Snapshot */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 2C12EB4B3699D5D597027962 /* Pods-MastodonIntent.release snapshot.xcconfig */; buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 127; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5186,11 +5517,10 @@ }; DBF8AE1C263293E400C9C23C /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9553C689FFA9EBC880CAB78D /* Pods-NotificationService.debug.xcconfig */; buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 127; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5210,11 +5540,10 @@ }; DBF8AE1D263293E400C9C23C /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9776D7C4B79101CF70181127 /* Pods-NotificationService.release.xcconfig */; buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 127; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5239,6 +5568,7 @@ isa = XCConfigurationList; buildConfigurations = ( DB427DFA25BAA00100D1B89D /* Debug */, + DB848E2A282B5E6300A302CC /* Profile */, DB427DFB25BAA00100D1B89D /* Release */, DBEB19E127E4658E00B0E80E /* Release Snapshot */, ); @@ -5249,6 +5579,7 @@ isa = XCConfigurationList; buildConfigurations = ( DB427DFD25BAA00100D1B89D /* Debug */, + DB848E2B282B5E6300A302CC /* Profile */, DB427DFE25BAA00100D1B89D /* Release */, DBEB19E227E4658E00B0E80E /* Release Snapshot */, ); @@ -5259,6 +5590,7 @@ isa = XCConfigurationList; buildConfigurations = ( DB427E0025BAA00100D1B89D /* Debug */, + DB848E2C282B5E6300A302CC /* Profile */, DB427E0125BAA00100D1B89D /* Release */, DBEB19E327E4658E00B0E80E /* Release Snapshot */, ); @@ -5269,6 +5601,7 @@ isa = XCConfigurationList; buildConfigurations = ( DB427E0325BAA00100D1B89D /* Debug */, + DB848E2D282B5E6300A302CC /* Profile */, DB427E0425BAA00100D1B89D /* Release */, DBEB19E427E4658E00B0E80E /* Release Snapshot */, ); @@ -5279,6 +5612,7 @@ isa = XCConfigurationList; buildConfigurations = ( DB6804892637CD4C00430867 /* Debug */, + DB848E2E282B5E6300A302CC /* Profile */, DB68048A2637CD4C00430867 /* Release */, DBEB19E527E4658E00B0E80E /* Release Snapshot */, ); @@ -5289,6 +5623,7 @@ isa = XCConfigurationList; buildConfigurations = ( DB8FABD026AEC7B2008E5AF4 /* Debug */, + DB848E31282B5E6300A302CC /* Profile */, DB8FABD326AEC7B2008E5AF4 /* Release */, DBEB19E827E4658E00B0E80E /* Release Snapshot */, ); @@ -5299,6 +5634,7 @@ isa = XCConfigurationList; buildConfigurations = ( DBC6461D26A170AB00B0E31B /* Debug */, + DB848E30282B5E6300A302CC /* Profile */, DBC6462026A170AB00B0E31B /* Release */, DBEB19E727E4658E00B0E80E /* Release Snapshot */, ); @@ -5309,6 +5645,7 @@ isa = XCConfigurationList; buildConfigurations = ( DBF8AE1C263293E400C9C23C /* Debug */, + DB848E2F282B5E6300A302CC /* Profile */, DBF8AE1D263293E400C9C23C /* Release */, DBEB19E627E4658E00B0E80E /* Release Snapshot */, ); @@ -5355,7 +5692,7 @@ repositoryURL = "https://github.com/TwidereProject/MetaTextKit.git"; requirement = { kind = exactVersion; - version = 2.2.1; + version = 2.2.3; }; }; DB0E2D2C26833FF600865C3C /* XCRemoteSwiftPackageReference "Nuke-FLAnimatedImage-Plugin" */ = { @@ -5374,6 +5711,14 @@ minimumVersion = 4.1.0; }; }; + DB3EA8F6281BBA4C00598866 /* XCRemoteSwiftPackageReference "Alamofire" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Alamofire/Alamofire.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.4.0; + }; + }; DB552D4D26BBD10C00E481F6 /* XCRemoteSwiftPackageReference "swift-collections" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/apple/swift-collections.git"; @@ -5390,12 +5735,12 @@ minimumVersion = 4.2.2; }; }; - DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */ = { + DB8D8E2D28192EED009FD90F /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/MainasuK/UITextView-Placeholder"; + repositoryURL = "https://github.com/siteline/SwiftUI-Introspect.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 1.4.1; + minimumVersion = 0.1.4; }; }; DBA5A52D26F07ED800CACBAA /* XCRemoteSwiftPackageReference "PanModal" */ = { @@ -5464,64 +5809,85 @@ package = 2D939AC625EE14620076FA61 /* XCRemoteSwiftPackageReference "TOCropViewController" */; productName = CropViewController; }; - 5D526FE125BE9AC400460CB9 /* MastodonSDK */ = { + DB02EA0A280D180D00E751C5 /* KeychainAccess */ = { + isa = XCSwiftPackageProductDependency; + package = DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */; + productName = KeychainAccess; + }; + DB3EA8F4281BB65200598866 /* MastodonSDK */ = { isa = XCSwiftPackageProductDependency; productName = MastodonSDK; }; - DB00CA962632DDB600A54956 /* CommonOSLog */ = { + DB3EA8FB281BBAE100598866 /* AlamofireImage */ = { isa = XCSwiftPackageProductDependency; - package = DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */; - productName = CommonOSLog; + package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */; + productName = AlamofireImage; }; - DB0140BC25C40D7500F9F3CF /* CommonOSLog */ = { + DB3EA8FD281BBAF200598866 /* Alamofire */ = { isa = XCSwiftPackageProductDependency; - package = DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */; - productName = CommonOSLog; + package = DB3EA8F6281BBA4C00598866 /* XCRemoteSwiftPackageReference "Alamofire" */; + productName = Alamofire; }; - DB01E23226A98F0900C3965B /* MastodonMeta */ = { - isa = XCSwiftPackageProductDependency; - package = DB01E23126A98F0900C3965B /* XCRemoteSwiftPackageReference "MetaTextKit" */; - productName = MastodonMeta; - }; - DB01E23426A98F0900C3965B /* MetaTextKit */ = { + DB3EA8FF281BBB1D00598866 /* MetaTextKit */ = { isa = XCSwiftPackageProductDependency; package = DB01E23126A98F0900C3965B /* XCRemoteSwiftPackageReference "MetaTextKit" */; productName = MetaTextKit; }; - DB0C946426A6FD4D0088FB11 /* AlamofireImage */ = { + DB3EA901281BBD5D00598866 /* CommonOSLog */ = { + isa = XCSwiftPackageProductDependency; + package = DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */; + productName = CommonOSLog; + }; + DB3EA903281BBD9400598866 /* Introspect */ = { + isa = XCSwiftPackageProductDependency; + package = DB8D8E2D28192EED009FD90F /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; + productName = Introspect; + }; + DB3EA905281BBE8200598866 /* AlamofireImage */ = { isa = XCSwiftPackageProductDependency; package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */; productName = AlamofireImage; }; - DB179266278D5A4A00B71DEB /* MastodonSDK */ = { + DB3EA907281BBE8200598866 /* AlamofireNetworkActivityIndicator */ = { isa = XCSwiftPackageProductDependency; - productName = MastodonSDK; + package = 2D61336725C18A4F00CAE157 /* XCRemoteSwiftPackageReference "AlamofireNetworkActivityIndicator" */; + productName = AlamofireNetworkActivityIndicator; }; - DB3D0FF225BAA61700EAA174 /* AlamofireImage */ = { + DB3EA909281BBE8200598866 /* Alamofire */ = { + isa = XCSwiftPackageProductDependency; + package = DB3EA8F6281BBA4C00598866 /* XCRemoteSwiftPackageReference "Alamofire" */; + productName = Alamofire; + }; + DB3EA90B281BBE9600598866 /* AlamofireImage */ = { isa = XCSwiftPackageProductDependency; package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */; productName = AlamofireImage; }; + DB3EA90D281BBE9600598866 /* AlamofireNetworkActivityIndicator */ = { + isa = XCSwiftPackageProductDependency; + package = 2D61336725C18A4F00CAE157 /* XCRemoteSwiftPackageReference "AlamofireNetworkActivityIndicator" */; + productName = AlamofireNetworkActivityIndicator; + }; + DB3EA90F281BBE9600598866 /* Alamofire */ = { + isa = XCSwiftPackageProductDependency; + package = DB3EA8F6281BBA4C00598866 /* XCRemoteSwiftPackageReference "Alamofire" */; + productName = Alamofire; + }; + DB3EA911281BBEA800598866 /* AlamofireImage */ = { + isa = XCSwiftPackageProductDependency; + package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */; + productName = AlamofireImage; + }; + DB3EA913281BBEA800598866 /* Alamofire */ = { + isa = XCSwiftPackageProductDependency; + package = DB3EA8F6281BBA4C00598866 /* XCRemoteSwiftPackageReference "Alamofire" */; + productName = Alamofire; + }; DB552D4E26BBD10C00E481F6 /* OrderedCollections */ = { isa = XCSwiftPackageProductDependency; package = DB552D4D26BBD10C00E481F6 /* XCRemoteSwiftPackageReference "swift-collections" */; productName = OrderedCollections; }; - DB68050F2637D0F800430867 /* KeychainAccess */ = { - isa = XCSwiftPackageProductDependency; - package = DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */; - productName = KeychainAccess; - }; - DB6D9F41263527CE008423CD /* AlamofireImage */ = { - isa = XCSwiftPackageProductDependency; - package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */; - productName = AlamofireImage; - }; - DB9A487D2603456B008B817C /* UITextView+Placeholder */ = { - isa = XCSwiftPackageProductDependency; - package = DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */; - productName = "UITextView+Placeholder"; - }; DBA5A52E26F07ED800CACBAA /* PanModal */ = { isa = XCSwiftPackageProductDependency; package = DBA5A52D26F07ED800CACBAA /* XCRemoteSwiftPackageReference "PanModal" */; @@ -5547,24 +5913,6 @@ package = DBB525062611EAC0002F1F29 /* XCRemoteSwiftPackageReference "Tabman" */; productName = Tabman; }; - DBB8AB4726AED09C00F6D281 /* MastodonSDK */ = { - isa = XCSwiftPackageProductDependency; - productName = MastodonSDK; - }; - DBBC24A926A5301B00398BB9 /* MastodonSDK */ = { - isa = XCSwiftPackageProductDependency; - productName = MastodonSDK; - }; - DBBC24B726A5421800398BB9 /* CommonOSLog */ = { - isa = XCSwiftPackageProductDependency; - package = DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */; - productName = CommonOSLog; - }; - DBBC24D026A5484F00398BB9 /* UITextView+Placeholder */ = { - isa = XCSwiftPackageProductDependency; - package = DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */; - productName = "UITextView+Placeholder"; - }; DBF7A0FB26830C33004176A2 /* FPSIndicator */ = { isa = XCSwiftPackageProductDependency; package = DBF7A0FA26830C33004176A2 /* XCRemoteSwiftPackageReference "FPSIndicator" */; diff --git a/Mastodon.xcodeproj/xcshareddata/xcschemes/Mastodon - Release.xcscheme b/Mastodon.xcodeproj/xcshareddata/xcschemes/Mastodon - Release.xcscheme index d5959cead..f88978596 100644 --- a/Mastodon.xcodeproj/xcshareddata/xcschemes/Mastodon - Release.xcscheme +++ b/Mastodon.xcodeproj/xcshareddata/xcschemes/Mastodon - Release.xcscheme @@ -1,6 +1,6 @@ isShown orderHint - 4 + 5 CoreDataStack.xcscheme_^#shared#^_ orderHint 27 - Mastodon - RTL.xcscheme_^#shared#^_ - - orderHint - 19 - - Mastodon - Release.xcscheme_^#shared#^_ + Mastodon - Profile.xcscheme_^#shared#^_ orderHint 1 - Mastodon - Snapshot.xcscheme_^#shared#^_ + Mastodon - RTL.xcscheme_^#shared#^_ + + orderHint + 8 + + Mastodon - Release.xcscheme_^#shared#^_ orderHint 2 - Mastodon - ar.xcscheme + Mastodon - Snapshot.xcscheme_^#shared#^_ orderHint 3 + Mastodon - ar.xcscheme + + orderHint + 4 + Mastodon - ar.xcscheme_^#shared#^_ orderHint @@ -109,7 +114,7 @@ MastodonIntent.xcscheme_^#shared#^_ orderHint - 24 + 31 MastodonIntents.xcscheme_^#shared#^_ @@ -124,12 +129,12 @@ NotificationService.xcscheme_^#shared#^_ orderHint - 22 + 30 ShareActionExtension.xcscheme_^#shared#^_ orderHint - 23 + 32 SuppressBuildableAutocreation diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved index 11d453883..cca51e911 100644 --- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/Alamofire/Alamofire.git", "state": { "branch": null, - "revision": "f82c23a8a7ef8dc1a49a8bfc6a96883e79121864", - "version": "5.5.0" + "revision": "354dda32d89fc8cd4f5c46487f64957d355f53d8", + "version": "5.6.1" } }, { @@ -55,6 +55,15 @@ "version": "1.2.0" } }, + { + "package": "FaviconFinder", + "repositoryURL": "https://github.com/will-lumley/FaviconFinder.git", + "state": { + "branch": null, + "revision": "1f74844f77f79b95c0bb0130b3a87d4f340e6d3a", + "version": "3.3.0" + } + }, { "package": "FLAnimatedImage", "repositoryURL": "https://github.com/Flipboard/FLAnimatedImage.git", @@ -96,8 +105,8 @@ "repositoryURL": "https://github.com/TwidereProject/MetaTextKit.git", "state": { "branch": null, - "revision": "3ea336d3de7938dc112084c596a646e697b0feee", - "version": "2.2.1" + "revision": "2b9556a78b2986b8c0b04adc6da8ec206b448a0c", + "version": "2.2.3" } }, { @@ -105,8 +114,8 @@ "repositoryURL": "https://github.com/kean/Nuke.git", "state": { "branch": null, - "revision": "0db18dd34998cca18e9a28bcee136f84518007a0", - "version": "10.4.1" + "revision": "0ea7545b5c918285aacc044dc75048625c8257cc", + "version": "10.8.0" } }, { @@ -141,8 +150,8 @@ "repositoryURL": "https://github.com/SDWebImage/SDWebImage.git", "state": { "branch": null, - "revision": "2c53f531f1bedd253f55d85105409c28ed4a922c", - "version": "5.12.3" + "revision": "2e63d0061da449ad0ed130768d05dceb1496de44", + "version": "5.12.5" } }, { @@ -172,13 +181,22 @@ "version": "1.0.0" } }, + { + "package": "SwiftSoup", + "repositoryURL": "https://github.com/scinfu/SwiftSoup.git", + "state": { + "branch": null, + "revision": "41e7c263fb8c277e980ebcb9b0b5f6031d3d4886", + "version": "2.4.2" + } + }, { "package": "Introspect", "repositoryURL": "https://github.com/siteline/SwiftUI-Introspect.git", "state": { "branch": null, - "revision": "2e09be8af614401bc9f87d40093ec19ce56ccaf2", - "version": "0.1.3" + "revision": "f2616860a41f9d9932da412a8978fec79c06fe24", + "version": "0.1.4" } }, { diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index c8ce4acbd..d149e63a2 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -144,7 +144,7 @@ extension SceneCoordinator { case popover(sourceView: UIView) case panModal case custom(transitioningDelegate: UIViewControllerTransitioningDelegate) - case customPush + case customPush(animated: Bool) case safariPresent(animated: Bool, completion: (() -> Void)? = nil) case alertController(animated: Bool, completion: (() -> Void)? = nil) case activityViewControllerPresent(animated: Bool, completion: (() -> Void)? = nil) @@ -158,7 +158,7 @@ extension SceneCoordinator { case mastodonServerRules(viewModel: MastodonServerRulesViewModel) case mastodonConfirmEmail(viewModel: MastodonConfirmEmailViewModel) case mastodonResendEmail(viewModel: MastodonResendEmailViewModel) - case mastodonWebView(viewModel:WebViewModel) + case mastodonWebView(viewModel: WebViewModel) // search case searchDetail(viewModel: SearchDetailViewModel) @@ -184,6 +184,8 @@ extension SceneCoordinator { // report case report(viewModel: ReportViewModel) + case reportServerRules(viewModel: ReportServerRulesViewModel) + case reportStatus(viewModel: ReportStatusViewModel) case reportSupplementary(viewModel: ReportSupplementaryViewModel) case reportResult(viewModel: ReportResultViewModel) @@ -309,7 +311,7 @@ extension SceneCoordinator { if scene.isOnboarding { return OnboardingNavigationController(rootViewController: viewController) } else { - return UINavigationController(rootViewController: viewController) + return AdaptiveStatusBarStyleNavigationController(rootViewController: viewController) } }() modalNavigationController.modalPresentationCapturesStatusBarAppearance = true @@ -339,10 +341,10 @@ extension SceneCoordinator { viewController.transitioningDelegate = transitioningDelegate (splitViewController ?? presentingViewController)?.present(viewController, animated: true, completion: nil) - case .customPush: + case .customPush(let animated): // set delegate in view controller assert(sender?.navigationController?.delegate != nil) - sender?.navigationController?.pushViewController(viewController, animated: true) + sender?.navigationController?.pushViewController(viewController, animated: animated) case .safariPresent(let animated, let completion): if UserDefaults.shared.preferredUsingDefaultBrowser, case let .safari(url) = scene { @@ -368,10 +370,10 @@ extension SceneCoordinator { splitViewController?.contentSplitViewController.currentSupplementaryTab = tab splitViewController?.compactMainTabBarViewController.selectedIndex = tab.rawValue - splitViewController?.compactMainTabBarViewController.currentTab.value = tab + splitViewController?.compactMainTabBarViewController.currentTab = tab tabBarController.selectedIndex = tab.rawValue - tabBarController.currentTab.value = tab + tabBarController.currentTab = tab } } @@ -447,6 +449,14 @@ private extension SceneCoordinator { let _viewController = ReportViewController() _viewController.viewModel = viewModel viewController = _viewController + case .reportServerRules(let viewModel): + let _viewController = ReportServerRulesViewController() + _viewController.viewModel = viewModel + viewController = _viewController + case .reportStatus(let viewModel): + let _viewController = ReportStatusViewController() + _viewController.viewModel = viewModel + viewController = _viewController case .reportSupplementary(let viewModel): let _viewController = ReportSupplementaryViewController() _viewController.viewModel = viewModel diff --git a/Mastodon/Diffiable/Discovery/DiscoveryItem.swift b/Mastodon/Diffiable/Discovery/DiscoveryItem.swift new file mode 100644 index 000000000..024c4a2da --- /dev/null +++ b/Mastodon/Diffiable/Discovery/DiscoveryItem.swift @@ -0,0 +1,17 @@ +// +// DiscoveryItem.swift +// Mastodon +// +// Created by MainasuK on 2022-4-13. +// + +import Foundation +import MastodonSDK +import CoreDataStack + +enum DiscoveryItem: Hashable { + case hashtag(Mastodon.Entity.Tag) + case link(Mastodon.Entity.Link) + case user(ManagedObjectRecord) + case bottomLoader +} diff --git a/Mastodon/Diffiable/Discovery/DiscoverySection.swift b/Mastodon/Diffiable/Discovery/DiscoverySection.swift new file mode 100644 index 000000000..cab2eb82b --- /dev/null +++ b/Mastodon/Diffiable/Discovery/DiscoverySection.swift @@ -0,0 +1,74 @@ +// +// DiscoverySection.swift +// Mastodon +// +// Created by MainasuK on 2022-4-13. +// + +import os.log +import UIKit +import MastodonUI + +enum DiscoverySection: CaseIterable { + // case posts + case hashtags + case news + case forYou +} + +extension DiscoverySection { + + static let logger = Logger(subsystem: "DiscoverySection", category: "logic") + + class Configuration { + weak var profileCardTableViewCellDelegate: ProfileCardTableViewCellDelegate? + + public init(profileCardTableViewCellDelegate: ProfileCardTableViewCellDelegate? = nil) { + self.profileCardTableViewCellDelegate = profileCardTableViewCellDelegate + } + } + + static func diffableDataSource( + tableView: UITableView, + context: AppContext, + configuration: Configuration + ) -> UITableViewDiffableDataSource { + tableView.register(TrendTableViewCell.self, forCellReuseIdentifier: String(describing: TrendTableViewCell.self)) + tableView.register(NewsTableViewCell.self, forCellReuseIdentifier: String(describing: NewsTableViewCell.self)) + tableView.register(ProfileCardTableViewCell.self, forCellReuseIdentifier: String(describing: ProfileCardTableViewCell.self)) + tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self)) + + return UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item in + switch item { + case .hashtag(let tag): + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TrendTableViewCell.self), for: indexPath) as! TrendTableViewCell + cell.trendView.configure(tag: tag) + return cell + case .link(let link): + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NewsTableViewCell.self), for: indexPath) as! NewsTableViewCell + cell.newsView.configure(link: link) + return cell + case .user(let record): + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ProfileCardTableViewCell.self), for: indexPath) as! ProfileCardTableViewCell + context.managedObjectContext.performAndWait { + guard let user = record.object(in: context.managedObjectContext) else { return } + cell.configure( + tableView: tableView, + user: user, + profileCardTableViewCellDelegate: configuration.profileCardTableViewCellDelegate + ) + } + context.authenticationService.activeMastodonAuthentication + .map { $0?.user } + .assign(to: \.me, on: cell.profileCardView.viewModel.relationshipViewModel) + .store(in: &cell.disposeBag) + return cell + case .bottomLoader: + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell + cell.activityIndicatorView.startAnimating() + return cell + } + } + } + +} diff --git a/Mastodon/Diffiable/Onboarding/PickServerSection.swift b/Mastodon/Diffiable/Onboarding/PickServerSection.swift index 5faaefbcc..01a31f6f6 100644 --- a/Mastodon/Diffiable/Onboarding/PickServerSection.swift +++ b/Mastodon/Diffiable/Onboarding/PickServerSection.swift @@ -29,7 +29,7 @@ extension PickServerSection { weak dependency, weak pickServerCellDelegate ] tableView, indexPath, item -> UITableViewCell? in - guard let dependency = dependency else { return nil } + guard let _ = dependency else { return nil } switch item { case .header: let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: OnboardingHeadlineTableViewCell.self), for: indexPath) as! OnboardingHeadlineTableViewCell diff --git a/Mastodon/Diffiable/Search/SearchHistorySection.swift b/Mastodon/Diffiable/Search/SearchHistorySection.swift index dba1dc18a..557b49f2b 100644 --- a/Mastodon/Diffiable/Search/SearchHistorySection.swift +++ b/Mastodon/Diffiable/Search/SearchHistorySection.swift @@ -69,10 +69,10 @@ extension SearchHistorySection { let trendHeaderRegister = UICollectionView.SupplementaryRegistration(elementKind: UICollectionView.elementKindSectionHeader) { [weak dataSource] supplementaryView, elementKind, indexPath in supplementaryView.delegate = configuration.searchHistorySectionHeaderCollectionReusableViewDelegate - guard let dataSource = dataSource else { return } - let sections = dataSource.snapshot().sectionIdentifiers - guard indexPath.section < sections.count else { return } - let section = sections[indexPath.section] + guard let _ = dataSource else { return } + // let sections = dataSource.snapshot().sectionIdentifiers + // guard indexPath.section < sections.count else { return } + // let section = sections[indexPath.section] } dataSource.supplementaryViewProvider = { (collectionView: UICollectionView, elementKind: String, indexPath: IndexPath) in diff --git a/Mastodon/Diffiable/Search/SearchSection.swift b/Mastodon/Diffiable/Search/SearchSection.swift index 21f1d479c..4f550abf7 100644 --- a/Mastodon/Diffiable/Search/SearchSection.swift +++ b/Mastodon/Diffiable/Search/SearchSection.swift @@ -21,26 +21,7 @@ extension SearchSection { ) -> UICollectionViewDiffableDataSource { let trendCellRegister = UICollectionView.CellRegistration { cell, indexPath, item in - let primaryLabelText = "#" + item.name - let secondaryLabelText = L10n.Scene.Search.Recommend.HashTag.peopleTalking(item.talkingPeopleCount ?? 0) - cell.primaryLabel.text = primaryLabelText - cell.secondaryLabel.text = secondaryLabelText - - cell.lineChartView.data = (item.history ?? []) - .sorted(by: { $0.day < $1.day }) // latest last - .map { entry in - guard let point = Int(entry.accounts) else { - return .zero - } - return CGFloat(point) - } - - cell.isAccessibilityElement = true - cell.accessibilityLabel = [ - primaryLabelText, - secondaryLabelText - ].joined(separator: ", ") } let dataSource = UICollectionViewDiffableDataSource( diff --git a/Mastodon/Diffiable/Settings/SettingsSection.swift b/Mastodon/Diffiable/Settings/SettingsSection.swift index adc7140be..6925303d8 100644 --- a/Mastodon/Diffiable/Settings/SettingsSection.swift +++ b/Mastodon/Diffiable/Settings/SettingsSection.swift @@ -51,7 +51,7 @@ extension SettingsSection { } cell.delegate = settingsAppearanceTableViewCellDelegate return cell - case .appearancePreference(let record, let appearanceType): + case .appearancePreference(let record, _): let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SettingsToggleTableViewCell.self), for: indexPath) as! SettingsToggleTableViewCell cell.delegate = settingsToggleCellDelegate managedObjectContext.performAndWait { diff --git a/Mastodon/Extension/CoreDataStack/MastodonUser.swift b/Mastodon/Extension/CoreDataStack/MastodonUser.swift index 02a983680..bc5f159d9 100644 --- a/Mastodon/Extension/CoreDataStack/MastodonUser.swift +++ b/Mastodon/Extension/CoreDataStack/MastodonUser.swift @@ -9,53 +9,6 @@ import Foundation import CoreDataStack import MastodonSDK -extension MastodonUser { - - public var displayNameWithFallback: String { - return !displayName.isEmpty ? displayName : username - } - - public var acctWithDomain: String { - if !acct.contains("@") { - // Safe concat due to username cannot contains "@" - return username + "@" + domain - } else { - return acct - } - } - - public var domainFromAcct: String { - if !acct.contains("@") { - return domain - } else { - let domain = acct.split(separator: "@").last - return String(domain!) - } - } - -} - -extension MastodonUser { - - public func headerImageURL() -> URL? { - return URL(string: header) - } - - public func headerImageURLWithFallback(domain: String) -> URL { - return URL(string: header) ?? URL(string: "https://\(domain)/headers/original/missing.png")! - } - - public func avatarImageURL() -> URL? { - let string = UserDefaults.shared.preferredStaticAvatar ? avatarStatic ?? avatar : avatar - return URL(string: string) - } - - public func avatarImageURLWithFallback(domain: String) -> URL { - return avatarImageURL() ?? URL(string: "https://\(domain)/avatars/original/missing.png")! - } - -} - extension MastodonUser { public var profileURL: URL { diff --git a/Mastodon/Extension/MastodonSDK/Mastodon+Entity+Tag.swift b/Mastodon/Extension/MastodonSDK/Mastodon+Entity+Tag.swift index 2d0be6965..6251d1814 100644 --- a/Mastodon/Extension/MastodonSDK/Mastodon+Entity+Tag.swift +++ b/Mastodon/Extension/MastodonSDK/Mastodon+Entity+Tag.swift @@ -7,24 +7,12 @@ import MastodonSDK -extension Mastodon.Entity.Tag: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(name) - } - - public static func == (lhs: Mastodon.Entity.Tag, rhs: Mastodon.Entity.Tag) -> Bool { - return lhs.name == rhs.name - } -} - -extension Mastodon.Entity.Tag { - - /// the sum of recent 2 days - public var talkingPeopleCount: Int? { - return history? - .prefix(2) - .compactMap { Int($0.accounts) } - .reduce(0, +) - } - -} +//extension Mastodon.Entity.Tag: Hashable { +// public func hash(into hasher: inout Hasher) { +// hasher.combine(name) +// } +// +// public static func == (lhs: Mastodon.Entity.Tag, rhs: Mastodon.Entity.Tag) -> Bool { +// return lhs.name == rhs.name +// } +//} diff --git a/Mastodon/Service/ThemeService/ThemeService+Appearance.swift b/Mastodon/Extension/MastodonUI/ThemeService.swift similarity index 97% rename from Mastodon/Service/ThemeService/ThemeService+Appearance.swift rename to Mastodon/Extension/MastodonUI/ThemeService.swift index 896ed888e..5fe213d06 100644 --- a/Mastodon/Service/ThemeService/ThemeService+Appearance.swift +++ b/Mastodon/Extension/MastodonUI/ThemeService.swift @@ -1,11 +1,13 @@ // -// ThemeService+Appearance.swift +// ThemeService.swift // Mastodon // -// Created by MainasuK Cirno on 2021-7-19. +// Created by MainasuK on 2022-4-13. // import UIKit +import MastodonCommon +import MastodonUI extension ThemeService { func set(themeName: ThemeName) { diff --git a/Mastodon/Extension/UINavigationController.swift b/Mastodon/Extension/UINavigationController.swift deleted file mode 100644 index 9a9c44ab3..000000000 --- a/Mastodon/Extension/UINavigationController.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// UINavigationController.swift -// Mastodon -// -// Created by MainasuK Cirno on 2021-3-31. -// - -import UIKit - -// This not works! -// SeeAlso: `AdaptiveStatusBarStyleNavigationController` -extension UINavigationController { - open override var childForStatusBarStyle: UIViewController? { - return visibleViewController - } -} diff --git a/Mastodon/Extension/UIView.swift b/Mastodon/Extension/UIView.swift deleted file mode 100644 index d4814b7ec..000000000 --- a/Mastodon/Extension/UIView.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// UIView.swift -// Mastodon -// -// Created by sxiaojian on 2021/2/4. -// - -import UIKit - -// MARK: - Convenience view creation method -extension UIView { - - static let separatorColor: UIColor = { - UIColor(dynamicProvider: { collection in - switch collection.userInterfaceStyle { - case .dark: - return ThemeService.shared.currentTheme.value.separator - default: - return .separator - } - }) - }() - - static var separatorLine: UIView { - let line = UIView() - line.backgroundColor = UIView.separatorColor - return line - } - - static func separatorLineHeight(of view: UIView) -> CGFloat { - return 1.0 / view.traitCollection.displayScale - } - -} - -// MARK: - Convenience view appearance modification method -extension UIView { - @discardableResult - func applyCornerRadius(radius: CGFloat) -> Self { - layer.masksToBounds = true - layer.cornerRadius = radius - layer.cornerCurve = .continuous - return self - } - - @discardableResult - func applyShadow( - color: UIColor, - alpha: Float, - x: CGFloat, - y: CGFloat, - blur: CGFloat, - spread: CGFloat = 0) -> Self - { - layer.masksToBounds = false - layer.shadowColor = color.cgColor - layer.shadowOpacity = alpha - layer.shadowOffset = CGSize(width: x, height: y) - layer.shadowRadius = blur / 2.0 - if spread == 0 { - layer.shadowPath = nil - } else { - let dx = -spread - let rect = bounds.insetBy(dx: dx, dy: dx) - layer.shadowPath = UIBezierPath(rect: rect).cgPath - } - return self - } -} - diff --git a/Mastodon/Generated/AutoGenerateTableViewDelegate.generated.swift b/Mastodon/Generated/AutoGenerateTableViewDelegate.generated.swift index ebf867007..7d6e01cf7 100644 --- a/Mastodon/Generated/AutoGenerateTableViewDelegate.generated.swift +++ b/Mastodon/Generated/AutoGenerateTableViewDelegate.generated.swift @@ -1,14 +1,7 @@ // Generated using Sourcery 1.6.1 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT - - - - - - - -// sourcery:inline:UserTimelineViewController.AutoGenerateTableViewDelegate +// sourcery:inline:DiscoveryCommunityViewController.AutoGenerateTableViewDelegate // Generated using Sourcery // DO NOT EDIT @@ -33,3 +26,13 @@ func tableView(_ tableView: UITableView, willPerformPreviewActionForMenuWith con } // sourcery:end + + + + + + + + + + diff --git a/Mastodon/Info.plist b/Mastodon/Info.plist index 311ee390b..0c3dea8f5 100644 --- a/Mastodon/Info.plist +++ b/Mastodon/Info.plist @@ -30,7 +30,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.3.0 + 1.4.2 CFBundleURLTypes @@ -43,7 +43,7 @@ CFBundleVersion - 109 + 127 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/Mastodon/Persistence/Extension/MastodonEmoji.swift b/Mastodon/Persistence/Extension/MastodonEmoji.swift index e9274a24d..2ea23c67c 100644 --- a/Mastodon/Persistence/Extension/MastodonEmoji.swift +++ b/Mastodon/Persistence/Extension/MastodonEmoji.swift @@ -22,13 +22,3 @@ extension MastodonEmoji { ) } } - -extension Collection where Element == MastodonEmoji { - public var asDictionary: MastodonContent.Emojis { - var dictionary: MastodonContent.Emojis = [:] - for emoji in self { - dictionary[emoji.code] = emoji.url - } - return dictionary - } -} diff --git a/Mastodon/Preference/ThemePreference.swift b/Mastodon/Preference/ThemePreference.swift index 624047798..5465cb22f 100644 --- a/Mastodon/Preference/ThemePreference.swift +++ b/Mastodon/Preference/ThemePreference.swift @@ -5,17 +5,3 @@ // Created by MainasuK Cirno on 2021-7-5. // -import UIKit -import MastodonExtension - -extension UserDefaults { - - @objc dynamic var currentThemeNameRawValue: String { - get { - register(defaults: [#function: ThemeName.mastodon.rawValue]) - return string(forKey: #function) ?? ThemeName.mastodon.rawValue - } - set { self[#function] = newValue } - } - -} diff --git a/Mastodon/Protocol/PageboyNavigateable.swift b/Mastodon/Protocol/PageboyNavigateable.swift new file mode 100644 index 000000000..8ff59ef4f --- /dev/null +++ b/Mastodon/Protocol/PageboyNavigateable.swift @@ -0,0 +1,92 @@ +// +// PageboyNavigateable.swift +// Mastodon +// +// Created by MainasuK on 2022-5-11. +// + +import UIKit +import Pageboy +import MastodonLocalization + +typealias PageboyNavigateable = PageboyNavigateableCore & PageboyNavigateableRelay + +protocol PageboyNavigateableCore: AnyObject { + var navigateablePageViewController: PageboyViewController { get } + var pageboyNavigateKeyCommands: [UIKeyCommand] { get } + + func pageboyNavigateKeyCommandHandler(_ sender: UIKeyCommand) + func navigate(direction: PageboyNavigationDirection) +} + +@objc protocol PageboyNavigateableRelay: AnyObject { + func pageboyNavigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) +} + +enum PageboyNavigationDirection: String, CaseIterable { + case previous + case next + + var title: String { + switch self { + case .previous: return L10n.Common.Controls.Keyboard.SegmentedControl.previousSection + case .next: return L10n.Common.Controls.Keyboard.SegmentedControl.nextSection + } + } + + // UIKeyCommand input + var input: String { + switch self { + case .previous: return "[" + case .next: return "]" + } + } + + var modifierFlags: UIKeyModifierFlags { + switch self { + case .previous: return [.shift, .command] + case .next: return [.shift, .command] + } + } + + var propertyList: Any { + return rawValue + } +} + +extension PageboyNavigateableCore where Self: PageboyNavigateableRelay { + var pageboyNavigateKeyCommands: [UIKeyCommand] { + PageboyNavigationDirection.allCases.map { direction in + UIKeyCommand( + title: direction.title, + image: nil, + action: #selector(Self.pageboyNavigateKeyCommandHandlerRelay(_:)), + input: direction.input, + modifierFlags: direction.modifierFlags, + propertyList: direction.propertyList, + alternates: [], + discoverabilityTitle: nil, + attributes: [], + state: .off + ) + } + } + + func pageboyNavigateKeyCommandHandler(_ sender: UIKeyCommand) { + guard let rawValue = sender.propertyList as? String, + let direction = PageboyNavigationDirection(rawValue: rawValue) else { return } + navigate(direction: direction) + } + +} + +extension PageboyNavigateableCore { + func navigate(direction: PageboyNavigationDirection) { + switch direction { + case .previous: + navigateablePageViewController.scrollToPage(.previous, animated: true) + case .next: + navigateablePageViewController.scrollToPage(.next, animated: true) + } + } +} diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift index bf54f70ad..7e376ed0f 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift @@ -38,11 +38,15 @@ extension DataSourceFacade { meta: Meta ) async { switch meta { + // note: + // some server mark the normal url as "u-url" class. highlighted content is a URL case .url(_, _, let url, _), .mention(_, let url, _) where url.lowercased().hasPrefix("http"): - // note: - // some server mark the normal url as "u-url" class. highlighted content is a URL - guard let url = URL(string: url) else { return } + // fix non-ascii character URL link can not open issue + guard let url = URL(string: url) ?? URL(string: url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? url) else { + assertionFailure() + return + } if let domain = provider.context.authenticationService.activeMastodonAuthenticationBox.value?.domain, url.host == domain, url.pathComponents.count >= 4, url.pathComponents[0] == "/", diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift index 36eaab621..66259a099 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift @@ -122,12 +122,12 @@ extension DataSourceFacade { let barButtonItem: UIBarButtonItem? } - @MainActor - static func createProfileActionMenu( - dependency: NeedsDependency, - user: ManagedObjectRecord - ) -> UIMenu { - var children: [UIMenuElement] = [] +// @MainActor +// static func createProfileActionMenu( +// dependency: NeedsDependency, +// user: ManagedObjectRecord +// ) -> UIMenu { +// var children: [UIMenuElement] = [] // let name = mastodonUser.displayNameWithFallback // // if let shareUser = shareUser { @@ -339,9 +339,9 @@ extension DataSourceFacade { // } // children.append(deleteAction) // } - - return UIMenu(title: "", options: [], children: children) - } +// +// return UIMenu(title: "", options: [], children: children) +// } static func createActivityViewController( dependency: NeedsDependency, diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift b/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift index cbc6bf348..8beaabbae 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift @@ -99,7 +99,7 @@ extension DataSourceFacade { try await managedObjectContext.performChanges { guard let authenticationBox = _authenticationBox else { return } - guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return } + guard let _ = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return } let request = SearchHistory.sortedFetchRequest request.predicate = SearchHistory.predicate( domain: authenticationBox.domain, diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift index eab85e95e..36ceb6dd6 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift @@ -286,24 +286,8 @@ extension DataSourceFacade { try await dependency.context.managedObjectContext.perform { guard let _status = status.object(in: dependency.context.managedObjectContext) else { return } let status = _status.reblog ?? _status - - let allToggled = status.isContentSensitiveToggled && status.isMediaSensitiveToggled - - status.update(isContentSensitiveToggled: !allToggled) - status.update(isMediaSensitiveToggled: !allToggled) + status.update(isSensitiveToggled: !status.isSensitiveToggled) } } -// static func responseToToggleMediaSensitiveAction( -// dependency: NeedsDependency, -// status: ManagedObjectRecord -// ) async throws { -// try await dependency.context.managedObjectContext.perform { -// guard let _status = status.object(in: dependency.context.managedObjectContext) else { return } -// let status = _status.reblog ?? _status -// -// status.update(isMediaSensitiveToggled: !status.isMediaSensitiveToggled) -// } -// } - } diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift index 0924028ff..ca7fdeb18 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift @@ -135,7 +135,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Med let status = _status.reblog ?? _status return NotificationMediaTransitionContext( status: .init(objectID: status.objectID), - needsToggleMediaSensitive: status.isMediaSensitiveToggled ? !status.sensitive : status.sensitive + needsToggleMediaSensitive: status.isSensitiveToggled ? !status.sensitive : status.sensitive ) } @@ -187,7 +187,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Med let status = _status.reblog ?? _status return NotificationMediaTransitionContext( status: .init(objectID: status.objectID), - needsToggleMediaSensitive: status.isMediaSensitiveToggled ? !status.sensitive : status.sensitive + needsToggleMediaSensitive: status.isMediaSensitive ? !status.isSensitiveToggled : false ) } @@ -486,7 +486,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider { provider: self, user: user ) - case .notification(let notification): + case .notification: assertionFailure("TODO") default: assertionFailure("TODO") diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift index d14b5c346..f00e14840 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift @@ -143,12 +143,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & MediaPrev return } - let managedObjectContext = self.context.managedObjectContext - let needsToggleMediaSensitive: Bool = try await managedObjectContext.perform { - guard let _status = status.object(in: managedObjectContext) else { return false } - let status = _status.reblog ?? _status - return status.isMediaSensitiveToggled ? !status.sensitive : status.sensitive - } + let needsToggleMediaSensitive = await !statusView.viewModel.isMediaReveal guard !needsToggleMediaSensitive else { try await DataSourceFacade.responseToToggleSensitiveAction( @@ -499,7 +494,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider { provider: self, user: user ) - case .notification(let notification): + case .notification: assertionFailure("TODO") default: assertionFailure("TODO") diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewControllerNavigateable.swift b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewControllerNavigateable.swift index fb4a7d843..ce4e03cdb 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewControllerNavigateable.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewControllerNavigateable.swift @@ -115,7 +115,7 @@ extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvid guard let provider = self as? (DataSourceProvider & MediaPreviewableViewController) else { return } guard let indexPathForSelectedRow = tableView.indexPathForSelectedRow, - let cell = tableView.cellForRow(at: indexPathForSelectedRow) as? StatusTableViewCell + let cell = tableView.cellForRow(at: indexPathForSelectedRow) as? StatusViewContainerTableViewCell else { return } guard let mediaView = cell.statusView.mediaGridContainerView.mediaViews.first else { return } diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+TableViewControllerNavigateable.swift b/Mastodon/Protocol/Provider/DataSourceProvider+TableViewControllerNavigateable.swift index f7e50cff8..50fa17866 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+TableViewControllerNavigateable.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+TableViewControllerNavigateable.swift @@ -138,7 +138,7 @@ extension TableViewControllerNavigateableCore where Self: DataSourceProvider { target: .status, status: record ) - case .notification(let record): + case .notification: assertionFailure() default: assertionFailure() diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift index 3968df110..c00c36971 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift @@ -93,7 +93,7 @@ extension UITableViewDelegate where Self: DataSourceProvider & MediaPreviewableV guard let image = mediaView.thumbnail(), let assetURLString = mediaView.configuration?.assetURL, let assetURL = URL(string: assetURLString), - let resourceType = mediaView.configuration?.resourceType + let _ = mediaView.configuration?.resourceType else { // not provide preview unless thumbnail ready return nil diff --git a/Mastodon/Resources/ckb.lproj/InfoPlist.strings b/Mastodon/Resources/ckb.lproj/InfoPlist.strings new file mode 100644 index 000000000..710865573 --- /dev/null +++ b/Mastodon/Resources/ckb.lproj/InfoPlist.strings @@ -0,0 +1,4 @@ +"NSCameraUsageDescription" = "Used to take photo for post status"; +"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; +"NewPostShortcutItemTitle" = "New Post"; +"SearchShortcutItemTitle" = "Search"; \ No newline at end of file diff --git a/Mastodon/Resources/gl.lproj/InfoPlist.strings b/Mastodon/Resources/gl.lproj/InfoPlist.strings new file mode 100644 index 000000000..710865573 --- /dev/null +++ b/Mastodon/Resources/gl.lproj/InfoPlist.strings @@ -0,0 +1,4 @@ +"NSCameraUsageDescription" = "Used to take photo for post status"; +"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; +"NewPostShortcutItemTitle" = "New Post"; +"SearchShortcutItemTitle" = "Search"; \ No newline at end of file diff --git a/Mastodon/Resources/it.lproj/InfoPlist.strings b/Mastodon/Resources/it.lproj/InfoPlist.strings new file mode 100644 index 000000000..710865573 --- /dev/null +++ b/Mastodon/Resources/it.lproj/InfoPlist.strings @@ -0,0 +1,4 @@ +"NSCameraUsageDescription" = "Used to take photo for post status"; +"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; +"NewPostShortcutItemTitle" = "New Post"; +"SearchShortcutItemTitle" = "Search"; \ No newline at end of file diff --git a/Mastodon/Resources/sv.lproj/InfoPlist.strings b/Mastodon/Resources/sv.lproj/InfoPlist.strings new file mode 100644 index 000000000..710865573 --- /dev/null +++ b/Mastodon/Resources/sv.lproj/InfoPlist.strings @@ -0,0 +1,4 @@ +"NSCameraUsageDescription" = "Used to take photo for post status"; +"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; +"NewPostShortcutItemTitle" = "New Post"; +"SearchShortcutItemTitle" = "Search"; \ No newline at end of file diff --git a/Mastodon/Resources/tr.lproj/InfoPlist.strings b/Mastodon/Resources/tr.lproj/InfoPlist.strings new file mode 100644 index 000000000..710865573 --- /dev/null +++ b/Mastodon/Resources/tr.lproj/InfoPlist.strings @@ -0,0 +1,4 @@ +"NSCameraUsageDescription" = "Used to take photo for post status"; +"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; +"NewPostShortcutItemTitle" = "New Post"; +"SearchShortcutItemTitle" = "Search"; \ No newline at end of file diff --git a/Mastodon/Resources/zh-Hant.lproj/InfoPlist.strings b/Mastodon/Resources/zh-Hant.lproj/InfoPlist.strings new file mode 100644 index 000000000..710865573 --- /dev/null +++ b/Mastodon/Resources/zh-Hant.lproj/InfoPlist.strings @@ -0,0 +1,4 @@ +"NSCameraUsageDescription" = "Used to take photo for post status"; +"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; +"NewPostShortcutItemTitle" = "New Post"; +"SearchShortcutItemTitle" = "Search"; \ No newline at end of file diff --git a/Mastodon/Scene/Account/AccountViewController.swift b/Mastodon/Scene/Account/AccountViewController.swift index 42c9e1d62..20d7b26a1 100644 --- a/Mastodon/Scene/Account/AccountViewController.swift +++ b/Mastodon/Scene/Account/AccountViewController.swift @@ -118,7 +118,7 @@ extension AccountListViewController { // the presentingViewController may deinit. // Hold it and check the window to prevent PanModel crash - guard let presentingViewController = presentingViewController else { return } + guard let _ = presentingViewController else { return } guard self.view.window != nil else { return } self.hasLoaded = true diff --git a/Mastodon/Scene/Compose/AutoComplete/AutoCompleteViewModel+State.swift b/Mastodon/Scene/Compose/AutoComplete/AutoCompleteViewModel+State.swift index ebda78a1e..632b57b66 100644 --- a/Mastodon/Scene/Compose/AutoComplete/AutoCompleteViewModel+State.swift +++ b/Mastodon/Scene/Compose/AutoComplete/AutoCompleteViewModel+State.swift @@ -77,7 +77,7 @@ extension AutoCompleteViewModel.State { override func didEnter(from previousState: GKState?) { super.didEnter(from: previousState) - guard let viewModel = viewModel, let stateMachine = stateMachine else { return } + guard let viewModel = viewModel, let _ = stateMachine else { return } let searchText = viewModel.inputText.value let searchType = AutoCompleteViewModel.SearchType(inputText: searchText) ?? .default diff --git a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusPollOptionCollectionViewCell.swift b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusPollOptionCollectionViewCell.swift index 7ea43f154..c1869669c 100644 --- a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusPollOptionCollectionViewCell.swift +++ b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusPollOptionCollectionViewCell.swift @@ -10,6 +10,7 @@ import UIKit import Combine import MastodonAsset import MastodonLocalization +import MastodonUI protocol ComposeStatusPollOptionCollectionViewCellDelegate: AnyObject { func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, textFieldDidBeginEditing textField: UITextField) diff --git a/Mastodon/Scene/Discovery/Community/CommunityViewViewModel.swift b/Mastodon/Scene/Discovery/Community/CommunityViewViewModel.swift new file mode 100644 index 000000000..51eba9b36 --- /dev/null +++ b/Mastodon/Scene/Discovery/Community/CommunityViewViewModel.swift @@ -0,0 +1,64 @@ +// +// DiscoveryCommunityViewViewModel.swift +// Mastodon +// +// Created by MainasuK on 2022-4-29. +// + +import os.log +import UIKit +import Combine +import GameplayKit +import CoreData +import CoreDataStack +import MastodonSDK + +final class DiscoveryCommunityViewViewModel { + + let logger = Logger(subsystem: "DiscoveryCommunityViewViewModel", category: "ViewModel") + + var disposeBag = Set() + + // input + let context: AppContext + let viewDidAppeared = PassthroughSubject() + let statusFetchedResultsController: StatusFetchedResultsController + + // 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 + }() + + let didLoadLatest = PassthroughSubject() + + init(context: AppContext) { + self.context = context + self.statusFetchedResultsController = StatusFetchedResultsController( + managedObjectContext: context.managedObjectContext, + domain: nil, + additionalTweetPredicate: nil + ) + // end init + + context.authenticationService.activeMastodonAuthentication + .map { $0?.domain } + .assign(to: \.value, on: statusFetchedResultsController.domain) + .store(in: &disposeBag) + + } + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} diff --git a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewController+DataSourceProvider.swift b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewController+DataSourceProvider.swift new file mode 100644 index 000000000..e9441b5d3 --- /dev/null +++ b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewController+DataSourceProvider.swift @@ -0,0 +1,34 @@ +// +// DiscoveryCommunityViewController+DataSourceProvider.swift +// Mastodon +// +// Created by MainasuK on 2022-4-29. +// + +import UIKit + +extension DiscoveryCommunityViewController: DataSourceProvider { + func item(from source: DataSourceItem.Source) async -> DataSourceItem? { + var _indexPath = source.indexPath + if _indexPath == nil, let cell = source.tableViewCell { + _indexPath = await self.indexPath(for: cell) + } + guard let indexPath = _indexPath else { return nil } + + guard let item = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { + return nil + } + + switch item { + case .status(let record): + return .status(record: record) + default: + return nil + } + } + + @MainActor + private func indexPath(for cell: UITableViewCell) async -> IndexPath? { + return tableView.indexPath(for: cell) + } +} diff --git a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewController.swift b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewController.swift new file mode 100644 index 000000000..4cc32c250 --- /dev/null +++ b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewController.swift @@ -0,0 +1,171 @@ +// +// DiscoveryCommunityViewController.swift +// Mastodon +// +// Created by MainasuK on 2022-4-29. +// + +import os.log +import UIKit +import Combine +import MastodonUI + +// Local Timeline +final class DiscoveryCommunityViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { + + let logger = Logger(subsystem: "DiscoveryCommunityViewController", category: "ViewController") + + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } + weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + + var disposeBag = Set() + var viewModel: DiscoveryCommunityViewModel! + + let mediaPreviewTransitionController = MediaPreviewTransitionController() + + lazy var tableView: UITableView = { + let tableView = UITableView() + tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 100 + tableView.separatorStyle = .none + tableView.backgroundColor = .clear + return tableView + }() + + let refreshControl = UIRefreshControl() + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + +extension DiscoveryCommunityViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.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.refreshControl = refreshControl + refreshControl.addTarget(self, action: #selector(DiscoveryCommunityViewController.refreshControlValueChanged(_:)), for: .valueChanged) + viewModel.didLoadLatest + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self = self else { return } + self.refreshControl.endRefreshing() + } + .store(in: &disposeBag) + + tableView.delegate = self + viewModel.setupDiffableDataSource( + tableView: tableView, + statusTableViewCellDelegate: self + ) + + // setup batch fetch + viewModel.listBatchFetchViewModel.setup(scrollView: tableView) + viewModel.listBatchFetchViewModel.shouldFetch + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self = self else { return } + guard self.view.window != nil else { return } + self.viewModel.stateMachine.enter(DiscoveryCommunityViewModel.State.Loading.self) + } + .store(in: &disposeBag) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + tableView.deselectRow(with: transitionCoordinator, animated: animated) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + viewModel.viewDidAppeared.send() + } + +} + +extension DiscoveryCommunityViewController { + + @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) { + if !viewModel.stateMachine.enter(DiscoveryCommunityViewModel.State.Reloading.self) { + refreshControl.endRefreshing() + } + } + +} + +// MARK: - UITableViewDelegate +extension DiscoveryCommunityViewController: UITableViewDelegate, AutoGenerateTableViewDelegate { + // sourcery:inline:CommunityViewController.AutoGenerateTableViewDelegate + + // Generated using Sourcery + // DO NOT EDIT + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + aspectTableView(tableView, didSelectRowAt: 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) + } + // sourcery:end +} + +// MARK: - StatusTableViewCellDelegate +extension DiscoveryCommunityViewController: StatusTableViewCellDelegate { } + +// MARK: ScrollViewContainer +extension DiscoveryCommunityViewController: ScrollViewContainer { + var scrollView: UIScrollView? { + tableView + } +} + +extension DiscoveryCommunityViewController { + override var keyCommands: [UIKeyCommand]? { + return navigationKeyCommands + statusNavigationKeyCommands + } +} + +// MARK: - StatusTableViewControllerNavigateable +extension DiscoveryCommunityViewController: StatusTableViewControllerNavigateable { + @objc func navigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) { + navigateKeyCommandHandler(sender) + } + + @objc func statusKeyCommandHandlerRelay(_ sender: UIKeyCommand) { + statusKeyCommandHandler(sender) + } +} diff --git a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+Diffable.swift b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+Diffable.swift new file mode 100644 index 000000000..26335ec3d --- /dev/null +++ b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+Diffable.swift @@ -0,0 +1,65 @@ +// +// DiscoveryCommunityViewModel+Diffable.swift +// Mastodon +// +// Created by MainasuK on 2022-4-29. +// + +import UIKit +import Combine + +extension DiscoveryCommunityViewModel { + + func setupDiffableDataSource( + tableView: UITableView, + statusTableViewCellDelegate: StatusTableViewCellDelegate + ) { + diffableDataSource = StatusSection.diffableDataSource( + tableView: tableView, + context: context, + configuration: StatusSection.Configuration( + statusTableViewCellDelegate: statusTableViewCellDelegate, + timelineMiddleLoaderTableViewCellDelegate: nil, + filterContext: .none, + activeFilters: nil + ) + ) + + stateMachine.enter(State.Reloading.self) + + statusFetchedResultsController.$records + .receive(on: DispatchQueue.main) + .sink { [weak self] records in + guard let self = self else { return } + guard let diffableDataSource = self.diffableDataSource else { return } + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + + let items = records.map { StatusItem.status(record: $0) } + snapshot.appendItems(items, toSection: .main) + + if let currentState = self.stateMachine.currentState { + switch currentState { + case is State.Initial, + is State.Reloading, + is State.Loading, + is State.Idle, + is State.Fail: + if !items.isEmpty { + snapshot.appendItems([.bottomLoader], toSection: .main) + } + case is State.NoMore: + break + default: + assertionFailure() + break + } + } + + diffableDataSource.applySnapshot(snapshot, animated: false) + } + .store(in: &disposeBag) + } + +} diff --git a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+State.swift b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+State.swift new file mode 100644 index 000000000..a3947e6ab --- /dev/null +++ b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+State.swift @@ -0,0 +1,206 @@ +// +// DiscoveryCommunityViewModel+State.swift +// Mastodon +// +// Created by MainasuK on 2022-4-29. +// + +import os.log +import Foundation +import GameplayKit +import MastodonSDK + +extension DiscoveryCommunityViewModel { + class State: GKState, NamingState { + + let logger = Logger(subsystem: "DiscoveryCommunityViewModel.State", category: "StateMachine") + + let id = UUID() + + var name: String { + String(describing: Self.self) + } + + weak var viewModel: DiscoveryCommunityViewModel? + + init(viewModel: DiscoveryCommunityViewModel) { + self.viewModel = viewModel + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + let previousState = previousState as? DiscoveryCommunityViewModel.State + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] enter \(self.name), previous: \(previousState?.name ?? "")") + } + + @MainActor + func enter(state: State.Type) { + stateMachine?.enter(state) + } + + deinit { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(self.name)") + } + } +} + +extension DiscoveryCommunityViewModel.State { + class Initial: DiscoveryCommunityViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Reloading.Type: + return true + default: + return false + } + } + } + + class Reloading: DiscoveryCommunityViewModel.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 } + + stateMachine.enter(Loading.self) + } + } + + class Fail: DiscoveryCommunityViewModel.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: DiscoveryCommunityViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Reloading.Type, is Loading.Type: + return true + default: + return false + } + } + } + + class Loading: DiscoveryCommunityViewModel.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) + guard let viewModel = viewModel, let stateMachine = stateMachine else { return } + + switch previousState { + case is Reloading: + maxID = nil + default: + break + } + + guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else { + stateMachine.enter(Fail.self) + return + } + + let maxID = self.maxID + let isReloading = maxID == nil + + Task { + do { + let response = try await viewModel.context.apiService.publicTimeline( + query: .init( + local: true, + remote: nil, + onlyMedia: nil, + maxID: maxID, + sinceID: nil, + minID: nil, + limit: 20 + ), + authenticationBox: authenticationBox + ) + + let newMaxID = response.link?.maxID + let hasMore = newMaxID != nil + self.maxID = newMaxID + + var hasNewStatusesAppend = false + var statusIDs = isReloading ? [] : viewModel.statusFetchedResultsController.statusIDs.value + for status in response.value { + guard !statusIDs.contains(status.id) else { continue } + statusIDs.append(status.id) + hasNewStatusesAppend = true + } + + if hasNewStatusesAppend, hasMore { + self.maxID = response.link?.maxID + await enter(state: Idle.self) + } else { + await enter(state: NoMore.self) + } + viewModel.statusFetchedResultsController.statusIDs.value = statusIDs + viewModel.didLoadLatest.send() + + } catch { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch user timeline fail: \(error.localizedDescription)") + await enter(state: Fail.self) + } + } // end Task + } // end func + } + + class NoMore: DiscoveryCommunityViewModel.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) + + } + } +} diff --git a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel.swift b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel.swift new file mode 100644 index 000000000..8911a506e --- /dev/null +++ b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel.swift @@ -0,0 +1,65 @@ +// +// DiscoveryCommunityViewModel.swift +// Mastodon +// +// Created by MainasuK on 2022-4-29. +// + +import os.log +import UIKit +import Combine +import GameplayKit +import CoreData +import CoreDataStack +import MastodonSDK + +final class DiscoveryCommunityViewModel { + + let logger = Logger(subsystem: "DiscoveryCommunityViewModel", category: "ViewModel") + + var disposeBag = Set() + + // input + let context: AppContext + let viewDidAppeared = PassthroughSubject() + let statusFetchedResultsController: StatusFetchedResultsController + let listBatchFetchViewModel = ListBatchFetchViewModel() + + // 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 + }() + + let didLoadLatest = PassthroughSubject() + + init(context: AppContext) { + self.context = context + self.statusFetchedResultsController = StatusFetchedResultsController( + managedObjectContext: context.managedObjectContext, + domain: nil, + additionalTweetPredicate: nil + ) + // end init + + context.authenticationService.activeMastodonAuthentication + .map { $0?.domain } + .assign(to: \.value, on: statusFetchedResultsController.domain) + .store(in: &disposeBag) + + } + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} diff --git a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewViewModel.swift b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewViewModel.swift new file mode 100644 index 000000000..bbf508b69 --- /dev/null +++ b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewViewModel.swift @@ -0,0 +1,64 @@ +// +// DiscoveryCommunityViewModel.swift +// Mastodon +// +// Created by MainasuK on 2022-4-29. +// + +import os.log +import UIKit +import Combine +import GameplayKit +import CoreData +import CoreDataStack +import MastodonSDK + +final class DiscoveryCommunityViewModel { + + let logger = Logger(subsystem: "DiscoveryCommunityViewModel", category: "ViewModel") + + var disposeBag = Set() + + // input + let context: AppContext + let viewDidAppeared = PassthroughSubject() + let statusFetchedResultsController: StatusFetchedResultsController + + // 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 + }() + + let didLoadLatest = PassthroughSubject() + + init(context: AppContext) { + self.context = context + self.statusFetchedResultsController = StatusFetchedResultsController( + managedObjectContext: context.managedObjectContext, + domain: nil, + additionalTweetPredicate: nil + ) + // end init + + context.authenticationService.activeMastodonAuthentication + .map { $0?.domain } + .assign(to: \.value, on: statusFetchedResultsController.domain) + .store(in: &disposeBag) + + } + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} diff --git a/Mastodon/Scene/Discovery/DiscoveryViewController.swift b/Mastodon/Scene/Discovery/DiscoveryViewController.swift new file mode 100644 index 000000000..1803f687a --- /dev/null +++ b/Mastodon/Scene/Discovery/DiscoveryViewController.swift @@ -0,0 +1,157 @@ +// +// DiscoveryViewController.swift +// Mastodon +// +// Created by MainasuK on 2022-4-12. +// + +import os.log +import UIKit +import Combine +import Tabman +import Pageboy +import MastodonAsset +import MastodonUI + +public class DiscoveryViewController: TabmanViewController, NeedsDependency { + + public static let containerViewMarginForRegularHorizontalSizeClass: CGFloat = 64 + public static let containerViewMarginForCompactHorizontalSizeClass: CGFloat = 16 + + var disposeBag = Set() + + let logger = Logger(subsystem: "DiscoveryViewController", category: "ViewController") + + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } + weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + + private(set) lazy var viewModel = DiscoveryViewModel( + context: context, + coordinator: coordinator + ) + + private(set) lazy var buttonBar: TMBar.ButtonBar = { + let buttonBar = TMBar.ButtonBar() + buttonBar.backgroundView.style = .custom(view: buttonBarBackgroundView) + buttonBar.layout.interButtonSpacing = 0 + buttonBar.layout.contentInset = .zero + buttonBar.indicator.backgroundColor = Asset.Colors.Label.primary.color + buttonBar.indicator.weight = .custom(value: 2) + return buttonBar + }() + + let buttonBarBackgroundView: UIView = { + let view = UIView() + let barBottomLine = UIView.separatorLine + barBottomLine.backgroundColor = Asset.Colors.Label.secondary.color.withAlphaComponent(0.5) + barBottomLine.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(barBottomLine) + NSLayoutConstraint.activate([ + barBottomLine.leadingAnchor.constraint(equalTo: view.leadingAnchor), + barBottomLine.trailingAnchor.constraint(equalTo: view.trailingAnchor), + barBottomLine.bottomAnchor.constraint(equalTo: view.bottomAnchor), + barBottomLine.heightAnchor.constraint(equalToConstant: 2).priority(.required - 1), + ]) + return view + }() + + func customizeButtonBarAppearance() { + // The implmention use CATextlayer. Adapt for Dark Mode without dynamic colors + // Needs trigger update when `userInterfaceStyle` chagnes + let userInterfaceStyle = traitCollection.userInterfaceStyle + buttonBar.buttons.customize { button in + switch userInterfaceStyle { + case .dark: + // Asset.Colors.Label.primary.color + button.selectedTintColor = UIColor(red: 238.0/255.0, green: 238.0/255.0, blue: 238.0/255.0, alpha: 1.0) + // Asset.Colors.Label.secondary.color + button.tintColor = UIColor(red: 151.0/255.0, green: 157.0/255.0, blue: 173.0/255.0, alpha: 1.0) + default: + // Asset.Colors.Label.primary.color + button.selectedTintColor = UIColor(red: 40.0/255.0, green: 44.0/255.0, blue: 55.0/255.0, alpha: 1.0) + // Asset.Colors.Label.secondary.color + button.tintColor = UIColor(red: 60.0/255.0, green: 60.0/255.0, blue: 67.0/255.0, alpha: 0.6) + } + + button.backgroundColor = .clear + button.contentInset = UIEdgeInsets(top: 12, left: 26, bottom: 12, right: 26) + } + } + +} + +extension DiscoveryViewController { + + public override func viewDidLoad() { + super.viewDidLoad() + + setupAppearance(theme: ThemeService.shared.currentTheme.value) + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.main) + .sink { [weak self] theme in + guard let self = self else { return } + self.setupAppearance(theme: theme) + } + .store(in: &disposeBag) + + dataSource = viewModel + addBar( + buttonBar, + dataSource: viewModel, + at: .top + ) + customizeButtonBarAppearance() + + viewModel.$viewControllers + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self = self else { return } + self.reloadData() + } + .store(in: &disposeBag) + } + + public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + customizeButtonBarAppearance() + } + +} + +extension DiscoveryViewController { + + private func setupAppearance(theme: Theme) { + view.backgroundColor = theme.secondarySystemBackgroundColor + buttonBarBackgroundView.backgroundColor = theme.systemBackgroundColor + } + +} + +// MARK: - ScrollViewContainer +extension DiscoveryViewController: ScrollViewContainer { + var scrollView: UIScrollView? { + return (currentViewController as? ScrollViewContainer)?.scrollView + } +} + +extension DiscoveryViewController { + + public override var keyCommands: [UIKeyCommand]? { + return pageboyNavigateKeyCommands + } + +} + +// MARK: - PageboyNavigateable +extension DiscoveryViewController: PageboyNavigateable { + + var navigateablePageViewController: PageboyViewController { + return self + } + + @objc func pageboyNavigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) { + pageboyNavigateKeyCommandHandler(sender) + } + +} diff --git a/Mastodon/Scene/Discovery/DiscoveryViewModel.swift b/Mastodon/Scene/Discovery/DiscoveryViewModel.swift new file mode 100644 index 000000000..dfeb16e2b --- /dev/null +++ b/Mastodon/Scene/Discovery/DiscoveryViewModel.swift @@ -0,0 +1,173 @@ +// +// DiscoveryViewModel.swift +// Mastodon +// +// Created by MainasuK on 2022-4-12. +// + +import UIKit +import Combine +import Tabman +import Pageboy +import MastodonLocalization + +final class DiscoveryViewModel { + + var disposeBag = Set() + + // input + let context: AppContext + let discoveryPostsViewController: DiscoveryPostsViewController + let discoveryHashtagsViewController: DiscoveryHashtagsViewController + let discoveryNewsViewController: DiscoveryNewsViewController + let discoveryCommunityViewController: DiscoveryCommunityViewController + let discoveryForYouViewController: DiscoveryForYouViewController + + @Published var viewControllers: [ScrollViewContainer & PageViewController] + + init(context: AppContext, coordinator: SceneCoordinator) { + func setupDependency(_ needsDependency: NeedsDependency) { + needsDependency.context = context + needsDependency.coordinator = coordinator + } + + self.context = context + discoveryPostsViewController = { + let viewController = DiscoveryPostsViewController() + setupDependency(viewController) + viewController.viewModel = DiscoveryPostsViewModel(context: context) + return viewController + }() + discoveryHashtagsViewController = { + let viewController = DiscoveryHashtagsViewController() + setupDependency(viewController) + viewController.viewModel = DiscoveryHashtagsViewModel(context: context) + return viewController + }() + discoveryNewsViewController = { + let viewController = DiscoveryNewsViewController() + setupDependency(viewController) + viewController.viewModel = DiscoveryNewsViewModel(context: context) + return viewController + }() + discoveryCommunityViewController = { + let viewController = DiscoveryCommunityViewController() + setupDependency(viewController) + viewController.viewModel = DiscoveryCommunityViewModel(context: context) + return viewController + }() + discoveryForYouViewController = { + let viewController = DiscoveryForYouViewController() + setupDependency(viewController) + viewController.viewModel = DiscoveryForYouViewModel(context: context) + return viewController + }() + self.viewControllers = [ + discoveryPostsViewController, + discoveryHashtagsViewController, + discoveryNewsViewController, + discoveryCommunityViewController, + discoveryForYouViewController, + ] + // end init + + discoveryPostsViewController.viewModel.$isServerSupportEndpoint + .receive(on: DispatchQueue.main) + .sink { [weak self] isServerSupportEndpoint in + guard let self = self else { return } + if !isServerSupportEndpoint { + self.viewControllers.removeAll(where: { + $0 === self.discoveryPostsViewController || $0 === self.discoveryPostsViewController + }) + } + } + .store(in: &disposeBag) + + discoveryNewsViewController.viewModel.$isServerSupportEndpoint + .receive(on: DispatchQueue.main) + .sink { [weak self] isServerSupportEndpoint in + guard let self = self else { return } + if !isServerSupportEndpoint { + self.viewControllers.removeAll(where: { $0 === self.discoveryNewsViewController }) + } + } + .store(in: &disposeBag) + } + +} + + +// MARK: - PageboyViewControllerDataSource +extension DiscoveryViewModel: PageboyViewControllerDataSource { + + func numberOfViewControllers(in pageboyViewController: PageboyViewController) -> Int { + return viewControllers.count + } + + func viewController(for pageboyViewController: PageboyViewController, at index: PageboyViewController.PageIndex) -> UIViewController? { + return viewControllers[index] + } + + func defaultPage(for pageboyViewController: PageboyViewController) -> PageboyViewController.Page? { + return .first + } + +} + +// MARK: - TMBarDataSource +extension DiscoveryViewModel: TMBarDataSource { + func barItem(for bar: TMBar, at index: Int) -> TMBarItemable { + guard !viewControllers.isEmpty, index < viewControllers.count else { + assertionFailure() + return TMBarItem(title: "") + } + return viewControllers[index].tabItem + } +} + +protocol PageViewController: UIViewController { + var tabItemTitle: String { get } + var tabItem: TMBarItemable { get } +} + +// MARK: - PageViewController +extension DiscoveryPostsViewController: PageViewController { + var tabItemTitle: String { L10n.Scene.Discovery.Tabs.posts } + var tabItem: TMBarItemable { + return TMBarItem(title: tabItemTitle) + } +} + + +// MARK: - PageViewController +extension DiscoveryHashtagsViewController: PageViewController { + var tabItemTitle: String { L10n.Scene.Discovery.Tabs.hashtags } + var tabItem: TMBarItemable { + + return TMBarItem(title: tabItemTitle) + } +} + +// MARK: - PageViewController +extension DiscoveryNewsViewController: PageViewController { + var tabItemTitle: String { L10n.Scene.Discovery.Tabs.news } + var tabItem: TMBarItemable { + return TMBarItem(title: tabItemTitle) + } +} + +// MARK: - PageViewController +extension DiscoveryCommunityViewController: PageViewController { + var tabItemTitle: String { L10n.Scene.Discovery.Tabs.community } + var tabItem: TMBarItemable { + return TMBarItem(title: tabItemTitle) + } +} + +// MARK: - PageViewController +extension DiscoveryForYouViewController: PageViewController { + var tabItemTitle: String { L10n.Scene.Discovery.Tabs.forYou } + var tabItem: TMBarItemable { + return TMBarItem(title: tabItemTitle) + } +} diff --git a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift new file mode 100644 index 000000000..b8b8f25a7 --- /dev/null +++ b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift @@ -0,0 +1,146 @@ +// +// DiscoveryForYouViewController.swift +// Mastodon +// +// Created by MainasuK on 2022-4-14. +// + +import os.log +import UIKit +import Combine +import MastodonUI + +final class DiscoveryForYouViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { + + let logger = Logger(subsystem: "DiscoveryForYouViewController", category: "ViewController") + + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } + weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + + var disposeBag = Set() + var viewModel: DiscoveryForYouViewModel! + + let mediaPreviewTransitionController = MediaPreviewTransitionController() + + lazy var tableView: UITableView = { + let tableView = UITableView() + tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 100 + tableView.separatorStyle = .none + tableView.backgroundColor = .clear + return tableView + }() + + let refreshControl = UIRefreshControl() + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + +extension DiscoveryForYouViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.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( + tableView: tableView, + profileCardTableViewCellDelegate: self + ) + + tableView.refreshControl = refreshControl + refreshControl.addTarget(self, action: #selector(DiscoveryForYouViewController.refreshControlValueChanged(_:)), for: .valueChanged) + viewModel.$isFetching + .receive(on: DispatchQueue.main) + .sink { [weak self] isFetching in + guard let self = self else { return } + if !isFetching { + self.refreshControl.endRefreshing() + } + } + .store(in: &disposeBag) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + refreshControl.endRefreshing() + tableView.deselectRow(with: transitionCoordinator, animated: animated) + } + +} + +extension DiscoveryForYouViewController { + + @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) { + Task { + try await viewModel.fetch() + } + } + +} + +// MARK: - UITableViewDelegate +extension DiscoveryForYouViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(indexPath)") + guard case let .user(record) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } + guard let user = record.object(in: context.managedObjectContext) else { return } + let profileViewModel = CachedProfileViewModel( + context: context, + mastodonUser: user + ) + coordinator.present( + scene: .profile(viewModel: profileViewModel), + from: self, + transition: .show + ) + } + +} + +// MARK: - ProfileCardTableViewCellDelegate +extension DiscoveryForYouViewController: ProfileCardTableViewCellDelegate { + func profileCardTableViewCell(_ cell: ProfileCardTableViewCell, profileCardView: ProfileCardView, relationshipButtonDidPressed button: ProfileRelationshipActionButton) { + guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else { return } + guard let indexPath = tableView.indexPath(for: cell) else { return } + guard case let .user(record) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } + + Task { + try await DataSourceFacade.responseToUserFollowAction( + dependency: self, + user: record, + authenticationBox: authenticationBox + ) + } // end Task + } +} + +// MARK: ScrollViewContainer +extension DiscoveryForYouViewController: ScrollViewContainer { + var scrollView: UIScrollView? { + tableView + } +} + diff --git a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift new file mode 100644 index 000000000..fe53cf221 --- /dev/null +++ b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift @@ -0,0 +1,47 @@ +// +// DiscoveryForYouViewModel+Diffable.swift +// Mastodon +// +// Created by MainasuK on 2022-4-14. +// + +import UIKit +import Combine +import MastodonUI + +extension DiscoveryForYouViewModel { + + func setupDiffableDataSource( + tableView: UITableView, + profileCardTableViewCellDelegate: ProfileCardTableViewCellDelegate + ) { + diffableDataSource = DiscoverySection.diffableDataSource( + tableView: tableView, + context: context, + configuration: DiscoverySection.Configuration( + profileCardTableViewCellDelegate: profileCardTableViewCellDelegate + ) + ) + + Task { + try await fetch() + } + + userFetchedResultsController.$records + .receive(on: DispatchQueue.main) + .sink { [weak self] records in + guard let self = self else { return } + guard let diffableDataSource = self.diffableDataSource else { return } + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.forYou]) + + let items = records.map { DiscoveryItem.user($0) } + snapshot.appendItems(items, toSection: .forYou) + + diffableDataSource.applySnapshot(snapshot, animated: false) + } + .store(in: &disposeBag) + } + +} diff --git a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel.swift b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel.swift new file mode 100644 index 000000000..7be4aebaf --- /dev/null +++ b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel.swift @@ -0,0 +1,75 @@ +// +// DiscoveryForYouViewModel.swift +// Mastodon +// +// Created by MainasuK on 2022-4-14. +// + +import os.log +import UIKit +import Combine +import GameplayKit +import CoreData +import CoreDataStack +import MastodonSDK + +final class DiscoveryForYouViewModel { + + var disposeBag = Set() + + // input + let context: AppContext + let userFetchedResultsController: UserFetchedResultsController + @Published var isFetching = false + + // output + var diffableDataSource: UITableViewDiffableDataSource? + let didLoadLatest = PassthroughSubject() + + init(context: AppContext) { + self.context = context + self.userFetchedResultsController = UserFetchedResultsController( + managedObjectContext: context.managedObjectContext, + domain: nil, + additionalPredicate: nil + ) + // end init + + context.authenticationService.activeMastodonAuthenticationBox + .map { $0?.domain } + .assign(to: \.domain, on: userFetchedResultsController) + .store(in: &disposeBag) + } + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + +extension DiscoveryForYouViewModel { + func fetch() async throws { + guard !isFetching else { return } + isFetching = true + defer { isFetching = false } + + guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return } + + do { + let response = try await context.apiService.suggestionAccountV2( + query: nil, + authenticationBox: authenticationBox + ) + let userIDs = response.value.map { $0.account.id } + userFetchedResultsController.userIDs = userIDs + } catch { + // fallback V1 + let response2 = try await context.apiService.suggestionAccount( + query: nil, + authenticationBox: authenticationBox + ) + let userIDs = response2.value.map { $0.id } + userFetchedResultsController.userIDs = userIDs + } + } +} diff --git a/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewController.swift b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewController.swift new file mode 100644 index 000000000..6e6d96924 --- /dev/null +++ b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewController.swift @@ -0,0 +1,228 @@ +// +// DiscoveryHashtagsViewController.swift +// Mastodon +// +// Created by MainasuK on 2022-4-13. +// + +import os.log +import UIKit +import Combine +import MastodonUI + +final class DiscoveryHashtagsViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { + + let logger = Logger(subsystem: "TrendPostsViewController", category: "ViewController") + + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } + weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + + var disposeBag = Set() + var viewModel: DiscoveryHashtagsViewModel! + + let mediaPreviewTransitionController = MediaPreviewTransitionController() + + lazy var tableView: UITableView = { + let tableView = UITableView() + tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 100 + tableView.separatorStyle = .none + tableView.backgroundColor = .clear + return tableView + }() + + let refreshControl = UIRefreshControl() + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + +extension DiscoveryHashtagsViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.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.refreshControl = refreshControl + refreshControl.addTarget(self, action: #selector(DiscoveryHashtagsViewController.refreshControlValueChanged(_:)), for: .valueChanged) + + tableView.delegate = self + viewModel.setupDiffableDataSource( + tableView: tableView + ) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + tableView.deselectRow(with: transitionCoordinator, animated: animated) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + viewModel.viewDidAppeared.send() + } + +} + +extension DiscoveryHashtagsViewController { + + @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) { + Task { @MainActor in + do { + try await viewModel.fetch() + } catch { + // do nothing + } + sender.endRefreshing() + } // end Task + } + +} + +// MARK: - UITableViewDelegate +extension DiscoveryHashtagsViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(indexPath)") + guard case let .hashtag(tag) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } + let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, hashtag: tag.name) + coordinator.present( + scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), + from: self, + transition: .show + ) + } + + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + guard let cell = cell as? TrendTableViewCell else { return } + guard let diffableDataSource = viewModel.diffableDataSource else { return } + guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } + + if let lastItem = diffableDataSource.snapshot().itemIdentifiers.last, item == lastItem { + cell.configureSeparator(style: .edge) + } + } +} + +// MARK: ScrollViewContainer +extension DiscoveryHashtagsViewController: ScrollViewContainer { + var scrollView: UIScrollView? { + tableView + } +} + +extension DiscoveryHashtagsViewController { + override var keyCommands: [UIKeyCommand]? { + return navigationKeyCommands + } +} + +// MARK: - TableViewControllerNavigateable +extension DiscoveryHashtagsViewController: TableViewControllerNavigateable { + @objc func navigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) { + navigateKeyCommandHandler(sender) + } + + func navigate(direction: TableViewNavigationDirection) { + if let indexPathForSelectedRow = tableView.indexPathForSelectedRow { + // navigate up/down on the current selected item + navigateToTag(direction: direction, indexPath: indexPathForSelectedRow) + } else { + // set first visible item selected + navigateToFirstVisibleTag() + } + } + + private func navigateToTag(direction: TableViewNavigationDirection, indexPath: IndexPath) { + guard let diffableDataSource = viewModel.diffableDataSource else { return } + let items = diffableDataSource.snapshot().itemIdentifiers + guard let selectedItem = diffableDataSource.itemIdentifier(for: indexPath), + let selectedItemIndex = items.firstIndex(of: selectedItem) else { + return + } + + let _navigateToItem: DiscoveryItem? = { + var index = selectedItemIndex + while 0.. 1 { + // drop first when visible not the first cell of table + visibleItems.removeFirst() + } + guard let item = visibleItems.first, let indexPath = diffableDataSource.indexPath(for: item) else { return } + let scrollPosition: UITableView.ScrollPosition = overrideNavigationScrollPosition ?? Self.navigateScrollPosition(tableView: tableView, indexPath: indexPath) + tableView.selectRow(at: indexPath, animated: true, scrollPosition: scrollPosition) + } + + static func validNavigateableItem(_ item: DiscoveryItem) -> Bool { + switch item { + case .hashtag: + return true + default: + return false + } + } + + func open() { + guard let indexPathForSelectedRow = tableView.indexPathForSelectedRow else { return } + guard let diffableDataSource = viewModel.diffableDataSource else { return } + guard let item = diffableDataSource.itemIdentifier(for: indexPathForSelectedRow) else { return } + + guard case let .hashtag(tag) = item else { return } + let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, hashtag: tag.name) + coordinator.present( + scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), + from: self, + transition: .show + ) + } +} diff --git a/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewModel+Diffable.swift b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewModel+Diffable.swift new file mode 100644 index 000000000..0370f3f58 --- /dev/null +++ b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewModel+Diffable.swift @@ -0,0 +1,42 @@ +// +// DiscoveryHashtagsViewModel+Diffable.swift +// Mastodon +// +// Created by MainasuK on 2022-4-13. +// + +import UIKit + +extension DiscoveryHashtagsViewModel { + + func setupDiffableDataSource( + tableView: UITableView + ) { + diffableDataSource = DiscoverySection.diffableDataSource( + tableView: tableView, + context: context, + configuration: DiscoverySection.Configuration() + ) + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.hashtags]) + diffableDataSource?.apply(snapshot) + + $hashtags + .receive(on: DispatchQueue.main) + .sink { [weak self] hashtags in + guard let self = self else { return } + guard let diffableDataSource = self.diffableDataSource else { return } + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.hashtags]) + + let items = hashtags.map { DiscoveryItem.hashtag($0) } + snapshot.appendItems(items, toSection: .hashtags) + + diffableDataSource.apply(snapshot) + } + .store(in: &disposeBag) + } + +} diff --git a/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewModel.swift b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewModel.swift new file mode 100644 index 000000000..1b119f3d7 --- /dev/null +++ b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewModel.swift @@ -0,0 +1,77 @@ +// +// DiscoveryHashtagsViewModel.swift +// Mastodon +// +// Created by MainasuK on 2022-4-13. +// + +import os.log +import UIKit +import Combine +import GameplayKit +import CoreData +import CoreDataStack +import MastodonSDK + +final class DiscoveryHashtagsViewModel { + + let logger = Logger(subsystem: "DiscoveryHashtagsViewModel", category: "ViewModel") + + var disposeBag = Set() + + // input + let context: AppContext + let viewDidAppeared = PassthroughSubject() + + // output + var diffableDataSource: UITableViewDiffableDataSource? + @Published var hashtags: [Mastodon.Entity.Tag] = [] + + init(context: AppContext) { + self.context = context + // end init + + Publishers.CombineLatest( + context.authenticationService.activeMastodonAuthenticationBox, + viewDidAppeared + ) + .compactMap { authenticationBox, _ -> MastodonAuthenticationBox? in + return authenticationBox + } + .throttle(for: 3, scheduler: DispatchQueue.main, latest: true) + .asyncMap { authenticationBox in + try await context.apiService.trendHashtags(domain: authenticationBox.domain, query: nil) + } + .retry(3) + .map { response in Result, Error> { response } } + .catch { error in Just(Result, Error> { throw error }) } + .receive(on: DispatchQueue.main) + .sink { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let response): + self.hashtags = response.value.filter { !$0.name.isEmpty } + case .failure: + break + } + } + .store(in: &disposeBag) + } + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + +extension DiscoveryHashtagsViewModel { + + @MainActor + func fetch() async throws { + guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return } + let response = try await context.apiService.trendHashtags(domain: authenticationBox.domain, query: nil) + hashtags = response.value.filter { !$0.name.isEmpty } + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch tags: \(response.value.count)") + } + +} diff --git a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewController.swift b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewController.swift new file mode 100644 index 000000000..f73602ae4 --- /dev/null +++ b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewController.swift @@ -0,0 +1,229 @@ +// +// DiscoveryNewsViewController.swift +// Mastodon +// +// Created by MainasuK on 2022-4-13. +// + +import os.log +import UIKit +import Combine +import MastodonUI + +final class DiscoveryNewsViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { + + let logger = Logger(subsystem: "TrendPostsViewController", category: "ViewController") + + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } + weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + + var disposeBag = Set() + var viewModel: DiscoveryNewsViewModel! + + let mediaPreviewTransitionController = MediaPreviewTransitionController() + + lazy var tableView: UITableView = { + let tableView = UITableView() + tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 100 + tableView.separatorStyle = .none + tableView.backgroundColor = .clear + return tableView + }() + + let refreshControl = UIRefreshControl() + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + +extension DiscoveryNewsViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.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( + tableView: tableView + ) + + tableView.refreshControl = refreshControl + refreshControl.addTarget(self, action: #selector(DiscoveryNewsViewController.refreshControlValueChanged(_:)), for: .valueChanged) + viewModel.didLoadLatest + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self = self else { return } + self.refreshControl.endRefreshing() + } + .store(in: &disposeBag) + + // setup batch fetch + viewModel.listBatchFetchViewModel.setup(scrollView: tableView) + viewModel.listBatchFetchViewModel.shouldFetch + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self = self else { return } + guard self.view.window != nil else { return } + self.viewModel.stateMachine.enter(DiscoveryNewsViewModel.State.Loading.self) + } + .store(in: &disposeBag) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + refreshControl.endRefreshing() + tableView.deselectRow(with: transitionCoordinator, animated: animated) + } + +} + +extension DiscoveryNewsViewController { + + @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) { + guard viewModel.stateMachine.enter(DiscoveryNewsViewModel.State.Reloading.self) else { + sender.endRefreshing() + return + } + } + +} + +// MARK: - UITableViewDelegate +extension DiscoveryNewsViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(indexPath)") + guard case let .link(link) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } + guard let url = URL(string: link.url) else { return } + coordinator.present( + scene: .safari(url: url), + from: self, + transition: .safariPresent(animated: true, completion: nil) + ) + } + +} + +// MARK: ScrollViewContainer +extension DiscoveryNewsViewController: ScrollViewContainer { + var scrollView: UIScrollView? { + tableView + } +} + +extension DiscoveryNewsViewController { + override var keyCommands: [UIKeyCommand]? { + return navigationKeyCommands + } +} + +extension DiscoveryNewsViewController: TableViewControllerNavigateable { + + func navigate(direction: TableViewNavigationDirection) { + if let indexPathForSelectedRow = tableView.indexPathForSelectedRow { + // navigate up/down on the current selected item + navigateToLink(direction: direction, indexPath: indexPathForSelectedRow) + } else { + // set first visible item selected + navigateToFirstVisibleLink() + } + } + + private func navigateToLink(direction: TableViewNavigationDirection, indexPath: IndexPath) { + guard let diffableDataSource = viewModel.diffableDataSource else { return } + let items = diffableDataSource.snapshot().itemIdentifiers + guard let selectedItem = diffableDataSource.itemIdentifier(for: indexPath), + let selectedItemIndex = items.firstIndex(of: selectedItem) else { + return + } + + let _navigateToItem: DiscoveryItem? = { + var index = selectedItemIndex + while 0.. 1 { + // drop first when visible not the first cell of table + visibleItems.removeFirst() + } + guard let item = visibleItems.first, let indexPath = diffableDataSource.indexPath(for: item) else { return } + let scrollPosition: UITableView.ScrollPosition = overrideNavigationScrollPosition ?? Self.navigateScrollPosition(tableView: tableView, indexPath: indexPath) + tableView.selectRow(at: indexPath, animated: true, scrollPosition: scrollPosition) + } + + static func validNavigateableItem(_ item: DiscoveryItem) -> Bool { + switch item { + case .link: + return true + default: + return false + } + } + + func open() { + guard let indexPathForSelectedRow = tableView.indexPathForSelectedRow else { return } + guard let diffableDataSource = viewModel.diffableDataSource else { return } + guard let item = diffableDataSource.itemIdentifier(for: indexPathForSelectedRow) else { return } + + guard case let .link(link) = item else { return } + guard let url = URL(string: link.url) else { return } + coordinator.present( + scene: .safari(url: url), + from: self, + transition: .safariPresent(animated: true, completion: nil) + ) + } + + func navigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) { + navigateKeyCommandHandler(sender) + } + +} diff --git a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+Diffable.swift b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+Diffable.swift new file mode 100644 index 000000000..ab3634a3f --- /dev/null +++ b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+Diffable.swift @@ -0,0 +1,60 @@ +// +// DiscoveryNewsViewModel+Diffable.swift +// Mastodon +// +// Created by MainasuK on 2022-4-13. +// + +import UIKit +import Combine + +extension DiscoveryNewsViewModel { + + func setupDiffableDataSource( + tableView: UITableView + ) { + diffableDataSource = DiscoverySection.diffableDataSource( + tableView: tableView, + context: context, + configuration: DiscoverySection.Configuration() + ) + + stateMachine.enter(State.Reloading.self) + + $links + .receive(on: DispatchQueue.main) + .sink { [weak self] links in + guard let self = self else { return } + guard let diffableDataSource = self.diffableDataSource else { return } + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.news]) + + let items = links.map { DiscoveryItem.link($0) } + snapshot.appendItems(items, toSection: .news) + + if let currentState = self.stateMachine.currentState { + switch currentState { + case is State.Initial, + is State.Loading, + is State.Idle, + is State.Fail: + if !items.isEmpty { + snapshot.appendItems([.bottomLoader], toSection: .news) + } + case is State.Reloading: + break + case is State.NoMore: + break + default: + assertionFailure() + break + } + } + + diffableDataSource.applySnapshot(snapshot, animated: false) + } + .store(in: &disposeBag) + } + +} diff --git a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+State.swift b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+State.swift new file mode 100644 index 000000000..92b84d176 --- /dev/null +++ b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+State.swift @@ -0,0 +1,213 @@ +// +// DiscoveryNewsViewModel+State.swift +// Mastodon +// +// Created by MainasuK on 2022-4-13. +// + +import os.log +import Foundation +import GameplayKit +import MastodonSDK + +extension DiscoveryNewsViewModel { + class State: GKState, NamingState { + + let logger = Logger(subsystem: "DiscoveryNewsViewModel.State", category: "StateMachine") + + let id = UUID() + + var name: String { + String(describing: Self.self) + } + + weak var viewModel: DiscoveryNewsViewModel? + + init(viewModel: DiscoveryNewsViewModel) { + self.viewModel = viewModel + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + let previousState = previousState as? DiscoveryNewsViewModel.State + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] enter \(self.name), previous: \(previousState?.name ?? "")") + } + + @MainActor + func enter(state: State.Type) { + stateMachine?.enter(state) + } + + deinit { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(self.name)") + } + } +} + +extension DiscoveryNewsViewModel.State { + class Initial: DiscoveryNewsViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Reloading.Type: + return true + default: + return false + } + } + } + + class Reloading: DiscoveryNewsViewModel.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 } + + stateMachine.enter(Loading.self) + } + } + + class Fail: DiscoveryNewsViewModel.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: DiscoveryNewsViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Reloading.Type, is Loading.Type: + return true + default: + return false + } + } + } + + class Loading: DiscoveryNewsViewModel.State { + + var offset: Int? + + 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) + guard let viewModel = viewModel, let stateMachine = stateMachine else { return } + + + switch previousState { + case is Reloading: + offset = nil + default: + break + } + + guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else { + stateMachine.enter(Fail.self) + return + } + + let offset = self.offset + let isReloading = offset == nil + + Task { + do { + let response = try await viewModel.context.apiService.trendLinks( + domain: authenticationBox.domain, + query: Mastodon.API.Trends.StatusQuery( + offset: offset, + limit: nil + ) + ) + let newOffset: Int? = { + guard let offset = response.link?.offset else { return nil } + return self.offset.flatMap { max($0, offset) } ?? offset + }() + + let hasMore: Bool = { + guard let newOffset = newOffset else { return false } + return newOffset != self.offset // not the same one + }() + + self.offset = newOffset + + var hasNewItemsAppend = false + var links = isReloading ? [] : viewModel.links + for link in response.value { + guard !links.contains(link) else { continue } + links.append(link) + hasNewItemsAppend = true + } + + if hasNewItemsAppend, hasMore { + await enter(state: Idle.self) + } else { + await enter(state: NoMore.self) + } + viewModel.links = links + viewModel.didLoadLatest.send() + } catch { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch news fail: \(error.localizedDescription)") + if let error = error as? Mastodon.API.Error, error.httpResponseStatus.code == 404 { + viewModel.isServerSupportEndpoint = false + await enter(state: NoMore.self) + } else { + await enter(state: Fail.self) + } + + viewModel.didLoadLatest.send() + } + } // end Task + } // end func + } + + class NoMore: DiscoveryNewsViewModel.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) + } + } +} diff --git a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel.swift b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel.swift new file mode 100644 index 000000000..2c4d89dc8 --- /dev/null +++ b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel.swift @@ -0,0 +1,74 @@ +// +// DiscoveryNewsViewModel.swift +// Mastodon +// +// Created by MainasuK on 2022-4-13. +// + +import os.log +import UIKit +import Combine +import GameplayKit +import CoreData +import CoreDataStack +import MastodonSDK + +final class DiscoveryNewsViewModel { + + var disposeBag = Set() + + // input + let context: AppContext + let listBatchFetchViewModel = ListBatchFetchViewModel() + + // output + @Published var links: [Mastodon.Entity.Link] = [] + 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 + }() + + let didLoadLatest = PassthroughSubject() + @Published var isServerSupportEndpoint = true + + init(context: AppContext) { + self.context = context + // end init + + Task { + await checkServerEndpoint() + } // end Task + } + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + + +extension DiscoveryNewsViewModel { + func checkServerEndpoint() async { + guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return } + + do { + _ = try await context.apiService.trendLinks( + domain: authenticationBox.domain, + query: .init(offset: nil, limit: nil) + ) + } catch let error as Mastodon.API.Error where error.httpResponseStatus.code == 404 { + isServerSupportEndpoint = false + } catch { + // do nothing + } + } +} diff --git a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController+DataSourceProvider.swift b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController+DataSourceProvider.swift new file mode 100644 index 000000000..c3495b245 --- /dev/null +++ b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController+DataSourceProvider.swift @@ -0,0 +1,34 @@ +// +// DiscoveryPostsViewController+DataSourceProvider.swift +// Mastodon +// +// Created by MainasuK on 2022-4-12. +// + +import UIKit + +extension DiscoveryPostsViewController: DataSourceProvider { + func item(from source: DataSourceItem.Source) async -> DataSourceItem? { + var _indexPath = source.indexPath + if _indexPath == nil, let cell = source.tableViewCell { + _indexPath = await self.indexPath(for: cell) + } + guard let indexPath = _indexPath else { return nil } + + guard let item = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { + return nil + } + + switch item { + case .status(let record): + return .status(record: record) + default: + return nil + } + } + + @MainActor + private func indexPath(for cell: UITableViewCell) async -> IndexPath? { + return tableView.indexPath(for: cell) + } +} diff --git a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift new file mode 100644 index 000000000..a1d5b5e76 --- /dev/null +++ b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift @@ -0,0 +1,190 @@ +// +// DiscoveryPostsViewController.swift +// Mastodon +// +// Created by MainasuK on 2022-4-12. +// + +import os.log +import UIKit +import Combine +import MastodonUI + +final class DiscoveryPostsViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { + + let logger = Logger(subsystem: "TrendPostsViewController", category: "ViewController") + + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } + weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + + var disposeBag = Set() + var viewModel: DiscoveryPostsViewModel! + + let mediaPreviewTransitionController = MediaPreviewTransitionController() + + lazy var tableView: UITableView = { + let tableView = UITableView() + tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 100 + tableView.separatorStyle = .none + tableView.backgroundColor = .clear + return tableView + }() + + let refreshControl = UIRefreshControl() + + let discoveryIntroBannerView = DiscoveryIntroBannerView() + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + +extension DiscoveryPostsViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.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), + ]) + + discoveryIntroBannerView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(discoveryIntroBannerView) + NSLayoutConstraint.activate([ + discoveryIntroBannerView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor), + discoveryIntroBannerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + discoveryIntroBannerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + ]) + + discoveryIntroBannerView.delegate = self + discoveryIntroBannerView.isHidden = UserDefaults.shared.discoveryIntroBannerNeedsHidden + UserDefaults.shared.publisher(for: \.discoveryIntroBannerNeedsHidden) + .receive(on: DispatchQueue.main) + .assign(to: \.isHidden, on: discoveryIntroBannerView) + .store(in: &disposeBag) + + tableView.delegate = self + viewModel.setupDiffableDataSource( + tableView: tableView, + statusTableViewCellDelegate: self + ) + + tableView.refreshControl = refreshControl + refreshControl.addTarget(self, action: #selector(DiscoveryPostsViewController.refreshControlValueChanged(_:)), for: .valueChanged) + viewModel.didLoadLatest + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self = self else { return } + self.refreshControl.endRefreshing() + } + .store(in: &disposeBag) + + // setup batch fetch + viewModel.listBatchFetchViewModel.setup(scrollView: tableView) + viewModel.listBatchFetchViewModel.shouldFetch + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self = self else { return } + guard self.view.window != nil else { return } + self.viewModel.stateMachine.enter(DiscoveryPostsViewModel.State.Loading.self) + } + .store(in: &disposeBag) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + refreshControl.endRefreshing() + tableView.deselectRow(with: transitionCoordinator, animated: animated) + } + +} + +extension DiscoveryPostsViewController { + + @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) { + guard viewModel.stateMachine.enter(DiscoveryPostsViewModel.State.Reloading.self) else { + sender.endRefreshing() + return + } + } + +} + +// MARK: - UITableViewDelegate +extension DiscoveryPostsViewController: UITableViewDelegate, AutoGenerateTableViewDelegate { + // sourcery:inline:DiscoveryPostsViewController.AutoGenerateTableViewDelegate + + // Generated using Sourcery + // DO NOT EDIT + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + aspectTableView(tableView, didSelectRowAt: 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) + } + // sourcery:end +} + +// MARK: - StatusTableViewCellDelegate +extension DiscoveryPostsViewController: StatusTableViewCellDelegate { } + +// MARK: ScrollViewContainer +extension DiscoveryPostsViewController: ScrollViewContainer { + var scrollView: UIScrollView? { + tableView + } +} + +// MARK: - DiscoveryIntroBannerViewDelegate +extension DiscoveryPostsViewController: DiscoveryIntroBannerViewDelegate { + func discoveryIntroBannerView(_ bannerView: DiscoveryIntroBannerView, closeButtonDidPressed button: UIButton) { + UserDefaults.shared.discoveryIntroBannerNeedsHidden = true + } +} + +extension DiscoveryPostsViewController { + override var keyCommands: [UIKeyCommand]? { + return navigationKeyCommands + statusNavigationKeyCommands + } +} + +// MARK: - StatusTableViewControllerNavigateable +extension DiscoveryPostsViewController: StatusTableViewControllerNavigateable { + @objc func navigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) { + navigateKeyCommandHandler(sender) + } + + @objc func statusKeyCommandHandlerRelay(_ sender: UIKeyCommand) { + statusKeyCommandHandler(sender) + } +} diff --git a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+Diffable.swift b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+Diffable.swift new file mode 100644 index 000000000..5c82384c7 --- /dev/null +++ b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+Diffable.swift @@ -0,0 +1,65 @@ +// +// DiscoveryPostsViewModel+Diffable.swift +// Mastodon +// +// Created by MainasuK on 2022-4-12. +// + +import UIKit +import Combine + +extension DiscoveryPostsViewModel { + + func setupDiffableDataSource( + tableView: UITableView, + statusTableViewCellDelegate: StatusTableViewCellDelegate + ) { + diffableDataSource = StatusSection.diffableDataSource( + tableView: tableView, + context: context, + configuration: StatusSection.Configuration( + statusTableViewCellDelegate: statusTableViewCellDelegate, + timelineMiddleLoaderTableViewCellDelegate: nil, + filterContext: .none, + activeFilters: nil + ) + ) + + stateMachine.enter(State.Reloading.self) + + statusFetchedResultsController.$records + .receive(on: DispatchQueue.main) + .sink { [weak self] records in + guard let self = self else { return } + guard let diffableDataSource = self.diffableDataSource else { return } + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + + let items = records.map { StatusItem.status(record: $0) } + snapshot.appendItems(items, toSection: .main) + + if let currentState = self.stateMachine.currentState { + switch currentState { + case is State.Initial, + is State.Reloading, + is State.Loading, + is State.Idle, + is State.Fail: + if !items.isEmpty { + snapshot.appendItems([.bottomLoader], toSection: .main) + } + case is State.NoMore: + break + default: + assertionFailure() + break + } + } + + diffableDataSource.applySnapshot(snapshot, animated: false) + } + .store(in: &disposeBag) + } + +} diff --git a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+State.swift b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+State.swift new file mode 100644 index 000000000..0ff6cbb14 --- /dev/null +++ b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+State.swift @@ -0,0 +1,213 @@ +// +// DiscoveryPostsViewModel+State.swift +// Mastodon +// +// Created by MainasuK on 2022-4-12. +// + +import os.log +import Foundation +import GameplayKit +import MastodonSDK + +extension DiscoveryPostsViewModel { + class State: GKState, NamingState { + + let logger = Logger(subsystem: "DiscoveryPostsViewModel.State", category: "StateMachine") + + let id = UUID() + + var name: String { + String(describing: Self.self) + } + + weak var viewModel: DiscoveryPostsViewModel? + + init(viewModel: DiscoveryPostsViewModel) { + self.viewModel = viewModel + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + let previousState = previousState as? DiscoveryPostsViewModel.State + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] enter \(self.name), previous: \(previousState?.name ?? "")") + } + + @MainActor + func enter(state: State.Type) { + stateMachine?.enter(state) + } + + deinit { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(self.name)") + } + } +} + +extension DiscoveryPostsViewModel.State { + class Initial: DiscoveryPostsViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Reloading.Type: + return true + default: + return false + } + } + } + + class Reloading: DiscoveryPostsViewModel.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 } + + stateMachine.enter(Loading.self) + } + } + + class Fail: DiscoveryPostsViewModel.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: DiscoveryPostsViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Reloading.Type, is Loading.Type: + return true + default: + return false + } + } + } + + class Loading: DiscoveryPostsViewModel.State { + + var offset: Int? + + 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) + guard let viewModel = viewModel, let stateMachine = stateMachine else { return } + + switch previousState { + case is Reloading: + offset = nil + default: + break + } + + guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else { + stateMachine.enter(Fail.self) + return + } + + let offset = self.offset + let isReloading = offset == nil + + Task { + do { + let response = try await viewModel.context.apiService.trendStatuses( + domain: authenticationBox.domain, + query: Mastodon.API.Trends.StatusQuery( + offset: offset, + limit: nil + ) + ) + let newOffset: Int? = { + guard let offset = response.link?.offset else { return nil } + return self.offset.flatMap { max($0, offset) } ?? offset + }() + + let hasMore: Bool = { + guard let newOffset = newOffset else { return false } + return newOffset != self.offset // not the same one + }() + + self.offset = newOffset + + var hasNewStatusesAppend = false + var statusIDs = isReloading ? [] : viewModel.statusFetchedResultsController.statusIDs.value + for status in response.value { + guard !statusIDs.contains(status.id) else { continue } + statusIDs.append(status.id) + hasNewStatusesAppend = true + } + + if hasNewStatusesAppend, hasMore { + await enter(state: Idle.self) + } else { + await enter(state: NoMore.self) + } + viewModel.statusFetchedResultsController.statusIDs.value = statusIDs + viewModel.didLoadLatest.send() + + } catch { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch posts fail: \(error.localizedDescription)") + if let error = error as? Mastodon.API.Error, error.httpResponseStatus.code == 404 { + viewModel.isServerSupportEndpoint = false + await enter(state: NoMore.self) + } else { + await enter(state: Fail.self) + } + + viewModel.didLoadLatest.send() + } + } // end Task + } // end func + } + + class NoMore: DiscoveryPostsViewModel.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) + } + } +} diff --git a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel.swift b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel.swift new file mode 100644 index 000000000..c001bb7b3 --- /dev/null +++ b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel.swift @@ -0,0 +1,83 @@ +// +// DiscoveryPostsViewModel.swift +// Mastodon +// +// Created by MainasuK on 2022-4-12. +// + +import os.log +import UIKit +import Combine +import GameplayKit +import CoreData +import CoreDataStack +import MastodonSDK + +final class DiscoveryPostsViewModel { + + var disposeBag = Set() + + // input + let context: AppContext + let statusFetchedResultsController: StatusFetchedResultsController + let listBatchFetchViewModel = ListBatchFetchViewModel() + + // 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 + }() + + let didLoadLatest = PassthroughSubject() + @Published var isServerSupportEndpoint = true + + init(context: AppContext) { + self.context = context + self.statusFetchedResultsController = StatusFetchedResultsController( + managedObjectContext: context.managedObjectContext, + domain: nil, + additionalTweetPredicate: nil + ) + // end init + + context.authenticationService.activeMastodonAuthentication + .map { $0?.domain } + .assign(to: \.value, on: statusFetchedResultsController.domain) + .store(in: &disposeBag) + + Task { + await checkServerEndpoint() + } // end Task + } + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + +extension DiscoveryPostsViewModel { + func checkServerEndpoint() async { + guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return } + + do { + _ = try await context.apiService.trendStatuses( + domain: authenticationBox.domain, + query: .init(offset: nil, limit: nil) + ) + } catch let error as Mastodon.API.Error where error.httpResponseStatus.code == 404 { + isServerSupportEndpoint = false + } catch { + // do nothing + } + } +} diff --git a/Mastodon/Scene/Discovery/View/DiscoveryIntroBannerView.swift b/Mastodon/Scene/Discovery/View/DiscoveryIntroBannerView.swift new file mode 100644 index 000000000..afc2cb7db --- /dev/null +++ b/Mastodon/Scene/Discovery/View/DiscoveryIntroBannerView.swift @@ -0,0 +1,102 @@ +// +// DiscoveryIntroBannerView.swift +// Mastodon +// +// Created by MainasuK on 2022-4-19. +// + +import os.log +import UIKit +import Combine +import MastodonAsset +import MastodonLocalization + +public protocol DiscoveryIntroBannerViewDelegate: AnyObject { + func discoveryIntroBannerView(_ bannerView: DiscoveryIntroBannerView, closeButtonDidPressed button: UIButton) +} + +public final class DiscoveryIntroBannerView: UIView { + + let logger = Logger(subsystem: "DiscoveryIntroBannerView", category: "View") + + var _disposeBag = Set() + + public weak var delegate: DiscoveryIntroBannerViewDelegate? + + let label: UILabel = { + let label = UILabel() + label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 16, weight: .regular)) + label.textColor = Asset.Colors.Label.primary.color + label.text = L10n.Scene.Discovery.intro + label.numberOfLines = 0 + return label + }() + + let closeButton: HitTestExpandedButton = { + let button = HitTestExpandedButton(type: .system) + button.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal) + button.tintColor = Asset.Colors.Label.secondary.color + return button + }() + + public override init(frame: CGRect) { + super.init(frame: frame) + _init() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension DiscoveryIntroBannerView { + private func _init() { + preservesSuperviewLayoutMargins = true + + setupAppearance(theme: ThemeService.shared.currentTheme.value) + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.main) + .sink { [weak self] theme in + guard let self = self else { return } + self.setupAppearance(theme: theme) + } + .store(in: &_disposeBag) + + closeButton.translatesAutoresizingMaskIntoConstraints = false + addSubview(closeButton) + NSLayoutConstraint.activate([ + closeButton.topAnchor.constraint(equalTo: topAnchor, constant: 16).priority(.required - 1), + layoutMarginsGuide.trailingAnchor.constraint(equalTo: closeButton.trailingAnchor), + closeButton.heightAnchor.constraint(equalToConstant: 20).priority(.required - 1), + closeButton.widthAnchor.constraint(equalToConstant: 20).priority(.required - 1), + ]) + + label.translatesAutoresizingMaskIntoConstraints = false + addSubview(label) + NSLayoutConstraint.activate([ + label.topAnchor.constraint(equalTo: topAnchor, constant: 16).priority(.required - 1), + label.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), + closeButton.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 10), + bottomAnchor.constraint(equalTo: label.bottomAnchor, constant: 16).priority(.required - 1), + ]) + + closeButton.addTarget(self, action: #selector(DiscoveryIntroBannerView.closeButtonDidPressed(_:)), for: .touchUpInside) + } +} + +extension DiscoveryIntroBannerView { + @objc private func closeButtonDidPressed(_ sender: UIButton) { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + delegate?.discoveryIntroBannerView(self, closeButtonDidPressed: sender) + } +} + +extension DiscoveryIntroBannerView { + + private func setupAppearance(theme: Theme) { + backgroundColor = theme.systemBackgroundColor + } + +} diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift index b3a8ca040..31de401d2 100644 --- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift +++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift @@ -28,7 +28,7 @@ final class HashtagTimelineViewController: UIViewController, NeedsDependency, Me let composeBarButtonItem: UIBarButtonItem = { let barButtonItem = UIBarButtonItem() - barButtonItem.image = UIImage(systemName: "square.and.pencil")?.withRenderingMode(.alwaysTemplate) + barButtonItem.image = Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate) return barButtonItem }() @@ -84,7 +84,6 @@ extension HashtagTimelineViewController { ]) tableView.delegate = self -// tableView.prefetchDataSource = self viewModel.setupDiffableDataSource( tableView: tableView, statusTableViewCellDelegate: self @@ -158,27 +157,6 @@ extension HashtagTimelineViewController { } -// MARK: - TableViewCellHeightCacheableContainer -//extension HashtagTimelineViewController: TableViewCellHeightCacheableContainer { -// var cellFrameCache: NSCache { -// return viewModel.cellFrameCache -// } -//} - -//// MARK: - UIScrollViewDelegate -//extension HashtagTimelineViewController { -// func scrollViewDidScroll(_ scrollView: UIScrollView) { -// aspectScrollViewDidScroll(scrollView) -// } -//} - -//extension HashtagTimelineViewController: LoadMoreConfigurableTableViewContainer { -// typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell -// typealias LoadingState = HashtagTimelineViewModel.LoadOldestState.Loading -// var loadMoreConfigurableTableView: UITableView { return tableView } -// var loadMoreConfigurableStateMachine: GKStateMachine { return viewModel.loadOldestStateMachine } -//} - // MARK: - UITableViewDelegate extension HashtagTimelineViewController: UITableViewDelegate, AutoGenerateTableViewDelegate { // sourcery:inline:HashtagTimelineViewController.AutoGenerateTableViewDelegate @@ -206,82 +184,23 @@ extension HashtagTimelineViewController: UITableViewDelegate, AutoGenerateTableV } // sourcery:end -// func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { -// return aspectTableView(tableView, estimatedHeightForRowAt: indexPath) -// } -// -// func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { -// aspectTableView(tableView, willDisplay: cell, forRowAt: indexPath) -// } -// -// func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { -// aspectTableView(tableView, didEndDisplaying: cell, forRowAt: indexPath) -// } -// -// func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { -// aspectTableView(tableView, didSelectRowAt: 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) -// } - } -// MARK: - UITableViewDataSourcePrefetching -//extension HashtagTimelineViewController: UITableViewDataSourcePrefetching { -// func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) { -// aspectTableView(tableView, prefetchRowsAt: indexPaths) -// } -//} - // MARK: - StatusTableViewCellDelegate extension HashtagTimelineViewController: StatusTableViewCellDelegate { } -// MARK: - AVPlayerViewControllerDelegate -//extension HashtagTimelineViewController: AVPlayerViewControllerDelegate { -// -// func playerViewController(_ playerViewController: AVPlayerViewController, willBeginFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) { -// aspectPlayerViewController(playerViewController, willBeginFullScreenPresentationWithAnimationCoordinator: coordinator) -// } -// -// func playerViewController(_ playerViewController: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) { -// aspectPlayerViewController(playerViewController, willEndFullScreenPresentationWithAnimationCoordinator: coordinator) -// } -// -//} - -// MARK: - StatusTableViewCellDelegate -//extension HashtagTimelineViewController: StatusTableViewCellDelegate { -// weak var playerViewControllerDelegate: AVPlayerViewControllerDelegate? { return self } -// func parent() -> UIViewController { return self } -//} - -//extension HashtagTimelineViewController { -// override var keyCommands: [UIKeyCommand]? { -// return navigationKeyCommands + statusNavigationKeyCommands -// } -//} -// -//// MARK: - StatusTableViewControllerNavigateable -//extension HashtagTimelineViewController: StatusTableViewControllerNavigateable { -// @objc func navigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) { -// navigateKeyCommandHandler(sender) -// } -// -// @objc func statusKeyCommandHandlerRelay(_ sender: UIKeyCommand) { -// statusKeyCommandHandler(sender) -// } -//} +extension HashtagTimelineViewController { + override var keyCommands: [UIKeyCommand]? { + return navigationKeyCommands + statusNavigationKeyCommands + } +} +// MARK: - StatusTableViewControllerNavigateable +extension HashtagTimelineViewController: StatusTableViewControllerNavigateable { + @objc func navigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) { + navigateKeyCommandHandler(sender) + } + + @objc func statusKeyCommandHandlerRelay(_ sender: UIKeyCommand) { + statusKeyCommandHandler(sender) + } +} diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift index 8b1d390f5..4fae66d33 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift @@ -58,6 +58,10 @@ extension HomeTimelineViewController { guard let self = self else { return } self.showWelcomeAction(action) }, + UIAction(title: "Register", image: UIImage(systemName: "list.bullet.rectangle.portrait.fill"), attributes: []) { [weak self] action in + guard let self = self else { return } + self.showRegisterAction(action) + }, UIAction(title: "Confirm Email", image: UIImage(systemName: "envelope"), attributes: []) { [weak self] action in guard let self = self else { return } self.showConfirmEmail(action) @@ -182,7 +186,7 @@ extension HomeTimelineViewController { } func match(item: StatusItem) -> Bool { - let authenticationBox = AppContext.shared.authenticationService.activeMastodonAuthenticationBox.value + // let authenticationBox = AppContext.shared.authenticationService.activeMastodonAuthenticationBox.value switch item { case .feed(let record): guard let feed = record.object(in: AppContext.shared.managedObjectContext) else { return false } @@ -294,6 +298,33 @@ extension HomeTimelineViewController { @objc private func showWelcomeAction(_ sender: UIAction) { coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil)) } + + @objc private func showRegisterAction(_ sender: UIAction) { + Task { @MainActor in + try await showRegisterController() + } // end Task + } + + @MainActor + func showRegisterController(domain: String = "mstdn.jp") async throws { + let viewController = try await MastodonRegisterViewController.create( + context: context, + coordinator: coordinator, + domain: "mstdn.jp" + ) + let navigationController = UINavigationController(rootViewController: viewController) + navigationController.modalPresentationStyle = .fullScreen + present(navigationController, animated: true) { + viewController.navigationItem.leftBarButtonItem = UIBarButtonItem( + systemItem: .close, + primaryAction: UIAction(handler: { [weak viewController] _ in + guard let viewController = viewController else { return } + viewController.dismiss(animated: true) + }), + menu: nil + ) + } + } @objc private func showConfirmEmail(_ sender: UIAction) { let mastodonConfirmEmailViewModel = MastodonConfirmEmailViewModel() diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift index 7b7f35e5d..64d3d5941 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift @@ -17,6 +17,7 @@ import AlamofireImage import StoreKit import MastodonAsset import MastodonLocalization +import MastodonUI final class HomeTimelineViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { @@ -50,19 +51,11 @@ final class HomeTimelineViewController: UIViewController, NeedsDependency, Media let settingBarButtonItem: UIBarButtonItem = { let barButtonItem = UIBarButtonItem() barButtonItem.tintColor = ThemeService.tintColor - barButtonItem.image = UIImage(systemName: "gear")?.withRenderingMode(.alwaysTemplate) + barButtonItem.image = Asset.ObjectsAndTools.gear.image.withRenderingMode(.alwaysTemplate) barButtonItem.accessibilityLabel = L10n.Common.Controls.Actions.settings return barButtonItem }() - let composeBarButtonItem: UIBarButtonItem = { - let barButtonItem = UIBarButtonItem() - barButtonItem.tintColor = ThemeService.tintColor - barButtonItem.image = UIImage(systemName: "square.and.pencil")?.withRenderingMode(.alwaysTemplate) - barButtonItem.accessibilityLabel = L10n.Common.Controls.Actions.compose - return barButtonItem - }() - let tableView: UITableView = { let tableView = ControlContainableTableView() tableView.register(StatusTableViewCell.self, forCellReuseIdentifier: String(describing: StatusTableViewCell.self)) @@ -108,14 +101,14 @@ extension HomeTimelineViewController { guard let self = self else { return } #if DEBUG // display debug menu - self.navigationItem.leftBarButtonItem = { + self.navigationItem.rightBarButtonItem = { let barButtonItem = UIBarButtonItem() barButtonItem.image = UIImage(systemName: "ellipsis.circle") barButtonItem.menu = self.debugMenu return barButtonItem }() #else - self.navigationItem.leftBarButtonItem = displaySettingBarButtonItem ? self.settingBarButtonItem : nil + self.navigationItem.rightBarButtonItem = displaySettingBarButtonItem ? self.settingBarButtonItem : nil #endif } .store(in: &disposeBag) @@ -132,16 +125,6 @@ extension HomeTimelineViewController { titleView.button.menu = self.debugMenu #endif - viewModel.$displayComposeBarButtonItem - .receive(on: DispatchQueue.main) - .sink { [weak self] displayComposeBarButtonItem in - guard let self = self else { return } - self.navigationItem.rightBarButtonItem = displayComposeBarButtonItem ? self.composeBarButtonItem : nil - } - .store(in: &disposeBag) - composeBarButtonItem.target = self - composeBarButtonItem.action = #selector(HomeTimelineViewController.composeBarButtonItemPressed(_:)) - navigationItem.titleView = titleView titleView.delegate = self @@ -291,7 +274,7 @@ extension HomeTimelineViewController { tableView.deselectRow(with: transitionCoordinator, animated: animated) // needs trigger manually after onboarding dismiss - setNeedsStatusBarAppearanceUpdate() + setNeedsStatusBarAppearanceUpdate() } override func viewDidAppear(_ animated: Bool) { @@ -410,18 +393,7 @@ extension HomeTimelineViewController { let settingsViewModel = SettingsViewModel(context: context, setting: setting) coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil)) } - - @objc private func composeBarButtonItemPressed(_ sender: UIBarButtonItem) { - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) - guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return } - let composeViewModel = ComposeViewModel( - context: context, - composeKind: .post, - authenticationBox: authenticationBox - ) - coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil)) - } - + @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) { guard viewModel.loadLatestStateMachine.enter(HomeTimelineViewModel.LoadLatestState.Loading.self) else { sender.endRefreshing() diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift index b2c280fb5..be7de3a5a 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift @@ -33,7 +33,6 @@ final class HomeTimelineViewModel: NSObject { @Published var lastAutomaticFetchTimestamp: Date? = nil @Published var scrollPositionRecord: ScrollPositionRecord? = nil @Published var displaySettingBarButtonItem = true - @Published var displayComposeBarButtonItem = true weak var tableView: UITableView? weak var timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate? diff --git a/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift b/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift index e67ee0106..016884f72 100644 --- a/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift +++ b/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift @@ -108,6 +108,8 @@ extension HomeTimelineNavigationBarTitleView { logoButton.setImage(Asset.Asset.mastodonTextLogo.image.withRenderingMode(.alwaysTemplate), for: .normal) logoButton.contentMode = .center logoButton.isHidden = false + logoButton.accessibilityLabel = "Logo Button" // TODO :i18n + logoButton.accessibilityHint = "Tap to scroll to top and tap again to previous location" case .newPostButton: configureButton( title: L10n.Scene.HomeTimeline.NavigationBarState.newPosts, @@ -115,6 +117,7 @@ extension HomeTimelineNavigationBarTitleView { backgroundColor: Asset.Colors.brandBlue.color ) button.isHidden = false + button.accessibilityLabel = L10n.Scene.HomeTimeline.NavigationBarState.newPosts case .offlineButton: configureButton( title: L10n.Scene.HomeTimeline.NavigationBarState.offline, @@ -122,12 +125,14 @@ extension HomeTimelineNavigationBarTitleView { backgroundColor: Asset.Colors.danger.color ) button.isHidden = false + button.accessibilityLabel = L10n.Scene.HomeTimeline.NavigationBarState.offline case .publishingPostLabel: label.font = .systemFont(ofSize: 17, weight: .semibold) label.textColor = Asset.Colors.Label.primary.color label.text = L10n.Scene.HomeTimeline.NavigationBarState.publishing label.textAlignment = .center label.isHidden = false + button.accessibilityLabel = L10n.Scene.HomeTimeline.NavigationBarState.publishing case .publishedButton: blockingState = state configureButton( @@ -136,6 +141,7 @@ extension HomeTimelineNavigationBarTitleView { backgroundColor: Asset.Colors.successGreen.color ) button.isHidden = false + button.accessibilityLabel = L10n.Scene.HomeTimeline.NavigationBarState.published let presentDuration: TimeInterval = 0.33 let scaleAnimator = UIViewPropertyAnimator(duration: presentDuration, timingParameters: UISpringTimingParameters()) diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift index bdb4d05cb..16130251c 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift @@ -39,6 +39,8 @@ final class NotificationTimelineViewController: UIViewController, NeedsDependenc return tableView }() + let cellFrameCache = NSCache() + deinit { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) } @@ -122,6 +124,16 @@ extension NotificationTimelineViewController { } +// MARK: - CellFrameCacheContainer +extension NotificationTimelineViewController: CellFrameCacheContainer { + func keyForCache(tableView: UITableView, indexPath: IndexPath) -> NSNumber? { + guard let diffableDataSource = viewModel.diffableDataSource else { return nil } + guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return nil } + let key = NSNumber(value: item.hashValue) + return key + } +} + extension NotificationTimelineViewController { @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) { @@ -162,6 +174,13 @@ extension NotificationTimelineViewController: UITableViewDelegate, AutoGenerateT // sourcery:end + func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { + guard let frame = retrieveCellFrame(tableView: tableView, indexPath: indexPath) else { + return 300 + } + return ceil(frame.height) + } + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { guard let item = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return @@ -172,6 +191,10 @@ extension NotificationTimelineViewController: UITableViewDelegate, AutoGenerateT await viewModel.loadMore(item: item) } } + + func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { + cacheCellFrame(tableView: tableView, didEndDisplaying: cell, forRowAt: indexPath) + } } diff --git a/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift b/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift index b1b2280d8..cb7a96f85 100644 --- a/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift +++ b/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift @@ -229,6 +229,11 @@ extension MastodonConfirmEmailViewController { } } +// MARK: - PanPopableViewController +extension MastodonConfirmEmailViewController: PanPopableViewController { + var isPanPopable: Bool { false } +} + // MARK: - OnboardingViewControllerAppearance extension MastodonConfirmEmailViewController: OnboardingViewControllerAppearance { } diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift index 2d43faa56..d05b446ae 100644 --- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift +++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift @@ -12,6 +12,7 @@ import GameController import AuthenticationServices import MastodonAsset import MastodonLocalization +import MastodonUI final class MastodonPickServerViewController: UIViewController, NeedsDependency { @@ -91,7 +92,7 @@ extension MastodonPickServerViewController { 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.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor), ]) navigationActionView.translatesAutoresizingMaskIntoConstraints = false @@ -106,10 +107,10 @@ extension MastodonPickServerViewController { ]) navigationActionView - .observe(\.bounds, options: [.initial, .new]) { [weak self] navigationActionView, _ in + .observe(\.bounds, options: [.initial, .new]) { [weak self] _, _ in guard let self = self else { return } - let inset = navigationActionView.frame.height - self.tableView.contentInset.bottom = inset + let inset = self.navigationActionView.frame.height + self.viewModel.additionalTableViewInsets.bottom = inset } .store(in: &observations) @@ -144,6 +145,14 @@ extension MastodonPickServerViewController { pickServerServerSectionTableHeaderViewDelegate: self, pickServerCellDelegate: self ) + + KeyboardResponderService + .configure( + scrollView: tableView, + layoutNeedsUpdate: viewModel.viewDidAppear.eraseToAnyPublisher(), + additionalSafeAreaInsets: viewModel.$additionalTableViewInsets.eraseToAnyPublisher() + ) + .store(in: &disposeBag) viewModel .selectedServer @@ -238,6 +247,7 @@ extension MastodonPickServerViewController { super.viewDidAppear(animated) tableView.flashScrollIndicators() + viewModel.viewDidAppear.send() } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { @@ -332,7 +342,10 @@ extension MastodonPickServerViewController { ) else { throw APIService.APIError.explicit(.badResponse) } - return MastodonPickServerViewModel.SignUpResponseSecond(instance: response.instance, authenticateInfo: authenticateInfo) + return MastodonPickServerViewModel.SignUpResponseSecond( + instance: response.instance, + authenticateInfo: authenticateInfo + ) } .compactMap { [weak self] response -> AnyPublisher? in guard let self = self else { return nil } @@ -344,7 +357,13 @@ extension MastodonPickServerViewController { clientSecret: authenticateInfo.clientSecret, redirectURI: authenticateInfo.redirectURI ) - .map { MastodonPickServerViewModel.SignUpResponseThird(instance: instance, authenticateInfo: authenticateInfo, applicationToken: $0) } + .map { + MastodonPickServerViewModel.SignUpResponseThird( + instance: instance, + authenticateInfo: authenticateInfo, + applicationToken: $0 + ) + } .eraseToAnyPublisher() } .switchToLatest() @@ -416,28 +435,6 @@ extension MastodonPickServerViewController: UITableViewDelegate { viewModel.selectedServer.send(nil) } - 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 .categoryPicker: -// guard let cell = cell as? PickServerCategoriesCell else { return } -// guard let diffableDataSource = cell.diffableDataSource else { return } -// let snapshot = diffableDataSource.snapshot() -// -// let item = viewModel.selectCategoryItem.value -// guard let section = snapshot.indexOfSection(.main), -// let row = snapshot.indexOfItem(item) else { return } -// cell.collectionView.selectItem(at: IndexPath(item: row, section: section), animated: false, scrollPosition: .centeredHorizontally) -// case .search: -// guard let cell = cell as? PickServerSearchCell else { return } -// cell.searchTextField.text = viewModel.searchText.value - default: - break - } - } - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { guard let diffableDataSource = viewModel.diffableDataSource else { return nil } let snapshot = diffableDataSource.snapshot() diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift index af38b110b..b077cbbe1 100644 --- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift +++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift @@ -45,6 +45,8 @@ class MastodonPickServerViewModel: NSObject { let indexedServers = CurrentValueSubject<[Mastodon.Entity.Server], Never>([]) let unindexedServers = CurrentValueSubject<[Mastodon.Entity.Server]?, Never>([]) // set nil when loading let viewWillAppear = PassthroughSubject() + let viewDidAppear = CurrentValueSubject(Void()) + @Published var additionalTableViewInsets: UIEdgeInsets = .zero // output var diffableDataSource: UITableViewDiffableDataSource? @@ -114,8 +116,11 @@ extension MastodonPickServerViewModel { if self.mode == .signUp { indexedServers = indexedServers.filter { !$0.approvalRequired } } + // Note: + // sort by calculate last week users count + // and make medium size (~800) server to top - // group by language user preferred language first. Then sort by `totalUsers` + // group by language user preferred language first var languageToServersMapping = OrderedDictionary() for language in Locale.preferredLanguages { let local = Locale(identifier: language) @@ -125,14 +130,22 @@ extension MastodonPickServerViewModel { // append to dict languageToServersMapping[languageCode] = indexedServers .filter { $0.language.lowercased() == languageCode.lowercased() } - .sorted(by: { $0.totalUsers > $1.totalUsers }) + .sorted(by: { lh, rh in + let lhValue = abs(log2(800.0) - log2(Double(lh.lastWeekUsers))) + let rhValue = abs(log2(800.0) - log2(Double(rh.lastWeekUsers))) + return lhValue < rhValue + }) } - // sort remains servers by `totalUsers` + // sort remains servers let remainsServers = indexedServers .filter { server in return !languageToServersMapping.contains { _, servers in servers.contains(server) } } - .sorted(by: { $0.totalUsers > $1.totalUsers }) + .sorted(by: { lh, rh in + let lhValue = abs(log2(800.0) - log2(Double(lh.lastWeekUsers))) + let rhValue = abs(log2(800.0) - log2(Double(rh.lastWeekUsers))) + return lhValue < rhValue + }) var _indexedServers: [Mastodon.Entity.Server] = [] for key in languageToServersMapping.keys { diff --git a/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift b/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift index b2269b9c4..894cbdbdf 100644 --- a/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift +++ b/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift @@ -185,12 +185,12 @@ extension PickServerServerSectionTableHeaderView { override func accessibilityElementCount() -> Int { guard let diffableDataSource = diffableDataSource else { return 0 } - return diffableDataSource.snapshot().itemIdentifiers.count + return diffableDataSource.snapshot().itemIdentifiers.count + 1 } override func accessibilityElement(at index: Int) -> Any? { - guard let item = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) else { return nil } - return item + if let item = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) { return item } + return searchTextField } } diff --git a/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterTextFieldTableViewCell.swift b/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterTextFieldTableViewCell.swift index 3daa2eb18..15f234834 100644 --- a/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterTextFieldTableViewCell.swift +++ b/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterTextFieldTableViewCell.swift @@ -115,6 +115,7 @@ extension MastodonRegisterTextFieldTableViewCell { label.font = MastodonRegisterTextFieldTableViewCell.textFieldLabelFont label.textColor = Asset.Colors.Label.primary.color label.text = text + label.lineBreakMode = .byTruncatingMiddle label.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(label) @@ -123,6 +124,7 @@ extension MastodonRegisterTextFieldTableViewCell { label.leadingAnchor.constraint(equalTo: paddingView.trailingAnchor), containerView.trailingAnchor.constraint(equalTo: label.trailingAnchor, constant: 16), label.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), + label.widthAnchor.constraint(lessThanOrEqualToConstant: 180).priority(.required - 1), ]) return containerView }() diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterView.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterView.swift new file mode 100644 index 000000000..2be7c61d7 --- /dev/null +++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterView.swift @@ -0,0 +1,301 @@ +// +// MastodonRegisterView.swift +// Mastodon +// +// Created by MainasuK on 2022-4-27. +// + +import UIKit +import SwiftUI +import MastodonLocalization +import MastodonSDK +import MastodonAsset + +struct MastodonRegisterView: View { + + @ObservedObject var viewModel: MastodonRegisterViewModel + + @State var usernameRightViewWidth: CGFloat = 300 + + var body: some View { + ScrollView(.vertical) { + let margin: CGFloat = 16 + + // header + HStack { + Text(L10n.Scene.Register.title(viewModel.domain)) + .font(Font(MastodonPickServerViewController.largeTitleFont as CTFont)) + .foregroundColor(Color(Asset.Colors.Label.primary.color)) + Spacer() + } + .padding(.horizontal, margin) + + // Avatar selector + Menu { + // Photo Library + Button { + viewModel.avatarMediaMenuActionPublisher.send(.photoLibrary) + } label: { + Label(L10n.Scene.Compose.MediaSelection.photoLibrary, systemImage: "photo") + } + // Camera + if UIImagePickerController.isSourceTypeAvailable(.camera) { + Button { + viewModel.avatarMediaMenuActionPublisher.send(.camera) + } label: { + Label(L10n.Scene.Compose.MediaSelection.camera, systemImage: "camera") + } + } + // Browse + Button { + viewModel.avatarMediaMenuActionPublisher.send(.browse) + } label: { + Label(L10n.Scene.Compose.MediaSelection.browse, systemImage: "folder") + } + // Delete + if viewModel.avatarImage != nil { + Divider() + if #available(iOS 15.0, *) { + Button(role: .destructive) { + viewModel.avatarMediaMenuActionPublisher.send(.delete) + } label: { + Label(L10n.Scene.Register.Input.Avatar.delete, systemImage: "delete.left") + } + } else { + // Fallback on earlier ve rsions + Button { + viewModel.avatarMediaMenuActionPublisher.send(.delete) + } label: { + Label(L10n.Scene.Register.Input.Avatar.delete, systemImage: "delete.left") + } + } + } + } label: { + let avatarImage = viewModel.avatarImage ?? Asset.Scene.Onboarding.avatarPlaceholder.image + Image(uiImage: avatarImage) + .resizable() + .frame(width: 88, height: 88, alignment: .center) + .overlay(ZStack { + Color.black.opacity(0.5) + .frame(height: 22, alignment: .bottom) + Text(L10n.Common.Controls.Actions.edit) + .font(.system(size: 13, weight: .semibold)) + .foregroundColor(.white) + }, alignment: .bottom) + .cornerRadius(22) + } + .padding(EdgeInsets(top: 20, leading: 0, bottom: 20, trailing: 0)) + + // Display Name & Uesrname + VStack(alignment: .leading, spacing: 11) { + TextField(L10n.Scene.Register.Input.DisplayName.placeholder.localizedCapitalized, text: $viewModel.name) + .textContentType(.name) + .disableAutocorrection(true) + .modifier(FormTextFieldModifier(validateState: viewModel.displayNameValidateState)) + HStack { + TextField(L10n.Scene.Register.Input.Username.placeholder.localizedCapitalized, text: $viewModel.username) + .textContentType(.username) + .autocapitalization(.none) + .disableAutocorrection(true) + .keyboardType(.asciiCapable) + Text("@\(viewModel.domain)") + .lineLimit(1) + .truncationMode(.middle) + .measureWidth { usernameRightViewWidth = $0 } + .frame(width: min(300.0, usernameRightViewWidth), alignment: .trailing) + } + .modifier(FormTextFieldModifier(validateState: viewModel.usernameValidateState)) + .environment(\.layoutDirection, .leftToRight) // force LTR + if let errorPrompt = viewModel.usernameErrorPrompt { + Text(errorPrompt) + .modifier(FormFootnoteModifier()) + } + } + .padding(.horizontal, margin) + .padding(.bottom, 22) + + // Email & Password & Password hint + VStack(alignment: .leading, spacing: 11) { + TextField(L10n.Scene.Register.Input.Email.placeholder.localizedCapitalized, text: $viewModel.email) + .textContentType(.emailAddress) + .autocapitalization(.none) + .disableAutocorrection(true) + .keyboardType(.emailAddress) + .modifier(FormTextFieldModifier(validateState: viewModel.emailValidateState)) + if let errorPrompt = viewModel.emailErrorPrompt { + Text(errorPrompt) + .modifier(FormFootnoteModifier()) + } + SecureField(L10n.Scene.Register.Input.Password.placeholder.localizedCapitalized, text: $viewModel.password) + .textContentType(.newPassword) + .modifier(FormTextFieldModifier(validateState: viewModel.passwordValidateState)) + Text(L10n.Scene.Register.Input.Password.hint) + .modifier(FormFootnoteModifier(foregroundColor: .secondary)) + if let errorPrompt = viewModel.passwordErrorPrompt { + Text(errorPrompt) + .modifier(FormFootnoteModifier()) + } + } + .padding(.horizontal, margin) + .padding(.bottom, 22) + + // Reason + if viewModel.approvalRequired { + VStack(alignment: .leading, spacing: 11) { + TextField(L10n.Scene.Register.Input.Invite.registrationUserInviteRequest.localizedCapitalized, text: $viewModel.reason) + .modifier(FormTextFieldModifier(validateState: viewModel.reasonValidateState)) + if let errorPrompt = viewModel.reasonErrorPrompt { + Text(errorPrompt) + .modifier(FormFootnoteModifier()) + } + } + .padding(.horizontal, margin) + } + + Spacer() + .frame(minHeight: viewModel.bottomPaddingHeight) + } + .background( + Color(viewModel.backgroundColor) + .onTapGesture { + viewModel.endEditing.send() + } + ) + } + + struct FormTextFieldModifier: ViewModifier { + var validateState: MastodonRegisterViewModel.ValidateState + + func body(content: Content) -> some View { + ZStack { + let shadowColor: Color = { + switch validateState { + case .empty: return .black.opacity(0.125) + case .invalid: return Color(Asset.Colors.TextField.invalid.color) + case .valid: return Color(Asset.Colors.TextField.valid.color) + } + }() + Color(Asset.Scene.Onboarding.textFieldBackground.color) + .cornerRadius(10) + .shadow(color: shadowColor, radius: 1, x: 0, y: 2) + .animation(.easeInOut, value: validateState) + content + .padding() + .background(Color(Asset.Scene.Onboarding.textFieldBackground.color)) + .cornerRadius(10) + } + } + } + + struct FormFootnoteModifier: ViewModifier { + var foregroundColor = Color(Asset.Colors.TextField.invalid.color) + func body(content: Content) -> some View { + content + .font(.footnote) + .foregroundColor(foregroundColor) + .padding(.horizontal) + } + } + +} + +struct WidthKey: PreferenceKey { + static let defaultValue: CGFloat = 0 + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { + value = nextValue() + } +} + +extension View { + func measureWidth(_ f: @escaping (CGFloat) -> ()) -> some View { + overlay(GeometryReader { proxy in + Color.clear.preference(key: WidthKey.self, value: proxy.size.width) + } + .onPreferenceChange(WidthKey.self, perform: f)) + } +} + +#if DEBUG +struct MastodonRegisterView_Previews: PreviewProvider { + static var viewMdoel: MastodonRegisterViewModel { + let domain = "mstdn.jp" + return MastodonRegisterViewModel( + context: .shared, + domain: domain, + authenticateInfo: AuthenticationViewModel.AuthenticateInfo( + domain: domain, + application: Mastodon.Entity.Application( + name: "Preview", + website: nil, + vapidKey: nil, + redirectURI: nil, + clientID: "", + clientSecret: "" + ), + redirectURI: "" + )!, + instance: Mastodon.Entity.Instance(domain: "mstdn.jp"), + applicationToken: Mastodon.Entity.Token( + accessToken: "", + tokenType: "", + scope: "", + createdAt: Date() + ) + ) + } + + static var viewMdoel2: MastodonRegisterViewModel { + let domain = "mstdn.jp" + return MastodonRegisterViewModel( + context: .shared, + domain: domain, + authenticateInfo: AuthenticationViewModel.AuthenticateInfo( + domain: domain, + application: Mastodon.Entity.Application( + name: "Preview", + website: nil, + vapidKey: nil, + redirectURI: nil, + clientID: "", + clientSecret: "" + ), + redirectURI: "" + )!, + instance: Mastodon.Entity.Instance(domain: "mstdn.jp", approvalRequired: true), + applicationToken: Mastodon.Entity.Token( + accessToken: "", + tokenType: "", + scope: "", + createdAt: Date() + ) + ) + } + + static var previews: some View { + Group { + NavigationView { + MastodonRegisterView(viewModel: viewMdoel) + .navigationBarTitle(Text("")) + .navigationBarTitleDisplayMode(.inline) + } + NavigationView { + MastodonRegisterView(viewModel: viewMdoel) + .navigationBarTitle(Text("")) + .navigationBarTitleDisplayMode(.inline) + } + .preferredColorScheme(.dark) + NavigationView { + MastodonRegisterView(viewModel: viewMdoel) + .navigationBarTitle(Text("")) + .navigationBarTitleDisplayMode(.inline) + } + .environment(\.sizeCategory, .accessibilityExtraLarge) + NavigationView { + MastodonRegisterView(viewModel: viewMdoel2) + .navigationBarTitle(Text("")) + .navigationBarTitleDisplayMode(.inline) + } + } + } +} +#endif diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift index bd2db3d45..89c98759f 100644 --- a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift +++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift @@ -11,6 +11,8 @@ import MastodonSDK import os.log import PhotosUI import UIKit +import SwiftUI +import MastodonUI import MastodonAsset import MastodonLocalization @@ -27,6 +29,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } var viewModel: MastodonRegisterViewModel! + private(set) lazy var mastodonRegisterView = MastodonRegisterView(viewModel: viewModel) // picker private(set) lazy var imagePicker: PHPickerViewController = { @@ -51,22 +54,6 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O return documentPickerController }() - let tapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer - - let tableView: UITableView = { - let tableView = UITableView() - tableView.rowHeight = UITableView.automaticDimension - tableView.separatorStyle = .none - tableView.backgroundColor = .clear - tableView.keyboardDismissMode = .onDrag - if #available(iOS 15.0, *) { - tableView.sectionHeaderTopPadding = .leastNonzeroMagnitude - } else { - // Fallback on earlier versions - } - return tableView - }() - let navigationActionView: NavigationActionView = { let navigationActionView = NavigationActionView() navigationActionView.backgroundColor = Asset.Scene.Onboarding.background.color @@ -87,17 +74,21 @@ extension MastodonRegisterViewController { navigationItem.leftBarButtonItem = UIBarButtonItem() setupOnboardingAppearance() + viewModel.backgroundColor = view.backgroundColor ?? .clear defer { setupNavigationBarBackgroundView() } - tableView.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(tableView) + let hostingViewController = UIHostingController(rootView: mastodonRegisterView) + hostingViewController.view.preservesSuperviewLayoutMargins = true + addChild(hostingViewController) + hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(hostingViewController.view) 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), + hostingViewController.view.topAnchor.constraint(equalTo: view.topAnchor), + hostingViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + hostingViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), + hostingViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) navigationActionView.translatesAutoresizingMaskIntoConstraints = false @@ -115,7 +106,7 @@ extension MastodonRegisterViewController { .observe(\.bounds, options: [.initial, .new]) { [weak self] navigationActionView, _ in guard let self = self else { return } let inset = navigationActionView.frame.height - self.tableView.contentInset.bottom = inset + self.viewModel.bottomPaddingHeight = inset } .store(in: &observations) @@ -129,19 +120,14 @@ extension MastodonRegisterViewController { self.navigationActionView.nextButton.isEnabled = isAllValid } .store(in: &disposeBag) - - viewModel.setupDiffableDataSource(tableView: tableView) - -// KeyboardResponderService -// .configure( -// scrollView: tableView, -// layoutNeedsUpdate: viewModel.viewDidAppear.eraseToAnyPublisher() -// ) -// .store(in: &disposeBag) - // gesture - view.addGestureRecognizer(tapGestureRecognizer) - tapGestureRecognizer.addTarget(self, action: #selector(tapGestureRecognizerHandler)) + viewModel.endEditing + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self = self else { return } + self.view.endEditing(true) + } + .store(in: &disposeBag) // // return // if viewModel.approvalRequired { @@ -149,80 +135,22 @@ extension MastodonRegisterViewController { // } else { // passwordTextField.returnKeyType = .done // } -// -// viewModel.usernameValidateState -// .receive(on: DispatchQueue.main) -// .sink { [weak self] validateState in -// guard let self = self else { return } -// self.setTextFieldValidAppearance(self.usernameTextField, validateState: validateState) -// } -// .store(in: &disposeBag) -// viewModel.usernameErrorPrompt -// .receive(on: DispatchQueue.main) -// .sink { [weak self] prompt in -// guard let self = self else { return } -// self.usernameErrorPromptLabel.attributedText = prompt -// } -// .store(in: &disposeBag) -// viewModel.displayNameValidateState -// .receive(on: DispatchQueue.main) -// .sink { [weak self] validateState in -// guard let self = self else { return } -// self.setTextFieldValidAppearance(self.displayNameTextField, validateState: validateState) -// } -// .store(in: &disposeBag) -// viewModel.emailValidateState -// .receive(on: DispatchQueue.main) -// .sink { [weak self] validateState in -// guard let self = self else { return } -// self.setTextFieldValidAppearance(self.emailTextField, validateState: validateState) -// } -// .store(in: &disposeBag) -// viewModel.emailErrorPrompt -// .receive(on: DispatchQueue.main) -// .sink { [weak self] prompt in -// guard let self = self else { return } -// self.emailErrorPromptLabel.attributedText = prompt -// } -// .store(in: &disposeBag) -// viewModel.passwordValidateState -// .receive(on: DispatchQueue.main) -// .sink { [weak self] validateState in -// guard let self = self else { return } -// self.setTextFieldValidAppearance(self.passwordTextField, validateState: validateState) -// self.passwordCheckLabel.attributedText = MastodonRegisterViewModel.attributeStringForPassword(validateState: validateState) -// } -// .store(in: &disposeBag) -// viewModel.passwordErrorPrompt -// .receive(on: DispatchQueue.main) -// .sink { [weak self] prompt in -// guard let self = self else { return } -// self.passwordErrorPromptLabel.attributedText = prompt -// } -// .store(in: &disposeBag) -// viewModel.reasonErrorPrompt -// .receive(on: DispatchQueue.main) -// .sink { [weak self] prompt in -// guard let self = self else { return } -// self.reasonErrorPromptLabel.attributedText = prompt -// } -// .store(in: &disposeBag) -// viewModel.error -// .receive(on: DispatchQueue.main) -// .sink { [weak self] error in -// guard let self = self else { return } -// guard let error = error as? Mastodon.API.Error else { return } -// let alertController = UIAlertController(for: error, title: "Sign Up Failure", preferredStyle: .alert) -// let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil) -// alertController.addAction(okAction) -// self.coordinator.present( -// scene: .alertController(alertController: alertController), -// from: nil, -// transition: .alertController(animated: true, completion: nil) -// ) -// } -// .store(in: &disposeBag) -// + + viewModel.$error + .receive(on: DispatchQueue.main) + .sink { [weak self] error in + guard let self = self else { return } + guard let error = error as? Mastodon.API.Error else { return } + let alertController = UIAlertController(for: error, title: "Sign Up Failure", preferredStyle: .alert) + let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil) + alertController.addAction(okAction) + self.coordinator.present( + scene: .alertController(alertController: alertController), + from: nil, + transition: .alertController(animated: true, completion: nil) + ) + } + .store(in: &disposeBag) viewModel.avatarMediaMenuActionPublisher .receive(on: DispatchQueue.main) @@ -260,10 +188,6 @@ extension MastodonRegisterViewController { extension MastodonRegisterViewController { - @objc private func tapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) { - view.endEditing(true) - } - @objc private func backButtonPressed(_ sender: UIButton) { logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") navigationController?.popViewController(animated: true) @@ -403,65 +327,3 @@ extension MastodonRegisterViewController { } } - -extension MastodonRegisterViewController: UITextFieldDelegate { -// func textFieldDidBeginEditing(_ textField: UITextField) { -// let text = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" -// -// switch textField { -// case usernameTextField: -// viewModel.username.value = text -// case displayNameTextField: -// viewModel.displayName.value = text -// case emailTextField: -// viewModel.email.value = text -// case passwordTextField: -// viewModel.password.value = text -// case reasonTextField: -// viewModel.reason.value = text -// default: -// break -// } -// } -// -// func textFieldDidEndEditing(_ textField: UITextField) { -// let text = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" -// -// switch textField { -// case usernameTextField: -// viewModel.username.value = text -// case displayNameTextField: -// viewModel.displayName.value = text -// case emailTextField: -// viewModel.email.value = text -// case passwordTextField: -// viewModel.password.value = text -// case reasonTextField: -// viewModel.reason.value = text -// default: -// break -// } -// } -// -// func textFieldShouldReturn(_ textField: UITextField) -> Bool { -// switch textField { -// case usernameTextField: -// displayNameTextField.becomeFirstResponder() -// case displayNameTextField: -// emailTextField.becomeFirstResponder() -// case emailTextField: -// passwordTextField.becomeFirstResponder() -// case passwordTextField: -// if viewModel.approvalRequired { -// reasonTextField.becomeFirstResponder() -// } else { -// passwordTextField.resignFirstResponder() -// } -// case reasonTextField: -// reasonTextField.resignFirstResponder() -// default: -// break -// } -// return true -// } -} diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel+Diffable.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel+Diffable.swift index beb16890b..d8dc8943d 100644 --- a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel+Diffable.swift +++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel+Diffable.swift @@ -143,7 +143,7 @@ extension MastodonRegisterViewModel { snapshot.appendItems([.header(domain: domain)], toSection: .main) snapshot.appendItems([.avatar, .name, .username, .email, .password, .hint], toSection: .main) if approvalRequired { - snapshot.appendItems([.reason], toSection: .main) + snapshot.appendItems([.reason], toSection: .main) } diffableDataSource?.applySnapshot(snapshot, animated: false, completion: nil) } @@ -164,51 +164,6 @@ extension MastodonRegisterViewModel { .store(in: &cell.disposeBag) } - enum AvatarMediaMenuAction { - case photoLibrary - case camera - case browse - case delete - } - - private func createAvatarMediaContextMenu() -> UIMenu { - var children: [UIMenuElement] = [] - - // Photo Library - let photoLibraryAction = UIAction(title: L10n.Scene.Compose.MediaSelection.photoLibrary, image: UIImage(systemName: "rectangle.on.rectangle"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in - guard let self = self else { return } - self.avatarMediaMenuActionPublisher.send(.photoLibrary) - } - children.append(photoLibraryAction) - - // Camera - if UIImagePickerController.isSourceTypeAvailable(.camera) { - let cameraAction = UIAction(title: L10n.Scene.Compose.MediaSelection.camera, image: UIImage(systemName: "camera"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { [weak self] _ in - guard let self = self else { return } - self.avatarMediaMenuActionPublisher.send(.camera) - }) - children.append(cameraAction) - } - - // Browse - let browseAction = UIAction(title: L10n.Scene.Compose.MediaSelection.browse, image: UIImage(systemName: "ellipsis"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in - guard let self = self else { return } - self.avatarMediaMenuActionPublisher.send(.browse) - } - children.append(browseAction) - - // Delete - if avatarImage != nil { - let deleteAction = UIAction(title: L10n.Scene.Register.Input.Avatar.delete, image: UIImage(systemName: "delete.left"), identifier: nil, discoverabilityTitle: nil, attributes: [.destructive], state: .off) { [weak self] _ in - guard let self = self else { return } - self.avatarMediaMenuActionPublisher.send(.delete) - } - children.append(deleteAction) - } - - return UIMenu(title: "", image: nil, identifier: nil, options: .displayInline, children: children) - } - private func configureTextFieldCell( cell: MastodonRegisterTextFieldTableViewCell, validateState: Published.Publisher diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel.swift index 1ef9cf47a..e7fbd307d 100644 --- a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel.swift +++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel.swift @@ -12,7 +12,7 @@ import UIKit import MastodonAsset import MastodonLocalization -final class MastodonRegisterViewModel { +final class MastodonRegisterViewModel: ObservableObject { var disposeBag = Set() // input @@ -23,6 +23,7 @@ final class MastodonRegisterViewModel { let applicationToken: Mastodon.Entity.Token let viewDidAppear = CurrentValueSubject(Void()) + @Published var backgroundColor: UIColor = Asset.Scene.Onboarding.background.color @Published var avatarImage: UIImage? = nil @Published var name = "" @Published var username = "" @@ -30,10 +31,12 @@ final class MastodonRegisterViewModel { @Published var password = "" @Published var reason = "" - let usernameErrorPrompt = CurrentValueSubject(nil) - let emailErrorPrompt = CurrentValueSubject(nil) - let passwordErrorPrompt = CurrentValueSubject(nil) - let reasonErrorPrompt = CurrentValueSubject(nil) + @Published var usernameErrorPrompt: String? = nil + @Published var emailErrorPrompt: String? = nil + @Published var passwordErrorPrompt: String? = nil + @Published var reasonErrorPrompt: String? = nil + + @Published var bottomPaddingHeight: CGFloat = .zero // output var diffableDataSource: UITableViewDiffableDataSource? @@ -51,6 +54,7 @@ final class MastodonRegisterViewModel { @Published var error: Error? = nil let avatarMediaMenuActionPublisher = PassthroughSubject() + let endEditing = PassthroughSubject() init( context: AppContext, @@ -97,45 +101,46 @@ final class MastodonRegisterViewModel { .assign(to: \.usernameValidateState, on: self) .store(in: &disposeBag) - // TODO: check username available -// username -// .filter { !$0.isEmpty } -// .debounce(for: .milliseconds(300), scheduler: DispatchQueue.main) -// .removeDuplicates() -// .compactMap { [weak self] text -> AnyPublisher, Error>, Never>? in -// guard let self = self else { return nil } -// let query = Mastodon.API.Account.AccountLookupQuery(acct: text) -// return context.apiService.accountLookup(domain: domain, query: query, authorization: self.applicationAuthorization) -// .map { -// response -> Result, Error> in -// Result.success(response) -// } -// .catch { error in -// Just(Result.failure(error)) -// } -// .eraseToAnyPublisher() -// } -// .switchToLatest() -// .sink { [weak self] result in -// guard let self = self else { return } -// switch result { -// case .success: -// let text = L10n.Scene.Register.Error.Reason.taken(L10n.Scene.Register.Error.Item.username) -// self.usernameErrorPrompt.value = MastodonRegisterViewModel.errorPromptAttributedString(for: text) -// self.usernameValidateState.value = .invalid -// case .failure: -// break -// } -// } -// .store(in: &disposeBag) -// -// usernameValidateState -// .sink { [weak self] validateState in -// if validateState == .valid { -// self?.usernameErrorPrompt.value = nil -// } -// } -// .store(in: &disposeBag) + // check username available + $username + .filter { !$0.isEmpty } + .debounce(for: .milliseconds(300), scheduler: DispatchQueue.main) + .removeDuplicates() + .compactMap { [weak self] text -> AnyPublisher, Error>, Never>? in + guard let self = self else { return nil } + let query = Mastodon.API.Account.AccountLookupQuery(acct: text) + return context.apiService.accountLookup(domain: domain, query: query, authorization: self.applicationAuthorization) + .map { + response -> Result, Error> in + Result.success(response) + } + .catch { error in + Just(Result.failure(error)) + } + .eraseToAnyPublisher() + } + .switchToLatest() + .receive(on: DispatchQueue.main) + .sink { [weak self] result in + guard let self = self else { return } + switch result { + case .success: + let text = L10n.Scene.Register.Error.Reason.taken(L10n.Scene.Register.Error.Item.username) + self.usernameErrorPrompt = text + self.usernameValidateState = .invalid + case .failure: + break + } + } + .store(in: &disposeBag) + + $usernameValidateState + .sink { [weak self] validateState in + if validateState == .valid { + self?.usernameErrorPrompt = nil + } + } + .store(in: &disposeBag) $email .map { email in @@ -163,27 +168,31 @@ final class MastodonRegisterViewModel { .store(in: &disposeBag) } -// error -// .sink { [weak self] error in -// guard let self = self else { return } -// let error = error as? Mastodon.API.Error -// let mastodonError = error?.mastodonError -// if case let .generic(genericMastodonError) = mastodonError, -// let details = genericMastodonError.details -// { -// self.usernameErrorPrompt.value = details.usernameErrorDescriptions.first.flatMap { MastodonRegisterViewModel.errorPromptAttributedString(for: $0) } -// self.emailErrorPrompt.value = details.emailErrorDescriptions.first.flatMap { MastodonRegisterViewModel.errorPromptAttributedString(for: $0) } -// self.passwordErrorPrompt.value = details.passwordErrorDescriptions.first.flatMap { MastodonRegisterViewModel.errorPromptAttributedString(for: $0) } -// self.reasonErrorPrompt.value = details.reasonErrorDescriptions.first.flatMap { MastodonRegisterViewModel.errorPromptAttributedString(for: $0) } -// } else { -// self.usernameErrorPrompt.value = nil -// self.emailErrorPrompt.value = nil -// self.passwordErrorPrompt.value = nil -// self.reasonErrorPrompt.value = nil -// } -// } -// .store(in: &disposeBag) -// + $error + .sink { [weak self] error in + guard let self = self else { return } + let error = error as? Mastodon.API.Error + let mastodonError = error?.mastodonError + if case let .generic(genericMastodonError) = mastodonError, + let details = genericMastodonError.details + { + self.usernameErrorPrompt = details.usernameErrorDescriptions.first + details.usernameErrorDescriptions.first.flatMap { _ in self.usernameValidateState = .invalid } + self.emailErrorPrompt = details.emailErrorDescriptions.first + details.emailErrorDescriptions.first.flatMap { _ in self.emailValidateState = .invalid } + self.passwordErrorPrompt = details.passwordErrorDescriptions.first + details.passwordErrorDescriptions.first.flatMap { _ in self.passwordValidateState = .invalid } + self.reasonErrorPrompt = details.reasonErrorDescriptions.first + details.reasonErrorDescriptions.first.flatMap { _ in self.reasonValidateState = .invalid } + } else { + self.usernameErrorPrompt = nil + self.emailErrorPrompt = nil + self.passwordErrorPrompt = nil + self.reasonErrorPrompt = nil + } + } + .store(in: &disposeBag) + let publisherOne = Publishers.CombineLatest4( $usernameValidateState, $displayNameValidateState, @@ -213,7 +222,7 @@ final class MastodonRegisterViewModel { } extension MastodonRegisterViewModel { - enum ValidateState { + enum ValidateState: Hashable { case empty case invalid case valid @@ -271,3 +280,52 @@ extension MastodonRegisterViewModel { return attributeString } } + +extension MastodonRegisterViewModel { + + enum AvatarMediaMenuAction { + case photoLibrary + case camera + case browse + case delete + } + + private func createAvatarMediaContextMenu() -> UIMenu { + var children: [UIMenuElement] = [] + + // Photo Library + let photoLibraryAction = UIAction(title: L10n.Scene.Compose.MediaSelection.photoLibrary, image: UIImage(systemName: "rectangle.on.rectangle"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in + guard let self = self else { return } + self.avatarMediaMenuActionPublisher.send(.photoLibrary) + } + children.append(photoLibraryAction) + + // Camera + if UIImagePickerController.isSourceTypeAvailable(.camera) { + let cameraAction = UIAction(title: L10n.Scene.Compose.MediaSelection.camera, image: UIImage(systemName: "camera"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { [weak self] _ in + guard let self = self else { return } + self.avatarMediaMenuActionPublisher.send(.camera) + }) + children.append(cameraAction) + } + + // Browse + let browseAction = UIAction(title: L10n.Scene.Compose.MediaSelection.browse, image: UIImage(systemName: "ellipsis"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in + guard let self = self else { return } + self.avatarMediaMenuActionPublisher.send(.browse) + } + children.append(browseAction) + + // Delete + if avatarImage != nil { + let deleteAction = UIAction(title: L10n.Scene.Register.Input.Avatar.delete, image: UIImage(systemName: "delete.left"), identifier: nil, discoverabilityTitle: nil, attributes: [.destructive], state: .off) { [weak self] _ in + guard let self = self else { return } + self.avatarMediaMenuActionPublisher.send(.delete) + } + children.append(deleteAction) + } + + return UIMenu(title: "", image: nil, identifier: nil, options: .displayInline, children: children) + } + +} diff --git a/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewController+Debug.swift b/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewController+Debug.swift new file mode 100644 index 000000000..f6aaf5fba --- /dev/null +++ b/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewController+Debug.swift @@ -0,0 +1,50 @@ +// +// MastodonServerRulesViewController+Debug.swift +// Mastodon +// +// Created by MainasuK on 2022-4-27. +// + +import UIKit + +#if DEBUG + +extension MastodonRegisterViewController { + + @MainActor + static func create( + context: AppContext, + coordinator: SceneCoordinator, + domain: String + ) async throws -> MastodonRegisterViewController { + let viewController = MastodonRegisterViewController() + viewController.context = context + viewController.coordinator = coordinator + + let instanceResponse = try await context.apiService.instance(domain: domain).singleOutput() + let applicationResponse = try await context.apiService.createApplication(domain: domain).singleOutput() + let accessTokenResponse = try await context.apiService.applicationAccessToken( + domain: domain, + clientID: applicationResponse.value.clientID!, + clientSecret: applicationResponse.value.clientSecret!, + redirectURI: applicationResponse.value.redirectURI! + ).singleOutput() + + viewController.viewModel = MastodonRegisterViewModel( + context: context, + domain: domain, + authenticateInfo: .init( + domain: domain, + application: applicationResponse.value + )!, + instance: instanceResponse.value, + applicationToken: accessTokenResponse.value + ) + + return viewController + } + +} + +#endif + diff --git a/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift b/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift index 7f7b0dd00..8bdce2a6d 100644 --- a/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift +++ b/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift @@ -90,7 +90,10 @@ extension ProfileHeaderViewModel { extension ProfileHeaderViewModel { static func normalize(note: String?) -> String? { - guard let note = note?.trimmingCharacters(in: .whitespacesAndNewlines),!note.isEmpty else { + let _note = note?.replacingOccurrences(of: "
|
", with: "\u{2028}", options: .regularExpression, range: nil) + .replacingOccurrences(of: "

", with: "

\u{2029}", range: nil) + .trimmingCharacters(in: .whitespacesAndNewlines) + guard let note = _note, !note.isEmpty else { return nil } diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index a890505ef..4c3f9820a 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -13,8 +13,9 @@ import MetaTextKit import MastodonAsset import MastodonLocalization import MastodonUI -import Tabman import CoreDataStack +import Tabman +import Pageboy protocol ProfileViewModelEditable { func isEdited() -> Bool @@ -42,19 +43,34 @@ final class ProfileViewController: UIViewController, NeedsDependency, MediaPrevi }() private(set) lazy var settingBarButtonItem: UIBarButtonItem = { - let barButtonItem = UIBarButtonItem(image: UIImage(systemName: "gear"), style: .plain, target: self, action: #selector(ProfileViewController.settingBarButtonItemPressed(_:))) + let barButtonItem = UIBarButtonItem( + image: Asset.ObjectsAndTools.gear.image.withRenderingMode(.alwaysTemplate), + style: .plain, + target: self, + action: #selector(ProfileViewController.settingBarButtonItemPressed(_:)) + ) barButtonItem.tintColor = .white return barButtonItem }() private(set) lazy var shareBarButtonItem: UIBarButtonItem = { - let barButtonItem = UIBarButtonItem(image: UIImage(systemName: "square.and.arrow.up"), style: .plain, target: self, action: #selector(ProfileViewController.shareBarButtonItemPressed(_:))) + let barButtonItem = UIBarButtonItem( + image: Asset.Arrow.squareAndArrowUp.image.withRenderingMode(.alwaysTemplate), + style: .plain, + target: self, + action: #selector(ProfileViewController.shareBarButtonItemPressed(_:)) + ) barButtonItem.tintColor = .white return barButtonItem }() private(set) lazy var favoriteBarButtonItem: UIBarButtonItem = { - let barButtonItem = UIBarButtonItem(image: UIImage(systemName: "star"), style: .plain, target: self, action: #selector(ProfileViewController.favoriteBarButtonItemPressed(_:))) + let barButtonItem = UIBarButtonItem( + image: Asset.ObjectsAndTools.star.image.withRenderingMode(.alwaysTemplate), + style: .plain, + target: self, + action: #selector(ProfileViewController.favoriteBarButtonItemPressed(_:)) + ) barButtonItem.tintColor = .white return barButtonItem }() @@ -402,6 +418,7 @@ extension ProfileViewController { } extension ProfileViewController { + private func updateBarButtonInsets() { let margin: CGFloat = { switch traitCollection.userInterfaceIdiom { @@ -618,7 +635,7 @@ extension ProfileViewController { return nil } let name = user.displayNameWithFallback - let record = ManagedObjectRecord(objectID: user.objectID) + let _ = ManagedObjectRecord(objectID: user.objectID) let menu = MastodonMenu.setupMenu( actions: [ .muteUser(.init(name: name, isMuting: self.viewModel.isMuting.value)), @@ -633,7 +650,7 @@ extension ProfileViewController { .sink { [weak self] completion in guard let self = self else { return } switch completion { - case .failure(let error): + case .failure: self.moreMenuBarButtonItem.menu = nil case .finished: break @@ -937,6 +954,7 @@ extension ProfileViewController: ProfileHeaderViewDelegate { viewModel.isUpdating.value = true Task { do { + // TODO: handle error _ = try await viewModel.updateProfileInfo( headerProfileInfo: profileHeaderViewModel.editProfileInfo, aboutProfileInfo: profileAboutViewModel.editProfileInfo @@ -1138,25 +1156,28 @@ extension ProfileViewController: ScrollViewContainer { } } -//extension ProfileViewController { -// -// override var keyCommands: [UIKeyCommand]? { -// if !viewModel.isEditing.value { -// return segmentedControlNavigateKeyCommands -// } -// -// return nil -// } -// -//} +extension ProfileViewController { + + override var keyCommands: [UIKeyCommand]? { + if !viewModel.isEditing.value { + return pageboyNavigateKeyCommands + } + + return nil + } + +} + +// MARK: - PageboyNavigateable +extension ProfileViewController: PageboyNavigateable { + + var navigateablePageViewController: PageboyViewController { + return profileSegmentedViewController.pagingViewController + } + + @objc func pageboyNavigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) { + pageboyNavigateKeyCommandHandler(sender) + } + +} -// MARK: - SegmentedControlNavigateable -//extension ProfileViewController: SegmentedControlNavigateable { -// var navigateableSegmentedControl: UISegmentedControl { -// profileHeaderViewController.pageSegmentedControl -// } -// -// @objc func segmentedControlNavigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) { -// segmentedControlNavigateKeyCommandHandler(sender) -// } -//} diff --git a/Mastodon/Scene/Profile/ProfileViewModel.swift b/Mastodon/Scene/Profile/ProfileViewModel.swift index 403437daf..ac8c12e98 100644 --- a/Mastodon/Scene/Profile/ProfileViewModel.swift +++ b/Mastodon/Scene/Profile/ProfileViewModel.swift @@ -13,10 +13,11 @@ import MastodonSDK import MastodonMeta import MastodonAsset import MastodonLocalization +import MastodonUI // please override this base class class ProfileViewModel: NSObject { - + let logger = Logger(subsystem: "ProfileViewModel", category: "ViewModel") typealias UserID = String @@ -372,101 +373,6 @@ extension ProfileViewModel { } -extension ProfileViewModel { - - enum RelationshipAction: Int, CaseIterable { - case none // set hide from UI - case follow - case request - case pending - case following - case muting - case blocked - case blocking - case suspended - case edit - case editing - case updating - - var option: RelationshipActionOptionSet { - return RelationshipActionOptionSet(rawValue: 1 << rawValue) - } - } - - // construct option set on the enum for safe iterator - struct RelationshipActionOptionSet: OptionSet { - let rawValue: Int - - static let none = RelationshipAction.none.option - static let follow = RelationshipAction.follow.option - static let request = RelationshipAction.request.option - static let pending = RelationshipAction.pending.option - static let following = RelationshipAction.following.option - static let muting = RelationshipAction.muting.option - static let blocked = RelationshipAction.blocked.option - static let blocking = RelationshipAction.blocking.option - static let suspended = RelationshipAction.suspended.option - static let edit = RelationshipAction.edit.option - static let editing = RelationshipAction.editing.option - static let updating = RelationshipAction.updating.option - - static let editOptions: RelationshipActionOptionSet = [.edit, .editing, .updating] - - func highPriorityAction(except: RelationshipActionOptionSet) -> RelationshipAction? { - let set = subtracting(except) - for action in RelationshipAction.allCases.reversed() where set.contains(action.option) { - return action - } - - return nil - } - - var title: String { - guard let highPriorityAction = self.highPriorityAction(except: []) else { - assertionFailure() - return " " - } - switch highPriorityAction { - case .none: return " " - case .follow: return L10n.Common.Controls.Friendship.follow - case .request: return L10n.Common.Controls.Friendship.request - case .pending: return L10n.Common.Controls.Friendship.pending - case .following: return L10n.Common.Controls.Friendship.following - case .muting: return L10n.Common.Controls.Friendship.muted - case .blocked: return L10n.Common.Controls.Friendship.follow // blocked by user - case .blocking: return L10n.Common.Controls.Friendship.blocked - case .suspended: return L10n.Common.Controls.Friendship.follow - case .edit: return L10n.Common.Controls.Friendship.editInfo - case .editing: return L10n.Common.Controls.Actions.done - case .updating: return " " - } - } - - @available(*, deprecated, message: "") - var backgroundColor: UIColor { - guard let highPriorityAction = self.highPriorityAction(except: []) else { - assertionFailure() - return Asset.Colors.brandBlue.color - } - switch highPriorityAction { - case .none: return Asset.Colors.brandBlue.color - case .follow: return Asset.Colors.brandBlue.color - case .request: return Asset.Colors.brandBlue.color - case .pending: return Asset.Colors.brandBlue.color - case .following: return Asset.Colors.brandBlue.color - case .muting: return Asset.Colors.alertYellow.color - case .blocked: return Asset.Colors.brandBlue.color - case .blocking: return Asset.Colors.danger.color - case .suspended: return Asset.Colors.brandBlue.color - case .edit: return Asset.Colors.brandBlue.color - case .editing: return Asset.Colors.brandBlue.color - case .updating: return Asset.Colors.brandBlue.color - } - } - - } -} - extension ProfileViewModel { func updateProfileInfo( headerProfileInfo: ProfileHeaderViewModel.ProfileInfo, diff --git a/Mastodon/Scene/Profile/Segmented/Paging/ProfilePagingViewModel.swift b/Mastodon/Scene/Profile/Segmented/Paging/ProfilePagingViewModel.swift index e5220ef79..67a0ca93d 100644 --- a/Mastodon/Scene/Profile/Segmented/Paging/ProfilePagingViewModel.swift +++ b/Mastodon/Scene/Profile/Segmented/Paging/ProfilePagingViewModel.swift @@ -44,7 +44,7 @@ final class ProfilePagingViewModel: NSObject { let barItems: [TMBarItemable] = { let items = [ TMBarItem(title: L10n.Scene.Profile.SegmentedControl.posts), - TMBarItem(title: L10n.Scene.Profile.SegmentedControl.postsAndReplies), // TODO: i18n + TMBarItem(title: L10n.Scene.Profile.SegmentedControl.postsAndReplies), TMBarItem(title: L10n.Scene.Profile.SegmentedControl.media), TMBarItem(title: L10n.Scene.Profile.SegmentedControl.about), ] diff --git a/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift b/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift index 12925ca41..d9e52a8c7 100644 --- a/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift +++ b/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift @@ -36,7 +36,7 @@ final class UserTimelineViewController: UIViewController, NeedsDependency, Media let cellFrameCache = NSCache() deinit { - os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) } } diff --git a/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+State.swift b/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+State.swift index 06f657bad..ae870f7b5 100644 --- a/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+State.swift +++ b/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+State.swift @@ -195,6 +195,12 @@ extension UserTimelineViewModel.State { // trigger data source update. otherwise, spinner always display viewModel.isSuspended.value = viewModel.isSuspended.value + + // remove bottom loader + guard let diffableDataSource = viewModel.diffableDataSource else { return } + var snapshot = diffableDataSource.snapshot() + snapshot.deleteItems([.bottomLoader]) + diffableDataSource.apply(snapshot) } } } diff --git a/Mastodon/Scene/Report/Report/ReportViewController.swift b/Mastodon/Scene/Report/Report/ReportViewController.swift new file mode 100644 index 000000000..1fe1a720c --- /dev/null +++ b/Mastodon/Scene/Report/Report/ReportViewController.swift @@ -0,0 +1,206 @@ +// +// ReportViewController.swift +// Mastodon +// +// Created by ihugo on 2021/4/20. +// + +import os.log +import UIKit +import Combine +import CoreDataStack +import MastodonAsset +import MastodonLocalization + +class ReportViewController: UIViewController, NeedsDependency, ReportViewControllerAppearance { + + let logger = Logger(subsystem: "ReportViewController", category: "ViewController") + + var disposeBag = Set() + private var observations = Set() + + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } + weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + + var viewModel: ReportViewModel! + + lazy var cancelBarButtonItem = UIBarButtonItem( + barButtonSystemItem: .cancel, + target: self, + action: #selector(ReportViewController.cancelBarButtonItemDidPressed(_:)) + ) + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + +extension ReportViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + setupAppearance() + defer { setupNavigationBarBackgroundView() } + + navigationItem.rightBarButtonItem = cancelBarButtonItem + + viewModel.reportReasonViewModel.delegate = self + viewModel.reportServerRulesViewModel.delegate = self + viewModel.reportStatusViewModel.delegate = self + viewModel.reportSupplementaryViewModel.delegate = self + + let reportReasonViewController = ReportReasonViewController() + reportReasonViewController.context = context + reportReasonViewController.coordinator = coordinator + reportReasonViewController.viewModel = viewModel.reportReasonViewModel + + addChild(reportReasonViewController) + reportReasonViewController.view.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(reportReasonViewController.view) + reportReasonViewController.didMove(toParent: self) + NSLayoutConstraint.activate([ + reportReasonViewController.view.topAnchor.constraint(equalTo: view.topAnchor), + reportReasonViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + reportReasonViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), + reportReasonViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + } + +} + +extension ReportViewController { + + @objc private func cancelBarButtonItemDidPressed(_ sender: UIBarButtonItem) { + dismiss(animated: true, completion: nil) + } + +} + +// MARK: - UIAdaptivePresentationControllerDelegate +extension ReportViewController: UIAdaptivePresentationControllerDelegate { + func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool { + return viewModel.isReportSuccess + } +} + +// MARK: - ReportReasonViewControllerDelegate +extension ReportViewController: ReportReasonViewControllerDelegate { + func reportReasonViewController(_ viewController: ReportReasonViewController, nextButtonPressed button: UIButton) { + guard let reason = viewController.viewModel.selectReason else { return } + switch reason { + case .dislike: + let reportResultViewModel = ReportResultViewModel( + context: context, + user: viewModel.user, + isReported: false + ) + coordinator.present( + scene: .reportResult(viewModel: reportResultViewModel), + from: self, + transition: .show + ) + case .violateRule: + coordinator.present( + scene: .reportServerRules(viewModel: viewModel.reportServerRulesViewModel), + from: self, + transition: .show + ) + case .spam, .other: + coordinator.present( + scene: .reportStatus(viewModel: viewModel.reportStatusViewModel), + from: self, + transition: .show + ) + } + } +} + +// MARK: - ReportServerRulesViewControllerDelegate +extension ReportViewController: ReportServerRulesViewControllerDelegate { + func reportServerRulesViewController(_ viewController: ReportServerRulesViewController, nextButtonPressed button: UIButton) { + if viewController.viewModel.isDislike { + let reportResultViewModel = ReportResultViewModel( + context: context, + user: viewModel.user, + isReported: false + ) + coordinator.present( + scene: .reportResult(viewModel: reportResultViewModel), + from: self, + transition: .show + ) + } else if viewController.viewModel.selectRule != nil { + coordinator.present( + scene: .reportStatus(viewModel: viewModel.reportStatusViewModel), + from: self, + transition: .show + ) + } else { + assertionFailure() + } + } +} + +// MARK: - ReportStatusViewControllerDelegate +extension ReportViewController: ReportStatusViewControllerDelegate { + func reportStatusViewController(_ viewController: ReportStatusViewController, skipButtonDidPressed button: UIButton) { + coordinateToReportSupplementary() + } + + func reportStatusViewController(_ viewController: ReportStatusViewController, nextButtonDidPressed button: UIButton) { + coordinateToReportSupplementary() + } + + private func coordinateToReportSupplementary() { + coordinator.present( + scene: .reportSupplementary(viewModel: viewModel.reportSupplementaryViewModel), + from: self, + transition: .show + ) + } +} + +// MARK: - ReportSupplementaryViewControllerDelegate +extension ReportViewController: ReportSupplementaryViewControllerDelegate { + func reportSupplementaryViewController(_ viewController: ReportSupplementaryViewController, skipButtonDidPressed button: UIButton) { + report() + } + + func reportSupplementaryViewController(_ viewController: ReportSupplementaryViewController, nextButtonDidPressed button: UIButton) { + report() + } + + private func report() { + Task { @MainActor in + do { + let _ = try await viewModel.report() + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): report success") + + let reportResultViewModel = ReportResultViewModel( + context: context, + user: viewModel.user, + isReported: true + ) + + coordinator.present( + scene: .reportResult(viewModel: reportResultViewModel), + from: self, + transition: .show + ) + + } catch { + let alertController = UIAlertController(for: error, title: nil, preferredStyle: .alert) + let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil) + alertController.addAction(okAction) + self.coordinator.present( + scene: .alertController(alertController: alertController), + from: nil, + transition: .alertController(animated: true, completion: nil) + ) + } + } // end Task + } + +} diff --git a/Mastodon/Scene/Report/Report/ReportViewModel.swift b/Mastodon/Scene/Report/Report/ReportViewModel.swift new file mode 100644 index 000000000..f94a92d39 --- /dev/null +++ b/Mastodon/Scene/Report/Report/ReportViewModel.swift @@ -0,0 +1,190 @@ +// +// ReportViewModel.swift +// Mastodon +// +// Created by ihugo on 2021/4/19. +// + +import Combine +import CoreData +import CoreDataStack +import Foundation +import GameplayKit +import MastodonSDK +import OrderedCollections +import os.log +import UIKit +import MastodonLocalization + +class ReportViewModel { + + var disposeBag = Set() + + let reportReasonViewModel: ReportReasonViewModel + let reportServerRulesViewModel: ReportServerRulesViewModel + let reportStatusViewModel: ReportStatusViewModel + let reportSupplementaryViewModel: ReportSupplementaryViewModel + + // input + let context: AppContext + let user: ManagedObjectRecord + let status: ManagedObjectRecord? + + // output + @Published var isReporting = false + @Published var isReportSuccess = false + + init( + context: AppContext, + user: ManagedObjectRecord, + status: ManagedObjectRecord? + ) { + self.context = context + self.user = user + self.status = status + self.reportReasonViewModel = ReportReasonViewModel(context: context) + self.reportServerRulesViewModel = ReportServerRulesViewModel(context: context) + self.reportStatusViewModel = ReportStatusViewModel(context: context, user: user, status: status) + self.reportSupplementaryViewModel = ReportSupplementaryViewModel(context: context, user: user) + // end init + + guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { + return + } + + // setup reason viewModel + if status != nil { + reportReasonViewModel.headline = L10n.Scene.Report.StepOne.whatsWrongWithThisPost + } else { + Task { @MainActor in + let managedObjectContext = context.managedObjectContext + let _username: String? = try? await managedObjectContext.perform { + let user = user.object(in: managedObjectContext) + return user?.acctWithDomain + } + if let username = _username { + reportReasonViewModel.headline = L10n.Scene.Report.StepOne.whatsWrongWithThisUsername(username) + } else { + reportReasonViewModel.headline = L10n.Scene.Report.StepOne.whatsWrongWithThisAccount + } + } // end Task + } + + // bind server rules + Task { @MainActor in + do { + let response = try await context.apiService.instance(domain: authenticationBox.domain) + .timeout(3, scheduler: DispatchQueue.main) + .singleOutput() + let rules = response.value.rules ?? [] + reportReasonViewModel.serverRules = rules + reportServerRulesViewModel.serverRules = rules + } catch { + reportReasonViewModel.serverRules = [] + reportServerRulesViewModel.serverRules = [] + } + } // end Task + + $isReporting + .assign(to: &reportSupplementaryViewModel.$isBusy) + } + +} + +extension ReportViewModel { + @MainActor + func report() async throws { + guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value, + !isReporting + else { + assertionFailure() + return + } + + let managedObjectContext = context.managedObjectContext + let _query: Mastodon.API.Reports.FileReportQuery? = try await managedObjectContext.perform { + guard let user = self.user.object(in: managedObjectContext) else { return nil } + + // the status picker is essential step in report flow + // only check isSkip or not + let statusIDs: [Status.ID]? = { + if self.reportStatusViewModel.isSkip { + let _id: Status.ID? = self.reportStatusViewModel.status.flatMap { record -> Status.ID? in + guard let status = record.object(in: managedObjectContext) else { return nil } + return status.id + } + return _id.flatMap { [$0] } + } else { + return self.reportStatusViewModel.selectStatuses.compactMap { record -> Status.ID? in + guard let status = record.object(in: managedObjectContext) else { return nil } + return status.id + } + } + }() + + // the user comment is essential step in report flow + // only check isSkip or not + let comment: String? = { + var suffixes: [String] = [] + let content: String? + + // the server rules is NOT essential step in report flow + // append suffix depends which reason + if let reason = self.reportReasonViewModel.selectReason { + switch reason { + case .spam: + suffixes.append(reason.rawValue) + case .violateRule: + suffixes.append(reason.rawValue) + if let rule = self.reportServerRulesViewModel.selectRule { + suffixes.append(rule.text) + } else { + assertionFailure("should select valid rule") + } + case .dislike: + assertionFailure("should not enter the report flow") + case .other: + break + } + } + + content = self.reportSupplementaryViewModel.isSkip ? nil : self.reportSupplementaryViewModel.commentContext.comment + + let suffix: String? = { + let text = suffixes.joined(separator: ". ") + guard !text.isEmpty else { return nil } + return "<" + text + ">" + }() + + let comment = [content, suffix] + .compactMap { $0 } + .joined(separator: " ") + return comment.isEmpty ? nil : comment + }() + return Mastodon.API.Reports.FileReportQuery( + accountID: user.id, + statusIDs: statusIDs, + comment: comment, + forward: true + ) + } + + guard let query = _query else { return } + + do { + isReporting = true + #if DEBUG + try await Task.sleep(nanoseconds: .second * 3) + #else + let _ = try await context.apiService.report( + query: query, + authenticationBox: authenticationBox + ) + #endif + isReportSuccess = true + } catch { + isReporting = false + throw error + } + } +} diff --git a/Mastodon/Scene/Report/ReportReason/ReportReasonView.swift b/Mastodon/Scene/Report/ReportReason/ReportReasonView.swift new file mode 100644 index 000000000..67e948727 --- /dev/null +++ b/Mastodon/Scene/Report/ReportReason/ReportReasonView.swift @@ -0,0 +1,112 @@ +// +// ReportReasonView.swift +// Mastodon +// +// Created by MainasuK on 2022-5-10. +// + +import UIKit +import SwiftUI +import MastodonLocalization +import MastodonSDK +import MastodonAsset + +struct ReportReasonView: View { + + @ObservedObject var viewModel: ReportReasonViewModel + + var body: some View { + ScrollView(.vertical) { + HStack { + VStack(alignment: .leading, spacing: 8) { + Text(L10n.Scene.Report.StepOne.step1Of4) + .foregroundColor(Color(Asset.Colors.Label.secondary.color)) + .font(Font(UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)) as CTFont)) + Text(viewModel.headline) + .foregroundColor(Color(Asset.Colors.Label.primary.color)) + .font(Font(UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: .systemFont(ofSize: 28, weight: .bold)) as CTFont)) + Text(L10n.Scene.Report.StepOne.selectTheBestMatch) + .foregroundColor(Color(Asset.Colors.Label.secondary.color)) + .font(Font(UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)) as CTFont)) + } + Spacer() + } + .padding() + + VStack(spacing: 16) { + if let serverRules = viewModel.serverRules { + ForEach(ReportReasonViewModel.Reason.allCases, id: \.self) { reason in + switch reason { + case .violateRule where serverRules.isEmpty: + EmptyView() + default: + ReportReasonRowView(reason: reason, isSelect: reason == viewModel.selectReason) + .background( + Color(viewModel.backgroundColor) + ) + .onTapGesture { + viewModel.selectReason = reason + } + } + } + } else { + ProgressView() + } + } + .padding() + .transition(.opacity) + .animation(.easeInOut) + + Spacer() + .frame(minHeight: viewModel.bottomPaddingHeight) + } + .background( + Color(viewModel.backgroundColor) + ) + } + +} + +struct ReportReasonRowView: View { + + var reason: ReportReasonViewModel.Reason + var isSelect: Bool + + var body: some View { + HStack(spacing: 14) { + Image(systemName: isSelect ? "checkmark.circle.fill" : "circle") + .resizable() + .frame(width: 28, height: 28, alignment: .center) + VStack(alignment: .leading, spacing: 4) { + Text(reason.title) + .foregroundColor(Color(Asset.Colors.Label.primary.color)) + .font(.headline) + Text(reason.subtitle) + .font(.subheadline) + .foregroundColor(Color(Asset.Colors.Label.secondary.color)) + } + Spacer() + } + } + +} + +#if DEBUG +struct ReportReasonView_Previews: PreviewProvider { + static var previews: some View { + Group { + NavigationView { + ReportReasonView(viewModel: ReportReasonViewModel(context: .shared)) + .navigationBarTitle(Text("")) + .navigationBarTitleDisplayMode(.inline) + } + NavigationView { + ReportReasonView(viewModel: ReportReasonViewModel(context: .shared)) + .navigationBarTitle(Text("")) + .navigationBarTitleDisplayMode(.inline) + } + .preferredColorScheme(.dark) + } + } +} +#endif diff --git a/Mastodon/Scene/Report/ReportReason/ReportReasonViewController.swift b/Mastodon/Scene/Report/ReportReason/ReportReasonViewController.swift new file mode 100644 index 000000000..ac5d9e797 --- /dev/null +++ b/Mastodon/Scene/Report/ReportReason/ReportReasonViewController.swift @@ -0,0 +1,104 @@ +// +// ReportReasonViewController.swift +// Mastodon +// +// Created by MainasuK on 2022-5-10. +// + +import os.log +import UIKit +import SwiftUI +import Combine +import MastodonUI +import MastodonAsset +import MastodonLocalization + +protocol ReportReasonViewControllerDelegate: AnyObject { + func reportReasonViewController(_ viewController: ReportReasonViewController, nextButtonPressed button: UIButton) +} + +final class ReportReasonViewController: UIViewController, NeedsDependency, ReportViewControllerAppearance { + + let logger = Logger(subsystem: "ReportReasonViewController", category: "ViewController") + + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } + weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + + var disposeBag = Set() + private var observations = Set() + + var viewModel: ReportReasonViewModel! + private(set) lazy var reportReasonView = ReportReasonView(viewModel: viewModel) + + let navigationActionView: NavigationActionView = { + let navigationActionView = NavigationActionView() + navigationActionView.backgroundColor = Asset.Scene.Onboarding.background.color + navigationActionView.hidesBackButton = true + return navigationActionView + }() + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + +extension ReportReasonViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + setupAppearance() + defer { setupNavigationBarBackgroundView() } + + let hostingViewController = UIHostingController(rootView: reportReasonView) + hostingViewController.view.preservesSuperviewLayoutMargins = true + addChild(hostingViewController) + hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(hostingViewController.view) + NSLayoutConstraint.activate([ + hostingViewController.view.topAnchor.constraint(equalTo: view.topAnchor), + hostingViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + hostingViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), + hostingViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + + navigationActionView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(navigationActionView) + defer { + view.bringSubviewToFront(navigationActionView) + } + NSLayoutConstraint.activate([ + navigationActionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + navigationActionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + view.bottomAnchor.constraint(equalTo: navigationActionView.bottomAnchor), + ]) + + navigationActionView + .observe(\.bounds, options: [.initial, .new]) { [weak self] navigationActionView, _ in + guard let self = self else { return } + let inset = navigationActionView.frame.height + self.viewModel.bottomPaddingHeight = inset + } + .store(in: &observations) + + viewModel.$selectReason + .map { $0 != nil } + .assign(to: \.isEnabled, on: navigationActionView.nextButton) + .store(in: &disposeBag) + + navigationActionView.nextButton.addTarget(self, action: #selector(ReportReasonViewController.nextButtonPressed(_:)), for: .touchUpInside) + } + +} + +extension ReportReasonViewController { + + @objc private func nextButtonPressed(_ sender: UIButton) { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + + assert(viewModel.delegate != nil) + viewModel.delegate?.reportReasonViewController(self, nextButtonPressed: sender) + } + +} diff --git a/Mastodon/Scene/Report/ReportReason/ReportReasonViewModel.swift b/Mastodon/Scene/Report/ReportReason/ReportReasonViewModel.swift new file mode 100644 index 000000000..91715cba8 --- /dev/null +++ b/Mastodon/Scene/Report/ReportReason/ReportReasonViewModel.swift @@ -0,0 +1,84 @@ +// +// ReportReasonViewModel.swift +// Mastodon +// +// Created by MainasuK on 2022-5-10. +// + +import UIKit +import SwiftUI +import MastodonAsset +import MastodonSDK +import MastodonLocalization + +final class ReportReasonViewModel: ObservableObject { + + weak var delegate: ReportReasonViewControllerDelegate? + + // input + let context: AppContext + + @Published var headline = L10n.Scene.Report.StepOne.whatsWrongWithThisAccount + @Published var serverRules: [Mastodon.Entity.Instance.Rule]? + + @Published var bottomPaddingHeight: CGFloat = .zero + @Published var backgroundColor: UIColor = Asset.Scene.Report.background.color + + // output + @Published var selectReason: Reason? + + init(context: AppContext) { + self.context = context + // end init + } + +} + +extension ReportReasonViewModel { + enum Reason: Hashable, CaseIterable { + case dislike + case spam + case violateRule + case other + + var title: String { + switch self { + case .dislike: + return L10n.Scene.Report.StepOne.iDontLikeIt + case .spam: + return L10n.Scene.Report.StepOne.itsSpam + case .violateRule: + return L10n.Scene.Report.StepOne.itViolatesServerRules + case .other: + return L10n.Scene.Report.StepOne.itsSomethingElse + } + } + + var subtitle: String { + switch self { + case .dislike: + return L10n.Scene.Report.StepOne.itIsNotSomethingYouWantToSee + case .spam: + return L10n.Scene.Report.StepOne.maliciousLinksFakeEngagementOrRepetetiveReplies + case .violateRule: + return L10n.Scene.Report.StepOne.youAreAwareThatItBreaksSpecificRules + case .other: + return L10n.Scene.Report.StepOne.theIssueDoesNotFitIntoOtherCategories + } + } + + // do not i18n this + var rawValue: String { + switch self { + case .dislike: + return "I don’t like it" + case .spam: + return "It’s spam" + case .violateRule: + return "It violates server rules" + case .other: + return "It’s something else" + } + } + } +} diff --git a/Mastodon/Scene/Report/ReportResult/ReportResultView.swift b/Mastodon/Scene/Report/ReportResult/ReportResultView.swift new file mode 100644 index 000000000..7931538fc --- /dev/null +++ b/Mastodon/Scene/Report/ReportResult/ReportResultView.swift @@ -0,0 +1,217 @@ +// +// ReportResultView.swift +// Mastodon +// +// Created by MainasuK on 2022-5-11. +// + +import UIKit +import SwiftUI +import MastodonSDK +import MastodonUI +import MastodonAsset +import MastodonLocalization +import CoreDataStack + +struct ReportResultView: View { + + @ObservedObject var viewModel: ReportResultViewModel + + var avatarView: some View { + HStack { + Spacer() + ZStack { + AnimatedImage(imageURL: viewModel.avatarURL) + .frame(width: 106, height: 106, alignment: .center) + .background(Color(UIColor.systemFill)) + .cornerRadius(27) + Text(L10n.Scene.Report.reported) + .font(Font(FontFamily.Staatliches.regular.font(size: 49) as CTFont)) + .foregroundColor(Color(Asset.Scene.Report.reportBanner.color)) + .padding(EdgeInsets(top: 0, leading: 10, bottom: -2, trailing: 10)) + .background(Color(viewModel.backgroundColor)) + .cornerRadius(7) + .padding(7) + .background(Color(Asset.Scene.Report.reportBanner.color)) + .cornerRadius(12) + .rotationEffect(.degrees(-8)) + .offset(x: 0, y: -5) + } + Spacer() + } + .padding() + } + + var body: some View { + ScrollView(.vertical) { + HStack { + VStack(alignment: .leading, spacing: 8) { + Text(viewModel.headline) + .foregroundColor(Color(Asset.Colors.Label.primary.color)) + .font(Font(UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: .systemFont(ofSize: 28, weight: .bold)) as CTFont)) + if viewModel.isReported { + avatarView + Text(verbatim: "While we review this, you can take action against @\(viewModel.username)") + .foregroundColor(Color(Asset.Colors.Label.secondary.color)) + .font(Font(UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)) as CTFont)) + } else { + Text(verbatim: L10n.Scene.Report.StepFinal.whenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience) + .foregroundColor(Color(Asset.Colors.Label.secondary.color)) + .font(Font(UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)) as CTFont)) + } + } + Spacer() + } + .padding() + + VStack(spacing: 32) { + // Follow + VStack(alignment: .leading, spacing: 4) { + Text(L10n.Scene.Report.StepFinal.unfollowUser("@\(viewModel.username)")) + .font(.headline) + .foregroundColor(Color(Asset.Colors.Label.primary.color)) + ReportActionButton( + action: { + viewModel.followActionPublisher.send() + }, + title: viewModel.relationshipViewModel.isFollowing ? "Unfollow" : "Unfollowed", + isBusy: viewModel.isRequestFollow + ) + } + + // Mute + VStack(alignment: .leading, spacing: 4) { + Text(L10n.Scene.Report.StepFinal.muteUser("@\(viewModel.username)")) + .font(.headline) + .foregroundColor(Color(Asset.Colors.Label.primary.color)) + Text(verbatim: L10n.Scene.Report.StepFinal.youWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted) + .foregroundColor(Color(Asset.Colors.Label.secondary.color)) + .font(Font(UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 13, weight: .regular)) as CTFont)) + ReportActionButton( + action: { + viewModel.muteActionPublisher.send() + }, + title: viewModel.relationshipViewModel.isMuting ? L10n.Common.Controls.Friendship.muted : L10n.Common.Controls.Friendship.mute, + isBusy: viewModel.isRequestMute + ) + } + + // Block + VStack(alignment: .leading, spacing: 4) { + Text(L10n.Scene.Report.StepFinal.blockUser("@\(viewModel.username)")) + .font(.headline) + .foregroundColor(Color(Asset.Colors.Label.primary.color)) + Text(verbatim: L10n.Scene.Report.StepFinal.theyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked) + .foregroundColor(Color(Asset.Colors.Label.secondary.color)) + .font(Font(UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 13, weight: .regular)) as CTFont)) + ReportActionButton( + action: { + viewModel.blockActionPublisher.send() + }, + title: viewModel.relationshipViewModel.isBlocking ? L10n.Common.Controls.Friendship.blocked : L10n.Common.Controls.Friendship.block, + isBusy: viewModel.isRequestBlock + ) + } + } + .padding() + + Spacer() + .frame(minHeight: viewModel.bottomPaddingHeight) + } + .background( + Color(viewModel.backgroundColor) + ) + } + +} + +struct ReportActionButton: View { + + var action: () -> Void + var title: String + var isBusy: Bool + + var body: some View { + Button { + action() + } label: { + ZStack { + ProgressView() + .opacity(isBusy ? 1 : 0) + Text(title) + .font(.headline) + .foregroundColor(Color(Asset.Colors.Label.primary.color)) + .opacity(isBusy ? 0 : 1) + } + .frame(maxWidth: .infinity) + .padding() + .background(Color(UIColor.systemBackground)) + .cornerRadius(10) + .shadow(color: .black.opacity(0.1), radius: 2, x: 0, y: 1) + } + } + +} + +#if DEBUG +struct ReportResultView_Previews: PreviewProvider { + + static func viewModel(isReported: Bool) -> ReportResultViewModel { + let context = AppContext.shared + let request = MastodonUser.sortedFetchRequest + request.fetchLimit = 1 + + let property = MastodonUser.Property( + identifier: "1", + domain: "domain.com", + id: "1", + acct: "@user@domain.com", + username: "user", + displayName: "User", + avatar: "", + avatarStatic: "", + header: "", + headerStatic: "", + note: "", + url: "", + statusesCount: Int64(100), + followingCount: Int64(100), + followersCount: Int64(100), + locked: false, + bot: false, + suspended: false, + createdAt: Date(), + updatedAt: Date(), + emojis: [], + fields: [] + ) + let user = try! context.managedObjectContext.fetch(request).first ?? MastodonUser.insert(into: context.managedObjectContext, property: property) + + return ReportResultViewModel( + context: context, + user: .init(objectID: user.objectID), + isReported: isReported + ) + } + static var previews: some View { + Group { + NavigationView { + ReportResultView(viewModel: viewModel(isReported: true)) + .navigationBarTitle(Text("")) + .navigationBarTitleDisplayMode(.inline) + } + NavigationView { + ReportResultView(viewModel: viewModel(isReported: false)) + .navigationBarTitle(Text("")) + .navigationBarTitleDisplayMode(.inline) + } + NavigationView { + ReportResultView(viewModel: viewModel(isReported: true)) + .navigationBarTitle(Text("")) + .navigationBarTitleDisplayMode(.inline) + } + .preferredColorScheme(.dark) + } + } +} +#endif diff --git a/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift b/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift index 26f56b98d..957760f38 100644 --- a/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift +++ b/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift @@ -7,6 +7,7 @@ import os.log import UIKit +import SwiftUI import Combine import MastodonAsset import MastodonLocalization @@ -20,22 +21,13 @@ final class ReportResultViewController: UIViewController, NeedsDependency, Repor weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } var viewModel: ReportResultViewModel! - - let tableView: UITableView = { - let tableView = ControlContainableTableView() - tableView.backgroundColor = Asset.Scene.Report.background.color - tableView.rowHeight = UITableView.automaticDimension - tableView.separatorStyle = .none - tableView.backgroundColor = .clear - tableView.keyboardDismissMode = .onDrag - tableView.allowsMultipleSelection = true - if #available(iOS 15.0, *) { - tableView.sectionHeaderTopPadding = .leastNonzeroMagnitude - } else { - // Fallback on earlier versions - } - return tableView - }() + private(set) lazy var reportResultView = ReportResultView(viewModel: viewModel) + + lazy var doneBarButtonItem = UIBarButtonItem( + barButtonSystemItem: .done, + target: self, + action: #selector(ReportResultViewController.doneBarButtonItemDidPressed(_:)) + ) let navigationActionView: NavigationActionView = { let navigationActionView = NavigationActionView() @@ -60,21 +52,20 @@ extension ReportResultViewController { defer { setupNavigationBarBackgroundView() } navigationItem.hidesBackButton = true + navigationItem.rightBarButtonItem = doneBarButtonItem - tableView.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(tableView) + let hostingViewController = UIHostingController(rootView: reportResultView) + hostingViewController.view.preservesSuperviewLayoutMargins = true + addChild(hostingViewController) + hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(hostingViewController.view) 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), + hostingViewController.view.topAnchor.constraint(equalTo: view.topAnchor), + hostingViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + hostingViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), + hostingViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) - tableView.delegate = self - viewModel.setupDiffableDataSource( - tableView: tableView - ) - navigationActionView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(navigationActionView) defer { @@ -90,18 +81,93 @@ extension ReportResultViewController { .observe(\.bounds, options: [.initial, .new]) { [weak self] navigationActionView, _ in guard let self = self else { return } let inset = navigationActionView.frame.height - self.tableView.contentInset.bottom = inset - self.tableView.verticalScrollIndicatorInsets.bottom = inset + self.viewModel.bottomPaddingHeight = inset } .store(in: &observations) navigationActionView.nextButton.addTarget(self, action: #selector(ReportSupplementaryViewController.nextButtonDidPressed(_:)), for: .touchUpInside) + + viewModel.followActionPublisher + .throttle(for: 0.3, scheduler: DispatchQueue.main, latest: false) + .sink { [weak self] in + guard let self = self else { return } + guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { + return + } + Task { @MainActor in + guard !self.viewModel.isRequestFollow else { return } + self.viewModel.isRequestFollow = true + do { + try await DataSourceFacade.responseToUserFollowAction( + dependency: self, + user: self.viewModel.user, + authenticationBox: authenticationBox + ) + } catch { + // handle error + } + self.viewModel.isRequestFollow = false + } // end Task + } + .store(in: &disposeBag) + + viewModel.muteActionPublisher + .throttle(for: 0.3, scheduler: DispatchQueue.main, latest: false) + .sink { [weak self] in + guard let self = self else { return } + guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { + return + } + Task { @MainActor in + guard !self.viewModel.isRequestMute else { return } + self.viewModel.isRequestMute = true + do { + try await DataSourceFacade.responseToUserMuteAction( + dependency: self, + user: self.viewModel.user, + authenticationBox: authenticationBox + ) + } catch { + // handle error + } + self.viewModel.isRequestMute = false + } // end Task + } + .store(in: &disposeBag) + + viewModel.blockActionPublisher + .throttle(for: 0.3, scheduler: DispatchQueue.main, latest: false) + .sink { [weak self] in + guard let self = self else { return } + guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { + return + } + Task { @MainActor in + guard !self.viewModel.isRequestBlock else { return } + self.viewModel.isRequestBlock = true + do { + try await DataSourceFacade.responseToUserBlockAction( + dependency: self, + user: self.viewModel.user, + authenticationBox: authenticationBox + ) + } catch { + // handle error + } + self.viewModel.isRequestBlock = false + } // end Task + } + .store(in: &disposeBag) } } extension ReportResultViewController { + + @objc func doneBarButtonItemDidPressed(_ sender: UIButton) { + dismiss(animated: true, completion: nil) + } @objc func nextButtonDidPressed(_ sender: UIButton) { dismiss(animated: true, completion: nil) @@ -109,5 +175,7 @@ extension ReportResultViewController { } -// MARK: - UITableViewDelegate -extension ReportResultViewController: UITableViewDelegate { } +// MARK: - PanPopableViewController +extension ReportResultViewController: PanPopableViewController { + var isPanPopable: Bool { false } +} diff --git a/Mastodon/Scene/Report/ReportResult/ReportResultViewModel+Diffable.swift b/Mastodon/Scene/Report/ReportResult/ReportResultViewModel+Diffable.swift deleted file mode 100644 index a9c1272df..000000000 --- a/Mastodon/Scene/Report/ReportResult/ReportResultViewModel+Diffable.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// ReportResultViewModel+Diffable.swift -// Mastodon -// -// Created by MainasuK on 2022-2-8. -// - -import UIKit -import Combine -import CoreData -import CoreDataStack -import MastodonAsset -import MastodonLocalization - -extension ReportResultViewModel { - - static let reportItemHeaderContext = ReportItem.HeaderContext( - primaryLabelText: "Thanks for reporting, we’ll look into this.", - secondaryLabelText: "" - ) - - func setupDiffableDataSource( - tableView: UITableView - ) { - diffableDataSource = ReportSection.diffableDataSource( - tableView: tableView, - context: context, - configuration: ReportSection.Configuration() - ) - - var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections([.main]) - snapshot.appendItems([.header(context: ReportResultViewModel.reportItemHeaderContext)], toSection: .main) - snapshot.appendItems([.result(record: user)], toSection: .main) - diffableDataSource?.apply(snapshot) - } -} diff --git a/Mastodon/Scene/Report/ReportResult/ReportResultViewModel.swift b/Mastodon/Scene/Report/ReportResult/ReportResultViewModel.swift index 79fec4936..67d7475dd 100644 --- a/Mastodon/Scene/Report/ReportResult/ReportResultViewModel.swift +++ b/Mastodon/Scene/Report/ReportResult/ReportResultViewModel.swift @@ -12,25 +12,60 @@ import Foundation import MastodonSDK import os.log import UIKit +import MastodonAsset +import MastodonUI +import MastodonLocalization -class ReportResultViewModel { +class ReportResultViewModel: ObservableObject { var disposeBag = Set() // input let context: AppContext let user: ManagedObjectRecord + let isReported: Bool + + var headline: String { + isReported ? L10n.Scene.Report.reportSentTitle : L10n.Scene.Report.StepFinal.dontWantToSeeThis + } + @Published var bottomPaddingHeight: CGFloat = .zero + @Published var backgroundColor: UIColor = Asset.Scene.Report.background.color + + @Published var isRequestFollow = false + @Published var isRequestMute = false + @Published var isRequestBlock = false // output - var diffableDataSource: UITableViewDiffableDataSource? + @Published var avatarURL: URL? + @Published var username: String = "" + + let relationshipViewModel = RelationshipViewModel() + let muteActionPublisher = PassthroughSubject() + let followActionPublisher = PassthroughSubject() + let blockActionPublisher = PassthroughSubject() init( context: AppContext, - user: ManagedObjectRecord + user: ManagedObjectRecord, + isReported: Bool ) { self.context = context self.user = user + self.isReported = isReported // end init + + Task { @MainActor in + guard let user = user.object(in: context.managedObjectContext) else { return } + guard let me = context.authenticationService.activeMastodonAuthenticationBox.value?.authenticationRecord.object(in: context.managedObjectContext)?.user else { return } + self.relationshipViewModel.user = user + self.relationshipViewModel.me = me + + self.avatarURL = user.avatarImageURL() + self.username = user.acctWithDomain + + } // end Task } } + + diff --git a/Mastodon/Scene/Report/ReportServerRules/ReportServerRulesView.swift b/Mastodon/Scene/Report/ReportServerRules/ReportServerRulesView.swift new file mode 100644 index 000000000..57406402b --- /dev/null +++ b/Mastodon/Scene/Report/ReportServerRules/ReportServerRulesView.swift @@ -0,0 +1,115 @@ +// +// ReportServerRulesView.swift +// Mastodon +// +// Created by MainasuK on 2022-5-10. +// + +import UIKit +import SwiftUI +import MastodonLocalization +import MastodonSDK +import MastodonAsset + +struct ReportServerRulesView: View { + + @ObservedObject var viewModel: ReportServerRulesViewModel + + var body: some View { + ScrollView(.vertical) { + HStack { + VStack(alignment: .leading, spacing: 8) { + Text(L10n.Scene.Report.StepTwo.step2Of4) + .foregroundColor(Color(Asset.Colors.Label.secondary.color)) + .font(Font(UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)) as CTFont)) + Text(viewModel.headline) + .foregroundColor(Color(Asset.Colors.Label.primary.color)) + .font(Font(UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: .systemFont(ofSize: 28, weight: .bold)) as CTFont)) + Text(L10n.Scene.Report.StepTwo.selectAllThatApply) + .foregroundColor(Color(Asset.Colors.Label.secondary.color)) + .font(Font(UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)) as CTFont)) + } + Spacer() + } + .padding() + + VStack(spacing: 32) { + ForEach(viewModel.serverRules, id: \.self) { rule in + ReportServerRulesRowView( + title: rule.text, + isSelect: rule == viewModel.selectRule + ) + .background( + Color(viewModel.backgroundColor) + ) + .onTapGesture { + viewModel.selectRule = rule + viewModel.isDislike = false + } + } + ReportServerRulesRowView( + title: L10n.Scene.Report.StepTwo.iJustDonTLikeIt, + isSelect: viewModel.isDislike + ) + .background( + Color(viewModel.backgroundColor) + ) + .onTapGesture { + viewModel.selectRule = nil + viewModel.isDislike = true + } + } + .padding() + .transition(.opacity) + .animation(.easeInOut) + + Spacer() + .frame(minHeight: viewModel.bottomPaddingHeight) + } + .background( + Color(viewModel.backgroundColor) + ) + } + +} + +struct ReportServerRulesRowView: View { + + var title: String + var isSelect: Bool + + var body: some View { + HStack(spacing: 14) { + Image(systemName: isSelect ? "checkmark.circle.fill" : "circle") + .resizable() + .frame(width: 28, height: 28, alignment: .center) + VStack(alignment: .leading, spacing: 4) { + Text(title) + .foregroundColor(Color(Asset.Colors.Label.primary.color)) + .font(.headline) + } + Spacer() + } + } + +} + +#if DEBUG +struct ReportServerRulesView_Previews: PreviewProvider { + static var previews: some View { + Group { + NavigationView { + ReportServerRulesView(viewModel: ReportServerRulesViewModel(context: .shared)) + .navigationBarTitle(Text("")) + .navigationBarTitleDisplayMode(.inline) + } + NavigationView { + ReportServerRulesView(viewModel: ReportServerRulesViewModel(context: .shared)) + .navigationBarTitle(Text("")) + .navigationBarTitleDisplayMode(.inline) + } + .preferredColorScheme(.dark) + } + } +} +#endif diff --git a/Mastodon/Scene/Report/ReportServerRules/ReportServerRulesViewController.swift b/Mastodon/Scene/Report/ReportServerRules/ReportServerRulesViewController.swift new file mode 100644 index 000000000..330debbb8 --- /dev/null +++ b/Mastodon/Scene/Report/ReportServerRules/ReportServerRulesViewController.swift @@ -0,0 +1,117 @@ +// +// ReportServerRulesViewController.swift +// Mastodon +// +// Created by MainasuK on 2022-5-10. +// + +import os.log +import UIKit +import SwiftUI +import Combine +import MastodonUI +import MastodonAsset +import MastodonLocalization + +protocol ReportServerRulesViewControllerDelegate: AnyObject { + func reportServerRulesViewController(_ viewController: ReportServerRulesViewController, nextButtonPressed button: UIButton) +} + +final class ReportServerRulesViewController: UIViewController, NeedsDependency, ReportViewControllerAppearance { + + let logger = Logger(subsystem: "ReportReasonViewController", category: "ViewController") + + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } + weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + + var disposeBag = Set() + private var observations = Set() + + var viewModel: ReportServerRulesViewModel! + private(set) lazy var reportServerRulesView = ReportServerRulesView(viewModel: viewModel) + + lazy var cancelBarButtonItem = UIBarButtonItem( + barButtonSystemItem: .cancel, + target: self, + action: #selector(ReportServerRulesViewController.cancelBarButtonItemDidPressed(_:)) + ) + + let navigationActionView: NavigationActionView = { + let navigationActionView = NavigationActionView() + navigationActionView.backgroundColor = Asset.Scene.Onboarding.background.color + navigationActionView.hidesBackButton = true + return navigationActionView + }() + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + +extension ReportServerRulesViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + setupAppearance() + defer { setupNavigationBarBackgroundView() } + + let hostingViewController = UIHostingController(rootView: reportServerRulesView) + hostingViewController.view.preservesSuperviewLayoutMargins = true + addChild(hostingViewController) + hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(hostingViewController.view) + NSLayoutConstraint.activate([ + hostingViewController.view.topAnchor.constraint(equalTo: view.topAnchor), + hostingViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + hostingViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), + hostingViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + + navigationActionView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(navigationActionView) + defer { + view.bringSubviewToFront(navigationActionView) + } + NSLayoutConstraint.activate([ + navigationActionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + navigationActionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + view.bottomAnchor.constraint(equalTo: navigationActionView.bottomAnchor), + ]) + + navigationActionView + .observe(\.bounds, options: [.initial, .new]) { [weak self] navigationActionView, _ in + guard let self = self else { return } + let inset = navigationActionView.frame.height + self.viewModel.bottomPaddingHeight = inset + } + .store(in: &observations) + + Publishers.CombineLatest( + viewModel.$selectRule, + viewModel.$isDislike + ) + .map { $0 != nil || $1 } + .assign(to: \.isEnabled, on: navigationActionView.nextButton) + .store(in: &disposeBag) + + navigationActionView.nextButton.addTarget(self, action: #selector(ReportServerRulesViewController.nextButtonPressed(_:)), for: .touchUpInside) + } + +} + +extension ReportServerRulesViewController { + + @objc private func cancelBarButtonItemDidPressed(_ sender: UIBarButtonItem) { + dismiss(animated: true, completion: nil) + } + + @objc private func nextButtonPressed(_ sender: UIButton) { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + + assert(viewModel.delegate != nil) + viewModel.delegate?.reportServerRulesViewController(self, nextButtonPressed: sender) + } + +} diff --git a/Mastodon/Scene/Report/ReportServerRules/ReportServerRulesViewModel.swift b/Mastodon/Scene/Report/ReportServerRules/ReportServerRulesViewModel.swift new file mode 100644 index 000000000..e0e21f301 --- /dev/null +++ b/Mastodon/Scene/Report/ReportServerRules/ReportServerRulesViewModel.swift @@ -0,0 +1,36 @@ +// +// ReportServerRulesViewModel.swift +// Mastodon +// +// Created by MainasuK on 2022-5-10. +// + +import UIKit +import SwiftUI +import MastodonAsset +import MastodonSDK +import MastodonLocalization + +final class ReportServerRulesViewModel: ObservableObject { + + weak var delegate: ReportServerRulesViewControllerDelegate? + + // input + let context: AppContext + + @Published var headline = L10n.Scene.Report.StepTwo.whichRulesAreBeingViolated + @Published var serverRules: [Mastodon.Entity.Instance.Rule] = [] + + @Published var bottomPaddingHeight: CGFloat = .zero + @Published var backgroundColor: UIColor = Asset.Scene.Report.background.color + + // output + @Published var selectRule: Mastodon.Entity.Instance.Rule? + @Published var isDislike: Bool = false + + init(context: AppContext) { + self.context = context + // end init + } + +} diff --git a/Mastodon/Scene/Report/ReportStatus/ReportViewController.swift b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewController.swift similarity index 75% rename from Mastodon/Scene/Report/ReportStatus/ReportViewController.swift rename to Mastodon/Scene/Report/ReportStatus/ReportStatusViewController.swift index 12291a964..d3844a3be 100644 --- a/Mastodon/Scene/Report/ReportStatus/ReportViewController.swift +++ b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewController.swift @@ -1,8 +1,8 @@ // -// ReportViewController.swift +// ReportStatusViewController.swift // Mastodon // -// Created by ihugo on 2021/4/20. +// Created by MainasuK on 2022-5-10. // import os.log @@ -12,21 +12,29 @@ import CoreDataStack import MastodonAsset import MastodonLocalization -class ReportViewController: UIViewController, NeedsDependency, ReportViewControllerAppearance { +protocol ReportStatusViewControllerDelegate: AnyObject { + func reportStatusViewController(_ viewController: ReportStatusViewController, skipButtonDidPressed button: UIButton) + func reportStatusViewController(_ viewController: ReportStatusViewController, nextButtonDidPressed button: UIButton) +} + +class ReportStatusViewController: UIViewController, NeedsDependency, ReportViewControllerAppearance { + + let logger = Logger(subsystem: "ReportStatusViewController", category: "ViewController") var disposeBag = Set() private var observations = Set() weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } - - var viewModel: ReportViewModel! + + var viewModel: ReportStatusViewModel! // MAKK: - UI + lazy var cancelBarButtonItem = UIBarButtonItem( barButtonSystemItem: .cancel, target: self, - action: #selector(ReportViewController.cancelBarButtonItemDidPressed(_:)) + action: #selector(ReportStatusViewController.cancelBarButtonItemDidPressed(_:)) ) let tableView: UITableView = { @@ -58,7 +66,7 @@ class ReportViewController: UIViewController, NeedsDependency, ReportViewControl } -extension ReportViewController { +extension ReportStatusViewController { override func viewDidLoad() { super.viewDidLoad() @@ -67,7 +75,7 @@ extension ReportViewController { defer { setupNavigationBarBackgroundView() } navigationItem.rightBarButtonItem = cancelBarButtonItem - + tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) NSLayoutConstraint.activate([ @@ -109,7 +117,7 @@ extension ReportViewController { .sink { [weak self] _ in guard let self = self else { return } guard self.view.window != nil else { return } - self.viewModel.stateMachine.enter(ReportViewModel.State.Loading.self) + self.viewModel.stateMachine.enter(ReportStatusViewModel.State.Loading.self) } .store(in: &disposeBag) @@ -118,56 +126,38 @@ extension ReportViewController { .assign(to: \.isEnabled, on: navigationActionView.nextButton) .store(in: &disposeBag) - navigationActionView.backButton.addTarget(self, action: #selector(ReportViewController.skipButtonDidPressed(_:)), for: .touchUpInside) - navigationActionView.nextButton.addTarget(self, action: #selector(ReportViewController.nextButtonDidPressed(_:)), for: .touchUpInside) + navigationActionView.backButton.addTarget(self, action: #selector(ReportStatusViewController.skipButtonDidPressed(_:)), for: .touchUpInside) + navigationActionView.nextButton.addTarget(self, action: #selector(ReportStatusViewController.nextButtonDidPressed(_:)), for: .touchUpInside) } } -extension ReportViewController { - +extension ReportStatusViewController { + @objc private func cancelBarButtonItemDidPressed(_ sender: UIBarButtonItem) { dismiss(animated: true, completion: nil) } - @objc func skipButtonDidPressed(_ sender: UIButton) { - var selectStatuses: [ManagedObjectRecord] = [] - if let selectStatus = viewModel.status { - selectStatuses.append(selectStatus) - } + @objc private func skipButtonDidPressed(_ sender: UIButton) { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") - let reportSupplementaryViewModel = ReportSupplementaryViewModel( - context: context, - user: viewModel.user, - selectStatuses: selectStatuses - ) - coordinator.present( - scene: .reportSupplementary(viewModel: reportSupplementaryViewModel), - from: self, - transition: .show - ) + assert(viewModel.delegate != nil) + viewModel.isSkip = true + viewModel.delegate?.reportStatusViewController(self, skipButtonDidPressed: sender) } - @objc func nextButtonDidPressed(_ sender: UIButton) { - let selectStatuses = Array(viewModel.selectStatuses) - guard !selectStatuses.isEmpty else { return } + @objc private func nextButtonDidPressed(_ sender: UIButton) { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") - let reportSupplementaryViewModel = ReportSupplementaryViewModel( - context: context, - user: viewModel.user, - selectStatuses: selectStatuses - ) - coordinator.present( - scene: .reportSupplementary(viewModel: reportSupplementaryViewModel), - from: self, - transition: .show - ) + assert(viewModel.delegate != nil) + viewModel.isSkip = false + viewModel.delegate?.reportStatusViewController(self, nextButtonDidPressed: sender) } } // MARK: - UITableViewDelegate -extension ReportViewController: UITableViewDelegate { +extension ReportStatusViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { guard let item = viewModel.diffableDataSource?.itemIdentifier(for: indexPath), case .status = item @@ -214,7 +204,7 @@ extension ReportViewController: UITableViewDelegate { } // MARK: - UIAdaptivePresentationControllerDelegate -extension ReportViewController: UIAdaptivePresentationControllerDelegate { +extension ReportStatusViewController: UIAdaptivePresentationControllerDelegate { func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool { return false } diff --git a/Mastodon/Scene/Report/ReportStatus/ReportViewModel+Diffable.swift b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+Diffable.swift similarity index 93% rename from Mastodon/Scene/Report/ReportStatus/ReportViewModel+Diffable.swift rename to Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+Diffable.swift index 30ec5d872..4610a38d3 100644 --- a/Mastodon/Scene/Report/ReportStatus/ReportViewModel+Diffable.swift +++ b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+Diffable.swift @@ -12,11 +12,11 @@ import CoreDataStack import MastodonAsset import MastodonLocalization -extension ReportViewModel { +extension ReportStatusViewModel { static let reportItemHeaderContext = ReportItem.HeaderContext( primaryLabelText: L10n.Scene.Report.content1, - secondaryLabelText: L10n.Scene.Report.step1 + secondaryLabelText: L10n.Scene.Report.StepThree.step3Of4 ) func setupDiffableDataSource( @@ -41,7 +41,7 @@ extension ReportViewModel { var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) - snapshot.appendItems([.header(context: ReportViewModel.reportItemHeaderContext)], toSection: .main) + snapshot.appendItems([.header(context: ReportStatusViewModel.reportItemHeaderContext)], toSection: .main) let items = records.map { ReportItem.status(record: $0) } snapshot.appendItems(items, toSection: .main) diff --git a/Mastodon/Scene/Report/ReportStatus/ReportViewModel+State.swift b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+State.swift similarity index 92% rename from Mastodon/Scene/Report/ReportStatus/ReportViewModel+State.swift rename to Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+State.swift index 1bc43830f..c653fc4ad 100644 --- a/Mastodon/Scene/Report/ReportStatus/ReportViewModel+State.swift +++ b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+State.swift @@ -12,7 +12,7 @@ import CoreData import CoreDataStack import GameplayKit -extension ReportViewModel { +extension ReportStatusViewModel { class State: GKState { let logger = Logger(subsystem: "ReportViewModel.State", category: "StateMachine") @@ -23,15 +23,15 @@ extension ReportViewModel { String(describing: Self.self) } - weak var viewModel: ReportViewModel? + weak var viewModel: ReportStatusViewModel? - init(viewModel: ReportViewModel) { + init(viewModel: ReportStatusViewModel) { self.viewModel = viewModel } override func didEnter(from previousState: GKState?) { super.didEnter(from: previousState) - let previousState = previousState as? ReportViewModel.State + let previousState = previousState as? ReportStatusViewModel.State logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] enter \(self.name), previous: \(previousState?.name ?? "")") } @@ -46,8 +46,8 @@ extension ReportViewModel { } } -extension ReportViewModel.State { - class Initial: ReportViewModel.State { +extension ReportStatusViewModel.State { + class Initial: ReportStatusViewModel.State { override func isValidNextState(_ stateClass: AnyClass) -> Bool { guard let _ = viewModel else { return false } switch stateClass { @@ -59,7 +59,7 @@ extension ReportViewModel.State { } } - class Loading: ReportViewModel.State { + class Loading: ReportStatusViewModel.State { override func isValidNextState(_ stateClass: AnyClass) -> Bool { switch stateClass { case is Fail.Type: @@ -128,7 +128,7 @@ extension ReportViewModel.State { } } - class Fail: ReportViewModel.State { + class Fail: ReportStatusViewModel.State { override func isValidNextState(_ stateClass: AnyClass) -> Bool { switch stateClass { case is Loading.Type: @@ -139,7 +139,7 @@ extension ReportViewModel.State { } } - class Idle: ReportViewModel.State { + class Idle: ReportStatusViewModel.State { override func isValidNextState(_ stateClass: AnyClass) -> Bool { switch stateClass { case is Loading.Type: @@ -150,7 +150,7 @@ extension ReportViewModel.State { } } - class NoMore: ReportViewModel.State { + class NoMore: ReportStatusViewModel.State { override func isValidNextState(_ stateClass: AnyClass) -> Bool { return false } diff --git a/Mastodon/Scene/Report/ReportStatus/ReportViewModel.swift b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel.swift similarity index 91% rename from Mastodon/Scene/Report/ReportStatus/ReportViewModel.swift rename to Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel.swift index 46a475262..239960637 100644 --- a/Mastodon/Scene/Report/ReportStatus/ReportViewModel.swift +++ b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel.swift @@ -1,8 +1,8 @@ // -// ReportViewModel.swift +// ReportStatusViewModel.swift // Mastodon // -// Created by ihugo on 2021/4/19. +// Created by MainasuK on 2022-5-10. // import Combine @@ -15,10 +15,12 @@ import OrderedCollections import os.log import UIKit -class ReportViewModel { +class ReportStatusViewModel { var disposeBag = Set() + weak var delegate: ReportStatusViewControllerDelegate? + // input let context: AppContext let user: ManagedObjectRecord @@ -26,6 +28,7 @@ class ReportViewModel { let statusFetchedResultsController: StatusFetchedResultsController let listBatchFetchViewModel = ListBatchFetchViewModel() + @Published var isSkip = false @Published var selectStatuses = OrderedSet>() // output diff --git a/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewController.swift b/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewController.swift index 4f6e102b2..fd7783170 100644 --- a/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewController.swift +++ b/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewController.swift @@ -11,20 +11,25 @@ import Combine import MastodonAsset import MastodonLocalization +protocol ReportSupplementaryViewControllerDelegate: AnyObject { + func reportSupplementaryViewController(_ viewController: ReportSupplementaryViewController, skipButtonDidPressed button: UIButton) + func reportSupplementaryViewController(_ viewController: ReportSupplementaryViewController, nextButtonDidPressed button: UIButton) +} + final class ReportSupplementaryViewController: UIViewController, NeedsDependency, ReportViewControllerAppearance { let logger = Logger(subsystem: "ReportSupplementaryViewController", category: "ViewController") var disposeBag = Set() private var observations = Set() - + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } var viewModel: ReportSupplementaryViewModel! { willSet { precondition(!isViewLoaded) } } - // MAKK: - UI + lazy var cancelBarButtonItem = UIBarButtonItem( barButtonSystemItem: .cancel, target: self, @@ -74,16 +79,16 @@ extension ReportSupplementaryViewController { setupAppearance() defer { setupNavigationBarBackgroundView() } - navigationItem.rightBarButtonItem = cancelBarButtonItem - - viewModel.$isReporting + viewModel.$isBusy .receive(on: DispatchQueue.main) - .sink { [weak self] isReporting in + .sink { [weak self] isBusy in guard let self = self else { return } - self.navigationActionView.isUserInteractionEnabled = !isReporting + self.navigationItem.rightBarButtonItem = isBusy ? self.activityIndicatorBarButtonItem : self.cancelBarButtonItem + self.navigationItem.hidesBackButton = isBusy + self.navigationActionView.backButton.isUserInteractionEnabled = !isBusy } .store(in: &disposeBag) - + tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) NSLayoutConstraint.activate([ @@ -130,49 +135,25 @@ extension ReportSupplementaryViewController { } extension ReportSupplementaryViewController { - private func report(withComment: Bool) { - Task { - do { - let _ = try await viewModel.report(withComment: withComment) - logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): report success") - - let reportResultViewModel = ReportResultViewModel( - context: context, - user: viewModel.user - ) - - coordinator.present( - scene: .reportResult(viewModel: reportResultViewModel), - from: self, - transition: .show - ) - - } catch { - let alertController = UIAlertController(for: error, title: nil, preferredStyle: .alert) - let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil) - alertController.addAction(okAction) - self.coordinator.present( - scene: .alertController(alertController: alertController), - from: nil, - transition: .alertController(animated: true, completion: nil) - ) - } - } // end Task - } -} - -extension ReportSupplementaryViewController { - + @objc private func cancelBarButtonItemDidPressed(_ sender: UIBarButtonItem) { dismiss(animated: true, completion: nil) } @objc func skipButtonDidPressed(_ sender: UIButton) { - report(withComment: false) + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + + assert(viewModel.delegate != nil) + viewModel.isSkip = true + viewModel.delegate?.reportSupplementaryViewController(self, skipButtonDidPressed: sender) } @objc func nextButtonDidPressed(_ sender: UIButton) { - report(withComment: true) + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + + assert(viewModel.delegate != nil) + viewModel.isSkip = false + viewModel.delegate?.reportSupplementaryViewController(self, nextButtonDidPressed: sender) } } diff --git a/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewModel+Diffable.swift b/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewModel+Diffable.swift index 5fb9e7421..8cbc16242 100644 --- a/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewModel+Diffable.swift +++ b/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewModel+Diffable.swift @@ -15,8 +15,8 @@ import MastodonLocalization extension ReportSupplementaryViewModel { static let reportItemHeaderContext = ReportItem.HeaderContext( - primaryLabelText: L10n.Scene.Report.content2, - secondaryLabelText: L10n.Scene.Report.step2 + primaryLabelText: L10n.Scene.Report.StepFour.isThereAnythingElseWeShouldKnow, + secondaryLabelText: L10n.Scene.Report.StepFour.step4Of4 ) func setupDiffableDataSource( diff --git a/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewModel.swift b/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewModel.swift index e73e82dd6..c07ee1f54 100644 --- a/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewModel.swift +++ b/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewModel.swift @@ -12,71 +12,37 @@ import MastodonSDK class ReportSupplementaryViewModel { + weak var delegate: ReportSupplementaryViewControllerDelegate? + // Input var context: AppContext let user: ManagedObjectRecord - let selectStatuses: [ManagedObjectRecord] let commentContext = ReportItem.CommentContext() + @Published var isSkip = false + @Published var isBusy = false + // output var diffableDataSource: UITableViewDiffableDataSource? @Published var isNextButtonEnabled = false - @Published var isReporting = false - @Published var isReportSuccess = false init( context: AppContext, - user: ManagedObjectRecord, - selectStatuses: [ManagedObjectRecord] + user: ManagedObjectRecord ) { self.context = context self.user = user - self.selectStatuses = selectStatuses // end init - commentContext.$comment - .map { comment -> Bool in - return !comment.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty - } - .assign(to: &$isNextButtonEnabled) + Publishers.CombineLatest( + commentContext.$comment, + $isBusy + ) + .map { comment, isBusy -> Bool in + guard !isBusy else { return false } + return !comment.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + } + .assign(to: &$isNextButtonEnabled) } } - -extension ReportSupplementaryViewModel { - func report(withComment: Bool) async throws { - guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { - assertionFailure() - return - } - - let managedObjectContext = context.managedObjectContext - let _query: Mastodon.API.Reports.FileReportQuery? = try await managedObjectContext.perform { - guard let user = self.user.object(in: managedObjectContext) else { return nil } - let statusIDs = self.selectStatuses.compactMap { record -> Status.ID? in - guard let status = record.object(in: managedObjectContext) else { return nil } - return status.id - } - return Mastodon.API.Reports.FileReportQuery( - accountID: user.id, - statusIDs: statusIDs, - comment: withComment ? self.commentContext.comment : nil, - forward: nil - ) - } - - guard let query = _query else { return } - - do { - isReporting = true - let _ = try await context.apiService.report( - query: query, - authenticationBox: authenticationBox - ) - isReportSuccess = true - } catch { - isReporting = false - throw error - } - } -} diff --git a/Mastodon/Scene/Report/Share/Cell/ReportCommentTableViewCell.swift b/Mastodon/Scene/Report/Share/Cell/ReportCommentTableViewCell.swift index b982ee5ac..d735a094c 100644 --- a/Mastodon/Scene/Report/Share/Cell/ReportCommentTableViewCell.swift +++ b/Mastodon/Scene/Report/Share/Cell/ReportCommentTableViewCell.swift @@ -8,6 +8,7 @@ import UIKit import Combine import MastodonUI +import MastodonAsset import MastodonLocalization import UITextView_Placeholder @@ -27,7 +28,8 @@ final class ReportCommentTableViewCell: UITableViewCell { textView.attributedPlaceholder = NSAttributedString( string: L10n.Scene.Report.textPlaceholder, attributes: [ - .font: font + .font: font, + .foregroundColor: Asset.Colors.Label.secondary.color ] ) textView.textContainerInset = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16) @@ -80,4 +82,17 @@ extension ReportCommentTableViewCell { commentTextView.heightAnchor.constraint(greaterThanOrEqualToConstant: 100).priority(.defaultHigh), ]) } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + commentTextView.attributedPlaceholder = NSAttributedString( + string: L10n.Scene.Report.textPlaceholder, + attributes: [ + .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)), + .foregroundColor: Asset.Colors.Label.secondary.color + ] + ) + } + } diff --git a/Mastodon/Scene/Report/Share/ReportViewControllerAppearance.swift b/Mastodon/Scene/Report/Share/ReportViewControllerAppearance.swift index 6b35f3d89..fb9bcd63f 100644 --- a/Mastodon/Scene/Report/Share/ReportViewControllerAppearance.swift +++ b/Mastodon/Scene/Report/Share/ReportViewControllerAppearance.swift @@ -19,7 +19,7 @@ extension ReportViewControllerAppearance { func setupAppearance() { - title = L10n.Scene.Report.titleReport + // title = L10n.Scene.Report.titleReport view.backgroundColor = Asset.Scene.Report.background.color setupNavigationBarAppearance() diff --git a/Mastodon/Scene/Root/ContentSplitViewController.swift b/Mastodon/Scene/Root/ContentSplitViewController.swift index 5a34e1ed8..0058f5f6e 100644 --- a/Mastodon/Scene/Root/ContentSplitViewController.swift +++ b/Mastodon/Scene/Root/ContentSplitViewController.swift @@ -38,7 +38,6 @@ final class ContentSplitViewController: UIViewController, NeedsDependency { private(set) lazy var mainTabBarController: MainTabBarController = { let mainTabBarController = MainTabBarController(context: context, coordinator: coordinator) if let homeTimelineViewController = mainTabBarController.viewController(of: HomeTimelineViewController.self) { - homeTimelineViewController.viewModel.displayComposeBarButtonItem = false homeTimelineViewController.viewModel.displaySettingBarButtonItem = false } return mainTabBarController @@ -78,12 +77,23 @@ extension ContentSplitViewController { mainTabBarController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) + // response keyboard command tab switch + mainTabBarController.$currentTab + .sink { [weak self] tab in + guard let self = self else { return } + if tab != self.currentSupplementaryTab { + self.currentSupplementaryTab = tab + } + } + .store(in: &disposeBag) + $currentSupplementaryTab .removeDuplicates() .sink(receiveValue: { [weak self] tab in guard let self = self else { return } self.mainTabBarController.selectedIndex = tab.rawValue - self.mainTabBarController.currentTab.value = tab + self.mainTabBarController.currentTab = tab + self.sidebarViewController.viewModel.currentTab = tab }) .store(in: &disposeBag) } diff --git a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift index db50565aa..8970e2f29 100644 --- a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift +++ b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift @@ -11,6 +11,7 @@ import Combine import SafariServices import MastodonAsset import MastodonLocalization +import MastodonUI class MainTabBarController: UITabBarController { @@ -21,14 +22,29 @@ class MainTabBarController: UITabBarController { weak var context: AppContext! weak var coordinator: SceneCoordinator! + let composeButttonShadowBackgroundContainer = ShadowBackgroundContainer() + let composeButton: UIButton = { + let button = UIButton() + button.setImage(Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate), for: .normal) + button.setBackgroundImage(UIImage.placeholder(color: Asset.Colors.Label.primary.color), for: .normal) + button.setBackgroundImage(UIImage.placeholder(color: Asset.Colors.Label.primary.color.withAlphaComponent(0.8)), for: .highlighted) + button.tintColor = Asset.Colors.Label.primaryReverse.color + button.contentEdgeInsets = UIEdgeInsets(top: 6, left: 12, bottom: 6, right: 12) + button.layer.masksToBounds = true + button.layer.cornerCurve = .continuous + button.layer.cornerRadius = 8 + return button + }() + static let avatarButtonSize = CGSize(width: 25, height: 25) let avatarButton = CircleAvatarButton() - var currentTab = CurrentValueSubject(.home) + @Published var currentTab: Tab = .home enum Tab: Int, CaseIterable { case home case search + case compose case notification case me @@ -40,6 +56,7 @@ class MainTabBarController: UITabBarController { switch self { case .home: return L10n.Common.Controls.Tabs.home case .search: return L10n.Common.Controls.Tabs.search + case .compose: return L10n.Common.Controls.Actions.compose case .notification: return L10n.Common.Controls.Tabs.notification case .me: return L10n.Common.Controls.Tabs.profile } @@ -47,28 +64,41 @@ class MainTabBarController: UITabBarController { var image: UIImage { switch self { - case .home: return UIImage(systemName: "house.fill")! - case .search: return UIImage(systemName: "magnifyingglass")! - case .notification: return UIImage(systemName: "bell.fill")! + case .home: return Asset.ObjectsAndTools.house.image.withRenderingMode(.alwaysTemplate) + case .search: return Asset.ObjectsAndTools.magnifyingglass.image.withRenderingMode(.alwaysTemplate) + case .compose: return Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate) + case .notification: return Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate) + case .me: return UIImage(systemName: "person")! + } + } + + var selectedImage: UIImage { + switch self { + case .home: return Asset.ObjectsAndTools.houseFill.image.withRenderingMode(.alwaysTemplate) + case .search: return Asset.ObjectsAndTools.magnifyingglassFill.image.withRenderingMode(.alwaysTemplate) + case .compose: return Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate) + case .notification: return Asset.ObjectsAndTools.bellFill.image.withRenderingMode(.alwaysTemplate) case .me: return UIImage(systemName: "person.fill")! } } var largeImage: UIImage { switch self { - case .home: return UIImage(systemName: "house.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 80))! - case .search: return UIImage(systemName: "magnifyingglass", withConfiguration: UIImage.SymbolConfiguration(pointSize: 80))! - case .notification: return UIImage(systemName: "bell.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 80))! - case .me: return UIImage(systemName: "person.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 80))! + case .home: return Asset.ObjectsAndTools.house.image.withRenderingMode(.alwaysTemplate).resized(size: CGSize(width: 80, height: 80)) + case .search: return Asset.ObjectsAndTools.magnifyingglass.image.withRenderingMode(.alwaysTemplate).resized(size: CGSize(width: 80, height: 80)) + case .compose: return Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate).resized(size: CGSize(width: 80, height: 80)) + case .notification: return Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate).resized(size: CGSize(width: 80, height: 80)) + case .me: return UIImage(systemName: "person", withConfiguration: UIImage.SymbolConfiguration(pointSize: 80))! } } var sidebarImage: UIImage { switch self { - case .home: return UIImage(systemName: "house")! - case .search: return UIImage(systemName: "magnifyingglass")! - case .notification: return UIImage(systemName: "bell")! - case .me: return UIImage(systemName: "person.fill")! + case .home: return Asset.ObjectsAndTools.house.image.withRenderingMode(.alwaysTemplate) + case .search: return Asset.ObjectsAndTools.magnifyingglass.image.withRenderingMode(.alwaysTemplate) + case .compose: return Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate) + case .notification: return Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate) + case .me: return UIImage(systemName: "person")! } } @@ -85,6 +115,8 @@ class MainTabBarController: UITabBarController { _viewController.context = context _viewController.coordinator = coordinator viewController = _viewController + case .compose: + viewController = UIViewController() case .notification: let _viewController = NotificationViewController() _viewController.context = context @@ -105,6 +137,10 @@ class MainTabBarController: UITabBarController { var _viewControllers: [UIViewController] = [] private(set) var isReadyForWizardAvatarButton = false + + // output + var avatarURLObserver: AnyCancellable? + @Published var avatarURL: URL? init(context: AppContext, coordinator: SceneCoordinator) { self.context = context @@ -138,24 +174,30 @@ extension MainTabBarController { } .store(in: &disposeBag) + // seealso: `ThemeService.apply(theme:)` let tabs = Tab.allCases let viewControllers: [UIViewController] = tabs.map { tab in let viewController = tab.viewController(context: context, coordinator: coordinator) viewController.tabBarItem.tag = tab.tag - viewController.tabBarItem.title = tab.title - viewController.tabBarItem.image = tab.image + viewController.tabBarItem.title = tab.title // needs for acessiblity large content label + viewController.tabBarItem.image = tab.image.imageWithoutBaseline() + viewController.tabBarItem.selectedImage = tab.selectedImage.imageWithoutBaseline() + viewController.tabBarItem.largeContentSizeImage = tab.largeImage.imageWithoutBaseline() viewController.tabBarItem.accessibilityLabel = tab.title - viewController.tabBarItem.largeContentSizeImage = tab.largeImage viewController.tabBarItem.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0) + + switch tab { + case .compose: + viewController.tabBarItem.isEnabled = false + default: + break + } + return viewController } _viewControllers = viewControllers setViewControllers(viewControllers, animated: false) selectedIndex = 0 - - UITabBarItem.appearance().setTitleTextAttributes([.foregroundColor : UIColor.clear], for: .normal) - UITabBarItem.appearance().setTitleTextAttributes([.foregroundColor : UIColor.clear], for: .highlighted) - UITabBarItem.appearance().setTitleTextAttributes([.foregroundColor : UIColor.clear], for: .selected) context.apiService.error .receive(on: DispatchQueue.main) @@ -203,13 +245,15 @@ extension MainTabBarController { } .store(in: &disposeBag) - // handle push notification. toggle entry when finish fetch latest notification - Publishers.CombineLatest( + // handle push notification. + // toggle entry when finish fetch latest notification + Publishers.CombineLatest3( context.authenticationService.activeMastodonAuthentication, - context.notificationService.unreadNotificationCountDidUpdate + context.notificationService.unreadNotificationCountDidUpdate, + $currentTab ) .receive(on: DispatchQueue.main) - .sink { [weak self] authentication, _ in + .sink { [weak self] authentication, _, currentTab in guard let self = self else { return } guard let notificationViewController = self.notificationViewController else { return } @@ -218,24 +262,48 @@ extension MainTabBarController { return count > 0 } ?? false - let image = hasUnreadPushNotification ? UIImage(systemName: "bell.badge.fill")! : UIImage(systemName: "bell.fill")! - notificationViewController.tabBarItem.image = image - notificationViewController.navigationController?.tabBarItem.image = image + let image: UIImage = { + if currentTab == .notification { + return hasUnreadPushNotification ? Asset.ObjectsAndTools.bellBadgeFill.image.withRenderingMode(.alwaysTemplate) : Asset.ObjectsAndTools.bellFill.image.withRenderingMode(.alwaysTemplate) + } else { + return hasUnreadPushNotification ? Asset.ObjectsAndTools.bellBadge.image.withRenderingMode(.alwaysTemplate) : Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate) + } + }() + notificationViewController.tabBarItem.image = image.imageWithoutBaseline() + notificationViewController.navigationController?.tabBarItem.image = image.imageWithoutBaseline() } .store(in: &disposeBag) + layoutComposeButton() layoutAvatarButton() + + $avatarURL + .receive(on: DispatchQueue.main) + .sink { [weak self] avatarURL in + guard let self = self else { return } + self.avatarButton.avatarImageView.setImage( + url: avatarURL, + placeholder: .placeholder(color: .systemFill), + scaleToSize: MainTabBarController.avatarButtonSize + ) + } + .store(in: &disposeBag) context.authenticationService.activeMastodonAuthentication .receive(on: DispatchQueue.main) .sink { [weak self] activeMastodonAuthentication in guard let self = self else { return } - let avatarImageURL = activeMastodonAuthentication?.user.avatarImageURL() - self.avatarButton.avatarImageView.setImage( - url: avatarImageURL, - placeholder: .placeholder(color: .systemFill), - scaleToSize: MainTabBarController.avatarButtonSize - ) + if let user = activeMastodonAuthentication?.user { + self.avatarURLObserver = user.publisher(for: \.avatar) + .sink { [weak self, weak user] _ in + guard let self = self else { return } + guard let user = user else { return } + guard user.managedObjectContext != nil else { return } + self.avatarURL = user.avatarImageURL() + } + } else { + self.avatarURLObserver = nil + } // a11y let _profileTabItem = self.tabBar.items?.first { item in item.tag == Tab.me.tag } @@ -258,7 +326,7 @@ extension MainTabBarController { } .store(in: &disposeBag) - currentTab + $currentTab .receive(on: DispatchQueue.main) .sink { [weak self] tab in guard let self = self else { return } @@ -268,8 +336,18 @@ extension MainTabBarController { updateTabBarDisplay() + composeButton.addTarget(self, action: #selector(MainTabBarController.composeButtonDidPressed(_:)), for: .touchUpInside) + #if DEBUG -// selectedIndex = 1 + // Debug Register viewController + // Task { @MainActor in + // let _homeTimelineViewController = viewControllers + // .compactMap { $0 as? UINavigationController } + // .compactMap { $0.topViewController } + // .compactMap { $0 as? HomeTimelineViewController } + // .first + // try await _homeTimelineViewController?.showRegisterController() + // } // end Task #endif } @@ -277,23 +355,25 @@ extension MainTabBarController { super.traitCollectionDidChange(previousTraitCollection) updateTabBarDisplay() + updateComposeButtonAppearance() updateAvatarButtonAppearance() } } extension MainTabBarController { - private func updateTabBarDisplay() { - switch traitCollection.horizontalSizeClass { - case .compact: - tabBar.isHidden = false - default: - tabBar.isHidden = true - } + + @objc private func composeButtonDidPressed(_ sender: UIButton) { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return } + let composeViewModel = ComposeViewModel( + context: context, + composeKind: .post, + authenticationBox: authenticationBox + ) + coordinator.present(scene: .compose(viewModel: composeViewModel), from: nil, transition: .modal(animated: true, completion: nil)) } -} - -extension MainTabBarController { + @objc private func tabBarLongPressGestureRecognizerHandler(_ sender: UILongPressGestureRecognizer) { guard sender.state == .began else { return } @@ -321,6 +401,59 @@ extension MainTabBarController { } extension MainTabBarController { + + private func updateTabBarDisplay() { + switch traitCollection.horizontalSizeClass { + case .compact: + tabBar.isHidden = false + composeButttonShadowBackgroundContainer.isHidden = false + default: + tabBar.isHidden = true + composeButttonShadowBackgroundContainer.isHidden = true + } + } + + private func layoutComposeButton() { + guard composeButton.superview == nil else { return } + + let _composeTabItem = self.tabBar.items?.first { item in item.tag == Tab.compose.tag } + guard let composeTabItem = _composeTabItem else { return } + guard let view = composeTabItem.value(forKey: "view") as? UIView else { + return + } + + let _anchorImageView = view.subviews.first { subview in subview is UIImageView } as? UIImageView + guard let anchorImageView = _anchorImageView else { + assertionFailure() + return + } + anchorImageView.alpha = 0 + + composeButttonShadowBackgroundContainer.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(composeButttonShadowBackgroundContainer) // add to tabBar will crash on iPad when size class changing + NSLayoutConstraint.activate([ + composeButttonShadowBackgroundContainer.centerXAnchor.constraint(equalTo: anchorImageView.centerXAnchor), + composeButttonShadowBackgroundContainer.centerYAnchor.constraint(equalTo: anchorImageView.centerYAnchor), + ]) + composeButttonShadowBackgroundContainer.cornerRadius = composeButton.layer.cornerRadius + + composeButton.translatesAutoresizingMaskIntoConstraints = false + composeButttonShadowBackgroundContainer.addSubview(composeButton) + NSLayoutConstraint.activate([ + composeButton.topAnchor.constraint(equalTo: composeButttonShadowBackgroundContainer.topAnchor), + composeButton.leadingAnchor.constraint(equalTo: composeButttonShadowBackgroundContainer.leadingAnchor), + composeButton.trailingAnchor.constraint(equalTo: composeButttonShadowBackgroundContainer.trailingAnchor), + composeButton.bottomAnchor.constraint(equalTo: composeButttonShadowBackgroundContainer.bottomAnchor), + ]) + composeButton.setContentHuggingPriority(.required - 1, for: .horizontal) + composeButton.setContentHuggingPriority(.required - 1, for: .vertical) + } + + private func updateComposeButtonAppearance() { + composeButton.setBackgroundImage(UIImage.placeholder(color: Asset.Colors.Label.primary.color), for: .normal) + composeButton.setBackgroundImage(UIImage.placeholder(color: Asset.Colors.Label.primary.color.withAlphaComponent(0.8)), for: .highlighted) + } + private func layoutAvatarButton() { guard avatarButton.superview == nil else { return } @@ -340,8 +473,8 @@ extension MainTabBarController { self.avatarButton.translatesAutoresizingMaskIntoConstraints = false view.addSubview(self.avatarButton) NSLayoutConstraint.activate([ - self.avatarButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), - self.avatarButton.centerYAnchor.constraint(equalTo: anchorImageView.centerYAnchor, constant: 1.5), // 1.5pt offset + self.avatarButton.centerXAnchor.constraint(equalTo: anchorImageView.centerXAnchor), + self.avatarButton.centerYAnchor.constraint(equalTo: anchorImageView.centerYAnchor), self.avatarButton.widthAnchor.constraint(equalToConstant: MainTabBarController.avatarButtonSize.width).priority(.required - 1), self.avatarButton.heightAnchor.constraint(equalToConstant: MainTabBarController.avatarButtonSize.height).priority(.required - 1), ]) @@ -351,9 +484,10 @@ extension MainTabBarController { } private func updateAvatarButtonAppearance() { - avatarButton.borderColor = currentTab.value == .me ? .label : .systemFill + avatarButton.borderColor = currentTab == .me ? .label : .systemFill avatarButton.setNeedsLayout() } + } extension MainTabBarController { @@ -373,11 +507,12 @@ extension MainTabBarController: UITabBarControllerDelegate { func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: select %s", ((#file as NSString).lastPathComponent), #line, #function, viewController.debugDescription) defer { - if let tab = Tab(rawValue: tabBarController.selectedIndex) { - currentTab.value = tab + if let tab = Tab(rawValue: viewController.tabBarItem.tag) { + currentTab = tab } } - guard currentTab.value.rawValue == tabBarController.selectedIndex, + // assert index is as same as the tab rawValue + guard currentTab.rawValue == tabBarController.selectedIndex, let navigationController = viewController as? UINavigationController, navigationController.viewControllers.count == 1, let scrollViewContainer = navigationController.topViewController as? ScrollViewContainer else { @@ -448,7 +583,13 @@ extension MainTabBarController { var switchToTabKeyCommands: [UIKeyCommand] { var commands: [UIKeyCommand] = [] - for (i, tab) in Tab.allCases.enumerated() { + let tabs: [Tab] = [ + .home, + .search, + .notification, + .me + ] + for (i, tab) in tabs.enumerated() { let title = L10n.Common.Controls.Keyboard.Common.switchToTab(tab.title) let input = String(i + 1) let command = UIKeyCommand( @@ -554,7 +695,7 @@ extension MainTabBarController { let previousTab = Tab(rawValue: selectedIndex) selectedIndex = index if let tab = Tab(rawValue: index) { - currentTab.value = tab + currentTab = tab } if let previousTab = previousTab { diff --git a/Mastodon/Scene/Root/RootSplitViewController.swift b/Mastodon/Scene/Root/RootSplitViewController.swift index d9b18b0b4..f19282936 100644 --- a/Mastodon/Scene/Root/RootSplitViewController.swift +++ b/Mastodon/Scene/Root/RootSplitViewController.swift @@ -208,7 +208,7 @@ extension RootSplitViewController: UISplitViewControllerDelegate { switch proposedTopColumn { case .compact: RootSplitViewController.transform(from: contentSplitViewController.mainTabBarController, to: compactMainTabBarViewController) - compactMainTabBarViewController.currentTab.value = contentSplitViewController.currentSupplementaryTab + compactMainTabBarViewController.currentTab = contentSplitViewController.currentSupplementaryTab default: assertionFailure() @@ -231,11 +231,11 @@ extension RootSplitViewController: UISplitViewControllerDelegate { RootSplitViewController.transform(from: compactMainTabBarViewController, to: contentSplitViewController.mainTabBarController) - let tab = compactMainTabBarViewController.currentTab.value + let tab = compactMainTabBarViewController.currentTab if tab == .search { contentSplitViewController.currentSupplementaryTab = .home } else { - contentSplitViewController.currentSupplementaryTab = compactMainTabBarViewController.currentTab.value + contentSplitViewController.currentSupplementaryTab = compactMainTabBarViewController.currentTab } return proposedDisplayMode diff --git a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift index 6568ab0cd..c7cf3d49d 100644 --- a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift +++ b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift @@ -9,6 +9,7 @@ import os.log import UIKit import Combine import CoreDataStack +import MastodonUI protocol SidebarViewControllerDelegate: AnyObject { func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab) @@ -153,10 +154,9 @@ extension SidebarViewController { coordinator.animate { context in self.collectionView.collectionViewLayout.invalidateLayout() - } completion: { [weak self] context in -// guard let self = self else { return } + } completion: { context in + // do nothing } - } } diff --git a/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift b/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift index 3cc277dc6..a6698d6c2 100644 --- a/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift +++ b/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift @@ -22,7 +22,8 @@ final class SidebarViewModel { let context: AppContext @Published private var isSidebarDataSourceReady = false @Published private var isAvatarButtonDataReady = false - + @Published var currentTab: MainTabBarController.Tab = .home + // output var diffableDataSource: UICollectionViewDiffableDataSource? var secondaryDiffableDataSource: UICollectionViewDiffableDataSource? @@ -86,35 +87,55 @@ extension SidebarViewModel { } }() cell.item = SidebarListContentView.Item( + isActive: false, title: item.title, - image: item.sidebarImage, + image: item.image, + activeImage: item.selectedImage, imageURL: imageURL ) cell.setNeedsUpdateConfiguration() cell.isAccessibilityElement = true cell.accessibilityLabel = item.title + self.$currentTab + .receive(on: DispatchQueue.main) + .sink { [weak cell] currentTab in + guard let cell = cell else { return } + cell.item?.isActive = currentTab == item + cell.setNeedsUpdateConfiguration() + } + .store(in: &cell.disposeBag) + switch item { case .notification: - Publishers.CombineLatest( + Publishers.CombineLatest3( self.context.authenticationService.activeMastodonAuthentication, - self.context.notificationService.unreadNotificationCountDidUpdate + self.context.notificationService.unreadNotificationCountDidUpdate, + self.$currentTab ) .receive(on: DispatchQueue.main) - .sink { [weak cell] authentication, _ in + .sink { [weak cell] authentication, _, currentTab in guard let cell = cell else { return } let hasUnreadPushNotification: Bool = authentication.flatMap { authentication in let count = UserDefaults.shared.getNotificationCountWithAccessToken(accessToken: authentication.userAccessToken) return count > 0 } ?? false - let image = hasUnreadPushNotification ? UIImage(systemName: "bell.badge")! : UIImage(systemName: "bell")! - cell._contentView?.imageView.image = image + let image: UIImage = { + if currentTab == .notification { + return hasUnreadPushNotification ? Asset.ObjectsAndTools.bellBadgeFill.image.withRenderingMode(.alwaysTemplate) : Asset.ObjectsAndTools.bellFill.image.withRenderingMode(.alwaysTemplate) + } else { + return hasUnreadPushNotification ? Asset.ObjectsAndTools.bellBadge.image.withRenderingMode(.alwaysTemplate) : Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate) + } + }() + cell.item?.image = image + cell.item?.activeImage = image + cell.setNeedsUpdateConfiguration() } .store(in: &cell.disposeBag) case .me: guard let authentication = self.context.authenticationService.activeMastodonAuthentication.value else { break } - let currentUserDisplayName = authentication.user.displayNameWithFallback ?? "no user" + let currentUserDisplayName = authentication.user.displayNameWithFallback cell.accessibilityHint = L10n.Scene.AccountList.tabBarHint(currentUserDisplayName) default: break @@ -122,7 +143,7 @@ extension SidebarViewModel { } let cellRegistration = UICollectionView.CellRegistration { [weak self] cell, indexPath, item in - guard let self = self else { return } + guard let _ = self else { return } cell.item = item cell.setNeedsUpdateConfiguration() cell.isAccessibilityElement = true @@ -140,15 +161,19 @@ extension SidebarViewModel { return collectionView.dequeueConfiguredReusableCell(using: tabCellRegistration, for: indexPath, item: tab) case .setting: let item = SidebarListContentView.Item( + isActive: false, title: L10n.Common.Controls.Actions.settings, - image: UIImage(systemName: "gear")!, + image: Asset.ObjectsAndTools.gear.image.withRenderingMode(.alwaysTemplate), + activeImage: Asset.ObjectsAndTools.gear.image.withRenderingMode(.alwaysTemplate), imageURL: nil ) return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item) case .compose: let item = SidebarListContentView.Item( + isActive: false, title: L10n.Common.Controls.Actions.compose, - image: UIImage(systemName: "square.and.pencil")!, + image: Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate), + activeImage: Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate), imageURL: nil ) return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item) @@ -192,15 +217,15 @@ extension SidebarViewModel { } let item = SidebarListContentView.Item( + isActive: false, title: L10n.Common.Controls.Actions.compose, - image: UIImage(systemName: "square.and.pencil")!, + image: Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate), + activeImage: Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate), imageURL: nil ) return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item) } -// _secondaryDiffableDataSource.supplementaryViewProvider = { collectionView, elementKind, indexPath in -// return nil -// } + secondaryDiffableDataSource = _secondaryDiffableDataSource var secondarySnapshot = NSDiffableDataSourceSnapshot() diff --git a/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift b/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift index d6ae40e17..794563eaf 100644 --- a/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift +++ b/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift @@ -93,12 +93,14 @@ extension SidebarListContentView { // configure model imageView.isHidden = item.imageURL != nil avatarButton.isHidden = item.imageURL == nil - imageView.image = item.image.withRenderingMode(.alwaysTemplate) + imageView.image = item.isActive ? item.activeImage.withRenderingMode(.alwaysTemplate) : item.image.withRenderingMode(.alwaysTemplate) avatarButton.avatarImageView.setImage( url: item.imageURL, placeholder: avatarButton.avatarImageView.image ?? .placeholder(color: .systemFill), // reuse to avoid blink scaleToSize: nil ) + avatarButton.borderWidth = item.isActive ? 2 : 0 + avatarButton.setNeedsLayout() } } @@ -107,25 +109,32 @@ extension SidebarListContentView { // state var isSelected: Bool = false var isHighlighted: Bool = false + var isActive: Bool // model let title: String - let image: UIImage + var image: UIImage + var activeImage: UIImage let imageURL: URL? + static func == (lhs: SidebarListContentView.Item, rhs: SidebarListContentView.Item) -> Bool { return lhs.isSelected == rhs.isSelected && lhs.isHighlighted == rhs.isHighlighted + && lhs.isActive == rhs.isActive && lhs.title == rhs.title && lhs.image == rhs.image + && lhs.activeImage == rhs.activeImage && lhs.imageURL == rhs.imageURL } func hash(into hasher: inout Hasher) { hasher.combine(isSelected) hasher.combine(isHighlighted) + hasher.combine(isActive) hasher.combine(title) hasher.combine(image) + hasher.combine(activeImage) imageURL.flatMap { hasher.combine($0) } } } diff --git a/Mastodon/Scene/Search/Search/Cell/TrendCollectionViewCell.swift b/Mastodon/Scene/Search/Search/Cell/TrendCollectionViewCell.swift index a43d65df4..379cba70d 100644 --- a/Mastodon/Scene/Search/Search/Cell/TrendCollectionViewCell.swift +++ b/Mastodon/Scene/Search/Search/Cell/TrendCollectionViewCell.swift @@ -9,45 +9,13 @@ import UIKit import Combine import MetaTextKit import MastodonAsset +import MastodonUI final class TrendCollectionViewCell: UICollectionViewCell { var _disposeBag = Set() - let container: UIStackView = { - let stackView = UIStackView() - stackView.axis = .horizontal - stackView.spacing = 16 - return stackView - }() - - let infoContainer: UIStackView = { - let stackView = UIStackView() - stackView.axis = .vertical - return stackView - }() - - let lineChartContainer: UIStackView = { - let stackView = UIStackView() - stackView.axis = .vertical - return stackView - }() - - let primaryLabel: UILabel = { - let label = UILabel() - label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)) - label.textColor = Asset.Colors.Label.primary.color - return label - }() - - let secondaryLabel: UILabel = { - let label = UILabel() - label.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 15, weight: .regular)) - label.textColor = Asset.Colors.Label.secondary.color - return label - }() - - let lineChartView = LineChartView() + let trendView = TrendView() override func prepareForReuse() { super.prepareForReuse() @@ -77,44 +45,13 @@ extension TrendCollectionViewCell { } .store(in: &_disposeBag) - container.translatesAutoresizingMaskIntoConstraints = false - contentView.addSubview(container) + trendView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(trendView) NSLayoutConstraint.activate([ - container.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 11), - container.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), - container.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - contentView.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: 11), - ]) - - container.layoutMargins = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) - container.isLayoutMarginsRelativeArrangement = true - - // container: H - [ info container | padding | line chart container ] - container.addArrangedSubview(infoContainer) - - // info container: V - [ primary | secondary ] - infoContainer.addArrangedSubview(primaryLabel) - infoContainer.addArrangedSubview(secondaryLabel) - - // padding - let padding = UIView() - container.addArrangedSubview(padding) - - // line chart - container.addArrangedSubview(lineChartContainer) - - let lineChartViewTopPadding = UIView() - let lineChartViewBottomPadding = UIView() - lineChartViewTopPadding.translatesAutoresizingMaskIntoConstraints = false - lineChartViewBottomPadding.translatesAutoresizingMaskIntoConstraints = false - lineChartView.translatesAutoresizingMaskIntoConstraints = false - lineChartContainer.addArrangedSubview(lineChartViewTopPadding) - lineChartContainer.addArrangedSubview(lineChartView) - lineChartContainer.addArrangedSubview(lineChartViewBottomPadding) - NSLayoutConstraint.activate([ - lineChartView.widthAnchor.constraint(equalToConstant: 50), - lineChartView.heightAnchor.constraint(equalToConstant: 26), - lineChartViewTopPadding.heightAnchor.constraint(equalTo: lineChartViewBottomPadding.heightAnchor), + trendView.topAnchor.constraint(equalTo: contentView.topAnchor), + trendView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + trendView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + trendView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), ]) } diff --git a/Mastodon/Scene/Search/Search/SearchViewController.swift b/Mastodon/Scene/Search/Search/SearchViewController.swift index d1bed9484..982844f51 100644 --- a/Mastodon/Scene/Search/Search/SearchViewController.swift +++ b/Mastodon/Scene/Search/Search/SearchViewController.swift @@ -15,7 +15,7 @@ import MastodonLocalization final class HeightFixedSearchBar: UISearchBar { override var intrinsicContentSize: CGSize { - return CGSize(width: CGFloat.greatestFiniteMagnitude, height: 44) + return CGSize(width: CGFloat.greatestFiniteMagnitude, height: 36) } } @@ -35,19 +35,26 @@ final class SearchViewController: UIViewController, NeedsDependency { // layout alongside with split mode button (on iPad) let titleViewContainer = UIView() let searchBar = HeightFixedSearchBar() - - let collectionView: UICollectionView = { - var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) - configuration.backgroundColor = .clear - configuration.headerMode = .supplementary - let layout = UICollectionViewCompositionalLayout.list(using: configuration) - let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) - collectionView.backgroundColor = .clear - return collectionView - }() + +// let collectionView: UICollectionView = { +// var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) +// configuration.backgroundColor = .clear +// configuration.headerMode = .supplementary +// let layout = UICollectionViewCompositionalLayout.list(using: configuration) +// let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) +// collectionView.backgroundColor = .clear +// return collectionView +// }() let searchBarTapPublisher = PassthroughSubject() + private(set) lazy var discoveryViewController: DiscoveryViewController = { + let viewController = DiscoveryViewController() + viewController.context = context + viewController.coordinator = coordinator + return viewController + }() + deinit { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) } @@ -71,19 +78,31 @@ extension SearchViewController { setupSearchBar() - collectionView.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(collectionView) +// collectionView.translatesAutoresizingMaskIntoConstraints = false +// view.addSubview(collectionView) +// NSLayoutConstraint.activate([ +// collectionView.topAnchor.constraint(equalTo: view.topAnchor), +// collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), +// collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), +// collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor), +// ]) +// +// collectionView.delegate = self +// viewModel.setupDiffableDataSource( +// collectionView: collectionView +// ) + + addChild(discoveryViewController) + discoveryViewController.view.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(discoveryViewController.view) NSLayoutConstraint.activate([ - collectionView.topAnchor.constraint(equalTo: view.topAnchor), - collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + discoveryViewController.view.topAnchor.constraint(equalTo: view.topAnchor), + discoveryViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + discoveryViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), + discoveryViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) - collectionView.delegate = self - viewModel.setupDiffableDataSource( - collectionView: collectionView - ) +// discoveryViewController.view.isHidden = true } override func viewDidAppear(_ animated: Bool) { @@ -113,7 +132,10 @@ extension SearchViewController { searchBar.trailingAnchor.constraint(equalTo: titleViewContainer.trailingAnchor), searchBar.bottomAnchor.constraint(equalTo: titleViewContainer.bottomAnchor), ]) + searchBar.setContentHuggingPriority(.required, for: .horizontal) + searchBar.setContentHuggingPriority(.required, for: .vertical) navigationItem.titleView = titleViewContainer +// navigationItem.titleView = searchBar searchBarTapPublisher .throttle(for: 0.5, scheduler: DispatchQueue.main, latest: false) @@ -123,7 +145,10 @@ extension SearchViewController { let searchDetailViewModel = SearchDetailViewModel() searchDetailViewModel.needsBecomeFirstResponder = true self.navigationController?.delegate = self.searchTransitionController - self.coordinator.present(scene: .searchDetail(viewModel: searchDetailViewModel), from: self, transition: .customPush) + // FIXME: + // use `.customPush(animated: false)` false to disable navigation bar animation for searchBar layout + // but that should be a fade transition whe fixed size searchBar + self.coordinator.present(scene: .searchDetail(viewModel: searchDetailViewModel), from: self, transition: .customPush(animated: false)) } .store(in: &disposeBag) } @@ -151,21 +176,21 @@ extension SearchViewController: UISearchControllerDelegate { } // MARK: - UICollectionViewDelegate -extension SearchViewController: UICollectionViewDelegate { - func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): select item at: \(indexPath.debugDescription)") - - defer { - collectionView.deselectItem(at: indexPath, animated: true) - } - - guard let diffableDataSource = viewModel.diffableDataSource else { return } - guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } - - switch item { - case .trend(let hashtag): - let viewModel = HashtagTimelineViewModel(context: context, hashtag: hashtag.name) - coordinator.present(scene: .hashtagTimeline(viewModel: viewModel), from: self, transition: .show) - } - } -} +//extension SearchViewController: UICollectionViewDelegate { +// func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { +// logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): select item at: \(indexPath.debugDescription)") +// +// defer { +// collectionView.deselectItem(at: indexPath, animated: true) +// } +// +// guard let diffableDataSource = viewModel.diffableDataSource else { return } +// guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } +// +// switch item { +// case .trend(let hashtag): +// let viewModel = HashtagTimelineViewModel(context: context, hashtag: hashtag.name) +// coordinator.present(scene: .hashtagTimeline(viewModel: viewModel), from: self, transition: .show) +// } +// } +//} diff --git a/Mastodon/Scene/Search/Search/SearchViewModel+Diffable.swift b/Mastodon/Scene/Search/Search/SearchViewModel+Diffable.swift index ca741b7f3..3f448289a 100644 --- a/Mastodon/Scene/Search/Search/SearchViewModel+Diffable.swift +++ b/Mastodon/Scene/Search/Search/SearchViewModel+Diffable.swift @@ -8,35 +8,35 @@ import UIKit import MastodonSDK -extension SearchViewModel { - - func setupDiffableDataSource( - collectionView: UICollectionView - ) { - diffableDataSource = SearchSection.diffableDataSource( - collectionView: collectionView, - context: context - ) - - var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections([.trend]) - diffableDataSource?.apply(snapshot) - - $hashtags - .receive(on: DispatchQueue.main) - .sink { [weak self] hashtags in - guard let self = self else { return } - guard let diffableDataSource = self.diffableDataSource else { return } - - var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections([.trend]) - - let trendItems = hashtags.map { SearchItem.trend($0) } - snapshot.appendItems(trendItems, toSection: .trend) - - diffableDataSource.apply(snapshot) - } - .store(in: &disposeBag) - } - -} +//extension SearchViewModel { +// +// func setupDiffableDataSource( +// collectionView: UICollectionView +// ) { +// diffableDataSource = SearchSection.diffableDataSource( +// collectionView: collectionView, +// context: context +// ) +// +// var snapshot = NSDiffableDataSourceSnapshot() +// snapshot.appendSections([.trend]) +// diffableDataSource?.apply(snapshot) +// +// $hashtags +// .receive(on: DispatchQueue.main) +// .sink { [weak self] hashtags in +// guard let self = self else { return } +// guard let diffableDataSource = self.diffableDataSource else { return } +// +// var snapshot = NSDiffableDataSourceSnapshot() +// snapshot.appendSections([.trend]) +// +// let trendItems = hashtags.map { SearchItem.trend($0) } +// snapshot.appendItems(trendItems, toSection: .trend) +// +// diffableDataSource.apply(snapshot) +// } +// .store(in: &disposeBag) +// } +// +//} diff --git a/Mastodon/Scene/Search/Search/SearchViewModel.swift b/Mastodon/Scene/Search/Search/SearchViewModel.swift index 2776713df..b47bc2e88 100644 --- a/Mastodon/Scene/Search/Search/SearchViewModel.swift +++ b/Mastodon/Scene/Search/Search/SearchViewModel.swift @@ -29,31 +29,31 @@ final class SearchViewModel: NSObject { self.context = context super.init() - Publishers.CombineLatest( - context.authenticationService.activeMastodonAuthenticationBox, - viewDidAppeared - ) - .compactMap { authenticationBox, _ -> MastodonAuthenticationBox? in - return authenticationBox - } - .throttle(for: 3, scheduler: DispatchQueue.main, latest: true) - .asyncMap { authenticationBox in - try await context.apiService.trends(domain: authenticationBox.domain, query: nil) - } - .retry(3) - .map { response in Result, Error> { response } } - .catch { error in Just(Result, Error> { throw error }) } - .receive(on: DispatchQueue.main) - .sink { [weak self] result in - guard let self = self else { return } - switch result { - case .success(let response): - self.hashtags = response.value - case .failure: - break - } - } - .store(in: &disposeBag) +// Publishers.CombineLatest( +// context.authenticationService.activeMastodonAuthenticationBox, +// viewDidAppeared +// ) +// .compactMap { authenticationBox, _ -> MastodonAuthenticationBox? in +// return authenticationBox +// } +// .throttle(for: 3, scheduler: DispatchQueue.main, latest: true) +// .asyncMap { authenticationBox in +// try await context.apiService.trendHashtags(domain: authenticationBox.domain, query: nil) +// } +// .retry(3) +// .map { response in Result, Error> { response } } +// .catch { error in Just(Result, Error> { throw error }) } +// .receive(on: DispatchQueue.main) +// .sink { [weak self] result in +// guard let self = self else { return } +// switch result { +// case .success(let response): +// self.hashtags = response.value +// case .failure: +// break +// } +// } +// .store(in: &disposeBag) } } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift index 5e143a33c..ecc1c0c02 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift @@ -12,6 +12,14 @@ import Pageboy import MastodonAsset import MastodonLocalization +final class CustomSearchController: UISearchController { + + let customSearchBar = UISearchBar(frame: CGRect(x: 0, y: 0, width: 300, height: 100)) + + override var searchBar: UISearchBar { customSearchBar } + +} + // Fake search bar not works on iPad with UISplitViewController // check device and fallback to standard UISearchController final class SearchDetailViewController: PageboyViewController, NeedsDependency { @@ -48,8 +56,8 @@ final class SearchDetailViewController: PageboyViewController, NeedsDependency { return navigationBar }() - let searchController: UISearchController = { - let searchController = UISearchController() + let searchController: CustomSearchController = { + let searchController = CustomSearchController() searchController.automaticallyShowsScopeBar = false searchController.dimsBackgroundDuringPresentation = false return searchController @@ -235,11 +243,17 @@ extension SearchDetailViewController { if isPhoneDevice { searchBar.setShowsCancelButton(true, animated: animated) - searchBar.becomeFirstResponder() + UIView.performWithoutAnimation { + self.searchBar.becomeFirstResponder() + } } else { - searchController.isActive = true - DispatchQueue.main.asyncAfter(deadline: .now() + 0.33) { - self.searchController.searchBar.becomeFirstResponder() + searchController.searchBar.setShowsCancelButton(true, animated: false) + searchController.searchBar.setShowsScope(true, animated: false) + UIView.performWithoutAnimation { + self.searchController.isActive = true + } + DispatchQueue.main.async { + self.searchController.searchBar.becomeFirstResponder() } } } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchHistory/View/SearchHistoryTableHeaderView.swift b/Mastodon/Scene/Search/SearchDetail/SearchHistory/View/SearchHistoryTableHeaderView.swift index 8ac661b18..fc41bdf27 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchHistory/View/SearchHistoryTableHeaderView.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchHistory/View/SearchHistoryTableHeaderView.swift @@ -10,6 +10,7 @@ import UIKit import Combine import MastodonAsset import MastodonLocalization +import MastodonUI protocol SearchHistoryTableHeaderViewDelegate: AnyObject { func searchHistoryTableHeaderView(_ searchHistoryTableHeaderView: SearchHistoryTableHeaderView, clearSearchHistoryButtonDidPressed button: UIButton) diff --git a/Mastodon/Scene/Settings/Cell/SettingsAppearanceTableViewCell.swift b/Mastodon/Scene/Settings/Cell/SettingsAppearanceTableViewCell.swift index 3760fd8ed..44b6b39c3 100644 --- a/Mastodon/Scene/Settings/Cell/SettingsAppearanceTableViewCell.swift +++ b/Mastodon/Scene/Settings/Cell/SettingsAppearanceTableViewCell.swift @@ -85,6 +85,9 @@ class SettingsAppearanceTableViewCell: UITableViewCell { subview.removeFromSuperview() } } + + // remove grouped style table corner radius + layer.cornerRadius = 0 } } diff --git a/Mastodon/Scene/Settings/SettingsViewController.swift b/Mastodon/Scene/Settings/SettingsViewController.swift index 4cf20cd09..8455ac7d9 100644 --- a/Mastodon/Scene/Settings/SettingsViewController.swift +++ b/Mastodon/Scene/Settings/SettingsViewController.swift @@ -448,7 +448,7 @@ extension SettingsViewController: SettingsAppearanceTableViewCellDelegate { guard let dataSource = viewModel.dataSource else { return } guard let indexPath = tableView.indexPath(for: cell) else { return } let item = dataSource.itemIdentifier(for: indexPath) - guard case let .appearance(record) = item else { return } + guard case .appearance = item else { return } Task { @MainActor in switch appearanceMode { diff --git a/Mastodon/Scene/Share/NavigationController/AdaptiveStatusBarStyleNavigationController.swift b/Mastodon/Scene/Share/NavigationController/AdaptiveStatusBarStyleNavigationController.swift index aac23285b..ec145f86d 100644 --- a/Mastodon/Scene/Share/NavigationController/AdaptiveStatusBarStyleNavigationController.swift +++ b/Mastodon/Scene/Share/NavigationController/AdaptiveStatusBarStyleNavigationController.swift @@ -10,7 +10,47 @@ import UIKit // Make status bar style adaptive for child view controller // SeeAlso: `modalPresentationCapturesStatusBarAppearance` class AdaptiveStatusBarStyleNavigationController: UINavigationController { + + private lazy var fullWidthBackGestureRecognizer = UIPanGestureRecognizer() + override var childForStatusBarStyle: UIViewController? { visibleViewController } } + +// ref: https://stackoverflow.com/a/60598558/3797903 +extension AdaptiveStatusBarStyleNavigationController { + + override func viewDidLoad() { + super.viewDidLoad() + + setupFullWidthBackGesture() + } + + private func setupFullWidthBackGesture() { + // The trick here is to wire up our full-width `fullWidthBackGestureRecognizer` to execute the same handler as + // the system `interactivePopGestureRecognizer`. That's done by assigning the same "targets" (effectively + // object and selector) of the system one to our gesture recognizer. + guard let interactivePopGestureRecognizer = interactivePopGestureRecognizer, + let targets = interactivePopGestureRecognizer.value(forKey: "targets") + else { return } + + fullWidthBackGestureRecognizer.setValue(targets, forKey: "targets") + fullWidthBackGestureRecognizer.delegate = self + view.addGestureRecognizer(fullWidthBackGestureRecognizer) + } +} + +// MARK: - UIGestureRecognizerDelegate +extension AdaptiveStatusBarStyleNavigationController: UIGestureRecognizerDelegate { + func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + let isSystemSwipeToBackEnabled = interactivePopGestureRecognizer?.isEnabled == true + let isThereStackedViewControllers = viewControllers.count > 1 + let isPanPopable = (topViewController as? PanPopableViewController)?.isPanPopable ?? true + return isSystemSwipeToBackEnabled && isThereStackedViewControllers && isPanPopable + } +} + +protocol PanPopableViewController: UIViewController { + var isPanPopable: Bool { get } +} diff --git a/Mastodon/Scene/Share/View/Content/ContentWarningOverlayView.swift b/Mastodon/Scene/Share/View/Content/ContentWarningOverlayView.swift index 78c5462f5..8300f865a 100644 --- a/Mastodon/Scene/Share/View/Content/ContentWarningOverlayView.swift +++ b/Mastodon/Scene/Share/View/Content/ContentWarningOverlayView.swift @@ -11,6 +11,7 @@ import Combine import UIKit import MastodonAsset import MastodonLocalization +import MastodonUI protocol ContentWarningOverlayViewDelegate: AnyObject { func contentWarningOverlayViewDidPressed(_ contentWarningOverlayView: ContentWarningOverlayView) diff --git a/Mastodon/Scene/Share/View/Content/MediaView+Configuration.swift b/Mastodon/Scene/Share/View/Content/MediaView+Configuration.swift index ad2fa398d..b782f8e8a 100644 --- a/Mastodon/Scene/Share/View/Content/MediaView+Configuration.swift +++ b/Mastodon/Scene/Share/View/Content/MediaView+Configuration.swift @@ -57,34 +57,8 @@ extension MediaView { } // end switch }() - if let previewURL = configuration.previewURL, - let url = URL(string: previewURL) - { - let placeholder = UIImage.placeholder(color: .systemGray6) - let request = URLRequest(url: url) - ImageDownloader.default.download(request) { response in - switch response.result { - case .success(let image): - configuration.previewImage = image - case .failure(let error): - configuration.previewImage = placeholder - } - } - } - - if let assetURL = configuration.assetURL, - let blurhash = configuration.blurhash - { - AppContext.shared.blurhashImageCacheService.image( - blurhash: blurhash, - size: configuration.aspectRadio, - url: assetURL - ) - .assign(to: \.blurhashImage, on: configuration) - .store(in: &configuration.blurhashImageDisposeBag) - } - - configuration.isReveal = status.sensitive ? status.isMediaSensitiveToggled : true + configuration.load() + configuration.isReveal = status.isMediaSensitive ? status.isSensitiveToggled : true return configuration } diff --git a/Mastodon/Scene/Share/View/Content/StatusView+Configuration.swift b/Mastodon/Scene/Share/View/Content/StatusView+Configuration.swift index 1a90c69af..ab4fea1ab 100644 --- a/Mastodon/Scene/Share/View/Content/StatusView+Configuration.swift +++ b/Mastodon/Scene/Share/View/Content/StatusView+Configuration.swift @@ -156,7 +156,6 @@ extension StatusView { .map { _ in author.avatarImageURL() } .assign(to: \.authorAvatarImageURL, on: viewModel) .store(in: &disposeBag) - // author name Publishers.CombineLatest( author.publisher(for: \.displayName), @@ -268,25 +267,19 @@ extension StatusView { .assign(to: \.visibility, on: viewModel) .store(in: &disposeBag) // sensitive - status.publisher(for: \.isContentSensitiveToggled) - .assign(to: \.isContentSensitiveToggled, on: viewModel) + viewModel.isContentSensitive = status.isContentSensitive + status.publisher(for: \.isSensitiveToggled) + .assign(to: \.isSensitiveToggled, on: viewModel) .store(in: &disposeBag) - - -// viewModel.source = status.source } private func configureMedia(status: Status) { let status = status.reblog ?? status - viewModel.isMediaSensitive = status.sensitive && !status.attachments.isEmpty // some servers set media sensitive even empty attachments + viewModel.isMediaSensitive = status.isMediaSensitive let configurations = MediaView.configuration(status: status) viewModel.mediaViewConfigurations = configurations - - status.publisher(for: \.isMediaSensitiveToggled) - .assign(to: \.isMediaSensitiveToggled, on: viewModel) - .store(in: &disposeBag) } private func configurePoll(status: Status) { diff --git a/Mastodon/Scene/Share/View/Content/ThreadMetaView.swift b/Mastodon/Scene/Share/View/Content/ThreadMetaView.swift index c339654f5..0953feecd 100644 --- a/Mastodon/Scene/Share/View/Content/ThreadMetaView.swift +++ b/Mastodon/Scene/Share/View/Content/ThreadMetaView.swift @@ -6,6 +6,7 @@ // import UIKit +import MastodonUI final class ThreadMetaView: UIView { diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift index a1033f052..a3315211e 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift @@ -61,7 +61,7 @@ extension StatusTableViewCell { statusView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16), containerViewLeadingLayoutConstraint, containerViewTrailingLayoutConstraint, - statusView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + contentView.bottomAnchor.constraint(equalTo: statusView.bottomAnchor, constant: 10), ]) statusView.setup(style: .inline) updateContainerViewMarginConstraints() diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift index e27cc2dd3..115175800 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift @@ -112,7 +112,7 @@ extension StatusThreadRootTableViewCell { statusView.statusMetricView ] - if !statusView.viewModel.isSensitive { + if !statusView.viewModel.isMediaSensitive { elements.removeAll(where: { $0 === statusView.contentSensitiveeToggleButton }) } diff --git a/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift b/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift index f730e0b8b..1b2d62211 100644 --- a/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift +++ b/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift @@ -255,6 +255,8 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { rect.size.height -= offset return rect }() + + // FIXME: let maskLayerToFinalPath = maskLayerToFinalRect.flatMap { UIBezierPath(rect: $0) }?.cgPath if let maskLayerToPath = maskLayerToPath { diff --git a/Mastodon/Scene/Transition/Search/SearchToSearchDetailViewControllerAnimatedTransitioning.swift b/Mastodon/Scene/Transition/Search/SearchToSearchDetailViewControllerAnimatedTransitioning.swift index f06d04d9a..e060acc83 100644 --- a/Mastodon/Scene/Transition/Search/SearchToSearchDetailViewControllerAnimatedTransitioning.swift +++ b/Mastodon/Scene/Transition/Search/SearchToSearchDetailViewControllerAnimatedTransitioning.swift @@ -46,14 +46,17 @@ extension SearchToSearchDetailViewControllerAnimatedTransitioning { let toViewEndFrame = transitionContext.finalFrame(for: toVC) transitionContext.containerView.addSubview(toView) toView.frame = toViewEndFrame + toView.setNeedsLayout() + toView.layoutIfNeeded() + toVC.searchBar.setNeedsLayout() + toVC.searchBar.layoutIfNeeded() toView.alpha = 0 let animator = UIViewPropertyAnimator(duration: transitionDuration(using: transitionContext), curve: curve) animator.addAnimations { - + toView.alpha = 1 } animator.addCompletion { position in - toView.alpha = 1 transitionContext.completeTransition(true) } return animator diff --git a/Mastodon/Service/APIService/APIService+HomeTimeline.swift b/Mastodon/Service/APIService/APIService+HomeTimeline.swift index 39d4cf6e1..863510af4 100644 --- a/Mastodon/Service/APIService/APIService+HomeTimeline.swift +++ b/Mastodon/Service/APIService/APIService+HomeTimeline.swift @@ -1,5 +1,5 @@ // -// APIService+HomeTimeline.swift +// µ.swift // Mastodon // // Created by MainasuK Cirno on 2021/2/3. diff --git a/Mastodon/Service/APIService/APIService+PublicTimeline.swift b/Mastodon/Service/APIService/APIService+PublicTimeline.swift new file mode 100644 index 000000000..fd0c2f0cd --- /dev/null +++ b/Mastodon/Service/APIService/APIService+PublicTimeline.swift @@ -0,0 +1,52 @@ +// +// APIService+PublicTimeline.swift +// Mastodon +// +// Created by MainasuK on 2022-4-29. +// + +import Foundation +import Combine +import CoreData +import CoreDataStack +import CommonOSLog +import MastodonSDK + +extension APIService { + + func publicTimeline( + query: Mastodon.API.Timeline.PublicTimelineQuery, + authenticationBox: MastodonAuthenticationBox + ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Status]> { + let domain = authenticationBox.domain + let authorization = authenticationBox.userAuthorization + + let response = try await Mastodon.API.Timeline.public( + session: session, + domain: domain, + query: query, + authorization: authorization + ).singleOutput() + + let managedObjectContext = self.backgroundManagedObjectContext + try await managedObjectContext.performChanges { + let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user + for entity in response.value { + _ = Persistence.Status.createOrMerge( + in: managedObjectContext, + context: Persistence.Status.PersistContext( + domain: domain, + entity: entity, + me: me, + statusCache: nil, + userCache: nil, + networkDate: response.networkDate + ) + ) + } + } + + return response + } // end func + +} diff --git a/Mastodon/Service/APIService/APIService+Recommend.swift b/Mastodon/Service/APIService/APIService+Recommend.swift index cb195b608..6e457ad07 100644 --- a/Mastodon/Service/APIService/APIService+Recommend.swift +++ b/Mastodon/Service/APIService/APIService+Recommend.swift @@ -13,12 +13,13 @@ import CoreDataStack import OSLog extension APIService { + func suggestionAccount( query: Mastodon.API.Suggestions.Query?, authenticationBox: MastodonAuthenticationBox ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> { - let response = try await Mastodon.API.Suggestions.get( + let response = try await Mastodon.API.Suggestions.accounts( session: session, domain: authenticationBox.domain, query: query, @@ -47,7 +48,7 @@ extension APIService { query: Mastodon.API.Suggestions.Query?, authenticationBox: MastodonAuthenticationBox ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.V2.SuggestionAccount]> { - let response = try await Mastodon.API.V2.Suggestions.get( + let response = try await Mastodon.API.V2.Suggestions.accounts( session: session, domain: authenticationBox.domain, query: query, diff --git a/Mastodon/Service/APIService/APIService+Thread.swift b/Mastodon/Service/APIService/APIService+Thread.swift index 782da5886..f6c36e5b6 100644 --- a/Mastodon/Service/APIService/APIService+Thread.swift +++ b/Mastodon/Service/APIService/APIService+Thread.swift @@ -34,7 +34,7 @@ extension APIService { let value = response.value.ancestors + response.value.descendants for entity in value { - Persistence.Status.createOrMerge( + _ = Persistence.Status.createOrMerge( in: managedObjectContext, context: Persistence.Status.PersistContext( domain: domain, diff --git a/Mastodon/Service/APIService/APIService+Trend.swift b/Mastodon/Service/APIService/APIService+Trend.swift index 0ce2a86a8..47dda6bd2 100644 --- a/Mastodon/Service/APIService/APIService+Trend.swift +++ b/Mastodon/Service/APIService/APIService+Trend.swift @@ -9,11 +9,12 @@ import Foundation import MastodonSDK extension APIService { - func trends( + + func trendHashtags( domain: String, - query: Mastodon.API.Trends.Query? + query: Mastodon.API.Trends.HashtagQuery? ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Tag]> { - let response = try await Mastodon.API.Trends.get( + let response = try await Mastodon.API.Trends.hashtags( session: session, domain: domain, query: query @@ -21,4 +22,48 @@ extension APIService { return response } + + func trendStatuses( + domain: String, + query: Mastodon.API.Trends.StatusQuery + ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Status]> { + let response = try await Mastodon.API.Trends.statuses( + session: session, + domain: domain, + query: query + ).singleOutput() + + let managedObjectContext = backgroundManagedObjectContext + try await managedObjectContext.performChanges { + for entity in response.value { + _ = Persistence.Status.createOrMerge( + in: managedObjectContext, + context: Persistence.Status.PersistContext( + domain: domain, + entity: entity, + me: nil, + statusCache: nil, + userCache: nil, + networkDate: response.networkDate + ) + ) + } // end for … in + } + + return response + } + + func trendLinks( + domain: String, + query: Mastodon.API.Trends.LinkQuery + ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Link]> { + let response = try await Mastodon.API.Trends.links( + session: session, + domain: domain, + query: query + ).singleOutput() + + return response + } + } diff --git a/Mastodon/Service/APIService/APIService+UserTimeline.swift b/Mastodon/Service/APIService/APIService+UserTimeline.swift index c5cb63180..85b8d6153 100644 --- a/Mastodon/Service/APIService/APIService+UserTimeline.swift +++ b/Mastodon/Service/APIService/APIService+UserTimeline.swift @@ -48,7 +48,7 @@ extension APIService { try await managedObjectContext.performChanges { let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user for entity in response.value { - Persistence.Status.createOrMerge( + _ = Persistence.Status.createOrMerge( in: managedObjectContext, context: Persistence.Status.PersistContext( domain: domain, diff --git a/Mastodon/Service/InstanceService.swift b/Mastodon/Service/InstanceService.swift index 4fb6309fd..03b8dfd4e 100644 --- a/Mastodon/Service/InstanceService.swift +++ b/Mastodon/Service/InstanceService.swift @@ -95,7 +95,7 @@ extension InstanceService { self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [Instance] update instance for domain: \(domain)") } } receiveValue: { [weak self] response in - guard let self = self else { return } + guard let _ = self else { return } // do nothing } .store(in: &disposeBag) diff --git a/Mastodon/Service/SettingService.swift b/Mastodon/Service/SettingService.swift index 1e8022c59..bd571d8f4 100644 --- a/Mastodon/Service/SettingService.swift +++ b/Mastodon/Service/SettingService.swift @@ -12,6 +12,7 @@ import CoreDataStack import MastodonSDK import MastodonAsset import MastodonLocalization +import MastodonCommon final class SettingService { @@ -190,18 +191,6 @@ extension SettingService { extension SettingService { static func updatePreference(setting: Setting) { - // set appearance -// let userInterfaceStyle: UIUserInterfaceStyle = { -// switch setting.appearance { -// case .automatic: return .unspecified -// case .light: return .light -// case .dark: return .dark -// } -// }() -// if UserDefaults.shared.customUserInterfaceStyle != userInterfaceStyle { -// UserDefaults.shared.customUserInterfaceStyle = userInterfaceStyle -// } - // set theme let themeName: ThemeName = setting.preferredTrueBlackDarkMode ? .system : .mastodon if UserDefaults.shared.currentThemeNameRawValue != themeName.rawValue { @@ -223,6 +212,6 @@ extension SettingService { if UserDefaults.shared.preferredUsingDefaultBrowser != setting.preferredUsingDefaultBrowser { UserDefaults.shared.preferredUsingDefaultBrowser = setting.preferredUsingDefaultBrowser } - } + } diff --git a/Mastodon/State/AppContext.swift b/Mastodon/State/AppContext.swift index 9de19c44f..683c81f33 100644 --- a/Mastodon/State/AppContext.swift +++ b/Mastodon/State/AppContext.swift @@ -11,6 +11,7 @@ import Combine import CoreData import CoreDataStack import AlamofireImage +import MastodonUI class AppContext: ObservableObject { @@ -35,7 +36,7 @@ class AppContext: ObservableObject { let photoLibraryService = PhotoLibraryService() let placeholderImageCacheService = PlaceholderImageCacheService() - let blurhashImageCacheService = BlurhashImageCacheService() + let blurhashImageCacheService = BlurhashImageCacheService.shared let documentStore: DocumentStore private var documentStoreSubscription: AnyCancellable! diff --git a/Mastodon/Supporting Files/SceneDelegate.swift b/Mastodon/Supporting Files/SceneDelegate.swift index d178cd0d3..54b1fd57e 100644 --- a/Mastodon/Supporting Files/SceneDelegate.swift +++ b/Mastodon/Supporting Files/SceneDelegate.swift @@ -10,7 +10,7 @@ import UIKit import Combine import CoreDataStack -#if DEBUG +#if PROFILE import FPSIndicator #endif @@ -22,7 +22,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? var coordinator: SceneCoordinator? - #if DEBUG + #if PROFILE var fpsIndicator: FPSIndicator? #endif @@ -87,8 +87,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } .store(in: &observations) - #if DEBUG - // fpsIndicator = FPSIndicator(windowScene: windowScene) + #if PROFILE + fpsIndicator = FPSIndicator(windowScene: windowScene) #endif } diff --git a/MastodonIntent/Info.plist b/MastodonIntent/Info.plist index 02385f4e6..f1f54a5e0 100644 --- a/MastodonIntent/Info.plist +++ b/MastodonIntent/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.3.0 + 1.4.2 CFBundleVersion - 109 + 127 NSExtension NSExtensionAttributes diff --git a/MastodonIntent/ckb.lproj/Intents.strings b/MastodonIntent/ckb.lproj/Intents.strings new file mode 100644 index 000000000..1d23253a3 --- /dev/null +++ b/MastodonIntent/ckb.lproj/Intents.strings @@ -0,0 +1,51 @@ +"16wxgf" = "پۆستێک بکە"; + +"751xkl" = "نووسین"; + +"CsR7G2" = "پۆستێک بکە"; + +"HZSGTr" = "چی پۆست بکرێت؟"; + +"HdGikU" = "پۆستکردنەکە سەرکەوتوو نەبوو"; + +"KDNTJ4" = "هۆکاری سەرنەکەوتن"; + +"RHxKOw" = "پۆست بە نووسینەوە بکە"; + +"RxSqsb" = "پۆست"; + +"WCIR3D" = "${content} لە ماستۆدۆن پۆست بکە"; + +"ZKJSNu" = "پۆست"; + +"ZS1XaK" = "${content}"; + +"ZbSjzC" = "دەرکەوتن"; + +"Zo4jgJ" = "دەرکەوتنی پۆست"; + +"apSxMG-dYQ5NN" = "${count} بژاردە بۆ 'گشتی' هەن."; + +"apSxMG-ehFLjY" = "${count} بژاردە بۆ 'تەنیا شوێنکەوتووەکان' هەن."; + +"ayoYEb-dYQ5NN" = "${content}، گشتی"; + +"ayoYEb-ehFLjY" = "${content}، تەنیا شوێنکەوتووەکان"; + +"dUyuGg" = "پۆستێک بکە"; + +"dYQ5NN" = "گشتی"; + +"ehFLjY" = "تەنیا شوێنکەوتووەکان"; + +"gfePDu" = "پۆستکردنەکە سەرکەوتوو نەبوو. ${failureReason}"; + +"k7dbKQ" = "پۆستەکە کرا."; + +"oGiqmY-dYQ5NN" = "دڵنیایت لە هەڵبژاردنی 'گشتی'؟"; + +"oGiqmY-ehFLjY" = "دڵنیایت لە هەڵبژاردنی 'تەنیا شوێنکەوتووەکان'؟"; + +"rM6dvp" = "بەستەر"; + +"ryJLwG" = "پۆستەکە کرا. "; diff --git a/MastodonIntent/ckb.lproj/Intents.stringsdict b/MastodonIntent/ckb.lproj/Intents.stringsdict new file mode 100644 index 000000000..5a39d5e64 --- /dev/null +++ b/MastodonIntent/ckb.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 + + + + diff --git a/MastodonIntent/gl.lproj/Intents.strings b/MastodonIntent/gl.lproj/Intents.strings new file mode 100644 index 000000000..2083cc701 --- /dev/null +++ b/MastodonIntent/gl.lproj/Intents.strings @@ -0,0 +1,51 @@ +"16wxgf" = "Publicar en Mastodon"; + +"751xkl" = "Texto a incluír"; + +"CsR7G2" = "Publicar en Mastodon"; + +"HZSGTr" = "Cal é o contido a publicar?"; + +"HdGikU" = "Fallou a publicación"; + +"KDNTJ4" = "Razón do fallo"; + +"RHxKOw" = "Enviar Publicación con texto"; + +"RxSqsb" = "Publicación"; + +"WCIR3D" = "Publicar ${content} en Mastodon"; + +"ZKJSNu" = "Publicación"; + +"ZS1XaK" = "${content}"; + +"ZbSjzC" = "Visibilidade"; + +"Zo4jgJ" = "Visibilidade da publicación"; + +"apSxMG-dYQ5NN" = "Hai ${count} opcións que coinciden con ‘Público’."; + +"apSxMG-ehFLjY" = "Hai ${count} opcións que coinciden con 'Só seguidoras’."; + +"ayoYEb-dYQ5NN" = "${content}, Público"; + +"ayoYEb-ehFLjY" = "${content}, Só seguidoras"; + +"dUyuGg" = "Publicar en Mastodon"; + +"dYQ5NN" = "Público"; + +"ehFLjY" = "Só seguidoras"; + +"gfePDu" = "Fallou a publicación. ${failureReason}"; + +"k7dbKQ" = "Publicación correcta."; + +"oGiqmY-dYQ5NN" = "Só para confirmar, querías ’Público'?"; + +"oGiqmY-ehFLjY" = "Só para confirmar, querías ’Só para seguidoras'?"; + +"rM6dvp" = "URL"; + +"ryJLwG" = "Publicación correcta. "; diff --git a/MastodonIntent/gl.lproj/Intents.stringsdict b/MastodonIntent/gl.lproj/Intents.stringsdict new file mode 100644 index 000000000..5a39d5e64 --- /dev/null +++ b/MastodonIntent/gl.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 + + + + diff --git a/MastodonIntent/it.lproj/Intents.strings b/MastodonIntent/it.lproj/Intents.strings new file mode 100644 index 000000000..d26aef14b --- /dev/null +++ b/MastodonIntent/it.lproj/Intents.strings @@ -0,0 +1,51 @@ +"16wxgf" = "Pubblica su Mastodon"; + +"751xkl" = "Contenuto testuale"; + +"CsR7G2" = "Pubblica su Mastodon"; + +"HZSGTr" = "Quale contenuto postare?"; + +"HdGikU" = "Pubblicazione non riuscita"; + +"KDNTJ4" = "Motivo del fallimento"; + +"RHxKOw" = "Invia post con contenuto testuale"; + +"RxSqsb" = "Pubblica"; + +"WCIR3D" = "Pubblica ${content} su Mastodon"; + +"ZKJSNu" = "Pubblica"; + +"ZS1XaK" = "${content}"; + +"ZbSjzC" = "Visibilità"; + +"Zo4jgJ" = "Visibilità del post"; + +"apSxMG-dYQ5NN" = "Ci sono ${count} opzioni corrispondenti a 'Pubblico'."; + +"apSxMG-ehFLjY" = "Ci sono ${count} opzioni corrispondenti a ‘Solo Seguaci’."; + +"ayoYEb-dYQ5NN" = "${content}, Pubblico"; + +"ayoYEb-ehFLjY" = "${content}, Solo seguaci"; + +"dUyuGg" = "Pubblica su Mastodon"; + +"dYQ5NN" = "Pubblico"; + +"ehFLjY" = "Solo i seguaci"; + +"gfePDu" = "Pubblicazione fallita. ${failureReason}"; + +"k7dbKQ" = "Post inviato con successo."; + +"oGiqmY-dYQ5NN" = "Solo per confermare, volevi ‘Pubblico’?"; + +"oGiqmY-ehFLjY" = "Solo per confermare, volevi 'Solo seguaci'?"; + +"rM6dvp" = "URL"; + +"ryJLwG" = "Post inviato con successo. "; diff --git a/MastodonIntent/it.lproj/Intents.stringsdict b/MastodonIntent/it.lproj/Intents.stringsdict new file mode 100644 index 000000000..5a39d5e64 --- /dev/null +++ b/MastodonIntent/it.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 + + + + diff --git a/MastodonIntent/sv.lproj/Intents.strings b/MastodonIntent/sv.lproj/Intents.strings new file mode 100644 index 000000000..b85bec4c5 --- /dev/null +++ b/MastodonIntent/sv.lproj/Intents.strings @@ -0,0 +1,52 @@ +"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."; + diff --git a/MastodonIntent/sv.lproj/Intents.stringsdict b/MastodonIntent/sv.lproj/Intents.stringsdict new file mode 100644 index 000000000..5a39d5e64 --- /dev/null +++ b/MastodonIntent/sv.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 + + + + diff --git a/MastodonIntent/tr.lproj/Intents.strings b/MastodonIntent/tr.lproj/Intents.strings new file mode 100644 index 000000000..dc60dee73 --- /dev/null +++ b/MastodonIntent/tr.lproj/Intents.strings @@ -0,0 +1,51 @@ +"16wxgf" = "Mastodon'da paylaş"; + +"751xkl" = "Metin içeriği"; + +"CsR7G2" = "Mastodon'da paylaş"; + +"HZSGTr" = "Ne içeriği paylaşılacak?"; + +"HdGikU" = "Gönderi paylaşılamadı"; + +"KDNTJ4" = "Hata Sebebi"; + +"RHxKOw" = "Metin içeriği ile gönderiyi paylaş"; + +"RxSqsb" = "Gönderi"; + +"WCIR3D" = "${content} içeriğini Mastodon'da paylaş"; + +"ZKJSNu" = "Gönderi"; + +"ZS1XaK" = "${content}"; + +"ZbSjzC" = "Gizlilik"; + +"Zo4jgJ" = "Gönderi Gizliliği"; + +"apSxMG-dYQ5NN" = "\"Herkese açık\" ile eşleşen ${count} seçenek var."; + +"apSxMG-ehFLjY" = "\"Sadece takipçiler\" ile eşleşen ${count} seçenek var."; + +"ayoYEb-dYQ5NN" = "${content}, Herkese açık"; + +"ayoYEb-ehFLjY" = "${content}, Sadece takipçiler"; + +"dUyuGg" = "Mastodon'da paylaş"; + +"dYQ5NN" = "Herkese açık"; + +"ehFLjY" = "Sadece takipçiler"; + +"gfePDu" = "Gönderi paylaşılamadı. ${failureReason}"; + +"k7dbKQ" = "Gönderi başarıyla paylaşıldı."; + +"oGiqmY-dYQ5NN" = "\"Herkese açık\" paylaşmak istediğinize emin misiniz?"; + +"oGiqmY-ehFLjY" = "\"Sadece takipçiler\" için paylaşmak istediğinize emin misiniz?"; + +"rM6dvp" = "Bağlantı"; + +"ryJLwG" = "Gönderi başarıyla paylaşıldı. "; diff --git a/MastodonIntent/tr.lproj/Intents.stringsdict b/MastodonIntent/tr.lproj/Intents.stringsdict new file mode 100644 index 000000000..5a39d5e64 --- /dev/null +++ b/MastodonIntent/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 + + + + diff --git a/MastodonIntent/zh-Hant.lproj/Intents.strings b/MastodonIntent/zh-Hant.lproj/Intents.strings new file mode 100644 index 000000000..a45a5f511 --- /dev/null +++ b/MastodonIntent/zh-Hant.lproj/Intents.strings @@ -0,0 +1,51 @@ +"16wxgf" = "於 Mastodon 上發嘟文"; + +"751xkl" = "文字內容"; + +"CsR7G2" = "於 Mastodon 上發嘟文"; + +"HZSGTr" = "要發什麼嘟呢?"; + +"HdGikU" = "嘟文失敗"; + +"KDNTJ4" = "失敗原因"; + +"RHxKOw" = "發送文字內容嘟文"; + +"RxSqsb" = "嘟文"; + +"WCIR3D" = "將 ${content} 於 Mastodon 上發嘟"; + +"ZKJSNu" = "嘟文"; + +"ZS1XaK" = "${content}"; + +"ZbSjzC" = "可見性"; + +"Zo4jgJ" = "嘟文可見性"; + +"apSxMG-dYQ5NN" = "有 ${count} 個選項符合「公開」。"; + +"apSxMG-ehFLjY" = "有 ${count} 個選項符合「僅限跟隨者」。"; + +"ayoYEb-dYQ5NN" = "${content},公開"; + +"ayoYEb-ehFLjY" = "${content},僅限跟隨者"; + +"dUyuGg" = "於 Mastodon 上發嘟文"; + +"dYQ5NN" = "公開"; + +"ehFLjY" = "僅限跟隨者"; + +"gfePDu" = "發嘟失敗。${failureReason}"; + +"k7dbKQ" = "成功發出嘟文。"; + +"oGiqmY-dYQ5NN" = "再確認一次,您想要「公開」?"; + +"oGiqmY-ehFLjY" = "再確認一次,您想要「僅限跟隨者」?"; + +"rM6dvp" = "網址"; + +"ryJLwG" = "成功發出嘟文。 "; diff --git a/MastodonIntent/zh-Hant.lproj/Intents.stringsdict b/MastodonIntent/zh-Hant.lproj/Intents.stringsdict new file mode 100644 index 000000000..5a39d5e64 --- /dev/null +++ b/MastodonIntent/zh-Hant.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 + + + + diff --git a/MastodonSDK/Package.swift b/MastodonSDK/Package.swift index af27091fa..c963be72d 100644 --- a/MastodonSDK/Package.swift +++ b/MastodonSDK/Package.swift @@ -20,24 +20,20 @@ let package = Package( "MastodonLocalization", "MastodonSDK", "MastodonUI", - ]), - .library( - name: "MastodonCommon", - targets: [ - "MastodonCommon", - ]), + ]) ], dependencies: [ .package(url: "https://github.com/SwiftyJSON/SwiftyJSON.git", from: "5.0.0"), .package(url: "https://github.com/apple/swift-nio.git", from: "1.0.0"), .package(url: "https://github.com/kean/Nuke.git", from: "10.3.1"), .package(url: "https://github.com/Flipboard/FLAnimatedImage.git", from: "1.0.0"), - .package(url: "https://github.com/TwidereProject/MetaTextKit.git", .exact("2.2.1")), + .package(url: "https://github.com/TwidereProject/MetaTextKit.git", .exact("2.2.3")), .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.4.0"), .package(url: "https://github.com/Alamofire/AlamofireImage.git", from: "4.1.0"), .package(name: "NukeFLAnimatedImagePlugin", url: "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git", from: "8.0.0"), .package(name: "UITextView+Placeholder", url: "https://github.com/MainasuK/UITextView-Placeholder.git", from: "1.4.1"), - .package(name: "Introspect", url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "0.1.3") + .package(name: "Introspect", url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "0.1.3"), + .package(name: "FaviconFinder", url: "https://github.com/will-lumley/FaviconFinder.git", from: "3.2.2"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. @@ -60,7 +56,9 @@ let package = Package( ), .target( name: "MastodonCommon", - dependencies: [] + dependencies: [ + "MastodonExtension" + ] ), .target( name: "MastodonExtension", @@ -85,14 +83,15 @@ let package = Package( "MastodonExtension", "MastodonAsset", "MastodonLocalization", - "Nuke", - "NukeFLAnimatedImagePlugin", - "UITextView+Placeholder", - "Introspect", .product(name: "Alamofire", package: "Alamofire"), .product(name: "AlamofireImage", package: "AlamofireImage"), - .product(name: "MetaTextKit", package: "MetaTextKit"), .product(name: "FLAnimatedImage", package: "FLAnimatedImage"), + .product(name: "FaviconFinder", package: "FaviconFinder"), + .product(name: "MetaTextKit", package: "MetaTextKit"), + .product(name: "Nuke", package: "Nuke"), + .product(name: "NukeFLAnimatedImagePlugin", package: "NukeFLAnimatedImagePlugin"), + .product(name: "Introspect", package: "Introspect"), + .product(name: "UITextView+Placeholder", package: "UITextView+Placeholder"), ] ), .testTarget( diff --git a/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 3.xcdatamodel/contents b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 3.xcdatamodel/contents index a6f0ee0ce..16a1c7c84 100644 --- a/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 3.xcdatamodel/contents +++ b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 3.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -185,8 +185,7 @@ - - + @@ -262,7 +261,7 @@ - + diff --git a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Status.swift b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Status.swift index d17d1c616..0c7291913 100644 --- a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Status.swift +++ b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Status.swift @@ -42,9 +42,7 @@ public final class Status: NSManagedObject { @NSManaged public private(set) var spoilerText: String? // sourcery: autoUpdatableObject - @NSManaged public private(set) var isContentSensitiveToggled: Bool - // sourcery: autoUpdatableObject - @NSManaged public private(set) var isMediaSensitiveToggled: Bool + @NSManaged public private(set) var isSensitiveToggled: Bool @NSManaged public private(set) var application: Application? @@ -432,14 +430,9 @@ extension Status: AutoUpdatableObject { self.spoilerText = spoilerText } } - public func update(isContentSensitiveToggled: Bool) { - if self.isContentSensitiveToggled != isContentSensitiveToggled { - self.isContentSensitiveToggled = isContentSensitiveToggled - } - } - public func update(isMediaSensitiveToggled: Bool) { - if self.isMediaSensitiveToggled != isMediaSensitiveToggled { - self.isMediaSensitiveToggled = isMediaSensitiveToggled + public func update(isSensitiveToggled: Bool) { + if self.isSensitiveToggled != isSensitiveToggled { + self.isSensitiveToggled = isSensitiveToggled } } public func update(reblogsCount: Int64) { diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Arrow/square.and.arrow.up.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Arrow/square.and.arrow.up.imageset/Contents.json new file mode 100644 index 000000000..33c521feb --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Arrow/square.and.arrow.up.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Share iOS.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Arrow/square.and.arrow.up.imageset/Share iOS.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Arrow/square.and.arrow.up.imageset/Share iOS.pdf new file mode 100644 index 000000000..99e7c6724 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Arrow/square.and.arrow.up.imageset/Share iOS.pdf @@ -0,0 +1,110 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 5.004150 3.928345 cm +0.000000 0.000000 0.000000 scn +16.750000 11.071655 m +17.129696 11.071655 17.443491 10.789501 17.493153 10.423426 c +17.500000 10.321655 l +17.500000 3.320786 l +17.500000 1.587753 16.143545 0.171539 14.434423 0.075930 c +14.250000 0.070786 l +3.250000 0.070786 l +1.516969 0.070786 0.100754 1.427240 0.005145 3.136362 c +0.000000 3.320786 l +0.000000 10.321655 l +0.000000 10.735868 0.335786 11.071655 0.750000 11.071655 c +1.129696 11.071655 1.443491 10.789501 1.493153 10.423426 c +1.500000 10.321655 l +1.500000 3.320786 l +1.500000 2.402613 2.207110 1.649591 3.106473 1.576586 c +3.250000 1.570786 l +14.250000 1.570786 l +15.168174 1.570786 15.921191 2.277895 15.994198 3.177258 c +16.000000 3.320786 l +16.000000 10.321655 l +16.000000 10.735868 16.335787 11.071655 16.750000 11.071655 c +h +3.215397 14.855352 m +8.211636 19.851965 l +8.477744 20.118093 8.894129 20.142456 9.187749 19.924934 c +9.271878 19.852423 l +14.276731 14.855809 l +14.569866 14.563158 14.570257 14.088284 14.277605 13.795150 c +14.011558 13.528664 13.594913 13.504115 13.301123 13.721727 c +13.216944 13.794276 l +9.493999 17.510654 l +9.494885 5.816710 l +9.494885 5.437014 9.212732 5.123218 8.846657 5.073555 c +8.744885 5.066710 l +8.365190 5.066710 8.051395 5.348864 8.001733 5.714939 c +7.994885 5.816710 l +7.993999 17.513655 l +4.276096 13.794732 l +4.009840 13.528456 3.593178 13.504234 3.299558 13.722077 c +3.215437 13.794693 l +2.949160 14.060949 2.924938 14.477612 3.142782 14.771232 c +3.215397 14.855352 l +8.211636 19.851965 l +3.215397 14.855352 l +h +f +n +Q + +endstream +endobj + +3 0 obj + 1600 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 28.000000 28.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001690 00000 n +0000001713 00000 n +0000001886 00000 n +0000001960 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2019 +%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Label/primary.colorset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Label/primary.colorset/Contents.json index a36ab82ce..0c0c8af04 100644 --- a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Label/primary.colorset/Contents.json +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Label/primary.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.216", - "green" : "0.173", - "red" : "0.157" + "blue" : "55", + "green" : "44", + "red" : "40" } }, "idiom" : "universal" diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json index cd123376b..b23080b6b 100644 --- a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json @@ -4,10 +4,10 @@ "color" : { "color-space" : "srgb", "components" : { - "alpha" : "0.600", - "blue" : "67", - "green" : "60", - "red" : "60" + "alpha" : "1.000", + "blue" : "133", + "green" : "112", + "red" : "102" } }, "idiom" : "universal" diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.badge.fill.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.badge.fill.imageset/Contents.json new file mode 100644 index 000000000..956d7c99c --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.badge.fill.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "bell.badge.fill.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.badge.fill.imageset/bell.badge.fill.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.badge.fill.imageset/bell.badge.fill.pdf new file mode 100644 index 000000000..10b05f319 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.badge.fill.imageset/bell.badge.fill.pdf @@ -0,0 +1,94 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 4.000000 3.251190 cm +0.000000 0.000000 0.000000 scn +17.228973 14.106000 m +17.604315 14.106000 17.967243 14.159944 18.310644 14.260609 c +18.330521 14.022223 18.340664 13.781026 18.340664 13.537420 c +18.340664 9.025727 l +19.917404 5.460056 l +19.978443 5.322020 20.010000 5.172432 20.010000 5.021130 c +20.010000 4.429121 19.537165 3.949203 18.953897 3.949203 c +1.056364 3.949203 l +0.907638 3.949203 0.760588 3.981087 0.624843 4.042767 c +0.092485 4.284660 -0.145878 4.918783 0.092444 5.459118 c +1.665367 9.025314 l +1.665486 13.551995 l +1.670396 13.833986 l +1.824999 18.382032 5.507209 22.000000 10.003014 22.000000 c +11.607554 22.000000 13.106230 21.539965 14.378001 20.742750 c +13.732699 20.037863 13.338069 19.093309 13.338069 18.055204 c +13.338069 15.874119 15.080086 14.106000 17.228973 14.106000 c +h +13.291688 2.819208 m +13.026393 1.219061 11.654965 0.000000 10.003014 0.000000 c +8.351062 0.000000 6.979633 1.219061 6.714338 2.819208 c +13.291688 2.819208 l +h +17.228973 15.234344 m +18.763893 15.234344 20.008190 16.497286 20.008190 18.055204 c +20.008190 19.613121 18.763893 20.876064 17.228973 20.876064 c +15.694054 20.876064 14.449756 19.613121 14.449756 18.055204 c +14.449756 16.497286 15.694054 15.234344 17.228973 15.234344 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 1305 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 28.000000 28.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001395 00000 n +0000001418 00000 n +0000001591 00000 n +0000001665 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1724 +%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.badge.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.badge.imageset/Contents.json new file mode 100644 index 000000000..7e5adf71d --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.badge.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "bell.badge.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.badge.imageset/bell.badge.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.badge.imageset/bell.badge.pdf new file mode 100644 index 000000000..0aa1ebbd9 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.badge.imageset/bell.badge.pdf @@ -0,0 +1,112 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 4.000000 3.000000 cm +0.000000 0.000000 0.000000 scn +9.991263 22.000000 m +11.600367 22.000000 13.104079 21.540958 14.379686 20.742632 c +13.999659 20.330532 13.705750 19.835861 13.527027 19.288073 c +12.501425 19.935305 11.289410 20.308149 9.991263 20.308149 c +6.295337 20.308149 3.312736 17.291861 3.312205 13.540749 c +3.312205 8.567127 l +1.816118 5.077236 l +18.175934 5.077236 l +16.670322 8.566052 l +16.670446 13.526248 l +16.666271 13.780252 l +16.662128 13.902481 16.654818 14.023849 16.644423 14.144276 c +16.834492 14.115398 17.029049 14.100430 17.227058 14.100430 c +17.602913 14.100430 17.966335 14.154360 18.310204 14.254990 c +18.322084 14.112284 18.330519 13.968523 18.335421 13.823792 c +18.340088 13.540749 l +18.340088 8.919773 l +19.876272 5.360107 l +19.953144 5.181980 19.992825 4.989672 19.992825 4.795261 c +19.992825 4.016609 19.369843 3.385387 18.601355 3.385387 c +13.330793 3.383699 l +13.330793 1.514933 11.835634 -0.000002 9.991263 -0.000002 c +8.212763 -0.000002 6.758977 1.408655 6.657403 3.184881 c +6.651230 3.386263 l +1.391824 3.385387 l +1.201094 3.385387 1.012400 3.425116 0.837460 3.502110 c +0.132594 3.812326 -0.190615 4.642769 0.115552 5.356956 c +1.642440 8.918699 l +1.642440 13.540869 l +1.643104 18.227058 5.373904 22.000000 9.991263 22.000000 c +h +11.660524 3.386263 m +8.321499 3.383699 l +8.321499 2.449318 9.069077 1.691849 9.991263 1.691849 c +10.859203 1.691849 11.572474 2.362822 11.653385 3.220762 c +11.660524 3.386263 l +h +14.488358 18.551571 m +14.594155 19.146286 14.884010 19.676186 15.296021 20.078548 c +15.796463 20.567268 16.477119 20.867832 17.227058 20.867832 c +18.764036 20.867832 20.010000 19.605387 20.010000 18.048080 c +20.010000 16.815081 19.228939 15.766922 18.140787 15.383841 c +17.854582 15.283082 17.547131 15.228331 17.227058 15.228331 c +16.950932 15.228331 16.684196 15.269077 16.432392 15.344961 c +15.282606 15.691461 14.444118 16.770555 14.444118 18.048080 c +14.444118 18.219936 14.459294 18.388199 14.488358 18.551571 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 2038 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 28.000000 28.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000002128 00000 n +0000002151 00000 n +0000002324 00000 n +0000002398 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2457 +%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.fill.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.fill.imageset/Contents.json new file mode 100644 index 000000000..eea5ce48f --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.fill.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "bell.fill.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.fill.imageset/bell.fill.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.fill.imageset/bell.fill.pdf new file mode 100644 index 000000000..b738813f8 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.fill.imageset/bell.fill.pdf @@ -0,0 +1,88 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 3.994385 2.997498 cm +0.000000 0.000000 0.000000 scn +13.471207 3.000130 m +13.228097 1.303843 11.769090 0.000013 10.005556 0.000013 c +8.242023 0.000013 6.783014 1.303843 6.539904 3.000130 c +13.471207 3.000130 l +h +10.005556 22.002502 m +14.615297 22.002502 18.368101 18.333513 18.503042 13.756456 c +18.503042 13.501259 l +18.506800 13.501259 l +18.506556 9.389503 l +19.920570 5.745182 l +19.958755 5.646759 19.984545 5.544180 19.997494 5.439810 c +20.007233 5.282207 l +20.007233 4.619465 19.503553 4.074364 18.858105 4.008816 c +18.727232 4.002207 l +1.280344 4.002207 l +1.121637 4.002207 0.964313 4.031721 0.816398 4.089247 c +0.198722 4.329462 -0.126749 4.996468 0.046107 5.621784 c +0.087384 5.746153 l +1.503556 9.390502 l +1.504312 13.501259 l +1.504312 18.196365 5.310449 22.002502 10.005556 22.002502 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 870 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 28.000000 28.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000000960 00000 n +0000000982 00000 n +0000001155 00000 n +0000001229 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1288 +%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.imageset/Contents.json new file mode 100644 index 000000000..da621b2a4 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "bell.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.imageset/bell.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.imageset/bell.pdf new file mode 100644 index 000000000..52a4bea08 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/bell.imageset/bell.pdf @@ -0,0 +1,106 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 3.994385 2.997498 cm +0.000000 0.000000 0.000000 scn +10.005556 22.002502 m +14.615297 22.002502 18.368101 18.333513 18.503042 13.756456 c +18.506800 13.501259 l +18.506556 8.889502 l +19.920570 5.245478 l +19.958755 5.147055 19.984545 5.044476 19.997494 4.940105 c +20.007233 4.782503 l +20.007233 4.119761 19.503553 3.574659 18.858105 3.509111 c +18.727232 3.502502 l +13.506981 3.501438 l +13.506981 1.567654 11.939340 0.000013 10.005556 0.000013 c +8.136232 0.000013 6.609047 1.464890 6.509312 3.309326 c +6.504000 3.503502 l +1.280344 3.502502 l +1.121637 3.502502 0.964313 3.532017 0.816398 3.589542 c +0.198722 3.829758 -0.126749 4.496763 0.046107 5.122080 c +0.087384 5.246449 l +1.503556 8.890503 l +1.504312 13.501259 l +1.504312 18.196365 5.310449 22.002502 10.005556 22.002502 c +h +12.001492 3.352070 m +12.006001 3.503502 l +8.004131 3.501438 l +8.004131 2.396082 8.900200 1.500013 10.005556 1.500013 c +11.060669 1.500013 11.925088 2.316473 12.001492 3.352070 c +h +10.005556 20.502502 m +6.219432 20.502502 3.135237 17.497185 3.008372 13.741951 c +3.004312 13.501259 l +3.004312 8.749136 l +3.004312 8.687140 2.996625 8.625507 2.981514 8.565626 c +2.953312 8.477293 l +1.601556 5.003502 l +18.405556 5.003502 l +17.057579 8.477861 l +17.035206 8.535532 17.020094 8.595635 17.012506 8.656790 c +17.006800 8.749136 l +17.006800 13.501259 l +17.006800 17.367939 13.872236 20.502502 10.005556 20.502502 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 1451 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 28.000000 28.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001541 00000 n +0000001564 00000 n +0000001737 00000 n +0000001811 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1870 +%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/gear.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/gear.imageset/Contents.json new file mode 100644 index 000000000..8dfa77c79 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/gear.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "gear.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/gear.imageset/gear.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/gear.imageset/gear.pdf new file mode 100644 index 000000000..d0e5607b9 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/gear.imageset/gear.pdf @@ -0,0 +1,155 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 4.639893 4.163849 cm +0.000000 0.000000 0.000000 scn +9.372525 19.586090 m +10.106503 19.577631 10.837610 19.492828 11.554037 19.333052 c +11.866778 19.263306 12.100667 19.002579 12.136167 18.684128 c +12.306372 17.157282 l +12.383412 16.456230 12.975316 15.925249 13.680974 15.924509 c +13.870646 15.924213 14.058271 15.963713 14.233541 16.041258 c +15.634164 16.656530 l +15.925470 16.784498 16.265816 16.714733 16.483282 16.482475 c +17.495462 15.401449 18.249289 14.104940 18.688040 12.690507 c +18.782648 12.385511 18.673761 12.054057 18.416712 11.864587 c +17.175230 10.949497 l +16.821091 10.689301 16.611935 10.276085 16.611935 9.836634 c +16.611935 9.397182 16.821091 8.983968 17.176010 8.723198 c +18.418562 7.807791 l +18.675699 7.618353 18.784660 7.286852 18.690054 6.981801 c +18.251465 5.567602 17.498068 4.271195 16.486473 3.189995 c +16.269207 2.957779 15.929114 2.887827 15.637836 3.015442 c +14.231503 3.631588 l +13.829185 3.807648 13.367112 3.781857 12.986887 3.562117 c +12.606662 3.342377 12.353621 2.954891 12.305302 2.518358 c +12.136227 0.991678 l +12.101363 0.676876 11.872503 0.417910 11.564378 0.344601 c +10.115929 -0.000013 8.606833 -0.000013 7.158383 0.344601 c +6.850258 0.417910 6.621398 0.676876 6.586535 0.991678 c +6.417712 2.516102 l +6.368124 2.951803 6.114693 3.338112 5.734776 3.557108 c +5.354860 3.776103 4.893555 3.801792 4.492552 3.626715 c +3.085926 3.010441 l +2.794572 2.882792 2.454386 2.952816 2.237134 3.185158 c +1.224976 4.267610 0.471545 5.565555 0.033552 6.981297 c +-0.060786 7.286231 0.048213 7.617466 0.305198 7.806790 c +1.548530 8.722771 l +1.902669 8.982967 2.111826 9.396182 2.111826 9.835633 c +2.111826 10.275084 1.902669 10.688300 1.548066 10.948837 c +0.305513 11.863244 l +0.048147 12.052642 -0.060952 12.384308 0.033719 12.689507 c +0.472470 14.103941 1.226297 15.400448 2.238477 16.481474 c +2.455943 16.713732 2.796290 16.783497 3.087596 16.655531 c +4.487972 16.040365 l +4.890913 15.863533 5.354152 15.890244 5.736122 16.113400 c +6.116442 16.334003 6.369638 16.721867 6.418519 17.158447 c +6.588595 18.684128 l +6.624113 19.002741 6.858214 19.263550 7.171155 19.333147 c +7.888429 19.492670 8.620303 19.577436 9.372525 19.586090 c +h +9.372712 18.086191 m +8.918673 18.080845 8.465910 18.041662 8.018175 17.969074 c +7.909245 16.991905 l +7.807416 16.082413 7.280385 15.275068 6.490771 14.817059 c +5.696323 14.352917 4.727715 14.297064 3.884934 14.666923 c +2.986644 15.061529 l +2.414711 14.367364 1.959492 13.584736 1.638873 12.744408 c +2.436671 12.157299 l +3.175481 11.614474 3.611826 10.752420 3.611826 9.835633 c +3.611826 8.918846 3.175481 8.056792 2.437450 7.514541 c +1.638397 6.925866 l +1.958737 6.084057 2.414029 5.299965 2.986352 4.604462 c +3.891485 5.001020 l +4.729578 5.366932 5.691594 5.313361 6.483880 4.856663 c +7.276166 4.399964 7.804679 3.594347 7.908344 2.683468 c +8.017305 1.699598 l +8.906950 1.548321 9.815811 1.548321 10.705456 1.699598 c +10.814412 2.683426 l +10.915205 3.594033 11.443104 4.402411 12.236333 4.860834 c +13.029562 5.319258 13.993543 5.373064 14.833156 5.005638 c +15.737573 4.609392 l +16.309378 5.303812 16.764484 6.086632 17.085032 6.927112 c +16.287088 7.514968 l +15.548278 8.057793 15.111936 8.919847 15.111936 9.836634 c +15.111936 10.753421 15.548280 11.615475 16.286161 12.157617 c +17.083044 12.744996 l +16.762415 13.585482 16.307142 14.368252 15.735116 15.062530 c +14.838643 14.668724 l +14.473309 14.507083 14.078127 14.423886 13.679017 14.424510 c +12.209332 14.426050 10.975889 15.532539 10.815476 16.992264 c +10.706551 17.969393 l +10.261019 18.041893 9.812987 18.080971 9.372712 18.086191 c +h +9.360047 13.586140 m +11.431115 13.586140 13.110047 11.907207 13.110047 9.836140 c +13.110047 7.765072 11.431115 6.086140 9.360047 6.086140 c +7.288980 6.086140 5.610047 7.765072 5.610047 9.836140 c +5.610047 11.907207 7.288980 13.586140 9.360047 13.586140 c +h +9.360047 12.086140 m +8.117407 12.086140 7.110047 11.078780 7.110047 9.836140 c +7.110047 8.593499 8.117407 7.586140 9.360047 7.586140 c +10.602688 7.586140 11.610047 8.593499 11.610047 9.836140 c +11.610047 11.078780 10.602688 12.086140 9.360047 12.086140 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 4141 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 28.000000 28.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000004231 00000 n +0000004254 00000 n +0000004427 00000 n +0000004501 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +4560 +%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/house.fill.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/house.fill.imageset/Contents.json new file mode 100644 index 000000000..b12998cbd --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/house.fill.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Home-fill.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/house.fill.imageset/Home-fill.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/house.fill.imageset/Home-fill.pdf new file mode 100644 index 000000000..e8e156e31 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/house.fill.imageset/Home-fill.pdf @@ -0,0 +1,85 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 4.000000 2.835022 cm +0.000000 0.000000 0.000000 scn +8.592125 21.667089 m +9.414732 22.326954 10.585270 22.326952 11.407875 21.667089 c +19.157869 15.450343 l +19.690231 15.023304 19.999994 14.377716 19.999994 13.695241 c +19.999994 2.414970 l +19.999994 1.172331 18.992638 0.164970 17.749996 0.164970 c +15.250000 0.164970 l +14.007360 0.164970 13.000000 1.172327 13.000000 2.414968 c +13.000000 8.914963 l +13.000000 9.329177 12.664214 9.664963 12.250000 9.664963 c +7.750000 9.664963 l +7.335785 9.664963 7.000000 9.329177 7.000000 8.914964 c +7.000000 2.414970 l +7.000000 1.172331 5.992641 0.164970 4.750000 0.164970 c +2.250000 0.164970 l +1.007360 0.164970 0.000000 1.172327 0.000000 2.414968 c +0.000000 13.695240 l +0.000000 14.377715 0.309763 15.023304 0.842125 15.450343 c +8.592125 21.667089 l +h +f +n +Q + +endstream +endobj + +3 0 obj + 862 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 28.000000 28.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000000952 00000 n +0000000974 00000 n +0000001147 00000 n +0000001221 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1280 +%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/house.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/house.imageset/Contents.json new file mode 100644 index 000000000..968281c21 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/house.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Home.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/house.imageset/Home.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/house.imageset/Home.pdf new file mode 100644 index 000000000..1b5772ddd --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/house.imageset/Home.pdf @@ -0,0 +1,105 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 4.000000 2.834961 cm +0.000000 0.000000 0.000000 scn +8.591907 21.668686 m +9.414594 22.328781 10.585405 22.328781 11.408092 21.668686 c +19.158089 15.450356 l +19.690319 15.023312 19.999994 14.377806 19.999994 13.695429 c +19.999994 2.415045 l +19.999994 1.172405 18.992638 0.165045 17.749996 0.165045 c +14.750000 0.165045 l +13.507360 0.165045 12.500000 1.172403 12.500000 2.415045 c +12.500000 8.415037 l +12.500000 8.829250 12.164213 9.165037 11.750000 9.165037 c +8.250000 9.165037 l +7.835786 9.165037 7.500000 8.829250 7.500000 8.415037 c +7.500000 2.415045 l +7.500000 1.172403 6.492640 0.165045 5.250000 0.165045 c +2.250000 0.165045 l +1.007360 0.165045 0.000000 1.172401 0.000000 2.415043 c +0.000000 13.695428 l +0.000000 14.377805 0.309674 15.023312 0.841907 15.450356 c +8.591907 21.668686 l +h +10.469363 20.498734 m +10.195135 20.718765 9.804865 20.718765 9.530635 20.498734 c +1.780635 14.280403 l +1.603225 14.138056 1.500000 13.922887 1.500000 13.695428 c +1.500000 2.415043 l +1.500000 2.000832 1.835786 1.665045 2.250000 1.665045 c +5.250000 1.665045 l +5.664214 1.665045 6.000000 2.000832 6.000000 2.415045 c +6.000000 8.415037 l +6.000000 9.657678 7.007360 10.665037 8.250000 10.665037 c +11.750000 10.665037 l +12.992640 10.665037 14.000000 9.657678 14.000000 8.415037 c +14.000000 2.415045 l +14.000000 2.000832 14.335787 1.665045 14.750000 1.665045 c +17.749996 1.665045 l +18.164207 1.665045 18.499994 2.000830 18.499994 2.415045 c +18.499994 13.695429 l +18.499994 13.922888 18.396770 14.138056 18.219358 14.280403 c +10.469363 20.498734 l +h +f +n +Q + +endstream +endobj + +3 0 obj + 1604 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 28.000000 28.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001694 00000 n +0000001717 00000 n +0000001890 00000 n +0000001964 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2023 +%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/magnifyingglass.fill.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/magnifyingglass.fill.imageset/Contents.json new file mode 100644 index 000000000..6986c762a --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/magnifyingglass.fill.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Search-Fill.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/magnifyingglass.fill.imageset/Search-Fill.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/magnifyingglass.fill.imageset/Search-Fill.pdf new file mode 100644 index 000000000..26e5a4576 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/magnifyingglass.fill.imageset/Search-Fill.pdf @@ -0,0 +1,83 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 2.000000 1.855835 cm +0.000000 0.000000 0.000000 scn +9.500000 24.144165 m +14.746705 24.144165 19.000000 19.890869 19.000000 14.644165 c +19.000000 12.562231 18.330292 10.636717 17.194551 9.071299 c +23.560659 2.704824 l +24.146446 2.119038 24.146446 1.169292 23.560659 0.583506 c +23.011484 0.034330 22.142424 0.000008 21.553263 0.480536 c +21.439341 0.583506 l +15.072866 6.949614 l +13.507448 5.813873 11.581934 5.144165 9.500000 5.144165 c +4.253295 5.144165 0.000000 9.397460 0.000000 14.644165 c +0.000000 19.890869 4.253295 24.144165 9.500000 24.144165 c +h +9.500000 21.144165 m +5.910149 21.144165 3.000000 18.234016 3.000000 14.644165 c +3.000000 11.054314 5.910149 8.144165 9.500000 8.144165 c +13.089851 8.144165 16.000000 11.054314 16.000000 14.644165 c +16.000000 18.234016 13.089851 21.144165 9.500000 21.144165 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 887 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 28.000000 28.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000000977 00000 n +0000000999 00000 n +0000001172 00000 n +0000001246 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1305 +%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/magnifyingglass.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/magnifyingglass.imageset/Contents.json new file mode 100644 index 000000000..e4ab2267b --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/magnifyingglass.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Search.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/magnifyingglass.imageset/Search.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/magnifyingglass.imageset/Search.pdf new file mode 100644 index 000000000..7bd1758c7 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/magnifyingglass.imageset/Search.pdf @@ -0,0 +1,83 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 2.750000 2.679199 cm +0.000000 0.000000 0.000000 scn +8.750000 22.570801 m +13.582491 22.570801 17.500000 18.653292 17.500000 13.820801 c +17.500000 11.674633 16.727327 9.708933 15.444990 8.186705 c +22.280331 1.351131 l +22.573223 1.058239 22.573223 0.583363 22.280331 0.290470 c +22.014065 0.024204 21.597401 -0.000004 21.303789 0.217852 c +21.219669 0.290470 l +14.384096 7.125811 l +12.861868 5.843473 10.896168 5.070801 8.750000 5.070801 c +3.917509 5.070801 0.000000 8.988310 0.000000 13.820801 c +0.000000 18.653292 3.917509 22.570801 8.750000 22.570801 c +h +8.750000 21.070801 m +4.745935 21.070801 1.500000 17.824865 1.500000 13.820801 c +1.500000 9.816736 4.745935 6.570801 8.750000 6.570801 c +12.754065 6.570801 16.000000 9.816736 16.000000 13.820801 c +16.000000 17.824865 12.754065 21.070801 8.750000 21.070801 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 885 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 28.000000 28.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000000975 00000 n +0000000997 00000 n +0000001170 00000 n +0000001244 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1303 +%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/square.and.pencil.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/square.and.pencil.imageset/Contents.json new file mode 100644 index 000000000..18bb2201a --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/square.and.pencil.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "square.and.pencil.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/square.and.pencil.imageset/square.and.pencil.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/square.and.pencil.imageset/square.and.pencil.pdf new file mode 100644 index 000000000..8cb4a15c9 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/ObjectsAndTools/square.and.pencil.imageset/square.and.pencil.pdf @@ -0,0 +1,93 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 3.000000 2.926788 cm +0.000000 0.000000 0.000000 scn +18.780287 17.792883 m +19.073179 18.085777 19.073177 18.560650 18.780285 18.853542 c +18.487391 19.146435 18.012516 19.146435 17.719624 18.853540 c +7.719669 8.853540 l +7.250000 7.323212 l +8.780332 7.792883 l +18.780287 17.792883 l +h +3.249999 18.073212 m +1.455072 18.073212 0.000000 16.618137 0.000000 14.823212 c +0.000000 3.323212 l +0.000000 1.528286 1.455075 0.073212 3.250000 0.073212 c +14.750000 0.073212 l +16.544926 0.073212 18.000000 1.528286 18.000000 3.323212 c +18.000000 11.323212 l +18.000000 11.737425 17.664213 12.073212 17.250000 12.073212 c +16.835787 12.073212 16.500000 11.737425 16.500000 11.323212 c +16.500000 3.323212 l +16.500000 2.356712 15.716498 1.573212 14.750000 1.573212 c +3.250000 1.573212 l +2.283502 1.573212 1.500000 2.356712 1.500000 3.323212 c +1.500000 14.823212 l +1.500000 15.789711 2.283501 16.573212 3.249999 16.573212 c +11.249994 16.573212 l +11.664207 16.573212 11.999994 16.908998 11.999994 17.323212 c +11.999994 17.737425 11.664207 18.073212 11.249994 18.073212 c +3.249999 18.073212 l +h +f +n +Q + +endstream +endobj + +3 0 obj + 1142 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001232 00000 n +0000001255 00000 n +0000001428 00000 n +0000001502 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1561 +%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Discovery/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Discovery/Contents.json new file mode 100644 index 000000000..6e965652d --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Discovery/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Discovery/profile.card.background.colorset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Discovery/profile.card.background.colorset/Contents.json new file mode 100644 index 000000000..1fc21a878 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Discovery/profile.card.background.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "251", + "green" : "250", + "red" : "249" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "4", + "green" : "5", + "red" : "6" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift b/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift index 26e54900f..b594f9209 100644 --- a/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift +++ b/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift @@ -24,6 +24,7 @@ public enum Asset { public enum Arrow { public static let `repeat` = ImageAsset(name: "Arrow/repeat") public static let repeatSmall = ImageAsset(name: "Arrow/repeat.small") + public static let squareAndArrowUp = ImageAsset(name: "Arrow/square.and.arrow.up") } public enum Asset { public static let email = ImageAsset(name: "Asset/email") @@ -99,10 +100,23 @@ public enum Asset { public static let faceSmilingAdaptive = ImageAsset(name: "Human/face.smiling.adaptive") } public enum ObjectsAndTools { + public static let bellBadgeFill = ImageAsset(name: "ObjectsAndTools/bell.badge.fill") + public static let bellBadge = ImageAsset(name: "ObjectsAndTools/bell.badge") + public static let bellFill = ImageAsset(name: "ObjectsAndTools/bell.fill") + public static let bell = ImageAsset(name: "ObjectsAndTools/bell") + public static let gear = ImageAsset(name: "ObjectsAndTools/gear") + public static let houseFill = ImageAsset(name: "ObjectsAndTools/house.fill") + public static let house = ImageAsset(name: "ObjectsAndTools/house") + public static let magnifyingglassFill = ImageAsset(name: "ObjectsAndTools/magnifyingglass.fill") + public static let magnifyingglass = ImageAsset(name: "ObjectsAndTools/magnifyingglass") + public static let squareAndPencil = ImageAsset(name: "ObjectsAndTools/square.and.pencil") public static let starFill = ImageAsset(name: "ObjectsAndTools/star.fill") public static let star = ImageAsset(name: "ObjectsAndTools/star") } public enum Scene { + public enum Discovery { + public static let profileCardBackground = ColorAsset(name: "Scene/Discovery/profile.card.background") + } public enum Onboarding { public static let avatarPlaceholder = ImageAsset(name: "Scene/Onboarding/avatar.placeholder") public static let background = ColorAsset(name: "Scene/Onboarding/background") diff --git a/AppShared/UserDefaults.swift b/MastodonSDK/Sources/MastodonCommon/Extension/UserDefaults.swift similarity index 58% rename from AppShared/UserDefaults.swift rename to MastodonSDK/Sources/MastodonCommon/Extension/UserDefaults.swift index 31f8e27ef..53d298d0f 100644 --- a/AppShared/UserDefaults.swift +++ b/MastodonSDK/Sources/MastodonCommon/Extension/UserDefaults.swift @@ -1,14 +1,12 @@ // // UserDefaults.swift -// AppShared +// // -// Created by MainasuK Cirno on 2021-4-27. +// Created by MainasuK on 2022-4-29. // -import UIKit -import MastodonCommon +import Foundation extension UserDefaults { public static let shared = UserDefaults(suiteName: AppName.groupID)! } - diff --git a/Mastodon/Preference/AppearancePreference.swift b/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Appearance.swift similarity index 81% rename from Mastodon/Preference/AppearancePreference.swift rename to MastodonSDK/Sources/MastodonCommon/Preference/Preference+Appearance.swift index 034bf9654..713c926bc 100644 --- a/Mastodon/Preference/AppearancePreference.swift +++ b/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Appearance.swift @@ -9,7 +9,7 @@ import UIKit extension UserDefaults { - @objc dynamic var customUserInterfaceStyle: UIUserInterfaceStyle { + @objc public dynamic var customUserInterfaceStyle: UIUserInterfaceStyle { get { register(defaults: [#function: UIUserInterfaceStyle.unspecified.rawValue]) return UIUserInterfaceStyle(rawValue: integer(forKey: #function)) ?? .unspecified @@ -17,7 +17,7 @@ extension UserDefaults { set { self[#function] = newValue.rawValue } } - @objc dynamic var preferredStaticAvatar: Bool { + @objc public dynamic var preferredStaticAvatar: Bool { get { // default false // without set register to profile timeline performance @@ -26,7 +26,7 @@ extension UserDefaults { set { self[#function] = newValue } } - @objc dynamic var preferredStaticEmoji: Bool { + @objc public dynamic var preferredStaticEmoji: Bool { get { // default false // without set register to profile timeline performance diff --git a/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Discovery.swift b/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Discovery.swift new file mode 100644 index 000000000..0c6a9c544 --- /dev/null +++ b/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Discovery.swift @@ -0,0 +1,19 @@ +// +// Preference+Discovery.swift +// +// +// Created by MainasuK on 2022-4-19. +// + +import Foundation + +extension UserDefaults { + + @objc public dynamic var discoveryIntroBannerNeedsHidden: Bool { + get { + return bool(forKey: #function) + } + set { self[#function] = newValue } + } + +} diff --git a/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Theme.swift b/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Theme.swift new file mode 100644 index 000000000..a87a8da75 --- /dev/null +++ b/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Theme.swift @@ -0,0 +1,26 @@ +// +// Preference+Theme.swift +// +// +// Created by MainasuK on 2022-4-13. +// + +import UIKit +import MastodonExtension + +public enum ThemeName: String, CaseIterable { + case system + case mastodon +} + +extension UserDefaults { + + @objc public dynamic var currentThemeNameRawValue: String { + get { + register(defaults: [#function: ThemeName.mastodon.rawValue]) + return string(forKey: #function) ?? ThemeName.mastodon.rawValue + } + set { self[#function] = newValue } + } + +} diff --git a/MastodonSDK/Sources/MastodonExtension/UIView.swift b/MastodonSDK/Sources/MastodonExtension/UIView.swift index 5466c464d..f96d1618a 100644 --- a/MastodonSDK/Sources/MastodonExtension/UIView.swift +++ b/MastodonSDK/Sources/MastodonExtension/UIView.swift @@ -12,3 +12,37 @@ extension UIView { return UIScreen.main.scale != UIScreen.main.nativeScale } } + +extension UIView { + @discardableResult + public func applyCornerRadius(radius: CGFloat) -> Self { + layer.masksToBounds = true + layer.cornerRadius = radius + layer.cornerCurve = .continuous + return self + } + + @discardableResult + public func applyShadow( + color: UIColor, + alpha: Float, + x: CGFloat, + y: CGFloat, + blur: CGFloat, + spread: CGFloat = 0 + ) -> Self { + layer.masksToBounds = false + layer.shadowColor = color.cgColor + layer.shadowOpacity = alpha + layer.shadowOffset = CGSize(width: x, height: y) + layer.shadowRadius = blur / 2.0 + if spread == 0 { + layer.shadowPath = nil + } else { + let dx = -spread + let rect = bounds.insetBy(dx: dx, dy: dx) + layer.shadowPath = UIBezierPath(rect: rect).cgPath + } + return self + } +} diff --git a/MastodonSDK/Sources/MastodonExtension/UInt64.swift b/MastodonSDK/Sources/MastodonExtension/UInt64.swift new file mode 100644 index 000000000..03dfbaca7 --- /dev/null +++ b/MastodonSDK/Sources/MastodonExtension/UInt64.swift @@ -0,0 +1,12 @@ +// +// UInt64.swift +// +// +// Created by MainasuK on 2022-4-13. +// + +import Foundation + +extension UInt64 { + public static let second: UInt64 = 1_000_000_000 +} diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index 15d786efb..b5403a142 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -270,6 +270,8 @@ public enum L10n { public static let contentWarning = L10n.tr("Localizable", "Common.Controls.Status.ContentWarning") /// Tap anywhere to reveal public static let mediaContentWarning = L10n.tr("Localizable", "Common.Controls.Status.MediaContentWarning") + /// Sensitive Content + public static let sensitiveContent = L10n.tr("Localizable", "Common.Controls.Status.SensitiveContent") /// Show Post public static let showPost = L10n.tr("Localizable", "Common.Controls.Status.ShowPost") /// Show user profile @@ -543,6 +545,22 @@ public enum L10n { public static let title = L10n.tr("Localizable", "Scene.ConfirmEmail.OpenEmailApp.Title") } } + public enum Discovery { + /// These are the posts gaining traction in your corner of Mastodon. + public static let intro = L10n.tr("Localizable", "Scene.Discovery.Intro") + public enum Tabs { + /// Community + public static let community = L10n.tr("Localizable", "Scene.Discovery.Tabs.Community") + /// For You + public static let forYou = L10n.tr("Localizable", "Scene.Discovery.Tabs.ForYou") + /// Hashtags + public static let hashtags = L10n.tr("Localizable", "Scene.Discovery.Tabs.Hashtags") + /// News + public static let news = L10n.tr("Localizable", "Scene.Discovery.Tabs.News") + /// Posts + public static let posts = L10n.tr("Localizable", "Scene.Discovery.Tabs.Posts") + } + } public enum Favorite { /// Your Favorites public static let title = L10n.tr("Localizable", "Scene.Favorite.Title") @@ -567,6 +585,12 @@ public enum L10n { public static let published = L10n.tr("Localizable", "Scene.HomeTimeline.NavigationBarState.Published") /// Publishing post... public static let publishing = L10n.tr("Localizable", "Scene.HomeTimeline.NavigationBarState.Publishing") + public enum Accessibility { + /// Tap to scroll to top and tap again to previous location + public static let logoHint = L10n.tr("Localizable", "Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint") + /// Logo Button + public static let logoLabel = L10n.tr("Localizable", "Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel") + } } } public enum Notification { @@ -822,6 +846,86 @@ public enum L10n { } /// Report public static let titleReport = L10n.tr("Localizable", "Scene.Report.TitleReport") + public enum StepFinal { + /// Block %@ + public static func blockUser(_ p1: Any) -> String { + return L10n.tr("Localizable", "Scene.Report.StepFinal.BlockUser", String(describing: p1)) + } + /// Don’t want to see this? + public static let dontWantToSeeThis = L10n.tr("Localizable", "Scene.Report.StepFinal.DontWantToSeeThis") + /// Mute %@ + public static func muteUser(_ p1: Any) -> String { + return L10n.tr("Localizable", "Scene.Report.StepFinal.MuteUser", String(describing: p1)) + } + /// They will no longer be able to follow or see your posts, but they can see if they’ve been blocked. + public static let theyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked = L10n.tr("Localizable", "Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked") + /// Unfollow + public static let unfollow = L10n.tr("Localizable", "Scene.Report.StepFinal.Unfollow") + /// Unfollowed + public static let unfollowed = L10n.tr("Localizable", "Scene.Report.StepFinal.Unfollowed") + /// Unfollow %@ + public static func unfollowUser(_ p1: Any) -> String { + return L10n.tr("Localizable", "Scene.Report.StepFinal.UnfollowUser", String(describing: p1)) + } + /// When you see something you don’t like on Mastodon, you can remove the person from your experience. + public static let whenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience = L10n.tr("Localizable", "Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience.") + /// You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted. + public static let youWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted = L10n.tr("Localizable", "Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted") + } + public enum StepFour { + /// Is there anything else we should know? + public static let isThereAnythingElseWeShouldKnow = L10n.tr("Localizable", "Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow") + /// Step 4 of 4 + public static let step4Of4 = L10n.tr("Localizable", "Scene.Report.StepFour.Step4Of4") + } + public enum StepOne { + /// I don’t like it + public static let iDontLikeIt = L10n.tr("Localizable", "Scene.Report.StepOne.IDontLikeIt") + /// It is not something you want to see + public static let itIsNotSomethingYouWantToSee = L10n.tr("Localizable", "Scene.Report.StepOne.ItIsNotSomethingYouWantToSee") + /// It’s something else + public static let itsSomethingElse = L10n.tr("Localizable", "Scene.Report.StepOne.ItsSomethingElse") + /// It’s spam + public static let itsSpam = L10n.tr("Localizable", "Scene.Report.StepOne.ItsSpam") + /// It violates server rules + public static let itViolatesServerRules = L10n.tr("Localizable", "Scene.Report.StepOne.ItViolatesServerRules") + /// Malicious links, fake engagement, or repetetive replies + public static let maliciousLinksFakeEngagementOrRepetetiveReplies = L10n.tr("Localizable", "Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies") + /// Select the best match + public static let selectTheBestMatch = L10n.tr("Localizable", "Scene.Report.StepOne.SelectTheBestMatch") + /// Step 1 of 4 + public static let step1Of4 = L10n.tr("Localizable", "Scene.Report.StepOne.Step1Of4") + /// The issue does not fit into other categories + public static let theIssueDoesNotFitIntoOtherCategories = L10n.tr("Localizable", "Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories") + /// What's wrong with this account? + public static let whatsWrongWithThisAccount = L10n.tr("Localizable", "Scene.Report.StepOne.WhatsWrongWithThisAccount") + /// What's wrong with this post? + public static let whatsWrongWithThisPost = L10n.tr("Localizable", "Scene.Report.StepOne.WhatsWrongWithThisPost") + /// What's wrong with %@? + public static func whatsWrongWithThisUsername(_ p1: Any) -> String { + return L10n.tr("Localizable", "Scene.Report.StepOne.WhatsWrongWithThisUsername", String(describing: p1)) + } + /// You are aware that it breaks specific rules + public static let youAreAwareThatItBreaksSpecificRules = L10n.tr("Localizable", "Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules") + } + public enum StepThree { + /// Are there any posts that back up this report? + public static let areThereAnyPostsThatBackUpThisReport = L10n.tr("Localizable", "Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport") + /// Select all that apply + public static let selectAllThatApply = L10n.tr("Localizable", "Scene.Report.StepThree.SelectAllThatApply") + /// Step 3 of 4 + public static let step3Of4 = L10n.tr("Localizable", "Scene.Report.StepThree.Step3Of4") + } + public enum StepTwo { + /// I just don’t like it + public static let iJustDonTLikeIt = L10n.tr("Localizable", "Scene.Report.StepTwo.IJustDon’tLikeIt") + /// Select all that apply + public static let selectAllThatApply = L10n.tr("Localizable", "Scene.Report.StepTwo.SelectAllThatApply") + /// Step 2 of 4 + public static let step2Of4 = L10n.tr("Localizable", "Scene.Report.StepTwo.Step2Of4") + /// Which rules are being violated? + public static let whichRulesAreBeingViolated = L10n.tr("Localizable", "Scene.Report.StepTwo.WhichRulesAreBeingViolated") + } } public enum Search { /// Search @@ -876,11 +980,11 @@ public enum L10n { } } public enum ServerPicker { - /// Pick a community based on your interests, region, or a general purpose one. + /// Pick a server based on your interests, region, or a general purpose one. public static let subtitle = L10n.tr("Localizable", "Scene.ServerPicker.Subtitle") - /// Pick a community based on your interests, region, or a general purpose one. Each community is operated by an entirely independent organization or individual. + /// Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual. public static let subtitleExtend = L10n.tr("Localizable", "Scene.ServerPicker.SubtitleExtend") - /// Mastodon is made of users in different communities. + /// Mastodon is made of users in different servers. public static let title = L10n.tr("Localizable", "Scene.ServerPicker.Title") public enum Button { /// See Less @@ -927,8 +1031,10 @@ public enum L10n { public static let noResults = L10n.tr("Localizable", "Scene.ServerPicker.EmptyState.NoResults") } public enum Input { - /// Search communities + /// Search servers public static let placeholder = L10n.tr("Localizable", "Scene.ServerPicker.Input.Placeholder") + /// Search communities or enter URL + public static let searchServersOrEnterUrl = L10n.tr("Localizable", "Scene.ServerPicker.Input.SearchServersOrEnterUrl") } public enum Label { /// CATEGORY diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.strings index f26554fe5..c49b9f9d6 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.strings @@ -58,7 +58,7 @@ "Common.Controls.Actions.SignIn" = "تسجيل الدخول"; "Common.Controls.Actions.SignUp" = "إنشاء حِساب"; "Common.Controls.Actions.Skip" = "تخطي"; -"Common.Controls.Actions.TakePhoto" = "التقاط صورة"; +"Common.Controls.Actions.TakePhoto" = "اِلتِقاطُ صُورَة"; "Common.Controls.Actions.TryAgain" = "المُحاولة مرة أُخرى"; "Common.Controls.Actions.UnblockDomain" = "رفع الحظر عن %@"; "Common.Controls.Friendship.Block" = "حظر"; @@ -108,6 +108,7 @@ "Common.Controls.Status.MediaContentWarning" = "اُنقُر لِلكَشف"; "Common.Controls.Status.Poll.Closed" = "انتهى"; "Common.Controls.Status.Poll.Vote" = "صَوِّت"; +"Common.Controls.Status.SensitiveContent" = "مُحتَوى حَسَّاس"; "Common.Controls.Status.ShowPost" = "أظْهِر مَنشور"; "Common.Controls.Status.ShowUserProfile" = "أظْهِر المِلَفَّ التَّعريفِيَّ لِلمُستَخدِم"; "Common.Controls.Status.Tag.Email" = "بَريدٌ إلِكتُرُونِيّ"; @@ -125,7 +126,7 @@ "Common.Controls.Status.Visibility.Unlisted" = "يُمكِنُ لِلجَميعِ رُؤيَةُ هَذَا المَنشورِ وَلكِنَّهُ لَا يُعرَضُ فِي الخَطِّ الزَمنيّ العام."; "Common.Controls.Tabs.Home" = "الرَّئِيسَة"; "Common.Controls.Tabs.Notification" = "الإشعارات"; -"Common.Controls.Tabs.Profile" = "الملف التعريفي"; +"Common.Controls.Tabs.Profile" = "المِلَفُّ التَّعريفِيّ"; "Common.Controls.Tabs.Search" = "البَحث"; "Common.Controls.Timeline.Filtered" = "مُصفَّى"; "Common.Controls.Timeline.Header.BlockedWarning" = "لا يُمكِنُكَ عَرض الملف التَعريفي لهذا المُستخدِم @@ -163,7 +164,7 @@ "Scene.Compose.Attachment.Video" = "مقطع مرئي"; "Scene.Compose.AutoComplete.SpaceToAdd" = "انقر على مساحة لإضافتِها"; "Scene.Compose.ComposeAction" = "نَشر"; -"Scene.Compose.ContentInputPlaceholder" = "أخبِرنا بِما يَجُولُ فِي ذِهنَك"; +"Scene.Compose.ContentInputPlaceholder" = "عَبِّر عَمَّ يَجُولُ فِي ذِهنِك"; "Scene.Compose.ContentWarning.Placeholder" = "اكتب تَحذيرًا دَقيقًا هُنا..."; "Scene.Compose.Keyboard.AppendAttachmentEntry" = "إضافة مُرفَق - %@"; "Scene.Compose.Keyboard.DiscardPost" = "تجاهُل المنشور"; @@ -171,9 +172,9 @@ "Scene.Compose.Keyboard.SelectVisibilityEntry" = "اختر مدى الظهور - %@"; "Scene.Compose.Keyboard.ToggleContentWarning" = "تبديل تحذير المُحتَوى"; "Scene.Compose.Keyboard.TogglePoll" = "تبديل الاستطلاع"; -"Scene.Compose.MediaSelection.Browse" = "تصفح"; -"Scene.Compose.MediaSelection.Camera" = "إلتقاط صورة"; -"Scene.Compose.MediaSelection.PhotoLibrary" = "مكتبة الصور"; +"Scene.Compose.MediaSelection.Browse" = "تَصَفَّح"; +"Scene.Compose.MediaSelection.Camera" = "اِلتِقاطُ صُورَة"; +"Scene.Compose.MediaSelection.PhotoLibrary" = "مَكتَبَةُ الصُّوَر"; "Scene.Compose.Poll.DurationTime" = "المُدَّة: %@"; "Scene.Compose.Poll.OneDay" = "يومٌ واحِد"; "Scene.Compose.Poll.OneHour" = "ساعةٌ واحدة"; @@ -201,9 +202,17 @@ "Scene.ConfirmEmail.Subtitle" = "لقد أرسلنا للتو بريد إلكتروني إلى %@، انقر على الرابط لتأكيد حسابك."; "Scene.ConfirmEmail.Title" = "شيءٌ أخير."; +"Scene.Discovery.Intro" = "هَذِهِ هِيَ المَنشُوراتُ الَّتي تَكْتَسِبُ شَعبِيَّةً فِي الرُّكنِ الخاصِّ بِكَ مِن مَاستُودون."; +"Scene.Discovery.Tabs.Community" = "المُجتَمَع"; +"Scene.Discovery.Tabs.ForYou" = "لَك"; +"Scene.Discovery.Tabs.Hashtags" = "وُسُوم"; +"Scene.Discovery.Tabs.News" = "أخبار"; +"Scene.Discovery.Tabs.Posts" = "مَنشُورات"; "Scene.Favorite.Title" = "مُفضَّلَتُك"; "Scene.Follower.Footer" = "لا يُمكِن عَرض المُتابِعين مِنَ الخوادم الأُخرى."; "Scene.Following.Footer" = "لا يُمكِن عَرض المُتابَعات مِنَ الخوادم الأُخرى."; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "اُنقُر لِلتمريرِ لأعلى واُنقُر مَرّةً أُخرَى لِلذَّهابِ إلَى المَوقِعِ السَّابِق"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "ُّزِرُّ الشِّعار"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "إظهار منشورات جديدة"; "Scene.HomeTimeline.NavigationBarState.Offline" = "غَير مُتَّصِل"; "Scene.HomeTimeline.NavigationBarState.Published" = "تمَّ النَّشر!"; @@ -284,8 +293,39 @@ "Scene.Report.Reported" = "مُبْلَغٌ عَنه"; "Scene.Report.Send" = "إرسال البلاغ"; "Scene.Report.SkipToSend" = "إرسال بدون تعليق"; -"Scene.Report.Step1" = "الخطوة الأولى مِن أصل اثنتين"; -"Scene.Report.Step2" = "الخطوة الثانية والأخيرة"; +"Scene.Report.Step1" = "الخطوة 1 مِن أصل 2"; +"Scene.Report.Step2" = "الخطوة 2 مِن أصل 2"; +"Scene.Report.StepFinal.BlockUser" = "حَظرُ %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "ألَا تُريدُ رُؤيَةَ هَذَا؟"; +"Scene.Report.StepFinal.MuteUser" = "كَتمُ %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "لَن يَتمكَّنَ بَعدَ الآنِ مِن مُتابَعَةِ مَنشوراتِكَ أو رُؤيَتِها، وَلكِن يُمكِنَهُ مَعرِفَةُ مَا إذا حُظَرِت عَنه."; +"Scene.Report.StepFinal.Unfollow" = "إلغاءُ المُتابَعَة"; +"Scene.Report.StepFinal.UnfollowUser" = "إلغاءُ مُتابَعَةِ %@"; +"Scene.Report.StepFinal.Unfollowed" = "أُلغِيَت المُتابَعَة"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "عِندما تَرى شيئًا لَا يُعجِبُكَ عَلَى مَاستودُون، يُمكِنُكَ إزالَةُ الشَّخصِ مِن تَجرِبَتِك."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "لَن تَرى مُشارَكاتِهِ أو إعادَاتِ تَدوينَهِ فِي تغذيَتِكَ الرَّئيسَة. لَن يَعرِفَ أنَّهُ قَد كُتِمَ أيضًا."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "هَل هُناكَ شَيءٌ آخَرَ يَجِبُ أن نَعلَمَ بِه؟"; +"Scene.Report.StepFour.Step4Of4" = "الخطوة 4 مِن أصل 4"; +"Scene.Report.StepOne.IDontLikeIt" = "لا يُعجِبُني"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "إنَّهُ ليسَ شيئًا تُريدُ رُؤيَتَه"; +"Scene.Report.StepOne.ItViolatesServerRules" = "يَنتَهِكُ قَواعِدَ الخادِم"; +"Scene.Report.StepOne.ItsSomethingElse" = "إنَّهُ شَيءٌ آخَر"; +"Scene.Report.StepOne.ItsSpam" = "إنَّهُ غَيرٌ مَرغوبٍ فيه"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "رَوابِطٌ ضَارَّة، اِرتِباطاتٌ مُزيَّفَة أو رُدودٌ مُتَكَرِّرَة"; +"Scene.Report.StepOne.SelectTheBestMatch" = "اِختَر أفضلَ تَطابُق"; +"Scene.Report.StepOne.Step1Of4" = "الخطوة 1 مِن أصل 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "المُشكِلَةُ لَا تَتَناسَبُ مَعَ الفِئاتِ الأُخرَى"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "ما المُشكِلَةُ فِي هَذَا الحِساب؟"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "ما المُشكِلَةُ فِي هَذَا المَنشُور؟"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "ما المُشكِلَة مَعَ %@؟"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "أنتَ مُدِركٌ لِانتِهاكِهِ قَواعِدًا مُحَدَّدَة"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "هَل هُناكَ أيُّ مَنشُوراتٍ أُخرَى تَتَوافَقُ مَعَ هَذَا التَّقرير؟"; +"Scene.Report.StepThree.SelectAllThatApply" = "اِختَر كُلَّ ما يَنطَبِق"; +"Scene.Report.StepThree.Step3Of4" = "الخطوة 3 مِن أصل 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "أنا فَقَط لا يُعجِبُني"; +"Scene.Report.StepTwo.SelectAllThatApply" = "اِختَر كُلَّ ما يَنطَبِق"; +"Scene.Report.StepTwo.Step2Of4" = "الخطوة 2 مِن أصل 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "مَا هِيَ القَواعِدُ الَّتي تُنتَهَك؟"; "Scene.Report.TextPlaceholder" = "اكتب أو الصق تعليقات إضافيَّة"; "Scene.Report.Title" = "الإبلاغ عن %@"; "Scene.Report.TitleReport" = "إبلاغ"; @@ -326,6 +366,7 @@ "Scene.ServerPicker.EmptyState.FindingServers" = "يجري إيجاد خوادم متوفِّرَة..."; "Scene.ServerPicker.EmptyState.NoResults" = "لا توجد نتائج"; "Scene.ServerPicker.Input.Placeholder" = "اِبحَث عن خادِم أو انضم إلى آخر خاص بك..."; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "اِبحث عَن مُجتَمَعَات أو أدخِل عُنوانَ URL"; "Scene.ServerPicker.Label.Category" = "الفئة"; "Scene.ServerPicker.Label.Language" = "اللُّغة"; "Scene.ServerPicker.Label.Users" = "مُستَخدِم"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.stringsdict index 32782f1c0..dddba5132 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.stringsdict @@ -109,7 +109,7 @@ NSStringFormatValueTypeKey ld zero - لا منشور + لا مَنشورات one منشورٌ واحِد two @@ -447,13 +447,13 @@ zero تتبقى لَحظة one - تتبقى ثانية + تتبقى ثانية واحِدة two - تتبقى ثانيتين + تتبقى ثانيتان few تتبقى %ld ثوان many - تتبقى %ld ثانيةً + تتبقى %ld ثانية other تتبقى %ld ثانية @@ -471,15 +471,15 @@ zero مُنذُ لَحظة one - مُنذُ سنة + مُنذُ %ldع two - مُنذُ سنتين + مُنذُ %ldع few - مُنذُ %ld سنين + مُنذُ %ldع many - مُنذُ %ld سنةً + مُنذُ %ldع other - مُنذُ %ld سنة + مُنذُ %ldع
date.month.ago.abbr @@ -495,15 +495,15 @@ zero مُنذُ لَحظة one - مُنذُ شهر + مُنذُ %ldش two - مُنذُ شهرين + مُنذُ %ldش few - مُنذُ %ld أشهُر + مُنذُ %ldش many - مُنذُ %ld شهرًا + مُنذُ %ldش other - مُنذُ %ld شهر + مُنذُ %ldش date.day.ago.abbr @@ -519,15 +519,15 @@ zero مُنذُ لَحظة one - مُنذُ يوم + مُنذُ %ldي two - مُنذُ يومين + مُنذُ %ldي few - مُنذُ %ld أيام + مُنذُ %ldي many - مُنذُ %ld يومًا + مُنذُ %ldي other - مُنذُ %ld يوم + مُنذُ %ldي date.hour.ago.abbr @@ -543,15 +543,15 @@ zero مُنذُ لَحظة one - مُنذُ ساعة + مُنذُ %ldس two - مُنذُ ساعتين + مُنذُ %ldس few - مُنذُ %ld ساعات + مُنذُ %ldس many - مُنذُ %ld ساعةًَ + مُنذُ %ldس other - مُنذُ %ld ساعة + مُنذُ %ldس date.minute.ago.abbr @@ -567,15 +567,15 @@ zero مُنذُ لَحظة one - مُنذُ دقيقة + مُنذُ %ldد two - مُنذُ دقيقتان + مُنذُ %ldد few - مُنذُ %ld دقائق + مُنذُ %ldد many - مُنذُ %ld دقيقةً + مُنذُ %ldد other - مُنذُ %ld دقيقة + مُنذُ %ldد date.second.ago.abbr @@ -591,15 +591,15 @@ zero مُنذُ لَحظة one - مُنذُ ثانية + مُنذُ %ldث two - مُنذُ ثانيتين + مُنذُ %ldث few - مُنذُ %ld ثوان + مُنذُ %ldث many - مُنذُ %ld ثانية + مُنذُ %ldث other - مُنذُ %ld ثانية + مُنذُ %ldث diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ca.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/ca.lproj/Localizable.strings index 0cb63f34b..502b26729 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ca.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ca.lproj/Localizable.strings @@ -5,7 +5,7 @@ "Common.Alerts.Common.PleaseTryAgain" = "Si us plau intenta-ho de nou."; "Common.Alerts.Common.PleaseTryAgainLater" = "Si us plau, prova-ho més tard."; "Common.Alerts.DeletePost.Message" = "Estàs segur que vols suprimir aquesta publicació?"; -"Common.Alerts.DeletePost.Title" = "Estàs segur que vols suprimir aquesta publicació?"; +"Common.Alerts.DeletePost.Title" = "Esborrar Publicació"; "Common.Alerts.DiscardPostContent.Message" = "Confirma per a descartar el contingut de la publicació composta."; "Common.Alerts.DiscardPostContent.Title" = "Descarta l'esborrany"; "Common.Alerts.EditProfileFailure.Message" = "No es pot editar el perfil. Si us plau torna-ho a provar."; @@ -16,7 +16,7 @@ Comprova la teva connexió a Internet."; "Common.Alerts.PublishPostFailure.Title" = "Error de Publicació"; "Common.Alerts.SavePhotoFailure.Message" = "Activa el permís d'accés a la biblioteca de fotos per desar-la."; -"Common.Alerts.SavePhotoFailure.Title" = "Desa l'Error de la Foto"; +"Common.Alerts.SavePhotoFailure.Title" = "Error al Desar la Foto"; "Common.Alerts.ServerError.Title" = "Error del Servidor"; "Common.Alerts.SignOut.Confirm" = "Tancar Sessió"; "Common.Alerts.SignOut.Message" = "Estàs segur que vols tancar la sessió?"; @@ -36,7 +36,7 @@ Comprova la teva connexió a Internet."; "Common.Controls.Actions.Discard" = "Descarta"; "Common.Controls.Actions.Done" = "Fet"; "Common.Controls.Actions.Edit" = "Edita"; -"Common.Controls.Actions.FindPeople" = "Busca persones per seguir"; +"Common.Controls.Actions.FindPeople" = "Busca persones a seguir"; "Common.Controls.Actions.ManuallySearch" = "Cerca manualment a canvi"; "Common.Controls.Actions.Next" = "Següent"; "Common.Controls.Actions.Ok" = "D'acord"; @@ -81,18 +81,18 @@ Comprova la teva connexió a Internet."; "Common.Controls.Keyboard.Common.OpenSettings" = "Obre la configuració"; "Common.Controls.Keyboard.Common.ShowFavorites" = "Mostra els Favorits"; "Common.Controls.Keyboard.Common.SwitchToTab" = "Canviar a %@"; -"Common.Controls.Keyboard.SegmentedControl.NextSection" = "Secció següent"; -"Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "Secció anterior"; +"Common.Controls.Keyboard.SegmentedControl.NextSection" = "Secció Següent"; +"Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "Secció Anterior"; "Common.Controls.Keyboard.Timeline.NextStatus" = "Publicació següent"; -"Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "Obre el perfil de l'autor"; -"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "Obre el perfil del impulsor"; +"Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "Obre el Perfil de l'Autor"; +"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "Obre el Perfil del Impulsor"; "Common.Controls.Keyboard.Timeline.OpenStatus" = "Obre la publicació"; "Common.Controls.Keyboard.Timeline.PreviewImage" = "Vista prèvia de l'Imatge"; "Common.Controls.Keyboard.Timeline.PreviousStatus" = "Publicació anterior"; -"Common.Controls.Keyboard.Timeline.ReplyStatus" = "Respon a la publicació"; +"Common.Controls.Keyboard.Timeline.ReplyStatus" = "Respon a la Publicació"; "Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "Commuta l'Avís de Contingut"; -"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Commuta el Favorit de la publicació"; -"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Commuta l'impuls de la publicació"; +"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Commuta el Favorit de la Publicació"; +"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Commuta l'Impuls de la Publicació"; "Common.Controls.Status.Actions.Favorite" = "Favorit"; "Common.Controls.Status.Actions.Hide" = "Amaga"; "Common.Controls.Status.Actions.Menu" = "Menú"; @@ -105,10 +105,11 @@ Comprova la teva connexió a Internet."; "Common.Controls.Status.Actions.Unfavorite" = "Desfer Favorit"; "Common.Controls.Status.Actions.Unreblog" = "Desfer l'impuls"; "Common.Controls.Status.ContentWarning" = "Advertència de Contingut"; -"Common.Controls.Status.MediaContentWarning" = "Toca qualsevol lloc per mostrar"; +"Common.Controls.Status.MediaContentWarning" = "Toca qualsevol lloc per a mostrar"; "Common.Controls.Status.Poll.Closed" = "Finalitzada"; "Common.Controls.Status.Poll.Vote" = "Vota"; -"Common.Controls.Status.ShowPost" = "Mostra la publicació"; +"Common.Controls.Status.SensitiveContent" = "Contingut sensible"; +"Common.Controls.Status.ShowPost" = "Mostra la Publicació"; "Common.Controls.Status.ShowUserProfile" = "Mostra el perfil de l'usuari"; "Common.Controls.Status.Tag.Email" = "Correu electrònic"; "Common.Controls.Status.Tag.Emoji" = "Emoji"; @@ -129,7 +130,7 @@ Comprova la teva connexió a Internet."; "Common.Controls.Tabs.Search" = "Cerca"; "Common.Controls.Timeline.Filtered" = "Filtrat"; "Common.Controls.Timeline.Header.BlockedWarning" = "No pots veure el perfil d'aquest usuari - fins que et desbloquegi."; +fins que et desbloquegi."; "Common.Controls.Timeline.Header.BlockingWarning" = "No pots veure el perfil d'aquest usuari fins que el desbloquegis. El teu perfil els sembla així."; @@ -141,8 +142,8 @@ El teu perfil els sembla així."; fins que el desbloquegis. El teu perfil els sembla així."; "Common.Controls.Timeline.Header.UserSuspendedWarning" = "El compte de %@ ha estat suspès."; -"Common.Controls.Timeline.Loader.LoadMissingPosts" = "Carrega les publicacions que falten"; -"Common.Controls.Timeline.Loader.LoadingMissingPosts" = "Carregant les publicacions que falten..."; +"Common.Controls.Timeline.Loader.LoadMissingPosts" = "Carrega les publicacions faltants"; +"Common.Controls.Timeline.Loader.LoadingMissingPosts" = "Carregant les publicacions faltants..."; "Common.Controls.Timeline.Loader.ShowMoreReplies" = "Mostra més respostes"; "Common.Controls.Timeline.Timestamp.Now" = "Ara"; "Scene.AccountList.AddAccount" = "Afegir compte"; @@ -198,12 +199,19 @@ carregat a Mastodon."; "Scene.ConfirmEmail.OpenEmailApp.Mail" = "Correu electrònic"; "Scene.ConfirmEmail.OpenEmailApp.OpenEmailClient" = "Obre el Client de Correu electrònic"; "Scene.ConfirmEmail.OpenEmailApp.Title" = "Comprova la teva safata d'entrada."; -"Scene.ConfirmEmail.Subtitle" = "Acabem d'enviar un correu electrònic a %@, -toca l'enllaç per a confirmar el teu compte."; +"Scene.ConfirmEmail.Subtitle" = "Toca l'enllaç del correu electrònic que t'hem enviat per a confirmar el teu compte."; "Scene.ConfirmEmail.Title" = "Una última cosa."; +"Scene.Discovery.Intro" = "Aquestes son les publicacions que criden l'atenció en el teu racó de Mastodon."; +"Scene.Discovery.Tabs.Community" = "Comunitat"; +"Scene.Discovery.Tabs.ForYou" = "Per a tu"; +"Scene.Discovery.Tabs.Hashtags" = "Etiquetes"; +"Scene.Discovery.Tabs.News" = "Notícies"; +"Scene.Discovery.Tabs.Posts" = "Publicacions"; "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.Accessibility.LogoHint" = "Toca per desplaçar-te cap a dalt i torna a toca de nou per tornar a la ubicació anterior"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Botó de logotip"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Veure noves publicacions"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Fora de línia"; "Scene.HomeTimeline.NavigationBarState.Published" = "Publicat!"; @@ -263,7 +271,7 @@ toca l'enllaç per a confirmar el teu compte."; "Scene.Register.Error.Reason.Unreachable" = "%@ sembla que no existeix"; "Scene.Register.Error.Special.EmailInvalid" = "Aquesta no és una adreça de correu electrònic vàlida"; "Scene.Register.Error.Special.PasswordTooShort" = "La contrasenya és massa curta (ha de tenir 8 caràcters com a mínim)"; -"Scene.Register.Error.Special.UsernameInvalid" = "El nom d'usuari només ha de contenir caràcters alfanumèrics i guions baixos"; +"Scene.Register.Error.Special.UsernameInvalid" = "El nom d'usuari ha de contenir només caràcters alfanumèrics i guions baixos"; "Scene.Register.Error.Special.UsernameTooLong" = "El nom d'usuari és massa llarg (no pot ser més llarg de 30 caràcters)"; "Scene.Register.Input.Avatar.Delete" = "Suprimeix"; "Scene.Register.Input.DisplayName.Placeholder" = "nom visible"; @@ -272,12 +280,12 @@ toca l'enllaç per a confirmar el teu compte."; "Scene.Register.Input.Password.Accessibility.Checked" = "verificat"; "Scene.Register.Input.Password.Accessibility.Unchecked" = "no verificat"; "Scene.Register.Input.Password.CharacterLimit" = "8 caràcters"; -"Scene.Register.Input.Password.Hint" = "La teva contrasenya ha de tenir com a mínim buit caràcters"; +"Scene.Register.Input.Password.Hint" = "La teva contrasenya ha de tenir com a mínim vuit caràcters"; "Scene.Register.Input.Password.Placeholder" = "contrasenya"; "Scene.Register.Input.Password.Require" = "La teva contrasenya com a mínim necessita:"; "Scene.Register.Input.Username.DuplicatePrompt" = "Aquest nom d'usuari ja està en ús."; "Scene.Register.Input.Username.Placeholder" = "nom d'usuari"; -"Scene.Register.Title" = "Parla'ns de tu."; +"Scene.Register.Title" = "Anem a configurar-te a %@"; "Scene.Report.Content1" = "Hi ha alguna altre publicació que vulguis afegir a l'informe?"; "Scene.Report.Content2" = "Hi ha alguna cosa que els moderadors hagin de saber sobre aquest informe?"; "Scene.Report.ReportSentTitle" = "Gràcies per informar, ho investigarem."; @@ -286,6 +294,37 @@ toca l'enllaç per a confirmar el teu compte."; "Scene.Report.SkipToSend" = "Envia sense comentaris"; "Scene.Report.Step1" = "Pas 1 de 2"; "Scene.Report.Step2" = "Pas 2 de 2"; +"Scene.Report.StepFinal.BlockUser" = "Bloca %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "No vols veure això?"; +"Scene.Report.StepFinal.MuteUser" = "Silencia %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "Ja no podran seguir ni veure les teves publicacions, però poden veure si han estat bloquejats."; +"Scene.Report.StepFinal.Unfollow" = "Deixa de seguir"; +"Scene.Report.StepFinal.UnfollowUser" = "Deixa de seguir %@"; +"Scene.Report.StepFinal.Unfollowed" = "S'ha deixat de seguir"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "Quan veus alguna cosa que no t'agrada a Mastodon, pots eliminar la persona de la vostra experiència."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "No veuràs les seves publicacions o impulsos a la teva línia de temps personal. No sabran que han estat silenciats."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Hi ha res més que hauríem de saber?"; +"Scene.Report.StepFour.Step4Of4" = "Pas 4 de 4"; +"Scene.Report.StepOne.IDontLikeIt" = "No m'agrada"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "No és una cosa que vulguis veure"; +"Scene.Report.StepOne.ItViolatesServerRules" = "Infringeix les normes del servidor"; +"Scene.Report.StepOne.ItsSomethingElse" = "És una altra cosa"; +"Scene.Report.StepOne.ItsSpam" = "És contingut brossa"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Enllaços maliciosos, compromís falç o respostes repetitives"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Selecciona la millor coincidència"; +"Scene.Report.StepOne.Step1Of4" = "Pas 1 de 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "El problema no encaixa en altres categories"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "Quin és el problema amb aquest compte?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "Quin és el problema amb aquesta publicació?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "Quin és el problema amb %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "Ets conscient que incompleix normes específiques"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Hi ha alguna publicació que recolzi aquest informe?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Selecciona tot el que correspongui"; +"Scene.Report.StepThree.Step3Of4" = "Pas 3 de 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "Simplement no m'agrada"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Selecciona tot el que correspongui"; +"Scene.Report.StepTwo.Step2Of4" = "Pas 2 de 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Quines normes s'estan infringint?"; "Scene.Report.TextPlaceholder" = "Escriu o enganxa comentaris addicionals"; "Scene.Report.Title" = "Informa sobre %@"; "Scene.Report.TitleReport" = "Informe"; @@ -322,21 +361,21 @@ toca l'enllaç per a confirmar el teu compte."; "Scene.ServerPicker.Button.Category.Tech" = "tecnologia"; "Scene.ServerPicker.Button.SeeLess" = "Veure Menys"; "Scene.ServerPicker.Button.SeeMore" = "Veure Més"; -"Scene.ServerPicker.EmptyState.BadNetwork" = "S'ha produït un error en carregar les dades. Comprova la teva connexió a Internet."; +"Scene.ServerPicker.EmptyState.BadNetwork" = "Alguna cosa no ha anat bé en carregar les dades. Comprova la teva connexió a Internet."; "Scene.ServerPicker.EmptyState.FindingServers" = "Cercant els servidors disponibles..."; "Scene.ServerPicker.EmptyState.NoResults" = "No hi ha resultats"; -"Scene.ServerPicker.Input.Placeholder" = "Troba un servidor o uneix-te al teu..."; +"Scene.ServerPicker.Input.Placeholder" = "Cerca servidors"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Cerca comunitats o introdueix l'URL"; "Scene.ServerPicker.Label.Category" = "CATEGORIA"; "Scene.ServerPicker.Label.Language" = "LLENGUATGE"; "Scene.ServerPicker.Label.Users" = "USUARIS"; "Scene.ServerPicker.Subtitle" = "Tria una comunitat segons els teus interessos, regió o una de propòsit general."; "Scene.ServerPicker.SubtitleExtend" = "Tria una comunitat segons els teus interessos, regió o una de propòsit general. Cada comunitat és operada per una organització totalment independent o individualment."; -"Scene.ServerPicker.Title" = "Tria un servidor, -qualsevol servidor."; +"Scene.ServerPicker.Title" = "Mastodon està fet d'usuaris en diferents comunitats."; "Scene.ServerRules.Button.Confirm" = "Hi estic d'acord"; "Scene.ServerRules.PrivacyPolicy" = "política de privadesa"; "Scene.ServerRules.Prompt" = "Al continuar, estàs subjecte als termes de servei i a la política de privacitat de %@."; -"Scene.ServerRules.Subtitle" = "Aquestes regles estan establertes per els administradors de %@."; +"Scene.ServerRules.Subtitle" = "Aquestes regles estan establertes i aplicades per els moderadors de %@."; "Scene.ServerRules.TermsOfService" = "termes del servei"; "Scene.ServerRules.Title" = "Algunes regles bàsiques."; "Scene.Settings.Footer.MastodonDescription" = "Mastodon és un programari de codi obert. Pots informar de problemes a GitHub a %@ (%@)"; @@ -362,7 +401,7 @@ qualsevol servidor."; "Scene.Settings.Section.Notifications.Trigger.Anyone" = "algú"; "Scene.Settings.Section.Notifications.Trigger.Follow" = "a qualsevol que segueixi"; "Scene.Settings.Section.Notifications.Trigger.Follower" = "un seguidor"; -"Scene.Settings.Section.Notifications.Trigger.Noone" = "algú"; +"Scene.Settings.Section.Notifications.Trigger.Noone" = "ningú"; "Scene.Settings.Section.Notifications.Trigger.Title" = "Notifica'm quan"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Desactiva avatars animats"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Desactiva emojis animats"; @@ -375,7 +414,7 @@ qualsevol servidor."; "Scene.Settings.Section.SpicyZone.Title" = "La Zona Picant"; "Scene.Settings.Title" = "Configuració"; "Scene.SuggestionAccount.FollowExplain" = "Quan segueixes algú, veuràs les seves publicacions a Inici."; -"Scene.SuggestionAccount.Title" = "Cerca Persones per Seguir"; +"Scene.SuggestionAccount.Title" = "Cerca Persones a Seguir"; "Scene.Thread.BackTitle" = "Publicació"; "Scene.Thread.Title" = "Publicació de %@"; "Scene.Welcome.GetStarted" = "Comença"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ckb.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/ckb.lproj/Localizable.strings new file mode 100644 index 000000000..89ba6315d --- /dev/null +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ckb.lproj/Localizable.strings @@ -0,0 +1,425 @@ +"Common.Alerts.BlockDomain.BlockEntireDomain" = "هەموو ڕاژەکارەکە ئاستەنگ بکە"; +"Common.Alerts.BlockDomain.Title" = "دڵنیایت دەتەوێت تەواوی %@ ئاستەنگ بکەیت؟ لە زۆر بارەکاندا ئاستەنگکردنی بچووک باشترە. ئەگەر وا بکەیت، لەو ڕاژەکارەوە هیچ شتێک نابینیت و هەموو شوێنکەوتووەکانت لەوێوە لادەبرێن."; +"Common.Alerts.CleanCache.Message" = "سەرکەوتووانە بیرگەی %@ پاک کرایەوە."; +"Common.Alerts.CleanCache.Title" = "بیرگە پاک بکەوە"; +"Common.Alerts.Common.PleaseTryAgain" = "تکایە دووبارە هەوڵ بدەوە."; +"Common.Alerts.Common.PleaseTryAgainLater" = "تکایە دواتر هەوڵ بدەوە."; +"Common.Alerts.DeletePost.Message" = "دڵنیایت دەتەوێت ئەم پۆستە بسڕیتەوە؟"; +"Common.Alerts.DeletePost.Title" = "بیسڕەوە"; +"Common.Alerts.DiscardPostContent.Message" = "دڵنیا ببەوە بۆ وازهێنان لە ناوەڕۆکەت."; +"Common.Alerts.DiscardPostContent.Title" = "ڕەشنووس هەڵمەگرە"; +"Common.Alerts.EditProfileFailure.Message" = "ناتوانرێت دەستکاریی پرۆفایل بکرێت. تکایە دووبارە هەوڵ بدەوە."; +"Common.Alerts.EditProfileFailure.Title" = "نەتوانرا دەستکاریی پرۆفایل بکرێت"; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.MoreThanOneVideo" = "ناتوانیت زیاتر لە یەک ڤیدیۆی پێوە بلکێنیت."; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.VideoAttachWithPhoto" = "ناتوانیت ڤیدیۆ بۆ پۆستێک زیاد بکەیت کە وێنەی تێدایە."; +"Common.Alerts.PublishPostFailure.Message" = "نەتوانرا پۆستەکە بکرێت. +تکایە لە بەردەستبوونی هێڵی ئینتەرنێت دڵنیا بە."; +"Common.Alerts.PublishPostFailure.Title" = "نەتوانرا پۆستەکە بکرێت"; +"Common.Alerts.SavePhotoFailure.Message" = "تکایە ڕێ بە ماستۆدۆن بدە تاوەکو بتوانێت وێنەکە هەڵبگرێت."; +"Common.Alerts.SavePhotoFailure.Title" = "نەتوانرا وێنەکە هەڵبگیرێت"; +"Common.Alerts.ServerError.Title" = "هەڵەی ڕاژەکار"; +"Common.Alerts.SignOut.Confirm" = "دەربچۆ"; +"Common.Alerts.SignOut.Message" = "دڵنیایت دەتەوێت دەربچیت؟"; +"Common.Alerts.SignOut.Title" = "دەربچۆ"; +"Common.Alerts.SignUpFailure.Title" = "تۆمارکردنەکە سەرکەوتوو نەبوو"; +"Common.Alerts.VoteFailure.PollEnded" = "دەنگدانەکە کۆتایی هاتووە"; +"Common.Alerts.VoteFailure.Title" = "نەتوانرا دەنگ بدرێت"; +"Common.Controls.Actions.Add" = "زیادی بکە"; +"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" = "لەبەری بگرەوە"; +"Common.Controls.Actions.Delete" = "بیسڕەوە"; +"Common.Controls.Actions.Discard" = "وازی لێ بێنە"; +"Common.Controls.Actions.Done" = "تەواو"; +"Common.Controls.Actions.Edit" = "دەستکاری"; +"Common.Controls.Actions.FindPeople" = "خەڵک بدۆزەوە"; +"Common.Controls.Actions.ManuallySearch" = "خۆت بگەڕێ"; +"Common.Controls.Actions.Next" = "دواتر"; +"Common.Controls.Actions.Ok" = "باشە"; +"Common.Controls.Actions.Open" = "بیکەوە"; +"Common.Controls.Actions.OpenInBrowser" = "لە وێبگەڕ بیکەوە"; +"Common.Controls.Actions.OpenInSafari" = "لە Safari بیکەوە"; +"Common.Controls.Actions.Preview" = "پێشبینین"; +"Common.Controls.Actions.Previous" = "پێشتر"; +"Common.Controls.Actions.Remove" = "لایبە"; +"Common.Controls.Actions.Reply" = "وەڵامی بدەوە"; +"Common.Controls.Actions.ReportUser" = "سکاڵا لە %@ بکە"; +"Common.Controls.Actions.Save" = "هەڵی بگرە"; +"Common.Controls.Actions.SavePhoto" = "هەڵی بگرە"; +"Common.Controls.Actions.SeeMore" = "زیاتر ببینە"; +"Common.Controls.Actions.Settings" = "رێکخستنەکان"; +"Common.Controls.Actions.Share" = "هاوبەشی بکە"; +"Common.Controls.Actions.SharePost" = "هاوبەشی بکە"; +"Common.Controls.Actions.ShareUser" = "%@ هاوبەش بکە"; +"Common.Controls.Actions.SignIn" = "بچۆ ژوورەوە"; +"Common.Controls.Actions.SignUp" = "خۆت تۆمار بکە"; +"Common.Controls.Actions.Skip" = "بیپەڕێنە"; +"Common.Controls.Actions.TakePhoto" = "وێنە بگرە"; +"Common.Controls.Actions.TryAgain" = "هەوڵ بدەوە"; +"Common.Controls.Actions.UnblockDomain" = "%@ ئاستەنگ مەکە"; +"Common.Controls.Friendship.Block" = "ئاستەنگی بکە"; +"Common.Controls.Friendship.BlockDomain" = "%@ ئاستەنگ بکە"; +"Common.Controls.Friendship.BlockUser" = "%@ ئاستەنگ بکە"; +"Common.Controls.Friendship.Blocked" = "ئاستەنگ کراوە"; +"Common.Controls.Friendship.EditInfo" = "دەستکاری"; +"Common.Controls.Friendship.Follow" = "شوێنی بکەوە"; +"Common.Controls.Friendship.Following" = "شوێنی دەکەویت"; +"Common.Controls.Friendship.Mute" = "بێدەنگی بکە"; +"Common.Controls.Friendship.MuteUser" = "%@ بێدەنگە"; +"Common.Controls.Friendship.Muted" = "بێدەنگ کراوە"; +"Common.Controls.Friendship.Pending" = "داوات کردووە"; +"Common.Controls.Friendship.Request" = "داوای لێ بکە"; +"Common.Controls.Friendship.Unblock" = "ئاستەنگی مەکە"; +"Common.Controls.Friendship.UnblockUser" = "%@ ئاستەنگ مەکە"; +"Common.Controls.Friendship.Unmute" = "بێدەنگی مەکە"; +"Common.Controls.Friendship.UnmuteUser" = "%@ بێدەنگ مەکە"; +"Common.Controls.Keyboard.Common.ComposeNewPost" = "پۆستێکی نوێ بکە"; +"Common.Controls.Keyboard.Common.OpenSettings" = "ڕێکخستنەکان بکەوە"; +"Common.Controls.Keyboard.Common.ShowFavorites" = "بەدڵبووەکان ببینە"; +"Common.Controls.Keyboard.Common.SwitchToTab" = "بڕۆ بۆ %@"; +"Common.Controls.Keyboard.SegmentedControl.NextSection" = "دەستنیشانکراوی دواتر"; +"Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "بەشی پێشتر"; +"Common.Controls.Keyboard.Timeline.NextStatus" = "پۆستی دواتر"; +"Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "پرۆفایلەکەی بکەوە"; +"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "پرۆفایلەکەی بکەوە"; +"Common.Controls.Keyboard.Timeline.OpenStatus" = "بیکەوە"; +"Common.Controls.Keyboard.Timeline.PreviewImage" = "بیبینە"; +"Common.Controls.Keyboard.Timeline.PreviousStatus" = "پۆستی پێشتر"; +"Common.Controls.Keyboard.Timeline.ReplyStatus" = "وەڵامی بدەوە"; +"Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "ئاگاداریی ناوەڕۆک نیشان بدە"; +"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "بەدڵبوونی پۆست"; +"Common.Controls.Keyboard.Timeline.ToggleReblog" = "پۆستکردنەوەی پۆست"; +"Common.Controls.Status.Actions.Favorite" = "بەدڵمە"; +"Common.Controls.Status.Actions.Hide" = "بیشارەوە"; +"Common.Controls.Status.Actions.Menu" = "پێڕست"; +"Common.Controls.Status.Actions.Reblog" = "پۆستی بکەوە"; +"Common.Controls.Status.Actions.Reply" = "وەڵامی بدەوە"; +"Common.Controls.Status.Actions.ShowGif" = "گیفەکە نیشان بدە"; +"Common.Controls.Status.Actions.ShowImage" = "وێنەکە نیشان بدە"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "ڤیدیۆکە لێ بدە"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "دەستی پیا بنێ و بیگرە بۆ نیشاندانی پێڕستەکە"; +"Common.Controls.Status.Actions.Unfavorite" = "بەدڵبوونەکە بگەڕێنەوە"; +"Common.Controls.Status.Actions.Unreblog" = "پۆستکردنەکە بگەڕێنەوە"; +"Common.Controls.Status.ContentWarning" = "ئاگاداریی ناوەڕۆک"; +"Common.Controls.Status.MediaContentWarning" = "دەستی پیا بنێ بۆ نیشاندانی"; +"Common.Controls.Status.Poll.Closed" = "داخراوە"; +"Common.Controls.Status.Poll.Vote" = "دەنگ بدە"; +"Common.Controls.Status.SensitiveContent" = "ناوەڕۆکی هەستیار"; +"Common.Controls.Status.ShowPost" = "پۆستەکە نیشان بدە"; +"Common.Controls.Status.ShowUserProfile" = "پرۆفایلەکەی نیشان بدە"; +"Common.Controls.Status.Tag.Email" = "ئیمێڵ"; +"Common.Controls.Status.Tag.Emoji" = "ئیمۆجی"; +"Common.Controls.Status.Tag.Hashtag" = "هاشتاگ"; +"Common.Controls.Status.Tag.Link" = "بەستەر"; +"Common.Controls.Status.Tag.Mention" = "ئاماژە"; +"Common.Controls.Status.Tag.Url" = "بەستەر"; +"Common.Controls.Status.TapToReveal" = "دەستی پیا بنێ بۆ نیشاندانی"; +"Common.Controls.Status.UserReblogged" = "%@ پۆست کرایەوە"; +"Common.Controls.Status.UserRepliedTo" = "لە وەڵامدا بۆ %@"; +"Common.Controls.Status.Visibility.Direct" = "تەنیا بەکارهێنەرە ئاماژە پێکراوەکە دەتوانێت ئەم پۆستە ببینێت."; +"Common.Controls.Status.Visibility.Private" = "تەنیا شوێنکەوتووەکانی دەتوانن ئەم پۆستە ببینن."; +"Common.Controls.Status.Visibility.PrivateFromMe" = "تەنیا شوێنکەوتووەکانم دەتوانن ئەم پۆستە ببینن."; +"Common.Controls.Status.Visibility.Unlisted" = "هەرکەسێک دەتوانێت ئەم پۆستە ببینێت بەڵام ناچێتە بەردەمیان."; +"Common.Controls.Tabs.Home" = "ماڵەوە"; +"Common.Controls.Tabs.Notification" = "ئاگادارکردنەوەکان"; +"Common.Controls.Tabs.Profile" = "پرۆفایل"; +"Common.Controls.Tabs.Search" = "بگەڕێ"; +"Common.Controls.Timeline.Filtered" = "پاڵێوراو"; +"Common.Controls.Timeline.Header.BlockedWarning" = "ناتوانیت پرۆفایلی ئەم بەکارهێنەرە ببینیت +تا ئەو کاتەی ئاستەنگەکەت لادەبات."; +"Common.Controls.Timeline.Header.BlockingWarning" = "ناتوانیت پرۆفایلی ئەم بەکارهێنەرە ببینیت +هەتا ئاستەنگەکەیان لادەبەیت. +پرۆفایلەکەت ئاوها لایان دەردەکەوێت."; +"Common.Controls.Timeline.Header.NoStatusFound" = "هیچ پۆستێک نەدۆزرایەوە"; +"Common.Controls.Timeline.Header.SuspendedWarning" = "ئەم بەکارهێنەرە ڕاگیراوە."; +"Common.Controls.Timeline.Header.UserBlockedWarning" = "ناتوانیت پرۆفایلی %@ ببینیت +تا ئەو کاتەی ئاستەنگەکەت لادەبات."; +"Common.Controls.Timeline.Header.UserBlockingWarning" = "ناتوانیت پرۆفایلی %@ ببینیت +هەتا ئاستەنگەکەیان لادەبەیت. +پرۆفایلەکەت ئاوها لایان دەردەکەوێت."; +"Common.Controls.Timeline.Header.UserSuspendedWarning" = "هەژماری %@ ڕاگیراوە."; +"Common.Controls.Timeline.Loader.LoadMissingPosts" = "پۆستە ماوەکان بار بکە"; +"Common.Controls.Timeline.Loader.LoadingMissingPosts" = "پۆستە ماوەکان بار دەکرێن..."; +"Common.Controls.Timeline.Loader.ShowMoreReplies" = "وەڵامی زیاتر نیشان بدە"; +"Common.Controls.Timeline.Timestamp.Now" = "ئێستا"; +"Scene.AccountList.AddAccount" = "هەژمارێک زیاد بکە"; +"Scene.AccountList.DismissAccountSwitcher" = "پێڕستی هەژمارەکان دابخە"; +"Scene.AccountList.TabBarHint" = "هەژماری ئێستا: %@. دوو جا دەستی پیا بنێ بۆ کردنەوەی پێڕستی هەژمارەکان."; +"Scene.Compose.Accessibility.AppendAttachment" = "پێوەکراوی پێوە بکە"; +"Scene.Compose.Accessibility.AppendPoll" = "دەنگدان زیاد بکە"; +"Scene.Compose.Accessibility.CustomEmojiPicker" = "هەڵبژێری ئیمۆجی"; +"Scene.Compose.Accessibility.DisableContentWarning" = "ئاگاداریی ناوەڕۆک ناچالاک بکە"; +"Scene.Compose.Accessibility.EnableContentWarning" = "ئاگاداریی ناوەڕۆک چالاک بکە"; +"Scene.Compose.Accessibility.PostVisibilityMenu" = "پێڕستی شێوازی دەرکەوتنی پۆست"; +"Scene.Compose.Accessibility.RemovePoll" = "دانگدانەکە لابە"; +"Scene.Compose.Attachment.AttachmentBroken" = "ئەم %@ـە تێک چووە و ناتوانیت بەرزی بکەیتەوە."; +"Scene.Compose.Attachment.DescriptionPhoto" = "وێنەکەت بۆ نابیناکان باس بکە..."; +"Scene.Compose.Attachment.DescriptionVideo" = "ڤیدیۆکەت بۆ نابیناکان باس بکە..."; +"Scene.Compose.Attachment.Photo" = "وێنە"; +"Scene.Compose.Attachment.Video" = "ڤیدیۆ"; +"Scene.Compose.AutoComplete.SpaceToAdd" = "بۆشایی دابنێ بۆ زیادکردن"; +"Scene.Compose.ComposeAction" = "بڵاوی بکەوە"; +"Scene.Compose.ContentInputPlaceholder" = "دەتەوێت چی پۆست بکەیت؟"; +"Scene.Compose.ContentWarning.Placeholder" = "ئاگادارییەکەت لێرە بنووسە..."; +"Scene.Compose.Keyboard.AppendAttachmentEntry" = "پێوەکراوی پێوە بکە - %@"; +"Scene.Compose.Keyboard.DiscardPost" = "پۆستەکە هەڵوەشێنەوە"; +"Scene.Compose.Keyboard.PublishPost" = "پۆستە بڵاو بکەوە"; +"Scene.Compose.Keyboard.SelectVisibilityEntry" = "شێوازی دەرکەوتن هەڵبژێرە - %@"; +"Scene.Compose.Keyboard.ToggleContentWarning" = "ئاگاداریی ناوەڕۆک نیشان بدە"; +"Scene.Compose.Keyboard.TogglePoll" = "دەنگدانەکە نیشان بدە"; +"Scene.Compose.MediaSelection.Browse" = "بگەڕێ"; +"Scene.Compose.MediaSelection.Camera" = "وێنە بگرە"; +"Scene.Compose.MediaSelection.PhotoLibrary" = "وێنەکان"; +"Scene.Compose.Poll.DurationTime" = "کات:‌ %@"; +"Scene.Compose.Poll.OneDay" = "1 ڕۆژ"; +"Scene.Compose.Poll.OneHour" = "1 کاتژمێر"; +"Scene.Compose.Poll.OptionNumber" = "بژاردەی %ld"; +"Scene.Compose.Poll.SevenDays" = "7 ڕۆژ"; +"Scene.Compose.Poll.SixHours" = "6 کاتژمێر"; +"Scene.Compose.Poll.ThirtyMinutes" = "30 خولەک"; +"Scene.Compose.Poll.ThreeDays" = "3 ڕۆژ"; +"Scene.Compose.ReplyingToUser" = "لە وەڵامدا بۆ %@"; +"Scene.Compose.Title.NewPost" = "پۆستی نوێ"; +"Scene.Compose.Title.NewReply" = "وەڵامی نوێ"; +"Scene.Compose.Visibility.Direct" = "ئەوانەی ئاماژەیان پێ دەکەم"; +"Scene.Compose.Visibility.Private" = "تەنیا شوێنکەوتووان"; +"Scene.Compose.Visibility.Public" = "گشتی"; +"Scene.Compose.Visibility.Unlisted" = "پێشنیارنەکراو"; +"Scene.ConfirmEmail.Button.OpenEmailApp" = "بەرنامەی ئیمێڵەکەت بکەوە"; +"Scene.ConfirmEmail.Button.Resend" = "بینێرەوە"; +"Scene.ConfirmEmail.DontReceiveEmail.Description" = "دڵنیا بە لەوەی ئیمێڵەکەت دروستە و هەموو بوخچەکانت بگەڕێ."; +"Scene.ConfirmEmail.DontReceiveEmail.ResendEmail" = "ئیمێڵەکە بنێرەوە"; +"Scene.ConfirmEmail.DontReceiveEmail.Title" = "ئیمێڵەکەت ببینە"; +"Scene.ConfirmEmail.OpenEmailApp.Description" = "ئیمێڵێکمان بۆ ناردیت. هەموو بوخچەکانت ببینە."; +"Scene.ConfirmEmail.OpenEmailApp.Mail" = "Mail"; +"Scene.ConfirmEmail.OpenEmailApp.OpenEmailClient" = "بەرنامەی ئیمێڵەکەت بکەوە"; +"Scene.ConfirmEmail.OpenEmailApp.Title" = "ئیمێڵەکانت ببینە."; +"Scene.ConfirmEmail.Subtitle" = "بۆ پشتڕاستکردنەوەی هەژمارەکەت ئەو بەستەرە بکەوە کە بە ئیمێڵ بۆمان ناردوویت."; +"Scene.ConfirmEmail.Title" = "کۆتا شت."; +"Scene.Discovery.Intro" = "پۆست هەیە سەرنجیان لەسەرە لە گۆشەکەی تۆ."; +"Scene.Discovery.Tabs.Community" = "Community"; +"Scene.Discovery.Tabs.ForYou" = "بۆ تۆ"; +"Scene.Discovery.Tabs.Hashtags" = "هاشتاگەکان"; +"Scene.Discovery.Tabs.News" = "هەواڵەکان"; +"Scene.Discovery.Tabs.Posts" = "پۆستەکان"; +"Scene.Favorite.Title" = "بەدڵبووەکانت"; +"Scene.Follower.Footer" = "شوێنکەوتووەکانی لە ڕاژەکارەکانی ترەوە نیشان نادرێت."; +"Scene.Following.Footer" = "شوێنکەوتنەکانی بۆ هەژماری ڕاژەکارەکانی تر نیشان نادرێت."; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; +"Scene.HomeTimeline.NavigationBarState.NewPosts" = "پۆستە نوێکان ببینە"; +"Scene.HomeTimeline.NavigationBarState.Offline" = "دەرهێڵ"; +"Scene.HomeTimeline.NavigationBarState.Published" = "بڵاوکرایەوە!"; +"Scene.HomeTimeline.NavigationBarState.Publishing" = "پۆستەکە بڵاو دەکرێتەوە..."; +"Scene.HomeTimeline.Title" = "ماڵەوە"; +"Scene.Notification.Keyobard.ShowEverything" = "هەموو شتێک نیشان بدە"; +"Scene.Notification.Keyobard.ShowMentions" = "ئاماژەکان نیشان بدە"; +"Scene.Notification.NotificationDescription.FavoritedYourPost" = "پۆستەکەتی بەدڵ بوو"; +"Scene.Notification.NotificationDescription.FollowedYou" = "شوێنت کەوت"; +"Scene.Notification.NotificationDescription.MentionedYou" = "ئاماژەی پێت کرد"; +"Scene.Notification.NotificationDescription.PollHasEnded" = "دەنگدانەکە کۆتایی هات"; +"Scene.Notification.NotificationDescription.RebloggedYourPost" = "پۆستەکەتی پۆست کردەوە"; +"Scene.Notification.NotificationDescription.RequestToFollowYou" = "داواکاری بۆ شوێنکەوتنت"; +"Scene.Notification.Title.Everything" = "هەمووی"; +"Scene.Notification.Title.Mentions" = "ئاماژەکان"; +"Scene.Preview.Keyboard.ClosePreview" = "پێشبینینەکە دابخە"; +"Scene.Preview.Keyboard.ShowNext" = "هی دواتر نیشان بدە"; +"Scene.Preview.Keyboard.ShowPrevious" = "هی پێشتر نیشان بدە"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "دوو جار دەستی پیا بنێ بۆ کردنەوەی لیستەکە"; +"Scene.Profile.Accessibility.EditAvatarImage" = "دەستکاریی وێنەکە بکە"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "وێنەکە نیشان بدە"; +"Scene.Profile.Accessibility.ShowBannerImage" = "وێنەکەی پشتەوە نیشان بدە"; +"Scene.Profile.Dashboard.Followers" = "شوێنکەوتوو"; +"Scene.Profile.Dashboard.Following" = "شوێنکەوتن"; +"Scene.Profile.Dashboard.Posts" = "پۆستەکان"; +"Scene.Profile.Fields.AddRow" = "ڕیز زیاد بکە"; +"Scene.Profile.Fields.Placeholder.Content" = "ناوەڕۆک"; +"Scene.Profile.Fields.Placeholder.Label" = "ناونیشان"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "دڵنیا ببەوە بۆ ئاستەنگکردنی %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "ئاستەنگی بکە"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "دڵیا ببەوە بۆ بێدەنگکردنی %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "بێدەنگی بکە"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "دڵنیا ببەوە بۆ لابردنی ئاستەنگی %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "ئاستەنگی مەکە"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "دڵنیا ببەوە بۆ بێدەنگنەکردنی %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Title" = "بێدەنگی مەکە"; +"Scene.Profile.SegmentedControl.About" = "دەربارە"; +"Scene.Profile.SegmentedControl.Media" = "میدیا"; +"Scene.Profile.SegmentedControl.Posts" = "پۆستەکان"; +"Scene.Profile.SegmentedControl.PostsAndReplies" = "پۆست و وەڵامەکان"; +"Scene.Profile.SegmentedControl.Replies" = "وەڵامەکان"; +"Scene.Register.Error.Item.Agreement" = "ڕێککەوتن"; +"Scene.Register.Error.Item.Email" = "ئیمێڵ"; +"Scene.Register.Error.Item.Locale" = "زمان"; +"Scene.Register.Error.Item.Password" = "تێپەڕوشە"; +"Scene.Register.Error.Item.Reason" = "هۆکار"; +"Scene.Register.Error.Item.Username" = "ناوی بەکارهێنەر"; +"Scene.Register.Error.Reason.Accepted" = "%@ دەبێت قبووڵ بکرێت"; +"Scene.Register.Error.Reason.Blank" = "%@ پێویستە"; +"Scene.Register.Error.Reason.Blocked" = "%@ خزمەتگوزارییەکی ئیمێڵی ڕێپێنەدراو بەکار دەهێنێت"; +"Scene.Register.Error.Reason.Inclusion" = "%@ پشتگیرینەکراوە"; +"Scene.Register.Error.Reason.Invalid" = "%@ نادروستە"; +"Scene.Register.Error.Reason.Reserved" = "%@ وشەیەکی گیراوە"; +"Scene.Register.Error.Reason.Taken" = "%@ بەکار هێنراوە لەلایەن یەکێکی تر"; +"Scene.Register.Error.Reason.TooLong" = "%@ زۆر درێژە"; +"Scene.Register.Error.Reason.TooShort" = "%@ زۆر کورتە"; +"Scene.Register.Error.Reason.Unreachable" = "%@ بوونی نییە"; +"Scene.Register.Error.Special.EmailInvalid" = "ئەم ئیمێڵە دروست نییە"; +"Scene.Register.Error.Special.PasswordTooShort" = "تێپەڕوشەکە زۆر کورتە (نابێت لە 8 نووسە کەمتر بێت)"; +"Scene.Register.Error.Special.UsernameInvalid" = "ناوی بەکارهێنەر دەبێت تەنیا پیت، ژمارە و هێڵی ژێرەوەی تێدا بێت"; +"Scene.Register.Error.Special.UsernameTooLong" = "ناوی بەکارهێنەرەکە زۆر درێژە (ناکرێت لە 30 نووسە زیاتر بێت)"; +"Scene.Register.Input.Avatar.Delete" = "بیسڕەوە"; +"Scene.Register.Input.DisplayName.Placeholder" = "ناوی نیشاندان"; +"Scene.Register.Input.Email.Placeholder" = "ئیمێڵ"; +"Scene.Register.Input.Invite.RegistrationUserInviteRequest" = "بۆچی دەتەوێت بەشدار بیت؟"; +"Scene.Register.Input.Password.Accessibility.Checked" = "هەڵبژێردراو"; +"Scene.Register.Input.Password.Accessibility.Unchecked" = "هەڵنەبژێردراو"; +"Scene.Register.Input.Password.CharacterLimit" = "8 پیت"; +"Scene.Register.Input.Password.Hint" = "دەبێت تێپەڕوشەکەت لایەنی کەم هەشت نووسە بێت"; +"Scene.Register.Input.Password.Placeholder" = "تێپەڕوشە"; +"Scene.Register.Input.Password.Require" = "تێپەڕوشەکەت لایەنی کەم پێویستیی هەیە بە:"; +"Scene.Register.Input.Username.DuplicatePrompt" = "ئەم ناوە گیراوە."; +"Scene.Register.Input.Username.Placeholder" = "ناوی بەکارهێنەر"; +"Scene.Register.Title" = "خۆت تۆمار بکە لە %@"; +"Scene.Report.Content1" = "پۆستی تر هەیە بتەوێت سکاڵایان لێ بکەیت؟"; +"Scene.Report.Content2" = "هیچ شتێکی هەیە بە چاودێرەکان بیزانن دەربارەی ئەم سکاڵایە؟"; +"Scene.Report.ReportSentTitle" = "سپاس بۆ سکاڵاکات، پێیدا دەچینەوە."; +"Scene.Report.Reported" = "سکاڵای لێ کرا"; +"Scene.Report.Send" = "سکاڵاکە بنێرە"; +"Scene.Report.SkipToSend" = "بەبێ لێدوان بینێرە"; +"Scene.Report.Step1" = "هەنگاوی 1 لە 2"; +"Scene.Report.Step2" = "هەنگاوی 2 لە 2"; +"Scene.Report.StepFinal.BlockUser" = "Block %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Don’t want to see this?"; +"Scene.Report.StepFinal.MuteUser" = "Mute %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; +"Scene.Report.StepFinal.Unfollow" = "Unfollow"; +"Scene.Report.StepFinal.UnfollowUser" = "Unfollow %@"; +"Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Is there anything else we should know?"; +"Scene.Report.StepFour.Step4Of4" = "Step 4 of 4"; +"Scene.Report.StepOne.IDontLikeIt" = "I don’t like it"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "It is not something you want to see"; +"Scene.Report.StepOne.ItViolatesServerRules" = "It violates server rules"; +"Scene.Report.StepOne.ItsSomethingElse" = "It’s something else"; +"Scene.Report.StepOne.ItsSpam" = "It’s spam"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Select the best match"; +"Scene.Report.StepOne.Step1Of4" = "Step 1 of 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "The issue does not fit into other categories"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "What's wrong with this account?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "What's wrong with this post?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "What's wrong with %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Are there any posts that back up this report?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepThree.Step3Of4" = "Step 3 of 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "I just don’t like it"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepTwo.Step2Of4" = "Step 2 of 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Which rules are being violated?"; +"Scene.Report.TextPlaceholder" = "ڕوونکردنەوەی زۆرتر بدە"; +"Scene.Report.Title" = "سکاڵا لە %@ بکە"; +"Scene.Report.TitleReport" = "سکاڵای لێ بکە"; +"Scene.Search.Recommend.Accounts.Description" = "لەوانەیە بتەوێت شوێنی ئەم هەژمارانە بکەویت"; +"Scene.Search.Recommend.Accounts.Follow" = "شوێنی بکەوە"; +"Scene.Search.Recommend.Accounts.Title" = "لەوانەیە حەزت لەمانە بێت"; +"Scene.Search.Recommend.ButtonText" = "هەمووی ببینە"; +"Scene.Search.Recommend.HashTag.Description" = "ئەو هاشتاگانەی سەرنجی زۆریان لەسەرە"; +"Scene.Search.Recommend.HashTag.PeopleTalking" = "%@ کەس باسی دەکەن"; +"Scene.Search.Recommend.HashTag.Title" = "ڕۆژەڤ"; +"Scene.Search.SearchBar.Cancel" = "بگەڕێوە"; +"Scene.Search.SearchBar.Placeholder" = "بۆ هاشتاگ و بەکارهێنەر بگەڕێ"; +"Scene.Search.Searching.Clear" = "بیانسڕەوە"; +"Scene.Search.Searching.EmptyState.NoResults" = "هیچ ئەنجامێک نەدۆزرایەوە"; +"Scene.Search.Searching.RecentSearch" = "گەڕانەکانی پێشترت"; +"Scene.Search.Searching.Segment.All" = "هەمووی"; +"Scene.Search.Searching.Segment.Hashtags" = "هاشتاگ"; +"Scene.Search.Searching.Segment.People" = "خەڵک"; +"Scene.Search.Searching.Segment.Posts" = "پۆست"; +"Scene.Search.Title" = "بگەڕێ"; +"Scene.ServerPicker.Button.Category.Academia" = "ئەکادیمیا"; +"Scene.ServerPicker.Button.Category.Activism" = "چالاکی"; +"Scene.ServerPicker.Button.Category.All" = "هەموو"; +"Scene.ServerPicker.Button.Category.AllAccessiblityDescription" = "بەش: هەموو"; +"Scene.ServerPicker.Button.Category.Art" = "هونەر"; +"Scene.ServerPicker.Button.Category.Food" = "خواردن"; +"Scene.ServerPicker.Button.Category.Furry" = "furry"; +"Scene.ServerPicker.Button.Category.Games" = "یاری"; +"Scene.ServerPicker.Button.Category.General" = "گشتی"; +"Scene.ServerPicker.Button.Category.Journalism" = "ڕۆژنامەوانی"; +"Scene.ServerPicker.Button.Category.Lgbt" = "lgbt"; +"Scene.ServerPicker.Button.Category.Music" = "موزیک"; +"Scene.ServerPicker.Button.Category.Regional" = "هەرێمی"; +"Scene.ServerPicker.Button.Category.Tech" = "تەکنۆلۆژیا"; +"Scene.ServerPicker.Button.SeeLess" = "کەمتر ببینە"; +"Scene.ServerPicker.Button.SeeMore" = "زیاتر ببینە"; +"Scene.ServerPicker.EmptyState.BadNetwork" = "هەڵەیەک ڕوویدا لە کاتی بارکردن. لە هەبوونی هێڵی ئینتەرنێت دڵنیا بە."; +"Scene.ServerPicker.EmptyState.FindingServers" = "ڕاژەکار دەدۆزرێتەوە..."; +"Scene.ServerPicker.EmptyState.NoResults" = "ئەنجام نییە"; +"Scene.ServerPicker.Input.Placeholder" = "بگەڕێ"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; +"Scene.ServerPicker.Label.Category" = "بەش"; +"Scene.ServerPicker.Label.Language" = "زمان"; +"Scene.ServerPicker.Label.Users" = "بەکارهێنەر"; +"Scene.ServerPicker.Subtitle" = "ڕاژەکارێکێکی گشتی یان دانەیەک لەسەر بنەمای حەزەکانت و هەرێمەکەت هەڵبژێرە."; +"Scene.ServerPicker.SubtitleExtend" = "ڕاژەکارێکێکی گشتی یان دانەیەک لەسەر بنەمای حەزەکانت و هەرێمەکەت هەڵبژێرە. هەر ڕاژەکارێک لەلایەن ڕێکخراوێک یان تاکەکەسێک بەڕێوە دەبرێت."; +"Scene.ServerPicker.Title" = "ماستۆدۆن لە چەندان بەکارهێنەر پێک دێت کە لە ڕاژەکاری جیاواز دان."; +"Scene.ServerRules.Button.Confirm" = "ڕازیم"; +"Scene.ServerRules.PrivacyPolicy" = "سیاسەتی تایبەتێتی"; +"Scene.ServerRules.Prompt" = "بەردەوامبوونت واتای ڕازیبوونتە بە مەرجەکانی خزمەتگوزاری و سیاسەتی تایبەتێتیی %@."; +"Scene.ServerRules.Subtitle" = "ئەمانە لەلایەن چاودێرەکانی %@ دانراون و ناچار دەکرێن."; +"Scene.ServerRules.TermsOfService" = "مەرجەکانی بەکارهێنان"; +"Scene.ServerRules.Title" = "یاساکانی ڕاژەکار"; +"Scene.Settings.Footer.MastodonDescription" = "ماستۆدۆن پڕۆژەیەکی سەرچاوەکراوەیە. دەتوانیت لە گیتهەب لە کێشەکان ئاگادارمان بکەیتەوە: %@ (%@)"; +"Scene.Settings.Keyboard.CloseSettingsWindow" = "ڕێخستنەکان دابخە"; +"Scene.Settings.Section.Appearance.Automatic" = "خۆکار"; +"Scene.Settings.Section.Appearance.Dark" = "تاریک"; +"Scene.Settings.Section.Appearance.Light" = "ڕووناک"; +"Scene.Settings.Section.Appearance.Title" = "ڕووخسار"; +"Scene.Settings.Section.BoringZone.AccountSettings" = "ڕێکخستنەکانی هەژمار"; +"Scene.Settings.Section.BoringZone.Privacy" = "سیاسەتی تایبەتێتی"; +"Scene.Settings.Section.BoringZone.Terms" = "مەرجەکانی بەکارهێنان"; +"Scene.Settings.Section.BoringZone.Title" = "ناوچە بێنازەکە"; +"Scene.Settings.Section.LookAndFeel.Light" = "ڕووناک"; +"Scene.Settings.Section.LookAndFeel.ReallyDark" = "زۆر تاریک"; +"Scene.Settings.Section.LookAndFeel.SortaDark" = "کەم تاریک"; +"Scene.Settings.Section.LookAndFeel.Title" = "ڕووخسار و هەست"; +"Scene.Settings.Section.LookAndFeel.UseSystem" = "سیستەم"; +"Scene.Settings.Section.Notifications.Boosts" = "پۆستەکەم پۆست دەکاتەوە"; +"Scene.Settings.Section.Notifications.Favorites" = "پۆستەکەمی بەدڵ دەبێت"; +"Scene.Settings.Section.Notifications.Follows" = "شوێنم دەکەوێت"; +"Scene.Settings.Section.Notifications.Mentions" = "ئاماژەم پێ دەکات"; +"Scene.Settings.Section.Notifications.Title" = "ئاماژەکان نیشان بدە"; +"Scene.Settings.Section.Notifications.Trigger.Anyone" = "هەرکەسێک"; +"Scene.Settings.Section.Notifications.Trigger.Follow" = "هەرکەسێک شوێنی دەکەوم"; +"Scene.Settings.Section.Notifications.Trigger.Follower" = "شوێنکەوتووێکم"; +"Scene.Settings.Section.Notifications.Trigger.Noone" = "هیچکەس"; +"Scene.Settings.Section.Notifications.Trigger.Title" = "ئاگادارم بکەوە کاتێک"; +"Scene.Settings.Section.Preference.DisableAvatarAnimation" = "وێنە جووڵاوەکان ناچالاک بکە"; +"Scene.Settings.Section.Preference.DisableEmojiAnimation" = "ئیمۆجییە جووڵاوەکان ناچالاک بکە"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "بەستەرەکان لەناو ماستۆدۆن بکەوە"; +"Scene.Settings.Section.Preference.Title" = "پەسەندەکان"; +"Scene.Settings.Section.Preference.TrueBlackDarkMode" = "دۆخی ڕەش"; +"Scene.Settings.Section.Preference.UsingDefaultBrowser" = "وێبگەڕی بنەڕەت بەکار بهێنە بۆ کردنەوەی بەستەرەکان"; +"Scene.Settings.Section.SpicyZone.Clear" = "بیرگە پاک بکەوە"; +"Scene.Settings.Section.SpicyZone.Signout" = "دەربچۆ"; +"Scene.Settings.Section.SpicyZone.Title" = "ناوچەی گەرم"; +"Scene.Settings.Title" = "رێکخستنەکان"; +"Scene.SuggestionAccount.FollowExplain" = "کاتێک شوێنی یەکێک دەکەویت، پۆستەکانی دێتە بەردەمت."; +"Scene.SuggestionAccount.Title" = "خەڵک بدۆزەوە"; +"Scene.Thread.BackTitle" = "پۆستەکە"; +"Scene.Thread.Title" = "پۆستی %@"; +"Scene.Welcome.GetStarted" = "دەست پێ بکە"; +"Scene.Welcome.LogIn" = "بچۆ ژوورەوە"; +"Scene.Welcome.Slogan" = "تۆڕی کۆمەڵایەتی +لەژێر دەستەکانت."; +"Scene.Wizard.AccessibilityHint" = "دوو جار دەستی پیا بنێ بۆ داخستنی"; +"Scene.Wizard.MultipleAccountSwitchIntroDescription" = "هەژمارەکەت بگۆڕە بە دەستڕاگرتن لەسەر دوگمەی پرۆفایلەکە."; +"Scene.Wizard.NewInMastodon" = "نوێ"; \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ckb.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/ckb.lproj/Localizable.stringsdict new file mode 100644 index 000000000..e744a4bd5 --- /dev/null +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ckb.lproj/Localizable.stringsdict @@ -0,0 +1,406 @@ + + + + + a11y.plural.count.unread.notification + + NSStringLocalizedFormatKey + %#@notification_count_unread_notification@ + notification_count_unread_notification + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld ئاگاداریی نەبینراو + other + %ld ئاگاداریی نەبینراو + + + a11y.plural.count.input_limit_exceeds + + NSStringLocalizedFormatKey + سنووری نووسین %#@character_count@ دەرباز دەکات + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld نووسە + other + %ld نووسە + + + a11y.plural.count.input_limit_remains + + NSStringLocalizedFormatKey + سنووری نووسین %#@character_count@ دەمێنێتەوە + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld نووسە + other + %ld نووسە + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@%#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + پۆست + other + پۆست + + + plural.count.post + + NSStringLocalizedFormatKey + %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld پۆست + other + %ld پۆست + + + plural.count.favorite + + NSStringLocalizedFormatKey + %#@favorite_count@ + favorite_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld بەدڵبوو + other + %ld بەدڵبوو + + + plural.count.reblog + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld پۆستکردنەوە + other + %ld پۆستکردنەوە + + + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld وەڵام + other + %ld وەڵام + + + plural.count.vote + + NSStringLocalizedFormatKey + %#@vote_count@ + vote_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld دەنگ + other + %ld دەنگ + + + plural.count.voter + + NSStringLocalizedFormatKey + %#@voter_count@ + voter_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld دەنگدەر + other + %ld دەنگدەر + + + plural.people_talking + + NSStringLocalizedFormatKey + %#@count_people_talking@ + count_people_talking + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld کەس باسی دەکات + other + %ld کەس باسی دەکەن + + + plural.count.following + + NSStringLocalizedFormatKey + %#@count_following@ + count_following + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld شوێنکەوتن + other + %ld شوێنکەوتن + + + plural.count.follower + + NSStringLocalizedFormatKey + %#@count_follower@ + count_follower + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld شوێنکەوتوو + other + %ld شوێنکەوتوو + + + date.year.left + + NSStringLocalizedFormatKey + %#@count_year_left@ + count_year_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld ساڵی ماوە + other + %ld ساڵی ماوە + + + date.month.left + + NSStringLocalizedFormatKey + %#@count_month_left@ + count_month_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld مانگی ماوە + other + %ld مانگی ماوە + + + date.day.left + + NSStringLocalizedFormatKey + %#@count_day_left@ + count_day_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld ڕۆژی ماوە + other + %ld ڕۆژی ماوە + + + date.hour.left + + NSStringLocalizedFormatKey + %#@count_hour_left@ + count_hour_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld کاتژمێری ماوە + other + %ld کاتژمێری ماوە + + + date.minute.left + + NSStringLocalizedFormatKey + %#@count_minute_left@ + count_minute_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld خولەکی ماوە + other + %ld خولەکی ماوە + + + date.second.left + + NSStringLocalizedFormatKey + %#@count_second_left@ + count_second_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld چرکەی ماوە + other + %ld چرکەی ماوە + + + date.year.ago.abbr + + NSStringLocalizedFormatKey + %#@count_year_ago_abbr@ + count_year_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld ساڵ لەمەوبەر + other + %ld ساڵ لەمەوبەر + + + date.month.ago.abbr + + NSStringLocalizedFormatKey + %#@count_month_ago_abbr@ + count_month_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld مانگ لەمەوبەر + other + %ld مانگ لەمەوبەر + + + date.day.ago.abbr + + NSStringLocalizedFormatKey + %#@count_day_ago_abbr@ + count_day_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld ڕۆژ لەمەوبەر + other + %ld ڕۆژ لەمەوبەر + + + date.hour.ago.abbr + + NSStringLocalizedFormatKey + %#@count_hour_ago_abbr@ + count_hour_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld کاتژمێر لەمەوبەر + other + %ld کاتژمێر لەمەوبەر + + + date.minute.ago.abbr + + NSStringLocalizedFormatKey + %#@count_minute_ago_abbr@ + count_minute_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld خولەک لەمەوبەر + other + %ld خولەک لەمەوبەر + + + date.second.ago.abbr + + NSStringLocalizedFormatKey + %#@count_second_ago_abbr@ + count_second_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld چرکە لەمەوبەر + other + %ld چرکە لەمەوبەر + + + + diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.strings index 325b1e949..7b3af4622 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.strings @@ -98,16 +98,17 @@ Bitte überprüfe deine Internetverbindung."; "Common.Controls.Status.Actions.Menu" = "Menü"; "Common.Controls.Status.Actions.Reblog" = "Teilen"; "Common.Controls.Status.Actions.Reply" = "Antworten"; -"Common.Controls.Status.Actions.ShowGif" = "Show GIF"; -"Common.Controls.Status.Actions.ShowImage" = "Show image"; -"Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; -"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tap then hold to show menu"; +"Common.Controls.Status.Actions.ShowGif" = "GIF anzeigen"; +"Common.Controls.Status.Actions.ShowImage" = "Bild anzeigen"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Zeige Video-Player"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Halte gedrückt um das Menü anzuzeigen"; "Common.Controls.Status.Actions.Unfavorite" = "Aus Favoriten entfernen"; "Common.Controls.Status.Actions.Unreblog" = "Nicht mehr teilen"; "Common.Controls.Status.ContentWarning" = "Inhaltswarnung"; "Common.Controls.Status.MediaContentWarning" = "Tippe irgendwo zum Anzeigen"; "Common.Controls.Status.Poll.Closed" = "Beendet"; "Common.Controls.Status.Poll.Vote" = "Abstimmen"; +"Common.Controls.Status.SensitiveContent" = "NSFW-Inhalt"; "Common.Controls.Status.ShowPost" = "Beitrag anzeigen"; "Common.Controls.Status.ShowUserProfile" = "Benutzerprofil anzeigen"; "Common.Controls.Status.Tag.Email" = "E-Mail"; @@ -116,7 +117,7 @@ Bitte überprüfe deine Internetverbindung."; "Common.Controls.Status.Tag.Link" = "Link"; "Common.Controls.Status.Tag.Mention" = "Erwähnung"; "Common.Controls.Status.Tag.Url" = "URL"; -"Common.Controls.Status.TapToReveal" = "Tap to reveal"; +"Common.Controls.Status.TapToReveal" = "Zum Anzeigen tippen"; "Common.Controls.Status.UserReblogged" = "%@ teilte"; "Common.Controls.Status.UserRepliedTo" = "Antwortet auf %@"; "Common.Controls.Status.Visibility.Direct" = "Nur erwähnte Benutzer können diesen Beitrag sehen."; @@ -201,9 +202,17 @@ kann nicht auf Mastodon hochgeladen werden."; "Scene.ConfirmEmail.Subtitle" = "Wir haben gerade eine E-Mail an %@ gesendet, tippe darin auf den Link, um Dein Konto zu bestätigen."; "Scene.ConfirmEmail.Title" = "Noch eine letzte Sache."; +"Scene.Discovery.Intro" = "Dies sind die Beiträge, die in deiner Umgebung auf Mastodon beliebter werden."; +"Scene.Discovery.Tabs.Community" = "Community"; +"Scene.Discovery.Tabs.ForYou" = "Für dich"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtags"; +"Scene.Discovery.Tabs.News" = "Nachrichten"; +"Scene.Discovery.Tabs.Posts" = "Beiträge"; "Scene.Favorite.Title" = "Deine Favoriten"; "Scene.Follower.Footer" = "Follower von anderen Servern werden nicht angezeigt."; "Scene.Following.Footer" = "Wem das Konto folgt wird von anderen Servern werden nicht angezeigt."; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Neue Beiträge anzeigen"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Offline"; "Scene.HomeTimeline.NavigationBarState.Published" = "Veröffentlicht!"; @@ -222,10 +231,10 @@ tippe darin auf den Link, um Dein Konto zu bestätigen."; "Scene.Preview.Keyboard.ClosePreview" = "Vorschau schließen"; "Scene.Preview.Keyboard.ShowNext" = "Nächstes anzeigen"; "Scene.Preview.Keyboard.ShowPrevious" = "Vorheriges anzeigen"; -"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Double tap to open the list"; -"Scene.Profile.Accessibility.EditAvatarImage" = "Edit avatar image"; -"Scene.Profile.Accessibility.ShowAvatarImage" = "Show avatar image"; -"Scene.Profile.Accessibility.ShowBannerImage" = "Show banner image"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Doppeltippen, um die Liste zu öffnen"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Profilbild bearbeiten"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Profilbild anzeigen"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Bannerbild anzeigen"; "Scene.Profile.Dashboard.Followers" = "Folger"; "Scene.Profile.Dashboard.Following" = "Gefolgte"; "Scene.Profile.Dashboard.Posts" = "Beiträge"; @@ -286,6 +295,37 @@ tippe darin auf den Link, um Dein Konto zu bestätigen."; "Scene.Report.SkipToSend" = "Ohne Kommentar abschicken"; "Scene.Report.Step1" = "Schritt 1 von 2"; "Scene.Report.Step2" = "Schritt 2 von 2"; +"Scene.Report.StepFinal.BlockUser" = "Block %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Don’t want to see this?"; +"Scene.Report.StepFinal.MuteUser" = "Mute %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; +"Scene.Report.StepFinal.Unfollow" = "Unfollow"; +"Scene.Report.StepFinal.UnfollowUser" = "Unfollow %@"; +"Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Is there anything else we should know?"; +"Scene.Report.StepFour.Step4Of4" = "Step 4 of 4"; +"Scene.Report.StepOne.IDontLikeIt" = "I don’t like it"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "It is not something you want to see"; +"Scene.Report.StepOne.ItViolatesServerRules" = "It violates server rules"; +"Scene.Report.StepOne.ItsSomethingElse" = "It’s something else"; +"Scene.Report.StepOne.ItsSpam" = "It’s spam"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Select the best match"; +"Scene.Report.StepOne.Step1Of4" = "Step 1 of 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "The issue does not fit into other categories"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "What's wrong with this account?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "What's wrong with this post?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "What's wrong with %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Are there any posts that back up this report?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepThree.Step3Of4" = "Step 3 of 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "I just don’t like it"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepTwo.Step2Of4" = "Step 2 of 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Which rules are being violated?"; "Scene.Report.TextPlaceholder" = "Zusätzliche Kommentare eingeben oder einfügen"; "Scene.Report.Title" = "%@ melden"; "Scene.Report.TitleReport" = "Melden"; @@ -326,6 +366,7 @@ tippe darin auf den Link, um Dein Konto zu bestätigen."; "Scene.ServerPicker.EmptyState.FindingServers" = "Verfügbare Server werden gesucht..."; "Scene.ServerPicker.EmptyState.NoResults" = "Keine Ergebnisse"; "Scene.ServerPicker.Input.Placeholder" = "Finde einen Server oder trete deinem eigenen bei..."; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; "Scene.ServerPicker.Label.Category" = "KATEGORIE"; "Scene.ServerPicker.Label.Language" = "SPRACHE"; "Scene.ServerPicker.Label.Users" = "BENUTZER"; @@ -366,7 +407,7 @@ beliebigen Server."; "Scene.Settings.Section.Notifications.Trigger.Title" = "Benachrichtige mich, wenn"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Animierte Profilbilder deaktivieren"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Animierte Emojis deaktivieren"; -"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Open links in Mastodon"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Links in Mastodon öffnen"; "Scene.Settings.Section.Preference.Title" = "Präferenzen"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Vollständig dunkler Dunkelmodus"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Standardbrowser zum Öffnen von Links verwenden"; @@ -382,5 +423,5 @@ beliebigen Server."; "Scene.Welcome.LogIn" = "Anmelden"; "Scene.Welcome.Slogan" = "Soziale Netzwerke wieder in deinen Händen."; "Scene.Wizard.AccessibilityHint" = "Doppeltippen, um diesen Assistenten zu schließen"; -"Scene.Wizard.MultipleAccountSwitchIntroDescription" = "Wechsel zwischen mehreren Konten durch drücken der Profil-Schaltfläche."; +"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/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.stringsdict index cd7218623..20e8b615e 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.stringsdict @@ -125,9 +125,9 @@ NSStringFormatValueTypeKey ld one - 1 reply + 1 Antwort other - %ld replies + %ld Antworten plural.count.vote diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings index 285c185bc..91ad3a0e3 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings @@ -108,6 +108,7 @@ Please check your internet connection."; "Common.Controls.Status.MediaContentWarning" = "Tap anywhere to reveal"; "Common.Controls.Status.Poll.Closed" = "Closed"; "Common.Controls.Status.Poll.Vote" = "Vote"; +"Common.Controls.Status.SensitiveContent" = "Sensitive Content"; "Common.Controls.Status.ShowPost" = "Show Post"; "Common.Controls.Status.ShowUserProfile" = "Show user profile"; "Common.Controls.Status.Tag.Email" = "Email"; @@ -200,9 +201,17 @@ uploaded to Mastodon."; "Scene.ConfirmEmail.OpenEmailApp.Title" = "Check your inbox."; "Scene.ConfirmEmail.Subtitle" = "Tap the link we emailed to you to verify your account."; "Scene.ConfirmEmail.Title" = "One last thing."; +"Scene.Discovery.Intro" = "These are the posts gaining traction in your corner of Mastodon."; +"Scene.Discovery.Tabs.Community" = "Community"; +"Scene.Discovery.Tabs.ForYou" = "For You"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtags"; +"Scene.Discovery.Tabs.News" = "News"; +"Scene.Discovery.Tabs.Posts" = "Posts"; "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.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "See new posts"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Offline"; "Scene.HomeTimeline.NavigationBarState.Published" = "Published!"; @@ -285,6 +294,37 @@ uploaded to Mastodon."; "Scene.Report.SkipToSend" = "Send without comment"; "Scene.Report.Step1" = "Step 1 of 2"; "Scene.Report.Step2" = "Step 2 of 2"; +"Scene.Report.StepFinal.BlockUser" = "Block %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Don’t want to see this?"; +"Scene.Report.StepFinal.MuteUser" = "Mute %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; +"Scene.Report.StepFinal.Unfollow" = "Unfollow"; +"Scene.Report.StepFinal.UnfollowUser" = "Unfollow %@"; +"Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Is there anything else we should know?"; +"Scene.Report.StepFour.Step4Of4" = "Step 4 of 4"; +"Scene.Report.StepOne.IDontLikeIt" = "I don’t like it"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "It is not something you want to see"; +"Scene.Report.StepOne.ItViolatesServerRules" = "It violates server rules"; +"Scene.Report.StepOne.ItsSomethingElse" = "It’s something else"; +"Scene.Report.StepOne.ItsSpam" = "It’s spam"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Select the best match"; +"Scene.Report.StepOne.Step1Of4" = "Step 1 of 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "The issue does not fit into other categories"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "What's wrong with this account?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "What's wrong with this post?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "What's wrong with %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Are there any posts that back up this report?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepThree.Step3Of4" = "Step 3 of 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "I just don’t like it"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepTwo.Step2Of4" = "Step 2 of 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Which rules are being violated?"; "Scene.Report.TextPlaceholder" = "Type or paste additional comments"; "Scene.Report.Title" = "Report %@"; "Scene.Report.TitleReport" = "Report"; @@ -324,13 +364,14 @@ uploaded to Mastodon."; "Scene.ServerPicker.EmptyState.BadNetwork" = "Something went wrong while loading the data. Check your internet connection."; "Scene.ServerPicker.EmptyState.FindingServers" = "Finding available servers..."; "Scene.ServerPicker.EmptyState.NoResults" = "No results"; -"Scene.ServerPicker.Input.Placeholder" = "Search communities"; +"Scene.ServerPicker.Input.Placeholder" = "Search servers"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; "Scene.ServerPicker.Label.Category" = "CATEGORY"; "Scene.ServerPicker.Label.Language" = "LANGUAGE"; "Scene.ServerPicker.Label.Users" = "USERS"; -"Scene.ServerPicker.Subtitle" = "Pick a community based on your interests, region, or a general purpose one."; -"Scene.ServerPicker.SubtitleExtend" = "Pick a community based on your interests, region, or a general purpose one. Each community is operated by an entirely independent organization or individual."; -"Scene.ServerPicker.Title" = "Mastodon is made of users in different communities."; +"Scene.ServerPicker.Subtitle" = "Pick a server based on your interests, region, or a general purpose one."; +"Scene.ServerPicker.SubtitleExtend" = "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual."; +"Scene.ServerPicker.Title" = "Mastodon is made of users in different servers."; "Scene.ServerRules.Button.Confirm" = "I Agree"; "Scene.ServerRules.PrivacyPolicy" = "privacy policy"; "Scene.ServerRules.Prompt" = "By continuing, you’re subject to the terms of service and privacy policy for %@."; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/es-419.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/es-419.lproj/Localizable.strings index f3670fb6b..5b05b535d 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/es-419.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/es-419.lproj/Localizable.strings @@ -5,7 +5,7 @@ "Common.Alerts.Common.PleaseTryAgain" = "Por favor, intentá de nuevo."; "Common.Alerts.Common.PleaseTryAgainLater" = "Por favor, intentá de nuevo más tarde."; "Common.Alerts.DeletePost.Message" = "¿Estás seguro que querés eliminar este mensaje?"; -"Common.Alerts.DeletePost.Title" = "¿Estás seguro que querés eliminar este mensaje?"; +"Common.Alerts.DeletePost.Title" = "Eliminar mensaje"; "Common.Alerts.DiscardPostContent.Message" = "Confirmá para descartar el contenido del mensaje redactado."; "Common.Alerts.DiscardPostContent.Title" = "Descartar borrador"; "Common.Alerts.EditProfileFailure.Message" = "No se pudo editar el perfil. Por favor, intentá de nuevo."; @@ -90,9 +90,9 @@ Por favor, revisá tu conexión a Internet."; "Common.Controls.Keyboard.Timeline.PreviewImage" = "Previsualizar imagen"; "Common.Controls.Keyboard.Timeline.PreviousStatus" = "Mensaje anterior"; "Common.Controls.Keyboard.Timeline.ReplyStatus" = "Responder al mensaje"; -"Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "Cambiar la advertencia de contenido"; -"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Cambiar la marca de favorito en el mensaje"; -"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Cambiar la adhesión en el mensaje"; +"Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "Cambiar modo de advertencia de contenido"; +"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Cambiar marca de favorito del mensaje"; +"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Cambiar adhesión al mensaje"; "Common.Controls.Status.Actions.Favorite" = "Marcar como favorito"; "Common.Controls.Status.Actions.Hide" = "Ocultar"; "Common.Controls.Status.Actions.Menu" = "Menú"; @@ -105,9 +105,10 @@ Por favor, revisá tu conexión a Internet."; "Common.Controls.Status.Actions.Unfavorite" = "Dejar de marcar como favorito"; "Common.Controls.Status.Actions.Unreblog" = "Deshacer adhesión"; "Common.Controls.Status.ContentWarning" = "Advertencia de contenido"; -"Common.Controls.Status.MediaContentWarning" = "Toca en cualquier lugar para mostrar"; +"Common.Controls.Status.MediaContentWarning" = "Tocá en cualquier parte para mostrar"; "Common.Controls.Status.Poll.Closed" = "Cerrada"; "Common.Controls.Status.Poll.Vote" = "Votar"; +"Common.Controls.Status.SensitiveContent" = "Contenido sensible"; "Common.Controls.Status.ShowPost" = "Mostrar mensaje"; "Common.Controls.Status.ShowUserProfile" = "Mostrar perfil de usuario"; "Common.Controls.Status.Tag.Email" = "Correo electrónico"; @@ -201,9 +202,17 @@ y no se puede subir a Mastodon."; "Scene.ConfirmEmail.Subtitle" = "Acabamos de enviar un correo electrónico a %@, pulsá en el enlace para confirmar tu cuenta."; "Scene.ConfirmEmail.Title" = "Una última cosa."; +"Scene.Discovery.Intro" = "Estos son los mensajes que están ganando tracción en tu rincón de Mastodon."; +"Scene.Discovery.Tabs.Community" = "Comunidad"; +"Scene.Discovery.Tabs.ForYou" = "Para vos"; +"Scene.Discovery.Tabs.Hashtags" = "Etiquetas"; +"Scene.Discovery.Tabs.News" = "Novedades"; +"Scene.Discovery.Tabs.Posts" = "Mensajes"; "Scene.Favorite.Title" = "Tus favoritos"; "Scene.Follower.Footer" = "No se muestran los seguidores de otros servidores."; "Scene.Following.Footer" = "No se muestran las cuentas de otros servidores que seguís."; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Ver nuevos mensajes"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Desconectado"; "Scene.HomeTimeline.NavigationBarState.Published" = "¡Enviado!"; @@ -286,6 +295,37 @@ pulsá en el enlace para confirmar tu cuenta."; "Scene.Report.SkipToSend" = "Enviar sin comentarios"; "Scene.Report.Step1" = "Paso 1 de 2"; "Scene.Report.Step2" = "Paso 2 de 2"; +"Scene.Report.StepFinal.BlockUser" = "Block %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Don’t want to see this?"; +"Scene.Report.StepFinal.MuteUser" = "Mute %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; +"Scene.Report.StepFinal.Unfollow" = "Unfollow"; +"Scene.Report.StepFinal.UnfollowUser" = "Unfollow %@"; +"Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Is there anything else we should know?"; +"Scene.Report.StepFour.Step4Of4" = "Step 4 of 4"; +"Scene.Report.StepOne.IDontLikeIt" = "I don’t like it"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "It is not something you want to see"; +"Scene.Report.StepOne.ItViolatesServerRules" = "It violates server rules"; +"Scene.Report.StepOne.ItsSomethingElse" = "It’s something else"; +"Scene.Report.StepOne.ItsSpam" = "It’s spam"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Select the best match"; +"Scene.Report.StepOne.Step1Of4" = "Step 1 of 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "The issue does not fit into other categories"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "What's wrong with this account?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "What's wrong with this post?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "What's wrong with %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Are there any posts that back up this report?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepThree.Step3Of4" = "Step 3 of 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "I just don’t like it"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepTwo.Step2Of4" = "Step 2 of 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Which rules are being violated?"; "Scene.Report.TextPlaceholder" = "Escribí o pegá comentarios adicionales"; "Scene.Report.Title" = "Denunciar a %@"; "Scene.Report.TitleReport" = "Denunciar"; @@ -326,6 +366,7 @@ pulsá en el enlace para confirmar tu cuenta."; "Scene.ServerPicker.EmptyState.FindingServers" = "Buscando servidores disponibles…"; "Scene.ServerPicker.EmptyState.NoResults" = "No hay resultados"; "Scene.ServerPicker.Input.Placeholder" = "Encontrá un servidor o unite al tuyo…"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; "Scene.ServerPicker.Label.Category" = "CATEGORÍA"; "Scene.ServerPicker.Label.Language" = "IDIOMA"; "Scene.ServerPicker.Label.Users" = "CUENTAS"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.strings index 4cbebb3e5..48b14608a 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.strings @@ -98,16 +98,17 @@ Por favor, revise su conexión a internet."; "Common.Controls.Status.Actions.Menu" = "Menú"; "Common.Controls.Status.Actions.Reblog" = "Rebloguear"; "Common.Controls.Status.Actions.Reply" = "Responder"; -"Common.Controls.Status.Actions.ShowGif" = "Show GIF"; -"Common.Controls.Status.Actions.ShowImage" = "Show image"; -"Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; -"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tap then hold to show menu"; +"Common.Controls.Status.Actions.ShowGif" = "Mostrar GIF"; +"Common.Controls.Status.Actions.ShowImage" = "Mostrar imagen"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Mostrar reproductor de vídeo"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Toca, después mantén para mostrar el menú"; "Common.Controls.Status.Actions.Unfavorite" = "No favorito"; "Common.Controls.Status.Actions.Unreblog" = "Deshacer reblogueo"; "Common.Controls.Status.ContentWarning" = "Advertencia de Contenido"; "Common.Controls.Status.MediaContentWarning" = "Pulsa en cualquier sitio para mostrar"; "Common.Controls.Status.Poll.Closed" = "Cerrado"; "Common.Controls.Status.Poll.Vote" = "Vota"; +"Common.Controls.Status.SensitiveContent" = "Sensitive Content"; "Common.Controls.Status.ShowPost" = "Mostrar Publicación"; "Common.Controls.Status.ShowUserProfile" = "Mostrar perfil del usuario"; "Common.Controls.Status.Tag.Email" = "E-mail"; @@ -116,7 +117,7 @@ Por favor, revise su conexión a internet."; "Common.Controls.Status.Tag.Link" = "Enlace"; "Common.Controls.Status.Tag.Mention" = "Mención"; "Common.Controls.Status.Tag.Url" = "URL"; -"Common.Controls.Status.TapToReveal" = "Tap to reveal"; +"Common.Controls.Status.TapToReveal" = "Tocar para revelar"; "Common.Controls.Status.UserReblogged" = "%@ lo reblogueó"; "Common.Controls.Status.UserRepliedTo" = "En respuesta a %@"; "Common.Controls.Status.Visibility.Direct" = "Sólo el usuario mencionado puede ver este mensaje."; @@ -201,9 +202,17 @@ subirse a Mastodon."; "Scene.ConfirmEmail.Subtitle" = "Te acabamos de enviar un correo a %@, pulsa en el enlace para confirmar tu cuenta."; "Scene.ConfirmEmail.Title" = "Una última cosa."; +"Scene.Discovery.Intro" = "These are the posts gaining traction in your corner of Mastodon."; +"Scene.Discovery.Tabs.Community" = "Community"; +"Scene.Discovery.Tabs.ForYou" = "Para Ti"; +"Scene.Discovery.Tabs.Hashtags" = "Etiquetas"; +"Scene.Discovery.Tabs.News" = "Noticias"; +"Scene.Discovery.Tabs.Posts" = "Publicaciones"; "Scene.Favorite.Title" = "Tus Favoritos"; "Scene.Follower.Footer" = "No se muestran los seguidores de otros servidores."; "Scene.Following.Footer" = "No se muestran los seguidos de otros servidores."; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Ver nuevas publicaciones"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Sin Conexión"; "Scene.HomeTimeline.NavigationBarState.Published" = "¡Publicado!"; @@ -222,10 +231,10 @@ pulsa en el enlace para confirmar tu cuenta."; "Scene.Preview.Keyboard.ClosePreview" = "Cerrar Previsualización"; "Scene.Preview.Keyboard.ShowNext" = "Mostrar Siguiente"; "Scene.Preview.Keyboard.ShowPrevious" = "Mostrar Anterior"; -"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Double tap to open the list"; -"Scene.Profile.Accessibility.EditAvatarImage" = "Edit avatar image"; -"Scene.Profile.Accessibility.ShowAvatarImage" = "Show avatar image"; -"Scene.Profile.Accessibility.ShowBannerImage" = "Show banner image"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Pulsa dos veces para abrir la lista"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Editar imagen del avatar"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Mostrar imagen del avatar"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Mostrar imagen de banner"; "Scene.Profile.Dashboard.Followers" = "seguidores"; "Scene.Profile.Dashboard.Following" = "siguiendo"; "Scene.Profile.Dashboard.Posts" = "publicaciones"; @@ -286,6 +295,37 @@ pulsa en el enlace para confirmar tu cuenta."; "Scene.Report.SkipToSend" = "Enviar sin comentarios"; "Scene.Report.Step1" = "Paso 1 de 2"; "Scene.Report.Step2" = "Paso 2 de 2"; +"Scene.Report.StepFinal.BlockUser" = "Block %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Don’t want to see this?"; +"Scene.Report.StepFinal.MuteUser" = "Mute %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; +"Scene.Report.StepFinal.Unfollow" = "Unfollow"; +"Scene.Report.StepFinal.UnfollowUser" = "Unfollow %@"; +"Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Is there anything else we should know?"; +"Scene.Report.StepFour.Step4Of4" = "Step 4 of 4"; +"Scene.Report.StepOne.IDontLikeIt" = "I don’t like it"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "It is not something you want to see"; +"Scene.Report.StepOne.ItViolatesServerRules" = "It violates server rules"; +"Scene.Report.StepOne.ItsSomethingElse" = "It’s something else"; +"Scene.Report.StepOne.ItsSpam" = "It’s spam"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Select the best match"; +"Scene.Report.StepOne.Step1Of4" = "Step 1 of 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "The issue does not fit into other categories"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "What's wrong with this account?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "What's wrong with this post?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "What's wrong with %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Are there any posts that back up this report?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepThree.Step3Of4" = "Step 3 of 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "I just don’t like it"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepTwo.Step2Of4" = "Step 2 of 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Which rules are being violated?"; "Scene.Report.TextPlaceholder" = "Escribe o pega comentarios adicionales"; "Scene.Report.Title" = "Reportar %@"; "Scene.Report.TitleReport" = "Reportar"; @@ -326,6 +366,7 @@ pulsa en el enlace para confirmar tu cuenta."; "Scene.ServerPicker.EmptyState.FindingServers" = "Encontrando servidores disponibles..."; "Scene.ServerPicker.EmptyState.NoResults" = "Sin resultados"; "Scene.ServerPicker.Input.Placeholder" = "Encuentra un servidor o únete al tuyo propio..."; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; "Scene.ServerPicker.Label.Category" = "CATEGORÍA"; "Scene.ServerPicker.Label.Language" = "IDIOMA"; "Scene.ServerPicker.Label.Users" = "USUARIOS"; @@ -366,7 +407,7 @@ cualquier servidor."; "Scene.Settings.Section.Notifications.Trigger.Title" = "Recibir notificación cuando"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Deshabilitar avatares animados"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Deshabilitar emojis animados"; -"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Open links in Mastodon"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Abrir links en Mastodon"; "Scene.Settings.Section.Preference.Title" = "Preferencias"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Modo oscuro negro real"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Usar navegador predeterminado para abrir los enlaces"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.stringsdict index 24e407d03..8f3e94f6b 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.stringsdict @@ -125,9 +125,9 @@ NSStringFormatValueTypeKey ld one - 1 reply + 1 respuesta other - %ld replies + %ld respuestas plural.count.vote diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/eu-ES.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/eu-ES.lproj/Localizable.strings index 341f4862a..8f2055df9 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/eu-ES.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/eu-ES.lproj/Localizable.strings @@ -98,16 +98,17 @@ Egiaztatu Interneteko konexioa."; "Common.Controls.Status.Actions.Menu" = "Menua"; "Common.Controls.Status.Actions.Reblog" = "Bultzada"; "Common.Controls.Status.Actions.Reply" = "Erantzun"; -"Common.Controls.Status.Actions.ShowGif" = "Show GIF"; -"Common.Controls.Status.Actions.ShowImage" = "Show image"; -"Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; -"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tap then hold to show menu"; +"Common.Controls.Status.Actions.ShowGif" = "Erakutsi GIFa"; +"Common.Controls.Status.Actions.ShowImage" = "Erakutsi irudia"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Erakutsi bideo-erreproduzigailua"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Sakatu eta eutsi menua erakusteko"; "Common.Controls.Status.Actions.Unfavorite" = "Kendu gogokoa"; "Common.Controls.Status.Actions.Unreblog" = "Desegin bultzada"; "Common.Controls.Status.ContentWarning" = "Edukiaren abisua"; "Common.Controls.Status.MediaContentWarning" = "Ukitu edonon bistaratzeko"; "Common.Controls.Status.Poll.Closed" = "Itxita"; "Common.Controls.Status.Poll.Vote" = "Bozkatu"; +"Common.Controls.Status.SensitiveContent" = "Sensitive Content"; "Common.Controls.Status.ShowPost" = "Erakutsi bidalketa"; "Common.Controls.Status.ShowUserProfile" = "Erakutsi erabiltzailearen profila"; "Common.Controls.Status.Tag.Email" = "Eposta"; @@ -116,7 +117,7 @@ Egiaztatu Interneteko konexioa."; "Common.Controls.Status.Tag.Link" = "Esteka"; "Common.Controls.Status.Tag.Mention" = "Aipatu"; "Common.Controls.Status.Tag.Url" = "URLa"; -"Common.Controls.Status.TapToReveal" = "Tap to reveal"; +"Common.Controls.Status.TapToReveal" = "Sakatu erakusteko"; "Common.Controls.Status.UserReblogged" = "%@ erabiltzaileak bultzada eman dio"; "Common.Controls.Status.UserRepliedTo" = "%@(r)i erantzuten"; "Common.Controls.Status.Visibility.Direct" = "Aipatutako erabiltzaileek soilik ikus dezakete bidalketa hau."; @@ -201,9 +202,17 @@ Mastodonera igo."; "Scene.ConfirmEmail.Subtitle" = "Eposta bat bidali dizugu %@ helbidera, sakatu kontua berresteko esteka."; "Scene.ConfirmEmail.Title" = "Eta azkenik..."; +"Scene.Discovery.Intro" = "These are the posts gaining traction in your corner of Mastodon."; +"Scene.Discovery.Tabs.Community" = "Community"; +"Scene.Discovery.Tabs.ForYou" = "Zuretzat"; +"Scene.Discovery.Tabs.Hashtags" = "Traolak"; +"Scene.Discovery.Tabs.News" = "Albisteak"; +"Scene.Discovery.Tabs.Posts" = "Argitalpenak"; "Scene.Favorite.Title" = "Zure gogokoak"; "Scene.Follower.Footer" = "Beste zerbitzarietako jarraitzaileak ez dira bistaratzen."; "Scene.Following.Footer" = "Beste zerbitzarietan jarraitutakoak ez dira bistaratzen."; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Ikusi bidal. berriak"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Konexio gabe"; "Scene.HomeTimeline.NavigationBarState.Published" = "Argitaratua!"; @@ -211,21 +220,21 @@ sakatu kontua berresteko esteka."; "Scene.HomeTimeline.Title" = "Hasiera"; "Scene.Notification.Keyobard.ShowEverything" = "Erakutsi guztia"; "Scene.Notification.Keyobard.ShowMentions" = "Erakutsi aipamenak"; -"Scene.Notification.NotificationDescription.FavoritedYourPost" = "erabiltzaileak zure bidalketa gogoko du"; +"Scene.Notification.NotificationDescription.FavoritedYourPost" = "(e)k zure bidalketa gogoko du"; "Scene.Notification.NotificationDescription.FollowedYou" = "zu jarraitzen hasi da"; "Scene.Notification.NotificationDescription.MentionedYou" = "erabiltzaileak aipatu zaitu"; "Scene.Notification.NotificationDescription.PollHasEnded" = "inkesta amaitu da"; -"Scene.Notification.NotificationDescription.RebloggedYourPost" = "erabiltzaileak bultzada eman dio zure bidalketari"; +"Scene.Notification.NotificationDescription.RebloggedYourPost" = "(e)k bultzada eman dio zure bidalketari"; "Scene.Notification.NotificationDescription.RequestToFollowYou" = "erabiltzaileak zu jarraitzea eskatu du"; "Scene.Notification.Title.Everything" = "Dena"; "Scene.Notification.Title.Mentions" = "Aipamenak"; "Scene.Preview.Keyboard.ClosePreview" = "Itxi aurrebista"; "Scene.Preview.Keyboard.ShowNext" = "Erakutsi hurrengoa"; "Scene.Preview.Keyboard.ShowPrevious" = "Erakutsi aurrekoa"; -"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Double tap to open the list"; -"Scene.Profile.Accessibility.EditAvatarImage" = "Edit avatar image"; -"Scene.Profile.Accessibility.ShowAvatarImage" = "Show avatar image"; -"Scene.Profile.Accessibility.ShowBannerImage" = "Show banner image"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Sakatu birritan zerrenda irekitzeko"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Editatu abatarra"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Erakutsi abatarra"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Erakutsi banner irudia"; "Scene.Profile.Dashboard.Followers" = "jarraitzaile"; "Scene.Profile.Dashboard.Following" = "jarraitzen"; "Scene.Profile.Dashboard.Posts" = "bidalketa"; @@ -286,6 +295,37 @@ sakatu kontua berresteko esteka."; "Scene.Report.SkipToSend" = "Bidali iruzkinik gabe"; "Scene.Report.Step1" = "1. urratsa 2tik"; "Scene.Report.Step2" = "2. urratsa 2tik"; +"Scene.Report.StepFinal.BlockUser" = "Block %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Don’t want to see this?"; +"Scene.Report.StepFinal.MuteUser" = "Mute %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; +"Scene.Report.StepFinal.Unfollow" = "Unfollow"; +"Scene.Report.StepFinal.UnfollowUser" = "Unfollow %@"; +"Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Is there anything else we should know?"; +"Scene.Report.StepFour.Step4Of4" = "Step 4 of 4"; +"Scene.Report.StepOne.IDontLikeIt" = "I don’t like it"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "It is not something you want to see"; +"Scene.Report.StepOne.ItViolatesServerRules" = "It violates server rules"; +"Scene.Report.StepOne.ItsSomethingElse" = "It’s something else"; +"Scene.Report.StepOne.ItsSpam" = "It’s spam"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Select the best match"; +"Scene.Report.StepOne.Step1Of4" = "Step 1 of 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "The issue does not fit into other categories"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "What's wrong with this account?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "What's wrong with this post?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "What's wrong with %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Are there any posts that back up this report?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepThree.Step3Of4" = "Step 3 of 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "I just don’t like it"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepTwo.Step2Of4" = "Step 2 of 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Which rules are being violated?"; "Scene.Report.TextPlaceholder" = "Idatzi edo itsatsi iruzkin gehigarriak"; "Scene.Report.Title" = "Salatu %@"; "Scene.Report.TitleReport" = "Salatu"; @@ -326,6 +366,7 @@ sakatu kontua berresteko esteka."; "Scene.ServerPicker.EmptyState.FindingServers" = "Erabilgarri dauden zerbitzariak bilatzen..."; "Scene.ServerPicker.EmptyState.NoResults" = "Emaitzarik ez"; "Scene.ServerPicker.Input.Placeholder" = "Bilatu zerbitzari bat edo sortu zurea..."; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; "Scene.ServerPicker.Label.Category" = "KATEGORIA"; "Scene.ServerPicker.Label.Language" = "HIZKUNTZA"; "Scene.ServerPicker.Label.Users" = "ERABILTZAILEAK"; @@ -366,7 +407,7 @@ edozein zerbitzari."; "Scene.Settings.Section.Notifications.Trigger.Title" = "Noiz jakinarazi:"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Desgaitu abatar animatuak"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Desgaitu emoji animatuak"; -"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Open links in Mastodon"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Ireki estekak Mastodonen"; "Scene.Settings.Section.Preference.Title" = "Hobespenak"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Benetako modu beltz iluna"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Erabili nabigatzaile lehenetsia estekak irekitzeko"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/eu-ES.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/eu-ES.lproj/Localizable.stringsdict index 2069e27a3..871fb10bc 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/eu-ES.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/eu-ES.lproj/Localizable.stringsdict @@ -125,9 +125,9 @@ NSStringFormatValueTypeKey ld one - 1 reply + Erantzun bat other - %ld replies + %ld erantzun plural.count.vote diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.strings index 69aa3172a..f5c60fcb7 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.strings @@ -98,9 +98,9 @@ Veuillez vérifier votre accès à Internet."; "Common.Controls.Status.Actions.Menu" = "Menu"; "Common.Controls.Status.Actions.Reblog" = "Rebloguer"; "Common.Controls.Status.Actions.Reply" = "Répondre"; -"Common.Controls.Status.Actions.ShowGif" = "Show GIF"; -"Common.Controls.Status.Actions.ShowImage" = "Show image"; -"Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; +"Common.Controls.Status.Actions.ShowGif" = "Afficher le GIF"; +"Common.Controls.Status.Actions.ShowImage" = "Afficher l’image"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Afficher le lecteur vidéo"; "Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tap then hold to show menu"; "Common.Controls.Status.Actions.Unfavorite" = "Retirer des favoris"; "Common.Controls.Status.Actions.Unreblog" = "Annuler le reblog"; @@ -108,6 +108,7 @@ Veuillez vérifier votre accès à Internet."; "Common.Controls.Status.MediaContentWarning" = "Tapotez n’importe où pour révéler la publication"; "Common.Controls.Status.Poll.Closed" = "Fermé"; "Common.Controls.Status.Poll.Vote" = "Voter"; +"Common.Controls.Status.SensitiveContent" = "Sensitive Content"; "Common.Controls.Status.ShowPost" = "Montrer la publication"; "Common.Controls.Status.ShowUserProfile" = "Montrer le profil de l’utilisateur·rice"; "Common.Controls.Status.Tag.Email" = "Courriel"; @@ -116,7 +117,7 @@ Veuillez vérifier votre accès à Internet."; "Common.Controls.Status.Tag.Link" = "Lien"; "Common.Controls.Status.Tag.Mention" = "Mention"; "Common.Controls.Status.Tag.Url" = "URL"; -"Common.Controls.Status.TapToReveal" = "Tap to reveal"; +"Common.Controls.Status.TapToReveal" = "Appuyer pour afficher"; "Common.Controls.Status.UserReblogged" = "%@ a reblogué"; "Common.Controls.Status.UserRepliedTo" = "À répondu à %@"; "Common.Controls.Status.Visibility.Direct" = "Seul·e l’utilisateur·rice mentionnée peut voir ce message."; @@ -201,9 +202,17 @@ téléversé sur Mastodon."; "Scene.ConfirmEmail.Subtitle" = "Nous venons d’envoyer un courriel à %@, tapotez le lien pour confirmer votre compte."; "Scene.ConfirmEmail.Title" = "Une dernière chose."; +"Scene.Discovery.Intro" = "These are the posts gaining traction in your corner of Mastodon."; +"Scene.Discovery.Tabs.Community" = "Community"; +"Scene.Discovery.Tabs.ForYou" = "Pour vous"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtags"; +"Scene.Discovery.Tabs.News" = "Actualité"; +"Scene.Discovery.Tabs.Posts" = "Messages"; "Scene.Favorite.Title" = "Vos favoris"; "Scene.Follower.Footer" = "Les abonné·e·s issus des autres serveurs ne sont pas affiché·e·s."; "Scene.Following.Footer" = "Les abonnés issus des autres serveurs ne sont pas affichés."; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Voir les nouvelles publications"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Hors ligne"; "Scene.HomeTimeline.NavigationBarState.Published" = "Publié!"; @@ -222,10 +231,10 @@ tapotez le lien pour confirmer votre compte."; "Scene.Preview.Keyboard.ClosePreview" = "Fermer l'aperçu"; "Scene.Preview.Keyboard.ShowNext" = "Afficher le suivant"; "Scene.Preview.Keyboard.ShowPrevious" = "Afficher le précédent"; -"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Double tap to open the list"; -"Scene.Profile.Accessibility.EditAvatarImage" = "Edit avatar image"; -"Scene.Profile.Accessibility.ShowAvatarImage" = "Show avatar image"; -"Scene.Profile.Accessibility.ShowBannerImage" = "Show banner image"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Appuyer deux fois pour ouvrir la liste"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Modifier l’avatar"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Afficher l’avatar"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Afficher l’image de la bannière"; "Scene.Profile.Dashboard.Followers" = "abonnés"; "Scene.Profile.Dashboard.Following" = "abonnements"; "Scene.Profile.Dashboard.Posts" = "publications"; @@ -286,6 +295,37 @@ tapotez le lien pour confirmer votre compte."; "Scene.Report.SkipToSend" = "Envoyer sans commentaire"; "Scene.Report.Step1" = "Étape 1 de 2"; "Scene.Report.Step2" = "Étape 2 de 2"; +"Scene.Report.StepFinal.BlockUser" = "Block %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Don’t want to see this?"; +"Scene.Report.StepFinal.MuteUser" = "Mute %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; +"Scene.Report.StepFinal.Unfollow" = "Unfollow"; +"Scene.Report.StepFinal.UnfollowUser" = "Unfollow %@"; +"Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Is there anything else we should know?"; +"Scene.Report.StepFour.Step4Of4" = "Step 4 of 4"; +"Scene.Report.StepOne.IDontLikeIt" = "I don’t like it"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "It is not something you want to see"; +"Scene.Report.StepOne.ItViolatesServerRules" = "It violates server rules"; +"Scene.Report.StepOne.ItsSomethingElse" = "It’s something else"; +"Scene.Report.StepOne.ItsSpam" = "It’s spam"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Select the best match"; +"Scene.Report.StepOne.Step1Of4" = "Step 1 of 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "The issue does not fit into other categories"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "What's wrong with this account?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "What's wrong with this post?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "What's wrong with %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Are there any posts that back up this report?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepThree.Step3Of4" = "Step 3 of 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "I just don’t like it"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepTwo.Step2Of4" = "Step 2 of 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Which rules are being violated?"; "Scene.Report.TextPlaceholder" = "Tapez ou collez des informations supplémentaires"; "Scene.Report.Title" = "Signaler %@"; "Scene.Report.TitleReport" = "Signalement"; @@ -326,6 +366,7 @@ tapotez le lien pour confirmer votre compte."; "Scene.ServerPicker.EmptyState.FindingServers" = "Recherche des serveurs disponibles..."; "Scene.ServerPicker.EmptyState.NoResults" = "Aucun résultat"; "Scene.ServerPicker.Input.Placeholder" = "Trouvez un serveur ou rejoignez le vôtre..."; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; "Scene.ServerPicker.Label.Category" = "CATÉGORIE"; "Scene.ServerPicker.Label.Language" = "LANGUE"; "Scene.ServerPicker.Label.Users" = "UTILISATEUR·RICE·S"; @@ -366,7 +407,7 @@ n'importe quel serveur."; "Scene.Settings.Section.Notifications.Trigger.Title" = "Me notifier lorsque"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Désactiver les avatars animés"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Désactiver les émojis animées"; -"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Open links in Mastodon"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Ouvrir les liens dans Mastodon"; "Scene.Settings.Section.Preference.Title" = "Préférences"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Vrai mode sombre"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Utiliser le navigateur par défaut pour ouvrir les liens"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.stringsdict index 93ee696f2..5c2b14978 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.stringsdict @@ -125,9 +125,9 @@ NSStringFormatValueTypeKey ld one - 1 reply + 1 réponse other - %ld replies + %ld réponses plural.count.vote diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/gd-GB.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/gd-GB.lproj/Localizable.strings index 9e4ab6029..a1576bb9e 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/gd-GB.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/gd-GB.lproj/Localizable.strings @@ -108,6 +108,7 @@ Thoir sùil air a’ cheangal agad ris an eadar-lìon."; "Common.Controls.Status.MediaContentWarning" = "Thoir gnogag àite sam bith gus a nochdadh"; "Common.Controls.Status.Poll.Closed" = "Dùinte"; "Common.Controls.Status.Poll.Vote" = "Cuir bhòt"; +"Common.Controls.Status.SensitiveContent" = "Susbaint fhrionasach"; "Common.Controls.Status.ShowPost" = "Seall am post"; "Common.Controls.Status.ShowUserProfile" = "Seall pròifil a’ chleachdaiche"; "Common.Controls.Status.Tag.Email" = "Post-d"; @@ -201,9 +202,17 @@ a luchdadh suas gu Mastodon."; "Scene.ConfirmEmail.Subtitle" = "Tha sinn air post-d a chur gu %@, thoir gnogag air a’ chunntas a dhearbhadh a’ chunntais agad."; "Scene.ConfirmEmail.Title" = "Aon rud eile."; +"Scene.Discovery.Intro" = "Seo na postaichean fèillmhor ’nad cheàrnaidh de Mhastodon."; +"Scene.Discovery.Tabs.Community" = "Coimhearsnachd"; +"Scene.Discovery.Tabs.ForYou" = "Dhut-sa"; +"Scene.Discovery.Tabs.Hashtags" = "Tagaichean hais"; +"Scene.Discovery.Tabs.News" = "Naidheachdan"; +"Scene.Discovery.Tabs.Posts" = "Postaichean"; "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.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Seall na postaichean ùra"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Far loidhne"; "Scene.HomeTimeline.NavigationBarState.Published" = "Chaidh fhoillseachadh!"; @@ -286,6 +295,37 @@ thoir gnogag air a’ chunntas a dhearbhadh a’ chunntais agad."; "Scene.Report.SkipToSend" = "Cuir gun bheachd ris"; "Scene.Report.Step1" = "Ceum 1 à 2"; "Scene.Report.Step2" = "Ceum 2 à 2"; +"Scene.Report.StepFinal.BlockUser" = "Block %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Don’t want to see this?"; +"Scene.Report.StepFinal.MuteUser" = "Mute %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; +"Scene.Report.StepFinal.Unfollow" = "Unfollow"; +"Scene.Report.StepFinal.UnfollowUser" = "Unfollow %@"; +"Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Is there anything else we should know?"; +"Scene.Report.StepFour.Step4Of4" = "Step 4 of 4"; +"Scene.Report.StepOne.IDontLikeIt" = "I don’t like it"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "It is not something you want to see"; +"Scene.Report.StepOne.ItViolatesServerRules" = "It violates server rules"; +"Scene.Report.StepOne.ItsSomethingElse" = "It’s something else"; +"Scene.Report.StepOne.ItsSpam" = "It’s spam"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Select the best match"; +"Scene.Report.StepOne.Step1Of4" = "Step 1 of 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "The issue does not fit into other categories"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "What's wrong with this account?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "What's wrong with this post?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "What's wrong with %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Are there any posts that back up this report?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepThree.Step3Of4" = "Step 3 of 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "I just don’t like it"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepTwo.Step2Of4" = "Step 2 of 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Which rules are being violated?"; "Scene.Report.TextPlaceholder" = "Sgrìobh no cuir ann beachdan a bharrachd"; "Scene.Report.Title" = "Dèan gearan mu %@"; "Scene.Report.TitleReport" = "Dèan gearan"; @@ -326,6 +366,7 @@ thoir gnogag air a’ chunntas a dhearbhadh a’ chunntais agad."; "Scene.ServerPicker.EmptyState.FindingServers" = "A’ lorg nam frithealaichean ri am faighinn…"; "Scene.ServerPicker.EmptyState.NoResults" = "Gun toradh"; "Scene.ServerPicker.Input.Placeholder" = "Lorg frithealaiche no gabh pàirt san fhear agad fhèin…"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; "Scene.ServerPicker.Label.Category" = "ROINN-SEÒRSA"; "Scene.ServerPicker.Label.Language" = "CÀNAN"; "Scene.ServerPicker.Label.Users" = "CLEACHDAICHEAN"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/gl.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/gl.lproj/Localizable.strings new file mode 100644 index 000000000..9aa918938 --- /dev/null +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/gl.lproj/Localizable.strings @@ -0,0 +1,426 @@ +"Common.Alerts.BlockDomain.BlockEntireDomain" = "Bloquear Dominio"; +"Common.Alerts.BlockDomain.Title" = "Tes a certeza de querer bloquear todo de %@? Na meirande parte dos casos uns bloqueos ou silenciados específicos son suficientes e preferibles. Non verás máis o contido deste dominio en ningunha cronoloxía pública e as túas seguidoras deste dominio serán eliminadas."; +"Common.Alerts.CleanCache.Message" = "Baleirouse %@ da caché correctamente."; +"Common.Alerts.CleanCache.Title" = "Limpar caché"; +"Common.Alerts.Common.PleaseTryAgain" = "Inténtao de novo."; +"Common.Alerts.Common.PleaseTryAgainLater" = "Inténtao de novo máis tarde."; +"Common.Alerts.DeletePost.Message" = "Tes a certeza de querer eliminar esta publicación?"; +"Common.Alerts.DeletePost.Title" = "Eliminar publicación"; +"Common.Alerts.DiscardPostContent.Message" = "Confirma que queres descartar o contido do borrador."; +"Common.Alerts.DiscardPostContent.Title" = "Descartar Borrador"; +"Common.Alerts.EditProfileFailure.Message" = "Non se editou o perfil. Inténtao máis tarde."; +"Common.Alerts.EditProfileFailure.Title" = "Erro ao editar o perfil"; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.MoreThanOneVideo" = "Non podes anexar máis de un vídeo."; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.VideoAttachWithPhoto" = "Non podes anexar un vídeo a unha publicación que xa contén imaxes."; +"Common.Alerts.PublishPostFailure.Message" = "Fallou a publicación. +Comproba a conexión a internet."; +"Common.Alerts.PublishPostFailure.Title" = "Fallou a publicación"; +"Common.Alerts.SavePhotoFailure.Message" = "Activa o permiso de acceso á galería de fotos para gardar a foto."; +"Common.Alerts.SavePhotoFailure.Title" = "Erro ao gardar a fotografía"; +"Common.Alerts.ServerError.Title" = "Erro do servidor"; +"Common.Alerts.SignOut.Confirm" = "Pechar sesión"; +"Common.Alerts.SignOut.Message" = "Tes a certeza de queres pechar a sesión?"; +"Common.Alerts.SignOut.Title" = "Pechar sesión"; +"Common.Alerts.SignUpFailure.Title" = "Fallou o rexistro"; +"Common.Alerts.VoteFailure.PollEnded" = "A enquisa rematou"; +"Common.Alerts.VoteFailure.Title" = "Fallou a votación"; +"Common.Controls.Actions.Add" = "Engadir"; +"Common.Controls.Actions.Back" = "Volver"; +"Common.Controls.Actions.BlockDomain" = "Bloquear a %@"; +"Common.Controls.Actions.Cancel" = "Cancelar"; +"Common.Controls.Actions.Compose" = "Escribir"; +"Common.Controls.Actions.Confirm" = "Confirmar"; +"Common.Controls.Actions.Continue" = "Continuar"; +"Common.Controls.Actions.CopyPhoto" = "Copiar foto"; +"Common.Controls.Actions.Delete" = "Eliminar"; +"Common.Controls.Actions.Discard" = "Descartar"; +"Common.Controls.Actions.Done" = "Feito"; +"Common.Controls.Actions.Edit" = "Editar"; +"Common.Controls.Actions.FindPeople" = "Atopar persoas para seguir"; +"Common.Controls.Actions.ManuallySearch" = "Buscar de xeito manual"; +"Common.Controls.Actions.Next" = "Seguinte"; +"Common.Controls.Actions.Ok" = "OK"; +"Common.Controls.Actions.Open" = "Abrir"; +"Common.Controls.Actions.OpenInBrowser" = "Abrir no navegador"; +"Common.Controls.Actions.OpenInSafari" = "Abrir en Safari"; +"Common.Controls.Actions.Preview" = "Vista previa"; +"Common.Controls.Actions.Previous" = "Anterior"; +"Common.Controls.Actions.Remove" = "Eliminar"; +"Common.Controls.Actions.Reply" = "Responder"; +"Common.Controls.Actions.ReportUser" = "Denunciar a %@"; +"Common.Controls.Actions.Save" = "Gardar"; +"Common.Controls.Actions.SavePhoto" = "Gardar foto"; +"Common.Controls.Actions.SeeMore" = "Ver máis"; +"Common.Controls.Actions.Settings" = "Axustes"; +"Common.Controls.Actions.Share" = "Compartir"; +"Common.Controls.Actions.SharePost" = "Compartir publicación"; +"Common.Controls.Actions.ShareUser" = "Compartir %@"; +"Common.Controls.Actions.SignIn" = "Acceder"; +"Common.Controls.Actions.SignUp" = "Inscribirse"; +"Common.Controls.Actions.Skip" = "Omitir"; +"Common.Controls.Actions.TakePhoto" = "Facer foto"; +"Common.Controls.Actions.TryAgain" = "Intentar de novo"; +"Common.Controls.Actions.UnblockDomain" = "Desbloquear a %@"; +"Common.Controls.Friendship.Block" = "Bloquear"; +"Common.Controls.Friendship.BlockDomain" = "Bloquear a %@"; +"Common.Controls.Friendship.BlockUser" = "Bloquear a %@"; +"Common.Controls.Friendship.Blocked" = "Bloqueada"; +"Common.Controls.Friendship.EditInfo" = "Editar info"; +"Common.Controls.Friendship.Follow" = "Seguir"; +"Common.Controls.Friendship.Following" = "Seguindo"; +"Common.Controls.Friendship.Mute" = "Acalar"; +"Common.Controls.Friendship.MuteUser" = "Acalar a %@"; +"Common.Controls.Friendship.Muted" = "Acalada"; +"Common.Controls.Friendship.Pending" = "Pendente"; +"Common.Controls.Friendship.Request" = "Solicitar"; +"Common.Controls.Friendship.Unblock" = "Desbloquear"; +"Common.Controls.Friendship.UnblockUser" = "Desbloquear a %@"; +"Common.Controls.Friendship.Unmute" = "Non Acalar"; +"Common.Controls.Friendship.UnmuteUser" = "Deixar de acalar a @%@"; +"Common.Controls.Keyboard.Common.ComposeNewPost" = "Escribir Nova publicación"; +"Common.Controls.Keyboard.Common.OpenSettings" = "Abrir axustes"; +"Common.Controls.Keyboard.Common.ShowFavorites" = "Mostrar Favoritos"; +"Common.Controls.Keyboard.Common.SwitchToTab" = "Cambiar a %@"; +"Common.Controls.Keyboard.SegmentedControl.NextSection" = "Sección seguinte"; +"Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "Sección anterior"; +"Common.Controls.Keyboard.Timeline.NextStatus" = "Publicación seguinte"; +"Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "Ver Perfil da autora"; +"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "Ver perfil de quen promoveu"; +"Common.Controls.Keyboard.Timeline.OpenStatus" = "Abrir publicación"; +"Common.Controls.Keyboard.Timeline.PreviewImage" = "Previsualización da imaxe"; +"Common.Controls.Keyboard.Timeline.PreviousStatus" = "Publicación anterior"; +"Common.Controls.Keyboard.Timeline.ReplyStatus" = "Responder á publicación"; +"Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "Marcar con Aviso sobre o contido"; +"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Engadir publicación a Favoritas"; +"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Promover a publicación"; +"Common.Controls.Status.Actions.Favorite" = "Favorecer"; +"Common.Controls.Status.Actions.Hide" = "Agochar"; +"Common.Controls.Status.Actions.Menu" = "Menú"; +"Common.Controls.Status.Actions.Reblog" = "Promover"; +"Common.Controls.Status.Actions.Reply" = "Responder"; +"Common.Controls.Status.Actions.ShowGif" = "Mostrar GIF"; +"Common.Controls.Status.Actions.ShowImage" = "Mostrar a imaxe"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Mostrar reprodutor de vídeo"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Toca e mantén preso para menú"; +"Common.Controls.Status.Actions.Unfavorite" = "Eliminar dos favoritos"; +"Common.Controls.Status.Actions.Unreblog" = "Retirar promoción"; +"Common.Controls.Status.ContentWarning" = "Aviso sobre o contido"; +"Common.Controls.Status.MediaContentWarning" = "Toca nalgures para mostrar"; +"Common.Controls.Status.Poll.Closed" = "Pechada"; +"Common.Controls.Status.Poll.Vote" = "Votar"; +"Common.Controls.Status.SensitiveContent" = "Contido sensible"; +"Common.Controls.Status.ShowPost" = "Mostrar publicación"; +"Common.Controls.Status.ShowUserProfile" = "Mostrar perfil da usuaria"; +"Common.Controls.Status.Tag.Email" = "Email"; +"Common.Controls.Status.Tag.Emoji" = "Emoticona"; +"Common.Controls.Status.Tag.Hashtag" = "Cancelo"; +"Common.Controls.Status.Tag.Link" = "Ligazón"; +"Common.Controls.Status.Tag.Mention" = "Mención"; +"Common.Controls.Status.Tag.Url" = "URL"; +"Common.Controls.Status.TapToReveal" = "Toca para mostrar"; +"Common.Controls.Status.UserReblogged" = "%@ promoveu"; +"Common.Controls.Status.UserRepliedTo" = "Respondeu a %@"; +"Common.Controls.Status.Visibility.Direct" = "Só a usuaria mencionada pode ver a publicación."; +"Common.Controls.Status.Visibility.Private" = "Só as seguidoras poden ver a publicación."; +"Common.Controls.Status.Visibility.PrivateFromMe" = "Só as miñas seguidoras poden ver esta publicación."; +"Common.Controls.Status.Visibility.Unlisted" = "A publicación é visible para calquera pero non aparece na cronoloxía pública."; +"Common.Controls.Tabs.Home" = "Inicio"; +"Common.Controls.Tabs.Notification" = "Notificación"; +"Common.Controls.Tabs.Profile" = "Perfil"; +"Common.Controls.Tabs.Search" = "Busca"; +"Common.Controls.Timeline.Filtered" = "Filtrado"; +"Common.Controls.Timeline.Header.BlockedWarning" = "Non podes ver o perfil desta usuaria +ata que te desbloquee."; +"Common.Controls.Timeline.Header.BlockingWarning" = "Non podes ver o perfil da usuaria +ata que a desbloquees. +Así ven outras o teu perfil."; +"Common.Controls.Timeline.Header.NoStatusFound" = "Non se atopa a publicación"; +"Common.Controls.Timeline.Header.SuspendedWarning" = "Esta usuaria foi suspendida."; +"Common.Controls.Timeline.Header.UserBlockedWarning" = "Non podes ver o perfil de %@ +ata que te desbloquee."; +"Common.Controls.Timeline.Header.UserBlockingWarning" = "Non podes ver o perfil de %@ +ata que o desbloquees. +Así se ve o teu perfil."; +"Common.Controls.Timeline.Header.UserSuspendedWarning" = "A conta de %@ foi suspendida."; +"Common.Controls.Timeline.Loader.LoadMissingPosts" = "Cargar publicacións que faltan"; +"Common.Controls.Timeline.Loader.LoadingMissingPosts" = "Cargando as publicacións que faltan..."; +"Common.Controls.Timeline.Loader.ShowMoreReplies" = "Mostrar máis respostas"; +"Common.Controls.Timeline.Timestamp.Now" = "Agora"; +"Scene.AccountList.AddAccount" = "Engadir conta"; +"Scene.AccountList.DismissAccountSwitcher" = "Desbotar intercambiador de contas"; +"Scene.AccountList.TabBarHint" = "Perfil seleccionado: %@. Dobre toque e manter para mostrar o intercambiador de contas"; +"Scene.Compose.Accessibility.AppendAttachment" = "Engadir anexo"; +"Scene.Compose.Accessibility.AppendPoll" = "Engadir enquisa"; +"Scene.Compose.Accessibility.CustomEmojiPicker" = "Selector emoji personalizado"; +"Scene.Compose.Accessibility.DisableContentWarning" = "Retirar Aviso sobre o contido"; +"Scene.Compose.Accessibility.EnableContentWarning" = "Marcar con Aviso sobre o contido"; +"Scene.Compose.Accessibility.PostVisibilityMenu" = "Visibilidade da publicación"; +"Scene.Compose.Accessibility.RemovePoll" = "Eliminar enquisa"; +"Scene.Compose.Attachment.AttachmentBroken" = "Este %@ está estragado e non pode +ser subido a Mastodon."; +"Scene.Compose.Attachment.DescriptionPhoto" = "Describe a foto para persoas con problemas visuais..."; +"Scene.Compose.Attachment.DescriptionVideo" = "Describe o vídeo para persoas con problemas visuais..."; +"Scene.Compose.Attachment.Photo" = "foto"; +"Scene.Compose.Attachment.Video" = "vídeo"; +"Scene.Compose.AutoComplete.SpaceToAdd" = "Barra de espazo engade"; +"Scene.Compose.ComposeAction" = "Publicar"; +"Scene.Compose.ContentInputPlaceholder" = "Escribe o que che apeteza"; +"Scene.Compose.ContentWarning.Placeholder" = "Escribe o teu aviso aquí..."; +"Scene.Compose.Keyboard.AppendAttachmentEntry" = "Engadir anexo - %@"; +"Scene.Compose.Keyboard.DiscardPost" = "Descartar publicación"; +"Scene.Compose.Keyboard.PublishPost" = "Publicar"; +"Scene.Compose.Keyboard.SelectVisibilityEntry" = "Elexir visibilidade - %@"; +"Scene.Compose.Keyboard.ToggleContentWarning" = "Marcar con Aviso sobre o contido"; +"Scene.Compose.Keyboard.TogglePoll" = "Activar enquisa"; +"Scene.Compose.MediaSelection.Browse" = "Explorar"; +"Scene.Compose.MediaSelection.Camera" = "Facer foto"; +"Scene.Compose.MediaSelection.PhotoLibrary" = "Biblioteca de fotos"; +"Scene.Compose.Poll.DurationTime" = "Duración: %@"; +"Scene.Compose.Poll.OneDay" = "1 Día"; +"Scene.Compose.Poll.OneHour" = "1 Hora"; +"Scene.Compose.Poll.OptionNumber" = "Opción %ld"; +"Scene.Compose.Poll.SevenDays" = "7 Días"; +"Scene.Compose.Poll.SixHours" = "6 Horas"; +"Scene.Compose.Poll.ThirtyMinutes" = "30 minutos"; +"Scene.Compose.Poll.ThreeDays" = "3 Días"; +"Scene.Compose.ReplyingToUser" = "en resposta a %@"; +"Scene.Compose.Title.NewPost" = "Nova publicación"; +"Scene.Compose.Title.NewReply" = "Nova resposta"; +"Scene.Compose.Visibility.Direct" = "Só para persoas mencionadas"; +"Scene.Compose.Visibility.Private" = "Só para seguidoras"; +"Scene.Compose.Visibility.Public" = "Público"; +"Scene.Compose.Visibility.Unlisted" = "Non listado"; +"Scene.ConfirmEmail.Button.OpenEmailApp" = "Abrir app de email"; +"Scene.ConfirmEmail.Button.Resend" = "Reenviar"; +"Scene.ConfirmEmail.DontReceiveEmail.Description" = "Comproba que o enderezo de email é correcto e que non vaia directo ao cartafol de spam."; +"Scene.ConfirmEmail.DontReceiveEmail.ResendEmail" = "Volver enviar o correo"; +"Scene.ConfirmEmail.DontReceiveEmail.Title" = "Revisa o teu correo"; +"Scene.ConfirmEmail.OpenEmailApp.Description" = "Enviámosche un email. Se non aparece, mira no cartafol do lixo."; +"Scene.ConfirmEmail.OpenEmailApp.Mail" = "Correo"; +"Scene.ConfirmEmail.OpenEmailApp.OpenEmailClient" = "Abrir cliente de email"; +"Scene.ConfirmEmail.OpenEmailApp.Title" = "Mira na caixa de correo."; +"Scene.ConfirmEmail.Subtitle" = "Preme na ligazón que che enviamos ao email para verificar a conta."; +"Scene.ConfirmEmail.Title" = "O último detalle."; +"Scene.Discovery.Intro" = "Estas son as publicacións en voga no teu recuncho de Mastodon."; +"Scene.Discovery.Tabs.Community" = "Comunidade"; +"Scene.Discovery.Tabs.ForYou" = "Para ti"; +"Scene.Discovery.Tabs.Hashtags" = "Cancelos"; +"Scene.Discovery.Tabs.News" = "Novas"; +"Scene.Discovery.Tabs.Posts" = "Publicacións"; +"Scene.Favorite.Title" = "Publicacións Favoritas"; +"Scene.Follower.Footer" = "Non se mostran seguidoras desde outros servidores."; +"Scene.Following.Footer" = "Non se mostran os seguimentos desde outros servidores."; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; +"Scene.HomeTimeline.NavigationBarState.NewPosts" = "Novas publicacións"; +"Scene.HomeTimeline.NavigationBarState.Offline" = "Sen conexión"; +"Scene.HomeTimeline.NavigationBarState.Published" = "Publicado!"; +"Scene.HomeTimeline.NavigationBarState.Publishing" = "Publicando..."; +"Scene.HomeTimeline.Title" = "Inicio"; +"Scene.Notification.Keyobard.ShowEverything" = "Mostrar Todo"; +"Scene.Notification.Keyobard.ShowMentions" = "Mostrar mencións"; +"Scene.Notification.NotificationDescription.FavoritedYourPost" = "marcou a túa publicación como favorita"; +"Scene.Notification.NotificationDescription.FollowedYou" = "séguete"; +"Scene.Notification.NotificationDescription.MentionedYou" = "mencionoute"; +"Scene.Notification.NotificationDescription.PollHasEnded" = "a enquisa rematou"; +"Scene.Notification.NotificationDescription.RebloggedYourPost" = "compartiu a túa publicación"; +"Scene.Notification.NotificationDescription.RequestToFollowYou" = "solicitou seguirte"; +"Scene.Notification.Title.Everything" = "Todo"; +"Scene.Notification.Title.Mentions" = "Mencións"; +"Scene.Preview.Keyboard.ClosePreview" = "Pechar vista previa"; +"Scene.Preview.Keyboard.ShowNext" = "Mostrar Seguinte"; +"Scene.Preview.Keyboard.ShowPrevious" = "Mostar Anterior"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Dous toques para abrir a lista"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Editar imaxe de avatar"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Mostrar imaxe de avatar"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Mostrar imaxe de cabeceira"; +"Scene.Profile.Dashboard.Followers" = "seguidoras"; +"Scene.Profile.Dashboard.Following" = "seguindo"; +"Scene.Profile.Dashboard.Posts" = "publicacións"; +"Scene.Profile.Fields.AddRow" = "Engadir fila"; +"Scene.Profile.Fields.Placeholder.Content" = "Contido"; +"Scene.Profile.Fields.Placeholder.Label" = "Etiqueta"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Confirma o bloqueo de %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Bloquear Conta"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "Confirma Acalar a %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "Acalar conta"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "Confirma o desbloqueo de %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "Desbloquear Conta"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "Confirma restablecer a %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Title" = "Retirar acalado da Conta"; +"Scene.Profile.SegmentedControl.About" = "Acerca de"; +"Scene.Profile.SegmentedControl.Media" = "Multimedia"; +"Scene.Profile.SegmentedControl.Posts" = "Publicacións"; +"Scene.Profile.SegmentedControl.PostsAndReplies" = "Publicacións e respostas"; +"Scene.Profile.SegmentedControl.Replies" = "Respostas"; +"Scene.Register.Error.Item.Agreement" = "Acordo"; +"Scene.Register.Error.Item.Email" = "Email"; +"Scene.Register.Error.Item.Locale" = "Locale"; +"Scene.Register.Error.Item.Password" = "Contrasinal"; +"Scene.Register.Error.Item.Reason" = "Razón"; +"Scene.Register.Error.Item.Username" = "Identificador"; +"Scene.Register.Error.Reason.Accepted" = "%@ debe ser aceptado"; +"Scene.Register.Error.Reason.Blank" = "%@ é requerido"; +"Scene.Register.Error.Reason.Blocked" = "%@ é un provedor de email non autorizado"; +"Scene.Register.Error.Reason.Inclusion" = "%@ non é un valor soportado"; +"Scene.Register.Error.Reason.Invalid" = "%@ non é válido"; +"Scene.Register.Error.Reason.Reserved" = "%@ é unha palabra reservada"; +"Scene.Register.Error.Reason.Taken" = "%@ xa está en uso"; +"Scene.Register.Error.Reason.TooLong" = "%@ é demasiado longo"; +"Scene.Register.Error.Reason.TooShort" = "%@ é demasiado curto"; +"Scene.Register.Error.Reason.Unreachable" = "%@ semella que non existe"; +"Scene.Register.Error.Special.EmailInvalid" = "Este non é un enderezo válido de email"; +"Scene.Register.Error.Special.PasswordTooShort" = "O contrasinal é demasiado curto (debe ter 8 caracteres como mínimo)"; +"Scene.Register.Error.Special.UsernameInvalid" = "O nome de usuaria só pode ter caracteres alfanuméricos e trazos baixos"; +"Scene.Register.Error.Special.UsernameTooLong" = "O nome de usuaria é demasiado longo (maior de 30 caracteres)"; +"Scene.Register.Input.Avatar.Delete" = "Eliminar"; +"Scene.Register.Input.DisplayName.Placeholder" = "nome público"; +"Scene.Register.Input.Email.Placeholder" = "email"; +"Scene.Register.Input.Invite.RegistrationUserInviteRequest" = "Por que queres unirte?"; +"Scene.Register.Input.Password.Accessibility.Checked" = "validado"; +"Scene.Register.Input.Password.Accessibility.Unchecked" = "non validado"; +"Scene.Register.Input.Password.CharacterLimit" = "8 caracteres"; +"Scene.Register.Input.Password.Hint" = "O teu contrasinal debe ter cando menos oito caracteres"; +"Scene.Register.Input.Password.Placeholder" = "contrasinal"; +"Scene.Register.Input.Password.Require" = "O contrasinal debe ter polo menos:"; +"Scene.Register.Input.Username.DuplicatePrompt" = "Este nome de usuaria xa está en uso."; +"Scene.Register.Input.Username.Placeholder" = "identificador"; +"Scene.Register.Title" = "Imos crear a túa conta en %@"; +"Scene.Report.Content1" = "Hai outras publicacións que desexes engadir á denuncia?"; +"Scene.Report.Content2" = "Hai algo que a moderación deba saber acerca desta denuncia?"; +"Scene.Report.ReportSentTitle" = "Grazas pola denuncia, investigarémola."; +"Scene.Report.Reported" = "DENUNCIADO"; +"Scene.Report.Send" = "Enviar Denuncia"; +"Scene.Report.SkipToSend" = "Enviar sen comentarios"; +"Scene.Report.Step1" = "Paso 1 de 2"; +"Scene.Report.Step2" = "Paso 2 de 2"; +"Scene.Report.StepFinal.BlockUser" = "Block %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Don’t want to see this?"; +"Scene.Report.StepFinal.MuteUser" = "Mute %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; +"Scene.Report.StepFinal.Unfollow" = "Unfollow"; +"Scene.Report.StepFinal.UnfollowUser" = "Unfollow %@"; +"Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Is there anything else we should know?"; +"Scene.Report.StepFour.Step4Of4" = "Step 4 of 4"; +"Scene.Report.StepOne.IDontLikeIt" = "I don’t like it"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "It is not something you want to see"; +"Scene.Report.StepOne.ItViolatesServerRules" = "It violates server rules"; +"Scene.Report.StepOne.ItsSomethingElse" = "It’s something else"; +"Scene.Report.StepOne.ItsSpam" = "It’s spam"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Select the best match"; +"Scene.Report.StepOne.Step1Of4" = "Step 1 of 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "The issue does not fit into other categories"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "What's wrong with this account?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "What's wrong with this post?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "What's wrong with %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Are there any posts that back up this report?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepThree.Step3Of4" = "Step 3 of 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "I just don’t like it"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepTwo.Step2Of4" = "Step 2 of 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Which rules are being violated?"; +"Scene.Report.TextPlaceholder" = "Escribe ou pega comentarios adicionais"; +"Scene.Report.Title" = "Denunciar a %@"; +"Scene.Report.TitleReport" = "Denunciar"; +"Scene.Search.Recommend.Accounts.Description" = "Mira se che interesan estas contas"; +"Scene.Search.Recommend.Accounts.Follow" = "Seguir"; +"Scene.Search.Recommend.Accounts.Title" = "Contas que poderían gustarche"; +"Scene.Search.Recommend.ButtonText" = "Ver todo"; +"Scene.Search.Recommend.HashTag.Description" = "Cancelos que están recibindo moita atención"; +"Scene.Search.Recommend.HashTag.PeopleTalking" = "%@ persoas están comentando"; +"Scene.Search.Recommend.HashTag.Title" = "En voga en Mastodon"; +"Scene.Search.SearchBar.Cancel" = "Cancelar"; +"Scene.Search.SearchBar.Placeholder" = "Buscar cancelos e usuarias"; +"Scene.Search.Searching.Clear" = "Limpar"; +"Scene.Search.Searching.EmptyState.NoResults" = "Sen resultados"; +"Scene.Search.Searching.RecentSearch" = "Buscas recentes"; +"Scene.Search.Searching.Segment.All" = "Todo"; +"Scene.Search.Searching.Segment.Hashtags" = "Cancelos"; +"Scene.Search.Searching.Segment.People" = "Persoas"; +"Scene.Search.Searching.Segment.Posts" = "Publicacións"; +"Scene.Search.Title" = "Procurar"; +"Scene.ServerPicker.Button.Category.Academia" = "academia"; +"Scene.ServerPicker.Button.Category.Activism" = "activismo"; +"Scene.ServerPicker.Button.Category.All" = "Todo"; +"Scene.ServerPicker.Button.Category.AllAccessiblityDescription" = "Categoría: Todo"; +"Scene.ServerPicker.Button.Category.Art" = "arte"; +"Scene.ServerPicker.Button.Category.Food" = "comida"; +"Scene.ServerPicker.Button.Category.Furry" = "peluxos"; +"Scene.ServerPicker.Button.Category.Games" = "xogos"; +"Scene.ServerPicker.Button.Category.General" = "xeral"; +"Scene.ServerPicker.Button.Category.Journalism" = "xornalismo"; +"Scene.ServerPicker.Button.Category.Lgbt" = "lgbt"; +"Scene.ServerPicker.Button.Category.Music" = "música"; +"Scene.ServerPicker.Button.Category.Regional" = "rexional"; +"Scene.ServerPicker.Button.Category.Tech" = "tecnoloxía"; +"Scene.ServerPicker.Button.SeeLess" = "Ver menos"; +"Scene.ServerPicker.Button.SeeMore" = "Ver máis"; +"Scene.ServerPicker.EmptyState.BadNetwork" = "Algo fallou ao cargar os datos. Comproba a conexión a internet."; +"Scene.ServerPicker.EmptyState.FindingServers" = "Buscando servidores dispoñibles..."; +"Scene.ServerPicker.EmptyState.NoResults" = "Sen resultados"; +"Scene.ServerPicker.Input.Placeholder" = "Buscar comunidades"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; +"Scene.ServerPicker.Label.Category" = "CATEGORÍA"; +"Scene.ServerPicker.Label.Language" = "IDIOMA"; +"Scene.ServerPicker.Label.Users" = "USUARIAS"; +"Scene.ServerPicker.Subtitle" = "Elixe unha comunidade en función dos teus intereses, rexión ou unha de propósito xeral."; +"Scene.ServerPicker.SubtitleExtend" = "Elixe unha comunidade en función dos teus intereses, rexión ou unha de propósito xeral. Cada comunidade está xestionada por unha organización totalmente independente ou unha única persoa."; +"Scene.ServerPicker.Title" = "Mastodon fórmano as persoas das diferentes comunidades."; +"Scene.ServerRules.Button.Confirm" = "Acepto"; +"Scene.ServerRules.PrivacyPolicy" = "polícica de privacidade"; +"Scene.ServerRules.Prompt" = "Ao continuar, acatas os termos do servizo e a política de privacidade para %@."; +"Scene.ServerRules.Subtitle" = "Son establecidas e aplicadas pola moderación de %@."; +"Scene.ServerRules.TermsOfService" = "termos do servizo"; +"Scene.ServerRules.Title" = "Algunhas regras básicas."; +"Scene.Settings.Footer.MastodonDescription" = "Mastodon é software de código aberto. Podes informar de fallos en GitHub en %@ (%@)"; +"Scene.Settings.Keyboard.CloseSettingsWindow" = "Pechar ventá de axustes"; +"Scene.Settings.Section.Appearance.Automatic" = "Automático"; +"Scene.Settings.Section.Appearance.Dark" = "Sempre escuro"; +"Scene.Settings.Section.Appearance.Light" = "Sempre claro"; +"Scene.Settings.Section.Appearance.Title" = "Aparencia"; +"Scene.Settings.Section.BoringZone.AccountSettings" = "Axustes da conta"; +"Scene.Settings.Section.BoringZone.Privacy" = "Política de Privacidade"; +"Scene.Settings.Section.BoringZone.Terms" = "Termos do Servizo"; +"Scene.Settings.Section.BoringZone.Title" = "A zona aburrida"; +"Scene.Settings.Section.LookAndFeel.Light" = "Claro"; +"Scene.Settings.Section.LookAndFeel.ReallyDark" = "Realmente escuro"; +"Scene.Settings.Section.LookAndFeel.SortaDark" = "Algo escuro"; +"Scene.Settings.Section.LookAndFeel.Title" = "Aparencia"; +"Scene.Settings.Section.LookAndFeel.UseSystem" = "Seguir o sistema"; +"Scene.Settings.Section.Notifications.Boosts" = "Promove a miña publicación"; +"Scene.Settings.Section.Notifications.Favorites" = "Favorece a miña publicación"; +"Scene.Settings.Section.Notifications.Follows" = "Me segue"; +"Scene.Settings.Section.Notifications.Mentions" = "Me menciona"; +"Scene.Settings.Section.Notifications.Title" = "Notificacións"; +"Scene.Settings.Section.Notifications.Trigger.Anyone" = "calquera"; +"Scene.Settings.Section.Notifications.Trigger.Follow" = "alguén a quen sigo"; +"Scene.Settings.Section.Notifications.Trigger.Follower" = "unha seguidora"; +"Scene.Settings.Section.Notifications.Trigger.Noone" = "ninguén"; +"Scene.Settings.Section.Notifications.Trigger.Title" = "Avisarme cando"; +"Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Desactivar avatares animados"; +"Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Desactivar emojis animados"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Abrir ligazóns en Mastodon"; +"Scene.Settings.Section.Preference.Title" = "Preferencias"; +"Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Modo negro verdadeiro"; +"Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Usar navegador por defecto para as ligazóns"; +"Scene.Settings.Section.SpicyZone.Clear" = "Limpar caché multimedia"; +"Scene.Settings.Section.SpicyZone.Signout" = "Pechar sesión"; +"Scene.Settings.Section.SpicyZone.Title" = "A zona picante"; +"Scene.Settings.Title" = "Axustes"; +"Scene.SuggestionAccount.FollowExplain" = "Cando sigas a alguén verás as súas publicacións na cronoloxía de inicio."; +"Scene.SuggestionAccount.Title" = "Atopar persoas para seguir"; +"Scene.Thread.BackTitle" = "Publicación"; +"Scene.Thread.Title" = "Publicación de %@"; +"Scene.Welcome.GetStarted" = "Comezar"; +"Scene.Welcome.LogIn" = "Acceder"; +"Scene.Welcome.Slogan" = "Comunicación social +de volta ás túas mans."; +"Scene.Wizard.AccessibilityHint" = "Dobre toque para desbotar este asistente"; +"Scene.Wizard.MultipleAccountSwitchIntroDescription" = "Cambia dunha conta a outra mantendo preso o botón do perfil."; +"Scene.Wizard.NewInMastodon" = "Novidade en Mastodon"; \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/gl.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/gl.lproj/Localizable.stringsdict new file mode 100644 index 000000000..a67c938ab --- /dev/null +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/gl.lproj/Localizable.stringsdict @@ -0,0 +1,406 @@ + + + + + a11y.plural.count.unread.notification + + NSStringLocalizedFormatKey + %#@notification_count_unread_notification@ + notification_count_unread_notification + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 notificación non lida + other + %ld notificacións non lidas + + + a11y.plural.count.input_limit_exceeds + + NSStringLocalizedFormatKey + O límite supera %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 caracter + other + %ld caracteres + + + a11y.plural.count.input_limit_remains + + NSStringLocalizedFormatKey + O límite de entrada mantense en %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 caracter + other + %ld caracteres + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@ %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + publicación + other + publicacións + + + plural.count.post + + NSStringLocalizedFormatKey + %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 publicación + other + %ld publicacións + + + plural.count.favorite + + NSStringLocalizedFormatKey + %#@favorite_count@ + favorite_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 favorito + other + %ld favoritos + + + plural.count.reblog + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 promoción + other + %ld promocións + + + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 resposta + other + %ld respostas + + + plural.count.vote + + NSStringLocalizedFormatKey + %#@vote_count@ + vote_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 voto + other + %ld votos + + + plural.count.voter + + NSStringLocalizedFormatKey + %#@voter_count@ + voter_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 votante + other + %ld votantes + + + plural.people_talking + + NSStringLocalizedFormatKey + %#@count_people_talking@ + count_people_talking + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 persoa comentando + other + %ld persoas comentando + + + plural.count.following + + NSStringLocalizedFormatKey + %#@count_following@ + count_following + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 seguimento + other + %ld seguimentos + + + plural.count.follower + + NSStringLocalizedFormatKey + %#@count_follower@ + count_follower + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 seguidora + other + %ld seguidoras + + + date.year.left + + NSStringLocalizedFormatKey + %#@count_year_left@ + count_year_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Queda 1 ano + other + Quedan %ld anos + + + date.month.left + + NSStringLocalizedFormatKey + %#@count_month_left@ + count_month_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Queda 1 mes + other + Quedan %ld meses + + + date.day.left + + NSStringLocalizedFormatKey + %#@count_day_left@ + count_day_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Queda 1 día + other + Quedan %ld días + + + date.hour.left + + NSStringLocalizedFormatKey + %#@count_hour_left@ + count_hour_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Queda 1 hora + other + Quedan %ld horas + + + date.minute.left + + NSStringLocalizedFormatKey + %#@count_minute_left@ + count_minute_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Queda 1 minuto + other + Quedan %ld minutos + + + date.second.left + + NSStringLocalizedFormatKey + %#@count_second_left@ + count_second_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Queda 1 segundo + other + Quedan %ld segundos + + + date.year.ago.abbr + + NSStringLocalizedFormatKey + %#@count_year_ago_abbr@ + count_year_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + hai 1 ano + other + hai %ld anos + + + date.month.ago.abbr + + NSStringLocalizedFormatKey + %#@count_month_ago_abbr@ + count_month_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + hai 1 mes + other + hai %ld meses + + + date.day.ago.abbr + + NSStringLocalizedFormatKey + %#@count_day_ago_abbr@ + count_day_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + hai 1 día + other + hai %ld días + + + date.hour.ago.abbr + + NSStringLocalizedFormatKey + %#@count_hour_ago_abbr@ + count_hour_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + fai 1 hora + other + fai %ld horas + + + date.minute.ago.abbr + + NSStringLocalizedFormatKey + %#@count_minute_ago_abbr@ + count_minute_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + fai 1 minuto + other + fai %ld minutos + + + date.second.ago.abbr + + NSStringLocalizedFormatKey + %#@count_second_ago_abbr@ + count_second_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + fai 1 seg. + other + fai %ld seg. + + + + diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/it.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/it.lproj/Localizable.strings new file mode 100644 index 000000000..38b0ed65d --- /dev/null +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/it.lproj/Localizable.strings @@ -0,0 +1,425 @@ +"Common.Alerts.BlockDomain.BlockEntireDomain" = "Blocca il dominio"; +"Common.Alerts.BlockDomain.Title" = "Vuoi davvero bloccare %@ completamente? Nella maggioranza dei casi, è preferibile e sufficiente bloccare o silenziare pochi account in modo mirato. Non vedrai i contenuti di quel dominio e tutti i tuoi follower da quel dominio verranno rimossi."; +"Common.Alerts.CleanCache.Message" = "Cache %@ pulita con successo."; +"Common.Alerts.CleanCache.Title" = "Pulisci la cache"; +"Common.Alerts.Common.PleaseTryAgain" = "Per favore riprova."; +"Common.Alerts.Common.PleaseTryAgainLater" = "Per favore, riprova più tardi."; +"Common.Alerts.DeletePost.Message" = "Vuoi veramente eliminare questo post?"; +"Common.Alerts.DeletePost.Title" = "Cancella il post"; +"Common.Alerts.DiscardPostContent.Message" = "Confermare di scartare il contenuto del post composto."; +"Common.Alerts.DiscardPostContent.Title" = "Elimina bozza"; +"Common.Alerts.EditProfileFailure.Message" = "Impossibile modificare il profilo. Per favore, riprova."; +"Common.Alerts.EditProfileFailure.Title" = "Errore nella modifica del profilo"; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.MoreThanOneVideo" = "Impossibile allegare più di un filmato."; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.VideoAttachWithPhoto" = "Impossibile allegare un filmato a un post che contiene già immagini."; +"Common.Alerts.PublishPostFailure.Message" = "Pubblicazione del post fallita. +Per favore verifica la tua connessione internet."; +"Common.Alerts.PublishPostFailure.Title" = "Pubblicazione fallita"; +"Common.Alerts.SavePhotoFailure.Message" = "Si prega di abilitare l'autorizzazione di accesso alla galleria immagini per salvare la foto."; +"Common.Alerts.SavePhotoFailure.Title" = "Salvataggio foto fallito"; +"Common.Alerts.ServerError.Title" = "Errore del server"; +"Common.Alerts.SignOut.Confirm" = "Esci"; +"Common.Alerts.SignOut.Message" = "Vuoi davvero scollegarti?"; +"Common.Alerts.SignOut.Title" = "Esci"; +"Common.Alerts.SignUpFailure.Title" = "Iscrizione fallita"; +"Common.Alerts.VoteFailure.PollEnded" = "Il sondaggio è terminato"; +"Common.Alerts.VoteFailure.Title" = "Voto fallito"; +"Common.Controls.Actions.Add" = "Aggiungi"; +"Common.Controls.Actions.Back" = "Indietro"; +"Common.Controls.Actions.BlockDomain" = "Blocca %@"; +"Common.Controls.Actions.Cancel" = "Annulla"; +"Common.Controls.Actions.Compose" = "Scrivi"; +"Common.Controls.Actions.Confirm" = "Conferma"; +"Common.Controls.Actions.Continue" = "Continua"; +"Common.Controls.Actions.CopyPhoto" = "Copia foto"; +"Common.Controls.Actions.Delete" = "Elimina"; +"Common.Controls.Actions.Discard" = "Abbandona"; +"Common.Controls.Actions.Done" = "Fatto"; +"Common.Controls.Actions.Edit" = "Modifica"; +"Common.Controls.Actions.FindPeople" = "Trova persone da seguire"; +"Common.Controls.Actions.ManuallySearch" = "Cerca manualmente invece"; +"Common.Controls.Actions.Next" = "Avanti"; +"Common.Controls.Actions.Ok" = "OK"; +"Common.Controls.Actions.Open" = "Apri"; +"Common.Controls.Actions.OpenInBrowser" = "Apri nel browser"; +"Common.Controls.Actions.OpenInSafari" = "Apri su Safari"; +"Common.Controls.Actions.Preview" = "Anteprima"; +"Common.Controls.Actions.Previous" = "Precedente"; +"Common.Controls.Actions.Remove" = "Rimuovi"; +"Common.Controls.Actions.Reply" = "Rispondi"; +"Common.Controls.Actions.ReportUser" = "Segnala %@"; +"Common.Controls.Actions.Save" = "Salva"; +"Common.Controls.Actions.SavePhoto" = "Salva foto"; +"Common.Controls.Actions.SeeMore" = "Visualizza altro"; +"Common.Controls.Actions.Settings" = "Impostazioni"; +"Common.Controls.Actions.Share" = "Condividi"; +"Common.Controls.Actions.SharePost" = "Condividi il post"; +"Common.Controls.Actions.ShareUser" = "Condividi %@"; +"Common.Controls.Actions.SignIn" = "Accedi"; +"Common.Controls.Actions.SignUp" = "Registrati"; +"Common.Controls.Actions.Skip" = "Salta"; +"Common.Controls.Actions.TakePhoto" = "Scatta foto"; +"Common.Controls.Actions.TryAgain" = "Riprova"; +"Common.Controls.Actions.UnblockDomain" = "Sblocca %@"; +"Common.Controls.Friendship.Block" = "Blocca"; +"Common.Controls.Friendship.BlockDomain" = "Blocca %@"; +"Common.Controls.Friendship.BlockUser" = "Blocca %@"; +"Common.Controls.Friendship.Blocked" = "Bloccato"; +"Common.Controls.Friendship.EditInfo" = "Modifica info"; +"Common.Controls.Friendship.Follow" = "Segui"; +"Common.Controls.Friendship.Following" = "Stai seguendo"; +"Common.Controls.Friendship.Mute" = "Silenzia"; +"Common.Controls.Friendship.MuteUser" = "Silenzia %@"; +"Common.Controls.Friendship.Muted" = "Silenziato"; +"Common.Controls.Friendship.Pending" = "In attesa"; +"Common.Controls.Friendship.Request" = "Richiesta"; +"Common.Controls.Friendship.Unblock" = "Sblocca"; +"Common.Controls.Friendship.UnblockUser" = "Sblocca %@"; +"Common.Controls.Friendship.Unmute" = "Riattiva"; +"Common.Controls.Friendship.UnmuteUser" = "Riattiva %@"; +"Common.Controls.Keyboard.Common.ComposeNewPost" = "Componi un nuovo post"; +"Common.Controls.Keyboard.Common.OpenSettings" = "Apri Impostazioni"; +"Common.Controls.Keyboard.Common.ShowFavorites" = "Mostra preferiti"; +"Common.Controls.Keyboard.Common.SwitchToTab" = "Passa a %@"; +"Common.Controls.Keyboard.SegmentedControl.NextSection" = "Sezione successiva"; +"Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "Sezione precedente"; +"Common.Controls.Keyboard.Timeline.NextStatus" = "Post successivo"; +"Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "Apri il profilo dell'autore"; +"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "Apri il profilo di chi ha condiviso"; +"Common.Controls.Keyboard.Timeline.OpenStatus" = "Apri il post"; +"Common.Controls.Keyboard.Timeline.PreviewImage" = "Anteprima immagine"; +"Common.Controls.Keyboard.Timeline.PreviousStatus" = "Post precedente"; +"Common.Controls.Keyboard.Timeline.ReplyStatus" = "Rispondi al post"; +"Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "Attiva/Disattiva avvertimento contenuti"; +"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Attiva/Disattiva preferito nel post"; +"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Attiva/Disattiva condivisione sul post"; +"Common.Controls.Status.Actions.Favorite" = "Preferito"; +"Common.Controls.Status.Actions.Hide" = "Nascondi"; +"Common.Controls.Status.Actions.Menu" = "Menù"; +"Common.Controls.Status.Actions.Reblog" = "Condivisione"; +"Common.Controls.Status.Actions.Reply" = "Rispondi"; +"Common.Controls.Status.Actions.ShowGif" = "Mostra GIF"; +"Common.Controls.Status.Actions.ShowImage" = "Mostra immagine"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Mostra lettore video"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tocca quindi tieni premuto per mostrare il menu"; +"Common.Controls.Status.Actions.Unfavorite" = "Non preferito"; +"Common.Controls.Status.Actions.Unreblog" = "Annulla condivisione"; +"Common.Controls.Status.ContentWarning" = "Avviso sul contenuto"; +"Common.Controls.Status.MediaContentWarning" = "Tocca ovunque per rivelare"; +"Common.Controls.Status.Poll.Closed" = "Chiuso"; +"Common.Controls.Status.Poll.Vote" = "Vota"; +"Common.Controls.Status.SensitiveContent" = "Contenuto sensibile"; +"Common.Controls.Status.ShowPost" = "Mostra il post"; +"Common.Controls.Status.ShowUserProfile" = "Mostra il profilo dell'utente"; +"Common.Controls.Status.Tag.Email" = "Email"; +"Common.Controls.Status.Tag.Emoji" = "Emoji"; +"Common.Controls.Status.Tag.Hashtag" = "Etichetta"; +"Common.Controls.Status.Tag.Link" = "Collegamento"; +"Common.Controls.Status.Tag.Mention" = "Menzione"; +"Common.Controls.Status.Tag.Url" = "URL"; +"Common.Controls.Status.TapToReveal" = "Tocca per rivelare"; +"Common.Controls.Status.UserReblogged" = "%@ hanno condiviso"; +"Common.Controls.Status.UserRepliedTo" = "Rispondi a %@"; +"Common.Controls.Status.Visibility.Direct" = "Solo l'utente menzionato può vedere questo post."; +"Common.Controls.Status.Visibility.Private" = "Solo i loro seguaci possono vedere questo post."; +"Common.Controls.Status.Visibility.PrivateFromMe" = "Solo i miei seguaci possono vedere questo post."; +"Common.Controls.Status.Visibility.Unlisted" = "Tutti possono vedere questo post ma non mostrare nella cronologia pubblica."; +"Common.Controls.Tabs.Home" = "Inizio"; +"Common.Controls.Tabs.Notification" = "Notifiche"; +"Common.Controls.Tabs.Profile" = "Profilo"; +"Common.Controls.Tabs.Search" = "Cerca"; +"Common.Controls.Timeline.Filtered" = "Filtrato"; +"Common.Controls.Timeline.Header.BlockedWarning" = "Non puoi visualizzare il profilo di questo utente +fino a quando non ti sbloccano."; +"Common.Controls.Timeline.Header.BlockingWarning" = "Non puoi visualizzare il profilo di questo utente +finché non li sblocchi. +Il tuo profilo sembra questo per loro."; +"Common.Controls.Timeline.Header.NoStatusFound" = "Nessun post trovato"; +"Common.Controls.Timeline.Header.SuspendedWarning" = "Questo utente è stato sospeso."; +"Common.Controls.Timeline.Header.UserBlockedWarning" = "Non puoi visualizzare il profilo di %@ +fino a quando non ti sbloccano."; +"Common.Controls.Timeline.Header.UserBlockingWarning" = "Non puoi visualizzare il profilo di %@ +finché non li sblocchi. +Il tuo profilo sembra questo per loro."; +"Common.Controls.Timeline.Header.UserSuspendedWarning" = "L'account di %@ è stato sospeso."; +"Common.Controls.Timeline.Loader.LoadMissingPosts" = "Carica i post mancanti"; +"Common.Controls.Timeline.Loader.LoadingMissingPosts" = "Caricamento post mancanti..."; +"Common.Controls.Timeline.Loader.ShowMoreReplies" = "Mostra più risposte"; +"Common.Controls.Timeline.Timestamp.Now" = "Ora"; +"Scene.AccountList.AddAccount" = "Aggiungi account"; +"Scene.AccountList.DismissAccountSwitcher" = "Ignora il cambio account"; +"Scene.AccountList.TabBarHint" = "Profilo corrente selezionato: %@. Doppio tocco e tieni premuto per mostrare il cambio account"; +"Scene.Compose.Accessibility.AppendAttachment" = "Aggiungi allegato"; +"Scene.Compose.Accessibility.AppendPoll" = "Aggiungi sondaggio"; +"Scene.Compose.Accessibility.CustomEmojiPicker" = "Selettore Emoji personalizzato"; +"Scene.Compose.Accessibility.DisableContentWarning" = "Disabilita avviso di contenuti"; +"Scene.Compose.Accessibility.EnableContentWarning" = "Abilita avvertimento contenuti"; +"Scene.Compose.Accessibility.PostVisibilityMenu" = "Menu di visibilità del post"; +"Scene.Compose.Accessibility.RemovePoll" = "Elimina sondaggio"; +"Scene.Compose.Attachment.AttachmentBroken" = "Questo %@ è rotto e non può essere +caricato su Mastodon."; +"Scene.Compose.Attachment.DescriptionPhoto" = "Descrivi la foto per gli utenti ipovedenti..."; +"Scene.Compose.Attachment.DescriptionVideo" = "Descrivi il filmato per gli utenti ipovedenti..."; +"Scene.Compose.Attachment.Photo" = "foto"; +"Scene.Compose.Attachment.Video" = "filmato"; +"Scene.Compose.AutoComplete.SpaceToAdd" = "Spazio da aggiungere"; +"Scene.Compose.ComposeAction" = "Pubblica"; +"Scene.Compose.ContentInputPlaceholder" = "Digita o incolla quello che hai in mente"; +"Scene.Compose.ContentWarning.Placeholder" = "Scrivi un avviso accurato qui..."; +"Scene.Compose.Keyboard.AppendAttachmentEntry" = "Aggiungi allegato - %@"; +"Scene.Compose.Keyboard.DiscardPost" = "Scarta post"; +"Scene.Compose.Keyboard.PublishPost" = "Pubblica il post"; +"Scene.Compose.Keyboard.SelectVisibilityEntry" = "Seleziona visibilità - %@"; +"Scene.Compose.Keyboard.ToggleContentWarning" = "Attiva/Disattiva avviso contenuti"; +"Scene.Compose.Keyboard.TogglePoll" = "Attiva/Disattiva Sondaggio"; +"Scene.Compose.MediaSelection.Browse" = "Sfoglia"; +"Scene.Compose.MediaSelection.Camera" = "Scatta foto"; +"Scene.Compose.MediaSelection.PhotoLibrary" = "Libreria foto"; +"Scene.Compose.Poll.DurationTime" = "Durata: %@"; +"Scene.Compose.Poll.OneDay" = "1 giorno"; +"Scene.Compose.Poll.OneHour" = "1 ora"; +"Scene.Compose.Poll.OptionNumber" = "Opzione %ld"; +"Scene.Compose.Poll.SevenDays" = "7 giorni"; +"Scene.Compose.Poll.SixHours" = "6 ore"; +"Scene.Compose.Poll.ThirtyMinutes" = "30 minuti"; +"Scene.Compose.Poll.ThreeDays" = "3 giorni"; +"Scene.Compose.ReplyingToUser" = "rispondendo a %@"; +"Scene.Compose.Title.NewPost" = "Nuovo post"; +"Scene.Compose.Title.NewReply" = "Nuova risposta"; +"Scene.Compose.Visibility.Direct" = "Solo le persone che menziono"; +"Scene.Compose.Visibility.Private" = "Solo i seguaci"; +"Scene.Compose.Visibility.Public" = "Pubblico"; +"Scene.Compose.Visibility.Unlisted" = "Non elencato"; +"Scene.ConfirmEmail.Button.OpenEmailApp" = "Apri l'app Email"; +"Scene.ConfirmEmail.Button.Resend" = "Invia di nuovo"; +"Scene.ConfirmEmail.DontReceiveEmail.Description" = "Controlla se il tuo indirizzo email sia corretto, così come la tua cartella spazzatura se non ce l'hai."; +"Scene.ConfirmEmail.DontReceiveEmail.ResendEmail" = "Invia e-mail di nuovo"; +"Scene.ConfirmEmail.DontReceiveEmail.Title" = "Controlla la tua e-mail"; +"Scene.ConfirmEmail.OpenEmailApp.Description" = "Ti abbiamo appena inviato un'email. Controlla la tua cartella spazzatura se non ce l'hai."; +"Scene.ConfirmEmail.OpenEmailApp.Mail" = "Posta"; +"Scene.ConfirmEmail.OpenEmailApp.OpenEmailClient" = "Apri client Email"; +"Scene.ConfirmEmail.OpenEmailApp.Title" = "Controlla la tua posta in arrivo."; +"Scene.ConfirmEmail.Subtitle" = "Tocca il link che ti abbiamo inviato per verificare il tuo account."; +"Scene.ConfirmEmail.Title" = "Un'ultima cosa."; +"Scene.Discovery.Intro" = "Questi sono i post che stanno guadagnando popolarità nel tuo angolo di Mastodon."; +"Scene.Discovery.Tabs.Community" = "Comunità"; +"Scene.Discovery.Tabs.ForYou" = "Per Te"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtag"; +"Scene.Discovery.Tabs.News" = "Notizie"; +"Scene.Discovery.Tabs.Posts" = "Post"; +"Scene.Favorite.Title" = "I tuoi preferiti"; +"Scene.Follower.Footer" = "I seguaci da altri server non vengono visualizzati."; +"Scene.Following.Footer" = "I follow da altri server non vengono visualizzati."; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tocca per scorrere verso l'alto e tocca di nuovo verso la posizione precedente"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Pulsante Logo"; +"Scene.HomeTimeline.NavigationBarState.NewPosts" = "Vedi nuovi post"; +"Scene.HomeTimeline.NavigationBarState.Offline" = "Non in linea"; +"Scene.HomeTimeline.NavigationBarState.Published" = "Pubblicato!"; +"Scene.HomeTimeline.NavigationBarState.Publishing" = "Pubblicazione post..."; +"Scene.HomeTimeline.Title" = "Inizio"; +"Scene.Notification.Keyobard.ShowEverything" = "Mostra Tutto"; +"Scene.Notification.Keyobard.ShowMentions" = "Mostra Menzioni"; +"Scene.Notification.NotificationDescription.FavoritedYourPost" = "ha apprezzato il tuo post"; +"Scene.Notification.NotificationDescription.FollowedYou" = "ti ha seguito"; +"Scene.Notification.NotificationDescription.MentionedYou" = "ti ha menzionato"; +"Scene.Notification.NotificationDescription.PollHasEnded" = "sondaggio terminato"; +"Scene.Notification.NotificationDescription.RebloggedYourPost" = "ha ripostato il tuo post"; +"Scene.Notification.NotificationDescription.RequestToFollowYou" = "richiesta di seguirti"; +"Scene.Notification.Title.Everything" = "Tutto"; +"Scene.Notification.Title.Mentions" = "Menzioni"; +"Scene.Preview.Keyboard.ClosePreview" = "Chiudi anteprima"; +"Scene.Preview.Keyboard.ShowNext" = "Mostra successivo"; +"Scene.Preview.Keyboard.ShowPrevious" = "Mostra precedente"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Doppio tocco per aprire la lista"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Modifica immagine avatar"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Mostra immagine avatar"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Mostra immagine banner"; +"Scene.Profile.Dashboard.Followers" = "seguaci"; +"Scene.Profile.Dashboard.Following" = "seguendo"; +"Scene.Profile.Dashboard.Posts" = "post"; +"Scene.Profile.Fields.AddRow" = "Aggiungi riga"; +"Scene.Profile.Fields.Placeholder.Content" = "Contenuto"; +"Scene.Profile.Fields.Placeholder.Label" = "Etichetta"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Confermi di bloccare %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Blocca account"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "Confermi di silenziare %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "Silenzia account"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "Conferma per sbloccare %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "Sblocca account"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "Confermi di riattivare %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Title" = "Riattiva account"; +"Scene.Profile.SegmentedControl.About" = "Info su"; +"Scene.Profile.SegmentedControl.Media" = "Media"; +"Scene.Profile.SegmentedControl.Posts" = "Post"; +"Scene.Profile.SegmentedControl.PostsAndReplies" = "Post e risposte"; +"Scene.Profile.SegmentedControl.Replies" = "Risposte"; +"Scene.Register.Error.Item.Agreement" = "Accordo"; +"Scene.Register.Error.Item.Email" = "Email"; +"Scene.Register.Error.Item.Locale" = "Locale"; +"Scene.Register.Error.Item.Password" = "Password"; +"Scene.Register.Error.Item.Reason" = "Motivo"; +"Scene.Register.Error.Item.Username" = "Nome utente"; +"Scene.Register.Error.Reason.Accepted" = "%@ deve essere accettato"; +"Scene.Register.Error.Reason.Blank" = "%@ è richiesto"; +"Scene.Register.Error.Reason.Blocked" = "%@ contiene un provider email non consentito"; +"Scene.Register.Error.Reason.Inclusion" = "%@ non è un valore supportato"; +"Scene.Register.Error.Reason.Invalid" = "%@ non è valido"; +"Scene.Register.Error.Reason.Reserved" = "%@ è una parola chiave riservata"; +"Scene.Register.Error.Reason.Taken" = "%@ è già in uso"; +"Scene.Register.Error.Reason.TooLong" = "%@ è troppo lungo"; +"Scene.Register.Error.Reason.TooShort" = "%@ è troppo corto"; +"Scene.Register.Error.Reason.Unreachable" = "%@ non sembra esistere"; +"Scene.Register.Error.Special.EmailInvalid" = "Questo non è un indirizzo email valido"; +"Scene.Register.Error.Special.PasswordTooShort" = "La password è troppo corta (deve contenere almeno 8 caratteri)"; +"Scene.Register.Error.Special.UsernameInvalid" = "Il nome utente deve contenere solo caratteri alfanumerici e trattini bassi"; +"Scene.Register.Error.Special.UsernameTooLong" = "Il nome utente è troppo lungo (non può essere più lungo di 30 caratteri)"; +"Scene.Register.Input.Avatar.Delete" = "Elimina"; +"Scene.Register.Input.DisplayName.Placeholder" = "visualizza nome"; +"Scene.Register.Input.Email.Placeholder" = "email"; +"Scene.Register.Input.Invite.RegistrationUserInviteRequest" = "Perché vuoi unirti?"; +"Scene.Register.Input.Password.Accessibility.Checked" = "verificato"; +"Scene.Register.Input.Password.Accessibility.Unchecked" = "non verificato"; +"Scene.Register.Input.Password.CharacterLimit" = "8 caratteri"; +"Scene.Register.Input.Password.Hint" = "La tua password deve essere di almeno 8 caratteri"; +"Scene.Register.Input.Password.Placeholder" = "password"; +"Scene.Register.Input.Password.Require" = "La tua password ha bisogno di almeno:"; +"Scene.Register.Input.Username.DuplicatePrompt" = "Questo nome utente è già stato preso."; +"Scene.Register.Input.Username.Placeholder" = "nome utente"; +"Scene.Register.Title" = "Facciamo in modo che sia configurato il %@"; +"Scene.Report.Content1" = "Ci sono altri post che vorresti aggiungere alla segnalazione?"; +"Scene.Report.Content2" = "C'è qualcosa che i moderatori dovrebbero sapere su questa segnalazione?"; +"Scene.Report.ReportSentTitle" = "Grazie per la segnalazione, esamineremo questo aspetto."; +"Scene.Report.Reported" = "SEGNALATO"; +"Scene.Report.Send" = "Invia segnalazione"; +"Scene.Report.SkipToSend" = "Invia senza commento"; +"Scene.Report.Step1" = "Fase 1 di 2"; +"Scene.Report.Step2" = "Fase 2 di 2"; +"Scene.Report.StepFinal.BlockUser" = "Blocca %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Non vuoi vedere questo?"; +"Scene.Report.StepFinal.MuteUser" = "Silenzia %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "Non saranno più in grado di seguire o vedere i tuoi post, ma possono vedere se sono stati bloccati."; +"Scene.Report.StepFinal.Unfollow" = "Smetti di seguire"; +"Scene.Report.StepFinal.UnfollowUser" = "Smetti di seguire %@"; +"Scene.Report.StepFinal.Unfollowed" = "Non seguito"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "Quando vedi qualcosa che non ti piace su Mastodon, puoi rimuovere la persona dalla tua esperienza."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "Non vedrai i loro post o le condivisioni nel tuo feed. Non sapranno di essere stati silenziati."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "C'è altro che dovremmo sapere?"; +"Scene.Report.StepFour.Step4Of4" = "Fase 4 di 4"; +"Scene.Report.StepOne.IDontLikeIt" = "Non mi piace"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "È qualcosa che non vuoi vedere"; +"Scene.Report.StepOne.ItViolatesServerRules" = "Viola le regole del server"; +"Scene.Report.StepOne.ItsSomethingElse" = "È qualcos'altro"; +"Scene.Report.StepOne.ItsSpam" = "È spam"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Collegamenti malevoli, false interazioni o risposte ripetitive"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Scegli la migliore corrispondenza"; +"Scene.Report.StepOne.Step1Of4" = "Fase 1 di 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "Il problema non rientra in altre categorie"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "Cosa c'è che non va con questo account?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "Cosa c'è che non va con questo post?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "Cosa c'è che non va con %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "Sei consapevole che violi regole specifiche"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Ci sono post a sostegno di questa segnalazione?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Seleziona tutte le risposte pertinenti"; +"Scene.Report.StepThree.Step3Of4" = "Fase 3 di 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "Non mi piace"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Seleziona tutte le risposte pertinenti"; +"Scene.Report.StepTwo.Step2Of4" = "Fase 2 di 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Quali regole vengono violate?"; +"Scene.Report.TextPlaceholder" = "Digita o incolla commenti aggiuntivi"; +"Scene.Report.Title" = "Segnala %@"; +"Scene.Report.TitleReport" = "Segnala"; +"Scene.Search.Recommend.Accounts.Description" = "Potresti voler seguire questi account"; +"Scene.Search.Recommend.Accounts.Follow" = "Segui"; +"Scene.Search.Recommend.Accounts.Title" = "Account che potrebbero piacerti"; +"Scene.Search.Recommend.ButtonText" = "Vedi tutto"; +"Scene.Search.Recommend.HashTag.Description" = "Hashtag che stanno ottenendo un bel po' di attenzione"; +"Scene.Search.Recommend.HashTag.PeopleTalking" = "%@ persone ne parlano"; +"Scene.Search.Recommend.HashTag.Title" = "Di tendenza su Mastodon"; +"Scene.Search.SearchBar.Cancel" = "Annulla"; +"Scene.Search.SearchBar.Placeholder" = "Cerca hashtag e utenti"; +"Scene.Search.Searching.Clear" = "Cancella"; +"Scene.Search.Searching.EmptyState.NoResults" = "Nessun risultato"; +"Scene.Search.Searching.RecentSearch" = "Ricerche recenti"; +"Scene.Search.Searching.Segment.All" = "Tutto"; +"Scene.Search.Searching.Segment.Hashtags" = "Hashtags"; +"Scene.Search.Searching.Segment.People" = "Persone"; +"Scene.Search.Searching.Segment.Posts" = "Post"; +"Scene.Search.Title" = "Cerca"; +"Scene.ServerPicker.Button.Category.Academia" = "accademia"; +"Scene.ServerPicker.Button.Category.Activism" = "attivismo"; +"Scene.ServerPicker.Button.Category.All" = "Tutti"; +"Scene.ServerPicker.Button.Category.AllAccessiblityDescription" = "Categoria: Tutti"; +"Scene.ServerPicker.Button.Category.Art" = "arte"; +"Scene.ServerPicker.Button.Category.Food" = "cibo"; +"Scene.ServerPicker.Button.Category.Furry" = "peloso"; +"Scene.ServerPicker.Button.Category.Games" = "giochi"; +"Scene.ServerPicker.Button.Category.General" = "generale"; +"Scene.ServerPicker.Button.Category.Journalism" = "giornalismo"; +"Scene.ServerPicker.Button.Category.Lgbt" = "lgbt"; +"Scene.ServerPicker.Button.Category.Music" = "musica"; +"Scene.ServerPicker.Button.Category.Regional" = "locale"; +"Scene.ServerPicker.Button.Category.Tech" = "tecnologia"; +"Scene.ServerPicker.Button.SeeLess" = "Vedi meno"; +"Scene.ServerPicker.Button.SeeMore" = "Vedi di più"; +"Scene.ServerPicker.EmptyState.BadNetwork" = "Qualcosa è andato storto durante il caricamento dei dati. Controlla la tua connessione internet."; +"Scene.ServerPicker.EmptyState.FindingServers" = "Ricerca server disponibili..."; +"Scene.ServerPicker.EmptyState.NoResults" = "Nessun risultato"; +"Scene.ServerPicker.Input.Placeholder" = "Cerca comunità"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Cerca comunità o inserisci l'URL"; +"Scene.ServerPicker.Label.Category" = "CATEGORIA"; +"Scene.ServerPicker.Label.Language" = "LINGUA"; +"Scene.ServerPicker.Label.Users" = "UTENTI"; +"Scene.ServerPicker.Subtitle" = "Scegli una comunità basata sui tuoi interessi, regione o uno scopo generale."; +"Scene.ServerPicker.SubtitleExtend" = "Scegli una comunità basata sui tuoi interessi, regione o uno scopo generale. Ogni comunità è gestita da un'organizzazione completamente indipendente o individuale."; +"Scene.ServerPicker.Title" = "Mastodon è fatto di utenti in diverse comunità."; +"Scene.ServerRules.Button.Confirm" = "Accetto"; +"Scene.ServerRules.PrivacyPolicy" = "privacy policy"; +"Scene.ServerRules.Prompt" = "Continuando, sei soggetto alle condizioni di servizio e all'informativa sulla privacy per %@."; +"Scene.ServerRules.Subtitle" = "Questi sono impostati e applicati dai moderatori %@."; +"Scene.ServerRules.TermsOfService" = "condizioni del servizio"; +"Scene.ServerRules.Title" = "Alcune regole di base."; +"Scene.Settings.Footer.MastodonDescription" = "Mastodon è un software open source. Puoi segnalare problemi su GitHub a %@ (%@)"; +"Scene.Settings.Keyboard.CloseSettingsWindow" = "Chiudi la finestra Impostazioni"; +"Scene.Settings.Section.Appearance.Automatic" = "Automatico"; +"Scene.Settings.Section.Appearance.Dark" = "Sempre scuro"; +"Scene.Settings.Section.Appearance.Light" = "Sempre chiaro"; +"Scene.Settings.Section.Appearance.Title" = "Aspetto"; +"Scene.Settings.Section.BoringZone.AccountSettings" = "Impostazioni account"; +"Scene.Settings.Section.BoringZone.Privacy" = "Politica sulla Privacy"; +"Scene.Settings.Section.BoringZone.Terms" = "Termini di servizio"; +"Scene.Settings.Section.BoringZone.Title" = "La zona boring"; +"Scene.Settings.Section.LookAndFeel.Light" = "Chiaro"; +"Scene.Settings.Section.LookAndFeel.ReallyDark" = "Davvero scuro"; +"Scene.Settings.Section.LookAndFeel.SortaDark" = "Un po' scuro"; +"Scene.Settings.Section.LookAndFeel.Title" = "Look and Feel"; +"Scene.Settings.Section.LookAndFeel.UseSystem" = "Predefinito di sistema"; +"Scene.Settings.Section.Notifications.Boosts" = "Condivide i miei post"; +"Scene.Settings.Section.Notifications.Favorites" = "Apprezza i miei post"; +"Scene.Settings.Section.Notifications.Follows" = "Mi segue"; +"Scene.Settings.Section.Notifications.Mentions" = "Mi menziona"; +"Scene.Settings.Section.Notifications.Title" = "Notifiche"; +"Scene.Settings.Section.Notifications.Trigger.Anyone" = "chiunque"; +"Scene.Settings.Section.Notifications.Trigger.Follow" = "chiunque io segua"; +"Scene.Settings.Section.Notifications.Trigger.Follower" = "un seguace"; +"Scene.Settings.Section.Notifications.Trigger.Noone" = "nessuno"; +"Scene.Settings.Section.Notifications.Trigger.Title" = "Avvisami quando"; +"Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Disabilita avatar animati"; +"Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Disabilita emoji animate"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Apri i link in Mastodon"; +"Scene.Settings.Section.Preference.Title" = "Preferenze"; +"Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Modalità molto scura"; +"Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Usa browser predefinito per aprire i collegamenti"; +"Scene.Settings.Section.SpicyZone.Clear" = "Cancella la cache multimediale"; +"Scene.Settings.Section.SpicyZone.Signout" = "Esci"; +"Scene.Settings.Section.SpicyZone.Title" = "La zona piccante"; +"Scene.Settings.Title" = "Impostazioni"; +"Scene.SuggestionAccount.FollowExplain" = "Quando segui qualcuno, vedrai i loro post nella tua home feed."; +"Scene.SuggestionAccount.Title" = "Trova alcune persone da seguire"; +"Scene.Thread.BackTitle" = "Post"; +"Scene.Thread.Title" = "Post da %@"; +"Scene.Welcome.GetStarted" = "Inizia"; +"Scene.Welcome.LogIn" = "Accedi"; +"Scene.Welcome.Slogan" = "Il social network, di nuovo nelle tue mani."; +"Scene.Wizard.AccessibilityHint" = "Doppio tocco per eliminare questa procedura guidata"; +"Scene.Wizard.MultipleAccountSwitchIntroDescription" = "Passa tra più account tenendo premuto il pulsante del profilo."; +"Scene.Wizard.NewInMastodon" = "Nuovo su Mastodon"; \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/it.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/it.lproj/Localizable.stringsdict new file mode 100644 index 000000000..710980608 --- /dev/null +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/it.lproj/Localizable.stringsdict @@ -0,0 +1,406 @@ + + + + + a11y.plural.count.unread.notification + + NSStringLocalizedFormatKey + %#@notification_count_unread_notification@ + notification_count_unread_notification + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 notifica non letta + other + %ld notifiche non lette + + + a11y.plural.count.input_limit_exceeds + + NSStringLocalizedFormatKey + Il limite di input supera %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 carattere + other + %ld caratteri + + + a11y.plural.count.input_limit_remains + + NSStringLocalizedFormatKey + Il limite di input rimane %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 carattere + other + %ld caratteri + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@ %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + post + other + post + + + plural.count.post + + NSStringLocalizedFormatKey + %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 post + other + %ld post + + + plural.count.favorite + + NSStringLocalizedFormatKey + %#@favorite_count@ + favorite_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 preferito + other + %ld preferiti + + + plural.count.reblog + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 condivisione + other + %ld condivisioni + + + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 risposta + other + %ld risposte + + + plural.count.vote + + NSStringLocalizedFormatKey + %#@vote_count@ + vote_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 voto + other + %ld voti + + + plural.count.voter + + NSStringLocalizedFormatKey + %#@voter_count@ + voter_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 votante + other + %ld votanti + + + plural.people_talking + + NSStringLocalizedFormatKey + %#@count_people_talking@ + count_people_talking + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 persona ne parla + other + %ld persone ne parlano + + + plural.count.following + + NSStringLocalizedFormatKey + %#@count_following@ + count_following + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 following + other + %ld stanno seguendo + + + plural.count.follower + + NSStringLocalizedFormatKey + %#@count_follower@ + count_follower + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 seguace + other + %ld seguaci + + + date.year.left + + NSStringLocalizedFormatKey + %#@count_year_left@ + count_year_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 anno rimasto + other + %ld anni rimasti + + + date.month.left + + NSStringLocalizedFormatKey + %#@count_month_left@ + count_month_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 mese rimasto + other + %ld mesi rimasti + + + date.day.left + + NSStringLocalizedFormatKey + %#@count_day_left@ + count_day_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 giorno rimasto + other + %ld giorni rimasti + + + date.hour.left + + NSStringLocalizedFormatKey + %#@count_hour_left@ + count_hour_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 ora rimasta + other + %ld ore rimaste + + + date.minute.left + + NSStringLocalizedFormatKey + %#@count_minute_left@ + count_minute_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 minuto rimasto + other + %ld minuti rimasti + + + date.second.left + + NSStringLocalizedFormatKey + %#@count_second_left@ + count_second_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 secondo rimasto + other + %ld secondi rimasti + + + date.year.ago.abbr + + NSStringLocalizedFormatKey + %#@count_year_ago_abbr@ + count_year_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 anno fa + other + %ld anni fa + + + date.month.ago.abbr + + NSStringLocalizedFormatKey + %#@count_month_ago_abbr@ + count_month_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 mese fa + other + %ld mesi fa + + + date.day.ago.abbr + + NSStringLocalizedFormatKey + %#@count_day_ago_abbr@ + count_day_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 giorno fa + other + %ld giorni fa + + + date.hour.ago.abbr + + NSStringLocalizedFormatKey + %#@count_hour_ago_abbr@ + count_hour_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 ora fa + other + %ld ore fa + + + date.minute.ago.abbr + + NSStringLocalizedFormatKey + %#@count_minute_ago_abbr@ + count_minute_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 minuto fa + other + %ld minuti fa + + + date.second.ago.abbr + + NSStringLocalizedFormatKey + %#@count_second_ago_abbr@ + count_second_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 secondo fa + other + %ld secondi fa + + + + diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.strings index a8164db9d..d746c1962 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.strings @@ -4,8 +4,8 @@ "Common.Alerts.CleanCache.Title" = "キャッシュを消去"; "Common.Alerts.Common.PleaseTryAgain" = "もう一度お試しください。"; "Common.Alerts.Common.PleaseTryAgainLater" = "後でもう一度お試しください。"; -"Common.Alerts.DeletePost.Message" = "本当に削除しますか?"; -"Common.Alerts.DeletePost.Title" = "この投稿を消去しますか?"; +"Common.Alerts.DeletePost.Message" = "本当にこの投稿を削除しますか?"; +"Common.Alerts.DeletePost.Title" = "投稿を削除"; "Common.Alerts.DiscardPostContent.Message" = "この操作は取り消しできません。下書きは失われます。"; "Common.Alerts.DiscardPostContent.Title" = "投稿を破棄しますか?"; "Common.Alerts.EditProfileFailure.Message" = "プロフィールを編集できません。もう一度お試しください。"; @@ -32,13 +32,13 @@ "Common.Controls.Actions.Confirm" = "確認"; "Common.Controls.Actions.Continue" = "続ける"; "Common.Controls.Actions.CopyPhoto" = "写真をコピー"; -"Common.Controls.Actions.Delete" = "消去"; +"Common.Controls.Actions.Delete" = "削除"; "Common.Controls.Actions.Discard" = "破棄"; "Common.Controls.Actions.Done" = "完了"; "Common.Controls.Actions.Edit" = "編集"; "Common.Controls.Actions.FindPeople" = "フォローする人を見つける"; "Common.Controls.Actions.ManuallySearch" = "手動で検索する"; -"Common.Controls.Actions.Next" = "次"; +"Common.Controls.Actions.Next" = "次へ"; "Common.Controls.Actions.Ok" = "OK"; "Common.Controls.Actions.Open" = "開く"; "Common.Controls.Actions.OpenInBrowser" = "ブラウザで開く"; @@ -46,7 +46,7 @@ "Common.Controls.Actions.Preview" = "プレビュー"; "Common.Controls.Actions.Previous" = "前"; "Common.Controls.Actions.Remove" = "消去"; -"Common.Controls.Actions.Reply" = "リプライ"; +"Common.Controls.Actions.Reply" = "返信"; "Common.Controls.Actions.ReportUser" = "%@を通報"; "Common.Controls.Actions.Save" = "保存"; "Common.Controls.Actions.SavePhoto" = "写真を撮る"; @@ -97,9 +97,9 @@ "Common.Controls.Status.Actions.Hide" = "非表示"; "Common.Controls.Status.Actions.Menu" = "メニュー"; "Common.Controls.Status.Actions.Reblog" = "ブースト"; -"Common.Controls.Status.Actions.Reply" = "リプライ"; -"Common.Controls.Status.Actions.ShowGif" = "Show GIF"; -"Common.Controls.Status.Actions.ShowImage" = "Show image"; +"Common.Controls.Status.Actions.Reply" = "返信"; +"Common.Controls.Status.Actions.ShowGif" = "GIFを表示"; +"Common.Controls.Status.Actions.ShowImage" = "画像を表示"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; "Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tap then hold to show menu"; "Common.Controls.Status.Actions.Unfavorite" = "お気に入り登録を取り消す"; @@ -108,6 +108,7 @@ "Common.Controls.Status.MediaContentWarning" = "どこかをタップして表示"; "Common.Controls.Status.Poll.Closed" = "クローズド"; "Common.Controls.Status.Poll.Vote" = "投票"; +"Common.Controls.Status.SensitiveContent" = "Sensitive Content"; "Common.Controls.Status.ShowPost" = "投稿を見る"; "Common.Controls.Status.ShowUserProfile" = "プロフィールを見る"; "Common.Controls.Status.Tag.Email" = "メール"; @@ -116,9 +117,9 @@ "Common.Controls.Status.Tag.Link" = "リンク"; "Common.Controls.Status.Tag.Mention" = "メンション"; "Common.Controls.Status.Tag.Url" = "URL"; -"Common.Controls.Status.TapToReveal" = "Tap to reveal"; +"Common.Controls.Status.TapToReveal" = "タップして表示"; "Common.Controls.Status.UserReblogged" = "%@がブースト"; -"Common.Controls.Status.UserRepliedTo" = "%@がリプライ"; +"Common.Controls.Status.UserRepliedTo" = "%@に返信"; "Common.Controls.Status.Visibility.Direct" = "この投稿はメンションされたユーザーに限り見ることができます。"; "Common.Controls.Status.Visibility.Private" = "この投稿はフォロワーに限り見ることができます。"; "Common.Controls.Status.Visibility.PrivateFromMe" = "この投稿はフォロワーに限り見ることができます。"; @@ -139,7 +140,7 @@ "Common.Controls.Timeline.Header.UserSuspendedWarning" = "%@のアカウントは停止されました。"; "Common.Controls.Timeline.Loader.LoadMissingPosts" = "不足している投稿を読み込む"; "Common.Controls.Timeline.Loader.LoadingMissingPosts" = "読込中..."; -"Common.Controls.Timeline.Loader.ShowMoreReplies" = "リプライをもっとみる"; +"Common.Controls.Timeline.Loader.ShowMoreReplies" = "返信をさらに表示"; "Common.Controls.Timeline.Timestamp.Now" = "今"; "Scene.AccountList.AddAccount" = "アカウントを追加"; "Scene.AccountList.DismissAccountSwitcher" = "アカウント切替画面を閉じます"; @@ -177,9 +178,9 @@ "Scene.Compose.Poll.SixHours" = "6時間"; "Scene.Compose.Poll.ThirtyMinutes" = "30分"; "Scene.Compose.Poll.ThreeDays" = "3日"; -"Scene.Compose.ReplyingToUser" = "%@にリプライ"; +"Scene.Compose.ReplyingToUser" = "%@に返信"; "Scene.Compose.Title.NewPost" = "新しい投稿"; -"Scene.Compose.Title.NewReply" = "新しいリプライ"; +"Scene.Compose.Title.NewReply" = "新しい返信"; "Scene.Compose.Visibility.Direct" = "ダイレクト"; "Scene.Compose.Visibility.Private" = "フォロワーのみ"; "Scene.Compose.Visibility.Public" = "パブリック"; @@ -195,9 +196,17 @@ "Scene.ConfirmEmail.OpenEmailApp.Title" = "メールを確認"; "Scene.ConfirmEmail.Subtitle" = "先程 %@ にメールを送信しました。リンクをタップしてアカウントを確認してください。"; "Scene.ConfirmEmail.Title" = "さいごにもうひとつ。"; +"Scene.Discovery.Intro" = "あなたのMastodonサーバーで注目を集めている投稿がここに表示されます。"; +"Scene.Discovery.Tabs.Community" = "コミュニティ"; +"Scene.Discovery.Tabs.ForYou" = "おすすめ"; +"Scene.Discovery.Tabs.Hashtags" = "ハッシュタグ"; +"Scene.Discovery.Tabs.News" = "ニュース"; +"Scene.Discovery.Tabs.Posts" = "投稿"; "Scene.Favorite.Title" = "お気に入り"; "Scene.Follower.Footer" = "他のサーバーからのフォロワーは表示されません。"; "Scene.Following.Footer" = "他のサーバーにいるフォローは表示されません。"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "新しい投稿を見る"; "Scene.HomeTimeline.NavigationBarState.Offline" = "オフライン"; "Scene.HomeTimeline.NavigationBarState.Published" = "投稿しました!"; @@ -205,12 +214,12 @@ "Scene.HomeTimeline.Title" = "ホーム"; "Scene.Notification.Keyobard.ShowEverything" = "すべて見る"; "Scene.Notification.Keyobard.ShowMentions" = "メンションを見る"; -"Scene.Notification.NotificationDescription.FavoritedYourPost" = "favorited your post"; -"Scene.Notification.NotificationDescription.FollowedYou" = "followed you"; -"Scene.Notification.NotificationDescription.MentionedYou" = "mentioned you"; -"Scene.Notification.NotificationDescription.PollHasEnded" = "poll has ended"; -"Scene.Notification.NotificationDescription.RebloggedYourPost" = "reblogged your post"; -"Scene.Notification.NotificationDescription.RequestToFollowYou" = "request to follow you"; +"Scene.Notification.NotificationDescription.FavoritedYourPost" = "さんがあなたの投稿をお気に入りに登録しました"; +"Scene.Notification.NotificationDescription.FollowedYou" = "さんにフォローされました"; +"Scene.Notification.NotificationDescription.MentionedYou" = "さんがあなたに返信しました"; +"Scene.Notification.NotificationDescription.PollHasEnded" = "アンケートが終了しました"; +"Scene.Notification.NotificationDescription.RebloggedYourPost" = "さんがあなたの投稿をブーストしました"; +"Scene.Notification.NotificationDescription.RequestToFollowYou" = "さんがあなたにフォローリクエストしました"; "Scene.Notification.Title.Everything" = "すべて"; "Scene.Notification.Title.Mentions" = "メンション"; "Scene.Preview.Keyboard.ClosePreview" = "プレビューを閉じる"; @@ -234,11 +243,11 @@ "Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "Unblock Account"; "Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "%@をミュートしますか?"; "Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Title" = "ミュートを解除"; -"Scene.Profile.SegmentedControl.About" = "About"; +"Scene.Profile.SegmentedControl.About" = "概要"; "Scene.Profile.SegmentedControl.Media" = "メディア"; "Scene.Profile.SegmentedControl.Posts" = "投稿"; -"Scene.Profile.SegmentedControl.PostsAndReplies" = "Posts and Replies"; -"Scene.Profile.SegmentedControl.Replies" = "リプライ"; +"Scene.Profile.SegmentedControl.PostsAndReplies" = "投稿と返信"; +"Scene.Profile.SegmentedControl.Replies" = "返信"; "Scene.Register.Error.Item.Agreement" = "契約"; "Scene.Register.Error.Item.Email" = "メール"; "Scene.Register.Error.Item.Locale" = "地域"; @@ -259,7 +268,7 @@ "Scene.Register.Error.Special.PasswordTooShort" = "パスワードが短すぎます(8文字以上)"; "Scene.Register.Error.Special.UsernameInvalid" = "ユーザーネームには、英数字とアンダースコアのみを使用してください。"; "Scene.Register.Error.Special.UsernameTooLong" = "ユーザー名が長すぎます(30文字以内)"; -"Scene.Register.Input.Avatar.Delete" = "消去"; +"Scene.Register.Input.Avatar.Delete" = "削除"; "Scene.Register.Input.DisplayName.Placeholder" = "表示名"; "Scene.Register.Input.Email.Placeholder" = "メール"; "Scene.Register.Input.Invite.RegistrationUserInviteRequest" = "なぜ参加したいと思ったのですか?"; @@ -274,15 +283,46 @@ "Scene.Register.Title" = "あなたのことを教えてください"; "Scene.Report.Content1" = "他に通報したい投稿はありますか?"; "Scene.Report.Content2" = "この通報についてモデレーターに伝達しておきたい事項はありますか?"; -"Scene.Report.ReportSentTitle" = "Thanks for reporting, we’ll look into this."; -"Scene.Report.Reported" = "REPORTED"; +"Scene.Report.ReportSentTitle" = "ご報告ありがとうございます、追って確認します。"; +"Scene.Report.Reported" = "報告済み"; "Scene.Report.Send" = "通報を送信"; "Scene.Report.SkipToSend" = "コメントなしで送信"; "Scene.Report.Step1" = "ステップ 1/2"; "Scene.Report.Step2" = "ステップ 2/2"; +"Scene.Report.StepFinal.BlockUser" = "Block %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Don’t want to see this?"; +"Scene.Report.StepFinal.MuteUser" = "Mute %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; +"Scene.Report.StepFinal.Unfollow" = "Unfollow"; +"Scene.Report.StepFinal.UnfollowUser" = "Unfollow %@"; +"Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Is there anything else we should know?"; +"Scene.Report.StepFour.Step4Of4" = "Step 4 of 4"; +"Scene.Report.StepOne.IDontLikeIt" = "I don’t like it"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "It is not something you want to see"; +"Scene.Report.StepOne.ItViolatesServerRules" = "It violates server rules"; +"Scene.Report.StepOne.ItsSomethingElse" = "It’s something else"; +"Scene.Report.StepOne.ItsSpam" = "It’s spam"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Select the best match"; +"Scene.Report.StepOne.Step1Of4" = "Step 1 of 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "The issue does not fit into other categories"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "What's wrong with this account?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "What's wrong with this post?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "What's wrong with %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Are there any posts that back up this report?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepThree.Step3Of4" = "Step 3 of 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "I just don’t like it"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepTwo.Step2Of4" = "Step 2 of 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Which rules are being violated?"; "Scene.Report.TextPlaceholder" = "追加コメントを入力"; "Scene.Report.Title" = "%@を通報"; -"Scene.Report.TitleReport" = "Report"; +"Scene.Report.TitleReport" = "報告する"; "Scene.Search.Recommend.Accounts.Description" = "以下のアカウントをフォローしてみてはいかがでしょうか?"; "Scene.Search.Recommend.Accounts.Follow" = "フォロー"; "Scene.Search.Recommend.Accounts.Title" = "おすすめのアカウント"; @@ -320,6 +360,7 @@ "Scene.ServerPicker.EmptyState.FindingServers" = "利用可能なサーバーの検索..."; "Scene.ServerPicker.EmptyState.NoResults" = "なし"; "Scene.ServerPicker.Input.Placeholder" = "サーバーを探す"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; "Scene.ServerPicker.Label.Category" = "カテゴリー"; "Scene.ServerPicker.Label.Language" = "言語"; "Scene.ServerPicker.Label.Users" = "ユーザー"; @@ -342,11 +383,11 @@ "Scene.Settings.Section.BoringZone.Privacy" = "プライバシーポリシー"; "Scene.Settings.Section.BoringZone.Terms" = "利用規約"; "Scene.Settings.Section.BoringZone.Title" = "アプリについて"; -"Scene.Settings.Section.LookAndFeel.Light" = "Light"; -"Scene.Settings.Section.LookAndFeel.ReallyDark" = "Really Dark"; -"Scene.Settings.Section.LookAndFeel.SortaDark" = "Sorta Dark"; -"Scene.Settings.Section.LookAndFeel.Title" = "Look and Feel"; -"Scene.Settings.Section.LookAndFeel.UseSystem" = "Use System"; +"Scene.Settings.Section.LookAndFeel.Light" = "ライト"; +"Scene.Settings.Section.LookAndFeel.ReallyDark" = "ブラック"; +"Scene.Settings.Section.LookAndFeel.SortaDark" = "ダーク"; +"Scene.Settings.Section.LookAndFeel.Title" = "テーマ"; +"Scene.Settings.Section.LookAndFeel.UseSystem" = "端末の設定を使う"; "Scene.Settings.Section.Notifications.Boosts" = "ブースト"; "Scene.Settings.Section.Notifications.Favorites" = "お気に入り登録"; "Scene.Settings.Section.Notifications.Follows" = "フォロー"; @@ -359,7 +400,7 @@ "Scene.Settings.Section.Notifications.Trigger.Title" = "通知を受け取る"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "アバターのアニメーションを無効化する"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "絵文字のアニメーションを無効化する"; -"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Open links in Mastodon"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Mastodonでリンクを開く"; "Scene.Settings.Section.Preference.Title" = "環境設定"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "真っ黒なダークテーマを使用する"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "既定のブラウザでリンクを開く"; @@ -371,7 +412,7 @@ "Scene.SuggestionAccount.Title" = "フォローする人を探す"; "Scene.Thread.BackTitle" = "投稿"; "Scene.Thread.Title" = "%@の投稿"; -"Scene.Welcome.GetStarted" = "Get Started"; +"Scene.Welcome.GetStarted" = "はじめる"; "Scene.Welcome.LogIn" = "ログイン"; "Scene.Welcome.Slogan" = "ソーシャルネットワーキングを、あなたの手の中に."; "Scene.Wizard.AccessibilityHint" = "チュートリアルを閉じるには、ダブルタップしてください"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.stringsdict index 8dfc95079..95c35172e 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.stringsdict @@ -153,7 +153,7 @@ NSStringFormatValueTypeKey ld other - %ld people talking + %ld人が投稿 plural.count.following diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.strings index 55b8a3a62..bbc9856c3 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.strings @@ -98,16 +98,17 @@ Ma ulac aɣilif, senqed tuqqna-inek internet."; "Common.Controls.Status.Actions.Menu" = "Umuɣ"; "Common.Controls.Status.Actions.Reblog" = "Aɛiwed n usuffeɣ"; "Common.Controls.Status.Actions.Reply" = "Err"; -"Common.Controls.Status.Actions.ShowGif" = "Show GIF"; -"Common.Controls.Status.Actions.ShowImage" = "Show image"; -"Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; -"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tap then hold to show menu"; +"Common.Controls.Status.Actions.ShowGif" = "Sken GIF"; +"Common.Controls.Status.Actions.ShowImage" = "Sken tugna"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Sken ameɣri n tvidyut"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Sit teǧǧeḍ aḍad-ik•im i wakken ad d-iffeɣ wumuɣ"; "Common.Controls.Status.Actions.Unfavorite" = "Kkes seg yismenyifen"; "Common.Controls.Status.Actions.Unreblog" = "Sefsex allus n usuffeɣ"; "Common.Controls.Status.ContentWarning" = "Alɣu n ugbur"; "Common.Controls.Status.MediaContentWarning" = "Sit anida tebɣiḍ i wakken ad twaliḍ"; "Common.Controls.Status.Poll.Closed" = "Ifukk"; "Common.Controls.Status.Poll.Vote" = "Dɣeṛ"; +"Common.Controls.Status.SensitiveContent" = "Agbur amḥulfu"; "Common.Controls.Status.ShowPost" = "Sken-d tasuffeɣt"; "Common.Controls.Status.ShowUserProfile" = "Ssken-d amaɣnu n useqdac"; "Common.Controls.Status.Tag.Email" = "Imayl"; @@ -116,7 +117,7 @@ Ma ulac aɣilif, senqed tuqqna-inek internet."; "Common.Controls.Status.Tag.Link" = "Aseɣwen"; "Common.Controls.Status.Tag.Mention" = "Tabdart"; "Common.Controls.Status.Tag.Url" = "URL"; -"Common.Controls.Status.TapToReveal" = "Tap to reveal"; +"Common.Controls.Status.TapToReveal" = "Sit i uskan"; "Common.Controls.Status.UserReblogged" = "Tettwasuffeɣ-d %@ i tikkelt-nniḍen"; "Common.Controls.Status.UserRepliedTo" = "Yerra ɣef %@"; "Common.Controls.Status.Visibility.Direct" = "D ineḍfaren-is kan i izemren ad walin tsuffeɣ-a."; @@ -200,9 +201,17 @@ Ad d-yettwasali ɣef Mastodon."; "Scene.ConfirmEmail.OpenEmailApp.Title" = "Sefqed Tanaka-inek."; "Scene.ConfirmEmail.Subtitle" = "Sit ɣef useɣwen i ak-n-uznen i wakken ad tesneqdeḍ amiḍan-ik."; "Scene.ConfirmEmail.Title" = "Taɣawsa taneggarut."; +"Scene.Discovery.Intro" = "Tigi d tisuffaɣ i d-ijebbden s waṭas deg tama-inek•inem n Mastodon."; +"Scene.Discovery.Tabs.Community" = "Community"; +"Scene.Discovery.Tabs.ForYou" = "I kečč·kem"; +"Scene.Discovery.Tabs.Hashtags" = "Ihacṭagen"; +"Scene.Discovery.Tabs.News" = "Isallen"; +"Scene.Discovery.Tabs.Posts" = "Tisuffaɣ"; "Scene.Favorite.Title" = "Ismenyifen-ik·im"; "Scene.Follower.Footer" = "Ineḍfaren seg yiqeddacen-nniḍen ur d-ttwaskanen ara."; "Scene.Following.Footer" = "Ineḍfaren seg yiqeddacen-nniḍen ur d-ttwaskanen ara."; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Tissufaɣ timaynutin"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Beṛṛa n tuqqna"; "Scene.HomeTimeline.NavigationBarState.Published" = "Yettwasuffeɣ!"; @@ -221,10 +230,10 @@ Ad d-yettwasali ɣef Mastodon."; "Scene.Preview.Keyboard.ClosePreview" = "Mdel timeẓri"; "Scene.Preview.Keyboard.ShowNext" = "Sken uḍfir"; "Scene.Preview.Keyboard.ShowPrevious" = "Sken udfir"; -"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Double tap to open the list"; -"Scene.Profile.Accessibility.EditAvatarImage" = "Edit avatar image"; -"Scene.Profile.Accessibility.ShowAvatarImage" = "Show avatar image"; -"Scene.Profile.Accessibility.ShowBannerImage" = "Show banner image"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Sin isitiyen i twaledyawt n tebdart"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Ẓreg tugna n avaṭar"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Sken tugna n avaṭar"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Sken tugna n uɣerrac"; "Scene.Profile.Dashboard.Followers" = "imeḍfaren"; "Scene.Profile.Dashboard.Following" = "iṭafaṛ"; "Scene.Profile.Dashboard.Posts" = "tisuffaɣ"; @@ -285,6 +294,37 @@ Ad d-yettwasali ɣef Mastodon."; "Scene.Report.SkipToSend" = "Azen s war awennit"; "Scene.Report.Step1" = "Aḥric 1 seg 2"; "Scene.Report.Step2" = "Aḥric 2 seg 2"; +"Scene.Report.StepFinal.BlockUser" = "Block %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Don’t want to see this?"; +"Scene.Report.StepFinal.MuteUser" = "Mute %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; +"Scene.Report.StepFinal.Unfollow" = "Unfollow"; +"Scene.Report.StepFinal.UnfollowUser" = "Unfollow %@"; +"Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Is there anything else we should know?"; +"Scene.Report.StepFour.Step4Of4" = "Step 4 of 4"; +"Scene.Report.StepOne.IDontLikeIt" = "I don’t like it"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "It is not something you want to see"; +"Scene.Report.StepOne.ItViolatesServerRules" = "It violates server rules"; +"Scene.Report.StepOne.ItsSomethingElse" = "It’s something else"; +"Scene.Report.StepOne.ItsSpam" = "It’s spam"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Select the best match"; +"Scene.Report.StepOne.Step1Of4" = "Step 1 of 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "The issue does not fit into other categories"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "What's wrong with this account?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "What's wrong with this post?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "What's wrong with %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Are there any posts that back up this report?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepThree.Step3Of4" = "Step 3 of 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "I just don’t like it"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepTwo.Step2Of4" = "Step 2 of 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Which rules are being violated?"; "Scene.Report.TextPlaceholder" = "Aru neɣ senteḍ iwenniten-nniḍen"; "Scene.Report.Title" = "Aneqqis %@"; "Scene.Report.TitleReport" = "Aneqqis"; @@ -311,7 +351,7 @@ Ad d-yettwasali ɣef Mastodon."; "Scene.ServerPicker.Button.Category.AllAccessiblityDescription" = "Taggayt: Akk"; "Scene.ServerPicker.Button.Category.Art" = "taẓuri"; "Scene.ServerPicker.Button.Category.Food" = "učči"; -"Scene.ServerPicker.Button.Category.Furry" = "furry"; +"Scene.ServerPicker.Button.Category.Furry" = "s taḍut"; "Scene.ServerPicker.Button.Category.Games" = "uraren"; "Scene.ServerPicker.Button.Category.General" = "amatu"; "Scene.ServerPicker.Button.Category.Journalism" = "taɣamsa"; @@ -325,6 +365,7 @@ Ad d-yettwasali ɣef Mastodon."; "Scene.ServerPicker.EmptyState.FindingServers" = "Tifin n yiqeddacen yellan..."; "Scene.ServerPicker.EmptyState.NoResults" = "Ulac igemmaḍ"; "Scene.ServerPicker.Input.Placeholder" = "Nadi timɣiwnin"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; "Scene.ServerPicker.Label.Category" = "TAGGAYT"; "Scene.ServerPicker.Label.Language" = "TUTLAYT"; "Scene.ServerPicker.Label.Users" = "ISEQDACEN"; @@ -364,7 +405,7 @@ Ad d-yettwasali ɣef Mastodon."; "Scene.Settings.Section.Notifications.Trigger.Title" = "Selɣu-yi-d mi ara"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Sens ivaṭaren yettembiwilen"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Sens imujiten yettembiwilen"; -"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Open links in Mastodon"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Ldi iseɣwan deg Mastodon"; "Scene.Settings.Section.Preference.Title" = "Imenyafen"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Askar aberkan n tidet"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Seqdec iminig amezwer i twaledyawt n yiseɣwan"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.stringsdict index c3d72dda2..4ccb271fa 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.stringsdict @@ -125,9 +125,9 @@ NSStringFormatValueTypeKey ld one - 1 reply + 1 tririt other - %ld replies + %ld tririyin plural.count.vote diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.strings index 608163f4e..6f46f990d 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.strings @@ -108,6 +108,7 @@ Jkx girêdana înternetê xwe kontrol bike."; "Common.Controls.Status.MediaContentWarning" = "Ji bo eşkerekirinê li derekî bitikîne"; "Common.Controls.Status.Poll.Closed" = "Girtî"; "Common.Controls.Status.Poll.Vote" = "Deng bide"; +"Common.Controls.Status.SensitiveContent" = "Naveroka hestiyarî"; "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"; @@ -201,9 +202,17 @@ Profîla te ji wan ra wiha xuya dike."; "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.Discovery.Intro" = "Ev şandiyên ku di quncika Mastodon balê dikişîne."; +"Scene.Discovery.Tabs.Community" = "Civak"; +"Scene.Discovery.Tabs.ForYou" = "Ji bo te"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtag"; +"Scene.Discovery.Tabs.News" = "Nûçe"; +"Scene.Discovery.Tabs.Posts" = "Şandî"; "Scene.Favorite.Title" = "Bijarteyê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.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Şandiyên nû bibîne"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Derhêl"; "Scene.HomeTimeline.NavigationBarState.Published" = "Hate weşandin!"; @@ -286,6 +295,37 @@ girêdanê bitikne da ku ajimêra xwe bidî piştrastkirin."; "Scene.Report.SkipToSend" = "Bêyî şirove bişîne"; "Scene.Report.Step1" = "Gav 1 ji 2"; "Scene.Report.Step2" = "Gav 2 ji 2"; +"Scene.Report.StepFinal.BlockUser" = "Block %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Don’t want to see this?"; +"Scene.Report.StepFinal.MuteUser" = "Mute %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; +"Scene.Report.StepFinal.Unfollow" = "Unfollow"; +"Scene.Report.StepFinal.UnfollowUser" = "Unfollow %@"; +"Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Is there anything else we should know?"; +"Scene.Report.StepFour.Step4Of4" = "Step 4 of 4"; +"Scene.Report.StepOne.IDontLikeIt" = "I don’t like it"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "It is not something you want to see"; +"Scene.Report.StepOne.ItViolatesServerRules" = "It violates server rules"; +"Scene.Report.StepOne.ItsSomethingElse" = "It’s something else"; +"Scene.Report.StepOne.ItsSpam" = "It’s spam"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Select the best match"; +"Scene.Report.StepOne.Step1Of4" = "Step 1 of 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "The issue does not fit into other categories"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "What's wrong with this account?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "What's wrong with this post?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "What's wrong with %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Are there any posts that back up this report?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepThree.Step3Of4" = "Step 3 of 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "I just don’t like it"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepTwo.Step2Of4" = "Step 2 of 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Which rules are being violated?"; "Scene.Report.TextPlaceholder" = "Şiroveyên daxwazkirê binivîsine an jî pê ve bike"; "Scene.Report.Title" = "%@ ragihîne"; "Scene.Report.TitleReport" = "Ragihandin"; @@ -325,14 +365,14 @@ girêdanê bitikne da ku ajimêra xwe bidî piştrastkirin."; "Scene.ServerPicker.EmptyState.BadNetwork" = "Di dema barkirina daneyan da çewtî derket. Girêdana xwe ya înternetê kontrol bike."; "Scene.ServerPicker.EmptyState.FindingServers" = "Peydakirina rajekarên berdest..."; "Scene.ServerPicker.EmptyState.NoResults" = "Encam tune"; -"Scene.ServerPicker.Input.Placeholder" = "Rajekarekî bibîne an jî beşdarî ya xwe bibe..."; +"Scene.ServerPicker.Input.Placeholder" = "Li rajekaran bigere"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; "Scene.ServerPicker.Label.Category" = "BEŞ"; "Scene.ServerPicker.Label.Language" = "ZIMAN"; "Scene.ServerPicker.Label.Users" = "BIKARHÊNER"; "Scene.ServerPicker.Subtitle" = "Li gorî berjewendî, herêm, an jî armancek gelemperî civakekê hilbijêre."; "Scene.ServerPicker.SubtitleExtend" = "Li gorî berjewendî, herêm, an jî armancek gelemperî civakekê hilbijêre. Her civakek ji hêla rêxistinek an kesek bi tevahî serbixwe ve tê xebitandin."; -"Scene.ServerPicker.Title" = "Rajekarekê hilbijêre, -Her kîjan rajekar be."; +"Scene.ServerPicker.Title" = "Mastodon ji bikarhênerên di civakên cuda de pêk tê."; "Scene.ServerRules.Button.Confirm" = "Ez dipejirînim"; "Scene.ServerRules.PrivacyPolicy" = "polîtikaya nihêniyê"; "Scene.ServerRules.Prompt" = "Bi domandinê, tu ji bo %@ di bin mercên bikaranînê û polîtîkaya nepenîtiyê dipejirînî."; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/nl.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/nl.lproj/Localizable.strings index 6a19a4bf6..f0b5eb42d 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/nl.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/nl.lproj/Localizable.strings @@ -1,63 +1,63 @@ -"Common.Alerts.BlockDomain.BlockEntireDomain" = "Domein blokkeren"; -"Common.Alerts.BlockDomain.Title" = "Weet u zeker dat u alles van %@ wilt blokkeren? In de meeste gevallen is het blokkeren of negeren van een paar specifieke personen voldoende en wenselijker. U zult geen berichten meer zien van dit domein en volgers van dit domein worden verwijderd."; +"Common.Alerts.BlockDomain.BlockEntireDomain" = "Blokkeer domein"; +"Common.Alerts.BlockDomain.Title" = "Weet je het echt heel erg zeker dat je alles van %@ wilt negeren? In de meeste gevallen is het blokkeren of negeren van een paar specifieke personen voldoende. Je zult geen berichten van deze server op openbare tijdlijnen zien of in jouw meldingen. Jouw volgers van deze server worden verwijderd."; "Common.Alerts.CleanCache.Message" = "Cache-geheugen (%@) succesvol gewist."; "Common.Alerts.CleanCache.Title" = "Cache-geheugen Wissen"; "Common.Alerts.Common.PleaseTryAgain" = "Probeer het opnieuw."; "Common.Alerts.Common.PleaseTryAgainLater" = "Probeer het later nog eens."; -"Common.Alerts.DeletePost.Message" = "Are you sure you want to delete this post?"; -"Common.Alerts.DeletePost.Title" = "Weet u zeker dat u dit bericht wilt verwijderen?"; +"Common.Alerts.DeletePost.Message" = "Weet je zeker dat je dit bericht wilt verwijderen?"; +"Common.Alerts.DeletePost.Title" = "Verwijder Bericht"; "Common.Alerts.DiscardPostContent.Message" = "Bevestig het verwijderen van het concept bericht."; -"Common.Alerts.DiscardPostContent.Title" = "Concept Verwijderen"; -"Common.Alerts.EditProfileFailure.Message" = "Het profiel kan niet bewerkt worden. Probeer het opnieuw."; +"Common.Alerts.DiscardPostContent.Title" = "Verwijder concept"; +"Common.Alerts.EditProfileFailure.Message" = "Je profiel kan niet bewerkt worden. Probeer het opnieuw."; "Common.Alerts.EditProfileFailure.Title" = "Profiel bewerken mislukt"; -"Common.Alerts.PublishPostFailure.AttachmentsMessage.MoreThanOneVideo" = "Slechts één video kan aan een bericht worden gekoppeld."; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.MoreThanOneVideo" = "Kan niet meer dan één video toevoegen."; "Common.Alerts.PublishPostFailure.AttachmentsMessage.VideoAttachWithPhoto" = "Een video kan niet aan een bericht met afbeeldingen worden gekoppeld."; -"Common.Alerts.PublishPostFailure.Message" = "Het publiceren van het bericht is mislukt. Controleer alstublieft uw internetverbinding."; +"Common.Alerts.PublishPostFailure.Message" = "Het publiceren van het bericht is mislukt. Controleer je internetverbinding."; "Common.Alerts.PublishPostFailure.Title" = "Publicatiefout"; "Common.Alerts.SavePhotoFailure.Message" = "Geef toestemming om de foto op te slaan."; "Common.Alerts.SavePhotoFailure.Title" = "Foto Opslaan Mislukt"; "Common.Alerts.ServerError.Title" = "Serverfout"; -"Common.Alerts.SignOut.Confirm" = "Afmelden"; -"Common.Alerts.SignOut.Message" = "Weet u zeker dat u zich wilt afmelden?"; -"Common.Alerts.SignOut.Title" = "Afmelden"; +"Common.Alerts.SignOut.Confirm" = "Log-uit"; +"Common.Alerts.SignOut.Message" = "Weet je zeker dat je wilt uitloggen?"; +"Common.Alerts.SignOut.Title" = "Log-uit"; "Common.Alerts.SignUpFailure.Title" = "Registratiefout"; "Common.Alerts.VoteFailure.PollEnded" = "De peiling is beëindigd"; "Common.Alerts.VoteFailure.Title" = "Stemmen Mislukt"; -"Common.Controls.Actions.Add" = "Toevoegen"; +"Common.Controls.Actions.Add" = "Voeg toe"; "Common.Controls.Actions.Back" = "Terug"; "Common.Controls.Actions.BlockDomain" = "Blokkeer %@"; -"Common.Controls.Actions.Cancel" = "Annuleren"; -"Common.Controls.Actions.Compose" = "Compose"; +"Common.Controls.Actions.Cancel" = "Annuleer"; +"Common.Controls.Actions.Compose" = "Schrijf bericht"; "Common.Controls.Actions.Confirm" = "Bevestigen"; -"Common.Controls.Actions.Continue" = "Doorgaan"; -"Common.Controls.Actions.CopyPhoto" = "Foto kopiëren"; -"Common.Controls.Actions.Delete" = "Verwijderen"; +"Common.Controls.Actions.Continue" = "Ga verder"; +"Common.Controls.Actions.CopyPhoto" = "Kopieer foto"; +"Common.Controls.Actions.Delete" = "Verwijder"; "Common.Controls.Actions.Discard" = "Weggooien"; "Common.Controls.Actions.Done" = "Klaar"; -"Common.Controls.Actions.Edit" = "Bewerken"; +"Common.Controls.Actions.Edit" = "Bewerk"; "Common.Controls.Actions.FindPeople" = "Zoek mensen om te volgen"; "Common.Controls.Actions.ManuallySearch" = "Handmatig zoeken"; "Common.Controls.Actions.Next" = "Volgende"; -"Common.Controls.Actions.Ok" = "Oké"; +"Common.Controls.Actions.Ok" = "Ok"; "Common.Controls.Actions.Open" = "Open"; -"Common.Controls.Actions.OpenInBrowser" = "Open in Browser"; +"Common.Controls.Actions.OpenInBrowser" = "Open in browser"; "Common.Controls.Actions.OpenInSafari" = "Open in Safari"; "Common.Controls.Actions.Preview" = "Voorvertoning"; "Common.Controls.Actions.Previous" = "Vorige"; -"Common.Controls.Actions.Remove" = "Verwijderen"; -"Common.Controls.Actions.Reply" = "Reageren"; +"Common.Controls.Actions.Remove" = "Verwijder"; +"Common.Controls.Actions.Reply" = "Reageer"; "Common.Controls.Actions.ReportUser" = "Rapporteer %@"; -"Common.Controls.Actions.Save" = "Opslaan"; -"Common.Controls.Actions.SavePhoto" = "Foto Opslaan"; +"Common.Controls.Actions.Save" = "Bewaar"; +"Common.Controls.Actions.SavePhoto" = "Bewaar foto"; "Common.Controls.Actions.SeeMore" = "Meer"; "Common.Controls.Actions.Settings" = "Instellingen"; -"Common.Controls.Actions.Share" = "Delen"; -"Common.Controls.Actions.SharePost" = "Bericht Delen"; +"Common.Controls.Actions.Share" = "Deel"; +"Common.Controls.Actions.SharePost" = "Deel bericht"; "Common.Controls.Actions.ShareUser" = "Delen %@"; "Common.Controls.Actions.SignIn" = "Aanmelden"; "Common.Controls.Actions.SignUp" = "Registreren"; "Common.Controls.Actions.Skip" = "Overslaan"; -"Common.Controls.Actions.TakePhoto" = "Foto Maken"; +"Common.Controls.Actions.TakePhoto" = "Maak foto"; "Common.Controls.Actions.TryAgain" = "Probeer Opnieuw"; "Common.Controls.Actions.UnblockDomain" = "Deblokkeer %@"; "Common.Controls.Friendship.Block" = "Blokkeren"; @@ -79,7 +79,7 @@ "Common.Controls.Keyboard.Common.ComposeNewPost" = "Nieuw Bericht Opstellen"; "Common.Controls.Keyboard.Common.OpenSettings" = "Open Instellingen"; "Common.Controls.Keyboard.Common.ShowFavorites" = "Favorieten Weergeven"; -"Common.Controls.Keyboard.Common.SwitchToTab" = "Wisselen naar %@"; +"Common.Controls.Keyboard.Common.SwitchToTab" = "Overschakelen naar %@"; "Common.Controls.Keyboard.SegmentedControl.NextSection" = "Volgende Sectie"; "Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "Vorige Sectie"; "Common.Controls.Keyboard.Timeline.NextStatus" = "Volgend Bericht"; @@ -88,25 +88,26 @@ "Common.Controls.Keyboard.Timeline.OpenStatus" = "Open Bericht"; "Common.Controls.Keyboard.Timeline.PreviewImage" = "Voorvertoning Afbeelding"; "Common.Controls.Keyboard.Timeline.PreviousStatus" = "Vorig Bericht"; -"Common.Controls.Keyboard.Timeline.ReplyStatus" = "Reageren"; -"Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "Inhoudswaarschuwing Omschakelen"; -"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Favoriet Omschakelen bij Bericht"; +"Common.Controls.Keyboard.Timeline.ReplyStatus" = "Reageer"; +"Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "Inhoudswaarschuwing"; +"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Favoriet in-/uitschakelen bij Post"; "Common.Controls.Keyboard.Timeline.ToggleReblog" = "Delen bij berichten omschakelen"; "Common.Controls.Status.Actions.Favorite" = "Toevoegen aan Favorieten"; -"Common.Controls.Status.Actions.Hide" = "Hide"; +"Common.Controls.Status.Actions.Hide" = "Verberg"; "Common.Controls.Status.Actions.Menu" = "Menu"; "Common.Controls.Status.Actions.Reblog" = "Delen"; "Common.Controls.Status.Actions.Reply" = "Reageren"; -"Common.Controls.Status.Actions.ShowGif" = "Show GIF"; -"Common.Controls.Status.Actions.ShowImage" = "Show image"; -"Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; -"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tap then hold to show menu"; +"Common.Controls.Status.Actions.ShowGif" = "GIF weergeven"; +"Common.Controls.Status.Actions.ShowImage" = "Toon afbeelding"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Toon videospeler"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tik en houd vast om menu te tonen"; "Common.Controls.Status.Actions.Unfavorite" = "Verwijderen uit Favorieten"; "Common.Controls.Status.Actions.Unreblog" = "Delen ongedaan maken"; "Common.Controls.Status.ContentWarning" = "Inhoudswaarschuwing"; "Common.Controls.Status.MediaContentWarning" = "Tap hier om te tonen"; "Common.Controls.Status.Poll.Closed" = "Gesloten"; "Common.Controls.Status.Poll.Vote" = "Stemmen"; +"Common.Controls.Status.SensitiveContent" = "Gevoelige inhoud"; "Common.Controls.Status.ShowPost" = "Toon Bericht"; "Common.Controls.Status.ShowUserProfile" = "Toon Gebruikersprofiel"; "Common.Controls.Status.Tag.Email" = "Email"; @@ -115,17 +116,17 @@ "Common.Controls.Status.Tag.Link" = "Link"; "Common.Controls.Status.Tag.Mention" = "Vermelden"; "Common.Controls.Status.Tag.Url" = "URL"; -"Common.Controls.Status.TapToReveal" = "Tap to reveal"; -"Common.Controls.Status.UserReblogged" = "%@ gedeeld"; +"Common.Controls.Status.TapToReveal" = "Tik om te onthullen"; +"Common.Controls.Status.UserReblogged" = "%@ heeft gedeeld"; "Common.Controls.Status.UserRepliedTo" = "Reactie op %@"; -"Common.Controls.Status.Visibility.Direct" = "Only mentioned user can see this post."; -"Common.Controls.Status.Visibility.Private" = "Only their followers can see this post."; -"Common.Controls.Status.Visibility.PrivateFromMe" = "Only my followers can see this post."; -"Common.Controls.Status.Visibility.Unlisted" = "Everyone can see this post but not display in the public timeline."; +"Common.Controls.Status.Visibility.Direct" = "Alleen de vermelde persoon kan dit bericht zien."; +"Common.Controls.Status.Visibility.Private" = "Alleen hun volgers kunnen dit bericht zien."; +"Common.Controls.Status.Visibility.PrivateFromMe" = "Alleen mijn volgers kunnen dit bericht zien."; +"Common.Controls.Status.Visibility.Unlisted" = "Iedereen kan dit bericht zien maar het wordt niet in de publieke tijdlijn getoond."; "Common.Controls.Tabs.Home" = "Start"; "Common.Controls.Tabs.Notification" = "Melding"; "Common.Controls.Tabs.Profile" = "Profiel"; -"Common.Controls.Tabs.Search" = "Zoeken"; +"Common.Controls.Tabs.Search" = "Zoek"; "Common.Controls.Timeline.Filtered" = "Gefilterd"; "Common.Controls.Timeline.Header.BlockedWarning" = "U kunt het profiel van deze gebruiker niet bekijken zolang u geblokkeerd bent."; "Common.Controls.Timeline.Header.BlockingWarning" = "U kunt het profiel van deze geblokkeerde gebruiker niet bekijken. @@ -140,9 +141,9 @@ Uw profiel ziet er zo uit voor hen."; "Common.Controls.Timeline.Loader.LoadingMissingPosts" = "Resterende berichten laden..."; "Common.Controls.Timeline.Loader.ShowMoreReplies" = "Toon meer reacties"; "Common.Controls.Timeline.Timestamp.Now" = "Nu"; -"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" = "Voeg account toe"; +"Scene.AccountList.DismissAccountSwitcher" = "Annuleer account wisselen"; +"Scene.AccountList.TabBarHint" = "Huidige geselecteerde profiel: %@. Dubbel-tik en houd vast om account te wisselen"; "Scene.Compose.Accessibility.AppendAttachment" = "Bijlage Toevoegen"; "Scene.Compose.Accessibility.AppendPoll" = "Peiling Toevoegen"; "Scene.Compose.Accessibility.CustomEmojiPicker" = "Eigen Emojikiezer"; @@ -184,7 +185,7 @@ Uw profiel ziet er zo uit voor hen."; "Scene.Compose.Visibility.Public" = "Openbaar"; "Scene.Compose.Visibility.Unlisted" = "Niet-vermeld"; "Scene.ConfirmEmail.Button.OpenEmailApp" = "Email Openen"; -"Scene.ConfirmEmail.Button.Resend" = "Resend"; +"Scene.ConfirmEmail.Button.Resend" = "Verstuur opnieuw"; "Scene.ConfirmEmail.DontReceiveEmail.Description" = "Controleer of uw emailadres correct is en of the email in de ongewenste email filter terecht is gekomen."; "Scene.ConfirmEmail.DontReceiveEmail.ResendEmail" = "Email Opnieuw Versturen"; "Scene.ConfirmEmail.DontReceiveEmail.Title" = "Controleer uw emailadres"; @@ -195,9 +196,17 @@ Uw profiel ziet er zo uit voor hen."; "Scene.ConfirmEmail.Subtitle" = "We hebben een e-mail gestuurd naar %@, klik op de link om uw account te bevestigen."; "Scene.ConfirmEmail.Title" = "Nog één ding."; +"Scene.Discovery.Intro" = "Dit zijn de berichten die populair zijn in jouw Mastodon-kringen."; +"Scene.Discovery.Tabs.Community" = "Community"; +"Scene.Discovery.Tabs.ForYou" = "Voor jou"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtags"; +"Scene.Discovery.Tabs.News" = "Nieuws"; +"Scene.Discovery.Tabs.Posts" = "Berichten"; "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.Follower.Footer" = "Volgers van andere servers worden niet weergegeven."; +"Scene.Following.Footer" = "Volgers van andere servers worden niet weergegeven."; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Bekijk nieuwe berichten"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Offline"; "Scene.HomeTimeline.NavigationBarState.Published" = "Gepubliceerd!"; @@ -205,39 +214,39 @@ klik op de link om uw account te bevestigen."; "Scene.HomeTimeline.Title" = "Start"; "Scene.Notification.Keyobard.ShowEverything" = "Alles weergeven"; "Scene.Notification.Keyobard.ShowMentions" = "Vermeldingen weergeven"; -"Scene.Notification.NotificationDescription.FavoritedYourPost" = "favorited your post"; -"Scene.Notification.NotificationDescription.FollowedYou" = "followed you"; -"Scene.Notification.NotificationDescription.MentionedYou" = "mentioned you"; -"Scene.Notification.NotificationDescription.PollHasEnded" = "poll has ended"; -"Scene.Notification.NotificationDescription.RebloggedYourPost" = "reblogged your post"; -"Scene.Notification.NotificationDescription.RequestToFollowYou" = "request to follow you"; +"Scene.Notification.NotificationDescription.FavoritedYourPost" = "heeft je bericht als favoriet gemrkeerd"; +"Scene.Notification.NotificationDescription.FollowedYou" = "volgt je"; +"Scene.Notification.NotificationDescription.MentionedYou" = "noemde je"; +"Scene.Notification.NotificationDescription.PollHasEnded" = "de peiling is beëindigd"; +"Scene.Notification.NotificationDescription.RebloggedYourPost" = "deelde je bericht"; +"Scene.Notification.NotificationDescription.RequestToFollowYou" = "verzoek je te volgen"; "Scene.Notification.Title.Everything" = "Alles"; "Scene.Notification.Title.Mentions" = "Vermeldingen"; "Scene.Preview.Keyboard.ClosePreview" = "Voorbeeldweergave Sluiten"; "Scene.Preview.Keyboard.ShowNext" = "Volgende"; "Scene.Preview.Keyboard.ShowPrevious" = "Vorige"; -"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Double tap to open the list"; -"Scene.Profile.Accessibility.EditAvatarImage" = "Edit avatar image"; -"Scene.Profile.Accessibility.ShowAvatarImage" = "Show avatar image"; -"Scene.Profile.Accessibility.ShowBannerImage" = "Show banner image"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Dubbel tikken om de lijst te openen"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Bewerk avatar-afbeelding"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Toon avatar-afbeelding"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Toon banner-afbeelding"; "Scene.Profile.Dashboard.Followers" = "volgers"; "Scene.Profile.Dashboard.Following" = "volgend"; "Scene.Profile.Dashboard.Posts" = "berichten"; "Scene.Profile.Fields.AddRow" = "Rij Toevoegen"; "Scene.Profile.Fields.Placeholder.Content" = "Inhoud"; "Scene.Profile.Fields.Placeholder.Label" = "Etiket"; -"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Confirm to block %@"; -"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Block Account"; -"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "Confirm to mute %@"; -"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "Mute Account"; -"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "Confirm to unblock %@"; -"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "Unblock Account"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Bevestig om %@ te blokkeren"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Blokkeer account"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "Bevestig om %@ te negeren"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "Negeer account"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "Bevestig om %@ te deblokkeren"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "Deblokkeer Account"; "Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "Bevestig om %@ te negeren"; "Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Title" = "Account Negeren"; -"Scene.Profile.SegmentedControl.About" = "About"; +"Scene.Profile.SegmentedControl.About" = "Informatie"; "Scene.Profile.SegmentedControl.Media" = "Media"; "Scene.Profile.SegmentedControl.Posts" = "Berichten"; -"Scene.Profile.SegmentedControl.PostsAndReplies" = "Posts and Replies"; +"Scene.Profile.SegmentedControl.PostsAndReplies" = "Berichten en reacties"; "Scene.Profile.SegmentedControl.Replies" = "Reacties"; "Scene.Register.Error.Item.Agreement" = "Overeenkomst"; "Scene.Register.Error.Item.Email" = "Email"; @@ -263,26 +272,57 @@ klik op de link om uw account te bevestigen."; "Scene.Register.Input.DisplayName.Placeholder" = "weergavenaam"; "Scene.Register.Input.Email.Placeholder" = "email"; "Scene.Register.Input.Invite.RegistrationUserInviteRequest" = "Waarom wil u zich hier registreren?"; -"Scene.Register.Input.Password.Accessibility.Checked" = "checked"; -"Scene.Register.Input.Password.Accessibility.Unchecked" = "unchecked"; -"Scene.Register.Input.Password.CharacterLimit" = "8 characters"; +"Scene.Register.Input.Password.Accessibility.Checked" = "ingeschakeld"; +"Scene.Register.Input.Password.Accessibility.Unchecked" = "uitgeschakeld"; +"Scene.Register.Input.Password.CharacterLimit" = "8 tekens"; "Scene.Register.Input.Password.Hint" = "Uw wachtwoord moet ten minste acht tekens bevatten"; "Scene.Register.Input.Password.Placeholder" = "wachtwoord"; -"Scene.Register.Input.Password.Require" = "Your password needs at least:"; +"Scene.Register.Input.Password.Require" = "Je wachtwoord moet ten minste:"; "Scene.Register.Input.Username.DuplicatePrompt" = "Deze gebruikersnaam is al in gebruik."; "Scene.Register.Input.Username.Placeholder" = "gebruikersnaam"; "Scene.Register.Title" = "Vertel ons over uzelf."; "Scene.Report.Content1" = "Zijn er nog meer berichten die u aan het rapport wilt toevoegen?"; "Scene.Report.Content2" = "Is er iets anders over dit rapport dat de moderators zouden moeten weten?"; -"Scene.Report.ReportSentTitle" = "Thanks for reporting, we’ll look into this."; -"Scene.Report.Reported" = "REPORTED"; +"Scene.Report.ReportSentTitle" = "Bedankt voor het rapporteren, we zullen hiernaar kijken."; +"Scene.Report.Reported" = "Gerapporteerd"; "Scene.Report.Send" = "Stuur rapport"; "Scene.Report.SkipToSend" = "Verstuur zonder opmerkingen"; "Scene.Report.Step1" = "Stap 1 van 2"; "Scene.Report.Step2" = "Stap 2 van 2"; +"Scene.Report.StepFinal.BlockUser" = "Block %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Don’t want to see this?"; +"Scene.Report.StepFinal.MuteUser" = "Mute %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; +"Scene.Report.StepFinal.Unfollow" = "Unfollow"; +"Scene.Report.StepFinal.UnfollowUser" = "Unfollow %@"; +"Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Is there anything else we should know?"; +"Scene.Report.StepFour.Step4Of4" = "Step 4 of 4"; +"Scene.Report.StepOne.IDontLikeIt" = "I don’t like it"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "It is not something you want to see"; +"Scene.Report.StepOne.ItViolatesServerRules" = "It violates server rules"; +"Scene.Report.StepOne.ItsSomethingElse" = "It’s something else"; +"Scene.Report.StepOne.ItsSpam" = "It’s spam"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Select the best match"; +"Scene.Report.StepOne.Step1Of4" = "Step 1 of 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "The issue does not fit into other categories"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "What's wrong with this account?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "What's wrong with this post?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "What's wrong with %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Are there any posts that back up this report?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepThree.Step3Of4" = "Step 3 of 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "I just don’t like it"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepTwo.Step2Of4" = "Step 2 of 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Which rules are being violated?"; "Scene.Report.TextPlaceholder" = "Schrijf of plak aanvullende opmerkingen"; "Scene.Report.Title" = "Rapporteer %@"; -"Scene.Report.TitleReport" = "Report"; +"Scene.Report.TitleReport" = "Rapporteer"; "Scene.Search.Recommend.Accounts.Description" = "Misschien dat u geïnteresseerd bent in deze accounts"; "Scene.Search.Recommend.Accounts.Follow" = "Volgen"; "Scene.Search.Recommend.Accounts.Title" = "Interessante accounts voor u"; @@ -320,11 +360,12 @@ klik op de link om uw account te bevestigen."; "Scene.ServerPicker.EmptyState.FindingServers" = "Beschikbare servers zoeken..."; "Scene.ServerPicker.EmptyState.NoResults" = "Geen resultaten"; "Scene.ServerPicker.Input.Placeholder" = "Zoek uw server of sluit u bij een nieuwe server aan..."; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; "Scene.ServerPicker.Label.Category" = "CATEGORIE"; "Scene.ServerPicker.Label.Language" = "TAAL"; "Scene.ServerPicker.Label.Users" = "GEBRUIKERS"; -"Scene.ServerPicker.Subtitle" = "Pick a community based on your interests, region, or a general purpose one."; -"Scene.ServerPicker.SubtitleExtend" = "Pick a community based on your interests, region, or a general purpose one. Each community is operated by an entirely independent organization or individual."; +"Scene.ServerPicker.Subtitle" = "Kies een gemeenschap gebaseerd op jouw interesses, regio of een algemeen doel."; +"Scene.ServerPicker.SubtitleExtend" = "Kies een gemeenschap gebaseerd op jouw interesses, regio, of een algemeen doel. Elke gemeenschap wordt beheerd door een volledig onafhankelijke organisatie of individu."; "Scene.ServerPicker.Title" = "Kies een server, welke dan ook."; "Scene.ServerRules.Button.Confirm" = "Ik Ga Akkoord"; "Scene.ServerRules.PrivacyPolicy" = "privacybeleid"; @@ -342,11 +383,11 @@ klik op de link om uw account te bevestigen."; "Scene.Settings.Section.BoringZone.Privacy" = "Privacybeleid"; "Scene.Settings.Section.BoringZone.Terms" = "Servicevoorwaarden"; "Scene.Settings.Section.BoringZone.Title" = "De Saaie Instellingen"; -"Scene.Settings.Section.LookAndFeel.Light" = "Light"; -"Scene.Settings.Section.LookAndFeel.ReallyDark" = "Really Dark"; -"Scene.Settings.Section.LookAndFeel.SortaDark" = "Sorta Dark"; -"Scene.Settings.Section.LookAndFeel.Title" = "Look and Feel"; -"Scene.Settings.Section.LookAndFeel.UseSystem" = "Use System"; +"Scene.Settings.Section.LookAndFeel.Light" = "Licht"; +"Scene.Settings.Section.LookAndFeel.ReallyDark" = "Echt donker"; +"Scene.Settings.Section.LookAndFeel.SortaDark" = "Donker"; +"Scene.Settings.Section.LookAndFeel.Title" = "Opmaak"; +"Scene.Settings.Section.LookAndFeel.UseSystem" = "Gebruik systeem"; "Scene.Settings.Section.Notifications.Boosts" = "Mijn bericht deelt"; "Scene.Settings.Section.Notifications.Favorites" = "Mijn bericht als favoriet toevoegt"; "Scene.Settings.Section.Notifications.Follows" = "Mij volgt"; @@ -371,9 +412,9 @@ klik op de link om uw account te bevestigen."; "Scene.SuggestionAccount.Title" = "Zoek Mensen om te Volgen"; "Scene.Thread.BackTitle" = "Bericht"; "Scene.Thread.Title" = "Bericht van %@"; -"Scene.Welcome.GetStarted" = "Get Started"; -"Scene.Welcome.LogIn" = "Log In"; +"Scene.Welcome.GetStarted" = "Aan de slag"; +"Scene.Welcome.LogIn" = "Log in"; "Scene.Welcome.Slogan" = "Sociale media terug in uw handen."; -"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" = "Tik tweemaal om het popup-scherm te sluiten"; +"Scene.Wizard.MultipleAccountSwitchIntroDescription" = "Schakel tussen meerdere accounts door de profielknop in te drukken en vast te houden."; +"Scene.Wizard.NewInMastodon" = "Nieuw in Mastodon"; \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ru.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/ru.lproj/Localizable.strings index 6913d753f..ba32a979e 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ru.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ru.lproj/Localizable.strings @@ -4,7 +4,7 @@ "Common.Alerts.CleanCache.Title" = "Очистка кэша"; "Common.Alerts.Common.PleaseTryAgain" = "Пожалуйста, попробуйте ещё раз."; "Common.Alerts.Common.PleaseTryAgainLater" = "Пожалуйста, попробуйте позже."; -"Common.Alerts.DeletePost.Message" = "Are you sure you want to delete this post?"; +"Common.Alerts.DeletePost.Message" = "Вы уверены, что хотите удалить этот пост?"; "Common.Alerts.DeletePost.Title" = "Вы уверены, что хотите удалить этот пост?"; "Common.Alerts.DiscardPostContent.Message" = "Вы действительно хотите удалить набранное содержимое поста?"; "Common.Alerts.DiscardPostContent.Title" = "Удалить черновик"; @@ -69,7 +69,7 @@ "Common.Controls.Friendship.Follow" = "Подписаться"; "Common.Controls.Friendship.Following" = "В подписках"; "Common.Controls.Friendship.Mute" = "Игнорировать"; -"Common.Controls.Friendship.MuteUser" = "Добавить %@ в игнорируемые"; +"Common.Controls.Friendship.MuteUser" = "Игнорировать %@"; "Common.Controls.Friendship.Muted" = "В игнорируемых"; "Common.Controls.Friendship.Pending" = "Отправлен"; "Common.Controls.Friendship.Request" = "Отправить запрос"; @@ -94,20 +94,21 @@ "Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Добавить или убрать из избранного"; "Common.Controls.Keyboard.Timeline.ToggleReblog" = "Продвинуть или убрать продвижение"; "Common.Controls.Status.Actions.Favorite" = "Добавить в избранное"; -"Common.Controls.Status.Actions.Hide" = "Hide"; +"Common.Controls.Status.Actions.Hide" = "Скрыть"; "Common.Controls.Status.Actions.Menu" = "Меню"; "Common.Controls.Status.Actions.Reblog" = "Продвинуть"; "Common.Controls.Status.Actions.Reply" = "Ответить"; -"Common.Controls.Status.Actions.ShowGif" = "Show GIF"; -"Common.Controls.Status.Actions.ShowImage" = "Show image"; -"Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; -"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tap then hold to show menu"; +"Common.Controls.Status.Actions.ShowGif" = "Показать GIF"; +"Common.Controls.Status.Actions.ShowImage" = "Показать изображение"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Показать видеопроигрыватель"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Нажмите и удерживайте, чтобы показать меню"; "Common.Controls.Status.Actions.Unfavorite" = "Убрать из избранного"; "Common.Controls.Status.Actions.Unreblog" = "Убрать продвижение"; "Common.Controls.Status.ContentWarning" = "Предупреждение о содержании"; "Common.Controls.Status.MediaContentWarning" = "Нажмите в любом месте, чтобы показать"; "Common.Controls.Status.Poll.Closed" = "Завершён"; "Common.Controls.Status.Poll.Vote" = "Проголосовать"; +"Common.Controls.Status.SensitiveContent" = "Sensitive Content"; "Common.Controls.Status.ShowPost" = "Показать пост"; "Common.Controls.Status.ShowUserProfile" = "Показать профиль пользователя"; "Common.Controls.Status.Tag.Email" = "E-mail"; @@ -116,7 +117,7 @@ "Common.Controls.Status.Tag.Link" = "Ссылка"; "Common.Controls.Status.Tag.Mention" = "Упоминание"; "Common.Controls.Status.Tag.Url" = "Ссылка"; -"Common.Controls.Status.TapToReveal" = "Tap to reveal"; +"Common.Controls.Status.TapToReveal" = "Нажмите, чтобы показать"; "Common.Controls.Status.UserReblogged" = "%@ продвинул(а)"; "Common.Controls.Status.UserRepliedTo" = "Ответил(а) %@"; "Common.Controls.Status.Visibility.Direct" = "Only mentioned user can see this post."; @@ -198,7 +199,7 @@ "Scene.Compose.Visibility.Public" = "Публичный"; "Scene.Compose.Visibility.Unlisted" = "Скрытый"; "Scene.ConfirmEmail.Button.OpenEmailApp" = "Открыть приложение почты"; -"Scene.ConfirmEmail.Button.Resend" = "Resend"; +"Scene.ConfirmEmail.Button.Resend" = "Отправить заново"; "Scene.ConfirmEmail.DontReceiveEmail.Description" = "Проверьте, правильно ли указан ваш e-mail адрес, а также папку «спам», если ещё не сделали этого."; "Scene.ConfirmEmail.DontReceiveEmail.ResendEmail" = "Отправить ещё раз"; "Scene.ConfirmEmail.DontReceiveEmail.Title" = "Проверьте свой e-mail адрес"; @@ -211,9 +212,17 @@ Нажмите на ссылку в нём, чтобы подтвердить свою учётную запись."; "Scene.ConfirmEmail.Title" = "И ещё кое-что."; +"Scene.Discovery.Intro" = "These are the posts gaining traction in your corner of Mastodon."; +"Scene.Discovery.Tabs.Community" = "Сообщество"; +"Scene.Discovery.Tabs.ForYou" = "For You"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtags"; +"Scene.Discovery.Tabs.News" = "News"; +"Scene.Discovery.Tabs.Posts" = "Posts"; "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.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Показать новые"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Не в сети"; "Scene.HomeTimeline.NavigationBarState.Published" = "Опубликовано!"; @@ -253,7 +262,7 @@ "Scene.Profile.SegmentedControl.About" = "About"; "Scene.Profile.SegmentedControl.Media" = "Медиа"; "Scene.Profile.SegmentedControl.Posts" = "Посты"; -"Scene.Profile.SegmentedControl.PostsAndReplies" = "Posts and Replies"; +"Scene.Profile.SegmentedControl.PostsAndReplies" = "Посты и ответы"; "Scene.Profile.SegmentedControl.Replies" = "Ответы"; "Scene.Register.Error.Item.Agreement" = "Соглашение"; "Scene.Register.Error.Item.Email" = "E-mail"; @@ -296,9 +305,40 @@ "Scene.Report.SkipToSend" = "Отправить без комментария"; "Scene.Report.Step1" = "Шаг 1 из 2"; "Scene.Report.Step2" = "Шаг 2 из 2"; +"Scene.Report.StepFinal.BlockUser" = "Block %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Don’t want to see this?"; +"Scene.Report.StepFinal.MuteUser" = "Mute %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; +"Scene.Report.StepFinal.Unfollow" = "Unfollow"; +"Scene.Report.StepFinal.UnfollowUser" = "Unfollow %@"; +"Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Is there anything else we should know?"; +"Scene.Report.StepFour.Step4Of4" = "Step 4 of 4"; +"Scene.Report.StepOne.IDontLikeIt" = "I don’t like it"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "It is not something you want to see"; +"Scene.Report.StepOne.ItViolatesServerRules" = "It violates server rules"; +"Scene.Report.StepOne.ItsSomethingElse" = "It’s something else"; +"Scene.Report.StepOne.ItsSpam" = "It’s spam"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Select the best match"; +"Scene.Report.StepOne.Step1Of4" = "Step 1 of 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "The issue does not fit into other categories"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "What's wrong with this account?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "What's wrong with this post?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "What's wrong with %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Are there any posts that back up this report?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepThree.Step3Of4" = "Step 3 of 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "I just don’t like it"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepTwo.Step2Of4" = "Step 2 of 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Which rules are being violated?"; "Scene.Report.TextPlaceholder" = "Дополнительные комментарии"; "Scene.Report.Title" = "Пожаловаться на %@"; -"Scene.Report.TitleReport" = "Report"; +"Scene.Report.TitleReport" = "Жалоба"; "Scene.Search.Recommend.Accounts.Description" = "Возможно, вы захотите подписаться на эти профили"; "Scene.Search.Recommend.Accounts.Follow" = "Подписаться"; "Scene.Search.Recommend.Accounts.Title" = "Вам может понравится"; @@ -336,11 +376,12 @@ "Scene.ServerPicker.EmptyState.FindingServers" = "Ищем доступные сервера..."; "Scene.ServerPicker.EmptyState.NoResults" = "Нет результатов"; "Scene.ServerPicker.Input.Placeholder" = "Найдите сервер или присоединитесь к своему..."; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; "Scene.ServerPicker.Label.Category" = "КАТЕГОРИЯ"; "Scene.ServerPicker.Label.Language" = "ЯЗЫК"; "Scene.ServerPicker.Label.Users" = "ПОЛЬЗОВАТЕЛИ"; "Scene.ServerPicker.Subtitle" = "Выберите сообщество на основе своих интересов, региона или общей тематики."; -"Scene.ServerPicker.SubtitleExtend" = "Pick a community based on your interests, region, or a general purpose one. Each community is operated by an entirely independent organization or individual."; +"Scene.ServerPicker.SubtitleExtend" = "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual."; "Scene.ServerPicker.Title" = "Выберите сервер, любой сервер."; "Scene.ServerRules.Button.Confirm" = "Принимаю"; @@ -376,7 +417,7 @@ "Scene.Settings.Section.Notifications.Trigger.Title" = "Уведомлять меня, когда"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Отключить анимацию аватарок"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Отключить анимацию эмодзи"; -"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Open links in Mastodon"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Открывать ссылки в Мастодоне"; "Scene.Settings.Section.Preference.Title" = "Предпочтения"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Полноценно чёрный режим"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Использовать браузер по умолчанию для открытия ссылок"; @@ -388,10 +429,10 @@ "Scene.SuggestionAccount.Title" = "Подпишитесь на людей"; "Scene.Thread.BackTitle" = "Пост"; "Scene.Thread.Title" = "Пост %@"; -"Scene.Welcome.GetStarted" = "Get Started"; +"Scene.Welcome.GetStarted" = "Присоединиться"; "Scene.Welcome.LogIn" = "Вход"; "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.NewInMastodon" = "Новое в Мастодоне"; \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/sv_FI.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/sv_FI.lproj/Localizable.strings index 07d5aa5e7..135e34990 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/sv_FI.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/sv_FI.lproj/Localizable.strings @@ -1,21 +1,21 @@ "Common.Alerts.BlockDomain.BlockEntireDomain" = "Estä verkkotunnus"; -"Common.Alerts.BlockDomain.Title" = "Are you really, really sure you want to block the entire %@? 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."; +"Common.Alerts.BlockDomain.Title" = "Är du verkligen, verkligen säker på att du vill blockera hela %@? I de flesta fall är några riktade blockeringar eller nedtystade konton tillräckligt och att föredra. Du kommer inte se innehåll från den domänen och dina följare från den domänen kommer att tas bort."; "Common.Alerts.CleanCache.Message" = "%@ välimuisti tyhjennetty onnistuneesti."; "Common.Alerts.CleanCache.Title" = "Puhdista välimuisti"; "Common.Alerts.Common.PleaseTryAgain" = "Yritä uudelleen."; "Common.Alerts.Common.PleaseTryAgainLater" = "Yritä uudelleen myöhemmin."; -"Common.Alerts.DeletePost.Message" = "Are you sure you want to delete this post?"; +"Common.Alerts.DeletePost.Message" = "Är du säker på att du vill radera detta inlägg?"; "Common.Alerts.DeletePost.Title" = "Haluatko varmasti poistaa tämän julkaisun?"; -"Common.Alerts.DiscardPostContent.Message" = "Confirm to discard composed post content."; +"Common.Alerts.DiscardPostContent.Message" = "Bekräfta för att slänga inläggsutkast."; "Common.Alerts.DiscardPostContent.Title" = "Hylkää luonnos"; "Common.Alerts.EditProfileFailure.Message" = "Profiilia ei voida muoka. Yritä uudelleen."; "Common.Alerts.EditProfileFailure.Title" = "Virhe profiilin muokkauksessa"; "Common.Alerts.PublishPostFailure.AttachmentsMessage.MoreThanOneVideo" = "Ei voi liittä yhtä videota enempää."; -"Common.Alerts.PublishPostFailure.AttachmentsMessage.VideoAttachWithPhoto" = "Cannot attach a video to a post that already contains images."; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.VideoAttachWithPhoto" = "Det går inte att bifoga en video till en status som redan innehåller bilder."; "Common.Alerts.PublishPostFailure.Message" = "Julkaisun julkaiseminen epäonnistui. Tarkista internet-yhteytesi."; "Common.Alerts.PublishPostFailure.Title" = "Julkaiseminen epäonnistui"; -"Common.Alerts.SavePhotoFailure.Message" = "Please enable the photo library access permission to save the photo."; +"Common.Alerts.SavePhotoFailure.Message" = "Aktivera åtkomst till Bilder för att spara bilden."; "Common.Alerts.SavePhotoFailure.Title" = "Kuvan tallentaminen epäonnistui"; "Common.Alerts.ServerError.Title" = "Palvelinvirhe"; "Common.Alerts.SignOut.Confirm" = "Kirjaudu ulos"; @@ -23,7 +23,7 @@ Tarkista internet-yhteytesi."; "Common.Alerts.SignOut.Title" = "Kirjaudu ulos"; "Common.Alerts.SignUpFailure.Title" = "Rekisteröinti epäonnistui"; "Common.Alerts.VoteFailure.PollEnded" = "Kysely on päättynyt"; -"Common.Alerts.VoteFailure.Title" = "Vote Failure"; +"Common.Alerts.VoteFailure.Title" = "Röstning misslyckades"; "Common.Controls.Actions.Add" = "Lisää"; "Common.Controls.Actions.Back" = "Takaisin"; "Common.Controls.Actions.BlockDomain" = "Estä %@"; @@ -37,11 +37,11 @@ Tarkista internet-yhteytesi."; "Common.Controls.Actions.Done" = "Valmis"; "Common.Controls.Actions.Edit" = "Muokkaa"; "Common.Controls.Actions.FindPeople" = "Löydä tilejä seurattavaksi"; -"Common.Controls.Actions.ManuallySearch" = "Manually search instead"; +"Common.Controls.Actions.ManuallySearch" = "Sök manuellt istället"; "Common.Controls.Actions.Next" = "Seuraava"; "Common.Controls.Actions.Ok" = "OK"; "Common.Controls.Actions.Open" = "Avaa"; -"Common.Controls.Actions.OpenInBrowser" = "Open in Browser"; +"Common.Controls.Actions.OpenInBrowser" = "Öppna i webbläsare"; "Common.Controls.Actions.OpenInSafari" = "Avaa Safarissa"; "Common.Controls.Actions.Preview" = "Esikatselu"; "Common.Controls.Actions.Previous" = "Edellinen"; @@ -74,7 +74,7 @@ Tarkista internet-yhteytesi."; "Common.Controls.Friendship.Pending" = "Pyydetty"; "Common.Controls.Friendship.Request" = "Pyydä"; "Common.Controls.Friendship.Unblock" = "Poista esto"; -"Common.Controls.Friendship.UnblockUser" = "Unblock %@"; +"Common.Controls.Friendship.UnblockUser" = "Avblockera %@"; "Common.Controls.Friendship.Unmute" = "Poista mykistys"; "Common.Controls.Friendship.UnmuteUser" = "Poista mykistys tililtä %@"; "Common.Controls.Keyboard.Common.ComposeNewPost" = "Koosta uusi julkaisu"; @@ -82,47 +82,48 @@ Tarkista internet-yhteytesi."; "Common.Controls.Keyboard.Common.ShowFavorites" = "Näytä suosikit"; "Common.Controls.Keyboard.Common.SwitchToTab" = "Vaihda %@"; "Common.Controls.Keyboard.SegmentedControl.NextSection" = "Seuraava lohko"; -"Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "Previous Section"; +"Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "Föregående avsnitt"; "Common.Controls.Keyboard.Timeline.NextStatus" = "Seuraava julkaisu"; "Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "Avaa tekijän profiili"; "Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "Avaa edelleen jakajan profiili"; "Common.Controls.Keyboard.Timeline.OpenStatus" = "Avaa julkaisu"; -"Common.Controls.Keyboard.Timeline.PreviewImage" = "Preview Image"; +"Common.Controls.Keyboard.Timeline.PreviewImage" = "Förhandsgranska bild"; "Common.Controls.Keyboard.Timeline.PreviousStatus" = "Edellinen julkaisu"; "Common.Controls.Keyboard.Timeline.ReplyStatus" = "Vastaa julkaisuun"; "Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "Vaihda sisältövaroitus"; -"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Toggle Favorite on Post"; -"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Toggle Reblog on Post"; -"Common.Controls.Status.Actions.Favorite" = "Favorite"; +"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Växla favorit på inlägg"; +"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Växla ompostning på inlägg"; +"Common.Controls.Status.Actions.Favorite" = "Favorit"; "Common.Controls.Status.Actions.Hide" = "Dölj"; "Common.Controls.Status.Actions.Menu" = "Valikko"; "Common.Controls.Status.Actions.Reblog" = "Jaa edelleen"; "Common.Controls.Status.Actions.Reply" = "Vastaa"; -"Common.Controls.Status.Actions.ShowGif" = "Show GIF"; -"Common.Controls.Status.Actions.ShowImage" = "Show image"; -"Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; -"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tap then hold to show menu"; -"Common.Controls.Status.Actions.Unfavorite" = "Unfavorite"; +"Common.Controls.Status.Actions.ShowGif" = "Visa GIF"; +"Common.Controls.Status.Actions.ShowImage" = "Visa bild"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Visa videospelare"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tryck och håll ned för att visa menyn"; +"Common.Controls.Status.Actions.Unfavorite" = "Ta bort favorit"; "Common.Controls.Status.Actions.Unreblog" = "Peru edelleen jako"; "Common.Controls.Status.ContentWarning" = "Sisältövaroitus"; "Common.Controls.Status.MediaContentWarning" = "Napauta mistä tahansa paljastaaksesi"; "Common.Controls.Status.Poll.Closed" = "Suljettu"; -"Common.Controls.Status.Poll.Vote" = "Vote"; +"Common.Controls.Status.Poll.Vote" = "Rösta"; +"Common.Controls.Status.SensitiveContent" = "Känsligt innehåll"; "Common.Controls.Status.ShowPost" = "Näytä julkaisu"; "Common.Controls.Status.ShowUserProfile" = "Näytä tili"; "Common.Controls.Status.Tag.Email" = "Sähköposti"; "Common.Controls.Status.Tag.Emoji" = "Emoji"; "Common.Controls.Status.Tag.Hashtag" = "Hashtagi"; "Common.Controls.Status.Tag.Link" = "Linkki"; -"Common.Controls.Status.Tag.Mention" = "Mention"; +"Common.Controls.Status.Tag.Mention" = "Nämn"; "Common.Controls.Status.Tag.Url" = "URL"; -"Common.Controls.Status.TapToReveal" = "Tap to reveal"; +"Common.Controls.Status.TapToReveal" = "Tryck för att visa"; "Common.Controls.Status.UserReblogged" = "%@ jakoi edelleen"; "Common.Controls.Status.UserRepliedTo" = "Vastasi %@:lle"; -"Common.Controls.Status.Visibility.Direct" = "Only mentioned user can see this post."; -"Common.Controls.Status.Visibility.Private" = "Only their followers can see this post."; -"Common.Controls.Status.Visibility.PrivateFromMe" = "Only my followers can see this post."; -"Common.Controls.Status.Visibility.Unlisted" = "Everyone can see this post but not display in the public timeline."; +"Common.Controls.Status.Visibility.Direct" = "Endast nämnda användare kan se detta inlägg."; +"Common.Controls.Status.Visibility.Private" = "Endast deras följare kan se detta inlägg."; +"Common.Controls.Status.Visibility.PrivateFromMe" = "Bara mina följare kan se det här inlägget."; +"Common.Controls.Status.Visibility.Unlisted" = "Alla kan se detta inlägg men det visas inte i den offentliga tidslinjen."; "Common.Controls.Tabs.Home" = "Koti"; "Common.Controls.Tabs.Notification" = "Ilmoitus"; "Common.Controls.Tabs.Profile" = "Profiili"; @@ -155,13 +156,13 @@ Profiilisi näyttää tältä hänelle."; "Scene.Compose.Accessibility.EnableContentWarning" = "Ota sisältövaroitus käyttöön"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "Julkaisun näkyvyysvalikko"; "Scene.Compose.Accessibility.RemovePoll" = "Poista kysely"; -"Scene.Compose.Attachment.AttachmentBroken" = "This %@ is broken and can’t be -uploaded to Mastodon."; +"Scene.Compose.Attachment.AttachmentBroken" = "Denna %@ är trasig och kan inte +laddas upp till Mastodon."; "Scene.Compose.Attachment.DescriptionPhoto" = "Kuvaile kuva näkövammaisille..."; "Scene.Compose.Attachment.DescriptionVideo" = "Kuvaile video näkövammaisille..."; "Scene.Compose.Attachment.Photo" = "kuva"; "Scene.Compose.Attachment.Video" = "video"; -"Scene.Compose.AutoComplete.SpaceToAdd" = "Space to add"; +"Scene.Compose.AutoComplete.SpaceToAdd" = "Mellanslag att lägga till"; "Scene.Compose.ComposeAction" = "Julkaise"; "Scene.Compose.ContentInputPlaceholder" = "Kirjoita tai liitä, siitä mitä ajattelet"; "Scene.Compose.ContentWarning.Placeholder" = "Kirjoita tarkka varoitus tähän..."; @@ -190,7 +191,7 @@ uploaded to Mastodon."; "Scene.Compose.Visibility.Public" = "Julkinen"; "Scene.Compose.Visibility.Unlisted" = "Listaamaton"; "Scene.ConfirmEmail.Button.OpenEmailApp" = "Avaa sähköpostisovellus"; -"Scene.ConfirmEmail.Button.Resend" = "Resend"; +"Scene.ConfirmEmail.Button.Resend" = "Skicka igen"; "Scene.ConfirmEmail.DontReceiveEmail.Description" = "Tarkista, että sähköpostiosoitteesi on oikea, sekä roskapostikansiosi, jos et vielä ole."; "Scene.ConfirmEmail.DontReceiveEmail.ResendEmail" = "Lähetä sähköposti uudelleen"; "Scene.ConfirmEmail.DontReceiveEmail.Title" = "Tarkista sähköpostisi"; @@ -200,6 +201,12 @@ uploaded to Mastodon."; "Scene.ConfirmEmail.OpenEmailApp.Title" = "Tarkasta postilaatikkosi."; "Scene.ConfirmEmail.Subtitle" = "Lähetimme juuri sähköpostin osoitteeseen %@, napauta siinä olevaa linkkiä vahvistaaksesi tilisi."; "Scene.ConfirmEmail.Title" = "Viimeinen asia."; +"Scene.Discovery.Intro" = "Detta är de inlägg som engagerar i ditt hörn av Mastodon."; +"Scene.Discovery.Tabs.Community" = "Community"; +"Scene.Discovery.Tabs.ForYou" = "För dig"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtaggar"; +"Scene.Discovery.Tabs.News" = "Nyheter"; +"Scene.Discovery.Tabs.Posts" = "Inlägg"; "Scene.Favorite.Title" = "Omat suosikit"; "Scene.Follower.Footer" = "Seuraajia muilta palvelimilta ei näytetä."; "Scene.Following.Footer" = "Seurauksia muilta palvelimilta ei näytetä."; @@ -210,43 +217,43 @@ uploaded to Mastodon."; "Scene.HomeTimeline.Title" = "Koti"; "Scene.Notification.Keyobard.ShowEverything" = "Näytä kaikki"; "Scene.Notification.Keyobard.ShowMentions" = "Näytä maininnat"; -"Scene.Notification.NotificationDescription.FavoritedYourPost" = "favorited your post"; -"Scene.Notification.NotificationDescription.FollowedYou" = "followed you"; +"Scene.Notification.NotificationDescription.FavoritedYourPost" = "favoriserade ditt inlägg"; +"Scene.Notification.NotificationDescription.FollowedYou" = "följde dig"; "Scene.Notification.NotificationDescription.MentionedYou" = "nämnde dig"; -"Scene.Notification.NotificationDescription.PollHasEnded" = "poll has ended"; -"Scene.Notification.NotificationDescription.RebloggedYourPost" = "reblogged your post"; -"Scene.Notification.NotificationDescription.RequestToFollowYou" = "request to follow you"; +"Scene.Notification.NotificationDescription.PollHasEnded" = "omröstningen har avslutats"; +"Scene.Notification.NotificationDescription.RebloggedYourPost" = "ompostade ditt inlägg"; +"Scene.Notification.NotificationDescription.RequestToFollowYou" = "begär att följa dig"; "Scene.Notification.Title.Everything" = "Kaikki"; "Scene.Notification.Title.Mentions" = "Maininnat"; "Scene.Preview.Keyboard.ClosePreview" = "Sulje esikatselu"; "Scene.Preview.Keyboard.ShowNext" = "Näytä seuraava"; "Scene.Preview.Keyboard.ShowPrevious" = "Näytä edellinen"; -"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Double tap to open the list"; -"Scene.Profile.Accessibility.EditAvatarImage" = "Edit avatar image"; -"Scene.Profile.Accessibility.ShowAvatarImage" = "Show avatar image"; -"Scene.Profile.Accessibility.ShowBannerImage" = "Show banner image"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Dubbeltryck för att öppna listan"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Redigera profilbild"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Visa profilbild"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Visa banner"; "Scene.Profile.Dashboard.Followers" = "seuraajat"; "Scene.Profile.Dashboard.Following" = "seurataan"; "Scene.Profile.Dashboard.Posts" = "julkaisut"; "Scene.Profile.Fields.AddRow" = "Lisää rivi"; "Scene.Profile.Fields.Placeholder.Content" = "Sisältö"; "Scene.Profile.Fields.Placeholder.Label" = "Nimi"; -"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Confirm to block %@"; -"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Block Account"; -"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "Confirm to mute %@"; -"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "Mute Account"; -"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "Confirm to unblock %@"; -"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "Unblock Account"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Bekräfta för att blockera %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Blockera konto"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "Bekräfta för att tysta %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "Tysta konto"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "Bekräfta för att avblockera %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "Avblockera konto"; "Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "Vahvista, että haluat poistaa mykistyksen tililtä %@"; "Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Title" = "Poista tilin mykistys"; "Scene.Profile.SegmentedControl.About" = "Om"; "Scene.Profile.SegmentedControl.Media" = "Media"; "Scene.Profile.SegmentedControl.Posts" = "Julkaisut"; -"Scene.Profile.SegmentedControl.PostsAndReplies" = "Posts and Replies"; +"Scene.Profile.SegmentedControl.PostsAndReplies" = "Inlägg och svar"; "Scene.Profile.SegmentedControl.Replies" = "Vastaukset"; "Scene.Register.Error.Item.Agreement" = "Hyväksy"; "Scene.Register.Error.Item.Email" = "Sähköposti"; -"Scene.Register.Error.Item.Locale" = "Locale"; +"Scene.Register.Error.Item.Locale" = "Språk"; "Scene.Register.Error.Item.Password" = "Salasana"; "Scene.Register.Error.Item.Reason" = "Syy"; "Scene.Register.Error.Item.Username" = "Käyttäjänimi"; @@ -255,7 +262,7 @@ uploaded to Mastodon."; "Scene.Register.Error.Reason.Blocked" = "%@ sisältää estetyn sähköpostipalveluntarjoajan"; "Scene.Register.Error.Reason.Inclusion" = "%@ ei ole tuettu arvo"; "Scene.Register.Error.Reason.Invalid" = "%@ on virheellinen"; -"Scene.Register.Error.Reason.Reserved" = "%@ is a reserved keyword"; +"Scene.Register.Error.Reason.Reserved" = "%@ är ett reserverat nyckelord"; "Scene.Register.Error.Reason.Taken" = "%@ on jo käytössä"; "Scene.Register.Error.Reason.TooLong" = "%@ on liian pitkä"; "Scene.Register.Error.Reason.TooShort" = "%@ on liian lyhyt"; @@ -268,26 +275,26 @@ uploaded to Mastodon."; "Scene.Register.Input.DisplayName.Placeholder" = "näyttönimi"; "Scene.Register.Input.Email.Placeholder" = "sähköposti"; "Scene.Register.Input.Invite.RegistrationUserInviteRequest" = "Miksi haluat liittyä?"; -"Scene.Register.Input.Password.Accessibility.Checked" = "checked"; -"Scene.Register.Input.Password.Accessibility.Unchecked" = "unchecked"; -"Scene.Register.Input.Password.CharacterLimit" = "8 characters"; +"Scene.Register.Input.Password.Accessibility.Checked" = "markerad"; +"Scene.Register.Input.Password.Accessibility.Unchecked" = "avmarkerad"; +"Scene.Register.Input.Password.CharacterLimit" = "8 tecken"; "Scene.Register.Input.Password.Hint" = "Salasanassasi on oltava vähintään kahdeksan merkkiä"; "Scene.Register.Input.Password.Placeholder" = "salasana"; -"Scene.Register.Input.Password.Require" = "Your password needs at least:"; +"Scene.Register.Input.Password.Require" = "Ditt lösenord behöver minst:"; "Scene.Register.Input.Username.DuplicatePrompt" = "Tämä käyttäjänimi on varattu."; "Scene.Register.Input.Username.Placeholder" = "käyttäjänimi"; "Scene.Register.Title" = "Kerro meille sinusta."; "Scene.Report.Content1" = "Onko julkaisuja, joita haluaisit lisätä ilmiantoon?"; "Scene.Report.Content2" = "Onko valvojien syytä tietää tästä ilmiannosta?"; -"Scene.Report.ReportSentTitle" = "Thanks for reporting, we’ll look into this."; -"Scene.Report.Reported" = "REPORTED"; +"Scene.Report.ReportSentTitle" = "Tack för din rapport, vi ska titta på det."; +"Scene.Report.Reported" = "RAPPORTERAD"; "Scene.Report.Send" = "Lähetä ilmianto"; "Scene.Report.SkipToSend" = "Lähetä ilman kommentteja"; "Scene.Report.Step1" = "Vaihe 1/2"; "Scene.Report.Step2" = "Vaihe 2/2"; "Scene.Report.TextPlaceholder" = "Kirjoita tai liitä lisäkommentteja"; "Scene.Report.Title" = "Ilmianna %@"; -"Scene.Report.TitleReport" = "Report"; +"Scene.Report.TitleReport" = "Rapportera"; "Scene.Search.Recommend.Accounts.Description" = "Haluta ehkä seurata näitä tilejä"; "Scene.Search.Recommend.Accounts.Follow" = "Seuraa"; "Scene.Search.Recommend.Accounts.Title" = "Saatat pitää näistä tileistä"; @@ -328,8 +335,8 @@ uploaded to Mastodon."; "Scene.ServerPicker.Label.Category" = "KATEGORIA"; "Scene.ServerPicker.Label.Language" = "KIELI"; "Scene.ServerPicker.Label.Users" = "TILIÄ"; -"Scene.ServerPicker.Subtitle" = "Pick a community based on your interests, region, or a general purpose one."; -"Scene.ServerPicker.SubtitleExtend" = "Pick a community based on your interests, region, or a general purpose one. Each community is operated by an entirely independent organization or individual."; +"Scene.ServerPicker.Subtitle" = "Välj en gemenskap baserad på dina intressen, region eller ett allmänt syfte."; +"Scene.ServerPicker.SubtitleExtend" = "Välj en gemenskap baserad på dina intressen, region eller ett allmänt syfte. Varje gemenskap drivs av en helt oberoende organisation eller individ."; "Scene.ServerPicker.Title" = "Valitse palvelin, mikä tahansa palvelin."; "Scene.ServerRules.Button.Confirm" = "Hyväksyn"; @@ -349,12 +356,12 @@ mikä tahansa palvelin."; "Scene.Settings.Section.BoringZone.Terms" = "Palveluehdot"; "Scene.Settings.Section.BoringZone.Title" = "Tylsä alue"; "Scene.Settings.Section.LookAndFeel.Light" = "Ljust"; -"Scene.Settings.Section.LookAndFeel.ReallyDark" = "Really Dark"; -"Scene.Settings.Section.LookAndFeel.SortaDark" = "Sorta Dark"; -"Scene.Settings.Section.LookAndFeel.Title" = "Look and Feel"; -"Scene.Settings.Section.LookAndFeel.UseSystem" = "Use System"; +"Scene.Settings.Section.LookAndFeel.ReallyDark" = "Verkligen mörk"; +"Scene.Settings.Section.LookAndFeel.SortaDark" = "Ganska mörk"; +"Scene.Settings.Section.LookAndFeel.Title" = "Utseende och känsla"; +"Scene.Settings.Section.LookAndFeel.UseSystem" = "Följ systeminställningarna"; "Scene.Settings.Section.Notifications.Boosts" = "Omien julkaisujen edelleen jaot"; -"Scene.Settings.Section.Notifications.Favorites" = "Favorites my post"; +"Scene.Settings.Section.Notifications.Favorites" = "Favoriserar mitt inlägg"; "Scene.Settings.Section.Notifications.Follows" = "Seuraa minua"; "Scene.Settings.Section.Notifications.Mentions" = "Mainitsee minut"; "Scene.Settings.Section.Notifications.Title" = "Ilmoitukset"; @@ -365,7 +372,7 @@ mikä tahansa palvelin."; "Scene.Settings.Section.Notifications.Trigger.Title" = "Ilmoita minulle, kun"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Poista käytöstä animoidut avatarit"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Poista käytöstä animoidut emojit"; -"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Open links in Mastodon"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Öppna länkar i Mastodon"; "Scene.Settings.Section.Preference.Title" = "Lisäasetukset"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Todellinen mustan tumma tila"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Käytä oletusselainta linkkien avaamiseen"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/sv_FI.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/sv_FI.lproj/Localizable.stringsdict index 43231214a..091c8555e 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/sv_FI.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/sv_FI.lproj/Localizable.stringsdict @@ -125,9 +125,9 @@ NSStringFormatValueTypeKey ld one - 1 reply + 1 svar other - %ld replies + %ld svar plural.count.vote diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.strings index de50842f2..204700321 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.strings @@ -98,16 +98,17 @@ "Common.Controls.Status.Actions.Menu" = "เมนู"; "Common.Controls.Status.Actions.Reblog" = "ดัน"; "Common.Controls.Status.Actions.Reply" = "ตอบกลับ"; -"Common.Controls.Status.Actions.ShowGif" = "Show GIF"; -"Common.Controls.Status.Actions.ShowImage" = "Show image"; -"Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; -"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tap then hold to show menu"; +"Common.Controls.Status.Actions.ShowGif" = "แสดง GIF"; +"Common.Controls.Status.Actions.ShowImage" = "แสดงภาพ"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "แสดงตัวเล่นวิดีโอ"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "แตะค้างไว้เพื่อแสดงเมนู"; "Common.Controls.Status.Actions.Unfavorite" = "เลิกชื่นชอบ"; "Common.Controls.Status.Actions.Unreblog" = "เลิกทำการดัน"; "Common.Controls.Status.ContentWarning" = "คำเตือนเนื้อหา"; "Common.Controls.Status.MediaContentWarning" = "แตะที่ใดก็ตามเพื่อเปิดเผย"; "Common.Controls.Status.Poll.Closed" = "ปิดแล้ว"; "Common.Controls.Status.Poll.Vote" = "ลงคะแนน"; +"Common.Controls.Status.SensitiveContent" = "เนื้อหาที่ละเอียดอ่อน"; "Common.Controls.Status.ShowPost" = "แสดงโพสต์"; "Common.Controls.Status.ShowUserProfile" = "แสดงโปรไฟล์ผู้ใช้"; "Common.Controls.Status.Tag.Email" = "อีเมล"; @@ -116,7 +117,7 @@ "Common.Controls.Status.Tag.Link" = "ลิงก์"; "Common.Controls.Status.Tag.Mention" = "กล่าวถึง"; "Common.Controls.Status.Tag.Url" = "URL"; -"Common.Controls.Status.TapToReveal" = "Tap to reveal"; +"Common.Controls.Status.TapToReveal" = "แตะเพื่อเปิดเผย"; "Common.Controls.Status.UserReblogged" = "%@ ได้ดัน"; "Common.Controls.Status.UserRepliedTo" = "ตอบกลับ %@"; "Common.Controls.Status.Visibility.Direct" = "เฉพาะผู้ใช้ที่กล่าวถึงเท่านั้นที่สามารถเห็นโพสต์นี้"; @@ -200,9 +201,17 @@ "Scene.ConfirmEmail.OpenEmailApp.Title" = "ตรวจสอบกล่องขาเข้าของคุณ"; "Scene.ConfirmEmail.Subtitle" = "แตะลิงก์ที่เราส่งอีเมลถึงคุณเพื่อยืนยันบัญชีของคุณ"; "Scene.ConfirmEmail.Title" = "หนึ่งสิ่งสุดท้าย"; +"Scene.Discovery.Intro" = "นี่คือโพสต์ที่กำลังได้รับความสนใจในมุมของ Mastodon ของคุณ"; +"Scene.Discovery.Tabs.Community" = "ชุมชน"; +"Scene.Discovery.Tabs.ForYou" = "สำหรับคุณ"; +"Scene.Discovery.Tabs.Hashtags" = "แฮชแท็ก"; +"Scene.Discovery.Tabs.News" = "ข่าว"; +"Scene.Discovery.Tabs.Posts" = "โพสต์"; "Scene.Favorite.Title" = "รายการโปรดของคุณ"; "Scene.Follower.Footer" = "ไม่ได้แสดงผู้ติดตามจากเซิร์ฟเวอร์อื่น ๆ"; "Scene.Following.Footer" = "ไม่ได้แสดงการติดตามจากเซิร์ฟเวอร์อื่น ๆ"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "ดูโพสต์ใหม่"; "Scene.HomeTimeline.NavigationBarState.Offline" = "ออฟไลน์"; "Scene.HomeTimeline.NavigationBarState.Published" = "เผยแพร่แล้ว!"; @@ -221,10 +230,10 @@ "Scene.Preview.Keyboard.ClosePreview" = "ปิดตัวอย่าง"; "Scene.Preview.Keyboard.ShowNext" = "แสดงถัดไป"; "Scene.Preview.Keyboard.ShowPrevious" = "แสดงก่อนหน้า"; -"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Double tap to open the list"; -"Scene.Profile.Accessibility.EditAvatarImage" = "Edit avatar image"; -"Scene.Profile.Accessibility.ShowAvatarImage" = "Show avatar image"; -"Scene.Profile.Accessibility.ShowBannerImage" = "Show banner image"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "แตะสองครั้งเพื่อเปิดรายการ"; +"Scene.Profile.Accessibility.EditAvatarImage" = "แก้ไขภาพประจำตัว"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "แสดงภาพประจำตัว"; +"Scene.Profile.Accessibility.ShowBannerImage" = "แสดงภาพแบนเนอร์"; "Scene.Profile.Dashboard.Followers" = "ผู้ติดตาม"; "Scene.Profile.Dashboard.Following" = "กำลังติดตาม"; "Scene.Profile.Dashboard.Posts" = "โพสต์"; @@ -251,7 +260,7 @@ "Scene.Register.Error.Item.Reason" = "เหตุผล"; "Scene.Register.Error.Item.Username" = "ชื่อผู้ใช้"; "Scene.Register.Error.Reason.Accepted" = "ต้องยอมรับ %@"; -"Scene.Register.Error.Reason.Blank" = "ต้องมี %@"; +"Scene.Register.Error.Reason.Blank" = "ต้องการ %@"; "Scene.Register.Error.Reason.Blocked" = "%@ มีผู้ให้บริการอีเมลที่ไม่ได้รับอนุญาต"; "Scene.Register.Error.Reason.Inclusion" = "%@ ไม่ใช่ค่าที่รองรับ"; "Scene.Register.Error.Reason.Invalid" = "%@ ไม่ถูกต้อง"; @@ -271,9 +280,9 @@ "Scene.Register.Input.Password.Accessibility.Checked" = "กาเครื่องหมายแล้ว"; "Scene.Register.Input.Password.Accessibility.Unchecked" = "ไม่ได้กาเครื่องหมาย"; "Scene.Register.Input.Password.CharacterLimit" = "8 ตัวอักษร"; -"Scene.Register.Input.Password.Hint" = "รหัสผ่านของคุณต้องมีอย่างน้อยแปดตัวอักษร"; +"Scene.Register.Input.Password.Hint" = "รหัสผ่านของคุณจำเป็นต้องมีอย่างน้อยแปดตัวอักษร"; "Scene.Register.Input.Password.Placeholder" = "รหัสผ่าน"; -"Scene.Register.Input.Password.Require" = "รหัสผ่านของคุณต้องมีอย่างน้อย:"; +"Scene.Register.Input.Password.Require" = "รหัสผ่านของคุณจำเป็นต้องมีอย่างน้อย:"; "Scene.Register.Input.Username.DuplicatePrompt" = "ชื่อผู้ใช้นี้ถูกใช้ไปแล้ว"; "Scene.Register.Input.Username.Placeholder" = "ชื่อผู้ใช้"; "Scene.Register.Title" = "มาตั้งค่าของคุณใน %@ กันเลย"; @@ -285,6 +294,37 @@ "Scene.Report.SkipToSend" = "ส่งโดยไม่มีความคิดเห็น"; "Scene.Report.Step1" = "ขั้นตอนที่ 1 จาก 2"; "Scene.Report.Step2" = "ขั้นตอนที่ 2 จาก 2"; +"Scene.Report.StepFinal.BlockUser" = "Block %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Don’t want to see this?"; +"Scene.Report.StepFinal.MuteUser" = "Mute %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; +"Scene.Report.StepFinal.Unfollow" = "Unfollow"; +"Scene.Report.StepFinal.UnfollowUser" = "Unfollow %@"; +"Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Is there anything else we should know?"; +"Scene.Report.StepFour.Step4Of4" = "Step 4 of 4"; +"Scene.Report.StepOne.IDontLikeIt" = "I don’t like it"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "It is not something you want to see"; +"Scene.Report.StepOne.ItViolatesServerRules" = "It violates server rules"; +"Scene.Report.StepOne.ItsSomethingElse" = "It’s something else"; +"Scene.Report.StepOne.ItsSpam" = "It’s spam"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Select the best match"; +"Scene.Report.StepOne.Step1Of4" = "Step 1 of 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "The issue does not fit into other categories"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "What's wrong with this account?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "What's wrong with this post?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "What's wrong with %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Are there any posts that back up this report?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepThree.Step3Of4" = "Step 3 of 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "I just don’t like it"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepTwo.Step2Of4" = "Step 2 of 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Which rules are being violated?"; "Scene.Report.TextPlaceholder" = "พิมพ์หรือวางความคิดเห็นเพิ่มเติม"; "Scene.Report.Title" = "รายงาน %@"; "Scene.Report.TitleReport" = "รายงาน"; @@ -305,16 +345,16 @@ "Scene.Search.Searching.Segment.People" = "ผู้คน"; "Scene.Search.Searching.Segment.Posts" = "โพสต์"; "Scene.Search.Title" = "ค้นหา"; -"Scene.ServerPicker.Button.Category.Academia" = "วิชาการ"; +"Scene.ServerPicker.Button.Category.Academia" = "สถาบันการศึกษา"; "Scene.ServerPicker.Button.Category.Activism" = "กิจกรรมเพื่อการเปลี่ยนแปลง"; "Scene.ServerPicker.Button.Category.All" = "ทั้งหมด"; "Scene.ServerPicker.Button.Category.AllAccessiblityDescription" = "หมวดหมู่: ทั้งหมด"; "Scene.ServerPicker.Button.Category.Art" = "ศิลปะ"; "Scene.ServerPicker.Button.Category.Food" = "อาหาร"; -"Scene.ServerPicker.Button.Category.Furry" = "furry"; +"Scene.ServerPicker.Button.Category.Furry" = "สัตว์ขนยาว"; "Scene.ServerPicker.Button.Category.Games" = "เกม"; "Scene.ServerPicker.Button.Category.General" = "ทั่วไป"; -"Scene.ServerPicker.Button.Category.Journalism" = "การเขียนข่าว"; +"Scene.ServerPicker.Button.Category.Journalism" = "วารสารศาสตร์"; "Scene.ServerPicker.Button.Category.Lgbt" = "lgbt"; "Scene.ServerPicker.Button.Category.Music" = "ดนตรี"; "Scene.ServerPicker.Button.Category.Regional" = "ภูมิภาค"; @@ -324,13 +364,14 @@ "Scene.ServerPicker.EmptyState.BadNetwork" = "มีบางอย่างผิดพลาดขณะโหลดข้อมูล ตรวจสอบการเชื่อมต่ออินเทอร์เน็ตของคุณ"; "Scene.ServerPicker.EmptyState.FindingServers" = "กำลังค้นหาเซิร์ฟเวอร์ที่พร้อมใช้งาน..."; "Scene.ServerPicker.EmptyState.NoResults" = "ไม่มีผลลัพธ์"; -"Scene.ServerPicker.Input.Placeholder" = "ค้นหาชุมชน"; +"Scene.ServerPicker.Input.Placeholder" = "ค้นหาเซิร์ฟเวอร์"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; "Scene.ServerPicker.Label.Category" = "หมวดหมู่"; "Scene.ServerPicker.Label.Language" = "ภาษา"; "Scene.ServerPicker.Label.Users" = "ผู้ใช้"; -"Scene.ServerPicker.Subtitle" = "เลือกชุมชนตามความสนใจ, ภูมิภาค หรือวัตถุประสงค์ทั่วไปของคุณ"; -"Scene.ServerPicker.SubtitleExtend" = "เลือกชุมชนตามความสนใจ, ภูมิภาค หรือวัตถุประสงค์ทั่วไปของคุณ แต่ละชุมชนดำเนินการโดยองค์กรหรือบุคคลที่เป็นอิสระโดยสิ้นเชิง"; -"Scene.ServerPicker.Title" = "Mastodon ประกอบด้วยผู้ใช้ในชุมชนต่าง ๆ"; +"Scene.ServerPicker.Subtitle" = "เลือกเซิร์ฟเวอร์ตามความสนใจ, ภูมิภาค หรือวัตถุประสงค์ทั่วไปของคุณ"; +"Scene.ServerPicker.SubtitleExtend" = "เลือกเซิร์ฟเวอร์ตามความสนใจ, ภูมิภาค หรือวัตถุประสงค์ทั่วไปของคุณ แต่ละเซิร์ฟเวอร์ดำเนินการโดยองค์กรหรือบุคคลที่เป็นอิสระโดยสิ้นเชิง"; +"Scene.ServerPicker.Title" = "Mastodon ประกอบด้วยผู้ใช้ในเซิร์ฟเวอร์ต่าง ๆ"; "Scene.ServerRules.Button.Confirm" = "ฉันเห็นด้วย"; "Scene.ServerRules.PrivacyPolicy" = "นโยบายความเป็นส่วนตัว"; "Scene.ServerRules.Prompt" = "เมื่อคุณดำเนินการต่อ คุณอยู่ภายใต้เงื่อนไขการให้บริการและนโยบายความเป็นส่วนตัวสำหรับ %@"; @@ -364,7 +405,7 @@ "Scene.Settings.Section.Notifications.Trigger.Title" = "แจ้งเตือนฉันเมื่อ"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "ปิดใช้งานภาพประจำตัวแบบเคลื่อนไหว"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "ปิดใช้งานอีโมจิแบบเคลื่อนไหว"; -"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Open links in Mastodon"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "เปิดลิงก์ใน Mastodon"; "Scene.Settings.Section.Preference.Title" = "การกำหนดลักษณะ"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "โหมดมืดดำสนิท"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "ใช้เบราว์เซอร์เริ่มต้นเพื่อเปิดลิงก์"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.stringsdict index 3895c3993..8ae8feb7b 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.stringsdict @@ -111,7 +111,7 @@ NSStringFormatValueTypeKey ld other - %ld replies + %ld การตอบกลับ plural.count.vote diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/tr.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/tr.lproj/Localizable.strings new file mode 100644 index 000000000..9467072f6 --- /dev/null +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/tr.lproj/Localizable.strings @@ -0,0 +1,425 @@ +"Common.Alerts.BlockDomain.BlockEntireDomain" = "Alan Adını Engelle"; +"Common.Alerts.BlockDomain.Title" = "%@ alan adını tamamen engellemek istediğine gerçekten emin misiniz? Pek çok durumda o alan adından birkaç kişiyi engellemek ve sessize almak yeterlidir ve tercih edilir. Engellediğinizde bu alan adından herhangi bir içerik görmeyeceksiniz ve o alan adından olan takipçileriniz silinecek."; +"Common.Alerts.CleanCache.Message" = "%@ boyutunda önbellek temizlendi."; +"Common.Alerts.CleanCache.Title" = "Önbelleği Temizle"; +"Common.Alerts.Common.PleaseTryAgain" = "Lütfen tekrar deneyin."; +"Common.Alerts.Common.PleaseTryAgainLater" = "Lütfen daha sonra tekrar deneyin."; +"Common.Alerts.DeletePost.Message" = "Bu gönderiyi silmek istediğinize emin misiniz?"; +"Common.Alerts.DeletePost.Title" = "Gönderiyi Sil"; +"Common.Alerts.DiscardPostContent.Message" = "Yazdığın gönderiyi paylaşmadan silmek istiyor musun?"; +"Common.Alerts.DiscardPostContent.Title" = "Taslağı Sil"; +"Common.Alerts.EditProfileFailure.Message" = "Profil düzenlenemedi. Lütfen tekrar deneyin."; +"Common.Alerts.EditProfileFailure.Title" = "Profil Düzenleme Hatası"; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.MoreThanOneVideo" = "Gönderiye birden fazla video eklenemez."; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.VideoAttachWithPhoto" = "İçeriğinde görseller olan bir gönderiye video eklenemez."; +"Common.Alerts.PublishPostFailure.Message" = "Gönderi paylaşılamadı. Lütfen internet bağlantını kontrol et."; +"Common.Alerts.PublishPostFailure.Title" = "Paylaşılamadı"; +"Common.Alerts.SavePhotoFailure.Message" = "Görseli kaydetmek için lütfen galeri erişim iznini aktifleştirin."; +"Common.Alerts.SavePhotoFailure.Title" = "Görsel Kaydetme Hatası"; +"Common.Alerts.ServerError.Title" = "Sunucu Hatası"; +"Common.Alerts.SignOut.Confirm" = "Oturumu Kapat"; +"Common.Alerts.SignOut.Message" = "Oturumu kapatmak istediğinize emin misiniz?"; +"Common.Alerts.SignOut.Title" = "Oturumu Kapat"; +"Common.Alerts.SignUpFailure.Title" = "Kaydolma Başarısız"; +"Common.Alerts.VoteFailure.PollEnded" = "Anket bitti"; +"Common.Alerts.VoteFailure.Title" = "Oy Verme Başarısız"; +"Common.Controls.Actions.Add" = "Ekle"; +"Common.Controls.Actions.Back" = "Geri"; +"Common.Controls.Actions.BlockDomain" = "%@ kişisini engelle"; +"Common.Controls.Actions.Cancel" = "İptal et"; +"Common.Controls.Actions.Compose" = "Yaz"; +"Common.Controls.Actions.Confirm" = "Onayla"; +"Common.Controls.Actions.Continue" = "Devam et"; +"Common.Controls.Actions.CopyPhoto" = "Fotoğrafı Kopyala"; +"Common.Controls.Actions.Delete" = "Sil"; +"Common.Controls.Actions.Discard" = "Vazgeç"; +"Common.Controls.Actions.Done" = "Kapat"; +"Common.Controls.Actions.Edit" = "Düzenle"; +"Common.Controls.Actions.FindPeople" = "Takip etmek için birkaç kişi bul"; +"Common.Controls.Actions.ManuallySearch" = "Onun yerine manuel olarak ara"; +"Common.Controls.Actions.Next" = "İleri"; +"Common.Controls.Actions.Ok" = "Tamam"; +"Common.Controls.Actions.Open" = "Aç"; +"Common.Controls.Actions.OpenInBrowser" = "Tarayıcıda Aç"; +"Common.Controls.Actions.OpenInSafari" = "Safari'de Aç"; +"Common.Controls.Actions.Preview" = "Önizleme"; +"Common.Controls.Actions.Previous" = "Önceki"; +"Common.Controls.Actions.Remove" = "Kaldır"; +"Common.Controls.Actions.Reply" = "Yanıtla"; +"Common.Controls.Actions.ReportUser" = "%@ kişisini bildir"; +"Common.Controls.Actions.Save" = "Kaydet"; +"Common.Controls.Actions.SavePhoto" = "Fotoğrafı Kaydet"; +"Common.Controls.Actions.SeeMore" = "Daha Fazla Gör"; +"Common.Controls.Actions.Settings" = "Ayarlar"; +"Common.Controls.Actions.Share" = "Paylaş"; +"Common.Controls.Actions.SharePost" = "Gönderiyi Paylaş"; +"Common.Controls.Actions.ShareUser" = "%@ ile paylaş"; +"Common.Controls.Actions.SignIn" = "Giriş Yap"; +"Common.Controls.Actions.SignUp" = "Kaydol"; +"Common.Controls.Actions.Skip" = "Atla"; +"Common.Controls.Actions.TakePhoto" = "Fotoğraf Çek"; +"Common.Controls.Actions.TryAgain" = "Tekrar Deneyin"; +"Common.Controls.Actions.UnblockDomain" = "%@ kişisinin engelini kaldır"; +"Common.Controls.Friendship.Block" = "Engelle"; +"Common.Controls.Friendship.BlockDomain" = "%@ kişisini engelle"; +"Common.Controls.Friendship.BlockUser" = "%@ kişisini engelle"; +"Common.Controls.Friendship.Blocked" = "Engellendi"; +"Common.Controls.Friendship.EditInfo" = "Bilgiyi Düzenle"; +"Common.Controls.Friendship.Follow" = "Takip et"; +"Common.Controls.Friendship.Following" = "Takip ediliyor"; +"Common.Controls.Friendship.Mute" = "Sessize al"; +"Common.Controls.Friendship.MuteUser" = "Sustur %@"; +"Common.Controls.Friendship.Muted" = "Susturuldu"; +"Common.Controls.Friendship.Pending" = "Bekliyor"; +"Common.Controls.Friendship.Request" = "İstek"; +"Common.Controls.Friendship.Unblock" = "Engeli kaldır"; +"Common.Controls.Friendship.UnblockUser" = "%@ kişisinin engelini kaldır"; +"Common.Controls.Friendship.Unmute" = "Susturmayı kaldır"; +"Common.Controls.Friendship.UnmuteUser" = "Sesini aç %@"; +"Common.Controls.Keyboard.Common.ComposeNewPost" = "Yeni Gönderi Yaz"; +"Common.Controls.Keyboard.Common.OpenSettings" = "Ayarları Aç"; +"Common.Controls.Keyboard.Common.ShowFavorites" = "Favorilerimi Göster"; +"Common.Controls.Keyboard.Common.SwitchToTab" = "Geç: %@"; +"Common.Controls.Keyboard.SegmentedControl.NextSection" = "Sonraki Bölüm"; +"Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "Önceki Seçim"; +"Common.Controls.Keyboard.Timeline.NextStatus" = "Sonraki Gönderi"; +"Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "Yazarın Profilini Aç"; +"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "Yeniden Paylaşanın Profilini Aç"; +"Common.Controls.Keyboard.Timeline.OpenStatus" = "Gönderiyi Aç"; +"Common.Controls.Keyboard.Timeline.PreviewImage" = "Görseli Önizle"; +"Common.Controls.Keyboard.Timeline.PreviousStatus" = "Önceki Gönderi"; +"Common.Controls.Keyboard.Timeline.ReplyStatus" = "Gönderiyi Yanıtla"; +"Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "İçerik Uyarısı durumunu değiştir"; +"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Gönderiyi favorileme durumunu değiştir"; +"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Gönderiyi yeniden paylaşma durumunu değiştir"; +"Common.Controls.Status.Actions.Favorite" = "Favorile"; +"Common.Controls.Status.Actions.Hide" = "Gizle"; +"Common.Controls.Status.Actions.Menu" = "Menü"; +"Common.Controls.Status.Actions.Reblog" = "Yeniden paylaş"; +"Common.Controls.Status.Actions.Reply" = "Yanıtla"; +"Common.Controls.Status.Actions.ShowGif" = "GIF'i göster"; +"Common.Controls.Status.Actions.ShowImage" = "Görüntüyü göster"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Video oynatıcıyı göster"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Menüyü göstermek için dokunun ve basılı tutun"; +"Common.Controls.Status.Actions.Unfavorite" = "Favorilerden Çıkar"; +"Common.Controls.Status.Actions.Unreblog" = "Yeniden paylaşımı geri al"; +"Common.Controls.Status.ContentWarning" = "İçerik Uyarısı"; +"Common.Controls.Status.MediaContentWarning" = "Göstermek için herhangi bir yere basın"; +"Common.Controls.Status.Poll.Closed" = "Kapandı"; +"Common.Controls.Status.Poll.Vote" = "Oy ver"; +"Common.Controls.Status.SensitiveContent" = "Hassas İçerik"; +"Common.Controls.Status.ShowPost" = "Gönderiyi Göster"; +"Common.Controls.Status.ShowUserProfile" = "Kullanıcı profilini göster"; +"Common.Controls.Status.Tag.Email" = "E-posta"; +"Common.Controls.Status.Tag.Emoji" = "Emoji"; +"Common.Controls.Status.Tag.Hashtag" = "Etiket"; +"Common.Controls.Status.Tag.Link" = "Bağlantı"; +"Common.Controls.Status.Tag.Mention" = "Bahset"; +"Common.Controls.Status.Tag.Url" = "Bağlantı"; +"Common.Controls.Status.TapToReveal" = "Göstermek için basın"; +"Common.Controls.Status.UserReblogged" = "%@ yeniden paylaştı"; +"Common.Controls.Status.UserRepliedTo" = "%@ kullanıcısına yanıt verdi"; +"Common.Controls.Status.Visibility.Direct" = "Sadece bahsedilen kullanıcı bu gönderiyi görebilir."; +"Common.Controls.Status.Visibility.Private" = "Sadece gönderi sahibinin takipçileri bu gönderiyi görebilir."; +"Common.Controls.Status.Visibility.PrivateFromMe" = "Sadece benim takipçilerim bu gönderiyi görebilir."; +"Common.Controls.Status.Visibility.Unlisted" = "Bu gönderiyi herkes görebilir, fakat herkese açık zaman tünelinde gösterilmez."; +"Common.Controls.Tabs.Home" = "Ana Sayfa"; +"Common.Controls.Tabs.Notification" = "Bildirimler"; +"Common.Controls.Tabs.Profile" = "Profil"; +"Common.Controls.Tabs.Search" = "Arama"; +"Common.Controls.Timeline.Filtered" = "Filtrelenmiş"; +"Common.Controls.Timeline.Header.BlockedWarning" = "Bu kişi sizin engelinizi kaldırana +kadar onun profilini göremezsiniz."; +"Common.Controls.Timeline.Header.BlockingWarning" = "Bu kişinin engelini kaldırana kadar +onun profilini göremezsiniz. +Bu kişiye göre profiliniz böyle gözüküyor."; +"Common.Controls.Timeline.Header.NoStatusFound" = "Hiçbir Gönderi Bulunamadı"; +"Common.Controls.Timeline.Header.SuspendedWarning" = "Bu kullanıcı askıya alındı."; +"Common.Controls.Timeline.Header.UserBlockedWarning" = "%@ kişisi sizin engelinizi kaldırana +kadar onun profilini göremezsiniz."; +"Common.Controls.Timeline.Header.UserBlockingWarning" = "%@ kişisinin engelini kaldırana kadar +onun profilini göremezsiniz. +Bu kişiye göre profiliniz böyle gözüküyor."; +"Common.Controls.Timeline.Header.UserSuspendedWarning" = "%@ kişisinin hesabı askıya alındı."; +"Common.Controls.Timeline.Loader.LoadMissingPosts" = "Daha fazla gönderi yükle"; +"Common.Controls.Timeline.Loader.LoadingMissingPosts" = "Daha fazla gönderi yükleniyor..."; +"Common.Controls.Timeline.Loader.ShowMoreReplies" = "Daha fazla yanıt görüntüe"; +"Common.Controls.Timeline.Timestamp.Now" = "Şimdi"; +"Scene.AccountList.AddAccount" = "Hesap Ekle"; +"Scene.AccountList.DismissAccountSwitcher" = "Hesap Değiştiriciyi Kapat"; +"Scene.AccountList.TabBarHint" = "Şu anki seçili profil: %@. Hesap değiştiriciyi göstermek için iki kez dokunun ve basılı tutun"; +"Scene.Compose.Accessibility.AppendAttachment" = "Dosya Ekle"; +"Scene.Compose.Accessibility.AppendPoll" = "Anket Ekle"; +"Scene.Compose.Accessibility.CustomEmojiPicker" = "Özel Emoji Seçici"; +"Scene.Compose.Accessibility.DisableContentWarning" = "İçerik Uyarısını Kapat"; +"Scene.Compose.Accessibility.EnableContentWarning" = "İçerik Uyarısını Etkinleştir"; +"Scene.Compose.Accessibility.PostVisibilityMenu" = "Gönderi Görünürlüğü Menüsü"; +"Scene.Compose.Accessibility.RemovePoll" = "Anketi Kaldır"; +"Scene.Compose.Attachment.AttachmentBroken" = "Bu %@ bozuk ve Mastodon'a +yüklenemiyor."; +"Scene.Compose.Attachment.DescriptionPhoto" = "Görme engelliler için fotoğrafı tarif edin..."; +"Scene.Compose.Attachment.DescriptionVideo" = "Görme engelliler için videoyu tarif edin..."; +"Scene.Compose.Attachment.Photo" = "fotoğraf"; +"Scene.Compose.Attachment.Video" = "video"; +"Scene.Compose.AutoComplete.SpaceToAdd" = "Eklemek için boşluk tuşuna basın"; +"Scene.Compose.ComposeAction" = "Yayınla"; +"Scene.Compose.ContentInputPlaceholder" = "Aklınızdan geçenleri yazın veya yapıştırın"; +"Scene.Compose.ContentWarning.Placeholder" = "Buraya kesin bir uyarı yazın..."; +"Scene.Compose.Keyboard.AppendAttachmentEntry" = "Dosya Ekle - %@"; +"Scene.Compose.Keyboard.DiscardPost" = "Gönderiyi İptal Et"; +"Scene.Compose.Keyboard.PublishPost" = "Gönderiyi Yayınla"; +"Scene.Compose.Keyboard.SelectVisibilityEntry" = "Görünürlüğü Seç - %@"; +"Scene.Compose.Keyboard.ToggleContentWarning" = "İçerik Uyarısı durumunu değiştir"; +"Scene.Compose.Keyboard.TogglePoll" = "Anketi Aç/Kapat"; +"Scene.Compose.MediaSelection.Browse" = "Göz at"; +"Scene.Compose.MediaSelection.Camera" = "Fotoğraf Çek"; +"Scene.Compose.MediaSelection.PhotoLibrary" = "Fotoğraf Albümü"; +"Scene.Compose.Poll.DurationTime" = "Süre: %@"; +"Scene.Compose.Poll.OneDay" = "1 Gün"; +"Scene.Compose.Poll.OneHour" = "1 Saat"; +"Scene.Compose.Poll.OptionNumber" = "Seçenek %ld"; +"Scene.Compose.Poll.SevenDays" = "7 Gün"; +"Scene.Compose.Poll.SixHours" = "6 Saat"; +"Scene.Compose.Poll.ThirtyMinutes" = "30 dakika"; +"Scene.Compose.Poll.ThreeDays" = "3 Gün"; +"Scene.Compose.ReplyingToUser" = "yanıtlanıyor: %@"; +"Scene.Compose.Title.NewPost" = "Yeni Gönderi"; +"Scene.Compose.Title.NewReply" = "Yeni Yanıt"; +"Scene.Compose.Visibility.Direct" = "Sadece bahsettiğim insanlar"; +"Scene.Compose.Visibility.Private" = "Yalnızca takipçiler"; +"Scene.Compose.Visibility.Public" = "Herkese açık"; +"Scene.Compose.Visibility.Unlisted" = "Listelenmemiş"; +"Scene.ConfirmEmail.Button.OpenEmailApp" = "E-posta Uygulamasını Aç"; +"Scene.ConfirmEmail.Button.Resend" = "Yeniden gönder"; +"Scene.ConfirmEmail.DontReceiveEmail.Description" = "E-posta adresinizin doğru olup olmadığını ve doğru ise gereksiz klasörünüzü kontrol edin."; +"Scene.ConfirmEmail.DontReceiveEmail.ResendEmail" = "E-postayı Yeniden Gönder"; +"Scene.ConfirmEmail.DontReceiveEmail.Title" = "Posta kutunuzu kontrol edin"; +"Scene.ConfirmEmail.OpenEmailApp.Description" = "Size bir e-posta gönderdik. Eğer e-postayı almadıysanız, gereksiz klasörünü kontrol edin."; +"Scene.ConfirmEmail.OpenEmailApp.Mail" = "Posta"; +"Scene.ConfirmEmail.OpenEmailApp.OpenEmailClient" = "E-posta İstemcisini Aç"; +"Scene.ConfirmEmail.OpenEmailApp.Title" = "Gelen kutunuzu kontrol edin."; +"Scene.ConfirmEmail.Subtitle" = "Hesabınızı doğrulamak için size e-postayla gönderdiğimiz bağlantıya dokunun."; +"Scene.ConfirmEmail.Title" = "Son bir şey."; +"Scene.Discovery.Intro" = "Bunlar, Mastodon'un köşesinde ilgi çeken gönderilerdir."; +"Scene.Discovery.Tabs.Community" = "Topluluk"; +"Scene.Discovery.Tabs.ForYou" = "Senin İçin"; +"Scene.Discovery.Tabs.Hashtags" = "Etiketler"; +"Scene.Discovery.Tabs.News" = "Haberler"; +"Scene.Discovery.Tabs.Posts" = "Gönderiler"; +"Scene.Favorite.Title" = "Favorilerin"; +"Scene.Follower.Footer" = "Diğer sunucudaki takipçiler gösterilemiyor."; +"Scene.Following.Footer" = "Diğer sunucudaki takip edilenler gösterilemiyor."; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; +"Scene.HomeTimeline.NavigationBarState.NewPosts" = "Yeni gönderiler gör"; +"Scene.HomeTimeline.NavigationBarState.Offline" = "Çevrimdışı"; +"Scene.HomeTimeline.NavigationBarState.Published" = "Yayınlandı!"; +"Scene.HomeTimeline.NavigationBarState.Publishing" = "Gönderi yayınlanıyor..."; +"Scene.HomeTimeline.Title" = "Ana Sayfa"; +"Scene.Notification.Keyobard.ShowEverything" = "Her Şeyi Göster"; +"Scene.Notification.Keyobard.ShowMentions" = "Bahsetmeleri Göster"; +"Scene.Notification.NotificationDescription.FavoritedYourPost" = "gönderini favoriledi"; +"Scene.Notification.NotificationDescription.FollowedYou" = "seni takip etti"; +"Scene.Notification.NotificationDescription.MentionedYou" = "senden bahsetti"; +"Scene.Notification.NotificationDescription.PollHasEnded" = "anket sona erdi"; +"Scene.Notification.NotificationDescription.RebloggedYourPost" = "gönderini yeniden paylaştı"; +"Scene.Notification.NotificationDescription.RequestToFollowYou" = "size takip isteği gönderdi"; +"Scene.Notification.Title.Everything" = "Her şey"; +"Scene.Notification.Title.Mentions" = "Bahsetmeler"; +"Scene.Preview.Keyboard.ClosePreview" = "Önizlemeyi Kapat"; +"Scene.Preview.Keyboard.ShowNext" = "Sonrakini Göster"; +"Scene.Preview.Keyboard.ShowPrevious" = "Öncekini Göster"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Listeyi açmak için çift tıklayın"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Profil fotoğrafını düzenle"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Profil resmini göster"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Kapak fotoğrafını göster"; +"Scene.Profile.Dashboard.Followers" = "takipçi"; +"Scene.Profile.Dashboard.Following" = "takip ediliyor"; +"Scene.Profile.Dashboard.Posts" = "gönderiler"; +"Scene.Profile.Fields.AddRow" = "Satır Ekle"; +"Scene.Profile.Fields.Placeholder.Content" = "İçerik"; +"Scene.Profile.Fields.Placeholder.Label" = "Etiket"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "%@ engellemeyi onayla"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Hesabı Engelle"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "%@ susturmak için onaylayın"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "Hesabı sustur"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "%@ engellemeyi kaldırmayı onaylayın"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "Hesabın Engelini Kaldır"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "%@ susturmasını kaldırmak için onaylayın"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Title" = "Susturmayı kaldır"; +"Scene.Profile.SegmentedControl.About" = "Hakkında"; +"Scene.Profile.SegmentedControl.Media" = "Medya"; +"Scene.Profile.SegmentedControl.Posts" = "Gönderiler"; +"Scene.Profile.SegmentedControl.PostsAndReplies" = "Gönderiler ve Yanıtlar"; +"Scene.Profile.SegmentedControl.Replies" = "Yanıtlar"; +"Scene.Register.Error.Item.Agreement" = "Anlaşma"; +"Scene.Register.Error.Item.Email" = "E-posta"; +"Scene.Register.Error.Item.Locale" = "Locale"; +"Scene.Register.Error.Item.Password" = "Parola"; +"Scene.Register.Error.Item.Reason" = "Sebep"; +"Scene.Register.Error.Item.Username" = "Kullanıcı adı"; +"Scene.Register.Error.Reason.Accepted" = "%@ kabul edilmelidir"; +"Scene.Register.Error.Reason.Blank" = "%@ gerekli"; +"Scene.Register.Error.Reason.Blocked" = "%@ izin verilmeyen bir e-posta sağlayıcı içeriyor"; +"Scene.Register.Error.Reason.Inclusion" = "%@ desteklenen bir değer değil"; +"Scene.Register.Error.Reason.Invalid" = "%@ geçersiz"; +"Scene.Register.Error.Reason.Reserved" = "%@ rezerve edilen bir kelime"; +"Scene.Register.Error.Reason.Taken" = "%@ zaten kullanımda"; +"Scene.Register.Error.Reason.TooLong" = "%@ çok uzun"; +"Scene.Register.Error.Reason.TooShort" = "%@ çok kısa"; +"Scene.Register.Error.Reason.Unreachable" = "%@ mevcut değil"; +"Scene.Register.Error.Special.EmailInvalid" = "Bu geçerli bir e-posta adresi değil"; +"Scene.Register.Error.Special.PasswordTooShort" = "Şifre çok kısa (en az 8 karakter olmalı)"; +"Scene.Register.Error.Special.UsernameInvalid" = "Kullanıcı adı yalnızca alfasayısal karakterler ve alt çizgiler içerebilir"; +"Scene.Register.Error.Special.UsernameTooLong" = "Kullanıcı adı çok uzun (30 karakterden uzun olamaz)"; +"Scene.Register.Input.Avatar.Delete" = "Sil"; +"Scene.Register.Input.DisplayName.Placeholder" = "görünen ad"; +"Scene.Register.Input.Email.Placeholder" = "e-posta"; +"Scene.Register.Input.Invite.RegistrationUserInviteRequest" = "Neden katılmak istiyorsun?"; +"Scene.Register.Input.Password.Accessibility.Checked" = "işaretli"; +"Scene.Register.Input.Password.Accessibility.Unchecked" = "işaretsiz"; +"Scene.Register.Input.Password.CharacterLimit" = "8 karakter"; +"Scene.Register.Input.Password.Hint" = "Parolanız en az sekiz karakter içermelidir"; +"Scene.Register.Input.Password.Placeholder" = "parola"; +"Scene.Register.Input.Password.Require" = "Parolanızda en azından şunlar olmalı:"; +"Scene.Register.Input.Username.DuplicatePrompt" = "Bu kullanıcı adı alınmış."; +"Scene.Register.Input.Username.Placeholder" = "kullanıcı adı"; +"Scene.Register.Title" = "%@ için kurulumunuzu yapalım"; +"Scene.Report.Content1" = "Bu rapora eklemek istediğiniz başka gönderiler var mı?"; +"Scene.Report.Content2" = "Bu rapor hakkında moderatörlerin bilmesi gerektiği bir şey var mı?"; +"Scene.Report.ReportSentTitle" = "Rapor için teşekkürler, bununla ilgileneceğiz."; +"Scene.Report.Reported" = "RAPORLANDI"; +"Scene.Report.Send" = "Raporu Gönder"; +"Scene.Report.SkipToSend" = "Yorum yapmadan gönder"; +"Scene.Report.Step1" = "Adım 1/2"; +"Scene.Report.Step2" = "Adım 2/2"; +"Scene.Report.StepFinal.BlockUser" = "Block %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Bunu görmek istemiyor musunuz?"; +"Scene.Report.StepFinal.MuteUser" = "Mute %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; +"Scene.Report.StepFinal.Unfollow" = "Takibi bırak"; +"Scene.Report.StepFinal.UnfollowUser" = "Unfollow %@"; +"Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Bilmemiz gereken başka bir şey var mı?"; +"Scene.Report.StepFour.Step4Of4" = "Adım 4/4"; +"Scene.Report.StepOne.IDontLikeIt" = "Beğenmedim"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "Görmek isteyeceğim bir şey değil"; +"Scene.Report.StepOne.ItViolatesServerRules" = "Sunucu kurallarını ihlal ediyor"; +"Scene.Report.StepOne.ItsSomethingElse" = "Başka bir şey"; +"Scene.Report.StepOne.ItsSpam" = "Spam"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; +"Scene.Report.StepOne.SelectTheBestMatch" = "En iyi seçeneceği seçiniz"; +"Scene.Report.StepOne.Step1Of4" = "Adım 1/4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "Sorun bunlardan biri değil"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "Bu hesap ile ilgili sorun nedir?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "Bu gönderi ile ilgili sorun nedir?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "%@ kişisinin sorunu nedir?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Bu bildirimi destekleyecek herhangi bir gönderi var mı?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Geçerli olanların tümünü seçiniz"; +"Scene.Report.StepThree.Step3Of4" = "Adım 3/4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "Beğenmedim"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Geçerli olan tümünü seçiniz"; +"Scene.Report.StepTwo.Step2Of4" = "Adım 2/4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Hangi kurallar ihlal ediliyor?"; +"Scene.Report.TextPlaceholder" = "Ek yorum yazın veya yapıştırın"; +"Scene.Report.Title" = "%@ kişisini bildir"; +"Scene.Report.TitleReport" = "Raporla"; +"Scene.Search.Recommend.Accounts.Description" = "Bu hesapları takip etmek isteyebilirsiniz"; +"Scene.Search.Recommend.Accounts.Follow" = "Takip et"; +"Scene.Search.Recommend.Accounts.Title" = "Hoşunuza gidebilecek hesaplar"; +"Scene.Search.Recommend.ButtonText" = "Tümünü Gör"; +"Scene.Search.Recommend.HashTag.Description" = "Oldukça ilgi gören etiketler"; +"Scene.Search.Recommend.HashTag.PeopleTalking" = "%@ kişi konuşuyor"; +"Scene.Search.Recommend.HashTag.Title" = "Mastodon'da Popüler"; +"Scene.Search.SearchBar.Cancel" = "İptal"; +"Scene.Search.SearchBar.Placeholder" = "Etiketleri ve kullanıcıları ara"; +"Scene.Search.Searching.Clear" = "Temizle"; +"Scene.Search.Searching.EmptyState.NoResults" = "Sonuç yok"; +"Scene.Search.Searching.RecentSearch" = "Son aramalar"; +"Scene.Search.Searching.Segment.All" = "Tümü"; +"Scene.Search.Searching.Segment.Hashtags" = "Etiketler"; +"Scene.Search.Searching.Segment.People" = "İnsanlar"; +"Scene.Search.Searching.Segment.Posts" = "Gönderiler"; +"Scene.Search.Title" = "Arama"; +"Scene.ServerPicker.Button.Category.Academia" = "akademi"; +"Scene.ServerPicker.Button.Category.Activism" = "aktivizm"; +"Scene.ServerPicker.Button.Category.All" = "Tümü"; +"Scene.ServerPicker.Button.Category.AllAccessiblityDescription" = "Kategori: Tümü"; +"Scene.ServerPicker.Button.Category.Art" = "sanat"; +"Scene.ServerPicker.Button.Category.Food" = "yiyecek"; +"Scene.ServerPicker.Button.Category.Furry" = "furry"; +"Scene.ServerPicker.Button.Category.Games" = "oyunlar"; +"Scene.ServerPicker.Button.Category.General" = "genel"; +"Scene.ServerPicker.Button.Category.Journalism" = "gazetecilik"; +"Scene.ServerPicker.Button.Category.Lgbt" = "lgbt"; +"Scene.ServerPicker.Button.Category.Music" = "müzik"; +"Scene.ServerPicker.Button.Category.Regional" = "bölgesel"; +"Scene.ServerPicker.Button.Category.Tech" = "teknoloji"; +"Scene.ServerPicker.Button.SeeLess" = "Daha Az Göster"; +"Scene.ServerPicker.Button.SeeMore" = "Daha Fazla Gör"; +"Scene.ServerPicker.EmptyState.BadNetwork" = "Veriyi yüklerken bir hata oluştu. Lütfen internet bağlantınızı kontrol edin."; +"Scene.ServerPicker.EmptyState.FindingServers" = "Mevcut sunucular aranıyor..."; +"Scene.ServerPicker.EmptyState.NoResults" = "Sonuç yok"; +"Scene.ServerPicker.Input.Placeholder" = "Toplulukları ara"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; +"Scene.ServerPicker.Label.Category" = "KATEGORİ"; +"Scene.ServerPicker.Label.Language" = "DİL"; +"Scene.ServerPicker.Label.Users" = "KULLANICILAR"; +"Scene.ServerPicker.Subtitle" = "İlgi alanlarınıza, bölgenize veya genel amaçlı bir topluluk seçin."; +"Scene.ServerPicker.SubtitleExtend" = "İlgi alanlarınıza, bölgenize veya genel amaçlı bir topluluk seçin. Her topluluk tamamen bağımsız bir kuruluş veya kişi tarafından işletilmektedir."; +"Scene.ServerPicker.Title" = "Mastodon, farklı topluluklardaki kullanıcılardan oluşur."; +"Scene.ServerRules.Button.Confirm" = "Kabul Ediyorum"; +"Scene.ServerRules.PrivacyPolicy" = "gizlilik politikası"; +"Scene.ServerRules.Prompt" = "Devam ederek, %@ için kullanım şartlarını ve gizlilik politikasını kabul etmiş olursunuz."; +"Scene.ServerRules.Subtitle" = "Bunlar, %@ moderatörleri tarafından ayarlanmış ve uygulanmıştır."; +"Scene.ServerRules.TermsOfService" = "kullanım şartları"; +"Scene.ServerRules.Title" = "Bazı temel kurallar."; +"Scene.Settings.Footer.MastodonDescription" = "Mastodon açık kaynaklı bir yazılımdır. GitHub'dan %@ (%@) üzerinden katkıda bulunabilir veya sorunları bildirebilirsiniz"; +"Scene.Settings.Keyboard.CloseSettingsWindow" = "Ayarlar Penceresini Kapat"; +"Scene.Settings.Section.Appearance.Automatic" = "Otomatik"; +"Scene.Settings.Section.Appearance.Dark" = "Daima Koyu"; +"Scene.Settings.Section.Appearance.Light" = "Daima Açık"; +"Scene.Settings.Section.Appearance.Title" = "Görünüm"; +"Scene.Settings.Section.BoringZone.AccountSettings" = "Hesap Ayarları"; +"Scene.Settings.Section.BoringZone.Privacy" = "Gizlilik Politikası"; +"Scene.Settings.Section.BoringZone.Terms" = "Hizmet Şartları"; +"Scene.Settings.Section.BoringZone.Title" = "Sıkıcı Bölge"; +"Scene.Settings.Section.LookAndFeel.Light" = "Açık"; +"Scene.Settings.Section.LookAndFeel.ReallyDark" = "Gerçek Koyu"; +"Scene.Settings.Section.LookAndFeel.SortaDark" = "Hafif Koyu"; +"Scene.Settings.Section.LookAndFeel.Title" = "Görünüm"; +"Scene.Settings.Section.LookAndFeel.UseSystem" = "Sistem İle Aynı"; +"Scene.Settings.Section.Notifications.Boosts" = "Gönderimi yeniden paylaştığında"; +"Scene.Settings.Section.Notifications.Favorites" = "Gönderimi favorilerine eklediğinde"; +"Scene.Settings.Section.Notifications.Follows" = "Beni takip ettiğinde"; +"Scene.Settings.Section.Notifications.Mentions" = "Benden bahsettiğinde"; +"Scene.Settings.Section.Notifications.Title" = "Bildirimler"; +"Scene.Settings.Section.Notifications.Trigger.Anyone" = "herhangi biri"; +"Scene.Settings.Section.Notifications.Trigger.Follow" = "takip ettiğim biri"; +"Scene.Settings.Section.Notifications.Trigger.Follower" = "bir takipçim"; +"Scene.Settings.Section.Notifications.Trigger.Noone" = "bilgilendirme"; +"Scene.Settings.Section.Notifications.Trigger.Title" = "Beni şu durumda bilgilendir: "; +"Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Hareketli avatarları devre dışı bırak"; +"Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Hareketli emojileri devre dışı bırak"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Bağlantıları Mastodon içinden aç"; +"Scene.Settings.Section.Preference.Title" = "Tercihler"; +"Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Tam siyah koyu modu"; +"Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Bağlantıları varsayılan tarayıcıda aç"; +"Scene.Settings.Section.SpicyZone.Clear" = "Medya Önbelleğini Temizle"; +"Scene.Settings.Section.SpicyZone.Signout" = "Oturumu Kapat"; +"Scene.Settings.Section.SpicyZone.Title" = "Tehlikeli bölge"; +"Scene.Settings.Title" = "Ayarlar"; +"Scene.SuggestionAccount.FollowExplain" = "Birisini takip ettiğinizde, ana sayfanızda o kişinin gönderilerini görürsünüz."; +"Scene.SuggestionAccount.Title" = "Takip Edecek İnsanlar Bul"; +"Scene.Thread.BackTitle" = "Gönderi"; +"Scene.Thread.Title" = "%@ kullanıcının gönderisi"; +"Scene.Welcome.GetStarted" = "Başlayın"; +"Scene.Welcome.LogIn" = "Oturum Aç"; +"Scene.Welcome.Slogan" = "Sosyal ağ, +tekrardan ellerinizde."; +"Scene.Wizard.AccessibilityHint" = "Bu yardımı kapatmak için çift tıklayın"; +"Scene.Wizard.MultipleAccountSwitchIntroDescription" = "Profil butonuna basılı tutarak birden fazla hesap arasında geçiş yapın."; +"Scene.Wizard.NewInMastodon" = "Mastodon'da Yeni"; \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/tr.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/tr.lproj/Localizable.stringsdict new file mode 100644 index 000000000..d6817c1f6 --- /dev/null +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/tr.lproj/Localizable.stringsdict @@ -0,0 +1,406 @@ + + + + + a11y.plural.count.unread.notification + + NSStringLocalizedFormatKey + %#@notification_count_unread_notification@ + notification_count_unread_notification + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Okunmamış 1 bildirim + other + Okunmamış %ld bildirim + + + a11y.plural.count.input_limit_exceeds + + NSStringLocalizedFormatKey + %#@character_count@ karakter limiti aşıyor + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 karakter + other + %ld karakter + + + a11y.plural.count.input_limit_remains + + NSStringLocalizedFormatKey + %#@character_count@ karakter limiti var + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 karakter + other + %ld karakter + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@ %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + gönderi + other + gönderi + + + plural.count.post + + NSStringLocalizedFormatKey + %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 gönderi + other + %ld gönderi + + + plural.count.favorite + + NSStringLocalizedFormatKey + %#@favorite_count@ + favorite_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 favori + other + %ld favori + + + plural.count.reblog + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 yeniden paylaşım + other + %ld yeniden paylaşım + + + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld yanıt + other + %ld yanıt + + + plural.count.vote + + NSStringLocalizedFormatKey + %#@vote_count@ + vote_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 oy + other + %ld oy + + + plural.count.voter + + NSStringLocalizedFormatKey + %#@voter_count@ + voter_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 oy veren + other + %ld oy veren + + + plural.people_talking + + NSStringLocalizedFormatKey + %#@count_people_talking@ + count_people_talking + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 kişi konuşuyor + other + %ld kişi konuşuyor + + + plural.count.following + + NSStringLocalizedFormatKey + %#@count_following@ + count_following + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 takip edilen + other + %ld takip edilen + + + plural.count.follower + + NSStringLocalizedFormatKey + %#@count_follower@ + count_follower + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 takipçi + other + %ld takipçi + + + date.year.left + + NSStringLocalizedFormatKey + %#@count_year_left@ + count_year_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 yıl kaldı + other + %ld yıl kaldı + + + date.month.left + + NSStringLocalizedFormatKey + %#@count_month_left@ + count_month_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 ay kaldı + other + %ld ay kaldı + + + date.day.left + + NSStringLocalizedFormatKey + %#@count_day_left@ + count_day_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 gün kaldı + other + %ld gün kaldı + + + date.hour.left + + NSStringLocalizedFormatKey + %#@count_hour_left@ + count_hour_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 saat kaldı + other + %ld saat kaldı + + + date.minute.left + + NSStringLocalizedFormatKey + %#@count_minute_left@ + count_minute_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 dakika kaldı + other + %ld dakika kaldı + + + date.second.left + + NSStringLocalizedFormatKey + %#@count_second_left@ + count_second_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 saniye kaldı + other + %ld saniye kaldı + + + date.year.ago.abbr + + NSStringLocalizedFormatKey + %#@count_year_ago_abbr@ + count_year_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 yıl önce + other + %ld yıl önce + + + date.month.ago.abbr + + NSStringLocalizedFormatKey + %#@count_month_ago_abbr@ + count_month_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 ay önce + other + %ld ay önce + + + date.day.ago.abbr + + NSStringLocalizedFormatKey + %#@count_day_ago_abbr@ + count_day_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 gün önce + other + %ld gün önce + + + date.hour.ago.abbr + + NSStringLocalizedFormatKey + %#@count_hour_ago_abbr@ + count_hour_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 saat önce + other + %ld saat önce + + + date.minute.ago.abbr + + NSStringLocalizedFormatKey + %#@count_minute_ago_abbr@ + count_minute_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 dakika önce + other + %ld dakika önce + + + date.second.ago.abbr + + NSStringLocalizedFormatKey + %#@count_second_ago_abbr@ + count_second_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 saniye önce + other + %ld saniye önce + + + + diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/vi.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/vi.lproj/Localizable.strings index 95eef33b0..d93f0607d 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/vi.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/vi.lproj/Localizable.strings @@ -7,9 +7,9 @@ "Common.Alerts.DeletePost.Message" = "Bạn có chắc muốn xóa tút này không?"; "Common.Alerts.DeletePost.Title" = "Xóa tút"; "Common.Alerts.DiscardPostContent.Message" = "Xác nhận bỏ qua nội dung tút đã viết."; -"Common.Alerts.DiscardPostContent.Title" = "Hủy bản nháp"; -"Common.Alerts.EditProfileFailure.Message" = "Không thể chỉnh sửa trang cá nhân. Vui lòng thử lại."; -"Common.Alerts.EditProfileFailure.Title" = "Lỗi chỉnh sửa trang cá nhân"; +"Common.Alerts.DiscardPostContent.Title" = "Bỏ bản nháp"; +"Common.Alerts.EditProfileFailure.Message" = "Không thể chỉnh sửa hồ sơ. Vui lòng thử lại."; +"Common.Alerts.EditProfileFailure.Title" = "Lỗi chỉnh sửa hồ sơ"; "Common.Alerts.PublishPostFailure.AttachmentsMessage.MoreThanOneVideo" = "Không thể đính kèm nhiều video."; "Common.Alerts.PublishPostFailure.AttachmentsMessage.VideoAttachWithPhoto" = "Không thể đính kèm video cùng với hình ảnh."; "Common.Alerts.PublishPostFailure.Message" = "Không thể đăng tút. @@ -108,24 +108,25 @@ Vui lòng kiểm tra kết nối mạng."; "Common.Controls.Status.MediaContentWarning" = "Nhấn để hiển thị"; "Common.Controls.Status.Poll.Closed" = "Kết thúc"; "Common.Controls.Status.Poll.Vote" = "Bình chọn"; +"Common.Controls.Status.SensitiveContent" = "Nội dung nhạy cảm"; "Common.Controls.Status.ShowPost" = "Xem tút"; -"Common.Controls.Status.ShowUserProfile" = "Xem trang cá nhân"; +"Common.Controls.Status.ShowUserProfile" = "Xem trang hồ sơ"; "Common.Controls.Status.Tag.Email" = "Email"; "Common.Controls.Status.Tag.Emoji" = "Emoji"; "Common.Controls.Status.Tag.Hashtag" = "Hashtag"; "Common.Controls.Status.Tag.Link" = "Liên kết"; "Common.Controls.Status.Tag.Mention" = "Nhắc đến"; "Common.Controls.Status.Tag.Url" = "URL"; -"Common.Controls.Status.TapToReveal" = "Nhấn để hiển thị"; +"Common.Controls.Status.TapToReveal" = "Nhấn để xem"; "Common.Controls.Status.UserReblogged" = "%@ đăng lại"; -"Common.Controls.Status.UserRepliedTo" = "Trả lời %@"; +"Common.Controls.Status.UserRepliedTo" = "Trả lời đến %@"; "Common.Controls.Status.Visibility.Direct" = "Chỉ người được nhắc đến có thể thấy tút."; "Common.Controls.Status.Visibility.Private" = "Chỉ người theo dõi của họ có thể thấy tút này."; "Common.Controls.Status.Visibility.PrivateFromMe" = "Chỉ người theo dõi tôi có thể thấy tút này."; "Common.Controls.Status.Visibility.Unlisted" = "Ai cũng thấy tút này nhưng không hiện trên bảng tin máy chủ."; "Common.Controls.Tabs.Home" = "Bảng tin"; "Common.Controls.Tabs.Notification" = "Thông báo"; -"Common.Controls.Tabs.Profile" = "Trang cá nhân"; +"Common.Controls.Tabs.Profile" = "Trang hồ sơ"; "Common.Controls.Tabs.Search" = "Tìm kiếm"; "Common.Controls.Timeline.Filtered" = "Bộ lọc"; "Common.Controls.Timeline.Header.BlockedWarning" = "Bạn không thể xem trang người này @@ -174,7 +175,7 @@ tải lên Mastodon."; "Scene.Compose.MediaSelection.Browse" = "Chọn"; "Scene.Compose.MediaSelection.Camera" = "Chụp ảnh"; "Scene.Compose.MediaSelection.PhotoLibrary" = "Thư viện hình ảnh"; -"Scene.Compose.Poll.DurationTime" = "Thời gian: %@"; +"Scene.Compose.Poll.DurationTime" = "Thời hạn: %@"; "Scene.Compose.Poll.OneDay" = "1 ngày"; "Scene.Compose.Poll.OneHour" = "1 giờ"; "Scene.Compose.Poll.OptionNumber" = "Lựa chọn %ld"; @@ -182,10 +183,10 @@ tải lên Mastodon."; "Scene.Compose.Poll.SixHours" = "6 giờ"; "Scene.Compose.Poll.ThirtyMinutes" = "30 phút"; "Scene.Compose.Poll.ThreeDays" = "3 ngày"; -"Scene.Compose.ReplyingToUser" = "trả lời đến %@"; +"Scene.Compose.ReplyingToUser" = "trả lời %@"; "Scene.Compose.Title.NewPost" = "Viết tút"; "Scene.Compose.Title.NewReply" = "Viết trả lời"; -"Scene.Compose.Visibility.Direct" = "Chỉ người được nhắc đến"; +"Scene.Compose.Visibility.Direct" = "Nhắn riêng"; "Scene.Compose.Visibility.Private" = "Riêng tư"; "Scene.Compose.Visibility.Public" = "Công khai"; "Scene.Compose.Visibility.Unlisted" = "Hạn chế"; @@ -200,9 +201,17 @@ tải lên Mastodon."; "Scene.ConfirmEmail.OpenEmailApp.Title" = "Kiểm tra hộp thư của bạn."; "Scene.ConfirmEmail.Subtitle" = "Nhấn vào liên kết chúng tôi gửi qua email để xác thực tài khoản."; "Scene.ConfirmEmail.Title" = "Còn điều này nữa."; +"Scene.Discovery.Intro" = "Đây là những tút thu hút được sự chú ý trong góc Mastodon của bạn."; +"Scene.Discovery.Tabs.Community" = "Máy chủ"; +"Scene.Discovery.Tabs.ForYou" = "Dành cho bạn"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtag"; +"Scene.Discovery.Tabs.News" = "Tin tức"; +"Scene.Discovery.Tabs.Posts" = "Tút"; "Scene.Favorite.Title" = "Lượt thích"; "Scene.Follower.Footer" = "Không hiển thị người theo dõi từ máy chủ khác."; "Scene.Following.Footer" = "Không hiển thị người bạn theo dõi từ máy chủ khác."; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Đọc những tút mới"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Ngoại tuyến"; "Scene.HomeTimeline.NavigationBarState.Published" = "Đã đăng!"; @@ -285,6 +294,37 @@ tải lên Mastodon."; "Scene.Report.SkipToSend" = "Gửi không ghi chú"; "Scene.Report.Step1" = "Bước 1 trong 2"; "Scene.Report.Step2" = "Bước 2 trong 2"; +"Scene.Report.StepFinal.BlockUser" = "Block %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Don’t want to see this?"; +"Scene.Report.StepFinal.MuteUser" = "Mute %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; +"Scene.Report.StepFinal.Unfollow" = "Unfollow"; +"Scene.Report.StepFinal.UnfollowUser" = "Unfollow %@"; +"Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Is there anything else we should know?"; +"Scene.Report.StepFour.Step4Of4" = "Step 4 of 4"; +"Scene.Report.StepOne.IDontLikeIt" = "I don’t like it"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "It is not something you want to see"; +"Scene.Report.StepOne.ItViolatesServerRules" = "It violates server rules"; +"Scene.Report.StepOne.ItsSomethingElse" = "It’s something else"; +"Scene.Report.StepOne.ItsSpam" = "It’s spam"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Select the best match"; +"Scene.Report.StepOne.Step1Of4" = "Step 1 of 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "The issue does not fit into other categories"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "What's wrong with this account?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "What's wrong with this post?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "What's wrong with %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Are there any posts that back up this report?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepThree.Step3Of4" = "Step 3 of 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "I just don’t like it"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepTwo.Step2Of4" = "Step 2 of 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Which rules are being violated?"; "Scene.Report.TextPlaceholder" = "Nhập hoặc bổ sung chú thích"; "Scene.Report.Title" = "Báo cáo %@"; "Scene.Report.TitleReport" = "Báo cáo"; @@ -312,7 +352,7 @@ tải lên Mastodon."; "Scene.ServerPicker.Button.Category.Art" = "nghệ thuật"; "Scene.ServerPicker.Button.Category.Food" = "ăn uống"; "Scene.ServerPicker.Button.Category.Furry" = "furry"; -"Scene.ServerPicker.Button.Category.Games" = "trò chơi"; +"Scene.ServerPicker.Button.Category.Games" = "trò chơi điện tử"; "Scene.ServerPicker.Button.Category.General" = "chung"; "Scene.ServerPicker.Button.Category.Journalism" = "tin tức"; "Scene.ServerPicker.Button.Category.Lgbt" = "lgbt"; @@ -324,13 +364,14 @@ tải lên Mastodon."; "Scene.ServerPicker.EmptyState.BadNetwork" = "Đã xảy ra lỗi. Hãy thử lại hoặc kiểm tra kết nối internet của bạn."; "Scene.ServerPicker.EmptyState.FindingServers" = "Đang tìm máy chủ hoạt động..."; "Scene.ServerPicker.EmptyState.NoResults" = "Không có kết quả"; -"Scene.ServerPicker.Input.Placeholder" = "Tìm cộng đồng"; +"Scene.ServerPicker.Input.Placeholder" = "Tìm máy chủ"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; "Scene.ServerPicker.Label.Category" = "PHÂN LOẠI"; "Scene.ServerPicker.Label.Language" = "NGÔN NGỮ"; "Scene.ServerPicker.Label.Users" = "NGƯỜI DÙNG"; -"Scene.ServerPicker.Subtitle" = "Chọn một cộng đồng dựa theo sở thích, tôn giáo, hoặc ý muốn của bạn."; -"Scene.ServerPicker.SubtitleExtend" = "Chọn một cộng đồng dựa theo sở thích, tôn giáo, hoặc ý muốn của bạn. Mỗi cộng đồng có thể được vận hành bởi một tổ chức độc lập hoặc một cá nhân."; -"Scene.ServerPicker.Title" = "Mastodon gồm nhiều cộng đồng với nhiều thành viên khác nhau."; +"Scene.ServerPicker.Subtitle" = "Chọn một máy chủ dựa theo sở thích, tôn giáo, hoặc ý muốn của bạn."; +"Scene.ServerPicker.SubtitleExtend" = "Chọn một máy chủ dựa theo sở thích, tôn giáo, hoặc ý muốn của bạn. Mỗi máy chủ có thể được vận hành bởi một cá nhân hoặc một tổ chức."; +"Scene.ServerPicker.Title" = "Mastodon gồm nhiều máy chủ với thành viên riêng."; "Scene.ServerRules.Button.Confirm" = "Tôi đồng ý"; "Scene.ServerRules.PrivacyPolicy" = "chính sách bảo mật"; "Scene.ServerRules.Prompt" = "Tiếp tục nghĩa là bạn đồng ý điều khoản dịch vụ và chính sách bảo mật của %@."; @@ -359,7 +400,7 @@ tải lên Mastodon."; "Scene.Settings.Section.Notifications.Title" = "Thông báo"; "Scene.Settings.Section.Notifications.Trigger.Anyone" = "ai đó"; "Scene.Settings.Section.Notifications.Trigger.Follow" = "người tôi theo dõi"; -"Scene.Settings.Section.Notifications.Trigger.Follower" = "người theo dõi"; +"Scene.Settings.Section.Notifications.Trigger.Follower" = "người theo dõi tôi"; "Scene.Settings.Section.Notifications.Trigger.Noone" = "không một ai"; "Scene.Settings.Section.Notifications.Trigger.Title" = "Thông báo khi"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Tắt ảnh đại diện GIF"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.strings index f4a90541d..50e5b42bf 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.strings @@ -98,16 +98,17 @@ "Common.Controls.Status.Actions.Menu" = "菜单"; "Common.Controls.Status.Actions.Reblog" = "转发"; "Common.Controls.Status.Actions.Reply" = "回复"; -"Common.Controls.Status.Actions.ShowGif" = "Show GIF"; -"Common.Controls.Status.Actions.ShowImage" = "Show image"; -"Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; -"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tap then hold to show menu"; +"Common.Controls.Status.Actions.ShowGif" = "显示 GIF"; +"Common.Controls.Status.Actions.ShowImage" = "显示图片"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "显示视频播放器"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "长按以显示菜单"; "Common.Controls.Status.Actions.Unfavorite" = "取消喜欢"; "Common.Controls.Status.Actions.Unreblog" = "取消转发"; "Common.Controls.Status.ContentWarning" = "内容警告"; "Common.Controls.Status.MediaContentWarning" = "点击任意位置显示"; "Common.Controls.Status.Poll.Closed" = "已关闭"; "Common.Controls.Status.Poll.Vote" = "投票"; +"Common.Controls.Status.SensitiveContent" = "敏感内容"; "Common.Controls.Status.ShowPost" = "显示帖子"; "Common.Controls.Status.ShowUserProfile" = "查看用户个人资料"; "Common.Controls.Status.Tag.Email" = "电子邮箱"; @@ -116,7 +117,7 @@ "Common.Controls.Status.Tag.Link" = "链接"; "Common.Controls.Status.Tag.Mention" = "提及"; "Common.Controls.Status.Tag.Url" = "URL"; -"Common.Controls.Status.TapToReveal" = "Tap to reveal"; +"Common.Controls.Status.TapToReveal" = "点击以显示"; "Common.Controls.Status.UserReblogged" = "%@ 转发"; "Common.Controls.Status.UserRepliedTo" = "回复给 %@"; "Common.Controls.Status.Visibility.Direct" = "只有提到的用户才能看到此帖子。"; @@ -201,9 +202,17 @@ "Scene.ConfirmEmail.Subtitle" = "我们刚刚向 %@ 发送了一封电子邮件, 点击链接确认你的帐户。"; "Scene.ConfirmEmail.Title" = "最后一件事。"; +"Scene.Discovery.Intro" = "这些嘟文在你的长毛象小宇宙中备受关注。"; +"Scene.Discovery.Tabs.Community" = "社区"; +"Scene.Discovery.Tabs.ForYou" = "为你推荐"; +"Scene.Discovery.Tabs.Hashtags" = "话题标签"; +"Scene.Discovery.Tabs.News" = "最新消息"; +"Scene.Discovery.Tabs.Posts" = "嘟文"; "Scene.Favorite.Title" = "你的喜欢"; "Scene.Follower.Footer" = "不会显示来自其它服务器的关注者"; "Scene.Following.Footer" = "不会显示来自其它服务器的关注"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "查看新帖子"; "Scene.HomeTimeline.NavigationBarState.Offline" = "离线"; "Scene.HomeTimeline.NavigationBarState.Published" = "已发送"; @@ -222,10 +231,10 @@ "Scene.Preview.Keyboard.ClosePreview" = "关闭预览"; "Scene.Preview.Keyboard.ShowNext" = "显示下一个"; "Scene.Preview.Keyboard.ShowPrevious" = "显示前一个"; -"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Double tap to open the list"; -"Scene.Profile.Accessibility.EditAvatarImage" = "Edit avatar image"; -"Scene.Profile.Accessibility.ShowAvatarImage" = "Show avatar image"; -"Scene.Profile.Accessibility.ShowBannerImage" = "Show banner image"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "双击打开列表"; +"Scene.Profile.Accessibility.EditAvatarImage" = "编辑头像"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "显示头像"; +"Scene.Profile.Accessibility.ShowBannerImage" = "显示顶部横幅图片"; "Scene.Profile.Dashboard.Followers" = "关注者"; "Scene.Profile.Dashboard.Following" = "正在关注"; "Scene.Profile.Dashboard.Posts" = "帖子"; @@ -286,6 +295,37 @@ "Scene.Report.SkipToSend" = "直接发送"; "Scene.Report.Step1" = "步骤 1 / 2"; "Scene.Report.Step2" = "步骤 2 / 2"; +"Scene.Report.StepFinal.BlockUser" = "Block %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Don’t want to see this?"; +"Scene.Report.StepFinal.MuteUser" = "Mute %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; +"Scene.Report.StepFinal.Unfollow" = "Unfollow"; +"Scene.Report.StepFinal.UnfollowUser" = "Unfollow %@"; +"Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Is there anything else we should know?"; +"Scene.Report.StepFour.Step4Of4" = "Step 4 of 4"; +"Scene.Report.StepOne.IDontLikeIt" = "I don’t like it"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "It is not something you want to see"; +"Scene.Report.StepOne.ItViolatesServerRules" = "It violates server rules"; +"Scene.Report.StepOne.ItsSomethingElse" = "It’s something else"; +"Scene.Report.StepOne.ItsSpam" = "It’s spam"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Select the best match"; +"Scene.Report.StepOne.Step1Of4" = "Step 1 of 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "The issue does not fit into other categories"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "What's wrong with this account?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "What's wrong with this post?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "What's wrong with %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Are there any posts that back up this report?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepThree.Step3Of4" = "Step 3 of 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "I just don’t like it"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepTwo.Step2Of4" = "Step 2 of 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Which rules are being violated?"; "Scene.Report.TextPlaceholder" = "输入或粘贴额外的注释"; "Scene.Report.Title" = "举报 %@"; "Scene.Report.TitleReport" = "举报"; @@ -326,6 +366,7 @@ "Scene.ServerPicker.EmptyState.FindingServers" = "正在查找可用的服务器..."; "Scene.ServerPicker.EmptyState.NoResults" = "无结果"; "Scene.ServerPicker.Input.Placeholder" = "查找或加入你自己的服务器..."; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; "Scene.ServerPicker.Label.Category" = "类别"; "Scene.ServerPicker.Label.Language" = "语言"; "Scene.ServerPicker.Label.Users" = "用户"; @@ -366,7 +407,7 @@ "Scene.Settings.Section.Notifications.Trigger.Title" = "提示通知来自"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "禁用动画头像"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "禁用动画表情"; -"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Open links in Mastodon"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "在 Mastodon 中打开链接"; "Scene.Settings.Section.Preference.Title" = "偏好"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "纯黑模式"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "使用默认浏览器打开链接"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.stringsdict index 418903836..6c2661ee5 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.stringsdict @@ -111,7 +111,7 @@ NSStringFormatValueTypeKey ld other - %ld replies + %ld 条回复 plural.count.vote diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hant.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hant.lproj/Localizable.strings new file mode 100644 index 000000000..0de6127e6 --- /dev/null +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hant.lproj/Localizable.strings @@ -0,0 +1,421 @@ +"Common.Alerts.BlockDomain.BlockEntireDomain" = "封鎖網域"; +"Common.Alerts.BlockDomain.Title" = "真的非常確定封鎖整個 %@ 網域嗎?大部分情況下,您只需要封鎖或靜音少數特定的帳帳戶能滿足需求了。您將不能看到來自此網域的內容。您來自該網域的跟隨者也將被移除。"; +"Common.Alerts.CleanCache.Message" = "成功清除 %@ 快取。"; +"Common.Alerts.CleanCache.Title" = "清除快取"; +"Common.Alerts.Common.PleaseTryAgain" = "請再試一次。"; +"Common.Alerts.Common.PleaseTryAgainLater" = "請稍候再試。"; +"Common.Alerts.DeletePost.Message" = "是否確定要刪除此嘟文?"; +"Common.Alerts.DeletePost.Title" = "刪除嘟文"; +"Common.Alerts.DiscardPostContent.Message" = "確認放棄編寫中的嘟文內容。"; +"Common.Alerts.DiscardPostContent.Title" = "捨棄草稿"; +"Common.Alerts.EditProfileFailure.Message" = "無法編輯個人檔案。請重試。"; +"Common.Alerts.EditProfileFailure.Title" = "編輯個人檔案錯誤"; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.MoreThanOneVideo" = "無法附加一個以上影片。"; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.VideoAttachWithPhoto" = "無法在已有圖片的嘟文上附加影片。"; +"Common.Alerts.PublishPostFailure.Message" = "發表嘟文失敗。 +請檢查您的網路連線。"; +"Common.Alerts.PublishPostFailure.Title" = "發表嘟文失敗"; +"Common.Alerts.SavePhotoFailure.Message" = "請開啟圖片庫存取權限以儲存照片。"; +"Common.Alerts.SavePhotoFailure.Title" = "儲存照片失敗"; +"Common.Alerts.ServerError.Title" = "伺服器錯誤"; +"Common.Alerts.SignOut.Confirm" = "登出"; +"Common.Alerts.SignOut.Message" = "您確定要登出嗎?"; +"Common.Alerts.SignOut.Title" = "登出"; +"Common.Alerts.SignUpFailure.Title" = "註冊失敗"; +"Common.Alerts.VoteFailure.PollEnded" = "投票已結束"; +"Common.Alerts.VoteFailure.Title" = "投票失敗"; +"Common.Controls.Actions.Add" = "新增"; +"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" = "複製照片"; +"Common.Controls.Actions.Delete" = "刪除"; +"Common.Controls.Actions.Discard" = "捨棄"; +"Common.Controls.Actions.Done" = "完成"; +"Common.Controls.Actions.Edit" = "編輯"; +"Common.Controls.Actions.FindPeople" = "尋找一些人來跟隨"; +"Common.Controls.Actions.ManuallySearch" = "手動搜尋"; +"Common.Controls.Actions.Next" = "下一頁"; +"Common.Controls.Actions.Ok" = "OK"; +"Common.Controls.Actions.Open" = "開啟"; +"Common.Controls.Actions.OpenInBrowser" = "在瀏覽器中開啟"; +"Common.Controls.Actions.OpenInSafari" = "在 Safari 中開啟"; +"Common.Controls.Actions.Preview" = "預覽"; +"Common.Controls.Actions.Previous" = "上一步"; +"Common.Controls.Actions.Remove" = "刪除"; +"Common.Controls.Actions.Reply" = "回覆"; +"Common.Controls.Actions.ReportUser" = "檢舉 %@"; +"Common.Controls.Actions.Save" = "儲存"; +"Common.Controls.Actions.SavePhoto" = "儲存照片"; +"Common.Controls.Actions.SeeMore" = "檢視更多"; +"Common.Controls.Actions.Settings" = "設定"; +"Common.Controls.Actions.Share" = "分享"; +"Common.Controls.Actions.SharePost" = "分享嘟文"; +"Common.Controls.Actions.ShareUser" = "分享 %@"; +"Common.Controls.Actions.SignIn" = "登入"; +"Common.Controls.Actions.SignUp" = "註冊"; +"Common.Controls.Actions.Skip" = "跳過"; +"Common.Controls.Actions.TakePhoto" = "拍攝照片"; +"Common.Controls.Actions.TryAgain" = "再試一次"; +"Common.Controls.Actions.UnblockDomain" = "解除封鎖 %@"; +"Common.Controls.Friendship.Block" = "封鎖"; +"Common.Controls.Friendship.BlockDomain" = "封鎖 %@"; +"Common.Controls.Friendship.BlockUser" = "封鎖 %@"; +"Common.Controls.Friendship.Blocked" = "已封鎖"; +"Common.Controls.Friendship.EditInfo" = "編輯"; +"Common.Controls.Friendship.Follow" = "跟隨"; +"Common.Controls.Friendship.Following" = "跟隨中"; +"Common.Controls.Friendship.Mute" = "靜音"; +"Common.Controls.Friendship.MuteUser" = "靜音 %@"; +"Common.Controls.Friendship.Muted" = "已靜音"; +"Common.Controls.Friendship.Pending" = "等待中"; +"Common.Controls.Friendship.Request" = "請求"; +"Common.Controls.Friendship.Unblock" = "解除封鎖"; +"Common.Controls.Friendship.UnblockUser" = "解除封鎖 %@"; +"Common.Controls.Friendship.Unmute" = "取消靜音"; +"Common.Controls.Friendship.UnmuteUser" = "取消靜音 %@"; +"Common.Controls.Keyboard.Common.ComposeNewPost" = "撰寫新嘟文"; +"Common.Controls.Keyboard.Common.OpenSettings" = "開啟設定"; +"Common.Controls.Keyboard.Common.ShowFavorites" = "顯示最愛"; +"Common.Controls.Keyboard.Common.SwitchToTab" = "切換至 %@"; +"Common.Controls.Keyboard.SegmentedControl.NextSection" = "下一個區塊"; +"Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "上一個區塊"; +"Common.Controls.Keyboard.Timeline.NextStatus" = "下一則嘟文"; +"Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "開啟作者的個人檔案頁面"; +"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "開啟轉嘟者的個人檔案頁面"; +"Common.Controls.Keyboard.Timeline.OpenStatus" = "開啟嘟文"; +"Common.Controls.Keyboard.Timeline.PreviewImage" = "預覽圖片"; +"Common.Controls.Keyboard.Timeline.PreviousStatus" = "先前的嘟文"; +"Common.Controls.Keyboard.Timeline.ReplyStatus" = "回覆嘟文"; +"Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "切換內容警告"; +"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "切換最愛嘟文"; +"Common.Controls.Keyboard.Timeline.ToggleReblog" = "切換轉發嘟文"; +"Common.Controls.Status.Actions.Favorite" = "最愛"; +"Common.Controls.Status.Actions.Hide" = "隱藏"; +"Common.Controls.Status.Actions.Menu" = "選單"; +"Common.Controls.Status.Actions.Reblog" = "轉嘟"; +"Common.Controls.Status.Actions.Reply" = "回覆"; +"Common.Controls.Status.Actions.ShowGif" = "顯示 GIF"; +"Common.Controls.Status.Actions.ShowImage" = "顯示圖片"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "顯示影片播放器"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "輕觸然後按住以顯示選單"; +"Common.Controls.Status.Actions.Unfavorite" = "取消最愛"; +"Common.Controls.Status.Actions.Unreblog" = "取消轉嘟"; +"Common.Controls.Status.ContentWarning" = "內容警告"; +"Common.Controls.Status.MediaContentWarning" = "輕觸任何地方以顯示"; +"Common.Controls.Status.Poll.Closed" = "已關閉"; +"Common.Controls.Status.Poll.Vote" = "投票"; +"Common.Controls.Status.SensitiveContent" = "敏感內容"; +"Common.Controls.Status.ShowPost" = "顯示嘟文"; +"Common.Controls.Status.ShowUserProfile" = "顯示使用者個人檔案頁面"; +"Common.Controls.Status.Tag.Email" = "電子郵件"; +"Common.Controls.Status.Tag.Emoji" = "emoji"; +"Common.Controls.Status.Tag.Hashtag" = "主題標籤"; +"Common.Controls.Status.Tag.Link" = "連結"; +"Common.Controls.Status.Tag.Mention" = "提及"; +"Common.Controls.Status.Tag.Url" = "網址"; +"Common.Controls.Status.TapToReveal" = "輕觸以顯示"; +"Common.Controls.Status.UserReblogged" = "%@ 已轉嘟"; +"Common.Controls.Status.UserRepliedTo" = "回覆給 %@"; +"Common.Controls.Status.Visibility.Direct" = "只有被提及的使用者能看到此嘟文。"; +"Common.Controls.Status.Visibility.Private" = "只有他們的跟隨者能看到此嘟文。"; +"Common.Controls.Status.Visibility.PrivateFromMe" = "只有我的跟隨者能看到此嘟文。"; +"Common.Controls.Status.Visibility.Unlisted" = "任何人都能看到此嘟文,但是不會顯示於公開時間軸上。"; +"Common.Controls.Tabs.Home" = "首頁"; +"Common.Controls.Tabs.Notification" = "通知"; +"Common.Controls.Tabs.Profile" = "個人檔案"; +"Common.Controls.Tabs.Search" = "搜尋"; +"Common.Controls.Timeline.Filtered" = "已過濾"; +"Common.Controls.Timeline.Header.BlockedWarning" = "您無法瀏覽該使用者的個人檔案,除非他們取消封鎖您。"; +"Common.Controls.Timeline.Header.BlockingWarning" = "您無法瀏覽該使用者的個人檔案,除非您取消封鎖。 +您的個人檔案看起來像是這樣。"; +"Common.Controls.Timeline.Header.NoStatusFound" = "沒有任何嘟文"; +"Common.Controls.Timeline.Header.SuspendedWarning" = "此使用者已被停權。"; +"Common.Controls.Timeline.Header.UserBlockedWarning" = "您無法瀏覽 %@ 的個人檔案,除非他們取消封鎖您。"; +"Common.Controls.Timeline.Header.UserBlockingWarning" = "您無法瀏覽 %@ 的個人檔案,除非您取消封鎖。 +您的個人檔案看起來像是這樣。"; +"Common.Controls.Timeline.Header.UserSuspendedWarning" = "%@ 的帳號已經被停權。"; +"Common.Controls.Timeline.Loader.LoadMissingPosts" = "讀取錯過的嘟文"; +"Common.Controls.Timeline.Loader.LoadingMissingPosts" = "正在讀取錯過的嘟文..."; +"Common.Controls.Timeline.Loader.ShowMoreReplies" = "顯示更多回覆"; +"Common.Controls.Timeline.Timestamp.Now" = "剛剛"; +"Scene.AccountList.AddAccount" = "新增帳號"; +"Scene.AccountList.DismissAccountSwitcher" = "關閉帳號切換器"; +"Scene.AccountList.TabBarHint" = "目前已選擇的個人檔案:%@。點兩下然後按住以顯示帳號切換器"; +"Scene.Compose.Accessibility.AppendAttachment" = "新增附件"; +"Scene.Compose.Accessibility.AppendPoll" = "新增投票"; +"Scene.Compose.Accessibility.CustomEmojiPicker" = "自訂 emoji 選擇器"; +"Scene.Compose.Accessibility.DisableContentWarning" = "停用內容警告"; +"Scene.Compose.Accessibility.EnableContentWarning" = "啟用內容警告"; +"Scene.Compose.Accessibility.PostVisibilityMenu" = "嘟文可見性選單"; +"Scene.Compose.Accessibility.RemovePoll" = "移除投票"; +"Scene.Compose.Attachment.AttachmentBroken" = "此 %@ 已損毀,並無法被上傳至 Mastodon。"; +"Scene.Compose.Attachment.DescriptionPhoto" = "為視障人士提供圖片說明..."; +"Scene.Compose.Attachment.DescriptionVideo" = "為視障人士提供影片說明..."; +"Scene.Compose.Attachment.Photo" = "照片"; +"Scene.Compose.Attachment.Video" = "影片"; +"Scene.Compose.AutoComplete.SpaceToAdd" = "添加的空白"; +"Scene.Compose.ComposeAction" = "嘟出去"; +"Scene.Compose.ContentInputPlaceholder" = "正在想些什麼嗎?"; +"Scene.Compose.ContentWarning.Placeholder" = "請於此處寫下精準的警告..."; +"Scene.Compose.Keyboard.AppendAttachmentEntry" = "新增附件 - %@"; +"Scene.Compose.Keyboard.DiscardPost" = "捨棄嘟文"; +"Scene.Compose.Keyboard.PublishPost" = "發表嘟文"; +"Scene.Compose.Keyboard.SelectVisibilityEntry" = "選擇可見性 - %@"; +"Scene.Compose.Keyboard.ToggleContentWarning" = "切換內容警告"; +"Scene.Compose.Keyboard.TogglePoll" = "切換投票"; +"Scene.Compose.MediaSelection.Browse" = "瀏覽"; +"Scene.Compose.MediaSelection.Camera" = "拍攝照片"; +"Scene.Compose.MediaSelection.PhotoLibrary" = "圖片庫"; +"Scene.Compose.Poll.DurationTime" = "持續時間:%@"; +"Scene.Compose.Poll.OneDay" = "一天"; +"Scene.Compose.Poll.OneHour" = "一小時"; +"Scene.Compose.Poll.OptionNumber" = "選項 %ld"; +"Scene.Compose.Poll.SevenDays" = "七天"; +"Scene.Compose.Poll.SixHours" = "六小時"; +"Scene.Compose.Poll.ThirtyMinutes" = "30 分鐘"; +"Scene.Compose.Poll.ThreeDays" = "三天"; +"Scene.Compose.ReplyingToUser" = "正在回覆 %@"; +"Scene.Compose.Title.NewPost" = "新增嘟文"; +"Scene.Compose.Title.NewReply" = "新增回覆"; +"Scene.Compose.Visibility.Direct" = "僅限於我提及的人"; +"Scene.Compose.Visibility.Private" = "僅限跟隨者"; +"Scene.Compose.Visibility.Public" = "公開"; +"Scene.Compose.Visibility.Unlisted" = "不公開"; +"Scene.ConfirmEmail.Button.OpenEmailApp" = "開啟電子郵件 App"; +"Scene.ConfirmEmail.Button.Resend" = "重新發送"; +"Scene.ConfirmEmail.DontReceiveEmail.Description" = "請檢查您的電子郵件地址是否正確,以及您的垃圾信件夾。"; +"Scene.ConfirmEmail.DontReceiveEmail.ResendEmail" = "重新發送電子郵件"; +"Scene.ConfirmEmail.DontReceiveEmail.Title" = "請檢查您的電子郵件"; +"Scene.ConfirmEmail.OpenEmailApp.Description" = "我們剛寄送您一封電子郵件。請檢查您的垃圾信件夾。"; +"Scene.ConfirmEmail.OpenEmailApp.Mail" = "電子郵件"; +"Scene.ConfirmEmail.OpenEmailApp.OpenEmailClient" = "請開啟電子郵件程式"; +"Scene.ConfirmEmail.OpenEmailApp.Title" = "請檢查您的收件夾。"; +"Scene.ConfirmEmail.Subtitle" = "點擊我們寄送給您的帳號驗證連結。"; +"Scene.ConfirmEmail.Title" = "最後一步。"; +"Scene.Discovery.Intro" = "這些嘟文正在您 Mastodon 的角落受到注目。"; +"Scene.Discovery.Tabs.Community" = "社群"; +"Scene.Discovery.Tabs.ForYou" = "為您推薦"; +"Scene.Discovery.Tabs.Hashtags" = "主題標籤"; +"Scene.Discovery.Tabs.News" = "最新消息"; +"Scene.Discovery.Tabs.Posts" = "嘟文"; +"Scene.Favorite.Title" = "您的最愛"; +"Scene.Follower.Footer" = "來自其他伺服器的跟隨者不會被顯示。"; +"Scene.Following.Footer" = "來自其他伺服器的跟隨中不會被顯示。"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; +"Scene.HomeTimeline.NavigationBarState.NewPosts" = "檢視最新嘟文"; +"Scene.HomeTimeline.NavigationBarState.Offline" = "離線"; +"Scene.HomeTimeline.NavigationBarState.Published" = "嘟出去!"; +"Scene.HomeTimeline.NavigationBarState.Publishing" = "發表嘟文..."; +"Scene.HomeTimeline.Title" = "首頁"; +"Scene.Notification.Keyobard.ShowEverything" = "顯示全部"; +"Scene.Notification.Keyobard.ShowMentions" = "顯示提及"; +"Scene.Notification.NotificationDescription.FavoritedYourPost" = "最愛了您的嘟文"; +"Scene.Notification.NotificationDescription.FollowedYou" = "跟隨了您"; +"Scene.Notification.NotificationDescription.MentionedYou" = "提到了您"; +"Scene.Notification.NotificationDescription.PollHasEnded" = "投票已結束"; +"Scene.Notification.NotificationDescription.RebloggedYourPost" = "轉嘟了您的嘟文"; +"Scene.Notification.NotificationDescription.RequestToFollowYou" = "要求跟隨您"; +"Scene.Notification.Title.Everything" = "全部"; +"Scene.Notification.Title.Mentions" = "提及"; +"Scene.Preview.Keyboard.ClosePreview" = "關閉預覽"; +"Scene.Preview.Keyboard.ShowNext" = "顯示下一筆"; +"Scene.Preview.Keyboard.ShowPrevious" = "顯示上一筆"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "點兩下以開啟列表"; +"Scene.Profile.Accessibility.EditAvatarImage" = "編輯大頭貼"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "顯示大頭貼"; +"Scene.Profile.Accessibility.ShowBannerImage" = "顯示橫幅圖片"; +"Scene.Profile.Dashboard.Followers" = "跟隨者"; +"Scene.Profile.Dashboard.Following" = "跟隨中"; +"Scene.Profile.Dashboard.Posts" = "嘟文"; +"Scene.Profile.Fields.AddRow" = "新增列"; +"Scene.Profile.Fields.Placeholder.Content" = "內容"; +"Scene.Profile.Fields.Placeholder.Label" = "標籤"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "確認將 %@ 封鎖"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "封鎖"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "確認將 %@ 靜音"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "靜音"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "確認將 %@ 取消封鎖"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "取消封鎖"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "確認將 %@ 取消靜音"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Title" = "取消靜音"; +"Scene.Profile.SegmentedControl.About" = "關於"; +"Scene.Profile.SegmentedControl.Media" = "媒體"; +"Scene.Profile.SegmentedControl.Posts" = "嘟文"; +"Scene.Profile.SegmentedControl.PostsAndReplies" = "嘟文及回覆"; +"Scene.Profile.SegmentedControl.Replies" = "回覆"; +"Scene.Register.Error.Item.Agreement" = "條款"; +"Scene.Register.Error.Item.Email" = "電子郵件"; +"Scene.Register.Error.Item.Locale" = "地區"; +"Scene.Register.Error.Item.Password" = "密碼"; +"Scene.Register.Error.Item.Reason" = "原因"; +"Scene.Register.Error.Item.Username" = "使用者名稱"; +"Scene.Register.Error.Reason.Accepted" = "請先閱讀並同意 %@"; +"Scene.Register.Error.Reason.Blank" = "%@ 為必填選項"; +"Scene.Register.Error.Reason.Blocked" = "%@ 包含不被允許的電子郵件供應商"; +"Scene.Register.Error.Reason.Inclusion" = "%@ 是不被支援的值"; +"Scene.Register.Error.Reason.Invalid" = "%@ 是無效的"; +"Scene.Register.Error.Reason.Reserved" = "%@ 是一個保留的關鍵字"; +"Scene.Register.Error.Reason.Taken" = "%@ 已被使用"; +"Scene.Register.Error.Reason.TooLong" = "%@ 太長了"; +"Scene.Register.Error.Reason.TooShort" = "%@ 太短了"; +"Scene.Register.Error.Reason.Unreachable" = "%@ 似乎並不存在"; +"Scene.Register.Error.Special.EmailInvalid" = "這不是一個有效的電子郵件地址"; +"Scene.Register.Error.Special.PasswordTooShort" = "密碼太短了(至少需要八個字元)"; +"Scene.Register.Error.Special.UsernameInvalid" = "使用者名稱僅能包含英文字母數字以及底線"; +"Scene.Register.Error.Special.UsernameTooLong" = "使用者名稱太長了(不能超過 30 個字元)"; +"Scene.Register.Input.Avatar.Delete" = "刪除"; +"Scene.Register.Input.DisplayName.Placeholder" = "顯示名稱"; +"Scene.Register.Input.Email.Placeholder" = "電子郵件"; +"Scene.Register.Input.Invite.RegistrationUserInviteRequest" = "為什麼想要加入呢?"; +"Scene.Register.Input.Password.Accessibility.Checked" = "已選擇"; +"Scene.Register.Input.Password.Accessibility.Unchecked" = "取消勾選"; +"Scene.Register.Input.Password.CharacterLimit" = "8 個字元"; +"Scene.Register.Input.Password.Hint" = "您的密碼必須至少為八個字元"; +"Scene.Register.Input.Password.Placeholder" = "密碼"; +"Scene.Register.Input.Password.Require" = "您的密碼必須是至少:"; +"Scene.Register.Input.Username.DuplicatePrompt" = "此使用者名稱已被佔用。"; +"Scene.Register.Input.Username.Placeholder" = "使用者名稱"; +"Scene.Register.Title" = "讓我們一起設定 %@ 吧!"; +"Scene.Report.Content1" = "還有其他您想加入這份檢舉報告的嘟文嗎?"; +"Scene.Report.Content2" = "有其他管管們需要知道關於這份檢舉報告的嗎?"; +"Scene.Report.ReportSentTitle" = "感謝您的檢舉,我們將著手處理。"; +"Scene.Report.Reported" = "已檢舉"; +"Scene.Report.Send" = "傳送檢舉報告"; +"Scene.Report.SkipToSend" = "不加入備註並傳送"; +"Scene.Report.Step1" = "兩個步驟中的第一步"; +"Scene.Report.Step2" = "兩個步驟中的第二步"; +"Scene.Report.StepFinal.BlockUser" = "Block %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Don’t want to see this?"; +"Scene.Report.StepFinal.MuteUser" = "Mute %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; +"Scene.Report.StepFinal.Unfollow" = "Unfollow"; +"Scene.Report.StepFinal.UnfollowUser" = "Unfollow %@"; +"Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Is there anything else we should know?"; +"Scene.Report.StepFour.Step4Of4" = "Step 4 of 4"; +"Scene.Report.StepOne.IDontLikeIt" = "I don’t like it"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "It is not something you want to see"; +"Scene.Report.StepOne.ItViolatesServerRules" = "It violates server rules"; +"Scene.Report.StepOne.ItsSomethingElse" = "It’s something else"; +"Scene.Report.StepOne.ItsSpam" = "It’s spam"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Select the best match"; +"Scene.Report.StepOne.Step1Of4" = "Step 1 of 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "The issue does not fit into other categories"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "What's wrong with this account?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "What's wrong with this post?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "What's wrong with %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Are there any posts that back up this report?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepThree.Step3Of4" = "Step 3 of 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "I just don’t like it"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepTwo.Step2Of4" = "Step 2 of 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Which rules are being violated?"; +"Scene.Report.TextPlaceholder" = "請輸入或貼上額外的備註"; +"Scene.Report.Title" = "檢舉 %@"; +"Scene.Report.TitleReport" = "檢舉"; +"Scene.Search.Recommend.Accounts.Description" = "您也許想跟隨這些帳號"; +"Scene.Search.Recommend.Accounts.Follow" = "跟隨"; +"Scene.Search.Recommend.Accounts.Title" = "您可能會喜歡的帳號"; +"Scene.Search.Recommend.ButtonText" = "檢視全部"; +"Scene.Search.Recommend.HashTag.Description" = "發燒中的主題標籤"; +"Scene.Search.Recommend.HashTag.PeopleTalking" = "%@ 人正在討論"; +"Scene.Search.Recommend.HashTag.Title" = "Mastodon 上的熱門討論"; +"Scene.Search.SearchBar.Cancel" = "取消"; +"Scene.Search.SearchBar.Placeholder" = "搜尋主題標籤及使用者"; +"Scene.Search.Searching.Clear" = "清除"; +"Scene.Search.Searching.EmptyState.NoResults" = "沒有任何結果"; +"Scene.Search.Searching.RecentSearch" = "最近的搜尋"; +"Scene.Search.Searching.Segment.All" = "全部"; +"Scene.Search.Searching.Segment.Hashtags" = "主題標籤"; +"Scene.Search.Searching.Segment.People" = "使用者"; +"Scene.Search.Searching.Segment.Posts" = "嘟文"; +"Scene.Search.Title" = "搜尋"; +"Scene.ServerPicker.Button.Category.Academia" = "學術"; +"Scene.ServerPicker.Button.Category.Activism" = "社會運動"; +"Scene.ServerPicker.Button.Category.All" = "全部"; +"Scene.ServerPicker.Button.Category.AllAccessiblityDescription" = "分類:全部"; +"Scene.ServerPicker.Button.Category.Art" = "藝術"; +"Scene.ServerPicker.Button.Category.Food" = "食物"; +"Scene.ServerPicker.Button.Category.Furry" = "毛茸茸"; +"Scene.ServerPicker.Button.Category.Games" = "遊戲"; +"Scene.ServerPicker.Button.Category.General" = "一般"; +"Scene.ServerPicker.Button.Category.Journalism" = "新聞記者"; +"Scene.ServerPicker.Button.Category.Lgbt" = "LGBT"; +"Scene.ServerPicker.Button.Category.Music" = "音樂"; +"Scene.ServerPicker.Button.Category.Regional" = "區域性"; +"Scene.ServerPicker.Button.Category.Tech" = "科技"; +"Scene.ServerPicker.Button.SeeLess" = "顯示較少"; +"Scene.ServerPicker.Button.SeeMore" = "檢視更多"; +"Scene.ServerPicker.EmptyState.BadNetwork" = "讀取資料時發生錯誤。請檢查您的網路連線。"; +"Scene.ServerPicker.EmptyState.FindingServers" = "尋找可用的伺服器..."; +"Scene.ServerPicker.EmptyState.NoResults" = "沒有結果"; +"Scene.ServerPicker.Input.Placeholder" = "搜尋伺服器"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; +"Scene.ServerPicker.Label.Category" = "分類"; +"Scene.ServerPicker.Label.Language" = "語言"; +"Scene.ServerPicker.Label.Users" = "使用者"; +"Scene.ServerPicker.Subtitle" = "基於您的興趣、地區、或一般用途選定一個伺服器。"; +"Scene.ServerPicker.SubtitleExtend" = "基於您的興趣、地區、或一般用途選定一個伺服器。每個伺服器是由完全獨立的組織或個人營運。"; +"Scene.ServerPicker.Title" = "Mastodon 由不同伺服器的使用者組成。"; +"Scene.ServerRules.Button.Confirm" = "我已閱讀並同意"; +"Scene.ServerRules.PrivacyPolicy" = "隱私權政策"; +"Scene.ServerRules.Prompt" = "繼續的話,代表您將遵守 %@ 的服務條款和隱私權政策。"; +"Scene.ServerRules.Subtitle" = "這些被 %@ 的管管們制定以及實施。"; +"Scene.ServerRules.TermsOfService" = "服務條款"; +"Scene.ServerRules.Title" = "一些基本守則。"; +"Scene.Settings.Footer.MastodonDescription" = "Mastodon 是開源軟體。您可以在 GitHub %@ (%@) 上回報問題。"; +"Scene.Settings.Keyboard.CloseSettingsWindow" = "關閉設定視窗"; +"Scene.Settings.Section.Appearance.Automatic" = "自動"; +"Scene.Settings.Section.Appearance.Dark" = "深色佈景主題"; +"Scene.Settings.Section.Appearance.Light" = "亮色佈景主題"; +"Scene.Settings.Section.Appearance.Title" = "外觀設定"; +"Scene.Settings.Section.BoringZone.AccountSettings" = "帳號設定"; +"Scene.Settings.Section.BoringZone.Privacy" = "隱私權政策"; +"Scene.Settings.Section.BoringZone.Terms" = "服務條款"; +"Scene.Settings.Section.BoringZone.Title" = "無聊的這些"; +"Scene.Settings.Section.LookAndFeel.Light" = "淺色"; +"Scene.Settings.Section.LookAndFeel.ReallyDark" = "闇黑風格"; +"Scene.Settings.Section.LookAndFeel.SortaDark" = "有點黑又不會太黑"; +"Scene.Settings.Section.LookAndFeel.Title" = "外觀與風格"; +"Scene.Settings.Section.LookAndFeel.UseSystem" = "與系統一致"; +"Scene.Settings.Section.Notifications.Boosts" = "將我的嘟文轉嘟"; +"Scene.Settings.Section.Notifications.Favorites" = "將我的嘟文加到最愛"; +"Scene.Settings.Section.Notifications.Follows" = "跟隨著我"; +"Scene.Settings.Section.Notifications.Mentions" = "提到了我"; +"Scene.Settings.Section.Notifications.Title" = "通知"; +"Scene.Settings.Section.Notifications.Trigger.Anyone" = "任何人"; +"Scene.Settings.Section.Notifications.Trigger.Follow" = "任何我跟隨的人"; +"Scene.Settings.Section.Notifications.Trigger.Follower" = "跟隨者"; +"Scene.Settings.Section.Notifications.Trigger.Noone" = "沒有人"; +"Scene.Settings.Section.Notifications.Trigger.Title" = "以下狀況請通知我"; +"Scene.Settings.Section.Preference.DisableAvatarAnimation" = "停用動畫大頭貼"; +"Scene.Settings.Section.Preference.DisableEmojiAnimation" = "停用動畫 emoji"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "在 Mastodon 中開啟連結"; +"Scene.Settings.Section.Preference.Title" = "偏好設定"; +"Scene.Settings.Section.Preference.TrueBlackDarkMode" = "真☆闇黑模式"; +"Scene.Settings.Section.Preference.UsingDefaultBrowser" = "使用預設瀏覽器開啟連結"; +"Scene.Settings.Section.SpicyZone.Clear" = "清除媒體快取"; +"Scene.Settings.Section.SpicyZone.Signout" = "登出"; +"Scene.Settings.Section.SpicyZone.Title" = "危險地帶"; +"Scene.Settings.Title" = "設定"; +"Scene.SuggestionAccount.FollowExplain" = "當您跟隨這些人時,將會看到他們的嘟文出現在您的首頁時間軸上。"; +"Scene.SuggestionAccount.Title" = "尋找一些人來跟隨"; +"Scene.Thread.BackTitle" = "嘟文"; +"Scene.Thread.Title" = "來自 %@ 的嘟文"; +"Scene.Welcome.GetStarted" = "新手上路"; +"Scene.Welcome.LogIn" = "登入"; +"Scene.Welcome.Slogan" = "社群網路 +還權於您。"; +"Scene.Wizard.AccessibilityHint" = "點兩下以關閉此設定精靈"; +"Scene.Wizard.MultipleAccountSwitchIntroDescription" = "按住個人檔案按鈕以於多個帳號間切換。"; +"Scene.Wizard.NewInMastodon" = "Mastodon 新功能"; \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hant.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hant.lproj/Localizable.stringsdict new file mode 100644 index 000000000..0f28a8f6e --- /dev/null +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hant.lproj/Localizable.stringsdict @@ -0,0 +1,356 @@ + + + + + a11y.plural.count.unread.notification + + NSStringLocalizedFormatKey + %#@notification_count_unread_notification@ + notification_count_unread_notification + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 則未讀通知 + + + a11y.plural.count.input_limit_exceeds + + NSStringLocalizedFormatKey + 輸入字數限制超過 %#@character_count@ 字 + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 個字元 + + + a11y.plural.count.input_limit_remains + + NSStringLocalizedFormatKey + 輸入字數限制剩餘 %#@character_count@ 字 + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 個字 + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@ %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + 嘟文 + + + plural.count.post + + NSStringLocalizedFormatKey + %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 則嘟文 + + + plural.count.favorite + + NSStringLocalizedFormatKey + %#@favorite_count@ + favorite_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 個最愛 + + + plural.count.reblog + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 個轉嘟 + + + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 個回覆 + + + plural.count.vote + + NSStringLocalizedFormatKey + %#@vote_count@ + vote_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 票 + + + plural.count.voter + + NSStringLocalizedFormatKey + %#@voter_count@ + voter_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 位投票者 + + + plural.people_talking + + NSStringLocalizedFormatKey + %#@count_people_talking@ + count_people_talking + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 個人正在討論 + + + plural.count.following + + NSStringLocalizedFormatKey + %#@count_following@ + count_following + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 個跟隨中 + + + plural.count.follower + + NSStringLocalizedFormatKey + %#@count_follower@ + count_follower + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 個跟隨者 + + + date.year.left + + NSStringLocalizedFormatKey + %#@count_year_left@ + count_year_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + 還有 %ld 年 + + + date.month.left + + NSStringLocalizedFormatKey + %#@count_month_left@ + count_month_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + 還有 %ld 個月 + + + date.day.left + + NSStringLocalizedFormatKey + %#@count_day_left@ + count_day_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + 還有 %ld 天 + + + date.hour.left + + NSStringLocalizedFormatKey + %#@count_hour_left@ + count_hour_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + 還有 %ld 小時 + + + date.minute.left + + NSStringLocalizedFormatKey + %#@count_minute_left@ + count_minute_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + 還有 %ld 分鐘 + + + date.second.left + + NSStringLocalizedFormatKey + %#@count_second_left@ + count_second_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + 還有 %ld 秒 + + + date.year.ago.abbr + + NSStringLocalizedFormatKey + %#@count_year_ago_abbr@ + count_year_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 年前 + + + date.month.ago.abbr + + NSStringLocalizedFormatKey + %#@count_month_ago_abbr@ + count_month_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 個月前 + + + date.day.ago.abbr + + NSStringLocalizedFormatKey + %#@count_day_ago_abbr@ + count_day_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 天前 + + + date.hour.ago.abbr + + NSStringLocalizedFormatKey + %#@count_hour_ago_abbr@ + count_hour_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 小時前 + + + date.minute.ago.abbr + + NSStringLocalizedFormatKey + %#@count_minute_ago_abbr@ + count_minute_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 分鐘前 + + + date.second.ago.abbr + + NSStringLocalizedFormatKey + %#@count_second_ago_abbr@ + count_second_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 秒前 + + + + diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Suggestions.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Suggestions.swift index 558089645..4c424f3be 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Suggestions.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Suggestions.swift @@ -27,7 +27,7 @@ extension Mastodon.API.Suggestions { /// - query: query /// - authorization: User token. /// - Returns: `AnyPublisher` contains `Accounts` nested in the response - public static func get( + public static func accounts( session: URLSession, domain: String, query: Mastodon.API.Suggestions.Query?, diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Timeline.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Timeline.swift index c1857ae82..a569f7a66 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Timeline.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Timeline.swift @@ -33,16 +33,18 @@ extension Mastodon.API.Timeline { /// - session: `URLSession` /// - domain: Mastodon instance domain. e.g. "example.com" /// - query: `PublicTimelineQuery` with query parameters + /// - authorization: required if the instance has disabled public preview /// - Returns: `AnyPublisher` contains `Token` nested in the response public static func `public`( session: URLSession, domain: String, - query: PublicTimelineQuery + query: PublicTimelineQuery, + authorization: Mastodon.API.OAuth.Authorization? ) -> AnyPublisher, Error> { let request = Mastodon.API.get( url: publicTimelineEndpointURL(domain: domain), query: query, - authorization: nil + authorization: authorization ) return session.dataTaskPublisher(for: request) .tryMap { data, response in diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Trends.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Trends.swift index 385e3d756..d2dca8245 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Trends.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Trends.swift @@ -9,6 +9,7 @@ import Combine import Foundation extension Mastodon.API.Trends { + static func trendsURL(domain: String) -> URL { Mastodon.API.endpointURL(domain: domain).appendingPathComponent("trends") } @@ -27,10 +28,10 @@ extension Mastodon.API.Trends { /// - query: query /// - Returns: `AnyPublisher` contains `Hashtags` nested in the response - public static func get( + public static func hashtags( session: URLSession, domain: String, - query: Mastodon.API.Trends.Query? + query: Mastodon.API.Trends.HashtagQuery? ) -> AnyPublisher, Error> { let request = Mastodon.API.get( url: trendsURL(domain: domain), @@ -44,10 +45,8 @@ extension Mastodon.API.Trends { } .eraseToAnyPublisher() } -} -extension Mastodon.API.Trends { - public struct Query: Codable, GetQuery { + public struct HashtagQuery: Codable, GetQuery { public init(limit: Int?) { self.limit = limit } @@ -61,4 +60,113 @@ extension Mastodon.API.Trends { return items } } + +} + +extension Mastodon.API.Trends { + + static func trendStatusesURL(domain: String) -> URL { + Mastodon.API.endpointURL(domain: domain) + .appendingPathComponent("trends") + .appendingPathComponent("statuses") + } + + /// Trending status + /// + /// TBD + /// + /// Version history: + /// 3.?.? + /// # Reference + /// [Document](https://docs.joinmastodon.org/methods/instance/trends/) + /// - Parameters: + /// - session: `URLSession` + /// - domain: Mastodon instance domain. e.g. "example.com" + /// - query: query + /// - Returns: `[Status]` nested in the response + + public static func statuses( + session: URLSession, + domain: String, + query: Mastodon.API.Trends.StatusQuery? + ) -> AnyPublisher, Error> { + let request = Mastodon.API.get( + url: trendStatusesURL(domain: domain), + query: query, + authorization: nil + ) + return session.dataTaskPublisher(for: request) + .tryMap { data, response in + let value = try Mastodon.API.decode(type: [Mastodon.Entity.Status].self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + .eraseToAnyPublisher() + } + + public struct StatusQuery: Codable, GetQuery { + + public let offset: Int? + public let limit: Int? // Maximum number of results to return. Defaults to 10. + + public init( + offset: Int?, + limit: Int? + ) { + self.offset = offset + self.limit = limit + } + + var queryItems: [URLQueryItem]? { + var items: [URLQueryItem] = [] + offset.flatMap { items.append(URLQueryItem(name: "offset", value: String($0))) } + limit.flatMap { items.append(URLQueryItem(name: "limit", value: String($0))) } + guard !items.isEmpty else { return nil } + return items + } + } + +} + +extension Mastodon.API.Trends { + + static func trendLinksURL(domain: String) -> URL { + Mastodon.API.endpointURL(domain: domain) + .appendingPathComponent("trends") + .appendingPathComponent("links") + } + + /// Trending links + /// + /// TBD + /// + /// Version history: + /// 3.?.? + /// # Reference + /// [Document](https://docs.joinmastodon.org/methods/instance/trends/) + /// - Parameters: + /// - session: `URLSession` + /// - domain: Mastodon instance domain. e.g. "example.com" + /// - query: query + /// - Returns: `[Link]` nested in the response + + public static func links( + session: URLSession, + domain: String, + query: Mastodon.API.Trends.LinkQuery? + ) -> AnyPublisher, Error> { + let request = Mastodon.API.get( + url: trendLinksURL(domain: domain), + query: query, + authorization: nil + ) + return session.dataTaskPublisher(for: request) + .tryMap { data, response in + let value = try Mastodon.API.decode(type: [Mastodon.Entity.Link].self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + .eraseToAnyPublisher() + } + + public typealias LinkQuery = StatusQuery + } diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+V2+Suggestions.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+V2+Suggestions.swift index 9e6876b41..ed680dbac 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+V2+Suggestions.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+V2+Suggestions.swift @@ -20,7 +20,7 @@ extension Mastodon.API.V2.Suggestions { /// - query: query /// - authorization: User token. /// - Returns: `AnyPublisher` contains `AccountsSuggestion` nested in the response - public static func get( + public static func accounts( session: URLSession, domain: String, query: Mastodon.API.Suggestions.Query?, diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Instance.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Instance.swift index 7cf4890bc..5d649a841 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Instance.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Instance.swift @@ -38,7 +38,7 @@ extension Mastodon.Entity { // https://github.com/mastodon/mastodon/pull/16485 public let configuration: Configuration? - public init(domain: String) { + public init(domain: String, approvalRequired: Bool? = nil) { self.uri = domain self.title = domain self.description = "" @@ -47,7 +47,7 @@ extension Mastodon.Entity { self.version = nil self.languages = nil self.registrations = nil - self.approvalRequired = nil + self.approvalRequired = approvalRequired self.invitesEnabled = nil self.urls = nil self.statistics = nil diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Link.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Link.swift new file mode 100644 index 000000000..d1d7bd673 --- /dev/null +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Link.swift @@ -0,0 +1,54 @@ +// +// Mastodon+Entity+Link.swift +// +// +// Created by MainasuK on 2022-4-13. +// + +import Foundation + +extension Mastodon.Entity { + /// History + /// + /// - Since: 3.5.0 + /// - Version: 3.5.1 + /// # Last Update + /// 2022/4/13 + /// # Reference + /// [Document](TBD) + public struct Link: Codable { + public let url: String + public let title: String + public let description: String + public let providerName: String + public let providerURL: String + public let image: String + public let width: Int + public let height: Int + public let blurhash: String + public let history: [History] + + enum CodingKeys: String, CodingKey { + case url + case title + case description + case providerName = "provider_name" + case providerURL = "provider_url" + case image + case width + case height + case blurhash + case history + } + } +} + +extension Mastodon.Entity.Link: Hashable { + public static func == (lhs: Mastodon.Entity.Link, rhs: Mastodon.Entity.Link) -> Bool { + return lhs.url == rhs.url + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(url) + } +} diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Tag.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Tag.swift index b017d1551..84875359a 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Tag.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Tag.swift @@ -37,6 +37,7 @@ extension Mastodon.Entity { public func hash(into hasher: inout Hasher) { hasher.combine(name) + hasher.combine(url) } } } diff --git a/MastodonSDK/Sources/MastodonSDK/Response/Mastodon+Response+Content.swift b/MastodonSDK/Sources/MastodonSDK/Response/Mastodon+Response+Content.swift index db42169d8..6cf95752b 100644 --- a/MastodonSDK/Sources/MastodonSDK/Response/Mastodon+Response+Content.swift +++ b/MastodonSDK/Sources/MastodonSDK/Response/Mastodon+Response+Content.swift @@ -106,6 +106,7 @@ extension Mastodon.Response { public struct Link { public let maxID: Mastodon.Entity.Status.ID? public let minID: Mastodon.Entity.Status.ID? + public let offset: Int? init(link: String) { self.maxID = { @@ -125,6 +126,15 @@ extension Mastodon.Response { let id = link[range] return String(id) }() + + self.offset = { + guard let regex = try? NSRegularExpression(pattern: "offset=([[:digit:]]+)", options: []) else { return nil } + let results = regex.matches(in: link, options: [], range: NSRange(link.startIndex.. URL? { + return URL(string: header) + } + + public func headerImageURLWithFallback(domain: String) -> URL { + return URL(string: header) ?? URL(string: "https://\(domain)/headers/original/missing.png")! + } + + public func avatarImageURL() -> URL? { + let string = UserDefaults.shared.preferredStaticAvatar ? avatarStatic ?? avatar : avatar + return URL(string: string) + } + + public func avatarImageURLWithFallback(domain: String) -> URL { + return avatarImageURL() ?? URL(string: "https://\(domain)/avatars/original/missing.png")! + } + +} diff --git a/MastodonSDK/Sources/MastodonUI/Extension/MastodonSDK/Mastodon+Entity+Link.swift b/MastodonSDK/Sources/MastodonUI/Extension/MastodonSDK/Mastodon+Entity+Link.swift new file mode 100644 index 000000000..8f00911f9 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/Extension/MastodonSDK/Mastodon+Entity+Link.swift @@ -0,0 +1,21 @@ +// +// Mastodon+Entity+Link.swift +// +// +// Created by MainasuK on 2022-4-13. +// + +import Foundation +import MastodonSDK + +extension Mastodon.Entity.Link { + + /// the sum of recent 2 days + public var talkingPeopleCount: Int? { + return history + .prefix(2) + .compactMap { Int($0.accounts) } + .reduce(0, +) + } + +} diff --git a/MastodonSDK/Sources/MastodonUI/Extension/MastodonSDK/Mastodon+Entity+Tag.swift b/MastodonSDK/Sources/MastodonUI/Extension/MastodonSDK/Mastodon+Entity+Tag.swift new file mode 100644 index 000000000..4d58145e5 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/Extension/MastodonSDK/Mastodon+Entity+Tag.swift @@ -0,0 +1,21 @@ +// +// Mastodon+Entity+Tag.swift +// +// +// Created by MainasuK on 2022-4-13. +// + +import Foundation +import MastodonSDK + +extension Mastodon.Entity.Tag { + + /// the sum of recent 2 days + public var talkingPeopleCount: Int? { + return history? + .prefix(2) + .compactMap { Int($0.accounts) } + .reduce(0, +) + } + +} diff --git a/MastodonSDK/Sources/MastodonUI/Extension/MetaLabel.swift b/MastodonSDK/Sources/MastodonUI/Extension/MetaLabel.swift index 119e9e031..24a4027f5 100644 --- a/MastodonSDK/Sources/MastodonUI/Extension/MetaLabel.swift +++ b/MastodonSDK/Sources/MastodonUI/Extension/MetaLabel.swift @@ -20,6 +20,8 @@ extension MetaLabel { case notificationTitle case profileFieldName case profileFieldValue + case profileCardName + case profileCardUsername case recommendAccountName case titleView case settingTableFooter @@ -51,7 +53,7 @@ extension MetaLabel { textColor = Asset.Colors.Label.secondary.color case .statusName: - font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .bold)) + font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold)) textColor = Asset.Colors.Label.primary.color case .statusUsername: @@ -80,6 +82,14 @@ extension MetaLabel { font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)) textColor = Asset.Colors.Label.primary.color + case .profileCardName: + font = .systemFont(ofSize: 17, weight: .semibold) + textColor = Asset.Colors.Label.primary.color + + case .profileCardUsername: + font = .systemFont(ofSize: 15, weight: .regular) + textColor = Asset.Colors.Label.secondary.color + case .titleView: font = .systemFont(ofSize: 17, weight: .semibold) textColor = Asset.Colors.Label.primary.color diff --git a/MastodonSDK/Sources/MastodonUI/Extension/UIImage.swift b/MastodonSDK/Sources/MastodonUI/Extension/UIImage.swift new file mode 100644 index 000000000..141b723bc --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/Extension/UIImage.swift @@ -0,0 +1,18 @@ +// +// UIImage.swift +// +// +// Created by MainasuK on 2022-5-6. +// + +import UIKit + +extension UIImage { + + public func resized(size: CGSize) -> UIImage { + return UIGraphicsImageRenderer(size: size).image { context in + self.draw(in: CGRect(origin: .zero, size: size)) + } + } + +} diff --git a/MastodonSDK/Sources/MastodonUI/Extension/UIView.swift b/MastodonSDK/Sources/MastodonUI/Extension/UIView.swift new file mode 100644 index 000000000..0489965b5 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/Extension/UIView.swift @@ -0,0 +1,34 @@ +// +// UIView.swift +// +// +// Created by MainasuK on 2022-4-13. +// + +import UIKit + +extension UIView { + + static let separatorColor: UIColor = { + UIColor(dynamicProvider: { collection in + switch collection.userInterfaceStyle { + case .dark: + return ThemeService.shared.currentTheme.value.separator + default: + return .separator + } + }) + }() + + + public static var separatorLine: UIView { + let line = UIView() + line.backgroundColor = UIView.separatorColor + return line + } + + public static func separatorLineHeight(of view: UIView) -> CGFloat { + return 1.0 / view.traitCollection.displayScale + } + +} diff --git a/Mastodon/Helper/MastodonMetricFormatter.swift b/MastodonSDK/Sources/MastodonUI/Helper/MastodonMetricFormatter.swift similarity index 95% rename from Mastodon/Helper/MastodonMetricFormatter.swift rename to MastodonSDK/Sources/MastodonUI/Helper/MastodonMetricFormatter.swift index 3c9c4dd75..50fca8cc9 100644 --- a/Mastodon/Helper/MastodonMetricFormatter.swift +++ b/MastodonSDK/Sources/MastodonUI/Helper/MastodonMetricFormatter.swift @@ -7,8 +7,8 @@ import Foundation -final public class MastodonMetricFormatter: Formatter { - +public final class MastodonMetricFormatter: Formatter { + public func string(from number: Int) -> String? { let isPositive = number >= 0 let symbol = isPositive ? "" : "-" diff --git a/Mastodon/Service/BlurhashImageCacheService.swift b/MastodonSDK/Sources/MastodonUI/Service/BlurhashImageCacheService.swift similarity index 96% rename from Mastodon/Service/BlurhashImageCacheService.swift rename to MastodonSDK/Sources/MastodonUI/Service/BlurhashImageCacheService.swift index b15a9750b..cc9459b4e 100644 --- a/Mastodon/Service/BlurhashImageCacheService.swift +++ b/MastodonSDK/Sources/MastodonUI/Service/BlurhashImageCacheService.swift @@ -10,6 +10,9 @@ import Combine public final class BlurhashImageCacheService { + // MARK: - Singleton + public static let shared = BlurhashImageCacheService() + static let edgeMaxLength: CGFloat = 20 let cache = NSCache() diff --git a/Mastodon/Service/ThemeService/MastodonTheme.swift b/MastodonSDK/Sources/MastodonUI/Service/ThemeService/MastodonTheme.swift similarity index 98% rename from Mastodon/Service/ThemeService/MastodonTheme.swift rename to MastodonSDK/Sources/MastodonUI/Service/ThemeService/MastodonTheme.swift index 0dad463b6..76173590e 100644 --- a/Mastodon/Service/ThemeService/MastodonTheme.swift +++ b/MastodonSDK/Sources/MastodonUI/Service/ThemeService/MastodonTheme.swift @@ -7,6 +7,7 @@ import UIKit import MastodonAsset +import MastodonCommon struct MastodonTheme: Theme { diff --git a/Mastodon/Service/ThemeService/SystemTheme.swift b/MastodonSDK/Sources/MastodonUI/Service/ThemeService/SystemTheme.swift similarity index 98% rename from Mastodon/Service/ThemeService/SystemTheme.swift rename to MastodonSDK/Sources/MastodonUI/Service/ThemeService/SystemTheme.swift index 7796fde7b..cea10a281 100644 --- a/Mastodon/Service/ThemeService/SystemTheme.swift +++ b/MastodonSDK/Sources/MastodonUI/Service/ThemeService/SystemTheme.swift @@ -7,6 +7,7 @@ import UIKit import MastodonAsset +import MastodonCommon struct SystemTheme: Theme { diff --git a/Mastodon/Service/ThemeService/Theme.swift b/MastodonSDK/Sources/MastodonUI/Service/ThemeService/Theme.swift similarity index 82% rename from Mastodon/Service/ThemeService/Theme.swift rename to MastodonSDK/Sources/MastodonUI/Service/ThemeService/Theme.swift index 1a3b3c5d1..ae555da00 100644 --- a/Mastodon/Service/ThemeService/Theme.swift +++ b/MastodonSDK/Sources/MastodonUI/Service/ThemeService/Theme.swift @@ -6,6 +6,7 @@ // import UIKit +import MastodonCommon public protocol Theme { @@ -42,17 +43,3 @@ public protocol Theme { var notificationStatusBorderColor: UIColor { get } } - -public enum ThemeName: String, CaseIterable { - case system - case mastodon -} - -extension ThemeName { - public var theme: Theme { - switch self { - case .system: return SystemTheme() - case .mastodon: return MastodonTheme() - } - } -} diff --git a/Mastodon/Service/ThemeService/ThemeService.swift b/MastodonSDK/Sources/MastodonUI/Service/ThemeService/ThemeService.swift similarity index 56% rename from Mastodon/Service/ThemeService/ThemeService.swift rename to MastodonSDK/Sources/MastodonUI/Service/ThemeService/ThemeService.swift index b356d3469..394a5f896 100644 --- a/Mastodon/Service/ThemeService/ThemeService.swift +++ b/MastodonSDK/Sources/MastodonUI/Service/ThemeService/ThemeService.swift @@ -7,17 +7,17 @@ import UIKit import Combine -import AppShared +import MastodonCommon // ref: https://zamzam.io/protocol-oriented-themes-for-ios-apps/ -final class ThemeService { +public final class ThemeService { - static let tintColor: UIColor = .label + public static let tintColor: UIColor = .label // MARK: - Singleton public static let shared = ThemeService() - let currentTheme: CurrentValueSubject + public let currentTheme: CurrentValueSubject private init() { let theme = ThemeName(rawValue: UserDefaults.shared.currentThemeNameRawValue)?.theme ?? ThemeName.mastodon.theme @@ -25,3 +25,12 @@ final class ThemeService { } } + +extension ThemeName { + public var theme: Theme { + switch self { + case .system: return SystemTheme() + case .mastodon: return MastodonTheme() + } + } +} diff --git a/Mastodon/Vender/BlurHashDecode.swift b/MastodonSDK/Sources/MastodonUI/Vendor/BlurHashDecode.swift similarity index 100% rename from Mastodon/Vender/BlurHashDecode.swift rename to MastodonSDK/Sources/MastodonUI/Vendor/BlurHashDecode.swift diff --git a/Mastodon/Vender/BlurHashEncode.swift b/MastodonSDK/Sources/MastodonUI/Vendor/BlurHashEncode.swift similarity index 100% rename from Mastodon/Vender/BlurHashEncode.swift rename to MastodonSDK/Sources/MastodonUI/Vendor/BlurHashEncode.swift diff --git a/Mastodon/Vender/CurveAlgorithm.swift b/MastodonSDK/Sources/MastodonUI/Vendor/CurveAlgorithm.swift similarity index 100% rename from Mastodon/Vender/CurveAlgorithm.swift rename to MastodonSDK/Sources/MastodonUI/Vendor/CurveAlgorithm.swift diff --git a/MastodonSDK/Sources/MastodonUI/View/Container/MediaGridContainerView.swift b/MastodonSDK/Sources/MastodonUI/View/Container/MediaGridContainerView.swift index cb9c53f35..e3359fb58 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Container/MediaGridContainerView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Container/MediaGridContainerView.swift @@ -48,22 +48,7 @@ public final class MediaGridContainerView: UIView { return mediaViews }() - -// let sensitiveToggleButtonBlurVisualEffectView: UIVisualEffectView = { -// let visualEffectView = UIVisualEffectView(effect: ContentWarningOverlayView.blurVisualEffect) -// visualEffectView.layer.masksToBounds = true -// visualEffectView.layer.cornerRadius = MediaGridContainerView.sensitiveToggleButtonSize.width / 2 -// visualEffectView.layer.cornerCurve = .continuous -// return visualEffectView -// }() -// let sensitiveToggleButtonVibrancyVisualEffectView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: ContentWarningOverlayView.blurVisualEffect)) -// let sensitiveToggleButton: HitTestExpandedButton = { -// let button = HitTestExpandedButton(type: .system) -// button.contentEdgeInsets = UIEdgeInsets(top: 4, left: 4, bottom: 4, right: 4) -// button.imageView?.contentMode = .scaleAspectFit -// button.setImage(UIImage(systemName: "eye.slash.fill"), for: .normal) -// return button -// }() + let contentWarningOverlay = ContentWarningOverlayView() public override init(frame: CGRect) { super.init(frame: frame) @@ -86,7 +71,8 @@ public final class MediaGridContainerView: UIView { extension MediaGridContainerView { private func _init() { -// sensitiveToggleButton.addTarget(self, action: #selector(MediaGridContainerView.sensitiveToggleButtonDidPressed(_:)), for: .touchUpInside) + contentWarningOverlay.isUserInteractionEnabled = false + contentWarningOverlay.isHidden = true } } @@ -112,8 +98,8 @@ extension MediaGridContainerView { let mediaView = _mediaViews[0] layout.layout(in: self, mediaView: mediaView) -// layoutSensitiveToggleButton() -// bringSubviewToFront(sensitiveToggleButtonBlurVisualEffectView) + layoutContentWarningOverlay() + bringSubviewToFront(contentWarningOverlay) return mediaView } @@ -124,8 +110,8 @@ extension MediaGridContainerView { let mediaViews = Array(_mediaViews[0..() + + let container = UIStackView() + + let providerFaviconImageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFit + imageView.layer.masksToBounds = true + imageView.layer.cornerRadius = 2 + imageView.layer.cornerCurve = .continuous + return imageView + }() + + let providerNameLabel: UILabel = { + let label = UILabel() + label.font = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: .systemFont(ofSize: 13, weight: .semibold)) + label.textColor = Asset.Colors.Label.primary.color + return label + }() + + let headlineLabel: UILabel = { + let label = UILabel() + label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)) + label.textColor = Asset.Colors.Label.primary.color + label.numberOfLines = 0 + return label + }() + + let footnoteLabel: UILabel = { + let label = UILabel() + label.font = UIFontMetrics(forTextStyle: .caption1).scaledFont(for: .systemFont(ofSize: 12, weight: .medium)) + label.textColor = Asset.Colors.Label.secondary.color + return label + }() + + let imageView = MediaView() + +// let imageView = UIImageView() +// var imageViewMediaConfiguration: MediaView.Configuration? + + public func prepareForReuse() { + providerFaviconImageView.tag = (0..() + + public let relationshipViewModel = RelationshipViewModel() + + @Published public var userInterfaceStyle: UIUserInterfaceStyle? + @Published public var backgroundColor: UIColor? + + // Author + @Published public var authorBannerImageURL: URL? + @Published public var authorAvatarImageURL: URL? + @Published public var authorName: MetaContent? + @Published public var authorUsername: String? + + @Published public var bioContent: MetaContent? + + @Published public var statusesCount: Int? + @Published public var followingCount: Int? + @Published public var followersCount: Int? + + @Published public var isUpdating = false + @Published public var isFollowedBy = false + @Published public var isMuting = false + @Published public var isBlocking = false + @Published public var isBlockedBy = false + + @Published public var groupedAccessibilityLabel = "" + + init() { + backgroundColor = ThemeService.shared.currentTheme.value.systemBackgroundColor + Publishers.CombineLatest( + ThemeService.shared.currentTheme, + $userInterfaceStyle + ) + .sink { [weak self] theme, userInterfaceStyle in + guard let self = self else { return } + guard let userInterfaceStyle = userInterfaceStyle else { return } + switch userInterfaceStyle { + case .dark: + self.backgroundColor = theme.systemBackgroundColor + case .light, .unspecified: + self.backgroundColor = Asset.Scene.Discovery.profileCardBackground.color + @unknown default: + self.backgroundColor = Asset.Scene.Discovery.profileCardBackground.color + assertionFailure() + // do nothing + } + } + .store(in: &disposeBag) + } + } +} + +extension ProfileCardView.ViewModel { + func bind(view: ProfileCardView) { + bindAppearacne(view: view) + bindHeader(view: view) + bindUser(view: view) + bindBio(view: view) + bindRelationship(view: view) + bindDashboard(view: view) + bindAccessibility(view: view) + } + + private func bindAppearacne(view: ProfileCardView) { + userInterfaceStyle = view.traitCollection.userInterfaceStyle + + $backgroundColor + .assign(to: \.backgroundColor, on: view.container) + .store(in: &disposeBag) + $backgroundColor + .assign(to: \.backgroundColor, on: view.avatarButtonBackgroundView) + .store(in: &disposeBag) + } + + + private func bindHeader(view: ProfileCardView) { + $authorBannerImageURL + .sink { url in + guard let url = url else { return } + view.bannerImageView.af.setImage( + withURL: url, + placeholderImage: .placeholder(color: .systemGray3), + imageTransition: .crossDissolve(0.3) + ) + } + .store(in: &disposeBag) + } + + private func bindUser(view: ProfileCardView) { + $authorAvatarImageURL + .sink { url in + view.avatarButton.avatarImageView.configure( + configuration: .init( + url: url, + placeholder: .placeholder(color: .systemGray3) + ) + ) + view.avatarButton.avatarImageView.configure( + cornerConfiguration: .init(corner: .fixed(radius: 12)) + ) + } + .store(in: &disposeBag) + + // name + $authorName + .sink { metaContent in + let metaContent = metaContent ?? PlaintextMetaContent(string: " ") + view.authorNameLabel.configure(content: metaContent) + } + .store(in: &disposeBag) + // username + $authorUsername + .map { text -> String in + guard let text = text else { return "" } + return "@\(text)" + } + .sink { username in + let metaContent = PlaintextMetaContent(string: username) + view.authorUsernameLabel.configure(content: metaContent) + } + .store(in: &disposeBag) + } + + private func bindBio(view: ProfileCardView) { + $bioContent + .sink { metaContent in + let metaContent = metaContent ?? PlaintextMetaContent(string: " ") + view.bioMetaText.configure(content: metaContent) + } + .store(in: &disposeBag) + } + + private func bindRelationship(view: ProfileCardView) { + relationshipViewModel.$optionSet + .receive(on: DispatchQueue.main) + .sink { relationshipActionSet in + let relationshipActionSet = relationshipActionSet ?? .follow + view.relationshipActionButton.configure(actionOptionSet: relationshipActionSet) + } + .store(in: &disposeBag) + } + + private func bindDashboard(view: ProfileCardView) { + $statusesCount + .receive(on: DispatchQueue.main) + .sink { count in + let text = count.flatMap { MastodonMetricFormatter().string(from: $0) } ?? "-" + view.statusDashboardView.postDashboardMeterView.numberLabel.text = text + view.statusDashboardView.postDashboardMeterView.isAccessibilityElement = true + view.statusDashboardView.postDashboardMeterView.accessibilityLabel = L10n.Plural.Count.post(count ?? 0) + } + .store(in: &disposeBag) + $followingCount + .receive(on: DispatchQueue.main) + .sink { count in + let text = count.flatMap { MastodonMetricFormatter().string(from: $0) } ?? "-" + view.statusDashboardView.followingDashboardMeterView.numberLabel.text = text + view.statusDashboardView.followingDashboardMeterView.isAccessibilityElement = true + view.statusDashboardView.followingDashboardMeterView.accessibilityLabel = L10n.Plural.Count.following(count ?? 0) + } + .store(in: &disposeBag) + $followersCount + .receive(on: DispatchQueue.main) + .sink { count in + let text = count.flatMap { MastodonMetricFormatter().string(from: $0) } ?? "-" + view.statusDashboardView.followersDashboardMeterView.numberLabel.text = text + view.statusDashboardView.followersDashboardMeterView.isAccessibilityElement = true + view.statusDashboardView.followersDashboardMeterView.accessibilityLabel = L10n.Plural.Count.follower(count ?? 0) + } + .store(in: &disposeBag) + } + + private func bindAccessibility(view: ProfileCardView) { + let authorAccessibilityLabel = Publishers.CombineLatest( + $authorName, + $bioContent + ) + .map { authorName, bioContent -> String? in + var strings: [String?] = [] + strings.append(authorName?.string) + strings.append(bioContent?.string) + return strings.compactMap { $0 }.joined(separator: ", ") + } + + authorAccessibilityLabel + .map { $0 ?? "" } + .assign(to: &$groupedAccessibilityLabel) + + $groupedAccessibilityLabel + .sink { accessibilityLabel in + view.accessibilityLabel = accessibilityLabel + } + .store(in: &disposeBag) + } +} diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView.swift new file mode 100644 index 000000000..07f441500 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView.swift @@ -0,0 +1,285 @@ +// +// ProfileCardView.swift +// +// +// Created by MainasuK on 2022-4-14. +// + +import os.log +import UIKit +import Combine +import MetaTextKit +import MastodonAsset + +public protocol ProfileCardViewDelegate: AnyObject { + func profileCardView(_ profileCardView: ProfileCardView, relationshipButtonDidPressed button: ProfileRelationshipActionButton) +} + +public final class ProfileCardView: UIView { + + static let avatarSize = CGSize(width: 56, height: 56) + static let friendshipActionButtonSize = CGSize(width: 108, height: 34) + static let contentMargin: CGFloat = 16 + + weak var delegate: ProfileCardViewDelegate? + private var _disposeBag = Set() + var disposeBag = Set() + + let container = UIStackView() + + let bannerImageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFill + imageView.layer.masksToBounds = true + imageView.layer.cornerRadius = 3 + imageView.layer.cornerCurve = .continuous + return imageView + }() + + // avatar + public let avatarButtonBackgroundView = UIView() + public let avatarButton = AvatarButton() + + // author name + public let authorNameLabel = MetaLabel(style: .profileCardName) + + // author username + public let authorUsernameLabel = MetaLabel(style: .profileCardUsername) + + // bio + let bioMetaTextAdaptiveMarginContainerView = AdaptiveMarginContainerView() + let bioMetaText: MetaText = { + let metaText = MetaText() + metaText.textView.backgroundColor = .clear + metaText.textView.isEditable = false + metaText.textView.isSelectable = true + metaText.textView.isScrollEnabled = false + //metaText.textView.textContainer.lineFragmentPadding = 0 + //metaText.textView.textContainerInset = .zero + metaText.textView.layer.masksToBounds = false + metaText.textView.textDragInteraction?.isEnabled = false // disable drag for link and attachment + + metaText.textView.layer.masksToBounds = true + metaText.textView.layer.cornerCurve = .continuous + metaText.textView.layer.cornerRadius = 10 + + metaText.paragraphStyle = { + let style = NSMutableParagraphStyle() + style.lineSpacing = 5 + style.paragraphSpacing = 8 + return style + }() + metaText.textAttributes = [ + .font: UIFont.preferredFont(forTextStyle: .body), + .foregroundColor: Asset.Colors.Label.primary.color, + ] + metaText.linkAttributes = [ + .font: UIFont.preferredFont(forTextStyle: .body), + .foregroundColor: Asset.Colors.brandBlue.color, + ] + return metaText + }() + + let infoContainerAdaptiveMarginContainerView = AdaptiveMarginContainerView() + let infoContainer = UIStackView() + + let statusDashboardView = ProfileStatusDashboardView() + + let relationshipActionButtonShadowContainer = ShadowBackgroundContainer() + let relationshipActionButton: ProfileRelationshipActionButton = { + let button = ProfileRelationshipActionButton() + button.titleLabel?.font = .systemFont(ofSize: 17, weight: .semibold) + button.titleLabel?.adjustsFontSizeToFitWidth = true + button.titleLabel?.minimumScaleFactor = 0.5 + return button + }() + + public private(set) lazy var viewModel: ViewModel = { + let viewModel = ViewModel() + viewModel.bind(view: self) + return viewModel + }() + + public func prepareForReuse() { + disposeBag.removeAll() + bannerImageView.af.cancelImageRequest() + bannerImageView.image = nil + } + + override init(frame: CGRect) { + super.init(frame: frame) + _init() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension ProfileCardView { + private func _init() { + avatarButton.isUserInteractionEnabled = false + authorNameLabel.isUserInteractionEnabled = false + authorUsernameLabel.isUserInteractionEnabled = false + bioMetaText.textView.isUserInteractionEnabled = false + statusDashboardView.isUserInteractionEnabled = false + + // container: V - [ bannerContainer | authorContainer | bioMetaText | infoContainer ] + container.axis = .vertical + container.spacing = 8 + container.translatesAutoresizingMaskIntoConstraints = false + addSubview(container) + NSLayoutConstraint.activate([ + container.topAnchor.constraint(equalTo: topAnchor), + container.leadingAnchor.constraint(equalTo: leadingAnchor), + container.trailingAnchor.constraint(equalTo: trailingAnchor), + container.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + + // bannerContainer + let bannerContainer = UIView() + bannerContainer.translatesAutoresizingMaskIntoConstraints = false + container.addArrangedSubview(bannerContainer) + container.setCustomSpacing(6, after: bannerContainer) + + // bannerImageView + bannerImageView.translatesAutoresizingMaskIntoConstraints = false + bannerContainer.addSubview(bannerImageView) + NSLayoutConstraint.activate([ + bannerImageView.topAnchor.constraint(equalTo: bannerContainer.topAnchor, constant: 4), + bannerImageView.leadingAnchor.constraint(equalTo: bannerContainer.leadingAnchor, constant: 4), + bannerContainer.trailingAnchor.constraint(equalTo: bannerImageView.trailingAnchor, constant: 4), + bannerImageView.bottomAnchor.constraint(equalTo: bannerContainer.bottomAnchor), + bannerImageView.widthAnchor.constraint(equalTo: bannerImageView.heightAnchor, multiplier: 335.0/128.0).priority(.required - 1), + ]) + + // authorContainer: H - [ avatarPlaceholder | authorInfoContainer ] + let authorContainer = UIStackView() + authorContainer.axis = .horizontal + authorContainer.spacing = 16 + let authorContainerAdaptiveMarginContainerView = AdaptiveMarginContainerView() + authorContainerAdaptiveMarginContainerView.contentView = authorContainer + authorContainerAdaptiveMarginContainerView.margin = ProfileCardView.contentMargin + container.addArrangedSubview(authorContainerAdaptiveMarginContainerView) + + // avatarPlaceholder + let avatarPlaceholder = UIView() + avatarPlaceholder.translatesAutoresizingMaskIntoConstraints = false + authorContainer.addArrangedSubview(avatarPlaceholder) + NSLayoutConstraint.activate([ + avatarPlaceholder.widthAnchor.constraint(equalToConstant: ProfileCardView.avatarSize.width).priority(.required - 1), + avatarPlaceholder.heightAnchor.constraint(equalToConstant: ProfileCardView.avatarSize.height - 14).priority(.required - 1), + ]) + + avatarButton.translatesAutoresizingMaskIntoConstraints = false + authorContainer.addSubview(avatarButton) + NSLayoutConstraint.activate([ + avatarButton.leadingAnchor.constraint(equalTo: avatarPlaceholder.leadingAnchor), + avatarButton.trailingAnchor.constraint(equalTo: avatarPlaceholder.trailingAnchor), + avatarButton.bottomAnchor.constraint(equalTo: avatarPlaceholder.bottomAnchor), + avatarButton.heightAnchor.constraint(equalToConstant: ProfileCardView.avatarSize.height).priority(.required - 1), + ]) + + avatarButtonBackgroundView.layer.masksToBounds = true + avatarButtonBackgroundView.layer.cornerCurve = .continuous + avatarButtonBackgroundView.layer.cornerRadius = 12 + 1 + avatarButtonBackgroundView.translatesAutoresizingMaskIntoConstraints = false + authorContainer.insertSubview(avatarButtonBackgroundView, belowSubview: avatarButton) + NSLayoutConstraint.activate([ + avatarButtonBackgroundView.centerXAnchor.constraint(equalTo: avatarButton.centerXAnchor), + avatarButtonBackgroundView.centerYAnchor.constraint(equalTo: avatarButton.centerYAnchor), + avatarButtonBackgroundView.widthAnchor.constraint(equalToConstant: ProfileCardView.avatarSize.width + 4).priority(.required - 1), + avatarButtonBackgroundView.heightAnchor.constraint(equalToConstant: ProfileCardView.avatarSize.height + 4).priority(.required - 1), + ]) + + // authorInfoContainer: V - [ authorNameLabel | authorUsernameLabel ] + let authorInfoContainer = UIStackView() + authorInfoContainer.axis = .vertical + // authorInfoContainer.spacing = 2 + authorContainer.addArrangedSubview(authorInfoContainer) + + authorInfoContainer.addArrangedSubview(authorNameLabel) + authorInfoContainer.addArrangedSubview(authorUsernameLabel) + + // bioMetaText + bioMetaTextAdaptiveMarginContainerView.contentView = bioMetaText.textView + bioMetaTextAdaptiveMarginContainerView.margin = ProfileCardView.contentMargin + bioMetaText.textView.setContentHuggingPriority(.required - 1, for: .vertical) + bioMetaText.textView.setContentCompressionResistancePriority(.required - 1, for: .vertical) + container.addArrangedSubview(bioMetaTextAdaptiveMarginContainerView) + container.setCustomSpacing(16, after: bioMetaTextAdaptiveMarginContainerView) + + // infoContainer: H - [ statusDashboardView | (spacer) | relationshipActionButton ] + infoContainer.axis = .horizontal + infoContainer.spacing = 8 + infoContainerAdaptiveMarginContainerView.contentView = infoContainer + infoContainerAdaptiveMarginContainerView.margin = ProfileCardView.contentMargin + container.addArrangedSubview(infoContainerAdaptiveMarginContainerView) + + infoContainer.addArrangedSubview(statusDashboardView) + let infoContainerSpacer = UIView() + infoContainer.addArrangedSubview(UIView()) + infoContainerSpacer.setContentHuggingPriority(.defaultLow - 100, for: .vertical) + infoContainerSpacer.setContentHuggingPriority(.defaultLow - 100, for: .horizontal) + let relationshipActionButtonShadowContainer = ShadowBackgroundContainer() + relationshipActionButtonShadowContainer.translatesAutoresizingMaskIntoConstraints = false + infoContainer.addArrangedSubview(relationshipActionButtonShadowContainer) + + relationshipActionButton.translatesAutoresizingMaskIntoConstraints = false + relationshipActionButtonShadowContainer.addSubview(relationshipActionButton) + NSLayoutConstraint.activate([ + relationshipActionButton.topAnchor.constraint(equalTo: relationshipActionButtonShadowContainer.topAnchor), + relationshipActionButton.leadingAnchor.constraint(equalTo: relationshipActionButtonShadowContainer.leadingAnchor), + relationshipActionButton.trailingAnchor.constraint(equalTo: relationshipActionButtonShadowContainer.trailingAnchor), + relationshipActionButton.bottomAnchor.constraint(equalTo: relationshipActionButtonShadowContainer.bottomAnchor), + relationshipActionButtonShadowContainer.widthAnchor.constraint(greaterThanOrEqualToConstant: ProfileCardView.friendshipActionButtonSize.width).priority(.required - 1), + relationshipActionButtonShadowContainer.heightAnchor.constraint(equalToConstant: ProfileCardView.friendshipActionButtonSize.height).priority(.required - 1), + ]) + + let bottomPadding = UIView() + bottomPadding.translatesAutoresizingMaskIntoConstraints = false + container.addArrangedSubview(bottomPadding) + NSLayoutConstraint.activate([ + bottomPadding.heightAnchor.constraint(equalToConstant: 16).priority(.required - 10), + ]) + + relationshipActionButton.addTarget(self, action: #selector(ProfileCardView.relationshipActionButtonDidPressed(_:)), for: .touchUpInside) + } + + public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + viewModel.userInterfaceStyle = traitCollection.userInterfaceStyle + } + + public override func layoutSubviews() { + updateInfoContainerLayout() + super.layoutSubviews() + } + +} + +extension ProfileCardView { + public func setupLayoutFrame(_ rect: CGRect) { + frame.size.width = rect.width + bioMetaTextAdaptiveMarginContainerView.frame.size.width = frame.width + bioMetaTextAdaptiveMarginContainerView.contentView?.frame.size.width = frame.width - 2 * bioMetaTextAdaptiveMarginContainerView.margin + infoContainerAdaptiveMarginContainerView.frame.size.width = frame.width + infoContainerAdaptiveMarginContainerView.contentView?.frame.size.width = frame.width - 2 * infoContainerAdaptiveMarginContainerView.margin + } + + private func updateInfoContainerLayout() { + let isCompactAdaptive = bounds.width < 350 + infoContainer.axis = isCompactAdaptive ? .vertical : .horizontal + } +} + +extension ProfileCardView { + @objc private func relationshipActionButtonDidPressed(_ sender: UIButton) { + os_log(.debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + assert(sender === relationshipActionButton) + delegate?.profileCardView(self, relationshipButtonDidPressed: relationshipActionButton) + } +} diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift index f848b37e1..44f7bb9ab 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift @@ -14,6 +14,7 @@ import MastodonSDK import MastodonAsset import MastodonLocalization import MastodonExtension +import MastodonCommon import CoreDataStack extension StatusView { @@ -73,11 +74,9 @@ extension StatusView { // Sensitive @Published public var isContentSensitive: Bool = false - @Published public var isContentSensitiveToggled: Bool = false @Published public var isMediaSensitive: Bool = false - @Published public var isMediaSensitiveToggled: Bool = false - - @Published public var isSensitive: Bool = false // isContentSensitive || isMediaSensitive + @Published public var isSensitiveToggled = false + @Published public var isContentReveal: Bool = true @Published public var isMediaReveal: Bool = true @@ -129,9 +128,8 @@ extension StatusView { authorAvatarImageURL = nil isContentSensitive = false - isContentSensitiveToggled = false isMediaSensitive = false - isMediaSensitiveToggled = false + isSensitiveToggled = false activeFilters = [] filterContext = nil @@ -160,28 +158,18 @@ extension StatusView { $spoilerContent .map { $0 != nil } .assign(to: &$isContentSensitive) - // isSensitive - Publishers.CombineLatest( + // isReveal + Publishers.CombineLatest3( $isContentSensitive, - $isMediaSensitive - ) - .map { $0 || $1 } - .assign(to: &$isSensitive) - // $isContentReveal - Publishers.CombineLatest( - $isContentSensitive, - $isContentSensitiveToggled - ) - .map { $0 ? $1 : true } - .assign(to: &$isContentReveal) - // $isMediaReveal - Publishers.CombineLatest( $isMediaSensitive, - $isMediaSensitiveToggled + $isSensitiveToggled ) - .map { $1 ? !$0 : $0 } - .map { !$0 } - .assign(to: &$isMediaReveal) + .sink { [weak self] isContentSensitive, isMediaSensitive, isSensitiveToggled in + guard let self = self else { return } + self.isContentReveal = isContentSensitive ? isSensitiveToggled : true + self.isMediaReveal = isMediaSensitive ? isSensitiveToggled : true + } + .store(in: &disposeBag) } } } @@ -321,43 +309,26 @@ extension StatusView.ViewModel { statusView.setSpoilerOverlayViewHidden(isHidden: isContentReveal) - let image = isContentReveal ? UIImage(systemName: "eye.slash.fill") : UIImage(systemName: "eye.fill") - statusView.contentSensitiveeToggleButton.setImage(image, for: .normal) - self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): isContentReveal: \(isContentReveal)") } .store(in: &disposeBag) - $isSensitive + $isMediaSensitive .sink { isSensitive in guard isSensitive else { return } statusView.setContentSensitiveeToggleButtonDisplay() } .store(in: &disposeBag) -// // visibility -// Publishers.CombineLatest( -// $visibility, -// $isMyself -// ) -// .sink { visibility, isMyself in -// switch visibility { -// case .public: -// break -// case .unlisted: -// statusView.statusVisibilityView.label.text = "Everyone can see this post but not display in the public timeline." -// statusView.setVisibilityDisplay() -// case .private: -// statusView.statusVisibilityView.label.text = isMyself ? "Only my followers can see this post." : "Only their followers can see this post." -// statusView.setVisibilityDisplay() -// case .direct: -// statusView.statusVisibilityView.label.text = "Only mentioned user can see this post." -// statusView.setVisibilityDisplay() -// case ._other: -// break -// } -// } -// .store(in: &disposeBag) + $isSensitiveToggled + .sink { isSensitiveToggled in + // The button indicator go-to state for button action direction + // eye: when media is hidden + // eye-slash: when media display + let image = isSensitiveToggled ? UIImage(systemName: "eye.slash.fill") : UIImage(systemName: "eye.fill") + statusView.contentSensitiveeToggleButton.setImage(image, for: .normal) + } + .store(in: &disposeBag) } private func bindMedia(statusView: StatusView) { @@ -414,6 +385,7 @@ extension StatusView.ViewModel { $isMediaReveal .sink { isMediaReveal in + statusView.mediaGridContainerView.contentWarningOverlay.isHidden = isMediaReveal statusView.mediaGridContainerView.viewModel.isSensitiveToggleButtonDisplay = isMediaReveal } .store(in: &disposeBag) diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift index b938f2b97..7b96fe0b5 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift @@ -141,11 +141,11 @@ public final class StatusView: UIView { return style }() metaText.textAttributes = [ - .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular)), + .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)), .foregroundColor: Asset.Colors.Label.primary.color, ] metaText.linkAttributes = [ - .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold)), + .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)), .foregroundColor: Asset.Colors.brandBlue.color, ] return metaText @@ -508,6 +508,7 @@ extension StatusView.Style { // status content statusView.contentContainer.addArrangedSubview(statusView.contentMetaText.textView) + statusView.containerStackView.setCustomSpacing(16, after: statusView.contentMetaText.textView) statusView.spoilerOverlayView.translatesAutoresizingMaskIntoConstraints = false statusView.containerStackView.addSubview(statusView.spoilerOverlayView) @@ -757,7 +758,7 @@ extension StatusView: UITextViewDelegate { // MARK: - MetaTextViewDelegate extension StatusView: MetaTextViewDelegate { public func metaTextView(_ metaTextView: MetaTextView, didSelectMeta meta: Meta) { - logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): meta: \(String(describing: meta))") switch metaTextView { case contentMetaText.textView: delegate?.statusView(self, metaText: contentMetaText, didSelectMeta: meta) diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/TrendView+Configuration.swift b/MastodonSDK/Sources/MastodonUI/View/Content/TrendView+Configuration.swift new file mode 100644 index 000000000..fd5ddb245 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/Content/TrendView+Configuration.swift @@ -0,0 +1,35 @@ +// +// TrendView+Configuration.swift +// +// +// Created by MainasuK on 2022-4-13. +// + +import UIKit +import MastodonSDK +import MastodonLocalization + +extension TrendView { + public func configure(tag: Mastodon.Entity.Tag) { + let primaryLabelText = "#" + tag.name + let secondaryLabelText = L10n.Plural.peopleTalking(tag.talkingPeopleCount ?? 0) + + primaryLabel.text = primaryLabelText + secondaryLabel.text = secondaryLabelText + + lineChartView.data = (tag.history ?? []) + .sorted(by: { $0.day < $1.day }) // latest last + .map { entry in + guard let point = Int(entry.accounts) else { + return .zero + } + return CGFloat(point) + } + + isAccessibilityElement = true + accessibilityLabel = [ + primaryLabelText, + secondaryLabelText + ].joined(separator: ", ") + } +} diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/TrendView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/TrendView.swift new file mode 100644 index 000000000..ff1b9b708 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/Content/TrendView.swift @@ -0,0 +1,100 @@ +// +// TrendView.swift +// +// +// Created by MainasuK on 2022-4-13. +// + +import UIKit +import MastodonAsset + +public final class TrendView: UIView { + + let container: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.spacing = 16 + return stackView + }() + + let infoContainer: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + return stackView + }() + + let lineChartContainer: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + return stackView + }() + + let primaryLabel: UILabel = { + let label = UILabel() + label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)) + label.textColor = Asset.Colors.Label.primary.color + return label + }() + + let secondaryLabel: UILabel = { + let label = UILabel() + label.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 15, weight: .regular)) + label.textColor = Asset.Colors.Label.secondary.color + return label + }() + + let lineChartView = LineChartView() + + public override init(frame: CGRect) { + super.init(frame: frame) + _init() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension TrendView { + private func _init() { + container.translatesAutoresizingMaskIntoConstraints = false + addSubview(container) + NSLayoutConstraint.activate([ + container.topAnchor.constraint(equalTo: topAnchor, constant: 11), + container.leadingAnchor.constraint(equalTo: leadingAnchor), + container.trailingAnchor.constraint(equalTo: trailingAnchor), + bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: 11), + ]) + + // container: H - [ info container | padding | line chart container ] + container.addArrangedSubview(infoContainer) + + // info container: V - [ primary | secondary ] + infoContainer.addArrangedSubview(primaryLabel) + infoContainer.addArrangedSubview(secondaryLabel) + + // padding + let padding = UIView() + container.addArrangedSubview(padding) + + // line chart + container.addArrangedSubview(lineChartContainer) + + let lineChartViewTopPadding = UIView() + let lineChartViewBottomPadding = UIView() + lineChartViewTopPadding.translatesAutoresizingMaskIntoConstraints = false + lineChartViewBottomPadding.translatesAutoresizingMaskIntoConstraints = false + lineChartView.translatesAutoresizingMaskIntoConstraints = false + lineChartContainer.addArrangedSubview(lineChartViewTopPadding) + lineChartContainer.addArrangedSubview(lineChartView) + lineChartContainer.addArrangedSubview(lineChartViewBottomPadding) + NSLayoutConstraint.activate([ + lineChartView.widthAnchor.constraint(equalToConstant: 50), + lineChartView.heightAnchor.constraint(equalToConstant: 26), + lineChartViewTopPadding.heightAnchor.constraint(equalTo: lineChartViewBottomPadding.heightAnchor), + ]) + } +} + diff --git a/MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift b/MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift index 449254d20..4a5c44850 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift @@ -114,7 +114,7 @@ extension ActionToolbarContainer { container.addArrangedSubview(favoriteButton) container.addArrangedSubview(shareButton) NSLayoutConstraint.activate([ - replyButton.heightAnchor.constraint(equalToConstant: 44).priority(.defaultHigh), + replyButton.heightAnchor.constraint(equalToConstant: 36).priority(.defaultHigh), replyButton.heightAnchor.constraint(equalTo: reblogButton.heightAnchor).priority(.defaultHigh), replyButton.heightAnchor.constraint(equalTo: favoriteButton.heightAnchor).priority(.defaultHigh), replyButton.heightAnchor.constraint(equalTo: shareButton.heightAnchor).priority(.defaultHigh), @@ -216,7 +216,7 @@ extension ActionToolbarContainer { public func configureReply(count: Int, isEnabled: Bool) { let title = ActionToolbarContainer.title(from: count) replyButton.setTitle(title, for: .normal) - replyButton.accessibilityLabel = "\(count) reply" // TODO: i18n + replyButton.accessibilityLabel = L10n.Plural.Count.reply(count) } public func configureReblog(count: Int, isEnabled: Bool, isHighlighted: Bool) { diff --git a/MastodonSDK/Sources/MastodonUI/View/Control/ContentWarningOverlayView.swift b/MastodonSDK/Sources/MastodonUI/View/Control/ContentWarningOverlayView.swift index d559e4e04..70be5bbc2 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Control/ContentWarningOverlayView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Control/ContentWarningOverlayView.swift @@ -7,28 +7,24 @@ import os.log import UIKit - -public protocol ContentWarningOverlayViewDelegate: AnyObject { - func contentWarningOverlayViewDidPressed(_ contentWarningOverlayView: ContentWarningOverlayView) -} +import MastodonLocalization public final class ContentWarningOverlayView: UIView { - - public static let blurVisualEffect = UIBlurEffect(style: .systemUltraThinMaterial) - + let logger = Logger(subsystem: "ContentWarningOverlayView", category: "View") - public weak var delegate: ContentWarningOverlayViewDelegate? - - public let blurVisualEffectView = UIVisualEffectView(effect: ContentWarningOverlayView.blurVisualEffect) - public let vibrancyVisualEffectView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: ContentWarningOverlayView.blurVisualEffect)) -// let alertImageView: UIImageView = { -// let imageView = UIImageView() -// imageView.image = Asset.Indices.exclamationmarkTriangleLarge.image.withRenderingMode(.alwaysTemplate) -// return imageView -// }() - - public let tapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer + let hintLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 18, weight: .regular) + label.text = L10n.Common.Controls.Status.tapToReveal + label.textAlignment = .center + label.textColor = .white.withAlphaComponent(0.7) + label.layer.shadowOpacity = 0.3 + label.layer.shadowOffset = CGSize(width: 0, height: 2) + label.layer.shadowRadius = 2 + label.layer.shadowColor = UIColor.black.cgColor + return label + }() override init(frame: CGRect) { super.init(frame: frame) @@ -44,40 +40,12 @@ public final class ContentWarningOverlayView: UIView { extension ContentWarningOverlayView { private func _init() { - // overlay - blurVisualEffectView.translatesAutoresizingMaskIntoConstraints = false - addSubview(blurVisualEffectView) + hintLabel.translatesAutoresizingMaskIntoConstraints = false + addSubview(hintLabel) NSLayoutConstraint.activate([ - blurVisualEffectView.topAnchor.constraint(equalTo: topAnchor), - blurVisualEffectView.leadingAnchor.constraint(equalTo: leadingAnchor), - blurVisualEffectView.trailingAnchor.constraint(equalTo: trailingAnchor), - blurVisualEffectView.bottomAnchor.constraint(equalTo: bottomAnchor), + hintLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8), + trailingAnchor.constraint(equalTo: hintLabel.trailingAnchor, constant: 8), + centerYAnchor.constraint(equalTo: hintLabel.centerYAnchor, constant: 10), ]) - - vibrancyVisualEffectView.translatesAutoresizingMaskIntoConstraints = false - blurVisualEffectView.contentView.addSubview(vibrancyVisualEffectView) - NSLayoutConstraint.activate([ - vibrancyVisualEffectView.topAnchor.constraint(equalTo: blurVisualEffectView.contentView.topAnchor), - vibrancyVisualEffectView.leadingAnchor.constraint(equalTo: blurVisualEffectView.contentView.leadingAnchor), - vibrancyVisualEffectView.trailingAnchor.constraint(equalTo: blurVisualEffectView.contentView.trailingAnchor), - vibrancyVisualEffectView.bottomAnchor.constraint(equalTo: blurVisualEffectView.contentView.bottomAnchor), - ]) - -// alertImageView.translatesAutoresizingMaskIntoConstraints = false -// vibrancyVisualEffectView.contentView.addSubview(alertImageView) -// NSLayoutConstraint.activate([ -// alertImageView.centerXAnchor.constraint(equalTo: vibrancyVisualEffectView.contentView.centerXAnchor), -// alertImageView.centerYAnchor.constraint(equalTo: vibrancyVisualEffectView.contentView.centerYAnchor), -// ]) - - tapGestureRecognizer.addTarget(self, action: #selector(ContentWarningOverlayView.tapGestureRecognizerHandler(_:))) - addGestureRecognizer(tapGestureRecognizer) - } -} - -extension ContentWarningOverlayView { - @objc private func tapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) { - logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") - delegate?.contentWarningOverlayViewDidPressed(self) } } diff --git a/Mastodon/Scene/Search/Search/View/LineChartView.swift b/MastodonSDK/Sources/MastodonUI/View/Control/LineChartView.swift similarity index 85% rename from Mastodon/Scene/Search/Search/View/LineChartView.swift rename to MastodonSDK/Sources/MastodonUI/View/Control/LineChartView.swift index cd76fb0c8..c90b59f0e 100644 --- a/Mastodon/Scene/Search/Search/View/LineChartView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Control/LineChartView.swift @@ -7,12 +7,11 @@ import UIKit import Accelerate -import simd import MastodonAsset -final class LineChartView: UIView { +public final class LineChartView: UIView { - var data: [CGFloat] = [] { + public var data: [CGFloat] = [] { didSet { setNeedsLayout() } @@ -20,14 +19,13 @@ final class LineChartView: UIView { let lineShapeLayer = CAShapeLayer() let gradientLayer = CAGradientLayer() -// let dotShapeLayer = CAShapeLayer() - override init(frame: CGRect) { + public override init(frame: CGRect) { super.init(frame: frame) _init() } - required init?(coder: NSCoder) { + public required init?(coder: NSCoder) { super.init(coder: coder) _init() } @@ -38,10 +36,8 @@ extension LineChartView { private func _init() { lineShapeLayer.frame = bounds gradientLayer.frame = bounds -// dotShapeLayer.frame = bounds layer.addSublayer(lineShapeLayer) layer.addSublayer(gradientLayer) -// layer.addSublayer(dotShapeLayer) gradientLayer.colors = [ Asset.Colors.brandBlue.color.withAlphaComponent(0.5).cgColor, // set the same alpha to fill @@ -51,16 +47,14 @@ extension LineChartView { gradientLayer.endPoint = CGPoint(x: 0.5, y: 1) } - override func layoutSubviews() { + public override func layoutSubviews() { super.layoutSubviews() lineShapeLayer.frame = bounds gradientLayer.frame = bounds -// dotShapeLayer.frame = bounds guard data.count > 1 else { lineShapeLayer.path = nil -// dotShapeLayer.path = nil gradientLayer.isHidden = true return } @@ -113,9 +107,5 @@ extension LineChartView { maskLayer.strokeColor = UIColor.clear.cgColor maskLayer.lineWidth = 0.0 gradientLayer.mask = maskLayer - -// dotShapeLayer.lineWidth = 3 -// dotShapeLayer.fillColor = Asset.Colors.brandBlue.color.cgColor -// dotShapeLayer.path = dotPath.cgPath } } diff --git a/Mastodon/Scene/Profile/Header/View/ProfileRelationshipActionButton.swift b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileRelationshipActionButton.swift similarity index 80% rename from Mastodon/Scene/Profile/Header/View/ProfileRelationshipActionButton.swift rename to MastodonSDK/Sources/MastodonUI/View/Control/ProfileRelationshipActionButton.swift index 87c189a45..bdf696e74 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileRelationshipActionButton.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileRelationshipActionButton.swift @@ -6,23 +6,23 @@ // import UIKit -import MastodonUI import MastodonAsset +import MastodonLocalization -final class ProfileRelationshipActionButton: RoundedEdgesButton { +public final class ProfileRelationshipActionButton: RoundedEdgesButton { - let activityIndicatorView: UIActivityIndicatorView = { + public let activityIndicatorView: UIActivityIndicatorView = { let activityIndicatorView = UIActivityIndicatorView(style: .medium) activityIndicatorView.color = Asset.Colors.Label.primaryReverse.color return activityIndicatorView }() - override init(frame: CGRect) { + public override init(frame: CGRect) { super.init(frame: frame) _init() } - required init?(coder: NSCoder) { + public required init?(coder: NSCoder) { super.init(coder: coder) _init() } @@ -47,7 +47,7 @@ extension ProfileRelationshipActionButton { configureAppearance() } - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) configureAppearance() @@ -55,7 +55,7 @@ extension ProfileRelationshipActionButton { } extension ProfileRelationshipActionButton { - func configure(actionOptionSet: ProfileViewModel.RelationshipActionOptionSet) { + public func configure(actionOptionSet: RelationshipActionOptionSet) { setTitle(actionOptionSet.title, for: .normal) configureAppearance() @@ -87,9 +87,5 @@ extension ProfileRelationshipActionButton { setBackgroundImage(.placeholder(color: Asset.Scene.Profile.RelationshipButton.backgroundHighlightedLight.color), for: .highlighted) setBackgroundImage(.placeholder(color: Asset.Scene.Profile.RelationshipButton.backgroundHighlightedLight.color), for: .disabled) } -// setBackgroundImage(.placeholder(color: actionOptionSet.backgroundColor), for: .normal) -// setBackgroundImage(.placeholder(color: actionOptionSet.backgroundColor.withAlphaComponent(0.5)), for: .highlighted) -// setBackgroundImage(.placeholder(color: actionOptionSet.backgroundColor.withAlphaComponent(0.5)), for: .disabled) } } - diff --git a/Mastodon/Scene/Profile/Header/View/ProfileStatusDashboardMeterView.swift b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileStatusDashboardMeterView.swift similarity index 91% rename from Mastodon/Scene/Profile/Header/View/ProfileStatusDashboardMeterView.swift rename to MastodonSDK/Sources/MastodonUI/View/Control/ProfileStatusDashboardMeterView.swift index 9176d7a3c..0c9d243c2 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileStatusDashboardMeterView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileStatusDashboardMeterView.swift @@ -9,9 +9,9 @@ import UIKit import MastodonAsset import MastodonLocalization -final class ProfileStatusDashboardMeterView: UIView { +public final class ProfileStatusDashboardMeterView: UIView { - let numberLabel: UILabel = { + public let numberLabel: UILabel = { let label = UILabel() label.font = { let font = UIFont.systemFont(ofSize: 20, weight: .semibold) @@ -25,7 +25,7 @@ final class ProfileStatusDashboardMeterView: UIView { return label }() - let textLabel: UILabel = { + public let textLabel: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: 13, weight: .regular) label.textColor = Asset.Colors.Label.primary.color @@ -41,12 +41,12 @@ final class ProfileStatusDashboardMeterView: UIView { return label }() - override init(frame: CGRect) { + public override init(frame: CGRect) { super.init(frame: frame) _init() } - required init?(coder: NSCoder) { + public required init?(coder: NSCoder) { super.init(coder: coder) _init() } diff --git a/Mastodon/Scene/Profile/Header/View/ProfileStatusDashboardView.swift b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileStatusDashboardView.swift similarity index 79% rename from Mastodon/Scene/Profile/Header/View/ProfileStatusDashboardView.swift rename to MastodonSDK/Sources/MastodonUI/View/Control/ProfileStatusDashboardView.swift index 9448f1964..a45e8ef6a 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileStatusDashboardView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileStatusDashboardView.swift @@ -10,24 +10,24 @@ import UIKit import MastodonAsset import MastodonLocalization -protocol ProfileStatusDashboardViewDelegate: AnyObject { +public protocol ProfileStatusDashboardViewDelegate: AnyObject { func profileStatusDashboardView(_ dashboardView: ProfileStatusDashboardView, dashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView, meter: ProfileStatusDashboardView.Meter) } -final class ProfileStatusDashboardView: UIView { +public final class ProfileStatusDashboardView: UIView { - let postDashboardMeterView = ProfileStatusDashboardMeterView() - let followingDashboardMeterView = ProfileStatusDashboardMeterView() - let followersDashboardMeterView = ProfileStatusDashboardMeterView() + public let postDashboardMeterView = ProfileStatusDashboardMeterView() + public let followingDashboardMeterView = ProfileStatusDashboardMeterView() + public let followersDashboardMeterView = ProfileStatusDashboardMeterView() - weak var delegate: ProfileStatusDashboardViewDelegate? + public weak var delegate: ProfileStatusDashboardViewDelegate? - override init(frame: CGRect) { + public override init(frame: CGRect) { super.init(frame: frame) _init() } - required init?(coder: NSCoder) { + public required init?(coder: NSCoder) { super.init(coder: coder) _init() } @@ -35,7 +35,7 @@ final class ProfileStatusDashboardView: UIView { } extension ProfileStatusDashboardView { - enum Meter: Hashable { + public enum Meter: Hashable { case post case following case follower @@ -55,7 +55,7 @@ extension ProfileStatusDashboardView { containerStackView.heightAnchor.constraint(equalToConstant: 44).priority(.defaultHigh), ]) - let spacing: CGFloat = UIView.isZoomedMode ? 4 : 16 + let spacing: CGFloat = UIView.isZoomedMode ? 4 : 12 containerStackView.spacing = spacing containerStackView.axis = .horizontal containerStackView.distribution = .fillEqually @@ -75,15 +75,15 @@ extension ProfileStatusDashboardView { tapGestureRecognizer.addTarget(self, action: #selector(ProfileStatusDashboardView.tapGestureRecognizerHandler(_:))) meterView.addGestureRecognizer(tapGestureRecognizer) } - - followingDashboardMeterView.accessibilityHint = "Double tap to open the list" // TODO: i18n - followersDashboardMeterView.accessibilityHint = "Double tap to open the list" + + followingDashboardMeterView.accessibilityHint = L10n.Scene.Profile.Accessibility.doubleTapToOpenTheList + followersDashboardMeterView.accessibilityHint = L10n.Scene.Profile.Accessibility.doubleTapToOpenTheList } } extension ProfileStatusDashboardView { @objc private func tapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) { - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + os_log(.debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) guard let sourceView = sender.view as? ProfileStatusDashboardMeterView else { assertionFailure() return diff --git a/MastodonSDK/Sources/MastodonUI/View/TableViewCell/NewsTableViewCell.swift b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/NewsTableViewCell.swift new file mode 100644 index 000000000..f0b2aec8f --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/NewsTableViewCell.swift @@ -0,0 +1,61 @@ +// +// NewsTableViewCell.swift +// +// +// Created by MainasuK on 2022-4-13. +// + +import UIKit + +public final class NewsTableViewCell: UITableViewCell { + + public let newsView = NewsView() + + let separatorLine = UIView.separatorLine + + public override func prepareForReuse() { + super.prepareForReuse() + + newsView.prepareForReuse() + } + + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + _init() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension NewsTableViewCell { + + private func _init() { + newsView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(newsView) + NSLayoutConstraint.activate([ + newsView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16), + newsView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), + newsView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor), + contentView.bottomAnchor.constraint(equalTo: newsView.bottomAnchor, constant: 16), + ]) + + separatorLine.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(separatorLine) + NSLayoutConstraint.activate([ + separatorLine.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + separatorLine.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + separatorLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)), + ]) + + isAccessibilityElement = true + accessibilityElements = [ + newsView + ] + } + +} diff --git a/MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell+Configuration.swift b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell+Configuration.swift new file mode 100644 index 000000000..061af0f48 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell+Configuration.swift @@ -0,0 +1,30 @@ +// +// ProfileCardTableViewCell+Configuration.swift +// +// +// Created by MainasuK on 2022-4-19. +// + +import UIKit +import CoreDataStack + +extension ProfileCardTableViewCell { + + public func configure( + tableView: UITableView, + user: MastodonUser, + profileCardTableViewCellDelegate: ProfileCardTableViewCellDelegate? + ) { + if profileCardView.frame == .zero { + // set content view width + assert(layoutMarginsGuide.layoutFrame.width > .zero) + shadowBackgroundContainer.frame.size.width = layoutMarginsGuide.layoutFrame.width + profileCardView.setupLayoutFrame(layoutMarginsGuide.layoutFrame) + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): did layout for new cell") + } + + profileCardView.configure(user: user) + delegate = profileCardTableViewCellDelegate + } + +} diff --git a/MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell.swift b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell.swift new file mode 100644 index 000000000..aff7b6feb --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell.swift @@ -0,0 +1,92 @@ +// +// ProfileCardTableViewCell.swift +// +// +// Created by MainasuK on 2022-4-14. +// + +import os.log +import UIKit +import Combine + +public protocol ProfileCardTableViewCellDelegate: AnyObject { + func profileCardTableViewCell(_ cell: ProfileCardTableViewCell, profileCardView: ProfileCardView, relationshipButtonDidPressed button: ProfileRelationshipActionButton) +} + +public final class ProfileCardTableViewCell: UITableViewCell { + + let logger = Logger(subsystem: "ProfileCardTableViewCell", category: "Cell") + + public weak var delegate: ProfileCardTableViewCellDelegate? + public var disposeBag = Set() + + public let shadowBackgroundContainer = ShadowBackgroundContainer() + + public let profileCardView: ProfileCardView = { + let profileCardView = ProfileCardView() + profileCardView.layer.masksToBounds = true + profileCardView.layer.cornerRadius = 6 + profileCardView.layer.cornerCurve = .continuous + return profileCardView + }() + + public override func prepareForReuse() { + super.prepareForReuse() + + disposeBag.removeAll() + profileCardView.prepareForReuse() + } + + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + _init() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension ProfileCardTableViewCell { + + private func _init() { + selectionStyle = .none + + shadowBackgroundContainer.cornerRadius = 6 + shadowBackgroundContainer.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(shadowBackgroundContainer) + NSLayoutConstraint.activate([ + shadowBackgroundContainer.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10).priority(.required - 1), + shadowBackgroundContainer.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), + shadowBackgroundContainer.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor), + contentView.bottomAnchor.constraint(equalTo: shadowBackgroundContainer.bottomAnchor, constant: 10).priority(.required - 1), + ]) + + profileCardView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(profileCardView) + NSLayoutConstraint.activate([ + profileCardView.topAnchor.constraint(equalTo: shadowBackgroundContainer.topAnchor), + profileCardView.leadingAnchor.constraint(equalTo: shadowBackgroundContainer.leadingAnchor), + profileCardView.trailingAnchor.constraint(equalTo: shadowBackgroundContainer.trailingAnchor), + profileCardView.bottomAnchor.constraint(equalTo: shadowBackgroundContainer.bottomAnchor), + ]) + + profileCardView.delegate = self + + profileCardView.isAccessibilityElement = true + accessibilityElements = [ + profileCardView, + profileCardView.relationshipActionButton + ] + } + +} + +// MARK: - ProfileCardViewDelegate +extension ProfileCardTableViewCell: ProfileCardViewDelegate { + public func profileCardView(_ profileCardView: ProfileCardView, relationshipButtonDidPressed button: ProfileRelationshipActionButton) { + delegate?.profileCardTableViewCell(self, profileCardView: profileCardView, relationshipButtonDidPressed: button) + } +} diff --git a/MastodonSDK/Sources/MastodonUI/View/TableViewCell/TrendTableViewCell.swift b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/TrendTableViewCell.swift new file mode 100644 index 000000000..8c1cebff0 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/TrendTableViewCell.swift @@ -0,0 +1,86 @@ +// +// TrendTableViewCell.swift +// +// +// Created by MainasuK on 2022-4-13. +// + +import UIKit + +public final class TrendTableViewCell: UITableViewCell { + + public let trendView = TrendView() + + let separatorLine = UIView.separatorLine + + public override func prepareForReuse() { + super.prepareForReuse() + + configureSeparator(style: .inset) + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + _init() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension TrendTableViewCell { + + private func _init() { + trendView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(trendView) + NSLayoutConstraint.activate([ + trendView.topAnchor.constraint(equalTo: contentView.topAnchor), + trendView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), + trendView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor), + trendView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + ]) + + configureSeparator(style: .inset) + + accessibilityElements = [trendView] + } + +} + +extension TrendTableViewCell { + + public enum SeparatorStyle { + case edge + case inset + } + + public func configureSeparator(style: SeparatorStyle) { + separatorLine.removeFromSuperview() + separatorLine.removeConstraints(separatorLine.constraints) + + switch style { + case .edge: + separatorLine.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(separatorLine) + NSLayoutConstraint.activate([ + separatorLine.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + separatorLine.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + separatorLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)), + ]) + case .inset: + separatorLine.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(separatorLine) + NSLayoutConstraint.activate([ + separatorLine.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), + separatorLine.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + separatorLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)), + ]) + } + } + +} diff --git a/MastodonSDK/Sources/MastodonUI/ViewModel/RelationshipViewModel.swift b/MastodonSDK/Sources/MastodonUI/ViewModel/RelationshipViewModel.swift new file mode 100644 index 000000000..cee31c14a --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/ViewModel/RelationshipViewModel.swift @@ -0,0 +1,253 @@ +// +// RelationshipViewModel.swift +// +// +// Created by MainasuK on 2022-4-14. +// + +import UIKit +import Combine +import MastodonAsset +import MastodonLocalization +import CoreDataStack + +public enum RelationshipAction: Int, CaseIterable { + case isMyself + case followingBy + case blockingBy + case none // set hide from UI + case follow + case request + case pending + case following + case muting + case blocked + case blocking + case suspended + case edit + case editing + case updating + + public var option: RelationshipActionOptionSet { + return RelationshipActionOptionSet(rawValue: 1 << rawValue) + } +} + +// construct option set on the enum for safe iterator +public struct RelationshipActionOptionSet: OptionSet { + + public let rawValue: Int + + public init(rawValue: Int) { + self.rawValue = rawValue + } + + public static let isMyself = RelationshipAction.isMyself.option + public static let followingBy = RelationshipAction.followingBy.option + public static let blockingBy = RelationshipAction.blockingBy.option + public static let none = RelationshipAction.none.option + public static let follow = RelationshipAction.follow.option + public static let request = RelationshipAction.request.option + public static let pending = RelationshipAction.pending.option + public static let following = RelationshipAction.following.option + public static let muting = RelationshipAction.muting.option + public static let blocked = RelationshipAction.blocked.option + public static let blocking = RelationshipAction.blocking.option + public static let suspended = RelationshipAction.suspended.option + public static let edit = RelationshipAction.edit.option + public static let editing = RelationshipAction.editing.option + public static let updating = RelationshipAction.updating.option + + public static let editOptions: RelationshipActionOptionSet = [.edit, .editing, .updating] + + public func highPriorityAction(except: RelationshipActionOptionSet) -> RelationshipAction? { + let set = subtracting(except) + for action in RelationshipAction.allCases.reversed() where set.contains(action.option) { + return action + } + + return nil + } + + public var title: String { + guard let highPriorityAction = self.highPriorityAction(except: []) else { + assertionFailure() + return " " + } + switch highPriorityAction { + case .isMyself: return "" + case .followingBy: return " " + case .blockingBy: return " " + case .none: return " " + case .follow: return L10n.Common.Controls.Friendship.follow + case .request: return L10n.Common.Controls.Friendship.request + case .pending: return L10n.Common.Controls.Friendship.pending + case .following: return L10n.Common.Controls.Friendship.following + case .muting: return L10n.Common.Controls.Friendship.muted + case .blocked: return L10n.Common.Controls.Friendship.follow // blocked by user + case .blocking: return L10n.Common.Controls.Friendship.blocked + case .suspended: return L10n.Common.Controls.Friendship.follow + case .edit: return L10n.Common.Controls.Friendship.editInfo + case .editing: return L10n.Common.Controls.Actions.done + case .updating: return " " + } + } + +} + +public final class RelationshipViewModel { + + var disposeBag = Set() + + public var userObserver: AnyCancellable? + public var meObserver: AnyCancellable? + + // input + @Published public var user: MastodonUser? + @Published public var me: MastodonUser? + public let relationshipUpdatePublisher = CurrentValueSubject(Void()) // needs initial event + + // output + @Published public var isMyself = false + @Published public var optionSet: RelationshipActionOptionSet? + + @Published public var isFollowing = false + @Published public var isFollowingBy = false + @Published public var isMuting = false + @Published public var isBlocking = false + @Published public var isBlockingBy = false + + public init() { + Publishers.CombineLatest3( + $user, + $me, + relationshipUpdatePublisher + ) + .sink { [weak self] user, me, _ in + guard let self = self else { return } + self.update(user: user, me: me) + + guard let user = user, let me = me else { + self.userObserver = nil + self.meObserver = nil + return + } + + // do not modify object to prevent infinity loop + self.userObserver = RelationshipViewModel.createObjectChangePublisher(user: user) + .sink { [weak self] _ in + guard let self = self else { return } + self.relationshipUpdatePublisher.send() + } + + self.meObserver = RelationshipViewModel.createObjectChangePublisher(user: me) + .sink { [weak self] _ in + guard let self = self else { return } + self.relationshipUpdatePublisher.send() + } + } + .store(in: &disposeBag) + } + +} + +extension RelationshipViewModel { + + public static func createObjectChangePublisher(user: MastodonUser) -> AnyPublisher { + return ManagedObjectObserver + .observe(object: user) + .map { _ in Void() } + .catch { error in + return Just(Void()) + } + .eraseToAnyPublisher() + } + +} + +extension RelationshipViewModel { + private func update(user: MastodonUser?, me: MastodonUser?) { + guard let user = user, + let me = me + else { + reset() + return + } + + let optionSet = RelationshipViewModel.optionSet(user: user, me: me) + + self.isMyself = optionSet.contains(.isMyself) + self.isFollowingBy = optionSet.contains(.followingBy) + self.isFollowing = optionSet.contains(.following) + self.isMuting = optionSet.contains(.muting) + self.isBlockingBy = optionSet.contains(.blockingBy) + self.isBlocking = optionSet.contains(.blocking) + + + self.optionSet = optionSet + } + + private func reset() { + isMyself = false + isFollowingBy = false + isFollowing = false + isMuting = false + isBlockingBy = false + isBlocking = false + optionSet = nil + } +} + +extension RelationshipViewModel { + + public static func optionSet(user: MastodonUser, me: MastodonUser) -> RelationshipActionOptionSet { + let isMyself = user.id == me.id && user.domain == me.domain + guard !isMyself else { + return [.isMyself] + } + + let isProtected = user.locked + let isFollowingBy = me.followingBy.contains(user) + let isFollowing = user.followingBy.contains(me) + let isPending = user.followRequestedBy.contains(me) + let isMuting = user.mutingBy.contains(me) + let isBlockingBy = me.blockingBy.contains(user) + let isBlocking = user.blockingBy.contains(me) + + var optionSet: RelationshipActionOptionSet = [.follow] + + if isMyself { + optionSet.insert(.isMyself) + } + + if isProtected { + optionSet.insert(.request) + } + + if isFollowingBy { + optionSet.insert(.followingBy) + } + + if isFollowing { + optionSet.insert(.following) + } + + if isPending { + optionSet.insert(.pending) + } + + if isMuting { + optionSet.insert(.muting) + } + + if isBlockingBy { + optionSet.insert(.blockingBy) + } + + if isBlocking { + optionSet.insert(.blocking) + } + + return optionSet + } +} diff --git a/MastodonTests/Info.plist b/MastodonTests/Info.plist index 73f11cd26..a187ca268 100644 --- a/MastodonTests/Info.plist +++ b/MastodonTests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.3.0 + 1.4.2 CFBundleVersion - 109 + 127 diff --git a/MastodonUITests/Info.plist b/MastodonUITests/Info.plist index 73f11cd26..a187ca268 100644 --- a/MastodonUITests/Info.plist +++ b/MastodonUITests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.3.0 + 1.4.2 CFBundleVersion - 109 + 127 diff --git a/NotificationService/Info.plist b/NotificationService/Info.plist index 215b572b5..fa2356db2 100644 --- a/NotificationService/Info.plist +++ b/NotificationService/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.3.0 + 1.4.2 CFBundleVersion - 109 + 127 NSExtension NSExtensionPointIdentifier diff --git a/Podfile b/Podfile index ad7715abd..30f90a05e 100644 --- a/Podfile +++ b/Podfile @@ -29,21 +29,6 @@ target 'Mastodon' do end -target 'NotificationService' do - # Comment the next line if you don't want to use dynamic frameworks - use_frameworks! -end - -target 'ShareActionExtension' do - # Comment the next line if you don't want to use dynamic frameworks - use_frameworks! -end - -target 'MastodonIntent' do - # Comment the next line if you don't want to use dynamic frameworks - use_frameworks! -end - target 'AppShared' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! diff --git a/Podfile.lock b/Podfile.lock index f8dde6937..0c156eadc 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -40,6 +40,6 @@ SPEC CHECKSUMS: SwiftGen: 67860cc7c3cfc2ed25b9b74cfd55495fc89f9108 "UITextField+Shake": 298ac5a0f239d731bdab999b19b628c956ca0ac3 -PODFILE CHECKSUM: c471d1f9c923dc63bf8684415c79b85adb2ac36b +PODFILE CHECKSUM: 335d0ca70493d4c280d0f8fd7f26fe9be6a4e289 COCOAPODS: 1.11.3 diff --git a/ShareActionExtension/Info.plist b/ShareActionExtension/Info.plist index 82ba5658a..95cd141f7 100644 --- a/ShareActionExtension/Info.plist +++ b/ShareActionExtension/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.3.0 + 1.4.2 CFBundleVersion - 109 + 127 NSExtension NSExtensionAttributes diff --git a/ShareActionExtension/Scene/ShareViewController.swift b/ShareActionExtension/Scene/ShareViewController.swift index d45558f1a..622e0106b 100644 --- a/ShareActionExtension/Scene/ShareViewController.swift +++ b/ShareActionExtension/Scene/ShareViewController.swift @@ -12,6 +12,7 @@ import MastodonUI import SwiftUI import MastodonAsset import MastodonLocalization +import MastodonUI class ShareViewController: UIViewController { diff --git a/ShareActionExtension/Scene/ShareViewModel.swift b/ShareActionExtension/Scene/ShareViewModel.swift index fbad82209..c56f8ecfd 100644 --- a/ShareActionExtension/Scene/ShareViewModel.swift +++ b/ShareActionExtension/Scene/ShareViewModel.swift @@ -16,6 +16,7 @@ import SwiftUI import UniformTypeIdentifiers import MastodonAsset import MastodonLocalization +import MastodonUI final class ShareViewModel { diff --git a/ShareActionExtension/Scene/View/ComposeToolbarView.swift b/ShareActionExtension/Scene/View/ComposeToolbarView.swift index 73caac735..a903d3ebd 100644 --- a/ShareActionExtension/Scene/View/ComposeToolbarView.swift +++ b/ShareActionExtension/Scene/View/ComposeToolbarView.swift @@ -12,6 +12,7 @@ import MastodonSDK import MastodonUI import MastodonAsset import MastodonLocalization +import MastodonUI protocol ComposeToolbarViewDelegate: AnyObject { func composeToolbarView(_ composeToolbarView: ComposeToolbarView, contentWarningButtonDidPressed sender: UIButton) diff --git a/ShareActionExtension/Scene/View/StatusEditorView.swift b/ShareActionExtension/Scene/View/StatusEditorView.swift index 595057fa0..f670f6601 100644 --- a/ShareActionExtension/Scene/View/StatusEditorView.swift +++ b/ShareActionExtension/Scene/View/StatusEditorView.swift @@ -54,6 +54,9 @@ public struct StatusEditorView: UIViewRepresentable { } public func updateUIView(_ textView: UITextView, context: Context) { + // preserve currently selected text range to prevent cursor jump + let currentlySelectedRange = textView.selectedRange + // update content // textView.attributedText = attributedString textView.text = string @@ -66,6 +69,9 @@ public struct StatusEditorView: UIViewRepresentable { viewDidAppear = false textView.becomeFirstResponder() } + + // restore selected text range + textView.selectedRange = currentlySelectedRange } public func makeCoordinator() -> Coordinator { diff --git a/crowdin.yml b/crowdin.yml index 38421efa6..9fdca4a22 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -14,37 +14,37 @@ files: [ # Where translations will be placed # e.g. "/resources/%two_letters_code%/%original_file_name%" # - "translation" : "/Localization/StringsConvertor/input/%locale_with_underscore%/%original_file_name%", + "translation" : "/Localization/StringsConvertor/input/%osx_code%/%original_file_name%", # # The parameter "update_option" is optional. If it is not set, after the files update the translations for changed strings will be removed. Use to fix typos and for minor changes in the source strings # e.g. "update_as_unapproved" or "update_without_changes" # - "update_option" : "update_as_unapproved", + # "update_option" : "update_as_unapproved", # }, { # App info plist strings template "source" : "/Localization/ios-infoPlist.json", - "translation" : "/Localization/StringsConvertor/input/%locale_with_underscore%/%original_file_name%", + "translation" : "/Localization/StringsConvertor/input/%osx_code%/%original_file_name%", "update_option" : "update_as_unapproved", }, { # App strings dict "source" : "/Localization/Localizable.stringsdict", - "translation" : "/Localization/StringsConvertor/input/%locale_with_underscore%/%original_file_name%", - "update_option" : "update_as_unapproved", + "translation" : "/Localization/StringsConvertor/input/%osx_code%/%original_file_name%", + # "update_option" : "update_as_unapproved", }, { # intents strings "source" : "/MastodonIntent/en.lproj/Intents.strings", - "translation" : "/Localization/StringsConvertor/Intents/input/%locale_with_underscore%/%original_file_name%", - "update_option" : "update_as_unapproved", + "translation" : "/Localization/StringsConvertor/Intents/input/%osx_code%/%original_file_name%", + # "update_option" : "update_as_unapproved", }, { # intents strings dict "source" : "/MastodonIntent/en.lproj/Intents.stringsdict", - "translation" : "/Localization/StringsConvertor/Intents/input/%locale_with_underscore%/%original_file_name%", - "update_option" : "update_as_unapproved", + "translation" : "/Localization/StringsConvertor/Intents/input/%osx_code%/%original_file_name%", + # "update_option" : "update_as_unapproved", }, ] \ No newline at end of file diff --git a/update_localization.sh b/update_localization.sh index b234cd933..09cfc21d6 100755 --- a/update_localization.sh +++ b/update_localization.sh @@ -21,7 +21,7 @@ echo "${PODS_ROOT}/SwiftGen/bin/swiftgen" if [[ -f "${PODS_ROOT}/SwiftGen/bin/swiftgen" ]] then "${PODS_ROOT}/SwiftGen/bin/swiftgen" else - echo "Run 'pod install' or update your CocoaPods installation." + echo "Run 'bundle exec pod install' or update your CocoaPods installation." fi #task 4 clean temp file