diff --git a/.github/scripts/build-release.sh b/.github/scripts/build-release.sh
index e3efc59df..069aa673e 100755
--- a/.github/scripts/build-release.sh
+++ b/.github/scripts/build-release.sh
@@ -36,6 +36,8 @@ BUILD_NUMBER=$(app-store-connect get-latest-testflight-build-number $ENV_APP_ID
BUILD_NUMBER=$((BUILD_NUMBER+1))
CURRENT_PROJECT_VERSION=${BUILD_NUMBER:-0}
+echo "GITHUB_TAG_NAME=build-$CURRENT_PROJECT_VERSION" >> $GITHUB_ENV
+
agvtool new-version -all $CURRENT_PROJECT_VERSION
xcrun xcodebuild clean \
diff --git a/.github/workflows/develop-build.yml b/.github/workflows/develop-build.yml
index c37f00731..ede061098 100644
--- a/.github/workflows/develop-build.yml
+++ b/.github/workflows/develop-build.yml
@@ -4,6 +4,7 @@ on:
push:
branches:
- develop
+ - release*
- ci-test
jobs:
@@ -11,16 +12,17 @@ jobs:
name: Build
runs-on: macOS-12
steps:
- - name: checkout
+ - name: Checkout
uses: actions/checkout@v2
- - name: setup
+ - name: Setup
env:
NotificationEndpointDebug: ${{ secrets.NotificationEndpointDebug }}
NotificationEndpointRelease: ${{ secrets.NotificationEndpointRelease }}
run: exec ./.github/scripts/setup.sh
- - uses: actions/setup-python@v4
+ - name: Install codemagic-cli-tools
+ uses: actions/setup-python@v4
with:
python-version: '3.11'
- run: |
@@ -43,7 +45,7 @@ jobs:
api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}
- - name: build
+ - name: Build
env:
ENV_APP_ID: ${{ secrets.APP_ID }}
ENV_ISSUER_ID: ${{ secrets.APPSTORE_ISSUER_ID }}
@@ -60,6 +62,12 @@ jobs:
api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}
+ - name: Tag commit
+ uses: tvdias/github-tagger@v0.0.1
+ with:
+ repo-token: "${{ secrets.GITHUB_TOKEN }}"
+ tag: "${{ env.GITHUB_TAG_NAME }}"
+
- name: Clean up keychain and provisioning profile
if: ${{ always() }}
run: |
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 827276208..1c40b6556 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -6,6 +6,9 @@ on:
- master
- develop
- feature/*
+ - feature-*
+ - issue/*
+ - issue-*
pull_request:
branches:
- develop
diff --git a/Documentation/How-it-works.md b/Documentation/How-it-works.md
new file mode 100644
index 000000000..6b48159e5
--- /dev/null
+++ b/Documentation/How-it-works.md
@@ -0,0 +1,96 @@
+# How it works
+The app is currently build for iOS and iPadOS. We use the MVVM architecture to construct the whole app. Some design detail may not be the best practice. And any suggestions for improvements are welcome.
+
+## Data
+A typical status timeline fetches results from the database using a predicate that fetch the active account's entities. Then data source dequeues an item then configure the view. Likes many other MVVM applications. The app binds the Core Data entity to view via Combine publisher. Because the RunLoop dispatch drawing on the next loop. So we could return quickly.
+
+## Layout
+A timeline has many posts and each post has many components. For example avatar, name, username, timestamp, content, media, toolbar and e.t.c. The app uses `AutoLayout` with `UIStackView` to place it and control whether it should hide or not.
+
+## Performance
+Although it's easily loading timeline with hundreds of thousands of entities due to the Core Data fault mechanism. Some old devices may have slow performance when I/O bottleneck. There are three potential profile chances for entities:
+- preload fulfill
+- layout in background
+- limit the data fetching
+
+## SwiftUI
+Some view models already migrate to `@Published` annotated output. It's future-proof support for SwiftUI. There are some views already transformed to `SwiftUI` likes `MastodonRegisterView` and `ReportReasonView`.
+
+# Take it apart
+## Targets
+The app builds with those targets:
+
+- Mastodon: the app itself
+- NotificationService: E2E push notification service
+- ShareActionExtension: iOS share action
+- MastodonIntent: Siri shortcuts
+
+## MastodonSDK
+There is a self-hosted Swift Package that contains the common libraries to build this app.
+
+- CoreDataStack: Core Data model definition and util methods
+- MastodonAsset: image and font assets
+- MastodonCommon: store App Group ID
+- MastodonCore: the logic for the app
+- MastodonExtension: system API extension utility
+- MastodonLocalization: i18n resources
+- MastodonSDK: Mastodon API client
+- MastodonUI: App UI components
+
+#### CoreDataStack
+App uses Core Data as the backend to persist all entitles from the server. So the app has the capability to keep the timeline and notifications. Another reason for using a database is it makes the app could respond to entity changes between different sources. For example, a user could skim in the home timeline and then interact with the same post on other pages with favorite or reblog action. Core Data will handle the property modifications and notify the home timeline to update the view.
+
+To simplify the database operations. There is only one persistent store for all accounts. We use `domain` to identify entity for different servers (a.k.a instance). Do not mix the `domain` with the Mastodon remote server name. For example. The domain is `mastodon.online` whereever the post (e.g. post come from `mstdn.jp`) and friends from for the account sign in `mastodon.online`. Also, do not only rely on `id` because it has conflict potential between different `domain`. The unique predicate is `domain` + `id`.
+
+The app use "One stack, two context" setup. There is one main managed object context for UI displaying and another background managed context for entities creating and updating. We assert the background context performs in a queue. Also, the app could accept mulitple background context model. Then the flag `-com.apple.CoreData.ConcurrencyDebug 1` will be usful.
+
+###### How to create a new Entity
+First, select the `CoreData.xcdatamodeld` file and in menu `Editor > Add Model Version…` to create a new version. Make sure active the new version in the inspect panel. e.g. `Model Version. Current > "Core Data 5"`
+
+Then use the `Add Entity` button create new Entity.
+1. Give a name in data model inspect panel.
+2. Also, set the `Module` to `CoreDataStack`.
+3. Set the `Codegen` to `Manual/None`. We use `Sourery` generates the template code.
+4. Create the `Entity.swift` file and declear the properties and relationships.
+
+###### How to add or remove property for Entity
+We using the Core Data lightweight migration. Please check the rules detail [here](https://developer.apple.com/documentation/coredata/using_lightweight_migration). And keep in mind that we using two-way relationship. And a relationship could be one-to-one, one-to-many/many-to-one.
+
+Tip:
+
+Please check the `Soucery` and use that generates getter and setter for properties and relationships. It's could save you time. To take the benefit from the dynamic property. We can declare a raw value property and then use compute property to construct the struct we want (e.g. `Feed.acct`). Or control the primitive by hand and declare the mirror type for this value (e.g `Status.attachments`).
+
+###### How to persist the Entity
+Please check the `Persistence+Status.swift`. We follow the pattern: migrate the old one if exists. Otherwise, create a new one. (Maybe some improvements could be adopted?)
+
+#### MastodonAsset
+Sourcery powered assets package.
+
+#### MastodonCommon
+Shared code for preference and configuration.
+
+### MastodonCore
+The core logic to drive the app.
+
+#### MastodonExtension
+Utility extension codes for SDK.
+
+#### MastodonLocalization
+Sourcery powered i18n package.
+
+#### MastodonSDK
+Mastodon API wrapper with Combine style API.
+
+#### MastodonUI
+Mastodon app UI components.
+
+## NotificationService
+Mastodon server accepts push notification register and we use the [toot-relay](https://github.com/DagAgren/toot-relay) to pass the server notifications to APNs. The message is E2E encrypted. The app will create an on-device private key for notification and save it into the keychain.
+
+When the push notification is incoming. iOS will spawn our NotificationService extension to handle the message. At that time the message is decrypted and displayed as a banner or in-app silent notification event when the app is in the foreground. All the notification count and deep-link logic are handled by the main app.
+
+## ShareActionExtension
+The iOS Share Extension allows users to share links or media from other apps. The app uses the same implementation for the main app and the share extension. Then different is less available memoery for extension so maybe some memory bounded task could crash the app. (Plesae file the issue)
+
+## MastodonIntent
+iOS Siri shortcut supports. It allows iOS directly publish posts via Shortcut without app launching.
diff --git a/Documentation/Setup.md b/Documentation/Setup.md
index ecb518a59..4da3c41ca 100644
--- a/Documentation/Setup.md
+++ b/Documentation/Setup.md
@@ -12,7 +12,7 @@ Install the latest version of Xcode from the App Store or Apple Developer Downlo
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 [Arkana](https://github.com/rogerluan/arkana). Ruby Gems are managed through Bundler. The M1 Mac needs virtual ruby env to workaround compatibility issues.
+The app use [CocoaPods]() and [Arkana](https://github.com/rogerluan/arkana). Ruby Gems are managed through Bundler. The M1 Mac needs virtual ruby env to workaround compatibility issues. Make sure you have [Rosetta](https://support.apple.com/en-us/HT211861) installed if you are using the M1 Mac.
#### Intel Mac
diff --git a/Gemfile.lock b/Gemfile.lock
index e0ed91c5b..15d02a8ec 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -100,7 +100,9 @@ GEM
PLATFORMS
arm64-darwin-21
+ arm64-darwin-22
x86_64-darwin-21
+ x86_64-darwin-22
DEPENDENCIES
arkana
diff --git a/Localization/Localizable.stringsdict b/Localization/Localizable.stringsdict
index 051bb50ef..f8964ca5d 100644
--- a/Localization/Localizable.stringsdict
+++ b/Localization/Localizable.stringsdict
@@ -13,15 +13,15 @@
NSStringFormatValueTypeKey
ld
zero
- no unread notification
+ no unread notifications
one
1 unread notification
few
%ld unread notifications
many
- %ld unread notification
+ %ld unread notifications
other
- %ld unread notification
+ %ld unread notifications
a11y.plural.count.input_limit_exceeds
@@ -68,6 +68,28 @@
%ld characters
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ no characters
+ one
+ 1 character
+ few
+ %ld characters
+ many
+ %ld characters
+ other
+ %ld characters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/Intents/input/cs.lproj/Intents.strings b/Localization/StringsConvertor/Intents/input/cs.lproj/Intents.strings
index 6877490ba..6f29830a1 100644
--- a/Localization/StringsConvertor/Intents/input/cs.lproj/Intents.strings
+++ b/Localization/StringsConvertor/Intents/input/cs.lproj/Intents.strings
@@ -1,51 +1,51 @@
-"16wxgf" = "Post on Mastodon";
+"16wxgf" = "Příspěvek na Mastodon";
-"751xkl" = "Text Content";
+"751xkl" = "Textový obsah";
-"CsR7G2" = "Post on Mastodon";
+"CsR7G2" = "Příspěvek na Mastodon";
-"HZSGTr" = "What content to post?";
+"HZSGTr" = "Jaký obsah se má přidat?";
-"HdGikU" = "Posting failed";
+"HdGikU" = "Odeslání se nezdařilo";
-"KDNTJ4" = "Failure Reason";
+"KDNTJ4" = "Důvod selhání";
-"RHxKOw" = "Send Post with text content";
+"RHxKOw" = "Odeslat příspěvek s textovým obsahem";
-"RxSqsb" = "Post";
+"RxSqsb" = "Příspěvek";
-"WCIR3D" = "Post ${content} on Mastodon";
+"WCIR3D" = "Zveřejnit ${content} na Mastodon";
-"ZKJSNu" = "Post";
+"ZKJSNu" = "Příspěvek";
"ZS1XaK" = "${content}";
-"ZbSjzC" = "Visibility";
+"ZbSjzC" = "Viditelnost";
-"Zo4jgJ" = "Post Visibility";
+"Zo4jgJ" = "Viditelnost příspěvku";
-"apSxMG-dYQ5NN" = "There are ${count} options matching ‘Public’.";
+"apSxMG-dYQ5NN" = "Existuje ${count} možností odpovídajících 'Veřejný'.";
-"apSxMG-ehFLjY" = "There are ${count} options matching ‘Followers Only’.";
+"apSxMG-ehFLjY" = "Existuje ${count} možností, které odpovídají „jen sledujícím“.";
-"ayoYEb-dYQ5NN" = "${content}, Public";
+"ayoYEb-dYQ5NN" = "${content}, veřejné";
-"ayoYEb-ehFLjY" = "${content}, Followers Only";
+"ayoYEb-ehFLjY" = "${content}, pouze sledující";
-"dUyuGg" = "Post on Mastodon";
+"dUyuGg" = "Příspěvek na Mastodon";
-"dYQ5NN" = "Public";
+"dYQ5NN" = "Veřejný";
-"ehFLjY" = "Followers Only";
+"ehFLjY" = "Pouze sledující";
-"gfePDu" = "Posting failed. ${failureReason}";
+"gfePDu" = "Odeslání se nezdařilo. ${failureReason}";
-"k7dbKQ" = "Post was sent successfully.";
+"k7dbKQ" = "Příspěvek byl úspěšně odeslán.";
-"oGiqmY-dYQ5NN" = "Just to confirm, you wanted ‘Public’?";
+"oGiqmY-dYQ5NN" = "Jen pro kontrolu, chtěli jste „Veřejný“?";
-"oGiqmY-ehFLjY" = "Just to confirm, you wanted ‘Followers Only’?";
+"oGiqmY-ehFLjY" = "Jen pro kontrolu, chtěli jste „Pouze sledující“?";
"rM6dvp" = "URL";
-"ryJLwG" = "Post was sent successfully. ";
+"ryJLwG" = "Příspěvek byl úspěšně odeslán. ";
diff --git a/Localization/StringsConvertor/Intents/input/cs.lproj/Intents.stringsdict b/Localization/StringsConvertor/Intents/input/cs.lproj/Intents.stringsdict
index a739f778f..deea8db12 100644
--- a/Localization/StringsConvertor/Intents/input/cs.lproj/Intents.stringsdict
+++ b/Localization/StringsConvertor/Intents/input/cs.lproj/Intents.stringsdict
@@ -5,7 +5,7 @@
There are ${count} options matching ‘${content}’. - 2
NSStringLocalizedFormatKey
- There are %#@count_option@ matching ‘${content}’.
+ Existuje %#@count_option@ odpovídající „${content}“.
count_option
NSStringFormatSpecTypeKey
diff --git a/Localization/StringsConvertor/Intents/input/is.lproj/Intents.strings b/Localization/StringsConvertor/Intents/input/is.lproj/Intents.strings
new file mode 100644
index 000000000..196c33e70
--- /dev/null
+++ b/Localization/StringsConvertor/Intents/input/is.lproj/Intents.strings
@@ -0,0 +1,51 @@
+"16wxgf" = "Birta á Mastodon";
+
+"751xkl" = "Efni texta";
+
+"CsR7G2" = "Birta á Mastodon";
+
+"HZSGTr" = "Hvaða efni á að birta?";
+
+"HdGikU" = "Birting færslu mistókst";
+
+"KDNTJ4" = "Ástæða bilunar";
+
+"RHxKOw" = "Senda færslu með textaefni";
+
+"RxSqsb" = "Færsla";
+
+"WCIR3D" = "Birta ${content} á Mastodon";
+
+"ZKJSNu" = "Færsla";
+
+"ZS1XaK" = "${content}";
+
+"ZbSjzC" = "Sýnileiki";
+
+"Zo4jgJ" = "Sýnileiki færslu";
+
+"apSxMG-dYQ5NN" = "Það eru ${count} valkostir sem samsvara ‘Opinbert’.";
+
+"apSxMG-ehFLjY" = "Það eru ${count} valkostir sem samsvara ‘Einungis fylgjendur’.";
+
+"ayoYEb-dYQ5NN" = "${content}, opinbert";
+
+"ayoYEb-ehFLjY" = "${content}, einungis fylgjendur";
+
+"dUyuGg" = "Birta á Mastodon";
+
+"dYQ5NN" = "Opinbert";
+
+"ehFLjY" = "Einungis fylgjendur";
+
+"gfePDu" = "Birting færslu mistókst. ${failureReason}";
+
+"k7dbKQ" = "Það tókst að senda færsluna.";
+
+"oGiqmY-dYQ5NN" = "Bara til að staðfesta, þú vildir 'Opinbert'?";
+
+"oGiqmY-ehFLjY" = "Bara til að staðfesta, þú vildir ''Einungis fylgjendur'?";
+
+"rM6dvp" = "URL-slóð";
+
+"ryJLwG" = "Það tókst að senda færsluna. ";
diff --git a/Localization/StringsConvertor/Intents/input/is.lproj/Intents.stringsdict b/Localization/StringsConvertor/Intents/input/is.lproj/Intents.stringsdict
new file mode 100644
index 000000000..fe12c972a
--- /dev/null
+++ b/Localization/StringsConvertor/Intents/input/is.lproj/Intents.stringsdict
@@ -0,0 +1,38 @@
+
+
+
+
+ There are ${count} options matching ‘${content}’. - 2
+
+ NSStringLocalizedFormatKey
+ Það eru %#@count_option@ sem samsvara ‘${content}’.
+ count_option
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ %ld
+ one
+ 1 valkostur
+ other
+ %ld valkostir
+
+
+ There are ${count} options matching ‘${visibility}’.
+
+ NSStringLocalizedFormatKey
+ Það eru %#@count_option@ sem samsvara ‘${visibility}’.
+ count_option
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ %ld
+ one
+ 1 valkostur
+ other
+ %ld valkostir
+
+
+
+
diff --git a/Localization/StringsConvertor/Intents/input/kab.lproj/Intents.stringsdict b/Localization/StringsConvertor/Intents/input/kab.lproj/Intents.stringsdict
index a8aeeaaf1..6cead6f34 100644
--- a/Localization/StringsConvertor/Intents/input/kab.lproj/Intents.stringsdict
+++ b/Localization/StringsConvertor/Intents/input/kab.lproj/Intents.stringsdict
@@ -29,9 +29,9 @@
NSStringFormatValueTypeKey
%ld
one
- 1 uɣewwaṛ
+ %ld n uɣewwaṛ
other
- %ld iɣewwaṛen
+ %ld n iɣewwaṛen
diff --git a/Localization/StringsConvertor/Intents/input/pt-BR.lproj/Intents.strings b/Localization/StringsConvertor/Intents/input/pt-BR.lproj/Intents.strings
index 6877490ba..3e6806953 100644
--- a/Localization/StringsConvertor/Intents/input/pt-BR.lproj/Intents.strings
+++ b/Localization/StringsConvertor/Intents/input/pt-BR.lproj/Intents.strings
@@ -1,51 +1,51 @@
-"16wxgf" = "Post on Mastodon";
+"16wxgf" = "Postar no Mastodon";
-"751xkl" = "Text Content";
+"751xkl" = "Conteúdo do texto";
-"CsR7G2" = "Post on Mastodon";
+"CsR7G2" = "Postar no Mastodon";
-"HZSGTr" = "What content to post?";
+"HZSGTr" = "Qual conteúdo a publicar?";
-"HdGikU" = "Posting failed";
+"HdGikU" = "Falha na publicação";
-"KDNTJ4" = "Failure Reason";
+"KDNTJ4" = "Motivo da falha";
-"RHxKOw" = "Send Post with text content";
+"RHxKOw" = "Enviar postagem com conteúdo de texto";
-"RxSqsb" = "Post";
+"RxSqsb" = "Postagem";
-"WCIR3D" = "Post ${content} on Mastodon";
+"WCIR3D" = "Postar ${content} no Mastodon";
-"ZKJSNu" = "Post";
+"ZKJSNu" = "Postar";
"ZS1XaK" = "${content}";
-"ZbSjzC" = "Visibility";
+"ZbSjzC" = "Visibilidade";
-"Zo4jgJ" = "Post Visibility";
+"Zo4jgJ" = "Visibilidade da publicação";
-"apSxMG-dYQ5NN" = "There are ${count} options matching ‘Public’.";
+"apSxMG-dYQ5NN" = "Existem ${count} opções correspondentes a ‘Público’.";
-"apSxMG-ehFLjY" = "There are ${count} options matching ‘Followers Only’.";
+"apSxMG-ehFLjY" = "Existem ${count} opções correspondentes a ‘Apenas para seguidores’.";
-"ayoYEb-dYQ5NN" = "${content}, Public";
+"ayoYEb-dYQ5NN" = "${content}, Público";
-"ayoYEb-ehFLjY" = "${content}, Followers Only";
+"ayoYEb-ehFLjY" = "${content}, Apenas para seguidores";
-"dUyuGg" = "Post on Mastodon";
+"dUyuGg" = "Postar no Mastodon";
-"dYQ5NN" = "Public";
+"dYQ5NN" = "Público";
-"ehFLjY" = "Followers Only";
+"ehFLjY" = "Apenas para seguidores";
-"gfePDu" = "Posting failed. ${failureReason}";
+"gfePDu" = "Falha na publicação. ${failureReason}";
-"k7dbKQ" = "Post was sent successfully.";
+"k7dbKQ" = "Publicação enviada com sucesso.";
-"oGiqmY-dYQ5NN" = "Just to confirm, you wanted ‘Public’?";
+"oGiqmY-dYQ5NN" = "Só para confirmar, você queria ‘Público’?";
-"oGiqmY-ehFLjY" = "Just to confirm, you wanted ‘Followers Only’?";
+"oGiqmY-ehFLjY" = "Só para confirmar, você queria ‘Apenas para seguidores’?";
"rM6dvp" = "URL";
-"ryJLwG" = "Post was sent successfully. ";
+"ryJLwG" = "Publicação enviada com sucesso. ";
diff --git a/Localization/StringsConvertor/Intents/input/pt-BR.lproj/Intents.stringsdict b/Localization/StringsConvertor/Intents/input/pt-BR.lproj/Intents.stringsdict
index 18422c772..a48559b4a 100644
--- a/Localization/StringsConvertor/Intents/input/pt-BR.lproj/Intents.stringsdict
+++ b/Localization/StringsConvertor/Intents/input/pt-BR.lproj/Intents.stringsdict
@@ -5,7 +5,7 @@
There are ${count} options matching ‘${content}’. - 2
NSStringLocalizedFormatKey
- There are %#@count_option@ matching ‘${content}’.
+ Existem %#@count_option@ opções correspondentes a ‘${content}’.
count_option
NSStringFormatSpecTypeKey
@@ -13,15 +13,15 @@
NSStringFormatValueTypeKey
%ld
one
- 1 option
+ 1 opção
other
- %ld options
+ %ld opções
There are ${count} options matching ‘${visibility}’.
NSStringLocalizedFormatKey
- There are %#@count_option@ matching ‘${visibility}’.
+ Existem %#@count_option@ opções correspondentes a ‘${visibility}’.
count_option
NSStringFormatSpecTypeKey
@@ -29,9 +29,9 @@
NSStringFormatValueTypeKey
%ld
one
- 1 option
+ 1 opção
other
- %ld options
+ %ld opções
diff --git a/Localization/StringsConvertor/Sources/StringsConvertor/main.swift b/Localization/StringsConvertor/Sources/StringsConvertor/main.swift
index 76dc722f6..665161e2c 100644
--- a/Localization/StringsConvertor/Sources/StringsConvertor/main.swift
+++ b/Localization/StringsConvertor/Sources/StringsConvertor/main.swift
@@ -47,11 +47,13 @@ private func convert(from inputDirectoryURL: URL, to outputDirectory: URL) {
private func map(language: String) -> String? {
switch language {
+ case "Base.lproj": return "Base"
case "ar.lproj": return "ar" // Arabic
case "eu.lproj": return "eu" // Basque
case "ca.lproj": return "ca" // Catalan
case "zh-Hans.lproj": return "zh-Hans" // Chinese Simplified
case "zh-Hant.lproj": return "zh-Hant" // Chinese Traditional
+ case "cs.lproj": return "cs" // Czech
case "nl.lproj": return "nl" // Dutch
case "en.lproj": return "en"
case "fi.lproj": return "fi" // Finnish
@@ -64,6 +66,7 @@ private func map(language: String) -> String? {
case "kmr.lproj": return "ku" // Kurmanji (Kurdish) [intent mapping]
case "ru.lproj": return "ru" // Russian
case "gd.lproj": return "gd" // Scottish Gaelic
+ case "sl.lproj": return "sl" // Slovenian
case "ckb.lproj": return "ckb" // Sorani (Kurdish)
case "es.lproj": return "es" // Spanish
case "es_AR.lproj": return "es-AR" // Spanish, Argentina
diff --git a/Localization/StringsConvertor/input/Base.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/Base.lproj/Localizable.stringsdict
new file mode 100644
index 000000000..f8964ca5d
--- /dev/null
+++ b/Localization/StringsConvertor/input/Base.lproj/Localizable.stringsdict
@@ -0,0 +1,631 @@
+
+
+
+
+ a11y.plural.count.unread.notification
+
+ NSStringLocalizedFormatKey
+ %#@notification_count_unread_notification@
+ notification_count_unread_notification
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ no unread notifications
+ one
+ 1 unread notification
+ few
+ %ld unread notifications
+ many
+ %ld unread notifications
+ other
+ %ld unread notifications
+
+
+ a11y.plural.count.input_limit_exceeds
+
+ NSStringLocalizedFormatKey
+ Input limit exceeds %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 characters
+ one
+ 1 character
+ few
+ %ld characters
+ many
+ %ld characters
+ other
+ %ld characters
+
+
+ a11y.plural.count.input_limit_remains
+
+ NSStringLocalizedFormatKey
+ Input limit remains %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 characters
+ one
+ 1 character
+ few
+ %ld characters
+ many
+ %ld characters
+ other
+ %ld characters
+
+
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ no characters
+ one
+ 1 character
+ few
+ %ld characters
+ many
+ %ld characters
+ other
+ %ld characters
+
+
+ plural.count.followed_by_and_mutual
+
+ NSStringLocalizedFormatKey
+ %#@names@%#@count_mutual@
+ names
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+
+
+ count_mutual
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ Followed by %1$@
+ one
+ Followed by %1$@, and another mutual
+ few
+ Followed by %1$@, and %ld mutuals
+ many
+ Followed by %1$@, and %ld mutuals
+ other
+ Followed by %1$@, and %ld mutuals
+
+
+ plural.count.metric_formatted.post
+
+ NSStringLocalizedFormatKey
+ %@ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ posts
+ one
+ post
+ few
+ posts
+ many
+ posts
+ other
+ posts
+
+
+ plural.count.media
+
+ NSStringLocalizedFormatKey
+ %#@media_count@
+ media_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 media
+ one
+ 1 media
+ few
+ %ld media
+ many
+ %ld media
+ other
+ %ld media
+
+
+ plural.count.post
+
+ NSStringLocalizedFormatKey
+ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 posts
+ one
+ 1 post
+ few
+ %ld posts
+ many
+ %ld posts
+ other
+ %ld posts
+
+
+ plural.count.favorite
+
+ NSStringLocalizedFormatKey
+ %#@favorite_count@
+ favorite_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 favorites
+ one
+ 1 favorite
+ few
+ %ld favorites
+ many
+ %ld favorites
+ other
+ %ld favorites
+
+
+ plural.count.reblog
+
+ NSStringLocalizedFormatKey
+ %#@reblog_count@
+ reblog_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 reblogs
+ one
+ 1 reblog
+ few
+ %ld reblogs
+ many
+ %ld reblogs
+ other
+ %ld reblogs
+
+
+ plural.count.reply
+
+ NSStringLocalizedFormatKey
+ %#@reply_count@
+ reply_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 replies
+ one
+ 1 reply
+ few
+ %ld replies
+ many
+ %ld replies
+ other
+ %ld replies
+
+
+ plural.count.vote
+
+ NSStringLocalizedFormatKey
+ %#@vote_count@
+ vote_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 votes
+ one
+ 1 vote
+ few
+ %ld votes
+ many
+ %ld votes
+ other
+ %ld votes
+
+
+ plural.count.voter
+
+ NSStringLocalizedFormatKey
+ %#@voter_count@
+ voter_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 voters
+ one
+ 1 voter
+ few
+ %ld voters
+ many
+ %ld voters
+ other
+ %ld voters
+
+
+ plural.people_talking
+
+ NSStringLocalizedFormatKey
+ %#@count_people_talking@
+ count_people_talking
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 people talking
+ one
+ 1 people talking
+ few
+ %ld people talking
+ many
+ %ld people talking
+ other
+ %ld people talking
+
+
+ plural.count.following
+
+ NSStringLocalizedFormatKey
+ %#@count_following@
+ count_following
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 following
+ one
+ 1 following
+ few
+ %ld following
+ many
+ %ld following
+ other
+ %ld following
+
+
+ plural.count.follower
+
+ NSStringLocalizedFormatKey
+ %#@count_follower@
+ count_follower
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 followers
+ one
+ 1 follower
+ few
+ %ld followers
+ many
+ %ld followers
+ other
+ %ld followers
+
+
+ date.year.left
+
+ NSStringLocalizedFormatKey
+ %#@count_year_left@
+ count_year_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 years left
+ one
+ 1 year left
+ few
+ %ld years left
+ many
+ %ld years left
+ other
+ %ld years left
+
+
+ date.month.left
+
+ NSStringLocalizedFormatKey
+ %#@count_month_left@
+ count_month_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 months left
+ one
+ 1 months left
+ few
+ %ld months left
+ many
+ %ld months left
+ other
+ %ld months left
+
+
+ date.day.left
+
+ NSStringLocalizedFormatKey
+ %#@count_day_left@
+ count_day_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 days left
+ one
+ 1 day left
+ few
+ %ld days left
+ many
+ %ld days left
+ other
+ %ld days left
+
+
+ date.hour.left
+
+ NSStringLocalizedFormatKey
+ %#@count_hour_left@
+ count_hour_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 hours left
+ one
+ 1 hour left
+ few
+ %ld hours left
+ many
+ %ld hours left
+ other
+ %ld hours left
+
+
+ date.minute.left
+
+ NSStringLocalizedFormatKey
+ %#@count_minute_left@
+ count_minute_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 minutes left
+ one
+ 1 minute left
+ few
+ %ld minutes left
+ many
+ %ld minutes left
+ other
+ %ld minutes left
+
+
+ date.second.left
+
+ NSStringLocalizedFormatKey
+ %#@count_second_left@
+ count_second_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0 seconds left
+ one
+ 1 second left
+ few
+ %ld seconds left
+ many
+ %ld seconds left
+ other
+ %ld seconds left
+
+
+ date.year.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_year_ago_abbr@
+ count_year_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0y ago
+ one
+ 1y ago
+ few
+ %ldy ago
+ many
+ %ldy ago
+ other
+ %ldy ago
+
+
+ date.month.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_month_ago_abbr@
+ count_month_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0M ago
+ one
+ 1M ago
+ few
+ %ldM ago
+ many
+ %ldM ago
+ other
+ %ldM ago
+
+
+ date.day.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_day_ago_abbr@
+ count_day_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0d ago
+ one
+ 1d ago
+ few
+ %ldd ago
+ many
+ %ldd ago
+ other
+ %ldd ago
+
+
+ date.hour.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_hour_ago_abbr@
+ count_hour_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0h ago
+ one
+ 1h ago
+ few
+ %ldh ago
+ many
+ %ldh ago
+ other
+ %ldh ago
+
+
+ date.minute.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_minute_ago_abbr@
+ count_minute_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0m ago
+ one
+ 1m ago
+ few
+ %ldm ago
+ many
+ %ldm ago
+ other
+ %ldm ago
+
+
+ date.second.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_second_ago_abbr@
+ count_second_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ 0s ago
+ one
+ 1s ago
+ few
+ %lds ago
+ many
+ %lds ago
+ other
+ %lds ago
+
+
+
+
diff --git a/Localization/StringsConvertor/input/Base.lproj/app.json b/Localization/StringsConvertor/input/Base.lproj/app.json
new file mode 100644
index 000000000..ea046bfbc
--- /dev/null
+++ b/Localization/StringsConvertor/input/Base.lproj/app.json
@@ -0,0 +1,727 @@
+{
+ "common": {
+ "alerts": {
+ "common": {
+ "please_try_again": "Please try again.",
+ "please_try_again_later": "Please try again later."
+ },
+ "sign_up_failure": {
+ "title": "Sign Up Failure"
+ },
+ "server_error": {
+ "title": "Server Error"
+ },
+ "vote_failure": {
+ "title": "Vote Failure",
+ "poll_ended": "The poll has ended"
+ },
+ "discard_post_content": {
+ "title": "Discard Draft",
+ "message": "Confirm to discard composed post content."
+ },
+ "publish_post_failure": {
+ "title": "Publish Failure",
+ "message": "Failed to publish the post.\nPlease check your internet connection.",
+ "attachments_message": {
+ "video_attach_with_photo": "Cannot attach a video to a post that already contains images.",
+ "more_than_one_video": "Cannot attach more than one video."
+ }
+ },
+ "edit_profile_failure": {
+ "title": "Edit Profile Error",
+ "message": "Cannot edit profile. Please try again."
+ },
+ "sign_out": {
+ "title": "Sign Out",
+ "message": "Are you sure you want to sign out?",
+ "confirm": "Sign Out"
+ },
+ "block_domain": {
+ "title": "Are you really, really sure you want to block the entire %s? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain and any of your followers from that domain will be removed.",
+ "block_entire_domain": "Block Domain"
+ },
+ "save_photo_failure": {
+ "title": "Save Photo Failure",
+ "message": "Please enable the photo library access permission to save the photo."
+ },
+ "delete_post": {
+ "title": "Delete Post",
+ "message": "Are you sure you want to delete this post?"
+ },
+ "clean_cache": {
+ "title": "Clean Cache",
+ "message": "Successfully cleaned %s cache."
+ }
+ },
+ "controls": {
+ "actions": {
+ "back": "Back",
+ "next": "Next",
+ "previous": "Previous",
+ "open": "Open",
+ "add": "Add",
+ "remove": "Remove",
+ "edit": "Edit",
+ "save": "Save",
+ "ok": "OK",
+ "done": "Done",
+ "confirm": "Confirm",
+ "continue": "Continue",
+ "compose": "Compose",
+ "cancel": "Cancel",
+ "discard": "Discard",
+ "try_again": "Try Again",
+ "take_photo": "Take Photo",
+ "save_photo": "Save Photo",
+ "copy_photo": "Copy Photo",
+ "sign_in": "Log in",
+ "sign_up": "Create account",
+ "see_more": "See More",
+ "preview": "Preview",
+ "share": "Share",
+ "share_user": "Share %s",
+ "share_post": "Share Post",
+ "open_in_safari": "Open in Safari",
+ "open_in_browser": "Open in Browser",
+ "find_people": "Find people to follow",
+ "manually_search": "Manually search instead",
+ "skip": "Skip",
+ "reply": "Reply",
+ "report_user": "Report %s",
+ "block_domain": "Block %s",
+ "unblock_domain": "Unblock %s",
+ "settings": "Settings",
+ "delete": "Delete"
+ },
+ "tabs": {
+ "home": "Home",
+ "search": "Search",
+ "notifications": "Notifications",
+ "profile": "Profile"
+ },
+ "keyboard": {
+ "common": {
+ "switch_to_tab": "Switch to %s",
+ "compose_new_post": "Compose New Post",
+ "show_favorites": "Show Favorites",
+ "open_settings": "Open Settings"
+ },
+ "timeline": {
+ "previous_status": "Previous Post",
+ "next_status": "Next Post",
+ "open_status": "Open Post",
+ "open_author_profile": "Open Author's Profile",
+ "open_reblogger_profile": "Open Reblogger's Profile",
+ "reply_status": "Reply to Post",
+ "toggle_reblog": "Toggle Reblog on Post",
+ "toggle_favorite": "Toggle Favorite on Post",
+ "toggle_content_warning": "Toggle Content Warning",
+ "preview_image": "Preview Image"
+ },
+ "segmented_control": {
+ "previous_section": "Previous Section",
+ "next_section": "Next Section"
+ }
+ },
+ "status": {
+ "user_reblogged": "%s reblogged",
+ "user_replied_to": "Replied to %s",
+ "show_post": "Show Post",
+ "show_user_profile": "Show user profile",
+ "content_warning": "Content Warning",
+ "sensitive_content": "Sensitive Content",
+ "media_content_warning": "Tap anywhere to reveal",
+ "tap_to_reveal": "Tap to reveal",
+ "poll": {
+ "vote": "Vote",
+ "closed": "Closed"
+ },
+ "meta_entity": {
+ "url": "Link: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Show Profile: %s",
+ "email": "Email address: %s"
+ },
+ "actions": {
+ "reply": "Reply",
+ "reblog": "Reblog",
+ "unreblog": "Undo reblog",
+ "favorite": "Favorite",
+ "unfavorite": "Unfavorite",
+ "menu": "Menu",
+ "hide": "Hide",
+ "show_image": "Show image",
+ "show_gif": "Show GIF",
+ "show_video_player": "Show video player",
+ "tap_then_hold_to_show_menu": "Tap then hold to show menu"
+ },
+ "tag": {
+ "url": "URL",
+ "mention": "Mention",
+ "link": "Link",
+ "hashtag": "Hashtag",
+ "email": "Email",
+ "emoji": "Emoji"
+ },
+ "visibility": {
+ "unlisted": "Everyone can see this post but not display in the public timeline.",
+ "private": "Only their followers can see this post.",
+ "private_from_me": "Only my followers can see this post.",
+ "direct": "Only mentioned user can see this post."
+ }
+ },
+ "friendship": {
+ "follow": "Follow",
+ "following": "Following",
+ "request": "Request",
+ "pending": "Pending",
+ "block": "Block",
+ "block_user": "Block %s",
+ "block_domain": "Block %s",
+ "unblock": "Unblock",
+ "unblock_user": "Unblock %s",
+ "blocked": "Blocked",
+ "mute": "Mute",
+ "mute_user": "Mute %s",
+ "unmute": "Unmute",
+ "unmute_user": "Unmute %s",
+ "muted": "Muted",
+ "edit_info": "Edit Info",
+ "show_reblogs": "Show Reblogs",
+ "hide_reblogs": "Hide Reblogs"
+ },
+ "timeline": {
+ "filtered": "Filtered",
+ "timestamp": {
+ "now": "Now"
+ },
+ "loader": {
+ "load_missing_posts": "Load missing posts",
+ "loading_missing_posts": "Loading missing posts...",
+ "show_more_replies": "Show more replies"
+ },
+ "header": {
+ "no_status_found": "No Post Found",
+ "blocking_warning": "You can’t view this user's profile\nuntil you unblock them.\nYour profile looks like this to them.",
+ "user_blocking_warning": "You can’t view %s’s profile\nuntil you unblock them.\nYour profile looks like this to them.",
+ "blocked_warning": "You can’t view this user’s profile\nuntil they unblock you.",
+ "user_blocked_warning": "You can’t view %s’s profile\nuntil they unblock you.",
+ "suspended_warning": "This user has been suspended.",
+ "user_suspended_warning": "%s’s account has been suspended."
+ }
+ }
+ }
+ },
+ "scene": {
+ "welcome": {
+ "slogan": "Social networking\nback in your hands.",
+ "get_started": "Get Started",
+ "log_in": "Log In"
+ },
+ "login": {
+ "title": "Welcome back",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Enter URL or search for your server"
+ }
+ },
+ "server_picker": {
+ "title": "Mastodon is made of users in different servers.",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
+ "button": {
+ "category": {
+ "all": "All",
+ "all_accessiblity_description": "Category: All",
+ "academia": "academia",
+ "activism": "activism",
+ "food": "food",
+ "furry": "furry",
+ "games": "games",
+ "general": "general",
+ "journalism": "journalism",
+ "lgbt": "lgbt",
+ "regional": "regional",
+ "art": "art",
+ "music": "music",
+ "tech": "tech"
+ },
+ "see_less": "See Less",
+ "see_more": "See More"
+ },
+ "label": {
+ "language": "LANGUAGE",
+ "users": "USERS",
+ "category": "CATEGORY"
+ },
+ "input": {
+ "search_servers_or_enter_url": "Search communities or enter URL"
+ },
+ "empty_state": {
+ "finding_servers": "Finding available servers...",
+ "bad_network": "Something went wrong while loading the data. Check your internet connection.",
+ "no_results": "No results"
+ }
+ },
+ "register": {
+ "title": "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"
+ },
+ "username": {
+ "placeholder": "username",
+ "duplicate_prompt": "This username is taken."
+ },
+ "display_name": {
+ "placeholder": "display name"
+ },
+ "email": {
+ "placeholder": "email"
+ },
+ "password": {
+ "placeholder": "password",
+ "require": "Your password needs at least:",
+ "character_limit": "8 characters",
+ "accessibility": {
+ "checked": "checked",
+ "unchecked": "unchecked"
+ },
+ "hint": "Your password needs at least eight characters"
+ },
+ "invite": {
+ "registration_user_invite_request": "Why do you want to join?"
+ }
+ },
+ "error": {
+ "item": {
+ "username": "Username",
+ "email": "Email",
+ "password": "Password",
+ "agreement": "Agreement",
+ "locale": "Locale",
+ "reason": "Reason"
+ },
+ "reason": {
+ "blocked": "%s contains a disallowed email provider",
+ "unreachable": "%s does not seem to exist",
+ "taken": "%s is already in use",
+ "reserved": "%s is a reserved keyword",
+ "accepted": "%s must be accepted",
+ "blank": "%s is required",
+ "invalid": "%s is invalid",
+ "too_long": "%s is too long",
+ "too_short": "%s is too short",
+ "inclusion": "%s is not a supported value"
+ },
+ "special": {
+ "username_invalid": "Username must only contain alphanumeric characters and underscores",
+ "username_too_long": "Username is too long (can’t be longer than 30 characters)",
+ "email_invalid": "This is not a valid email address",
+ "password_too_short": "Password is too short (must be at least 8 characters)"
+ }
+ }
+ },
+ "server_rules": {
+ "title": "Some ground rules.",
+ "subtitle": "These are set and enforced by the %s moderators.",
+ "prompt": "By continuing, you’re subject to the terms of service and privacy policy for %s.",
+ "terms_of_service": "terms of service",
+ "privacy_policy": "privacy policy",
+ "button": {
+ "confirm": "I Agree"
+ }
+ },
+ "confirm_email": {
+ "title": "One last thing.",
+ "subtitle": "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"
+ },
+ "dont_receive_email": {
+ "title": "Check your email",
+ "description": "Check if your email address is correct as well as your junk folder if you haven’t.",
+ "resend_email": "Resend Email"
+ },
+ "open_email_app": {
+ "title": "Check your inbox.",
+ "description": "We just sent you an email. Check your junk folder if you haven’t.",
+ "mail": "Mail",
+ "open_email_client": "Open Email Client"
+ }
+ },
+ "home_timeline": {
+ "title": "Home",
+ "navigation_bar_state": {
+ "offline": "Offline",
+ "new_posts": "See new posts",
+ "published": "Published!",
+ "Publishing": "Publishing post...",
+ "accessibility": {
+ "logo_label": "Logo Button",
+ "logo_hint": "Tap to scroll to top and tap again to previous location"
+ }
+ }
+ },
+ "suggestion_account": {
+ "title": "Find People to Follow",
+ "follow_explain": "When you follow someone, you’ll see their posts in your home feed."
+ },
+ "compose": {
+ "title": {
+ "new_post": "New Post",
+ "new_reply": "New Reply"
+ },
+ "media_selection": {
+ "camera": "Take Photo",
+ "photo_library": "Photo Library",
+ "browse": "Browse"
+ },
+ "content_input_placeholder": "Type or paste what’s on your mind",
+ "compose_action": "Publish",
+ "replying_to_user": "replying to %s",
+ "attachment": {
+ "photo": "photo",
+ "video": "video",
+ "attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.",
+ "description_photo": "Describe the photo for the visually-impaired...",
+ "description_video": "Describe the video for the visually-impaired...",
+ "load_failed": "Load Failed",
+ "upload_failed": "Upload Failed",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "Attachment too large",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
+ },
+ "poll": {
+ "duration_time": "Duration: %s",
+ "thirty_minutes": "30 minutes",
+ "one_hour": "1 Hour",
+ "six_hours": "6 Hours",
+ "one_day": "1 Day",
+ "three_days": "3 Days",
+ "seven_days": "7 Days",
+ "option_number": "Option %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
+ },
+ "content_warning": {
+ "placeholder": "Write an accurate warning here..."
+ },
+ "visibility": {
+ "public": "Public",
+ "unlisted": "Unlisted",
+ "private": "Followers only",
+ "direct": "Only people I mention"
+ },
+ "auto_complete": {
+ "space_to_add": "Space to add"
+ },
+ "accessibility": {
+ "append_attachment": "Add Attachment",
+ "append_poll": "Add Poll",
+ "remove_poll": "Remove Poll",
+ "custom_emoji_picker": "Custom Emoji Picker",
+ "enable_content_warning": "Enable Content Warning",
+ "disable_content_warning": "Disable Content Warning",
+ "post_visibility_menu": "Post Visibility Menu",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
+ },
+ "keyboard": {
+ "discard_post": "Discard Post",
+ "publish_post": "Publish Post",
+ "toggle_poll": "Toggle Poll",
+ "toggle_content_warning": "Toggle Content Warning",
+ "append_attachment_entry": "Add Attachment - %s",
+ "select_visibility_entry": "Select Visibility - %s"
+ }
+ },
+ "profile": {
+ "header": {
+ "follows_you": "Follows You"
+ },
+ "dashboard": {
+ "posts": "posts",
+ "following": "following",
+ "followers": "followers"
+ },
+ "fields": {
+ "add_row": "Add Row",
+ "placeholder": {
+ "label": "Label",
+ "content": "Content"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
+ }
+ },
+ "segmented_control": {
+ "posts": "Posts",
+ "replies": "Replies",
+ "posts_and_replies": "Posts and Replies",
+ "media": "Media",
+ "about": "About"
+ },
+ "relationship_action_alert": {
+ "confirm_mute_user": {
+ "title": "Mute Account",
+ "message": "Confirm to mute %s"
+ },
+ "confirm_unmute_user": {
+ "title": "Unmute Account",
+ "message": "Confirm to unmute %s"
+ },
+ "confirm_block_user": {
+ "title": "Block Account",
+ "message": "Confirm to block %s"
+ },
+ "confirm_unblock_user": {
+ "title": "Unblock Account",
+ "message": "Confirm to unblock %s"
+ },
+ "confirm_show_reblogs": {
+ "title": "Show Reblogs",
+ "message": "Confirm to show reblogs"
+ },
+ "confirm_hide_reblogs": {
+ "title": "Hide Reblogs",
+ "message": "Confirm to hide reblogs"
+ }
+ },
+ "accessibility": {
+ "show_avatar_image": "Show avatar image",
+ "edit_avatar_image": "Edit avatar image",
+ "show_banner_image": "Show banner image",
+ "double_tap_to_open_the_list": "Double tap to open the list"
+ }
+ },
+ "follower": {
+ "title": "follower",
+ "footer": "Followers from other servers are not displayed."
+ },
+ "following": {
+ "title": "following",
+ "footer": "Follows from other servers are not displayed."
+ },
+ "familiarFollowers": {
+ "title": "Followers you familiar",
+ "followed_by_names": "Followed by %s"
+ },
+ "favorited_by": {
+ "title": "Favorited By"
+ },
+ "reblogged_by": {
+ "title": "Reblogged By"
+ },
+ "search": {
+ "title": "Search",
+ "search_bar": {
+ "placeholder": "Search hashtags and users",
+ "cancel": "Cancel"
+ },
+ "recommend": {
+ "button_text": "See All",
+ "hash_tag": {
+ "title": "Trending on Mastodon",
+ "description": "Hashtags that are getting quite a bit of attention",
+ "people_talking": "%s people are talking"
+ },
+ "accounts": {
+ "title": "Accounts you might like",
+ "description": "You may like to follow these accounts",
+ "follow": "Follow"
+ }
+ },
+ "searching": {
+ "segment": {
+ "all": "All",
+ "people": "People",
+ "hashtags": "Hashtags",
+ "posts": "Posts"
+ },
+ "empty_state": {
+ "no_results": "No results"
+ },
+ "recent_search": "Recent searches",
+ "clear": "Clear"
+ }
+ },
+ "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"
+ },
+ "notification": {
+ "title": {
+ "Everything": "Everything",
+ "Mentions": "Mentions"
+ },
+ "notification_description": {
+ "followed_you": "followed you",
+ "favorited_your_post": "favorited your post",
+ "reblogged_your_post": "reblogged your post",
+ "mentioned_you": "mentioned you",
+ "request_to_follow_you": "request to follow you",
+ "poll_has_ended": "poll has ended"
+ },
+ "keyobard": {
+ "show_everything": "Show Everything",
+ "show_mentions": "Show Mentions"
+ },
+ "follow_request": {
+ "accept": "Accept",
+ "accepted": "Accepted",
+ "reject": "reject",
+ "rejected": "Rejected"
+ }
+ },
+ "thread": {
+ "back_title": "Post",
+ "title": "Post from %s"
+ },
+ "settings": {
+ "title": "Settings",
+ "section": {
+ "appearance": {
+ "title": "Appearance",
+ "automatic": "Automatic",
+ "light": "Always Light",
+ "dark": "Always Dark"
+ },
+ "look_and_feel": {
+ "title": "Look and Feel",
+ "use_system": "Use System",
+ "really_dark": "Really Dark",
+ "sorta_dark": "Sorta Dark",
+ "light": "Light"
+ },
+ "notifications": {
+ "title": "Notifications",
+ "favorites": "Favorites my post",
+ "follows": "Follows me",
+ "boosts": "Reblogs my post",
+ "mentions": "Mentions me",
+ "trigger": {
+ "anyone": "anyone",
+ "follower": "a follower",
+ "follow": "anyone I follow",
+ "noone": "no one",
+ "title": "Notify me when"
+ }
+ },
+ "preference": {
+ "title": "Preferences",
+ "true_black_dark_mode": "True black dark mode",
+ "disable_avatar_animation": "Disable animated avatars",
+ "disable_emoji_animation": "Disable animated emojis",
+ "using_default_browser": "Use default browser to open links",
+ "open_links_in_mastodon": "Open links in Mastodon"
+ },
+ "boring_zone": {
+ "title": "The Boring Zone",
+ "account_settings": "Account Settings",
+ "terms": "Terms of Service",
+ "privacy": "Privacy Policy"
+ },
+ "spicy_zone": {
+ "title": "The Spicy Zone",
+ "clear": "Clear Media Cache",
+ "signout": "Sign Out"
+ }
+ },
+ "footer": {
+ "mastodon_description": "Mastodon is open source software. You can report issues on GitHub at %s (%s)"
+ },
+ "keyboard": {
+ "close_settings_window": "Close Settings Window"
+ }
+ },
+ "report": {
+ "title_report": "Report",
+ "title": "Report %s",
+ "step1": "Step 1 of 2",
+ "step2": "Step 2 of 2",
+ "content1": "Are there any other posts you’d like to add to the report?",
+ "content2": "Is there anything the moderators should know about this report?",
+ "report_sent_title": "Thanks for reporting, we’ll look into this.",
+ "send": "Send Report",
+ "skip_to_send": "Send without comment",
+ "text_placeholder": "Type or paste additional comments",
+ "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": {
+ "close_preview": "Close Preview",
+ "show_next": "Show Next",
+ "show_previous": "Show Previous"
+ }
+ },
+ "account_list": {
+ "tab_bar_hint": "Current selected profile: %s. Double tap then hold to show account switcher",
+ "dismiss_account_switcher": "Dismiss Account Switcher",
+ "add_account": "Add Account"
+ },
+ "wizard": {
+ "new_in_mastodon": "New in Mastodon",
+ "multiple_account_switch_intro_description": "Switch between multiple accounts by holding the profile button.",
+ "accessibility_hint": "Double tap to dismiss this wizard"
+ },
+ "bookmark": {
+ "title": "Bookmarks"
+ }
+ }
+}
diff --git a/Localization/StringsConvertor/input/Base.lproj/ios-infoPlist.json b/Localization/StringsConvertor/input/Base.lproj/ios-infoPlist.json
new file mode 100644
index 000000000..c6db73de0
--- /dev/null
+++ b/Localization/StringsConvertor/input/Base.lproj/ios-infoPlist.json
@@ -0,0 +1,6 @@
+{
+ "NSCameraUsageDescription": "Used to take photo for post status",
+ "NSPhotoLibraryAddUsageDescription": "Used to save photo into the Photo Library",
+ "NewPostShortcutItemTitle": "New Post",
+ "SearchShortcutItemTitle": "Search"
+}
diff --git a/Localization/StringsConvertor/input/ar.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/ar.lproj/Localizable.stringsdict
index 862d98184..91368a4fb 100644
--- a/Localization/StringsConvertor/input/ar.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/ar.lproj/Localizable.stringsdict
@@ -74,6 +74,30 @@
%ld حَرف
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ لَا حَرف
+ one
+ حَرفٌ واحِد
+ two
+ حَرفانِ اِثنان
+ few
+ %ld characters
+ many
+ %ld characters
+ other
+ %ld حَرف
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/ar.lproj/app.json b/Localization/StringsConvertor/input/ar.lproj/app.json
index ce68229ac..bf4bf454e 100644
--- a/Localization/StringsConvertor/input/ar.lproj/app.json
+++ b/Localization/StringsConvertor/input/ar.lproj/app.json
@@ -74,8 +74,8 @@
"take_photo": "اِلتِقاطُ صُورَة",
"save_photo": "حفظ الصورة",
"copy_photo": "نسخ الصورة",
- "sign_in": "تسجيل الدخول",
- "sign_up": "إنشاء حِساب",
+ "sign_in": "تسجيلُ الدخول",
+ "sign_up": "Create account",
"see_more": "عرض المزيد",
"preview": "مُعاينة",
"share": "المُشارك",
@@ -136,6 +136,12 @@
"vote": "صَوِّت",
"closed": "انتهى"
},
+ "meta_entity": {
+ "url": "رابِط: %s",
+ "hashtag": "وَسْم: %s",
+ "mention": "إظهار المِلف التعريفي: %s",
+ "email": "عُنوان البريد الإلكتُروني: %s"
+ },
"actions": {
"reply": "الرَّد",
"reblog": "إعادة النشر",
@@ -181,8 +187,8 @@
"unmute_user": "رفع الكتم عن %s",
"muted": "مكتوم",
"edit_info": "تَحريرُ المَعلُومات",
- "show_reblogs": "Show Reblogs",
- "hide_reblogs": "Hide Reblogs"
+ "show_reblogs": "إظهار إعادات التدوين",
+ "hide_reblogs": "إخفاء إعادات التدوين"
},
"timeline": {
"filtered": "مُصفَّى",
@@ -212,10 +218,16 @@
"get_started": "ابدأ الآن",
"log_in": "تسجيلُ الدخول"
},
+ "login": {
+ "title": "Welcome back",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Enter URL or search for your server"
+ }
+ },
"server_picker": {
"title": "اِختر خادِم،\nأيًّا مِنهُم.",
- "subtitle": "اختر مجتمعًا بناءً على اهتماماتك، منطقتك أو يمكنك حتى اختيارُ مجتمعٍ ذي غرضٍ عام.",
- "subtitle_extend": "اختر مجتمعًا بناءً على اهتماماتك، منطقتك أو يمكنك حتى اختيارُ مجتمعٍ ذي غرضٍ عام. تُشغَّل جميعُ المجتمعِ مِن قِبَلِ مُنظمَةٍ أو فردٍ مُستقلٍ تمامًا.",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
"button": {
"category": {
"all": "الكُل",
@@ -242,8 +254,7 @@
"category": "الفئة"
},
"input": {
- "placeholder": "اِبحَث عن خادِم أو انضم إلى آخر خاص بك...",
- "search_servers_or_enter_url": "اِبحَث فِي الخَوادِم أو أدخِل رابِط"
+ "search_servers_or_enter_url": "Search communities or enter URL"
},
"empty_state": {
"finding_servers": "يجري إيجاد خوادم متوفِّرَة...",
@@ -376,7 +387,13 @@
"video": "مقطع مرئي",
"attachment_broken": "هذا ال%s مُعطَّل\nويتعذَّرُ رفعُه إلى ماستودون.",
"description_photo": "صِف الصورة للمَكفوفين...",
- "description_video": "صِف المقطع المرئي للمَكفوفين..."
+ "description_video": "صِف المقطع المرئي للمَكفوفين...",
+ "load_failed": "فَشَلَ التَّحميل",
+ "upload_failed": "فَشَلَ الرَّفع",
+ "can_not_recognize_this_media_attachment": "يتعذَّرُ التعرُّفُ على وسائِطِ هذا المُرفَق",
+ "attachment_too_large": "المُرفَق كَبيرٌ جِدًّا",
+ "compressing_state": "يجري الضغط...",
+ "server_processing_state": "مُعالجة الخادم جارِيَة..."
},
"poll": {
"duration_time": "المُدَّة: %s",
@@ -386,7 +403,9 @@
"one_day": "يومٌ واحِد",
"three_days": "ثلاثةُ أيام",
"seven_days": "سبعةُ أيام",
- "option_number": "الخيار %ld"
+ "option_number": "الخيار %ld",
+ "the_poll_is_invalid": "الاِستِطلاعُ غيرُ صالِح",
+ "the_poll_has_empty_option": "يوجَدُ خِيارٌ فارِغٌ فِي الاِستِطلاع"
},
"content_warning": {
"placeholder": "اكتب تَحذيرًا دَقيقًا هُنا..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "منتقي الرموز التعبيرية المُخصَّص",
"enable_content_warning": "تفعيل تحذير المُحتَوى",
"disable_content_warning": "تعطيل تحذير المُحتَوى",
- "post_visibility_menu": "قائمة ظهور المنشور"
+ "post_visibility_menu": "قائمة ظهور المنشور",
+ "post_options": "Post Options",
+ "posting_as": "نَشر كَـ %s"
},
"keyboard": {
"discard_post": "تجاهُل المنشور",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "التسمية",
"content": "المُحتَوى"
+ },
+ "verified": {
+ "short": "تمَّ التَّحقق بِتاريخ %s",
+ "long": "تمَّ التَّحقق مِن مِلكية هذا الرابِطِ بِتاريخ %s"
}
},
"segmented_control": {
@@ -459,12 +484,12 @@
"message": "تأكيدُ رَفع الحَظرِ عَن %s"
},
"confirm_show_reblogs": {
- "title": "Show Reblogs",
- "message": "Confirm to show reblogs"
+ "title": "إظهار إعادات التدوين",
+ "message": "التأكيد لِإظهار إعادات التدوين"
},
"confirm_hide_reblogs": {
- "title": "Hide Reblogs",
- "message": "Confirm to hide reblogs"
+ "title": "إخفاء إعادات التدوين",
+ "message": "التأكيد لِإخفاء إعادات التدوين"
}
},
"accessibility": {
@@ -696,7 +721,7 @@
"accessibility_hint": "انقر نقرًا مزدوجًا لتجاهُل النافذة المنبثقة"
},
"bookmark": {
- "title": "Bookmarks"
+ "title": "العَلاماتُ المَرجعيَّة"
}
}
}
diff --git a/Localization/StringsConvertor/input/ca.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/ca.lproj/Localizable.stringsdict
index cc28edbc6..947597417 100644
--- a/Localization/StringsConvertor/input/ca.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/ca.lproj/Localizable.stringsdict
@@ -50,6 +50,22 @@
%ld caràcters
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ resten %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 caràcter
+ other
+ %ld caràcters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/ca.lproj/app.json b/Localization/StringsConvertor/input/ca.lproj/app.json
index 4766eb31b..52bb67c77 100644
--- a/Localization/StringsConvertor/input/ca.lproj/app.json
+++ b/Localization/StringsConvertor/input/ca.lproj/app.json
@@ -75,7 +75,7 @@
"save_photo": "Desa la foto",
"copy_photo": "Copia la foto",
"sign_in": "Iniciar sessió",
- "sign_up": "Registre",
+ "sign_up": "Crea un compte",
"see_more": "Veure més",
"preview": "Vista prèvia",
"share": "Comparteix",
@@ -136,6 +136,12 @@
"vote": "Vota",
"closed": "Finalitzada"
},
+ "meta_entity": {
+ "url": "Enllaç: %s",
+ "hashtag": "Etiqueta %s",
+ "mention": "Mostra el Perfil: %s",
+ "email": "Correu electrònic: %s"
+ },
"actions": {
"reply": "Respon",
"reblog": "Impuls",
@@ -212,10 +218,16 @@
"get_started": "Comença",
"log_in": "Inicia sessió"
},
+ "login": {
+ "title": "Ben tornat",
+ "subtitle": "T'inicia sessió en el servidor on has creat el teu compte.",
+ "server_search_field": {
+ "placeholder": "Insereix la URL o cerca el teu servidor"
+ }
+ },
"server_picker": {
"title": "Mastodon està fet d'usuaris en diferents comunitats.",
- "subtitle": "Tria una comunitat segons els teus interessos, regió o una de propòsit general.",
- "subtitle_extend": "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.",
+ "subtitle": "Tria un servidor en funció de la teva regió, interessos o un de propòsit general. Seguiràs podent connectar amb tothom a Mastodon, independentment del servidor.",
"button": {
"category": {
"all": "Totes",
@@ -242,8 +254,7 @@
"category": "CATEGORIA"
},
"input": {
- "placeholder": "Cerca servidors",
- "search_servers_or_enter_url": "Cerca servidors o introdueix l'enllaç"
+ "search_servers_or_enter_url": "Cerca comunitats o introdueix l'URL"
},
"empty_state": {
"finding_servers": "Cercant els servidors disponibles...",
@@ -376,7 +387,13 @@
"video": "vídeo",
"attachment_broken": "Aquest %s està trencat i no pot ser\ncarregat a Mastodon.",
"description_photo": "Descriu la foto per als disminuïts visuals...",
- "description_video": "Descriu el vídeo per als disminuïts visuals..."
+ "description_video": "Descriu el vídeo per als disminuïts visuals...",
+ "load_failed": "Ha fallat la càrrega",
+ "upload_failed": "Pujada fallida",
+ "can_not_recognize_this_media_attachment": "No es pot reconèixer aquest adjunt multimèdia",
+ "attachment_too_large": "El fitxer adjunt és massa gran",
+ "compressing_state": "Comprimint...",
+ "server_processing_state": "Servidor processant..."
},
"poll": {
"duration_time": "Durada: %s",
@@ -386,7 +403,9 @@
"one_day": "1 Dia",
"three_days": "3 Dies",
"seven_days": "7 Dies",
- "option_number": "Opció %ld"
+ "option_number": "Opció %ld",
+ "the_poll_is_invalid": "L'enquesta no és vàlida",
+ "the_poll_has_empty_option": "L'enquesta té una opció buida"
},
"content_warning": {
"placeholder": "Escriu un advertiment precís aquí..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Selector d'Emoji Personalitzat",
"enable_content_warning": "Activa l'Avís de Contingut",
"disable_content_warning": "Desactiva l'Avís de Contingut",
- "post_visibility_menu": "Menú de Visibilitat de Publicació"
+ "post_visibility_menu": "Menú de Visibilitat de Publicació",
+ "post_options": "Opcions del tut",
+ "posting_as": "Publicant com a %s"
},
"keyboard": {
"discard_post": "Descarta la Publicació",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Etiqueta",
"content": "Contingut"
+ },
+ "verified": {
+ "short": "Verificat a %s",
+ "long": "La propietat d'aquest enllaç es va verificar el dia %s"
}
},
"segmented_control": {
@@ -696,7 +721,7 @@
"accessibility_hint": "Toca dues vegades per descartar l'assistent"
},
"bookmark": {
- "title": "Bookmarks"
+ "title": "Marcadors"
}
}
}
diff --git a/Localization/StringsConvertor/input/ckb.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/ckb.lproj/Localizable.stringsdict
index 001a8a608..8116226ec 100644
--- a/Localization/StringsConvertor/input/ckb.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/ckb.lproj/Localizable.stringsdict
@@ -50,6 +50,22 @@
%ld نووسە
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character
+ other
+ %ld characters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/ckb.lproj/app.json b/Localization/StringsConvertor/input/ckb.lproj/app.json
index 49f72d7a3..787bbea36 100644
--- a/Localization/StringsConvertor/input/ckb.lproj/app.json
+++ b/Localization/StringsConvertor/input/ckb.lproj/app.json
@@ -74,8 +74,8 @@
"take_photo": "وێنە بگرە",
"save_photo": "هەڵی بگرە",
"copy_photo": "لەبەری بگرەوە",
- "sign_in": "بچۆ ژوورەوە",
- "sign_up": "خۆت تۆمار بکە",
+ "sign_in": "Log in",
+ "sign_up": "Create account",
"see_more": "زیاتر ببینە",
"preview": "پێشبینین",
"share": "هاوبەشی بکە",
@@ -136,6 +136,12 @@
"vote": "دەنگ بدە",
"closed": "داخراوە"
},
+ "meta_entity": {
+ "url": "Link: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Show Profile: %s",
+ "email": "Email address: %s"
+ },
"actions": {
"reply": "وەڵامی بدەوە",
"reblog": "پۆستی بکەوە",
@@ -212,10 +218,16 @@
"get_started": "دەست پێ بکە",
"log_in": "بچۆ ژوورەوە"
},
+ "login": {
+ "title": "Welcome back",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Enter URL or search for your server"
+ }
+ },
"server_picker": {
"title": "ماستۆدۆن لە چەندان بەکارهێنەر پێک دێت کە لە ڕاژەکاری جیاواز دان.",
- "subtitle": "ڕاژەکارێکێکی گشتی یان دانەیەک لەسەر بنەمای حەزەکانت و هەرێمەکەت هەڵبژێرە.",
- "subtitle_extend": "ڕاژەکارێکێکی گشتی یان دانەیەک لەسەر بنەمای حەزەکانت و هەرێمەکەت هەڵبژێرە. هەر ڕاژەکارێک لەلایەن ڕێکخراوێک یان تاکەکەسێک بەڕێوە دەبرێت.",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
"button": {
"category": {
"all": "هەموو",
@@ -242,8 +254,7 @@
"category": "بەش"
},
"input": {
- "placeholder": "بگەڕێ",
- "search_servers_or_enter_url": "Search servers or enter URL"
+ "search_servers_or_enter_url": "Search communities or enter URL"
},
"empty_state": {
"finding_servers": "ڕاژەکار دەدۆزرێتەوە...",
@@ -376,7 +387,13 @@
"video": "ڤیدیۆ",
"attachment_broken": "ئەم %sـە تێک چووە و ناتوانیت بەرزی بکەیتەوە.",
"description_photo": "وێنەکەت بۆ نابیناکان باس بکە...",
- "description_video": "ڤیدیۆکەت بۆ نابیناکان باس بکە..."
+ "description_video": "ڤیدیۆکەت بۆ نابیناکان باس بکە...",
+ "load_failed": "Load Failed",
+ "upload_failed": "Upload Failed",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "Attachment too large",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "کات: %s",
@@ -386,7 +403,9 @@
"one_day": "1 ڕۆژ",
"three_days": "3 ڕۆژ",
"seven_days": "7 ڕۆژ",
- "option_number": "بژاردەی %ld"
+ "option_number": "بژاردەی %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "ئاگادارییەکەت لێرە بنووسە..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "هەڵبژێری ئیمۆجی",
"enable_content_warning": "ئاگاداریی ناوەڕۆک چالاک بکە",
"disable_content_warning": "ئاگاداریی ناوەڕۆک ناچالاک بکە",
- "post_visibility_menu": "پێڕستی شێوازی دەرکەوتنی پۆست"
+ "post_visibility_menu": "پێڕستی شێوازی دەرکەوتنی پۆست",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "پۆستەکە هەڵوەشێنەوە",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "ناونیشان",
"content": "ناوەڕۆک"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
diff --git a/Localization/StringsConvertor/input/cs.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/cs.lproj/Localizable.stringsdict
index cdf35477e..6e44e9f0a 100644
--- a/Localization/StringsConvertor/input/cs.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/cs.lproj/Localizable.stringsdict
@@ -13,19 +13,19 @@
NSStringFormatValueTypeKey
ld
one
- 1 unread notification
+ 1 nepřečtené oznámení
few
- %ld unread notification
+ %ld nepřečtené oznámení
many
- %ld unread notification
+ %ld nepřečtených oznámení
other
- %ld unread notification
+ %ld nepřečtených oznámení
a11y.plural.count.input_limit_exceeds
NSStringLocalizedFormatKey
- Input limit exceeds %#@character_count@
+ Vstupní limit přesahuje %#@character_count@
character_count
NSStringFormatSpecTypeKey
@@ -33,19 +33,19 @@
NSStringFormatValueTypeKey
ld
one
- 1 character
+ 1 znak
few
- %ld characters
+ %ld znaky
many
- %ld characters
+ %ld znaků
other
- %ld characters
+ %ld znaků
a11y.plural.count.input_limit_remains
NSStringLocalizedFormatKey
- Input limit remains %#@character_count@
+ Vstupní limit zůstává %#@character_count@
character_count
NSStringFormatSpecTypeKey
@@ -53,13 +53,33 @@
NSStringFormatValueTypeKey
ld
one
- 1 character
+ 1 znak
few
- %ld characters
+ %ld znaky
many
- %ld characters
+ %ld znaků
other
- %ld characters
+ %ld znaků
+
+
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 znak
+ few
+ %ld znaky
+ many
+ %ld znaků
+ other
+ %ld znaků
plural.count.followed_by_and_mutual
@@ -108,13 +128,13 @@
NSStringFormatValueTypeKey
ld
one
- post
+ příspěvek
few
- posts
+ příspěvky
many
- posts
+ příspěvků
other
- posts
+ příspěvků
plural.count.media
@@ -128,13 +148,13 @@
NSStringFormatValueTypeKey
ld
one
- 1 media
+ 1 médium
few
- %ld media
+ %ld média
many
- %ld media
+ %ld médií
other
- %ld media
+ %ld médií
plural.count.post
@@ -148,13 +168,13 @@
NSStringFormatValueTypeKey
ld
one
- 1 post
+ 1 příspěvek
few
- %ld posts
+ %ld příspěvky
many
- %ld posts
+ %ld příspěvků
other
- %ld posts
+ %ld příspěvků
plural.count.favorite
@@ -168,7 +188,7 @@
NSStringFormatValueTypeKey
ld
one
- 1 favorite
+ 1 oblíbený
few
%ld favorites
many
@@ -208,13 +228,13 @@
NSStringFormatValueTypeKey
ld
one
- 1 reply
+ 1 odpověď
few
- %ld replies
+ %ld odpovědi
many
- %ld replies
+ %ld odpovědí
other
- %ld replies
+ %ld odpovědí
plural.count.vote
@@ -228,13 +248,13 @@
NSStringFormatValueTypeKey
ld
one
- 1 vote
+ 1 hlas
few
- %ld votes
+ %ld hlasy
many
- %ld votes
+ %ld hlasů
other
- %ld votes
+ %ld hlasů
plural.count.voter
@@ -248,13 +268,13 @@
NSStringFormatValueTypeKey
ld
one
- 1 voter
+ 1 hlasující
few
- %ld voters
+ %ld hlasující
many
- %ld voters
+ %ld hlasujících
other
- %ld voters
+ %ld hlasujících
plural.people_talking
@@ -288,13 +308,13 @@
NSStringFormatValueTypeKey
ld
one
- 1 following
+ 1 sledující
few
- %ld following
+ %ld sledující
many
- %ld following
+ %ld sledujících
other
- %ld following
+ %ld sledujících
plural.count.follower
@@ -328,13 +348,13 @@
NSStringFormatValueTypeKey
ld
one
- 1 year left
+ Zbývá 1 rok
few
- %ld years left
+ Zbývají %ld roky
many
- %ld years left
+ Zbývá %ld roků
other
- %ld years left
+ Zbývá %ld roků
date.month.left
@@ -348,7 +368,7 @@
NSStringFormatValueTypeKey
ld
one
- 1 months left
+ Zbývá 1 měsíc
few
%ld months left
many
diff --git a/Localization/StringsConvertor/input/cs.lproj/app.json b/Localization/StringsConvertor/input/cs.lproj/app.json
index 550f71808..680eb01bb 100644
--- a/Localization/StringsConvertor/input/cs.lproj/app.json
+++ b/Localization/StringsConvertor/input/cs.lproj/app.json
@@ -37,12 +37,12 @@
"confirm": "Odhlásit se"
},
"block_domain": {
- "title": "Are you really, really sure you want to block the entire %s? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain and any of your followers from that domain will be removed.",
+ "title": "Opravdu chcete blokovat celou doménu %s? Ve většině případů stačí zablokovat nebo skrýt pár konkrétních uživatelů, což také doporučujeme. Z této domény neuvidíte obsah v žádné veřejné časové ose ani v oznámeních. Vaši sledující z této domény budou odstraněni.",
"block_entire_domain": "Blokovat doménu"
},
"save_photo_failure": {
"title": "Uložení fotografie se nezdařilo",
- "message": "Please enable the photo library access permission to save the photo."
+ "message": "Pro uložení fotografie povolte přístup k knihovně fotografií."
},
"delete_post": {
"title": "Odstranit příspěvek",
@@ -75,11 +75,11 @@
"save_photo": "Uložit fotku",
"copy_photo": "Kopírovat fotografii",
"sign_in": "Přihlásit se",
- "sign_up": "Zaregistrovat se",
+ "sign_up": "Vytvořit účet",
"see_more": "Zobrazit více",
"preview": "Náhled",
"share": "Sdílet",
- "share_user": "Share %s",
+ "share_user": "Sdílet %s",
"share_post": "Sdílet příspěvek",
"open_in_safari": "Otevřít v Safari",
"open_in_browser": "Otevřít v prohlížeči",
@@ -125,7 +125,7 @@
},
"status": {
"user_reblogged": "%s reblogged",
- "user_replied_to": "Replied to %s",
+ "user_replied_to": "Odpověděl %s",
"show_post": "Zobrazit příspěvek",
"show_user_profile": "Zobrazit profil uživatele",
"content_warning": "Varování o obsahu",
@@ -136,18 +136,24 @@
"vote": "Hlasovat",
"closed": "Uzavřeno"
},
+ "meta_entity": {
+ "url": "Odkaz: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Zobrazit profil: %s",
+ "email": "E-mailová adresa: %s"
+ },
"actions": {
"reply": "Odpovědět",
"reblog": "Boostnout",
"unreblog": "Undo reblog",
- "favorite": "Favorite",
+ "favorite": "Oblíbit",
"unfavorite": "Odebrat z oblízených",
"menu": "Nabídka",
"hide": "Skrýt",
"show_image": "Zobrazit obrázek",
"show_gif": "Zobrazit GIF",
"show_video_player": "Zobrazit video přehrávač",
- "tap_then_hold_to_show_menu": "Tap then hold to show menu"
+ "tap_then_hold_to_show_menu": "Klepnutím podržte pro zobrazení nabídky"
},
"tag": {
"url": "URL",
@@ -159,22 +165,22 @@
},
"visibility": {
"unlisted": "Každý může vidět tento příspěvek, ale nezobrazovat ve veřejné časové ose.",
- "private": "Only their followers can see this post.",
- "private_from_me": "Only my followers can see this post.",
- "direct": "Only mentioned user can see this post."
+ "private": "Pouze jejich sledující mohou vidět tento příspěvek.",
+ "private_from_me": "Pouze moji sledující mohou vidět tento příspěvek.",
+ "direct": "Pouze zmíněný uživatel může vidět tento příspěvek."
}
},
"friendship": {
"follow": "Sledovat",
- "following": "Following",
- "request": "Request",
+ "following": "Sleduji",
+ "request": "Požadavek",
"pending": "Čekající",
"block": "Blokovat",
"block_user": "Blokovat %s",
"block_domain": "Blokovat %s",
"unblock": "Odblokovat",
"unblock_user": "Odblokovat %s",
- "blocked": "Blocked",
+ "blocked": "Blokovaný",
"mute": "Skrýt",
"mute_user": "Skrýt %s",
"unmute": "Odkrýt",
@@ -185,7 +191,7 @@
"hide_reblogs": "Hide Reblogs"
},
"timeline": {
- "filtered": "Filtered",
+ "filtered": "Filtrováno",
"timestamp": {
"now": "Nyní"
},
@@ -196,26 +202,32 @@
},
"header": {
"no_status_found": "Nebyl nalezen žádný příspěvek",
- "blocking_warning": "You can’t view this user's profile\nuntil you unblock them.\nYour profile looks like this to them.",
- "user_blocking_warning": "You can’t view %s’s profile\nuntil you unblock them.\nYour profile looks like this to them.",
- "blocked_warning": "You can’t view this user’s profile\nuntil they unblock you.",
- "user_blocked_warning": "You can’t view %s’s profile\nuntil they unblock you.",
- "suspended_warning": "This user has been suspended.",
- "user_suspended_warning": "%s’s account has been suspended."
+ "blocking_warning": "Nemůžete zobrazit profil tohoto uživatele, dokud ho neodblokujete.\nVáš profil pro něj vypadá takto.",
+ "user_blocking_warning": "Nemůžete zobrazit profil %s, dokud ho neodblokujete.\nVáš profil pro něj vypadá takto.",
+ "blocked_warning": "Nemůžeš zobrazit profil tohoto uživatele, dokud tě neodblokují.",
+ "user_blocked_warning": "Nemůžete zobrazit profil %s, dokud vás neodblokuje.",
+ "suspended_warning": "Tento uživatel byl pozastaven.",
+ "user_suspended_warning": "Účet %s byl pozastaven."
}
}
}
},
"scene": {
"welcome": {
- "slogan": "Social networking\nback in your hands.",
+ "slogan": "Sociální sítě opět ve vašich rukou.",
"get_started": "Začínáme",
"log_in": "Přihlásit se"
},
+ "login": {
+ "title": "Vítejte zpět",
+ "subtitle": "Přihlaste se na serveru, na kterém jste si vytvořili účet.",
+ "server_search_field": {
+ "placeholder": "Zadejte URL nebo vyhledávejte váš server"
+ }
+ },
"server_picker": {
"title": "Mastodon tvoří uživatelé z různých serverů.",
- "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.",
+ "subtitle": "Vyberte server založený ve vašem regionu, podle zájmů nebo podle obecného účelu. Stále můžete chatovat s kýmkoli na Mastodonu bez ohledu na vaše servery.",
"button": {
"category": {
"all": "Vše",
@@ -224,7 +236,7 @@
"activism": "aktivismus",
"food": "jídlo",
"furry": "furry",
- "games": "games",
+ "games": "hry",
"general": "obecné",
"journalism": "žurnalistika",
"lgbt": "lgbt",
@@ -242,8 +254,7 @@
"category": "KATEGORIE"
},
"input": {
- "placeholder": "Hledat servery",
- "search_servers_or_enter_url": "Hledat servery nebo zadat URL"
+ "search_servers_or_enter_url": "Hledejte komunity nebo zadejte URL"
},
"empty_state": {
"finding_servers": "Hledání dostupných serverů...",
@@ -252,8 +263,8 @@
}
},
"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",
+ "title": "Pojďme si nastavit %s",
+ "lets_get_you_set_up_on_domain": "Pojďme si nastavit %s",
"input": {
"avatar": {
"delete": "Smazat"
@@ -292,51 +303,51 @@
"reason": "Důvod"
},
"reason": {
- "blocked": "%s contains a disallowed email provider",
- "unreachable": "%s does not seem to exist",
+ "blocked": "%s používá zakázanou e-mailovou službu",
+ "unreachable": "%s pravděpodobně neexistuje",
"taken": "%s se již používá",
"reserved": "%s je rezervované klíčové slovo",
"accepted": "%s musí být přijato",
"blank": "%s je vyžadováno",
- "invalid": "%s is invalid",
- "too_long": "%s is too long",
- "too_short": "%s is too short",
- "inclusion": "%s is not a supported value"
+ "invalid": "%s je neplatné",
+ "too_long": "%s je příliš dlouhé",
+ "too_short": "%s je příliš krátké",
+ "inclusion": "%s není podporovaná hodnota"
},
"special": {
- "username_invalid": "Username must only contain alphanumeric characters and underscores",
- "username_too_long": "Username is too long (can’t be longer than 30 characters)",
+ "username_invalid": "Uživatelské jméno musí obsahovat pouze alfanumerické znaky a podtržítka",
+ "username_too_long": "Uživatelské jméno je příliš dlouhé (nemůže být delší než 30 znaků)",
"email_invalid": "Toto není platná e-mailová adresa",
- "password_too_short": "Password is too short (must be at least 8 characters)"
+ "password_too_short": "Heslo je příliš krátké (musí mít alespoň 8 znaků)"
}
}
},
"server_rules": {
- "title": "Some ground rules.",
- "subtitle": "These are set and enforced by the %s moderators.",
- "prompt": "By continuing, you’re subject to the terms of service and privacy policy for %s.",
- "terms_of_service": "terms of service",
- "privacy_policy": "privacy policy",
+ "title": "Některá základní pravidla.",
+ "subtitle": "Ty nastavují a prosazují moderátoři %s.",
+ "prompt": "Pokračováním budete podléhat podmínkám služby a zásad ochrany osobních údajů pro uživatele %s.",
+ "terms_of_service": "podmínky služby",
+ "privacy_policy": "zásady ochrany osobních údajů",
"button": {
- "confirm": "I Agree"
+ "confirm": "Souhlasím"
}
},
"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",
+ "title": "Ještě jedna věc.",
+ "subtitle": "Klepněte na odkaz, který jsme vám poslali e-mailem, abyste ověřili Váš účet.",
+ "tap_the_link_we_emailed_to_you_to_verify_your_account": "Klepněte na odkaz, který jsme vám poslali e-mailem, abyste ověřili Váš účet",
"button": {
- "open_email_app": "Open Email App",
- "resend": "Resend"
+ "open_email_app": "Otevřít e-mailovou aplikaci",
+ "resend": "Poslat znovu"
},
"dont_receive_email": {
- "title": "Check your email",
- "description": "Check if your email address is correct as well as your junk folder if you haven’t.",
- "resend_email": "Resend Email"
+ "title": "Zkontrolujte svůj e-mail",
+ "description": "Zkontrolujte, zda je vaše e-mailová adresa správná, stejně jako složka nevyžádané pošty, pokud ji máte.",
+ "resend_email": "Znovu odeslat e-mail"
},
"open_email_app": {
- "title": "Check your inbox.",
- "description": "We just sent you an email. Check your junk folder if you haven’t.",
+ "title": "Zkontrolujte doručenou poštu.",
+ "description": "Právě jsme vám poslali e-mail. Zkontrolujte složku nevyžádané zprávy, pokud ji máte.",
"mail": "Pošta",
"open_email_client": "Otevřít e-mailového klienta"
}
@@ -349,47 +360,55 @@
"published": "Publikováno!",
"Publishing": "Publikování příspěvku...",
"accessibility": {
- "logo_label": "Logo Button",
- "logo_hint": "Tap to scroll to top and tap again to previous location"
+ "logo_label": "Tlačítko s logem",
+ "logo_hint": "Klepnutím přejdete nahoru a znovu klepněte na předchozí místo"
}
}
},
"suggestion_account": {
- "title": "Find People to Follow",
- "follow_explain": "When you follow someone, you’ll see their posts in your home feed."
+ "title": "Najít lidi pro sledování",
+ "follow_explain": "Když někoho sledujete, uvidíte jejich příspěvky ve vašem domovském kanálu."
},
"compose": {
"title": {
"new_post": "Nový příspěvek",
- "new_reply": "New Reply"
+ "new_reply": "Nová odpověď"
},
"media_selection": {
- "camera": "Take Photo",
- "photo_library": "Photo Library",
- "browse": "Browse"
+ "camera": "Vyfotit",
+ "photo_library": "Knihovna fotografií",
+ "browse": "Procházet"
},
- "content_input_placeholder": "Type or paste what’s on your mind",
- "compose_action": "Publish",
- "replying_to_user": "replying to %s",
+ "content_input_placeholder": "Napište nebo vložte, co je na mysli",
+ "compose_action": "Zveřejnit",
+ "replying_to_user": "odpovídá na %s",
"attachment": {
- "photo": "photo",
+ "photo": "fotka",
"video": "video",
- "attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.",
- "description_photo": "Describe the photo for the visually-impaired...",
- "description_video": "Describe the video for the visually-impaired..."
+ "attachment_broken": "Tento %s je poškozený a nemůže být\nnahrán do Mastodonu.",
+ "description_photo": "Popište fotografii pro zrakově postižené osoby...",
+ "description_video": "Popište video pro zrakově postižené...",
+ "load_failed": "Načtení se nezdařilo",
+ "upload_failed": "Nahrání selhalo",
+ "can_not_recognize_this_media_attachment": "Nelze rozpoznat toto medium přílohy",
+ "attachment_too_large": "Příloha je příliš velká",
+ "compressing_state": "Probíhá komprese...",
+ "server_processing_state": "Zpracování serveru..."
},
"poll": {
- "duration_time": "Duration: %s",
+ "duration_time": "Doba trvání: %s",
"thirty_minutes": "30 minut",
"one_hour": "1 hodina",
"six_hours": "6 hodin",
"one_day": "1 den",
"three_days": "3 dny",
"seven_days": "7 dní",
- "option_number": "Možnost %ld"
+ "option_number": "Možnost %ld",
+ "the_poll_is_invalid": "Anketa je neplatná",
+ "the_poll_has_empty_option": "Anketa má prázdnou možnost"
},
"content_warning": {
- "placeholder": "Write an accurate warning here..."
+ "placeholder": "Zde napište přesné varování..."
},
"visibility": {
"public": "Veřejný",
@@ -398,7 +417,7 @@
"direct": "Pouze lidé, které zmíním"
},
"auto_complete": {
- "space_to_add": "Space to add"
+ "space_to_add": "Mezera k přidání"
},
"accessibility": {
"append_attachment": "Přidat přílohu",
@@ -407,13 +426,15 @@
"custom_emoji_picker": "Vlastní výběr Emoji",
"enable_content_warning": "Povolit upozornění na obsah",
"disable_content_warning": "Vypnout upozornění na obsah",
- "post_visibility_menu": "Menu viditelnosti příspěvku"
+ "post_visibility_menu": "Menu viditelnosti příspěvku",
+ "post_options": "Možnosti příspěvku",
+ "posting_as": "Odesílání jako %s"
},
"keyboard": {
"discard_post": "Zahodit příspěvek",
"publish_post": "Publikovat příspěvek",
"toggle_poll": "Přepnout anketu",
- "toggle_content_warning": "Toggle Content Warning",
+ "toggle_content_warning": "Přepnout varování obsahu",
"append_attachment_entry": "Přidat přílohu - %s",
"select_visibility_entry": "Vyberte viditelnost - %s"
}
@@ -424,14 +445,18 @@
},
"dashboard": {
"posts": "příspěvky",
- "following": "following",
+ "following": "sledování",
"followers": "sledující"
},
"fields": {
"add_row": "Přidat řádek",
"placeholder": {
- "label": "Label",
+ "label": "Označení",
"content": "Obsah"
+ },
+ "verified": {
+ "short": "Ověřeno na %s",
+ "long": "Vlastnictví tohoto odkazu bylo zkontrolováno na %s"
}
},
"segmented_control": {
@@ -439,7 +464,7 @@
"replies": "Odpovědí",
"posts_and_replies": "Příspěvky a odpovědi",
"media": "Média",
- "about": "About"
+ "about": "O uživateli"
},
"relationship_action_alert": {
"confirm_mute_user": {
@@ -448,7 +473,7 @@
},
"confirm_unmute_user": {
"title": "Zrušit skrytí účtu",
- "message": "Confirm to unmute %s"
+ "message": "Potvrďte zrušení ztlumení %s"
},
"confirm_block_user": {
"title": "Blokovat účet",
@@ -456,7 +481,7 @@
},
"confirm_unblock_user": {
"title": "Odblokovat účet",
- "message": "Confirm to unblock %s"
+ "message": "Potvrďte odblokování %s"
},
"confirm_show_reblogs": {
"title": "Show Reblogs",
@@ -468,235 +493,235 @@
}
},
"accessibility": {
- "show_avatar_image": "Show avatar image",
- "edit_avatar_image": "Edit avatar image",
- "show_banner_image": "Show banner image",
- "double_tap_to_open_the_list": "Double tap to open the list"
+ "show_avatar_image": "Zobrazit obrázek avataru",
+ "edit_avatar_image": "Upravit obrázek avataru",
+ "show_banner_image": "Zobrazit obrázek banneru",
+ "double_tap_to_open_the_list": "Dvojitým poklepáním otevřete seznam"
}
},
"follower": {
- "title": "follower",
- "footer": "Followers from other servers are not displayed."
+ "title": "sledující",
+ "footer": "Sledující z jiných serverů nejsou zobrazeni."
},
"following": {
- "title": "following",
- "footer": "Follows from other servers are not displayed."
+ "title": "sledování",
+ "footer": "Sledování z jiných serverů není zobrazeno."
},
"familiarFollowers": {
- "title": "Followers you familiar",
- "followed_by_names": "Followed by %s"
+ "title": "Sledující, které znáte",
+ "followed_by_names": "Sledován od %s"
},
"favorited_by": {
- "title": "Favorited By"
+ "title": "Oblíben"
},
"reblogged_by": {
"title": "Reblogged By"
},
"search": {
- "title": "Search",
+ "title": "Hledat",
"search_bar": {
- "placeholder": "Search hashtags and users",
- "cancel": "Cancel"
+ "placeholder": "Hledat hashtagy a uživatele",
+ "cancel": "Zrušit"
},
"recommend": {
- "button_text": "See All",
+ "button_text": "Zobrazit vše",
"hash_tag": {
- "title": "Trending on Mastodon",
- "description": "Hashtags that are getting quite a bit of attention",
- "people_talking": "%s people are talking"
+ "title": "Populární na Mastodonu",
+ "description": "Hashtagy, kterým se dostává dosti pozornosti",
+ "people_talking": "%s lidí mluví"
},
"accounts": {
- "title": "Accounts you might like",
- "description": "You may like to follow these accounts",
- "follow": "Follow"
+ "title": "Účty, které by se vám mohly líbit",
+ "description": "Možná budete chtít sledovat tyto účty",
+ "follow": "Sledovat"
}
},
"searching": {
"segment": {
- "all": "All",
- "people": "People",
- "hashtags": "Hashtags",
- "posts": "Posts"
+ "all": "Vše",
+ "people": "Lidé",
+ "hashtags": "Hashtagy",
+ "posts": "Příspěvky"
},
"empty_state": {
- "no_results": "No results"
+ "no_results": "Žádné výsledky"
},
- "recent_search": "Recent searches",
- "clear": "Clear"
+ "recent_search": "Nedávná hledání",
+ "clear": "Vymazat"
}
},
"discovery": {
"tabs": {
- "posts": "Posts",
- "hashtags": "Hashtags",
- "news": "News",
- "community": "Community",
- "for_you": "For You"
+ "posts": "Příspěvky",
+ "hashtags": "Hashtagy",
+ "news": "Zprávy",
+ "community": "Komunita",
+ "for_you": "Pro vás"
},
- "intro": "These are the posts gaining traction in your corner of Mastodon."
+ "intro": "Toto jsou příspěvky, které získávají pozornost ve vašem koutu Mastodonu."
},
"favorite": {
- "title": "Your Favorites"
+ "title": "Vaše oblíbené"
},
"notification": {
"title": {
- "Everything": "Everything",
- "Mentions": "Mentions"
+ "Everything": "Všechno",
+ "Mentions": "Zmínky"
},
"notification_description": {
- "followed_you": "followed you",
- "favorited_your_post": "favorited your post",
- "reblogged_your_post": "reblogged your post",
- "mentioned_you": "mentioned you",
- "request_to_follow_you": "request to follow you",
- "poll_has_ended": "poll has ended"
+ "followed_you": "vás sleduje",
+ "favorited_your_post": "si oblíbil váš příspěvek",
+ "reblogged_your_post": "boostnul váš příspěvek",
+ "mentioned_you": "vás zmínil/a",
+ "request_to_follow_you": "požádat vás o sledování",
+ "poll_has_ended": "anketa skončila"
},
"keyobard": {
- "show_everything": "Show Everything",
- "show_mentions": "Show Mentions"
+ "show_everything": "Zobrazit vše",
+ "show_mentions": "Zobrazit zmínky"
},
"follow_request": {
- "accept": "Accept",
- "accepted": "Accepted",
- "reject": "reject",
- "rejected": "Rejected"
+ "accept": "Přijmout",
+ "accepted": "Přijato",
+ "reject": "odmítnout",
+ "rejected": "Zamítnuto"
}
},
"thread": {
- "back_title": "Post",
- "title": "Post from %s"
+ "back_title": "Příspěvek",
+ "title": "Příspěvek od %s"
},
"settings": {
- "title": "Settings",
+ "title": "Nastavení",
"section": {
"appearance": {
- "title": "Appearance",
- "automatic": "Automatic",
- "light": "Always Light",
- "dark": "Always Dark"
+ "title": "Vzhled",
+ "automatic": "Automaticky",
+ "light": "Vždy světlý",
+ "dark": "Vždy tmavý"
},
"look_and_feel": {
- "title": "Look and Feel",
- "use_system": "Use System",
- "really_dark": "Really Dark",
+ "title": "Vzhled a chování",
+ "use_system": "Použít systém",
+ "really_dark": "Skutečně tmavý",
"sorta_dark": "Sorta Dark",
- "light": "Light"
+ "light": "Světlý"
},
"notifications": {
- "title": "Notifications",
- "favorites": "Favorites my post",
- "follows": "Follows me",
- "boosts": "Reblogs my post",
- "mentions": "Mentions me",
+ "title": "Upozornění",
+ "favorites": "Oblíbil si můj příspěvek",
+ "follows": "Sleduje mě",
+ "boosts": "Boostnul můj příspěvek",
+ "mentions": "Zmiňuje mě",
"trigger": {
- "anyone": "anyone",
- "follower": "a follower",
- "follow": "anyone I follow",
- "noone": "no one",
- "title": "Notify me when"
+ "anyone": "kdokoliv",
+ "follower": "sledující",
+ "follow": "kdokoli, koho sleduji",
+ "noone": "nikdo",
+ "title": "Upozornit, když"
}
},
"preference": {
- "title": "Preferences",
- "true_black_dark_mode": "True black dark mode",
- "disable_avatar_animation": "Disable animated avatars",
- "disable_emoji_animation": "Disable animated emojis",
- "using_default_browser": "Use default browser to open links",
- "open_links_in_mastodon": "Open links in Mastodon"
+ "title": "Předvolby",
+ "true_black_dark_mode": "Skutečný černý tmavý režim",
+ "disable_avatar_animation": "Zakázat animované avatary",
+ "disable_emoji_animation": "Zakázat animované emoji",
+ "using_default_browser": "Použít výchozí prohlížeč pro otevírání odkazů",
+ "open_links_in_mastodon": "Otevřít odkazy v Mastodonu"
},
"boring_zone": {
- "title": "The Boring Zone",
- "account_settings": "Account Settings",
- "terms": "Terms of Service",
- "privacy": "Privacy Policy"
+ "title": "Nudná část",
+ "account_settings": "Nastavení účtu",
+ "terms": "Podmínky služby",
+ "privacy": "Zásady ochrany osobních údajů"
},
"spicy_zone": {
- "title": "The Spicy Zone",
- "clear": "Clear Media Cache",
- "signout": "Sign Out"
+ "title": "Ostrá část",
+ "clear": "Vymazat mezipaměť médií",
+ "signout": "Odhlásit se"
}
},
"footer": {
- "mastodon_description": "Mastodon is open source software. You can report issues on GitHub at %s (%s)"
+ "mastodon_description": "Mastodon je open source software. Na GitHub můžete nahlásit problémy na %s (%s)"
},
"keyboard": {
- "close_settings_window": "Close Settings Window"
+ "close_settings_window": "Zavřít okno nastavení"
}
},
"report": {
- "title_report": "Report",
- "title": "Report %s",
- "step1": "Step 1 of 2",
- "step2": "Step 2 of 2",
- "content1": "Are there any other posts you’d like to add to the report?",
- "content2": "Is there anything the moderators should know about this report?",
- "report_sent_title": "Thanks for reporting, we’ll look into this.",
- "send": "Send Report",
- "skip_to_send": "Send without comment",
- "text_placeholder": "Type or paste additional comments",
- "reported": "REPORTED",
+ "title_report": "Nahlásit",
+ "title": "Nahlásit %s",
+ "step1": "Krok 1 ze 2",
+ "step2": "Krok 2 ze 2",
+ "content1": "Existují nějaké další příspěvky, které byste chtěli přidat do zprávy?",
+ "content2": "Je o tomto hlášení něco, co by měli vědět moderátoři?",
+ "report_sent_title": "Děkujeme za nahlášení, podíváme se na to.",
+ "send": "Odeslat hlášení",
+ "skip_to_send": "Odeslat bez komentáře",
+ "text_placeholder": "Napište nebo vložte další komentáře",
+ "reported": "NAHLÁŠEN",
"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_1_of_4": "Krok 1 ze 4",
+ "whats_wrong_with_this_post": "Co je na tomto příspěvku špatně?",
+ "whats_wrong_with_this_account": "Co je špatně s tímto účtem?",
+ "whats_wrong_with_this_username": "Co je špatně na %s?",
+ "select_the_best_match": "Vyberte nejbližší možnost",
+ "i_dont_like_it": "Nelíbí se mi",
+ "it_is_not_something_you_want_to_see": "Není to něco, co chcete vidět",
+ "its_spam": "Je to spam",
+ "malicious_links_fake_engagement_or_repetetive_replies": "Škodlivé odkazy, falešné zapojení nebo opakující se odpovědi",
+ "it_violates_server_rules": "Porušuje pravidla serveru",
+ "you_are_aware_that_it_breaks_specific_rules": "Máte za to, že porušuje konkrétní pravidla",
+ "its_something_else": "Jde o něco jiného",
+ "the_issue_does_not_fit_into_other_categories": "Problém neodpovídá ostatním kategoriím"
},
"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_2_of_4": "Krok 2 ze 4",
+ "which_rules_are_being_violated": "Jaká pravidla jsou porušována?",
+ "select_all_that_apply": "Vyberte všechna relevantní",
+ "i_just_don’t_like_it": "Jen se mi to nelíbí"
},
"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_3_of_4": "Krok 3 ze 4",
+ "are_there_any_posts_that_back_up_this_report": "Existují příspěvky dokládající toto hlášení?",
+ "select_all_that_apply": "Vyberte všechna relevantní"
},
"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_4_of_4": "Krok 4 ze 4",
+ "is_there_anything_else_we_should_know": "Je ještě něco jiného, co bychom měli vědět?"
},
"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"
+ "dont_want_to_see_this": "Nechcete to vidět?",
+ "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "Když uvidíte něco, co se vám nelíbí na Mastodonu, můžete odstranit tuto osobu ze svého zážitku.",
+ "unfollow": "Přestat sledovat",
+ "unfollowed": "Už nesledujete",
+ "unfollow_user": "Přestat sledovat %s",
+ "mute_user": "Skrýt %s",
+ "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "Neuvidíte jejich příspěvky nebo boostnutí v domovském kanálu. Nebudou vědět, že jsou skrytí.",
+ "block_user": "Blokovat %s",
+ "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "Už nebudou moci sledovat nebo vidět vaše příspěvky, ale mohou vidět, zda byly zablokovány.",
+ "while_we_review_this_you_can_take_action_against_user": "Zatímco to posuzujeme, můžete podniknout kroky proti %s"
}
},
"preview": {
"keyboard": {
- "close_preview": "Close Preview",
- "show_next": "Show Next",
- "show_previous": "Show Previous"
+ "close_preview": "Zavřít náhled",
+ "show_next": "Zobrazit další",
+ "show_previous": "Zobrazit předchozí"
}
},
"account_list": {
- "tab_bar_hint": "Current selected profile: %s. Double tap then hold to show account switcher",
- "dismiss_account_switcher": "Dismiss Account Switcher",
- "add_account": "Add Account"
+ "tab_bar_hint": "Aktuální vybraný profil: %s. Dvojitým poklepáním zobrazíte přepínač účtů",
+ "dismiss_account_switcher": "Zrušit přepínač účtů",
+ "add_account": "Přidat účet"
},
"wizard": {
- "new_in_mastodon": "New in Mastodon",
- "multiple_account_switch_intro_description": "Switch between multiple accounts by holding the profile button.",
- "accessibility_hint": "Double tap to dismiss this wizard"
+ "new_in_mastodon": "Nový v Mastodonu",
+ "multiple_account_switch_intro_description": "Přepínání mezi více účty podržením tlačítka profilu.",
+ "accessibility_hint": "Dvojitým poklepáním tohoto průvodce odmítnete"
},
"bookmark": {
- "title": "Bookmarks"
+ "title": "Záložky"
}
}
}
diff --git a/Localization/StringsConvertor/input/cs.lproj/ios-infoPlist.json b/Localization/StringsConvertor/input/cs.lproj/ios-infoPlist.json
index c6db73de0..88bbb346a 100644
--- a/Localization/StringsConvertor/input/cs.lproj/ios-infoPlist.json
+++ b/Localization/StringsConvertor/input/cs.lproj/ios-infoPlist.json
@@ -1,6 +1,6 @@
{
- "NSCameraUsageDescription": "Used to take photo for post status",
- "NSPhotoLibraryAddUsageDescription": "Used to save photo into the Photo Library",
- "NewPostShortcutItemTitle": "New Post",
- "SearchShortcutItemTitle": "Search"
+ "NSCameraUsageDescription": "Slouží k pořízení fotografie pro příspěvek",
+ "NSPhotoLibraryAddUsageDescription": "Slouží k uložení fotografie do knihovny fotografií",
+ "NewPostShortcutItemTitle": "Nový příspěvek",
+ "SearchShortcutItemTitle": "Hledat"
}
diff --git a/Localization/StringsConvertor/input/cy.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/cy.lproj/Localizable.stringsdict
index 038eaffda..9e4c09959 100644
--- a/Localization/StringsConvertor/input/cy.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/cy.lproj/Localizable.stringsdict
@@ -74,6 +74,30 @@
%ld characters
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ %ld characters
+ one
+ 1 character
+ two
+ %ld characters
+ few
+ %ld characters
+ many
+ %ld characters
+ other
+ %ld characters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/cy.lproj/app.json b/Localization/StringsConvertor/input/cy.lproj/app.json
index bc7f75d96..fec3197be 100644
--- a/Localization/StringsConvertor/input/cy.lproj/app.json
+++ b/Localization/StringsConvertor/input/cy.lproj/app.json
@@ -74,8 +74,8 @@
"take_photo": "Take Photo",
"save_photo": "Save Photo",
"copy_photo": "Copy Photo",
- "sign_in": "Sign In",
- "sign_up": "Sign Up",
+ "sign_in": "Log in",
+ "sign_up": "Create account",
"see_more": "See More",
"preview": "Preview",
"share": "Share",
@@ -136,6 +136,12 @@
"vote": "Vote",
"closed": "Closed"
},
+ "meta_entity": {
+ "url": "Link: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Show Profile: %s",
+ "email": "Email address: %s"
+ },
"actions": {
"reply": "Reply",
"reblog": "Hybwch",
@@ -212,10 +218,16 @@
"get_started": "Get Started",
"log_in": "Log In"
},
+ "login": {
+ "title": "Welcome back",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Enter URL or search for your server"
+ }
+ },
"server_picker": {
"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.",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
"button": {
"category": {
"all": "All",
@@ -242,8 +254,7 @@
"category": "CATEGORY"
},
"input": {
- "placeholder": "Search servers",
- "search_servers_or_enter_url": "Search servers or enter URL"
+ "search_servers_or_enter_url": "Search communities or enter URL"
},
"empty_state": {
"finding_servers": "Finding available servers...",
@@ -376,7 +387,13 @@
"video": "video",
"attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.",
"description_photo": "Describe the photo for the visually-impaired...",
- "description_video": "Describe the video for the visually-impaired..."
+ "description_video": "Describe the video for the visually-impaired...",
+ "load_failed": "Load Failed",
+ "upload_failed": "Upload Failed",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "Attachment too large",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "Duration: %s",
@@ -386,7 +403,9 @@
"one_day": "1 Day",
"three_days": "3 Days",
"seven_days": "7 Days",
- "option_number": "Option %ld"
+ "option_number": "Option %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "Write an accurate warning here..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Custom Emoji Picker",
"enable_content_warning": "Enable Content Warning",
"disable_content_warning": "Disable Content Warning",
- "post_visibility_menu": "Post Visibility Menu"
+ "post_visibility_menu": "Post Visibility Menu",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "Discard Post",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Label",
"content": "Content"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
diff --git a/Localization/StringsConvertor/input/da.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/da.lproj/Localizable.stringsdict
index bdcae6ac9..eabdc3c32 100644
--- a/Localization/StringsConvertor/input/da.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/da.lproj/Localizable.stringsdict
@@ -50,6 +50,22 @@
%ld characters
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character
+ other
+ %ld characters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/da.lproj/app.json b/Localization/StringsConvertor/input/da.lproj/app.json
index 80b0882d9..3113ada74 100644
--- a/Localization/StringsConvertor/input/da.lproj/app.json
+++ b/Localization/StringsConvertor/input/da.lproj/app.json
@@ -74,8 +74,8 @@
"take_photo": "Take Photo",
"save_photo": "Save Photo",
"copy_photo": "Copy Photo",
- "sign_in": "Sign In",
- "sign_up": "Sign Up",
+ "sign_in": "Log in",
+ "sign_up": "Create account",
"see_more": "See More",
"preview": "Preview",
"share": "Share",
@@ -136,6 +136,12 @@
"vote": "Vote",
"closed": "Closed"
},
+ "meta_entity": {
+ "url": "Link: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Show Profile: %s",
+ "email": "Email address: %s"
+ },
"actions": {
"reply": "Reply",
"reblog": "Reblog",
@@ -212,10 +218,16 @@
"get_started": "Get Started",
"log_in": "Log In"
},
+ "login": {
+ "title": "Welcome back",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Enter URL or search for your server"
+ }
+ },
"server_picker": {
"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.",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
"button": {
"category": {
"all": "All",
@@ -242,8 +254,7 @@
"category": "CATEGORY"
},
"input": {
- "placeholder": "Search servers",
- "search_servers_or_enter_url": "Search servers or enter URL"
+ "search_servers_or_enter_url": "Search communities or enter URL"
},
"empty_state": {
"finding_servers": "Finding available servers...",
@@ -376,7 +387,13 @@
"video": "video",
"attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.",
"description_photo": "Describe the photo for the visually-impaired...",
- "description_video": "Describe the video for the visually-impaired..."
+ "description_video": "Describe the video for the visually-impaired...",
+ "load_failed": "Load Failed",
+ "upload_failed": "Upload Failed",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "Attachment too large",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "Duration: %s",
@@ -386,7 +403,9 @@
"one_day": "1 Day",
"three_days": "3 Days",
"seven_days": "7 Days",
- "option_number": "Option %ld"
+ "option_number": "Option %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "Write an accurate warning here..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Custom Emoji Picker",
"enable_content_warning": "Enable Content Warning",
"disable_content_warning": "Disable Content Warning",
- "post_visibility_menu": "Post Visibility Menu"
+ "post_visibility_menu": "Post Visibility Menu",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "Discard Post",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Label",
"content": "Content"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
diff --git a/Localization/StringsConvertor/input/de.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/de.lproj/Localizable.stringsdict
index c6a8a4297..1965fd02b 100644
--- a/Localization/StringsConvertor/input/de.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/de.lproj/Localizable.stringsdict
@@ -50,6 +50,22 @@
%ld Zeichen
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character
+ other
+ %ld characters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
@@ -248,9 +264,9 @@
NSStringFormatValueTypeKey
ld
one
- 1 Follower
+ 1 Folgender
other
- %ld Follower
+ %ld Folgende
date.year.left
diff --git a/Localization/StringsConvertor/input/de.lproj/app.json b/Localization/StringsConvertor/input/de.lproj/app.json
index 355bfcc1b..894a0245e 100644
--- a/Localization/StringsConvertor/input/de.lproj/app.json
+++ b/Localization/StringsConvertor/input/de.lproj/app.json
@@ -75,7 +75,7 @@
"save_photo": "Foto speichern",
"copy_photo": "Foto kopieren",
"sign_in": "Anmelden",
- "sign_up": "Registrieren",
+ "sign_up": "Konto erstellen",
"see_more": "Mehr anzeigen",
"preview": "Vorschau",
"share": "Teilen",
@@ -136,6 +136,12 @@
"vote": "Abstimmen",
"closed": "Beendet"
},
+ "meta_entity": {
+ "url": "Link: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Show Profile: %s",
+ "email": "Email address: %s"
+ },
"actions": {
"reply": "Antworten",
"reblog": "Teilen",
@@ -212,10 +218,16 @@
"get_started": "Registrieren",
"log_in": "Anmelden"
},
+ "login": {
+ "title": "Willkommen zurück",
+ "subtitle": "Melden Sie sich auf dem Server an, auf dem Sie Ihr Konto erstellt haben.",
+ "server_search_field": {
+ "placeholder": "URL eingeben oder nach Server suchen"
+ }
+ },
"server_picker": {
"title": "Wähle einen Server,\nbeliebigen Server.",
- "subtitle": "Wähle eine Gemeinschaft, die auf deinen Interessen, Region oder einem allgemeinen Zweck basiert.",
- "subtitle_extend": "Wähle eine Gemeinschaft basierend auf deinen Interessen, deiner Region oder einem allgemeinen Zweck. Jede Gemeinschaft wird von einer völlig unabhängigen Organisation oder Einzelperson betrieben.",
+ "subtitle": "Wähle einen Server basierend auf deinen Interessen oder deiner Region – oder einfach einen allgemeinen. Du kannst trotzdem mit jedem interagieren, egal auf welchem Server.",
"button": {
"category": {
"all": "Alle",
@@ -242,8 +254,7 @@
"category": "KATEGORIE"
},
"input": {
- "placeholder": "Nach Server suchen oder URL eingeben",
- "search_servers_or_enter_url": "Nach Server suchen oder URL eingeben"
+ "search_servers_or_enter_url": "Suche nach einer Community oder gib eine URL ein"
},
"empty_state": {
"finding_servers": "Verfügbare Server werden gesucht...",
@@ -376,7 +387,13 @@
"video": "Video",
"attachment_broken": "Dieses %s scheint defekt zu sein und\nkann nicht auf Mastodon hochgeladen werden.",
"description_photo": "Für Menschen mit Sehbehinderung beschreiben...",
- "description_video": "Für Menschen mit Sehbehinderung beschreiben..."
+ "description_video": "Für Menschen mit Sehbehinderung beschreiben...",
+ "load_failed": "Laden fehlgeschlagen",
+ "upload_failed": "Upload fehlgeschlagen",
+ "can_not_recognize_this_media_attachment": "Medienanhang wurde nicht erkannt",
+ "attachment_too_large": "Anhang zu groß",
+ "compressing_state": "Komprimieren...",
+ "server_processing_state": "Serververarbeitung..."
},
"poll": {
"duration_time": "Dauer: %s",
@@ -386,7 +403,9 @@
"one_day": "1 Tag",
"three_days": "3 Tage",
"seven_days": "7 Tage",
- "option_number": "Auswahlmöglichkeit %ld"
+ "option_number": "Auswahlmöglichkeit %ld",
+ "the_poll_is_invalid": "Die Umfrage ist ungültig",
+ "the_poll_has_empty_option": "Die Umfrage hat eine leere Option"
},
"content_warning": {
"placeholder": "Schreibe eine Inhaltswarnung hier..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Benutzerdefinierter Emojiwähler",
"enable_content_warning": "Inhaltswarnung einschalten",
"disable_content_warning": "Inhaltswarnung ausschalten",
- "post_visibility_menu": "Sichtbarkeitsmenü"
+ "post_visibility_menu": "Sichtbarkeitsmenü",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "Beitrag verwerfen",
@@ -425,13 +446,17 @@
"dashboard": {
"posts": "Beiträge",
"following": "Gefolgte",
- "followers": "Folger"
+ "followers": "Folgende"
},
"fields": {
"add_row": "Zeile hinzufügen",
"placeholder": {
"label": "Bezeichnung",
"content": "Inhalt"
+ },
+ "verified": {
+ "short": "Überprüft am %s",
+ "long": "Besitz des Links wurde überprüft am %s"
}
},
"segmented_control": {
@@ -460,7 +485,7 @@
},
"confirm_show_reblogs": {
"title": "Reblogs anzeigen",
- "message": "Confirm to show reblogs"
+ "message": "Bestätigen um Reblogs anzuzeigen"
},
"confirm_hide_reblogs": {
"title": "Reblogs ausblenden",
@@ -476,11 +501,11 @@
},
"follower": {
"title": "Follower",
- "footer": "Follower von anderen Servern werden nicht angezeigt."
+ "footer": "Folger, die nicht auf deinem Server registriert sind, werden nicht angezeigt."
},
"following": {
"title": "Folgende",
- "footer": "Wem das Konto folgt wird von anderen Servern werden nicht angezeigt."
+ "footer": "Gefolgte, die nicht auf deinem Server registriert sind, werden nicht angezeigt."
},
"familiarFollowers": {
"title": "Follower, die dir bekannt vorkommen",
@@ -590,7 +615,7 @@
"mentions": "Mich erwähnt",
"trigger": {
"anyone": "jeder",
- "follower": "ein Folger",
+ "follower": "ein Folgender",
"follow": "ein von mir Gefolgter",
"noone": "niemand",
"title": "Benachrichtige mich, wenn"
@@ -696,7 +721,7 @@
"accessibility_hint": "Doppeltippen, um diesen Assistenten zu schließen"
},
"bookmark": {
- "title": "Bookmarks"
+ "title": "Lesezeichen"
}
}
}
diff --git a/Localization/StringsConvertor/input/de.lproj/ios-infoPlist.json b/Localization/StringsConvertor/input/de.lproj/ios-infoPlist.json
index fe8fe1c1a..a571fba1c 100644
--- a/Localization/StringsConvertor/input/de.lproj/ios-infoPlist.json
+++ b/Localization/StringsConvertor/input/de.lproj/ios-infoPlist.json
@@ -1,6 +1,6 @@
{
- "NSCameraUsageDescription": "Verwendet um Fotos für neue Beiträge aufzunehmen",
- "NSPhotoLibraryAddUsageDescription": "Verwendet um Fotos zu speichern",
+ "NSCameraUsageDescription": "Wird verwendet, um Fotos für neue Beiträge aufzunehmen",
+ "NSPhotoLibraryAddUsageDescription": "Wird verwendet, um Foto in der Foto-Mediathek zu speichern",
"NewPostShortcutItemTitle": "Neuer Beitrag",
- "SearchShortcutItemTitle": "Suche"
+ "SearchShortcutItemTitle": "Suchen"
}
diff --git a/Localization/StringsConvertor/input/en-US.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/en-US.lproj/Localizable.stringsdict
index bdcae6ac9..eabdc3c32 100644
--- a/Localization/StringsConvertor/input/en-US.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/en-US.lproj/Localizable.stringsdict
@@ -50,6 +50,22 @@
%ld characters
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character
+ other
+ %ld characters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/en-US.lproj/app.json b/Localization/StringsConvertor/input/en-US.lproj/app.json
index 80b0882d9..3113ada74 100644
--- a/Localization/StringsConvertor/input/en-US.lproj/app.json
+++ b/Localization/StringsConvertor/input/en-US.lproj/app.json
@@ -74,8 +74,8 @@
"take_photo": "Take Photo",
"save_photo": "Save Photo",
"copy_photo": "Copy Photo",
- "sign_in": "Sign In",
- "sign_up": "Sign Up",
+ "sign_in": "Log in",
+ "sign_up": "Create account",
"see_more": "See More",
"preview": "Preview",
"share": "Share",
@@ -136,6 +136,12 @@
"vote": "Vote",
"closed": "Closed"
},
+ "meta_entity": {
+ "url": "Link: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Show Profile: %s",
+ "email": "Email address: %s"
+ },
"actions": {
"reply": "Reply",
"reblog": "Reblog",
@@ -212,10 +218,16 @@
"get_started": "Get Started",
"log_in": "Log In"
},
+ "login": {
+ "title": "Welcome back",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Enter URL or search for your server"
+ }
+ },
"server_picker": {
"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.",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
"button": {
"category": {
"all": "All",
@@ -242,8 +254,7 @@
"category": "CATEGORY"
},
"input": {
- "placeholder": "Search servers",
- "search_servers_or_enter_url": "Search servers or enter URL"
+ "search_servers_or_enter_url": "Search communities or enter URL"
},
"empty_state": {
"finding_servers": "Finding available servers...",
@@ -376,7 +387,13 @@
"video": "video",
"attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.",
"description_photo": "Describe the photo for the visually-impaired...",
- "description_video": "Describe the video for the visually-impaired..."
+ "description_video": "Describe the video for the visually-impaired...",
+ "load_failed": "Load Failed",
+ "upload_failed": "Upload Failed",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "Attachment too large",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "Duration: %s",
@@ -386,7 +403,9 @@
"one_day": "1 Day",
"three_days": "3 Days",
"seven_days": "7 Days",
- "option_number": "Option %ld"
+ "option_number": "Option %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "Write an accurate warning here..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Custom Emoji Picker",
"enable_content_warning": "Enable Content Warning",
"disable_content_warning": "Disable Content Warning",
- "post_visibility_menu": "Post Visibility Menu"
+ "post_visibility_menu": "Post Visibility Menu",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "Discard Post",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Label",
"content": "Content"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
diff --git a/Localization/StringsConvertor/input/en.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/en.lproj/Localizable.stringsdict
index bdcae6ac9..297e6675a 100644
--- a/Localization/StringsConvertor/input/en.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/en.lproj/Localizable.stringsdict
@@ -50,6 +50,28 @@
%ld characters
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ no characters
+ one
+ 1 character
+ few
+ %ld characters
+ many
+ %ld characters
+ other
+ %ld characters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/en.lproj/app.json b/Localization/StringsConvertor/input/en.lproj/app.json
index 8867385e2..3113ada74 100644
--- a/Localization/StringsConvertor/input/en.lproj/app.json
+++ b/Localization/StringsConvertor/input/en.lproj/app.json
@@ -74,8 +74,8 @@
"take_photo": "Take Photo",
"save_photo": "Save Photo",
"copy_photo": "Copy Photo",
- "sign_in": "Sign In",
- "sign_up": "Sign Up",
+ "sign_in": "Log in",
+ "sign_up": "Create account",
"see_more": "See More",
"preview": "Preview",
"share": "Share",
@@ -138,7 +138,7 @@
},
"meta_entity": {
"url": "Link: %s",
- "hashtag": "Hastag %s",
+ "hashtag": "Hashtag: %s",
"mention": "Show Profile: %s",
"email": "Email address: %s"
},
@@ -218,10 +218,16 @@
"get_started": "Get Started",
"log_in": "Log In"
},
+ "login": {
+ "title": "Welcome back",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Enter URL or search for your server"
+ }
+ },
"server_picker": {
"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.",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
"button": {
"category": {
"all": "All",
@@ -248,8 +254,7 @@
"category": "CATEGORY"
},
"input": {
- "placeholder": "Search servers",
- "search_servers_or_enter_url": "Search servers or enter URL"
+ "search_servers_or_enter_url": "Search communities or enter URL"
},
"empty_state": {
"finding_servers": "Finding available servers...",
@@ -382,7 +387,13 @@
"video": "video",
"attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.",
"description_photo": "Describe the photo for the visually-impaired...",
- "description_video": "Describe the video for the visually-impaired..."
+ "description_video": "Describe the video for the visually-impaired...",
+ "load_failed": "Load Failed",
+ "upload_failed": "Upload Failed",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "Attachment too large",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "Duration: %s",
@@ -392,7 +403,9 @@
"one_day": "1 Day",
"three_days": "3 Days",
"seven_days": "7 Days",
- "option_number": "Option %ld"
+ "option_number": "Option %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "Write an accurate warning here..."
@@ -413,7 +426,9 @@
"custom_emoji_picker": "Custom Emoji Picker",
"enable_content_warning": "Enable Content Warning",
"disable_content_warning": "Disable Content Warning",
- "post_visibility_menu": "Post Visibility Menu"
+ "post_visibility_menu": "Post Visibility Menu",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "Discard Post",
@@ -438,6 +453,10 @@
"placeholder": {
"label": "Label",
"content": "Content"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
diff --git a/Localization/StringsConvertor/input/es-AR.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/es-AR.lproj/Localizable.stringsdict
index 2bd66395a..fb939a040 100644
--- a/Localization/StringsConvertor/input/es-AR.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/es-AR.lproj/Localizable.stringsdict
@@ -50,6 +50,22 @@
%ld caracteres
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ Quedan %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 caracter
+ other
+ %ld caracteres
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/es-AR.lproj/app.json b/Localization/StringsConvertor/input/es-AR.lproj/app.json
index 62d439a3c..5be543f98 100644
--- a/Localization/StringsConvertor/input/es-AR.lproj/app.json
+++ b/Localization/StringsConvertor/input/es-AR.lproj/app.json
@@ -75,7 +75,7 @@
"save_photo": "Guardar foto",
"copy_photo": "Copiar foto",
"sign_in": "Iniciar sesión",
- "sign_up": "Registrarse",
+ "sign_up": "Crear cuenta",
"see_more": "Ver más",
"preview": "Previsualización",
"share": "Compartir",
@@ -136,6 +136,12 @@
"vote": "Votar",
"closed": "Cerrada"
},
+ "meta_entity": {
+ "url": "Enlace: %s",
+ "hashtag": "Etiqueta: %s",
+ "mention": "Mostrar perfil: %s",
+ "email": "Dirección de correo electrónico: %s"
+ },
"actions": {
"reply": "Responder",
"reblog": "Adherir",
@@ -212,10 +218,16 @@
"get_started": "Comenzá",
"log_in": "Iniciar sesión"
},
+ "login": {
+ "title": "Hola de nuevo",
+ "subtitle": "Iniciá sesión en el servidor en donde creaste tu cuenta.",
+ "server_search_field": {
+ "placeholder": "Ingresá la dirección web o buscá tu servidor"
+ }
+ },
"server_picker": {
"title": "Mastodon está compuesto de cuentas en diferentes servidores.",
- "subtitle": "Elegí un servidor basado en tus intereses, región, o de propósitos generales.",
- "subtitle_extend": "Elegí un servidor basado en tus intereses, región, o de propósitos generales. Cada servidor es operado por una organización o individuo totalmente independientes.",
+ "subtitle": "Elegí un servidor basado en tu región, en tus intereses o uno de propósitos generales. Vas a poder interactuar con cualquier cuenta de Mastodon, independientemente del servidor.",
"button": {
"category": {
"all": "Todas",
@@ -242,8 +254,7 @@
"category": "CATEGORÍA"
},
"input": {
- "placeholder": "Buscar servidores",
- "search_servers_or_enter_url": "Buscar servidores o introducir dirección web"
+ "search_servers_or_enter_url": "Buscá comunidades o ingresá la dirección web"
},
"empty_state": {
"finding_servers": "Buscando servidores disponibles…",
@@ -376,7 +387,13 @@
"video": "video",
"attachment_broken": "Este archivo de %s está roto\ny no se puede subir a Mastodon.",
"description_photo": "Describí la imagen para personas con dificultades visuales…",
- "description_video": "Describí el video para personas con dificultades visuales…"
+ "description_video": "Describí el video para personas con dificultades visuales…",
+ "load_failed": "Falló la descarga",
+ "upload_failed": "Falló la subida",
+ "can_not_recognize_this_media_attachment": "No se pudo reconocer este archivo adjunto",
+ "attachment_too_large": "Adjunto demasiado grande",
+ "compressing_state": "Comprimiendo…",
+ "server_processing_state": "Servidor procesando…"
},
"poll": {
"duration_time": "Duración: %s",
@@ -386,7 +403,9 @@
"one_day": "1 día",
"three_days": "3 días",
"seven_days": "7 días",
- "option_number": "Opción %ld"
+ "option_number": "Opción %ld",
+ "the_poll_is_invalid": "La encuesta no es válida",
+ "the_poll_has_empty_option": "La encuesta tiene opción vacía"
},
"content_warning": {
"placeholder": "Escribí una advertencia precisa acá…"
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Selector de emoji personalizado",
"enable_content_warning": "Habilitar advertencia de contenido",
"disable_content_warning": "Deshabilitar advertencia de contenido",
- "post_visibility_menu": "Menú de visibilidad del mensaje"
+ "post_visibility_menu": "Menú de visibilidad del mensaje",
+ "post_options": "Opciones de mensaje",
+ "posting_as": "Enviar como %s"
},
"keyboard": {
"discard_post": "Descartar mensaje",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Nombre de campo",
"content": "Valor de campo"
+ },
+ "verified": {
+ "short": "Verificado en %s",
+ "long": "La propiedad de este enlace fue verificada el %s"
}
},
"segmented_control": {
@@ -696,7 +721,7 @@
"accessibility_hint": "Tocá dos veces para descartar este asistente"
},
"bookmark": {
- "title": "Bookmarks"
+ "title": "Marcadores"
}
}
}
diff --git a/Localization/StringsConvertor/input/es.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/es.lproj/Localizable.stringsdict
index def3d7bba..ca07b6b28 100644
--- a/Localization/StringsConvertor/input/es.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/es.lproj/Localizable.stringsdict
@@ -50,6 +50,22 @@
%ld caracteres
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character
+ other
+ %ld characters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/es.lproj/app.json b/Localization/StringsConvertor/input/es.lproj/app.json
index 39e0f37d1..1b90bfa10 100644
--- a/Localization/StringsConvertor/input/es.lproj/app.json
+++ b/Localization/StringsConvertor/input/es.lproj/app.json
@@ -74,8 +74,8 @@
"take_photo": "Tomar foto",
"save_photo": "Guardar foto",
"copy_photo": "Copiar foto",
- "sign_in": "Iniciar sesión",
- "sign_up": "Regístrate",
+ "sign_in": "Log in",
+ "sign_up": "Create account",
"see_more": "Ver más",
"preview": "Vista previa",
"share": "Compartir",
@@ -136,6 +136,12 @@
"vote": "Vota",
"closed": "Cerrado"
},
+ "meta_entity": {
+ "url": "Link: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Show Profile: %s",
+ "email": "Email address: %s"
+ },
"actions": {
"reply": "Responder",
"reblog": "Rebloguear",
@@ -212,10 +218,16 @@
"get_started": "Empezar",
"log_in": "Iniciar sesión"
},
+ "login": {
+ "title": "Welcome back",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Enter URL or search for your server"
+ }
+ },
"server_picker": {
"title": "Elige un servidor,\ncualquier servidor.",
- "subtitle": "Elige una comunidad relacionada con tus intereses, con tu región o una más genérica.",
- "subtitle_extend": "Elige una comunidad relacionada con tus intereses, con tu región o una más genérica. Cada comunidad está operada por una organización o individuo completamente independiente.",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
"button": {
"category": {
"all": "Todas",
@@ -242,8 +254,7 @@
"category": "CATEGORÍA"
},
"input": {
- "placeholder": "Encuentra un servidor o únete al tuyo propio...",
- "search_servers_or_enter_url": "Buscar servidores o introducir la URL"
+ "search_servers_or_enter_url": "Search communities or enter URL"
},
"empty_state": {
"finding_servers": "Encontrando servidores disponibles...",
@@ -376,7 +387,13 @@
"video": "vídeo",
"attachment_broken": "Este %s está roto y no puede\nsubirse a Mastodon.",
"description_photo": "Describe la foto para los usuarios con dificultad visual...",
- "description_video": "Describe el vídeo para los usuarios con dificultad visual..."
+ "description_video": "Describe el vídeo para los usuarios con dificultad visual...",
+ "load_failed": "Load Failed",
+ "upload_failed": "Upload Failed",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "Attachment too large",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "Duración: %s",
@@ -386,7 +403,9 @@
"one_day": "1 Día",
"three_days": "4 Días",
"seven_days": "7 Días",
- "option_number": "Opción %ld"
+ "option_number": "Opción %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "Escribe una advertencia precisa aquí..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Selector de Emojis Personalizados",
"enable_content_warning": "Activar Advertencia de Contenido",
"disable_content_warning": "Desactivar Advertencia de Contenido",
- "post_visibility_menu": "Menú de Visibilidad de la Publicación"
+ "post_visibility_menu": "Menú de Visibilidad de la Publicación",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "Descartar Publicación",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Nombre para el campo",
"content": "Contenido"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
diff --git a/Localization/StringsConvertor/input/eu.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/eu.lproj/Localizable.stringsdict
index 0159a7da9..057ca4010 100644
--- a/Localization/StringsConvertor/input/eu.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/eu.lproj/Localizable.stringsdict
@@ -50,6 +50,22 @@
%ld karaktere
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character
+ other
+ %ld characters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/eu.lproj/app.json b/Localization/StringsConvertor/input/eu.lproj/app.json
index 5c2e16601..3da0d6a00 100644
--- a/Localization/StringsConvertor/input/eu.lproj/app.json
+++ b/Localization/StringsConvertor/input/eu.lproj/app.json
@@ -74,8 +74,8 @@
"take_photo": "Atera argazkia",
"save_photo": "Gorde argazkia",
"copy_photo": "Kopiatu argazkia",
- "sign_in": "Hasi saioa",
- "sign_up": "Eman Izena",
+ "sign_in": "Log in",
+ "sign_up": "Create account",
"see_more": "Ikusi gehiago",
"preview": "Aurrebista",
"share": "Partekatu",
@@ -136,6 +136,12 @@
"vote": "Bozkatu",
"closed": "Itxita"
},
+ "meta_entity": {
+ "url": "Link: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Show Profile: %s",
+ "email": "Email address: %s"
+ },
"actions": {
"reply": "Erantzun",
"reblog": "Bultzada",
@@ -212,10 +218,16 @@
"get_started": "Nola hasi",
"log_in": "Hasi saioa"
},
+ "login": {
+ "title": "Welcome back",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Enter URL or search for your server"
+ }
+ },
"server_picker": {
"title": "Aukeratu zerbitzari bat,\nedozein zerbitzari.",
- "subtitle": "Aukeratu komunitate bat zure interes edo lurraldearen arabera, edo erabilera orokorreko bat.",
- "subtitle_extend": "Aukeratu komunitate bat zure interes edo lurraldearen arabera, edo erabilera orokorreko bat. Komunitate bakoitza erakunde edo norbanako independente batek kudeatzen du.",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
"button": {
"category": {
"all": "Guztiak",
@@ -242,8 +254,7 @@
"category": "KATEGORIA"
},
"input": {
- "placeholder": "Bilatu zerbitzari bat edo sortu zurea...",
- "search_servers_or_enter_url": "Search servers or enter URL"
+ "search_servers_or_enter_url": "Search communities or enter URL"
},
"empty_state": {
"finding_servers": "Erabilgarri dauden zerbitzariak bilatzen...",
@@ -376,7 +387,13 @@
"video": "bideoa",
"attachment_broken": "%s hondatuta dago eta ezin da\nMastodonera igo.",
"description_photo": "Deskribatu argazkia ikusmen arazoak dituztenentzat...",
- "description_video": "Deskribatu bideoa ikusmen arazoak dituztenentzat..."
+ "description_video": "Deskribatu bideoa ikusmen arazoak dituztenentzat...",
+ "load_failed": "Load Failed",
+ "upload_failed": "Upload Failed",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "Attachment too large",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "Iraupena: %s",
@@ -386,7 +403,9 @@
"one_day": "Egun 1",
"three_days": "3 egun",
"seven_days": "7 egun",
- "option_number": "%ld aukera"
+ "option_number": "%ld aukera",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "Idatzi abisu zehatz bat hemen..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Emoji pertsonalizatuen hautatzailea",
"enable_content_warning": "Gaitu edukiaren abisua",
"disable_content_warning": "Desgaitu edukiaren abisua",
- "post_visibility_menu": "Bidalketaren ikusgaitasunaren menua"
+ "post_visibility_menu": "Bidalketaren ikusgaitasunaren menua",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "Baztertu bidalketa",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Etiketa",
"content": "Edukia"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
diff --git a/Localization/StringsConvertor/input/fi.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/fi.lproj/Localizable.stringsdict
index 8048edf2d..ccfee35c9 100644
--- a/Localization/StringsConvertor/input/fi.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/fi.lproj/Localizable.stringsdict
@@ -50,6 +50,22 @@
%ld merkkiä
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character
+ other
+ %ld characters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/fi.lproj/app.json b/Localization/StringsConvertor/input/fi.lproj/app.json
index d6210c4d5..7dafe7fd1 100644
--- a/Localization/StringsConvertor/input/fi.lproj/app.json
+++ b/Localization/StringsConvertor/input/fi.lproj/app.json
@@ -74,8 +74,8 @@
"take_photo": "Ota kuva",
"save_photo": "Tallenna kuva",
"copy_photo": "Kopioi kuva",
- "sign_in": "Kirjaudu sisään",
- "sign_up": "Rekisteröidy",
+ "sign_in": "Log in",
+ "sign_up": "Create account",
"see_more": "Näytä lisää",
"preview": "Esikatselu",
"share": "Jaa",
@@ -136,6 +136,12 @@
"vote": "Vote",
"closed": "Suljettu"
},
+ "meta_entity": {
+ "url": "Link: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Show Profile: %s",
+ "email": "Email address: %s"
+ },
"actions": {
"reply": "Vastaa",
"reblog": "Jaa edelleen",
@@ -212,10 +218,16 @@
"get_started": "Get Started",
"log_in": "Log In"
},
+ "login": {
+ "title": "Welcome back",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Enter URL or search for your server"
+ }
+ },
"server_picker": {
"title": "Valitse palvelin,\nmikä tahansa palvelin.",
- "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.",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
"button": {
"category": {
"all": "Kaikki",
@@ -242,8 +254,7 @@
"category": "KATEGORIA"
},
"input": {
- "placeholder": "Etsi palvelin tai liity omaan...",
- "search_servers_or_enter_url": "Search servers or enter URL"
+ "search_servers_or_enter_url": "Search communities or enter URL"
},
"empty_state": {
"finding_servers": "Etsistään saatavilla olevia palvelimia...",
@@ -376,7 +387,13 @@
"video": "video",
"attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.",
"description_photo": "Kuvaile kuva näkövammaisille...",
- "description_video": "Kuvaile video näkövammaisille..."
+ "description_video": "Kuvaile video näkövammaisille...",
+ "load_failed": "Load Failed",
+ "upload_failed": "Upload Failed",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "Attachment too large",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "Kesto: %s",
@@ -386,7 +403,9 @@
"one_day": "1 päivä",
"three_days": "3 päivää",
"seven_days": "7 päivää",
- "option_number": "Vaihtoehto %ld"
+ "option_number": "Vaihtoehto %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "Kirjoita tarkka varoitus tähän..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Mukautettu emojivalitsin",
"enable_content_warning": "Ota sisältövaroitus käyttöön",
"disable_content_warning": "Poista sisältövaroitus käytöstä",
- "post_visibility_menu": "Julkaisun näkyvyysvalikko"
+ "post_visibility_menu": "Julkaisun näkyvyysvalikko",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "Hylkää julkaisu",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Nimi",
"content": "Sisältö"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
diff --git a/Localization/StringsConvertor/input/fr.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/fr.lproj/Localizable.stringsdict
index d9d860a47..4eb068697 100644
--- a/Localization/StringsConvertor/input/fr.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/fr.lproj/Localizable.stringsdict
@@ -50,6 +50,22 @@
%ld caractères
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ restants
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 caractère
+ other
+ %ld caractères
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/fr.lproj/app.json b/Localization/StringsConvertor/input/fr.lproj/app.json
index ed53d1096..fc8fd0e7a 100644
--- a/Localization/StringsConvertor/input/fr.lproj/app.json
+++ b/Localization/StringsConvertor/input/fr.lproj/app.json
@@ -136,6 +136,12 @@
"vote": "Voter",
"closed": "Fermé"
},
+ "meta_entity": {
+ "url": "Lien : %s",
+ "hashtag": "Hashtag : %s",
+ "mention": "Afficher le profile : %s",
+ "email": "Adresse e-mail : %s"
+ },
"actions": {
"reply": "Répondre",
"reblog": "Rebloguer",
@@ -212,10 +218,16 @@
"get_started": "Prise en main",
"log_in": "Se connecter"
},
+ "login": {
+ "title": "Content de vous revoir",
+ "subtitle": "Connectez-vous sur le serveur sur lequel vous avez créé votre compte.",
+ "server_search_field": {
+ "placeholder": "Entrez l'URL ou recherchez votre serveur"
+ }
+ },
"server_picker": {
"title": "Choisissez un serveur,\nn'importe quel serveur.",
- "subtitle": "Choisissez une communauté en fonction de vos intérêts, de votre région ou de votre objectif général.",
- "subtitle_extend": "Choisissez une communauté basée sur vos intérêts, votre région ou un but général. Chaque communauté est gérée par une organisation ou un individu entièrement indépendant.",
+ "subtitle": "Choisissez un serveur basé sur votre région, vos intérêts ou un généraliste. Vous pouvez toujours discuter avec n'importe qui sur Mastodon, indépendamment de vos serveurs.",
"button": {
"category": {
"all": "Tout",
@@ -242,8 +254,7 @@
"category": "CATÉGORIE"
},
"input": {
- "placeholder": "Trouvez un serveur ou rejoignez le vôtre...",
- "search_servers_or_enter_url": "Rechercher des serveurs ou entrer une URL"
+ "search_servers_or_enter_url": "Rechercher parmi les communautés ou renseigner une URL"
},
"empty_state": {
"finding_servers": "Recherche des serveurs disponibles...",
@@ -376,7 +387,13 @@
"video": "vidéo",
"attachment_broken": "Ce %s est brisé et ne peut pas être\ntéléversé sur Mastodon.",
"description_photo": "Décrire cette photo pour les personnes malvoyantes...",
- "description_video": "Décrire cette vidéo pour les personnes malvoyantes..."
+ "description_video": "Décrire cette vidéo pour les personnes malvoyantes...",
+ "load_failed": "Échec du chargement",
+ "upload_failed": "Échec de l’envoi",
+ "can_not_recognize_this_media_attachment": "Impossible de reconnaître cette pièce jointe",
+ "attachment_too_large": "La pièce jointe est trop volumineuse",
+ "compressing_state": "Compression...",
+ "server_processing_state": "Traitement du serveur..."
},
"poll": {
"duration_time": "Durée: %s",
@@ -386,7 +403,9 @@
"one_day": "1 Jour",
"three_days": "3 jour",
"seven_days": "7 jour",
- "option_number": "Option %ld"
+ "option_number": "Option %ld",
+ "the_poll_is_invalid": "Le sondage est invalide",
+ "the_poll_has_empty_option": "Le sondage n'a pas d'options"
},
"content_warning": {
"placeholder": "Écrivez un avertissement précis ici..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Sélecteur d’émojis personnalisés",
"enable_content_warning": "Basculer l’avertissement de contenu",
"disable_content_warning": "Désactiver l'avertissement de contenu",
- "post_visibility_menu": "Menu de Visibilité de la publication"
+ "post_visibility_menu": "Menu de Visibilité de la publication",
+ "post_options": "Options de publication",
+ "posting_as": "Publié en tant que %s"
},
"keyboard": {
"discard_post": "Rejeter la publication",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Étiquette",
"content": "Contenu"
+ },
+ "verified": {
+ "short": "Vérifié le %s",
+ "long": "La propriété de ce lien a été vérifiée le %s"
}
},
"segmented_control": {
@@ -696,7 +721,7 @@
"accessibility_hint": "Tapotez deux fois pour fermer cet assistant"
},
"bookmark": {
- "title": "Bookmarks"
+ "title": "Favoris"
}
}
}
diff --git a/Localization/StringsConvertor/input/gd.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/gd.lproj/Localizable.stringsdict
index d0ccb5f41..9b3e69ea7 100644
--- a/Localization/StringsConvertor/input/gd.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/gd.lproj/Localizable.stringsdict
@@ -62,6 +62,26 @@
%ld caractar
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ air fhàgail
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ %ld charactar
+ two
+ %ld charactar
+ few
+ %ld caractaran
+ other
+ %ld caractar
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/gd.lproj/app.json b/Localization/StringsConvertor/input/gd.lproj/app.json
index a2062a89b..74666cb0c 100644
--- a/Localization/StringsConvertor/input/gd.lproj/app.json
+++ b/Localization/StringsConvertor/input/gd.lproj/app.json
@@ -75,7 +75,7 @@
"save_photo": "Sàbhail an dealbh",
"copy_photo": "Dèan lethbhreac dhen dealbh",
"sign_in": "Clàraich a-steach",
- "sign_up": "Clàraich leinn",
+ "sign_up": "Cruthaich cunntas",
"see_more": "Seall a bharrachd",
"preview": "Ro-sheall",
"share": "Co-roinn",
@@ -136,6 +136,12 @@
"vote": "Cuir bhòt",
"closed": "Dùinte"
},
+ "meta_entity": {
+ "url": "Ceangal: %s",
+ "hashtag": "Taga hais: %s",
+ "mention": "Seall a’ phròifil: %s",
+ "email": "Seòladh puist-d: %s"
+ },
"actions": {
"reply": "Freagair",
"reblog": "Brosnaich",
@@ -181,8 +187,8 @@
"unmute_user": "Dì-mhùch %s",
"muted": "’Ga mhùchadh",
"edit_info": "Deasaich",
- "show_reblogs": "Show Reblogs",
- "hide_reblogs": "Hide Reblogs"
+ "show_reblogs": "Seall na brosnachaidhean",
+ "hide_reblogs": "Falaich na brosnachaidhean"
},
"timeline": {
"filtered": "Criathraichte",
@@ -212,10 +218,16 @@
"get_started": "Dèan toiseach-tòiseachaidh",
"log_in": "Clàraich a-steach"
},
+ "login": {
+ "title": "Fàilte air ais",
+ "subtitle": "Clàraich a-steach air an fhrithealaiche far an do chruthaich thu an cunntas agad.",
+ "server_search_field": {
+ "placeholder": "Cuir a-steach URL an fhrithealaiche agad"
+ }
+ },
"server_picker": {
"title": "Tha cleachdaichean Mhastodon air iomadh frithealaiche eadar-dhealaichte.",
- "subtitle": "Tagh frithealaiche stèidhichte air d’ ùidhean, air far a bheil thu no fear coitcheann.",
- "subtitle_extend": "Tagh frithealaiche stèidhichte air d’ ùidhean, air far a bheil thu no fear coitcheann. Tha gach frithealaiche fo stiùireadh buidhinn no neach neo-eisimeilich fa leth.",
+ "subtitle": "Tagh frithealaiche stèidhichte air na sgìre agad, d’ ùidhean, air far a bheil thu no fear coitcheann. ’S urrainn dhut fhathast conaltradh le duine sam bith air Mastodon ge b’ e na frithealaichean agaibh-se.",
"button": {
"category": {
"all": "Na h-uile",
@@ -242,8 +254,7 @@
"category": "ROINN-SEÒRSA"
},
"input": {
- "placeholder": "Lorg frithealaiche",
- "search_servers_or_enter_url": "Lorg frithealaiche no cuir a-steach URL"
+ "search_servers_or_enter_url": "Lorg coimhearsnachd no cuir a-steach URL"
},
"empty_state": {
"finding_servers": "A’ lorg nam frithealaichean ri am faighinn…",
@@ -376,7 +387,13 @@
"video": "video",
"attachment_broken": "Seo %s a tha briste is cha ghabh\na luchdadh suas gu Mastodon.",
"description_photo": "Mìnich an dealbh dhan fheadhainn air a bheil cion-lèirsinne…",
- "description_video": "Mìnich a’ video dhan fheadhainn air a bheil cion-lèirsinne…"
+ "description_video": "Mìnich a’ video dhan fheadhainn air a bheil cion-lèirsinne…",
+ "load_failed": "Dh’fhàillig leis an luchdadh",
+ "upload_failed": "Dh’fhàillig leis an luchdadh suas",
+ "can_not_recognize_this_media_attachment": "Cha do dh’aithnich sinn an ceanglachan meadhain seo",
+ "attachment_too_large": "Tha an ceanglachan ro mhòr",
+ "compressing_state": "’Ga dhùmhlachadh…",
+ "server_processing_state": "Tha am frithealaiche ’ga phròiseasadh…"
},
"poll": {
"duration_time": "Faide: %s",
@@ -386,7 +403,9 @@
"one_day": "Latha",
"three_days": "3 làithean",
"seven_days": "Seachdain",
- "option_number": "Roghainn %ld"
+ "option_number": "Roghainn %ld",
+ "the_poll_is_invalid": "Tha an cunntas-bheachd mì-dhligheach",
+ "the_poll_has_empty_option": "Tha roghainn fhalamh aig a’ chunntas-bheachd"
},
"content_warning": {
"placeholder": "Sgrìobh rabhadh pongail an-seo…"
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Roghnaichear nan Emoji gnàthaichte",
"enable_content_warning": "Cuir rabhadh susbainte an comas",
"disable_content_warning": "Cuir rabhadh susbainte à comas",
- "post_visibility_menu": "Clàr-taice faicsinneachd a’ phuist"
+ "post_visibility_menu": "Clàr-taice faicsinneachd a’ phuist",
+ "post_options": "Roghainnean postaidh",
+ "posting_as": "A’ postadh mar %s"
},
"keyboard": {
"discard_post": "Tilg air falbh am post",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Leubail",
"content": "Susbaint"
+ },
+ "verified": {
+ "short": "Air a dhearbhadh %s",
+ "long": "Chaidh dearbhadh cò leis a tha an ceangal seo %s"
}
},
"segmented_control": {
@@ -459,12 +484,12 @@
"message": "Dearbh dì-bhacadh %s"
},
"confirm_show_reblogs": {
- "title": "Show Reblogs",
- "message": "Confirm to show reblogs"
+ "title": "Seall na brosnachaidhean",
+ "message": "Dearbh sealladh nam brosnachaidhean"
},
"confirm_hide_reblogs": {
- "title": "Hide Reblogs",
- "message": "Confirm to hide reblogs"
+ "title": "Falaich na brosnachaidhean",
+ "message": "Dearbh falach nam brosnachaidhean"
}
},
"accessibility": {
@@ -696,7 +721,7 @@
"accessibility_hint": "Thoir gnogag dhùbailte a’ leigeil seachad an draoidh seo"
},
"bookmark": {
- "title": "Bookmarks"
+ "title": "Comharran-lìn"
}
}
}
diff --git a/Localization/StringsConvertor/input/gl.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/gl.lproj/Localizable.stringsdict
index ff9d87c18..51b146ed4 100644
--- a/Localization/StringsConvertor/input/gl.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/gl.lproj/Localizable.stringsdict
@@ -50,6 +50,22 @@
%ld caracteres
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ restantes
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 caracter
+ other
+ %ld caracteres
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/gl.lproj/app.json b/Localization/StringsConvertor/input/gl.lproj/app.json
index 513573f79..15c7a612a 100644
--- a/Localization/StringsConvertor/input/gl.lproj/app.json
+++ b/Localization/StringsConvertor/input/gl.lproj/app.json
@@ -75,7 +75,7 @@
"save_photo": "Gardar foto",
"copy_photo": "Copiar foto",
"sign_in": "Acceder",
- "sign_up": "Inscribirse",
+ "sign_up": "Crear conta",
"see_more": "Ver máis",
"preview": "Vista previa",
"share": "Compartir",
@@ -136,6 +136,12 @@
"vote": "Votar",
"closed": "Pechada"
},
+ "meta_entity": {
+ "url": "Ligazón: %s",
+ "hashtag": "Cancelo: %s",
+ "mention": "Mostrar Perfil: %s",
+ "email": "Enderezo de email: %s"
+ },
"actions": {
"reply": "Responder",
"reblog": "Promover",
@@ -181,8 +187,8 @@
"unmute_user": "Deixar de acalar a @%s",
"muted": "Acalada",
"edit_info": "Editar info",
- "show_reblogs": "Show Reblogs",
- "hide_reblogs": "Hide Reblogs"
+ "show_reblogs": "Mostrar Promocións",
+ "hide_reblogs": "Agochar Promocións"
},
"timeline": {
"filtered": "Filtrado",
@@ -212,10 +218,16 @@
"get_started": "Crear conta",
"log_in": "Acceder"
},
+ "login": {
+ "title": "Benvido outra vez",
+ "subtitle": "Conéctate ao servidor no que creaches a conta.",
+ "server_search_field": {
+ "placeholder": "Escribe o URL ou busca o teu servidor"
+ }
+ },
"server_picker": {
"title": "Mastodon fórmano as persoas das diferentes comunidades.",
- "subtitle": "Elixe unha comunidade en función dos teus intereses, rexión ou unha de propósito xeral.",
- "subtitle_extend": "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.",
+ "subtitle": "Elixe un servidor en función dos teus intereses, rexión o un de propósito xeral. Poderás conversar con calquera en Mastodon, independentemente do servidor que elixas.",
"button": {
"category": {
"all": "Todo",
@@ -242,8 +254,7 @@
"category": "CATEGORÍA"
},
"input": {
- "placeholder": "Buscar comunidades",
- "search_servers_or_enter_url": "Busca un servidor ou escribe URL"
+ "search_servers_or_enter_url": "Busca comunidades ou escribe URL"
},
"empty_state": {
"finding_servers": "Buscando servidores dispoñibles...",
@@ -376,7 +387,13 @@
"video": "vídeo",
"attachment_broken": "Este %s está estragado e non pode\nser subido a Mastodon.",
"description_photo": "Describe a foto para persoas con problemas visuais...",
- "description_video": "Describe o vídeo para persoas con problemas visuais..."
+ "description_video": "Describe o vídeo para persoas con problemas visuais...",
+ "load_failed": "Fallou a carga",
+ "upload_failed": "Erro na subida",
+ "can_not_recognize_this_media_attachment": "Non se recoñece o tipo de multimedia",
+ "attachment_too_large": "Adxunto demasiado grande",
+ "compressing_state": "Comprimindo...",
+ "server_processing_state": "Procesando no servidor..."
},
"poll": {
"duration_time": "Duración: %s",
@@ -386,7 +403,9 @@
"one_day": "1 Día",
"three_days": "3 Días",
"seven_days": "7 Días",
- "option_number": "Opción %ld"
+ "option_number": "Opción %ld",
+ "the_poll_is_invalid": "A enquisa non é válida",
+ "the_poll_has_empty_option": "A enquisa ten unha opción baleira"
},
"content_warning": {
"placeholder": "Escribe o teu aviso aquí..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Selector emoji personalizado",
"enable_content_warning": "Marcar con Aviso sobre o contido",
"disable_content_warning": "Retirar Aviso sobre o contido",
- "post_visibility_menu": "Visibilidade da publicación"
+ "post_visibility_menu": "Visibilidade da publicación",
+ "post_options": "Opcións da publicación",
+ "posting_as": "Publicando como %s"
},
"keyboard": {
"discard_post": "Descartar publicación",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Etiqueta",
"content": "Contido"
+ },
+ "verified": {
+ "short": "Verificada en %s",
+ "long": "A propiedade desta ligazón foi verificada o %s"
}
},
"segmented_control": {
@@ -459,12 +484,12 @@
"message": "Confirma o desbloqueo de %s"
},
"confirm_show_reblogs": {
- "title": "Show Reblogs",
- "message": "Confirm to show reblogs"
+ "title": "Mostrar Promocións",
+ "message": "Confirma para ver promocións"
},
"confirm_hide_reblogs": {
- "title": "Hide Reblogs",
- "message": "Confirm to hide reblogs"
+ "title": "Agochar Promocións",
+ "message": "Confirma para agochar promocións"
}
},
"accessibility": {
@@ -696,7 +721,7 @@
"accessibility_hint": "Dobre toque para desbotar este asistente"
},
"bookmark": {
- "title": "Bookmarks"
+ "title": "Marcadores"
}
}
}
diff --git a/Localization/StringsConvertor/input/hi.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/hi.lproj/Localizable.stringsdict
index bdcae6ac9..eabdc3c32 100644
--- a/Localization/StringsConvertor/input/hi.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/hi.lproj/Localizable.stringsdict
@@ -50,6 +50,22 @@
%ld characters
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character
+ other
+ %ld characters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/hi.lproj/app.json b/Localization/StringsConvertor/input/hi.lproj/app.json
index d9ef32b3a..f9414b6c0 100644
--- a/Localization/StringsConvertor/input/hi.lproj/app.json
+++ b/Localization/StringsConvertor/input/hi.lproj/app.json
@@ -74,8 +74,8 @@
"take_photo": "Take Photo",
"save_photo": "Save Photo",
"copy_photo": "Copy Photo",
- "sign_in": "Sign In",
- "sign_up": "Sign Up",
+ "sign_in": "Log in",
+ "sign_up": "Create account",
"see_more": "See More",
"preview": "Preview",
"share": "Share",
@@ -136,6 +136,12 @@
"vote": "Vote",
"closed": "Closed"
},
+ "meta_entity": {
+ "url": "Link: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Show Profile: %s",
+ "email": "Email address: %s"
+ },
"actions": {
"reply": "Reply",
"reblog": "Reblog",
@@ -212,10 +218,16 @@
"get_started": "Get Started",
"log_in": "Log In"
},
+ "login": {
+ "title": "Welcome back",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Enter URL or search for your server"
+ }
+ },
"server_picker": {
"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.",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
"button": {
"category": {
"all": "All",
@@ -242,8 +254,7 @@
"category": "CATEGORY"
},
"input": {
- "placeholder": "Search servers",
- "search_servers_or_enter_url": "Search servers or enter URL"
+ "search_servers_or_enter_url": "Search communities or enter URL"
},
"empty_state": {
"finding_servers": "Finding available servers...",
@@ -376,7 +387,13 @@
"video": "video",
"attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.",
"description_photo": "Describe the photo for the visually-impaired...",
- "description_video": "Describe the video for the visually-impaired..."
+ "description_video": "Describe the video for the visually-impaired...",
+ "load_failed": "Load Failed",
+ "upload_failed": "Upload Failed",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "Attachment too large",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "Duration: %s",
@@ -386,7 +403,9 @@
"one_day": "1 Day",
"three_days": "3 Days",
"seven_days": "7 Days",
- "option_number": "Option %ld"
+ "option_number": "Option %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "Write an accurate warning here..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Custom Emoji Picker",
"enable_content_warning": "Enable Content Warning",
"disable_content_warning": "Disable Content Warning",
- "post_visibility_menu": "Post Visibility Menu"
+ "post_visibility_menu": "Post Visibility Menu",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "Discard Post",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Label",
"content": "Content"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
diff --git a/Localization/StringsConvertor/input/id.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/id.lproj/Localizable.stringsdict
index 2635defb8..9b8aca01c 100644
--- a/Localization/StringsConvertor/input/id.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/id.lproj/Localizable.stringsdict
@@ -44,6 +44,20 @@
%ld karakter
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld characters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/id.lproj/app.json b/Localization/StringsConvertor/input/id.lproj/app.json
index 607e9a638..4f2050792 100644
--- a/Localization/StringsConvertor/input/id.lproj/app.json
+++ b/Localization/StringsConvertor/input/id.lproj/app.json
@@ -6,29 +6,29 @@
"please_try_again_later": "Silakan coba lagi nanti."
},
"sign_up_failure": {
- "title": "Sign Up Failure"
+ "title": "Gagal Mendaftar"
},
"server_error": {
"title": "Kesalahan Server"
},
"vote_failure": {
- "title": "Vote Failure",
+ "title": "Gagal Voting",
"poll_ended": "Japat telah berakhir"
},
"discard_post_content": {
"title": "Hapus Draf",
- "message": "Confirm to discard composed post content."
+ "message": "Konfirmasi untuk mengabaikan postingan yang dibuat."
},
"publish_post_failure": {
- "title": "Publish Failure",
- "message": "Failed to publish the post.\nPlease check your internet connection.",
+ "title": "Gagal Mempublikasikan",
+ "message": "Gagal mempublikasikan postingan.\nMohon periksa koneksi Internet Anda.",
"attachments_message": {
"video_attach_with_photo": "Tidak dapat melampirkan video di postingan yang sudah mengandung gambar.",
"more_than_one_video": "Tidak dapat melampirkan lebih dari satu video."
}
},
"edit_profile_failure": {
- "title": "Edit Profile Error",
+ "title": "Masalah dalam mengubah profil",
"message": "Tidak dapat menyunting profil. Harap coba lagi."
},
"sign_out": {
@@ -37,16 +37,16 @@
"confirm": "Keluar"
},
"block_domain": {
- "title": "Are you really, really sure you want to block the entire %s? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain and any of your followers from that domain will be removed.",
+ "title": "Apakah Anda benar, benar yakin ingin memblokir keseluruhan %s? Dalam kebanyakan kasus, beberapa pemblokiran atau pembisuan yang ditargetkan sudah cukup dan lebih disukai. Anda tidak akan melihat konten dari domain tersebut dan semua pengikut Anda dari domain itu akan dihapus.",
"block_entire_domain": "Blokir Domain"
},
"save_photo_failure": {
- "title": "Save Photo Failure",
- "message": "Please enable the photo library access permission to save the photo."
+ "title": "Gagal Menyimpan Foto",
+ "message": "Mohon aktifkan izin akses pustaka foto untuk menyimpan foto."
},
"delete_post": {
"title": "Apakah Anda yakin ingin menghapus postingan ini?",
- "message": "Are you sure you want to delete this post?"
+ "message": "Apakah Anda yakin untuk menghapus kiriman ini?"
},
"clean_cache": {
"title": "Bersihkan Cache",
@@ -67,24 +67,24 @@
"done": "Selesai",
"confirm": "Konfirmasi",
"continue": "Lanjut",
- "compose": "Compose",
+ "compose": "Tulis",
"cancel": "Batal",
- "discard": "Discard",
+ "discard": "Buang",
"try_again": "Coba Lagi",
- "take_photo": "Take Photo",
+ "take_photo": "Ambil Foto",
"save_photo": "Simpan Foto",
"copy_photo": "Salin Foto",
"sign_in": "Masuk",
- "sign_up": "Daftar",
+ "sign_up": "Buat akun",
"see_more": "Lihat lebih banyak",
"preview": "Pratinjau",
"share": "Bagikan",
"share_user": "Bagikan %s",
"share_post": "Bagikan Postingan",
"open_in_safari": "Buka di Safari",
- "open_in_browser": "Open in Browser",
+ "open_in_browser": "Buka di Peramban",
"find_people": "Cari orang untuk diikuti",
- "manually_search": "Manually search instead",
+ "manually_search": "Cari secara manual saja",
"skip": "Lewati",
"reply": "Balas",
"report_user": "Laporkan %s",
@@ -111,16 +111,16 @@
"next_status": "Postingan Selanjutnya",
"open_status": "Buka Postingan",
"open_author_profile": "Buka Profil Penulis",
- "open_reblogger_profile": "Open Reblogger's Profile",
+ "open_reblogger_profile": "Buka Profil Reblogger",
"reply_status": "Balas Postingan",
- "toggle_reblog": "Toggle Reblog on Post",
- "toggle_favorite": "Toggle Favorite on Post",
- "toggle_content_warning": "Toggle Content Warning",
- "preview_image": "Preview Image"
+ "toggle_reblog": "Nyalakan Reblog pada Postingan",
+ "toggle_favorite": "Nyalakan Favorit pada Postingan",
+ "toggle_content_warning": "Nyalakan Peringatan Konten",
+ "preview_image": "Pratinjau Gambar"
},
"segmented_control": {
- "previous_section": "Previous Section",
- "next_section": "Next Section"
+ "previous_section": "Bagian Sebelumnya",
+ "next_section": "Bagian Selanjutnya"
}
},
"status": {
@@ -129,25 +129,31 @@
"show_post": "Tampilkan Postingan",
"show_user_profile": "Tampilkan Profil Pengguna",
"content_warning": "Peringatan Konten",
- "sensitive_content": "Sensitive Content",
+ "sensitive_content": "Konten Sensitif",
"media_content_warning": "Ketuk di mana saja untuk melihat",
- "tap_to_reveal": "Tap to reveal",
+ "tap_to_reveal": "Ketuk untuk mengungkap",
"poll": {
- "vote": "Vote",
+ "vote": "Pilih",
"closed": "Ditutup"
},
+ "meta_entity": {
+ "url": "Tautan: %s",
+ "hashtag": "Tagar: %s",
+ "mention": "Tampilkan Profile: %s",
+ "email": "Alamat email: %s"
+ },
"actions": {
"reply": "Balas",
"reblog": "Reblog",
- "unreblog": "Undo reblog",
+ "unreblog": "Batalkan reblog",
"favorite": "Favorit",
- "unfavorite": "Unfavorite",
+ "unfavorite": "Batalkan favorit",
"menu": "Menu",
- "hide": "Hide",
- "show_image": "Show image",
- "show_gif": "Show GIF",
- "show_video_player": "Show video player",
- "tap_then_hold_to_show_menu": "Tap then hold to show menu"
+ "hide": "Sembunyikan",
+ "show_image": "Tampilkan gambar",
+ "show_gif": "Tampilkan GIF",
+ "show_video_player": "Tampilkan pemutar video",
+ "tap_then_hold_to_show_menu": "Ketuk lalu tahan untuk menampilkan menu"
},
"tag": {
"url": "URL",
@@ -159,16 +165,16 @@
},
"visibility": {
"unlisted": "Everyone can see this post but not display in the public timeline.",
- "private": "Only their followers can see this post.",
- "private_from_me": "Only my followers can see this post.",
- "direct": "Only mentioned user can see this post."
+ "private": "Hanya pengikut mereka yang dapat melihat postingan ini.",
+ "private_from_me": "Hanya pengikut saya yang dapat melihat postingan ini.",
+ "direct": "Hanya pengguna yang disebut yang dapat melihat postingan ini."
}
},
"friendship": {
"follow": "Ikuti",
"following": "Mengikuti",
- "request": "Request",
- "pending": "Pending",
+ "request": "Minta",
+ "pending": "Tertunda",
"block": "Blokir",
"block_user": "Blokir %s",
"block_domain": "Blokir %s",
@@ -181,8 +187,8 @@
"unmute_user": "Berhenti membisukan %s",
"muted": "Dibisukan",
"edit_info": "Sunting Info",
- "show_reblogs": "Show Reblogs",
- "hide_reblogs": "Hide Reblogs"
+ "show_reblogs": "Tampilkan Reblog",
+ "hide_reblogs": "Sembunyikan Reblog"
},
"timeline": {
"filtered": "Tersaring",
@@ -190,32 +196,38 @@
"now": "Sekarang"
},
"loader": {
- "load_missing_posts": "Load missing posts",
- "loading_missing_posts": "Loading missing posts...",
+ "load_missing_posts": "Muat postingan yang hilang",
+ "loading_missing_posts": "Memuat postingan yang hilang...",
"show_more_replies": "Tampilkan lebih banyak balasan"
},
"header": {
- "no_status_found": "No Post Found",
- "blocking_warning": "You can’t view this user's profile\nuntil you unblock them.\nYour profile looks like this to them.",
- "user_blocking_warning": "You can’t view %s’s profile\nuntil you unblock them.\nYour profile looks like this to them.",
- "blocked_warning": "You can’t view this user’s profile\nuntil they unblock you.",
- "user_blocked_warning": "You can’t view %s’s profile\nuntil they unblock you.",
- "suspended_warning": "This user has been suspended.",
- "user_suspended_warning": "%s’s account has been suspended."
+ "no_status_found": "Tidak Ditemukan Postingan",
+ "blocking_warning": "Anda tidak dapat melihat profil pengguna ini sampai Anda membuka blokir mereka.\nProfil Anda terlihat seperti ini bagi mereka.",
+ "user_blocking_warning": "Anda tidak dapat melihat profil %s sampai Anda membuka blokir mereka.\nProfil Anda terlihat seperti ini bagi mereka.",
+ "blocked_warning": "Anda tidak dapat melihat profil pengguna ini sampai mereka membuka blokir Anda.",
+ "user_blocked_warning": "Anda tidak dapat melihat profil %s sampai mereka membuka blokir Anda.",
+ "suspended_warning": "Pengguna ini telah ditangguhkan.",
+ "user_suspended_warning": "Akun %s telah ditangguhkan."
}
}
}
},
"scene": {
"welcome": {
- "slogan": "Social networking\nback in your hands.",
- "get_started": "Get Started",
- "log_in": "Log In"
+ "slogan": "Jejaring sosial dalam genggaman Anda.",
+ "get_started": "Mulai",
+ "log_in": "Login"
+ },
+ "login": {
+ "title": "Selamat datang kembali",
+ "subtitle": "Masuklah pada server yang Anda buat di mana akun Anda berada.",
+ "server_search_field": {
+ "placeholder": "Masukkan URL atau pencarian di server Anda"
+ }
},
"server_picker": {
"title": "Pilih sebuah server,\nserver manapun.",
- "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.",
+ "subtitle": "Pilih server berdasarkan agamamu, minat, atau subjek umum lainnya. Kamu masih bisa berkomunikasi dengan semua orang di Mastodon, tanpa memperdulikan server Anda.",
"button": {
"category": {
"all": "Semua",
@@ -242,8 +254,7 @@
"category": "KATEGORI"
},
"input": {
- "placeholder": "Search servers",
- "search_servers_or_enter_url": "Search servers or enter URL"
+ "search_servers_or_enter_url": "Cari komunitas atau masukkan URL"
},
"empty_state": {
"finding_servers": "Mencari server yang tersedia...",
@@ -253,7 +264,7 @@
},
"register": {
"title": "Beritahu kami tentang diri Anda.",
- "lets_get_you_set_up_on_domain": "Let’s get you set up on %s",
+ "lets_get_you_set_up_on_domain": "Mari kita siapkan Anda di %s",
"input": {
"avatar": {
"delete": "Hapus"
@@ -263,18 +274,18 @@
"duplicate_prompt": "Nama pengguna ini sudah diambil."
},
"display_name": {
- "placeholder": "display name"
+ "placeholder": "nama yang ditampilkan"
},
"email": {
"placeholder": "surel"
},
"password": {
"placeholder": "kata sandi",
- "require": "Your password needs at least:",
- "character_limit": "8 characters",
+ "require": "Kata sandi Anda harus memiliki setidaknya:",
+ "character_limit": "8 karakter",
"accessibility": {
- "checked": "checked",
- "unchecked": "unchecked"
+ "checked": "dicentang",
+ "unchecked": "tidak dicentang"
},
"hint": "Kata sandi Anda harus memiliki sekurang-kurangnya delapan karakter"
},
@@ -288,7 +299,7 @@
"email": "Surel",
"password": "Kata sandi",
"agreement": "Persetujuan",
- "locale": "Locale",
+ "locale": "Lokal",
"reason": "Alasan"
},
"reason": {
@@ -304,7 +315,7 @@
"inclusion": "%s is not a supported value"
},
"special": {
- "username_invalid": "Username must only contain alphanumeric characters and underscores",
+ "username_invalid": "Nama pengguna hanya berisi angka karakter dan garis bawah",
"username_too_long": "Nama pengguna terlalu panjang (tidak boleh lebih dari 30 karakter)",
"email_invalid": "Ini bukan alamat surel yang valid",
"password_too_short": "Kata sandi terlalu pendek (harus sekurang-kurangnya 8 karakter)"
@@ -361,12 +372,12 @@
"compose": {
"title": {
"new_post": "Postingan Baru",
- "new_reply": "New Reply"
+ "new_reply": "Pesan Baru"
},
"media_selection": {
- "camera": "Take Photo",
+ "camera": "Ambil Foto",
"photo_library": "Photo Library",
- "browse": "Browse"
+ "browse": "Telusuri"
},
"content_input_placeholder": "Ketik atau tempel apa yang Anda pada pikiran Anda",
"compose_action": "Publikasikan",
@@ -376,7 +387,13 @@
"video": "video",
"attachment_broken": "%s ini rusak dan tidak dapat diunggah ke Mastodon.",
"description_photo": "Jelaskan fotonya untuk mereka yang tidak dapat melihat dengan jelas...",
- "description_video": "Jelaskan videonya untuk mereka yang tidak dapat melihat dengan jelas..."
+ "description_video": "Jelaskan videonya untuk mereka yang tidak dapat melihat dengan jelas...",
+ "load_failed": "Gagal Memuat",
+ "upload_failed": "Gagal Mengunggah",
+ "can_not_recognize_this_media_attachment": "Tidak dapat mengenali lampiran media ini",
+ "attachment_too_large": "Lampiran terlalu besar",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "Durasi: %s",
@@ -386,14 +403,16 @@
"one_day": "1 Hari",
"three_days": "3 Hari",
"seven_days": "7 Hari",
- "option_number": "Option %ld"
+ "option_number": "Option %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "Tulis peringatan yang akurat di sini..."
},
"visibility": {
"public": "Publik",
- "unlisted": "Unlisted",
+ "unlisted": "Tidak terdaftar",
"private": "Pengikut saja",
"direct": "Hanya orang yang saya sebut"
},
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Custom Emoji Picker",
"enable_content_warning": "Aktifkan Peringatan Konten",
"disable_content_warning": "Nonaktifkan Peringatan Konten",
- "post_visibility_menu": "Post Visibility Menu"
+ "post_visibility_menu": "Post Visibility Menu",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "Discard Post",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Label",
"content": "Isi"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
diff --git a/Localization/StringsConvertor/input/is.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/is.lproj/Localizable.stringsdict
new file mode 100644
index 000000000..03b29f09b
--- /dev/null
+++ b/Localization/StringsConvertor/input/is.lproj/Localizable.stringsdict
@@ -0,0 +1,465 @@
+
+
+
+
+ a11y.plural.count.unread.notification
+
+ NSStringLocalizedFormatKey
+ %#@notification_count_unread_notification@
+ notification_count_unread_notification
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 ólesin tilkynning
+ other
+ %ld ólesnar tilkynningar
+
+
+ a11y.plural.count.input_limit_exceeds
+
+ NSStringLocalizedFormatKey
+ Inntak fer fram úr takmörkunum %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 stafur
+ other
+ %ld stafir
+
+
+ a11y.plural.count.input_limit_remains
+
+ NSStringLocalizedFormatKey
+ Inntakstakmörk haldast %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 stafur
+ other
+ %ld stafir
+
+
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ eftir
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 stafur
+ other
+ %ld stafir
+
+
+ plural.count.followed_by_and_mutual
+
+ NSStringLocalizedFormatKey
+ %#@names@%#@count_mutual@
+ names
+
+ one
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+
+
+ count_mutual
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ Fylgt af %1$@ og öðrum sameiginlegum
+ other
+ Fylgt af %1$@ og %ld sameiginlegum
+
+
+ plural.count.metric_formatted.post
+
+ NSStringLocalizedFormatKey
+ %@ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ færsla
+ other
+ færslur
+
+
+ plural.count.media
+
+ NSStringLocalizedFormatKey
+ %#@media_count@
+ media_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 gagnamiðill
+ other
+ %ld gagnamiðlar
+
+
+ plural.count.post
+
+ NSStringLocalizedFormatKey
+ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 færsla
+ other
+ %ld færslur
+
+
+ plural.count.favorite
+
+ NSStringLocalizedFormatKey
+ %#@favorite_count@
+ favorite_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 eftirlæti
+ other
+ %ld eftirlæti
+
+
+ plural.count.reblog
+
+ NSStringLocalizedFormatKey
+ %#@reblog_count@
+ reblog_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 endurbirting
+ other
+ %ld endurbirtingar
+
+
+ plural.count.reply
+
+ NSStringLocalizedFormatKey
+ %#@reply_count@
+ reply_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 svar
+ other
+ %ld svör
+
+
+ plural.count.vote
+
+ NSStringLocalizedFormatKey
+ %#@vote_count@
+ vote_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 atkvæði
+ other
+ %ld atkvæði
+
+
+ plural.count.voter
+
+ NSStringLocalizedFormatKey
+ %#@voter_count@
+ voter_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 kjósandi
+ other
+ %ld kjósendur
+
+
+ plural.people_talking
+
+ NSStringLocalizedFormatKey
+ %#@count_people_talking@
+ count_people_talking
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 aðili að spjalla
+ other
+ %ld aðilar að spjalla
+
+
+ plural.count.following
+
+ NSStringLocalizedFormatKey
+ %#@count_following@
+ count_following
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 fylgist með
+ other
+ %ld fylgjast með
+
+
+ plural.count.follower
+
+ NSStringLocalizedFormatKey
+ %#@count_follower@
+ count_follower
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 fylgjandi
+ other
+ %ld fylgjendur
+
+
+ date.year.left
+
+ NSStringLocalizedFormatKey
+ %#@count_year_left@
+ count_year_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 ár eftir
+ other
+ %ld ár eftir
+
+
+ date.month.left
+
+ NSStringLocalizedFormatKey
+ %#@count_month_left@
+ count_month_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 mánuður eftir
+ other
+ %ld mánuðir eftir
+
+
+ date.day.left
+
+ NSStringLocalizedFormatKey
+ %#@count_day_left@
+ count_day_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 dagur eftir
+ other
+ %ld dagar eftir
+
+
+ date.hour.left
+
+ NSStringLocalizedFormatKey
+ %#@count_hour_left@
+ count_hour_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 klukkustund eftir
+ other
+ %ld klukkustundir eftir
+
+
+ date.minute.left
+
+ NSStringLocalizedFormatKey
+ %#@count_minute_left@
+ count_minute_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 mínúta eftir
+ other
+ %ld mínútur eftir
+
+
+ date.second.left
+
+ NSStringLocalizedFormatKey
+ %#@count_second_left@
+ count_second_left
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 sekúnda eftir
+ other
+ %ld sekúndur eftir
+
+
+ date.year.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_year_ago_abbr@
+ count_year_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ Fyrir 1 ári síðan
+ other
+ Fyrir %ld árum síðan
+
+
+ date.month.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_month_ago_abbr@
+ count_month_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ Fyrir 1mín síðan
+ other
+ Fyrir %ldmín síðan
+
+
+ date.day.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_day_ago_abbr@
+ count_day_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ Fyrir 1 degi síðan
+ other
+ Fyrir %ld dögum síðan
+
+
+ date.hour.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_hour_ago_abbr@
+ count_hour_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1klst síðan
+ other
+ %ldklst síðan
+
+
+ date.minute.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_minute_ago_abbr@
+ count_minute_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1m síðan
+ other
+ %ldm síðan
+
+
+ date.second.ago.abbr
+
+ NSStringLocalizedFormatKey
+ %#@count_second_ago_abbr@
+ count_second_ago_abbr
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1s síðan
+ other
+ %lds síðan
+
+
+
+
diff --git a/Localization/StringsConvertor/input/is.lproj/app.json b/Localization/StringsConvertor/input/is.lproj/app.json
new file mode 100644
index 000000000..191a670ed
--- /dev/null
+++ b/Localization/StringsConvertor/input/is.lproj/app.json
@@ -0,0 +1,727 @@
+{
+ "common": {
+ "alerts": {
+ "common": {
+ "please_try_again": "Endilega reyndu aftur.",
+ "please_try_again_later": "Reyndu aftur síðar."
+ },
+ "sign_up_failure": {
+ "title": "Innskráning mistókst"
+ },
+ "server_error": {
+ "title": "Villa á þjóni"
+ },
+ "vote_failure": {
+ "title": "Greiðsla atkvæðis mistókst",
+ "poll_ended": "Könnuninni er lokið"
+ },
+ "discard_post_content": {
+ "title": "Henda drögum",
+ "message": "Staðfestu til að henda efni úr saminni færslu."
+ },
+ "publish_post_failure": {
+ "title": "Mistókst að birta",
+ "message": "Mistókst að birta færsluna.\nAthugaðu nettenginguna þína.",
+ "attachments_message": {
+ "video_attach_with_photo": "Ekki er hægt að hengja myndskeið við færslu sem þegar inniheldur myndir.",
+ "more_than_one_video": "Ekki er hægt að hengja við fleiri en eitt myndskeið."
+ }
+ },
+ "edit_profile_failure": {
+ "title": "Villa við breytingu á notandasniði",
+ "message": "Mistókst að breyta notandasniði. Endilega reyndu aftur."
+ },
+ "sign_out": {
+ "title": "Skrá út",
+ "message": "Ertu viss um að þú viljir skrá þig út?",
+ "confirm": "Skrá út"
+ },
+ "block_domain": {
+ "title": "Ertu alveg algjörlega viss um að þú viljir loka á allt %s? Í flestum tilfellum er vænlegra að nota færri en markvissari útilokanir eða að þagga niður tiltekna aðila. Þú munt ekki sjá neitt efni frá þessu léni og fylgjendur þínir frá þessu léni verða fjarlægðir.",
+ "block_entire_domain": "Útiloka lén"
+ },
+ "save_photo_failure": {
+ "title": "Mistókst að vista mynd",
+ "message": "Virkjaðu heimild til aðgangs að ljósmyndasafninu til að vista myndina."
+ },
+ "delete_post": {
+ "title": "Eyða færslu",
+ "message": "Ertu viss um að þú viljir eyða þessari færslu?"
+ },
+ "clean_cache": {
+ "title": "Hreinsa skyndiminni",
+ "message": "Tókst að hreinsa %s skyndiminni."
+ }
+ },
+ "controls": {
+ "actions": {
+ "back": "Til baka",
+ "next": "Næsta",
+ "previous": "Fyrri",
+ "open": "Opna",
+ "add": "Bæta við",
+ "remove": "Fjarlægja",
+ "edit": "Breyta",
+ "save": "Vista",
+ "ok": "Í lagi",
+ "done": "Lokið",
+ "confirm": "Staðfesta",
+ "continue": "Halda áfram",
+ "compose": "Skrifa",
+ "cancel": "Hætta við",
+ "discard": "Henda",
+ "try_again": "Reyna aftur",
+ "take_photo": "Taka ljósmynd",
+ "save_photo": "Vista mynd",
+ "copy_photo": "Afrita mynd",
+ "sign_in": "Skrá inn",
+ "sign_up": "Stofna notandaaðgang",
+ "see_more": "Sjá fleira",
+ "preview": "Forskoða",
+ "share": "Deila",
+ "share_user": "Deila %s",
+ "share_post": "Deila færslu",
+ "open_in_safari": "Opna í Safari",
+ "open_in_browser": "Opna í vafra",
+ "find_people": "Finna fólk til að fylgjast með",
+ "manually_search": "Leita handvirkt í staðinn",
+ "skip": "Sleppa",
+ "reply": "Svara",
+ "report_user": "Kæra %s",
+ "block_domain": "Útiloka %s",
+ "unblock_domain": "Opna á %s",
+ "settings": "Stillingar",
+ "delete": "Eyða"
+ },
+ "tabs": {
+ "home": "Heim",
+ "search": "Leita",
+ "notification": "Tilkynning",
+ "profile": "Notandasnið"
+ },
+ "keyboard": {
+ "common": {
+ "switch_to_tab": "Skipta yfir í %s",
+ "compose_new_post": "Semja nýja færslu",
+ "show_favorites": "Birta eftirlæti",
+ "open_settings": "Opna stillingar"
+ },
+ "timeline": {
+ "previous_status": "Fyrri færsla",
+ "next_status": "Næsta færsla",
+ "open_status": "Opna færslu",
+ "open_author_profile": "Opna notandasnið höfundar",
+ "open_reblogger_profile": "Opna notandasnið þess sem endurbirtir",
+ "reply_status": "Svara færslu",
+ "toggle_reblog": "Víxla endurbirtingu færslu af/á",
+ "toggle_favorite": "Víxla eftirlæti færslu af/á",
+ "toggle_content_warning": "Víxla af/á viðvörun vegna efnis",
+ "preview_image": "Forskoða mynd"
+ },
+ "segmented_control": {
+ "previous_section": "Fyrri hluti",
+ "next_section": "Næsti hluti"
+ }
+ },
+ "status": {
+ "user_reblogged": "%s endurbirti",
+ "user_replied_to": "Svaraði %s",
+ "show_post": "Sýna færslu",
+ "show_user_profile": "Birta notandasnið",
+ "content_warning": "Viðvörun vegna efnis",
+ "sensitive_content": "Viðkvæmt efni",
+ "media_content_warning": "Ýttu hvar sem er til að birta",
+ "tap_to_reveal": "Ýttu til að birta",
+ "poll": {
+ "vote": "Greiða atkvæði",
+ "closed": "Lokið"
+ },
+ "meta_entity": {
+ "url": "Tengill: %s",
+ "hashtag": "Myllumerki: %s",
+ "mention": "Sýna notandasnið: %s",
+ "email": "Tölvupóstfang: %s"
+ },
+ "actions": {
+ "reply": "Svara",
+ "reblog": "Endurbirta",
+ "unreblog": "Afturkalla endurbirtingu",
+ "favorite": "Eftirlæti",
+ "unfavorite": "Taka úr eftirlætum",
+ "menu": "Valmynd",
+ "hide": "Fela",
+ "show_image": "Sýna mynd",
+ "show_gif": "Birta GIF-myndir",
+ "show_video_player": "Sýna myndspilara",
+ "tap_then_hold_to_show_menu": "Ýttu og haltu til að sýna valmynd"
+ },
+ "tag": {
+ "url": "URL-slóð",
+ "mention": "Minnst á",
+ "link": "Tengill",
+ "hashtag": "Myllumerki",
+ "email": "Tölvupóstur",
+ "emoji": "Tjáningartákn"
+ },
+ "visibility": {
+ "unlisted": "Allir geta skoðað þessa færslu, en er ekki birt á opinberum tímalínum.",
+ "private": "Einungis fylgjendur þeirra geta séð þessa færslu.",
+ "private_from_me": "Einungis fylgjendur mínir geta séð þessa færslu.",
+ "direct": "Einungis notendur sem minnst er á geta séð þessa færslu."
+ }
+ },
+ "friendship": {
+ "follow": "Fylgja",
+ "following": "Fylgist með",
+ "request": "Beiðni",
+ "pending": "Í bið",
+ "block": "Útilokun",
+ "block_user": "Útiloka %s",
+ "block_domain": "Útiloka %s",
+ "unblock": "Aflétta útilokun",
+ "unblock_user": "Opna á %s",
+ "blocked": "Útilokað",
+ "mute": "Þagga niður",
+ "mute_user": "Þagga niður í %s",
+ "unmute": "Afþagga",
+ "unmute_user": "Afþagga %s",
+ "muted": "Þaggað",
+ "edit_info": "Breyta upplýsingum",
+ "show_reblogs": "Sýna endurbirtingar",
+ "hide_reblogs": "Fela endurbirtingar"
+ },
+ "timeline": {
+ "filtered": "Síað",
+ "timestamp": {
+ "now": "Núna"
+ },
+ "loader": {
+ "load_missing_posts": "Hlaða inn færslum sem vantar",
+ "loading_missing_posts": "Hleð inn færslum sem vantar...",
+ "show_more_replies": "Birta fleiri svör"
+ },
+ "header": {
+ "no_status_found": "Engar færslur fundust",
+ "blocking_warning": "Þú getur ekki séð snið þessa notanda\nfyrr en þú hættir að útiloka hann.\nSniðið þitt lítur svona út hjá honum.",
+ "user_blocking_warning": "Þú getur ekki séð sniðið hjá %s\nfyrr en þú hættir að útiloka hann.\nSniðið þitt lítur svona út hjá honum.",
+ "blocked_warning": "Þú getur ekki séð sniðið hjá þessum notanda\nfyrr en hann hættir að útiloka þig.",
+ "user_blocked_warning": "Þú getur ekki séð sniðið hjá %s\nfyrr en hann hættir að útiloka þig.",
+ "suspended_warning": "Þessi notandi hefur verið settur í bið.",
+ "user_suspended_warning": "Notandaaðgangurinn %s hefur verið settur í bið."
+ }
+ }
+ }
+ },
+ "scene": {
+ "welcome": {
+ "slogan": "Samfélagsmiðlar\naftur í þínar hendur.",
+ "get_started": "Komast í gang",
+ "log_in": "Skrá inn"
+ },
+ "login": {
+ "title": "Velkomin aftur",
+ "subtitle": "Skráðu þig inn á netþjóninum þar sem þú útbjóst aðganginn þinn.",
+ "server_search_field": {
+ "placeholder": "Settu inn slóð eða leitaðu að þjóninum þínum"
+ }
+ },
+ "server_picker": {
+ "title": "Mastodon samanstendur af notendum á mismunandi netþjónum.",
+ "subtitle": "Veldu netþjón út frá svæðinu þínu, áhugamálum, nú eða einhvern almennan. Þú getur samt spjallað við hvern sem er á Mastodon, burtséð frá á hvaða netþjóni þú ert.",
+ "button": {
+ "category": {
+ "all": "Allt",
+ "all_accessiblity_description": "Flokkur: Allt",
+ "academia": "akademískt",
+ "activism": "aðgerðasinnar",
+ "food": "matur",
+ "furry": "loðið",
+ "games": "leikir",
+ "general": "almennt",
+ "journalism": "blaðamennska",
+ "lgbt": "lgbt",
+ "regional": "svæðisbundið",
+ "art": "listir",
+ "music": "tónlist",
+ "tech": "tækni"
+ },
+ "see_less": "Sjá minna",
+ "see_more": "Sjá meira"
+ },
+ "label": {
+ "language": "TUNGUMÁL",
+ "users": "NOTENDUR",
+ "category": "FLOKKUR"
+ },
+ "input": {
+ "search_servers_or_enter_url": "Leitaðu að samfélögum eða settu inn slóð"
+ },
+ "empty_state": {
+ "finding_servers": "Finn tiltæka netþjóna...",
+ "bad_network": "Eitthvað fór úrskeiðis við að hlaða inn gögnunum. Athugaðu nettenginguna þína.",
+ "no_results": "Engar niðurstöður"
+ }
+ },
+ "register": {
+ "title": "Við skulum koma þér í gang á %s",
+ "lets_get_you_set_up_on_domain": "Við skulum koma þér í gang á %s",
+ "input": {
+ "avatar": {
+ "delete": "Eyða"
+ },
+ "username": {
+ "placeholder": "notandanafn",
+ "duplicate_prompt": "Þetta notandanafn er þegar í notkun."
+ },
+ "display_name": {
+ "placeholder": "birtingarnafn"
+ },
+ "email": {
+ "placeholder": "tölvupóstur"
+ },
+ "password": {
+ "placeholder": "lykilorð",
+ "require": "Lykilorðið þitt þarf að minnsta kosti:",
+ "character_limit": "8 stafi",
+ "accessibility": {
+ "checked": "merkt",
+ "unchecked": "ekki merkt"
+ },
+ "hint": "Lykilorðið þitt verður að vera að minnsta kosti 8 stafa langt"
+ },
+ "invite": {
+ "registration_user_invite_request": "Hvers vegna vilt þú taka þátt?"
+ }
+ },
+ "error": {
+ "item": {
+ "username": "Notandanafn",
+ "email": "Tölvupóstur",
+ "password": "Lykilorð",
+ "agreement": "Notkunarskilmálar",
+ "locale": "Staðfærsla",
+ "reason": "Ástæða"
+ },
+ "reason": {
+ "blocked": "%s notar óleyfilega tölvupóstþjónustu",
+ "unreachable": "%s virðist ekki vera til",
+ "taken": "%s er þegar í notkun",
+ "reserved": "%s er frátekið stikkorð",
+ "accepted": "%s verður að samþykkja",
+ "blank": "%s ier nauðsynlegt",
+ "invalid": "%s er ógilt",
+ "too_long": "%s er of langt",
+ "too_short": "%s er of stutt",
+ "inclusion": "%s er ekki stutt gildi"
+ },
+ "special": {
+ "username_invalid": "Notendanöfn geta einungis innihaldið bókstafi og undirstrikun",
+ "username_too_long": "Notandanafnið er of langt (má ekki vera lengra en 30 stafir)",
+ "email_invalid": "Þetta lítur ekki út eins og löglegt tölvupóstfang",
+ "password_too_short": "Lykilorð er of stutt (verður að hafa minnst 8 stafi)"
+ }
+ }
+ },
+ "server_rules": {
+ "title": "Nokkrar grunnreglur.",
+ "subtitle": "Þær eru settar og séð um að þeim sé fylgt af umsjónarmönnum %s.",
+ "prompt": "Með því að halda áfram samþykkir þú þjónustuskilmála og persónuverndarstefnu %s.",
+ "terms_of_service": "þjónustuskilmálar",
+ "privacy_policy": "persónuverndarstefna",
+ "button": {
+ "confirm": "Ég samþykki"
+ }
+ },
+ "confirm_email": {
+ "title": "Eitt að lokum.",
+ "subtitle": "Ýttu á tengilinn sem við sendum þér til að staðfesta tölvupóstfangið þitt.",
+ "tap_the_link_we_emailed_to_you_to_verify_your_account": "Ýttu á tengilinn sem við sendum þér til að staðfesta tölvupóstfangið þitt",
+ "button": {
+ "open_email_app": "Opna tölvupóstforrit",
+ "resend": "Endursenda"
+ },
+ "dont_receive_email": {
+ "title": "Athugaðu tölvupóstinn þinn",
+ "description": "Athugaðu hvort tölvupóstfangið þitt sé rétt auk þess að skoða í ruslpóstmöppuna þína ef þú hefur ekki gert það.",
+ "resend_email": "Endursenda tölvupóst"
+ },
+ "open_email_app": {
+ "title": "Athugaðu pósthólfið þitt.",
+ "description": "Við vorum að senda þér tölvupóst. Skoðaðu í ruslpóstmöppuna þína ef þú hefur ekki gert það.",
+ "mail": "Tölvupóstur",
+ "open_email_client": "Opna tölvupóstforrit"
+ }
+ },
+ "home_timeline": {
+ "title": "Heim",
+ "navigation_bar_state": {
+ "offline": "Ónettengt",
+ "new_posts": "Skoða nýjar færslur",
+ "published": "Birt!",
+ "Publishing": "Birti færslu...",
+ "accessibility": {
+ "logo_label": "Hnappur með táknmerki",
+ "logo_hint": "Ýttu til að skruna efst og ýttu aftur til að fara aftur á fyrri staðsetningu"
+ }
+ }
+ },
+ "suggestion_account": {
+ "title": "Finndu fólk til að fylgjast með",
+ "follow_explain": "Þegar þú fylgist með einhverjum, muntu sjá færslur frá viðkomandi á streyminu þínu."
+ },
+ "compose": {
+ "title": {
+ "new_post": "Ný færsla",
+ "new_reply": "Nýtt svar"
+ },
+ "media_selection": {
+ "camera": "Taktu mynd",
+ "photo_library": "Myndasafn",
+ "browse": "Flakka"
+ },
+ "content_input_placeholder": "Skrifaðu eða límdu það sem þér liggur á hjarta",
+ "compose_action": "Birta",
+ "replying_to_user": "svarar til @%s",
+ "attachment": {
+ "photo": "ljósmynd",
+ "video": "myndskeið",
+ "attachment_broken": "Þetta %s er skemmt og því ekki\nhægt að senda inn á Mastodon.",
+ "description_photo": "Lýstu myndinni fyrir sjónskerta...",
+ "description_video": "Lýstu myndskeiðinu fyrir sjónskerta...",
+ "load_failed": "Hleðsla mistókst",
+ "upload_failed": "Innsending mistókst",
+ "can_not_recognize_this_media_attachment": "Þekki ekki þetta myndviðhengi",
+ "attachment_too_large": "Viðhengi of stórt",
+ "compressing_state": "Þjappa...",
+ "server_processing_state": "Netþjónn er að vinna..."
+ },
+ "poll": {
+ "duration_time": "Tímalengd: %s",
+ "thirty_minutes": "30 mínútur",
+ "one_hour": "1 klukkustund",
+ "six_hours": "6 klukkustundir",
+ "one_day": "1 dagur",
+ "three_days": "3 dagar",
+ "seven_days": "7 dagar",
+ "option_number": "Valkostur %ld",
+ "the_poll_is_invalid": "Könnunin er ógild",
+ "the_poll_has_empty_option": "Könnunin er með auðan valkost"
+ },
+ "content_warning": {
+ "placeholder": "Skrifaðu nákvæma aðvörun hér..."
+ },
+ "visibility": {
+ "public": "Opinbert",
+ "unlisted": "Óskráð",
+ "private": "Einungis fylgjendur",
+ "direct": "Einungis fólk sem ég minnist á"
+ },
+ "auto_complete": {
+ "space_to_add": "Bil sem á að bæta við"
+ },
+ "accessibility": {
+ "append_attachment": "Bæta við viðhengi",
+ "append_poll": "Bæta við könnun",
+ "remove_poll": "Fjarlægja könnun",
+ "custom_emoji_picker": "Sérsniðið emoji-tánmyndaval",
+ "enable_content_warning": "Virkja viðvörun vegna efnis",
+ "disable_content_warning": "Gera viðvörun vegna efnis óvirka",
+ "post_visibility_menu": "Sýnileikavalmynd færslu",
+ "post_options": "Valkostir færslu",
+ "posting_as": "Birti sem %s"
+ },
+ "keyboard": {
+ "discard_post": "Henda færslu",
+ "publish_post": "Birta færslu",
+ "toggle_poll": "Víxla könnun af/á",
+ "toggle_content_warning": "Víxla af/á viðvörun vegna efnis",
+ "append_attachment_entry": "Bæta við viðhengi - %s",
+ "select_visibility_entry": "Veldu sýnileika - %s"
+ }
+ },
+ "profile": {
+ "header": {
+ "follows_you": "Fylgist með þér"
+ },
+ "dashboard": {
+ "posts": "færslur",
+ "following": "fylgist með",
+ "followers": "fylgjendur"
+ },
+ "fields": {
+ "add_row": "Bæta við röð",
+ "placeholder": {
+ "label": "Skýring",
+ "content": "Efni"
+ },
+ "verified": {
+ "short": "Sannreynt þann %s",
+ "long": "Eignarhald á þessum tengli var athugað þann %s"
+ }
+ },
+ "segmented_control": {
+ "posts": "Færslur",
+ "replies": "Svör",
+ "posts_and_replies": "Færslur og svör",
+ "media": "Gagnamiðlar",
+ "about": "Um hugbúnaðinn"
+ },
+ "relationship_action_alert": {
+ "confirm_mute_user": {
+ "title": "Þagga niður í aðgangi",
+ "message": "Staðfestu til að þagga niður í %s"
+ },
+ "confirm_unmute_user": {
+ "title": "Hætta að þagga niður í aðgangi",
+ "message": "Staðfestu til hætta að að þagga niður í %s"
+ },
+ "confirm_block_user": {
+ "title": "Útiloka notandaaðgang",
+ "message": "Staðfestu til að útiloka %s"
+ },
+ "confirm_unblock_user": {
+ "title": "Aflétta útilokun aðgangs",
+ "message": "Staðfestu til að hætta að útiloka %s"
+ },
+ "confirm_show_reblogs": {
+ "title": "Sýna endurbirtingar",
+ "message": "Staðfestu til að sýna endurbirtingar"
+ },
+ "confirm_hide_reblogs": {
+ "title": "Fela endurbirtingar",
+ "message": "Staðfestu til að fela endurbirtingar"
+ }
+ },
+ "accessibility": {
+ "show_avatar_image": "Sýna auðkennismynd",
+ "edit_avatar_image": "Breyta auðkennismynd",
+ "show_banner_image": "Sýna myndborða",
+ "double_tap_to_open_the_list": "Tvípikkaðu til að opna listann"
+ }
+ },
+ "follower": {
+ "title": "fylgjandi",
+ "footer": "Fylgjendur af öðrum netþjónum birtast ekki."
+ },
+ "following": {
+ "title": "fylgist með",
+ "footer": "Fylgjendur af öðrum netþjónum birtast ekki."
+ },
+ "familiarFollowers": {
+ "title": "Fylgjendur sem þú kannast við",
+ "followed_by_names": "Fylgt af %s"
+ },
+ "favorited_by": {
+ "title": "Sett í eftirlæti af"
+ },
+ "reblogged_by": {
+ "title": "Endurbirt af"
+ },
+ "search": {
+ "title": "Leita",
+ "search_bar": {
+ "placeholder": "Leita að myllumerkjum og notendum",
+ "cancel": "Hætta við"
+ },
+ "recommend": {
+ "button_text": "Sjá allt",
+ "hash_tag": {
+ "title": "Vinsælt á Mastodon",
+ "description": "Myllumerki sem eru að fá þónokkra athygli",
+ "people_talking": "%s manns eru að spjalla"
+ },
+ "accounts": {
+ "title": "Notandaaðgangar sem þú gætir haft áhuga á",
+ "description": "Þú gætir viljað fylgjast með þessum aðgöngum",
+ "follow": "Fylgjast með"
+ }
+ },
+ "searching": {
+ "segment": {
+ "all": "Allt",
+ "people": "Fólk",
+ "hashtags": "Myllumerki",
+ "posts": "Færslur"
+ },
+ "empty_state": {
+ "no_results": "Engar niðurstöður"
+ },
+ "recent_search": "Nýlegar leitir",
+ "clear": "Hreinsa"
+ }
+ },
+ "discovery": {
+ "tabs": {
+ "posts": "Færslur",
+ "hashtags": "Myllumerki",
+ "news": "Fréttir",
+ "community": "Samfélag",
+ "for_you": "Fyrir þig"
+ },
+ "intro": "Þetta eru færslurnar sem eru að fá aukna athygli í þínu horni á Mastodon."
+ },
+ "favorite": {
+ "title": "Eftirlætin þín"
+ },
+ "notification": {
+ "title": {
+ "Everything": "Allt",
+ "Mentions": "Minnst á"
+ },
+ "notification_description": {
+ "followed_you": "fylgdi þér",
+ "favorited_your_post": "setti færslu frá þér í eftirlæti",
+ "reblogged_your_post": "endurbirti færsluna þína",
+ "mentioned_you": "minntist á þig",
+ "request_to_follow_you": "bað um að fylgjast með þér",
+ "poll_has_ended": "könnun er lokið"
+ },
+ "keyobard": {
+ "show_everything": "Sýna allt",
+ "show_mentions": "Sýna þegar minnst er á"
+ },
+ "follow_request": {
+ "accept": "Samþykkja",
+ "accepted": "Samþykkt",
+ "reject": "hafna",
+ "rejected": "Hafnað"
+ }
+ },
+ "thread": {
+ "back_title": "Færsla",
+ "title": "Færsla frá %s"
+ },
+ "settings": {
+ "title": "Stillingar",
+ "section": {
+ "appearance": {
+ "title": "Útlit",
+ "automatic": "Sjálfvirkt",
+ "light": "Alltaf ljóst",
+ "dark": "Alltaf dökkt"
+ },
+ "look_and_feel": {
+ "title": "Útlit og viðmót",
+ "use_system": "Nota stillingar kerfis",
+ "really_dark": "Mjög dökkt",
+ "sorta_dark": "Nokkuð dökkt",
+ "light": "Ljóst"
+ },
+ "notifications": {
+ "title": "Tilkynningar",
+ "favorites": "Setur færsluna mína í eftirlæti",
+ "follows": "Fylgist með mér",
+ "boosts": "Endurbirtir færsluna mína",
+ "mentions": "Minnist á mig",
+ "trigger": {
+ "anyone": "hver sem er",
+ "follower": "fylgjandi",
+ "follow": "hverjum sá sem ég fylgi",
+ "noone": "enginn",
+ "title": "Tilkynna mér þegar"
+ }
+ },
+ "preference": {
+ "title": "Kjörstillingar",
+ "true_black_dark_mode": "Sannur svartur dökkur hamur",
+ "disable_avatar_animation": "Gera auðkennismyndir með hreyfingu óvirkar",
+ "disable_emoji_animation": "Gera tjáningartákn með hreyfingu óvirkar",
+ "using_default_browser": "Nota sjálfgefinn vafra til að opna tengla",
+ "open_links_in_mastodon": "Opna tengla í Mastodon"
+ },
+ "boring_zone": {
+ "title": "Óhressa svæðið",
+ "account_settings": "Stillingar aðgangs",
+ "terms": "Þjónustuskilmálar",
+ "privacy": "Meðferð persónuupplýsinga"
+ },
+ "spicy_zone": {
+ "title": "Kryddaða svæðið",
+ "clear": "Hreinsa skyndiminni margmiðlunarefnis",
+ "signout": "Skrá út"
+ }
+ },
+ "footer": {
+ "mastodon_description": "Mastodon er frjáls hugbúnaður með opinn grunnkóða. Þú getur tilkynnt vandamál í gegnum GitHub á %s (%s)"
+ },
+ "keyboard": {
+ "close_settings_window": "Loka stillingaglugga"
+ }
+ },
+ "report": {
+ "title_report": "Kæra",
+ "title": "Kæra %s",
+ "step1": "Skref 1 af 2",
+ "step2": "Skref 2 af 2",
+ "content1": "Eru einhverjar færslur sem þú myndir vilja bæta við kæruna?",
+ "content2": "Er eitthvað fleira sem umsjónarmenn ættu að vita varðandi þessa kæru?",
+ "report_sent_title": "Takk fyrir tilkynninguna, við munum skoða málið.",
+ "send": "Senda kæru",
+ "skip_to_send": "Senda án athugasemdar",
+ "text_placeholder": "Skrifaðu eða límdu aðrar athugasemdir",
+ "reported": "TILKYNNT",
+ "step_one": {
+ "step_1_of_4": "Skref 1 af 4",
+ "whats_wrong_with_this_post": "Hvað er athugavert við þessa færslu?",
+ "whats_wrong_with_this_account": "Hvað er athugavert við þennan notandaaðgang?",
+ "whats_wrong_with_this_username": "Hvað er athugavert við %s?",
+ "select_the_best_match": "Velja bestu samsvörun",
+ "i_dont_like_it": "Mér líkar það ekki",
+ "it_is_not_something_you_want_to_see": "Þetta er ekki eitthvað sem þið viljið sjá",
+ "its_spam": "Þetta er ruslpóstur",
+ "malicious_links_fake_engagement_or_repetetive_replies": "Slæmir tenglar, fölsk samskipti eða endurtekin svör",
+ "it_violates_server_rules": "Það gengur þvert á reglur fyrir netþjóninn",
+ "you_are_aware_that_it_breaks_specific_rules": "Þið eruð meðvituð um að þetta brýtur sértækar reglur",
+ "its_something_else": "Það er eitthvað annað",
+ "the_issue_does_not_fit_into_other_categories": "Vandamálið fellur ekki í aðra flokka"
+ },
+ "step_two": {
+ "step_2_of_4": "Skref 2 af 4",
+ "which_rules_are_being_violated": "Hvaða reglur eru brotnar?",
+ "select_all_that_apply": "Veldu allt sem á við",
+ "i_just_don’t_like_it": "Mér bara líkar það ekki"
+ },
+ "step_three": {
+ "step_3_of_4": "Skref 3 af 4",
+ "are_there_any_posts_that_back_up_this_report": "Eru einhverjar færslur sem styðja þessa kæru?",
+ "select_all_that_apply": "Veldu allt sem á við"
+ },
+ "step_four": {
+ "step_4_of_4": "Skref 4 af 4",
+ "is_there_anything_else_we_should_know": "Er eitthvað fleira sem við ættum að vita?"
+ },
+ "step_final": {
+ "dont_want_to_see_this": "Langar þig ekki að sjá þetta?",
+ "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "Þegar þú sér eitthvað á Mastodon sem þér líkar ekki, þá geturðu fjarlægt viðkomandi eintakling úr umhverfinu þínu.",
+ "unfollow": "Hætta að fylgjast með",
+ "unfollowed": "Hætti að fylgjast með",
+ "unfollow_user": "Hætta að fylgjast með %s",
+ "mute_user": "Þagga niður í %s",
+ "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "Þú munt ekki sjá færslur eða endurbirtingar frá viðkomandi á streyminu þínu. Viðkomandi aðilar munu ekki vita að þaggað hefur verið niður í þeim.",
+ "block_user": "Útiloka %s",
+ "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "Viðkomandi mun ekki lengur geta fylgst með eða séð færslurnar þínar, en munu sjá ef viðkomandi hefur verið útilokaður.",
+ "while_we_review_this_you_can_take_action_against_user": "Á meðan við yfirförum þetta, geturðu tekið til aðgerða gegn %s"
+ }
+ },
+ "preview": {
+ "keyboard": {
+ "close_preview": "Loka forskoðun",
+ "show_next": "Sýna næsta",
+ "show_previous": "Sýna fyrri"
+ }
+ },
+ "account_list": {
+ "tab_bar_hint": "Fyrirliggjandi valið notandasnið: %s. Tvípikkaðu og haltu niðri til að birta aðgangaskiptinn",
+ "dismiss_account_switcher": "Loka aðgangaskipti",
+ "add_account": "Bæta við notandaaðgangi"
+ },
+ "wizard": {
+ "new_in_mastodon": "Nýtt í Mastodon",
+ "multiple_account_switch_intro_description": "Skiptu milli notandaaðganga með því að halda niðri notandasniðshnappnum.",
+ "accessibility_hint": "Tvípikkaðu til að loka þessum leiðarvísi"
+ },
+ "bookmark": {
+ "title": "Bókamerki"
+ }
+ }
+}
diff --git a/Localization/StringsConvertor/input/is.lproj/ios-infoPlist.json b/Localization/StringsConvertor/input/is.lproj/ios-infoPlist.json
new file mode 100644
index 000000000..24af18431
--- /dev/null
+++ b/Localization/StringsConvertor/input/is.lproj/ios-infoPlist.json
@@ -0,0 +1,6 @@
+{
+ "NSCameraUsageDescription": "Notað til að taka mynd fyrir stöðufærslu",
+ "NSPhotoLibraryAddUsageDescription": "Notað til að vista mynd inn í ljósmyndasafnið",
+ "NewPostShortcutItemTitle": "Ný færsla",
+ "SearchShortcutItemTitle": "Leita"
+}
diff --git a/Localization/StringsConvertor/input/it.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/it.lproj/Localizable.stringsdict
index 38f986521..3a8549914 100644
--- a/Localization/StringsConvertor/input/it.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/it.lproj/Localizable.stringsdict
@@ -50,6 +50,22 @@
%ld caratteri
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ rimanenti
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 carattere
+ other
+ %ld caratteri
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/it.lproj/app.json b/Localization/StringsConvertor/input/it.lproj/app.json
index 269d299ec..f4f21762b 100644
--- a/Localization/StringsConvertor/input/it.lproj/app.json
+++ b/Localization/StringsConvertor/input/it.lproj/app.json
@@ -75,7 +75,7 @@
"save_photo": "Salva foto",
"copy_photo": "Copia foto",
"sign_in": "Accedi",
- "sign_up": "Registrati",
+ "sign_up": "Crea un account",
"see_more": "Visualizza altro",
"preview": "Anteprima",
"share": "Condividi",
@@ -136,6 +136,12 @@
"vote": "Vota",
"closed": "Chiuso"
},
+ "meta_entity": {
+ "url": "Collegamento: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Mostra il profilo: %s",
+ "email": "Indirizzo email: %s"
+ },
"actions": {
"reply": "Rispondi",
"reblog": "Condivisione",
@@ -212,10 +218,16 @@
"get_started": "Inizia",
"log_in": "Accedi"
},
+ "login": {
+ "title": "Bentornato/a",
+ "subtitle": "Accedi al server sul quale hai creato il tuo account.",
+ "server_search_field": {
+ "placeholder": "Inserisci l'URL o cerca il tuo server"
+ }
+ },
"server_picker": {
"title": "Mastodon è fatto di utenti in diverse comunità.",
- "subtitle": "Scegli una comunità basata sui tuoi interessi, regione o uno scopo generale.",
- "subtitle_extend": "Scegli una comunità basata sui tuoi interessi, regione o uno scopo generale. Ogni comunità è gestita da un'organizzazione completamente indipendente o individuale.",
+ "subtitle": "Scegli un server in base alla tua regione, ai tuoi interessi o uno generico. Puoi comunque chattare con chiunque su Mastodon, indipendentemente dai tuoi server.",
"button": {
"category": {
"all": "Tutti",
@@ -242,8 +254,7 @@
"category": "CATEGORIA"
},
"input": {
- "placeholder": "Cerca comunità",
- "search_servers_or_enter_url": "Cerca i server o inserisci l'URL"
+ "search_servers_or_enter_url": "Cerca le comunità o inserisci l'URL"
},
"empty_state": {
"finding_servers": "Ricerca server disponibili...",
@@ -376,7 +387,13 @@
"video": "filmato",
"attachment_broken": "Questo %s è rotto e non può essere\ncaricato su Mastodon.",
"description_photo": "Descrivi la foto per gli utenti ipovedenti...",
- "description_video": "Descrivi il filmato per gli utenti ipovedenti..."
+ "description_video": "Descrivi il filmato per gli utenti ipovedenti...",
+ "load_failed": "Caricamento fallito",
+ "upload_failed": "Caricamento fallito",
+ "can_not_recognize_this_media_attachment": "Impossibile riconoscere questo allegato multimediale",
+ "attachment_too_large": "Allegato troppo grande",
+ "compressing_state": "Compressione in corso...",
+ "server_processing_state": "Elaborazione del server in corso..."
},
"poll": {
"duration_time": "Durata: %s",
@@ -386,7 +403,9 @@
"one_day": "1 giorno",
"three_days": "3 giorni",
"seven_days": "7 giorni",
- "option_number": "Opzione %ld"
+ "option_number": "Opzione %ld",
+ "the_poll_is_invalid": "Il sondaggio non è valido",
+ "the_poll_has_empty_option": "Il sondaggio ha un'opzione vuota"
},
"content_warning": {
"placeholder": "Scrivi un avviso accurato qui..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Selettore Emoji personalizzato",
"enable_content_warning": "Abilita avvertimento contenuti",
"disable_content_warning": "Disabilita avviso di contenuti",
- "post_visibility_menu": "Menu di visibilità del post"
+ "post_visibility_menu": "Menu di visibilità del post",
+ "post_options": "Opzioni del messaggio",
+ "posting_as": "Pubblicazione come %s"
},
"keyboard": {
"discard_post": "Scarta post",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Etichetta",
"content": "Contenuto"
+ },
+ "verified": {
+ "short": "Verificato il %s",
+ "long": "La proprietà di questo collegamento è stata verificata il %s"
}
},
"segmented_control": {
@@ -696,7 +721,7 @@
"accessibility_hint": "Doppio tocco per eliminare questa procedura guidata"
},
"bookmark": {
- "title": "Bookmarks"
+ "title": "Segnalibri"
}
}
}
diff --git a/Localization/StringsConvertor/input/ja.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/ja.lproj/Localizable.stringsdict
index cbc999738..795a971b7 100644
--- a/Localization/StringsConvertor/input/ja.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/ja.lproj/Localizable.stringsdict
@@ -44,6 +44,20 @@
%ld 文字
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld characters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/ja.lproj/app.json b/Localization/StringsConvertor/input/ja.lproj/app.json
index b7615abf3..f73faabd4 100644
--- a/Localization/StringsConvertor/input/ja.lproj/app.json
+++ b/Localization/StringsConvertor/input/ja.lproj/app.json
@@ -74,8 +74,8 @@
"take_photo": "写真を撮る",
"save_photo": "写真を撮る",
"copy_photo": "写真をコピー",
- "sign_in": "サインイン",
- "sign_up": "サインアップ",
+ "sign_in": "Log in",
+ "sign_up": "Create account",
"see_more": "もっと見る",
"preview": "プレビュー",
"share": "共有",
@@ -136,6 +136,12 @@
"vote": "投票",
"closed": "終了"
},
+ "meta_entity": {
+ "url": "Link: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Show Profile: %s",
+ "email": "Email address: %s"
+ },
"actions": {
"reply": "返信",
"reblog": "ブースト",
@@ -212,10 +218,16 @@
"get_started": "はじめる",
"log_in": "ログイン"
},
+ "login": {
+ "title": "Welcome back",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Enter URL or search for your server"
+ }
+ },
"server_picker": {
"title": "サーバーを選択",
- "subtitle": "あなたの興味分野・地域に合ったコミュニティや、汎用のものを選択してください。",
- "subtitle_extend": "あなたの興味分野・地域に合ったコミュニティや、汎用のものを選択してください。各コミュニティはそれぞれ完全に独立した組織や個人によって運営されています。",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
"button": {
"category": {
"all": "すべて",
@@ -242,8 +254,7 @@
"category": "カテゴリー"
},
"input": {
- "placeholder": "サーバーを探す",
- "search_servers_or_enter_url": "サーバーを検索またはURLを入力"
+ "search_servers_or_enter_url": "Search communities or enter URL"
},
"empty_state": {
"finding_servers": "利用可能なサーバーの検索...",
@@ -376,7 +387,13 @@
"video": "動画",
"attachment_broken": "%sは壊れていてMastodonにアップロードできません。",
"description_photo": "閲覧が難しいユーザーへの画像説明",
- "description_video": "閲覧が難しいユーザーへの映像説明"
+ "description_video": "閲覧が難しいユーザーへの映像説明",
+ "load_failed": "Load Failed",
+ "upload_failed": "Upload Failed",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "Attachment too large",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "期間: %s",
@@ -386,7 +403,9 @@
"one_day": "1日",
"three_days": "3日",
"seven_days": "7日",
- "option_number": "オプション %ld"
+ "option_number": "オプション %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "ここに警告を書いてください..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "カスタム絵文字ピッカー",
"enable_content_warning": "閲覧注意を有効にする",
"disable_content_warning": "閲覧注意を無効にする",
- "post_visibility_menu": "投稿の表示メニュー"
+ "post_visibility_menu": "投稿の表示メニュー",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "投稿を破棄",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "ラベル",
"content": "コンテンツ"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
diff --git a/Localization/StringsConvertor/input/kab.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/kab.lproj/Localizable.stringsdict
index 7fc6a50bb..f18a906c0 100644
--- a/Localization/StringsConvertor/input/kab.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/kab.lproj/Localizable.stringsdict
@@ -50,6 +50,22 @@
%ld yisekkilen
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 n usekkil
+ other
+ %ld n isekkilen
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
@@ -120,7 +136,7 @@
NSStringFormatValueTypeKey
ld
one
- 1 tsuffeɣt
+ 1 n tsuffeɣt
other
%ld n tsuffaɣ
@@ -296,9 +312,9 @@
NSStringFormatValueTypeKey
ld
one
- Yeqqim-d 1 wass
+ Yeqqim-d 1 n wass
other
- Qqimen-d %ld wussan
+ Qqimen-d %ld n wussan
date.hour.left
@@ -312,9 +328,9 @@
NSStringFormatValueTypeKey
ld
one
- Yeqqim-d 1 usrag
+ Yeqqim-d 1 n wesrag
other
- Qqimen-d %ld yisragen
+ Qqimen-d %ld n yisragen
date.minute.left
@@ -328,9 +344,9 @@
NSStringFormatValueTypeKey
ld
one
- 1 tesdat i d-yeqqimen
+ 1 n tesdat i d-yeqqimen
other
- %ld tesdatin i d-yeqqimen
+ %ld n tesdatin i d-yeqqimen
date.second.left
@@ -344,9 +360,9 @@
NSStringFormatValueTypeKey
ld
one
- 1 tasint i d-yeqqimen
+ 1 n tasint i d-yeqqimen
other
- %ld tsinin i d-yeqqimen
+ %ld n tasinin i d-yeqqimen
date.year.ago.abbr
@@ -360,9 +376,9 @@
NSStringFormatValueTypeKey
ld
one
- 1 useggas aya
+ %ld n useggas aya
other
- %ld yiseggasen aya
+ %ld n yiseggasen aya
date.month.ago.abbr
@@ -376,9 +392,9 @@
NSStringFormatValueTypeKey
ld
one
- 1 wayyur aya
+ %ld n wayyur aya
other
- %ld wayyuren aya
+ %ld n wayyuren aya
date.day.ago.abbr
@@ -392,9 +408,9 @@
NSStringFormatValueTypeKey
ld
one
- 1 wass aya
+ %ld n wass aya
other
- %ld wussan aya
+ %ld n wussan aya
date.hour.ago.abbr
@@ -408,9 +424,9 @@
NSStringFormatValueTypeKey
ld
one
- 1 usrag aya
+ %ld n wesrag aya
other
- %ld yisragen aya
+ %ld n yisragen aya
date.minute.ago.abbr
@@ -424,9 +440,9 @@
NSStringFormatValueTypeKey
ld
one
- 1 tesdat aya
+ %ld n tesdat aya
other
- %ld tesdatin aya
+ %ld n tesdatin aya
date.second.ago.abbr
@@ -440,9 +456,9 @@
NSStringFormatValueTypeKey
ld
one
- 1 tasint aya
+ %ld n tasint aya
other
- %ld tsinin aya
+ %ld n tasinin aya
diff --git a/Localization/StringsConvertor/input/kab.lproj/app.json b/Localization/StringsConvertor/input/kab.lproj/app.json
index 2cff3d68d..62cea8780 100644
--- a/Localization/StringsConvertor/input/kab.lproj/app.json
+++ b/Localization/StringsConvertor/input/kab.lproj/app.json
@@ -75,7 +75,7 @@
"save_photo": "Sekles tawlaft",
"copy_photo": "Nɣel tawlaft",
"sign_in": "Qqen",
- "sign_up": "Jerred amiḍan",
+ "sign_up": "Snulfu-d amiḍan",
"see_more": "Wali ugar",
"preview": "Taskant",
"share": "Bḍu",
@@ -136,6 +136,12 @@
"vote": "Dɣeṛ",
"closed": "Ifukk"
},
+ "meta_entity": {
+ "url": "Asaɣ : %s",
+ "hashtag": "Ahacṭag : %s",
+ "mention": "Sken-d amaɣnu : %s",
+ "email": "Tansa imayl : %s"
+ },
"actions": {
"reply": "Err",
"reblog": "Aɛiwed n usuffeɣ",
@@ -212,10 +218,16 @@
"get_started": "Aha bdu tura",
"log_in": "Qqen"
},
+ "login": {
+ "title": "Ansuf yess·ek·em",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Sekcem URL neɣ nadi ɣef uqeddac-ik·im"
+ }
+ },
"server_picker": {
"title": "Mastodon yettwaxdem i yiseqdacen deg waṭas n temɣiwnin.",
- "subtitle": "Fren tamɣiwent almend n wayen tḥemmleḍ, n tmurt-ik neɣ n yiswi-inek amatu.",
- "subtitle_extend": "Fren tamɣiwent almend n wayen tḥemmleḍ, n tmurt-ik neɣ n yiswi-inek amatu. Yal tamɣiwent tsedday-itt tkebbanit neɣ amdan ilelliyen.",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
"button": {
"category": {
"all": "Akk",
@@ -242,7 +254,6 @@
"category": "TAGGAYT"
},
"input": {
- "placeholder": "Nadi timɣiwnin",
"search_servers_or_enter_url": "Nadi timɣiwnin neɣ sekcem URL"
},
"empty_state": {
@@ -346,8 +357,8 @@
"navigation_bar_state": {
"offline": "Beṛṛa n tuqqna",
"new_posts": "Tissufaɣ timaynutin",
- "published": "Yettwasuffeɣ!",
- "Publishing": "Asuffeɣ tasuffeɣt...",
+ "published": "Tettwasuffeɣ!",
+ "Publishing": "Asuffeɣ n tasuffeɣt...",
"accessibility": {
"logo_label": "Taqeffalt n ulugu",
"logo_hint": "Sit i wakken ad tɛeddiḍ i usawen, sit tikkelt-nniḍen i wakken ad tɛeddiḍ ɣer wadig yezrin"
@@ -376,7 +387,13 @@
"video": "tavidyutt",
"attachment_broken": "%s-a yerreẓ, ur yezmir ara\nAd d-yettwasali ɣef Mastodon.",
"description_photo": "Glem-d tawlaft i wid yesɛan ugur deg yiẓri...",
- "description_video": "Glem-d tavidyut i wid yesɛan ugur deg yiẓri..."
+ "description_video": "Glem-d tavidyut i wid yesɛan ugur deg yiẓri...",
+ "load_failed": "Load Failed",
+ "upload_failed": "Upload Failed",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "Attachment too large",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "Tangazt: %s",
@@ -386,7 +403,9 @@
"one_day": "1 n wass",
"three_days": "3 n wussan",
"seven_days": "7 n wussan",
- "option_number": "Taxtiṛt %ld"
+ "option_number": "Taxtiṛt %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "Aru alɣu-inek s telqeyt da..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Amefran n yimujiten udmawanen",
"enable_content_warning": "Rmed alɣu n ugbur",
"disable_content_warning": "Sens alɣu n ugbur",
- "post_visibility_menu": "Umuɣ n ubani n tsuffeɣt"
+ "post_visibility_menu": "Umuɣ n ubani n tsuffeɣt",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "Sefsex tasuffeɣt",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Tabzimt",
"content": "Agbur"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
@@ -646,7 +671,7 @@
"its_spam": "D aspam",
"malicious_links_fake_engagement_or_repetetive_replies": "Yir iseɣwan, yir agman d tririyin i d-yettuɣalen",
"it_violates_server_rules": "Truẓi n yilugan n uqeddac",
- "you_are_aware_that_it_breaks_specific_rules": "Teẓriḍ y•tettruẓu kra n yilugan",
+ "you_are_aware_that_it_breaks_specific_rules": "Teẓriḍ y·tettruẓu kra n yilugan",
"its_something_else": "Ɣef ssebba-nniḍen",
"the_issue_does_not_fit_into_other_categories": "Ugur ur yemṣada ara akk d taggayin-nniḍen"
},
diff --git a/Localization/StringsConvertor/input/kmr.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/kmr.lproj/Localizable.stringsdict
index 77571439f..c904186d8 100644
--- a/Localization/StringsConvertor/input/kmr.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/kmr.lproj/Localizable.stringsdict
@@ -50,6 +50,22 @@
%ld tîp
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ maye
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 peyv
+ other
+ %ld peyv
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/kmr.lproj/app.json b/Localization/StringsConvertor/input/kmr.lproj/app.json
index d48edf3ae..eb553885c 100644
--- a/Localization/StringsConvertor/input/kmr.lproj/app.json
+++ b/Localization/StringsConvertor/input/kmr.lproj/app.json
@@ -75,7 +75,7 @@
"save_photo": "Wêneyê tomar bike",
"copy_photo": "Wêneyê jê bigire",
"sign_in": "Têkeve",
- "sign_up": "Tomar bibe",
+ "sign_up": "Ajimêr biafirîne",
"see_more": "Bêtir bibîne",
"preview": "Pêşdîtin",
"share": "Parve bike",
@@ -136,6 +136,12 @@
"vote": "Deng bide",
"closed": "Girtî"
},
+ "meta_entity": {
+ "url": "Girêdan: %s",
+ "hashtag": "Hashtagê: %s",
+ "mention": "Profîlê nîşan bide: %s",
+ "email": "Navnîşanên e-nameyê: %s"
+ },
"actions": {
"reply": "Bersivê bide",
"reblog": "Ji nû ve nivîsandin",
@@ -181,8 +187,8 @@
"unmute_user": "%s bêdeng neke",
"muted": "Bêdengkirî",
"edit_info": "Zanyariyan serrast bike",
- "show_reblogs": "Show Reblogs",
- "hide_reblogs": "Hide Reblogs"
+ "show_reblogs": "Bilindkirinan nîşan bide",
+ "hide_reblogs": "Bilindkirinan veşêre"
},
"timeline": {
"filtered": "Parzûnkirî",
@@ -212,10 +218,16 @@
"get_started": "Dest pê bike",
"log_in": "Têkeve"
},
+ "login": {
+ "title": "Dîsa bi xêr hatî",
+ "subtitle": "Têketinê bike ser rajekarê ku te ajimêrê xwe tê de çê kiriye.",
+ "server_search_field": {
+ "placeholder": "Girêdanê têxe an jî li rajekarê xwe bigere"
+ }
+ },
"server_picker": {
"title": "Mastodon ji bikarhênerên di civakên cuda de pêk tê.",
- "subtitle": "Li gorî berjewendî, herêm, an jî armancek gelemperî civakekê hilbijêre.",
- "subtitle_extend": "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.",
+ "subtitle": "Li gorî herêm, berjewendî, an jî armanceke giştî rajekarekê hilbijêre. Tu hîn jî dikarî li ser Mastodon bi her kesî re biaxivî, her rajekarê te çi be.",
"button": {
"category": {
"all": "Hemû",
@@ -242,8 +254,7 @@
"category": "BEŞ"
},
"input": {
- "placeholder": "Li rajekaran bigere",
- "search_servers_or_enter_url": "Li rajekaran bigere an jî girêdanê têxe"
+ "search_servers_or_enter_url": "Li civakan bigere an jî girêdanê têxe"
},
"empty_state": {
"finding_servers": "Peydakirina rajekarên berdest...",
@@ -376,7 +387,13 @@
"video": "vîdyo",
"attachment_broken": "Ev %s naxebite û nayê barkirin\n li ser Mastodon.",
"description_photo": "Wêneyê ji bo kêmbînên dîtbar bide nasîn...",
- "description_video": "Vîdyoyê ji bo kêmbînên dîtbar bide nasîn..."
+ "description_video": "Vîdyoyê ji bo kêmbînên dîtbar bide nasîn...",
+ "load_failed": "Barkirin têk çû",
+ "upload_failed": "Barkirin têk çû",
+ "can_not_recognize_this_media_attachment": "Nikare ev pêveka medyayê nas bike",
+ "attachment_too_large": "Pêvek pir mezin e",
+ "compressing_state": "Tê guvaştin...",
+ "server_processing_state": "Pêvajoya rajekar pêş de diçe..."
},
"poll": {
"duration_time": "Dirêjî: %s",
@@ -386,7 +403,9 @@
"one_day": "1 Roj",
"three_days": "3 Roj",
"seven_days": "7 Roj",
- "option_number": "Vebijêrk %ld"
+ "option_number": "Vebijêrk %ld",
+ "the_poll_is_invalid": "Ev dengdayîn ne derbasdar e",
+ "the_poll_has_empty_option": "Vebijêrkên vê dengdayînê vala ne"
},
"content_warning": {
"placeholder": "Li vir hişyariyek hûrgilî binivîsine..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Hilbijêrê emojî yên kesanekirî",
"enable_content_warning": "Hişyariya naverokê çalak bike",
"disable_content_warning": "Hişyariya naverokê neçalak bike",
- "post_visibility_menu": "Kulîna xuyabûna şandiyê"
+ "post_visibility_menu": "Kulîna xuyabûna şandiyê",
+ "post_options": "Vebijêrkên şandiyê",
+ "posting_as": "Biweşîne wekî %s"
},
"keyboard": {
"discard_post": "Şandî paşguh bike",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Nîşan",
"content": "Naverok"
+ },
+ "verified": {
+ "short": "Hate piştrastkirin li ser %s",
+ "long": "Xwedaniya li vê girêdanê di %s de hatiye kontrolkirin"
}
},
"segmented_control": {
@@ -459,12 +484,12 @@
"message": "Ji bo rakirina astengkirinê %s bipejirîne"
},
"confirm_show_reblogs": {
- "title": "Show Reblogs",
- "message": "Confirm to show reblogs"
+ "title": "Bilindkirinan nîşan bide",
+ "message": "Bo nîşandana bilindkirinan bipejirîne"
},
"confirm_hide_reblogs": {
- "title": "Hide Reblogs",
- "message": "Confirm to hide reblogs"
+ "title": "Bilindkirinan veşêre",
+ "message": "Bo veşartina bilindkirinan bipejirîne"
}
},
"accessibility": {
@@ -696,7 +721,7 @@
"accessibility_hint": "Du caran bitikîne da ku çarçoveyahilpekok ji holê rakî"
},
"bookmark": {
- "title": "Bookmarks"
+ "title": "Şûnpel"
}
}
}
diff --git a/Localization/StringsConvertor/input/ko.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/ko.lproj/Localizable.stringsdict
index 9628be614..77aac5569 100644
--- a/Localization/StringsConvertor/input/ko.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/ko.lproj/Localizable.stringsdict
@@ -44,6 +44,20 @@
%ld 글자
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ 글자 남음
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld 글자
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/ko.lproj/app.json b/Localization/StringsConvertor/input/ko.lproj/app.json
index bbb4d1dea..070386bf9 100644
--- a/Localization/StringsConvertor/input/ko.lproj/app.json
+++ b/Localization/StringsConvertor/input/ko.lproj/app.json
@@ -75,7 +75,7 @@
"save_photo": "사진 저장",
"copy_photo": "사진 복사",
"sign_in": "로그인",
- "sign_up": "회원가입",
+ "sign_up": "계정 생성",
"see_more": "더 보기",
"preview": "미리보기",
"share": "공유",
@@ -129,13 +129,19 @@
"show_post": "게시물 보기",
"show_user_profile": "사용자 프로필 보기",
"content_warning": "열람 주의",
- "sensitive_content": "Sensitive Content",
+ "sensitive_content": "민감한 콘텐츠",
"media_content_warning": "아무 곳이나 눌러서 보기",
"tap_to_reveal": "눌러서 확인",
"poll": {
"vote": "투표",
"closed": "마감"
},
+ "meta_entity": {
+ "url": "링크: %s",
+ "hashtag": "해시태그: %s",
+ "mention": "프로필 보기: %s",
+ "email": "이메일 주소: %s"
+ },
"actions": {
"reply": "답글",
"reblog": "리블로그",
@@ -212,10 +218,16 @@
"get_started": "시작하기",
"log_in": "로그인"
},
+ "login": {
+ "title": "돌아오신 것을 환영합니다",
+ "subtitle": "계정을 만든 서버에 로그인.",
+ "server_search_field": {
+ "placeholder": "URL을 입력하거나 서버를 검색"
+ }
+ },
"server_picker": {
"title": "서버를 고르세요,\n아무 서버나 좋습니다.",
- "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.",
+ "subtitle": "당신의 지역이나, 관심사에 따라, 혹은 그냥 일반적인 목적의 서버를 고르세요. 어떤 서버를 고르더라도 마스토돈의 다른 모두와 소통할 수 있습니다.",
"button": {
"category": {
"all": "모두",
@@ -242,8 +254,7 @@
"category": "분류"
},
"input": {
- "placeholder": "서버 검색",
- "search_servers_or_enter_url": "서버를 검색하거나 URL을 입력하세요"
+ "search_servers_or_enter_url": "커뮤니티를 검색하거나 URL을 입력"
},
"empty_state": {
"finding_servers": "사용 가능한 서버를 찾는 중입니다...",
@@ -376,7 +387,13 @@
"video": "동영상",
"attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.",
"description_photo": "시각장애인을 위한 사진 설명…",
- "description_video": "시각장애인을 위한 영상 설명…"
+ "description_video": "시각장애인을 위한 영상 설명…",
+ "load_failed": "불러오기 실패",
+ "upload_failed": "업로드 실패",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "첨부파일이 너무 큽니다",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "기간: %s",
@@ -386,7 +403,9 @@
"one_day": "1일",
"three_days": "3일",
"seven_days": "7일",
- "option_number": "옵션 %ld"
+ "option_number": "옵션 %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "정확한 경고 문구를 여기에 작성하세요…"
@@ -407,7 +426,9 @@
"custom_emoji_picker": "커스텀 에모지 선택기",
"enable_content_warning": "열람 주의 설정",
"disable_content_warning": "열람 주의 해제",
- "post_visibility_menu": "게시물 공개범위 메뉴"
+ "post_visibility_menu": "게시물 공개범위 메뉴",
+ "post_options": "게시물 옵션",
+ "posting_as": "%s로 게시"
},
"keyboard": {
"discard_post": "글 버리기",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "라벨",
"content": "내용"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
diff --git a/Localization/StringsConvertor/input/lv.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/lv.lproj/Localizable.stringsdict
index 25f32c98d..ac30b4f8b 100644
--- a/Localization/StringsConvertor/input/lv.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/lv.lproj/Localizable.stringsdict
@@ -56,6 +56,24 @@
%ld characters
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ %ld characters
+ one
+ 1 character
+ other
+ %ld characters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/lv.lproj/app.json b/Localization/StringsConvertor/input/lv.lproj/app.json
index 0051383db..1ca18400b 100644
--- a/Localization/StringsConvertor/input/lv.lproj/app.json
+++ b/Localization/StringsConvertor/input/lv.lproj/app.json
@@ -74,8 +74,8 @@
"take_photo": "Uzņemt bildi",
"save_photo": "Saglabāt bildi",
"copy_photo": "Kopēt bildi",
- "sign_in": "Pieteikties",
- "sign_up": "Reģistrēties",
+ "sign_in": "Log in",
+ "sign_up": "Create account",
"see_more": "Skatīt vairāk",
"preview": "Priekšskatījums",
"share": "Dalīties",
@@ -136,6 +136,12 @@
"vote": "Balsot",
"closed": "Aizvērts"
},
+ "meta_entity": {
+ "url": "Link: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Show Profile: %s",
+ "email": "Email address: %s"
+ },
"actions": {
"reply": "Atbildēt",
"reblog": "Reblogot",
@@ -212,10 +218,16 @@
"get_started": "Get Started",
"log_in": "Pieteikties"
},
+ "login": {
+ "title": "Welcome back",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Enter URL or search for your server"
+ }
+ },
"server_picker": {
"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.",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
"button": {
"category": {
"all": "Visi",
@@ -242,8 +254,7 @@
"category": "KATEGORIJA"
},
"input": {
- "placeholder": "Meklēt serverus",
- "search_servers_or_enter_url": "Search servers or enter URL"
+ "search_servers_or_enter_url": "Search communities or enter URL"
},
"empty_state": {
"finding_servers": "Finding available servers...",
@@ -376,7 +387,13 @@
"video": "video",
"attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.",
"description_photo": "Describe the photo for the visually-impaired...",
- "description_video": "Describe the video for the visually-impaired..."
+ "description_video": "Describe the video for the visually-impaired...",
+ "load_failed": "Load Failed",
+ "upload_failed": "Upload Failed",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "Attachment too large",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "Duration: %s",
@@ -386,7 +403,9 @@
"one_day": "1 Diena",
"three_days": "3 Dienas",
"seven_days": "7 Dienas",
- "option_number": "Option %ld"
+ "option_number": "Option %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "Write an accurate warning here..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Custom Emoji Picker",
"enable_content_warning": "Enable Content Warning",
"disable_content_warning": "Disable Content Warning",
- "post_visibility_menu": "Post Visibility Menu"
+ "post_visibility_menu": "Post Visibility Menu",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "Discard Post",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Label",
"content": "Saturs"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
diff --git a/Localization/StringsConvertor/input/nl.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/nl.lproj/Localizable.stringsdict
index 314600ff7..84769b0c1 100644
--- a/Localization/StringsConvertor/input/nl.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/nl.lproj/Localizable.stringsdict
@@ -50,6 +50,22 @@
%ld tekens
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character
+ other
+ %ld characters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/nl.lproj/app.json b/Localization/StringsConvertor/input/nl.lproj/app.json
index 649fe5064..589c51d2d 100644
--- a/Localization/StringsConvertor/input/nl.lproj/app.json
+++ b/Localization/StringsConvertor/input/nl.lproj/app.json
@@ -74,8 +74,8 @@
"take_photo": "Maak foto",
"save_photo": "Bewaar foto",
"copy_photo": "Kopieer foto",
- "sign_in": "Aanmelden",
- "sign_up": "Registreren",
+ "sign_in": "Log in",
+ "sign_up": "Create account",
"see_more": "Meer",
"preview": "Voorvertoning",
"share": "Deel",
@@ -136,6 +136,12 @@
"vote": "Stemmen",
"closed": "Gesloten"
},
+ "meta_entity": {
+ "url": "Link: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Show Profile: %s",
+ "email": "Email address: %s"
+ },
"actions": {
"reply": "Reageren",
"reblog": "Delen",
@@ -212,10 +218,16 @@
"get_started": "Aan de slag",
"log_in": "Log in"
},
+ "login": {
+ "title": "Welcome back",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Enter URL or search for your server"
+ }
+ },
"server_picker": {
"title": "Kies een server, welke dan ook.",
- "subtitle": "Kies een gemeenschap gebaseerd op jouw interesses, regio of een algemeen doel.",
- "subtitle_extend": "Kies een gemeenschap gebaseerd op jouw interesses, regio, of een algemeen doel. Elke gemeenschap wordt beheerd door een volledig onafhankelijke organisatie of individu.",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
"button": {
"category": {
"all": "Alles",
@@ -242,8 +254,7 @@
"category": "CATEGORIE"
},
"input": {
- "placeholder": "Zoek uw server of sluit u bij een nieuwe server aan...",
- "search_servers_or_enter_url": "Search servers or enter URL"
+ "search_servers_or_enter_url": "Search communities or enter URL"
},
"empty_state": {
"finding_servers": "Beschikbare servers zoeken...",
@@ -376,7 +387,13 @@
"video": "video",
"attachment_broken": "Deze %s is corrupt en kan niet geüpload worden naar Mastodon.",
"description_photo": "Omschrijf de foto voor mensen met een visuele beperking...",
- "description_video": "Omschrijf de video voor mensen met een visuele beperking..."
+ "description_video": "Omschrijf de video voor mensen met een visuele beperking...",
+ "load_failed": "Load Failed",
+ "upload_failed": "Upload Failed",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "Attachment too large",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "Duur: %s",
@@ -386,7 +403,9 @@
"one_day": "1 Dag",
"three_days": "3 Dagen",
"seven_days": "7 Dagen",
- "option_number": "Optie %ld"
+ "option_number": "Optie %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "Schrijf hier een nauwkeurige waarschuwing..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Eigen Emojikiezer",
"enable_content_warning": "Inhoudswaarschuwing inschakelen",
"disable_content_warning": "Inhoudswaarschuwing Uitschakelen",
- "post_visibility_menu": "Berichtzichtbaarheidsmenu"
+ "post_visibility_menu": "Berichtzichtbaarheidsmenu",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "Bericht Verwijderen",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Etiket",
"content": "Inhoud"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
diff --git a/Localization/StringsConvertor/input/pt-BR.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/pt-BR.lproj/Localizable.stringsdict
index ba1532740..02fbf2d20 100644
--- a/Localization/StringsConvertor/input/pt-BR.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/pt-BR.lproj/Localizable.stringsdict
@@ -50,6 +50,22 @@
%ld caracteres
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ restantes
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 carácter
+ other
+ %ld carácteres
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
@@ -72,9 +88,9 @@
NSStringFormatValueTypeKey
ld
one
- Followed by %1$@, and another mutual
+ Seguido por %1$@, e outro em comum
other
- Followed by %1$@, and %ld mutuals
+ Seguido por %1$@, e %ld em comum
plural.count.metric_formatted.post
@@ -104,9 +120,9 @@
NSStringFormatValueTypeKey
ld
one
- 1 media
+ 1 mídia
other
- %ld media
+ %ld mídias
plural.count.post
diff --git a/Localization/StringsConvertor/input/pt-BR.lproj/app.json b/Localization/StringsConvertor/input/pt-BR.lproj/app.json
index 063ed346c..60d858235 100644
--- a/Localization/StringsConvertor/input/pt-BR.lproj/app.json
+++ b/Localization/StringsConvertor/input/pt-BR.lproj/app.json
@@ -2,11 +2,11 @@
"common": {
"alerts": {
"common": {
- "please_try_again": "Por favor tente novamente.",
+ "please_try_again": "Por favor, tente novamente.",
"please_try_again_later": "Tente novamente mais tarde."
},
"sign_up_failure": {
- "title": "Sign Up Failure"
+ "title": "Falha no cadastro"
},
"server_error": {
"title": "Erro do servidor"
@@ -17,7 +17,7 @@
},
"discard_post_content": {
"title": "Deletar Rascunho",
- "message": "Confirm to discard composed post content."
+ "message": "Confirme para descartar o conteúdo da publicação composta."
},
"publish_post_failure": {
"title": "Falha ao publicar",
@@ -42,7 +42,7 @@
},
"save_photo_failure": {
"title": "Falha ao salvar foto",
- "message": "Please enable the photo library access permission to save the photo."
+ "message": "Por favor, ative a permissão de acesso à galeria para salvar a foto."
},
"delete_post": {
"title": "Deletar Toot",
@@ -71,632 +71,657 @@
"cancel": "Cancelar",
"discard": "Descartar",
"try_again": "Tente novamente",
- "take_photo": "Take Photo",
+ "take_photo": "Tirar foto",
"save_photo": "Salvar foto",
"copy_photo": "Copiar foto",
- "sign_in": "Sign In",
- "sign_up": "Sign Up",
- "see_more": "See More",
- "preview": "Preview",
+ "sign_in": "Entrar",
+ "sign_up": "Criar conta",
+ "see_more": "Ver mais",
+ "preview": "Pré-visualização",
"share": "Compartilhar",
"share_user": "Compartilhar %s",
- "share_post": "Share Post",
- "open_in_safari": "Open in Safari",
- "open_in_browser": "Open in Browser",
- "find_people": "Find people to follow",
- "manually_search": "Manually search instead",
- "skip": "Skip",
- "reply": "Reply",
- "report_user": "Report %s",
- "block_domain": "Block %s",
- "unblock_domain": "Unblock %s",
- "settings": "Settings",
- "delete": "Delete"
+ "share_post": "Compartilhar postagem",
+ "open_in_safari": "Abrir no Safari",
+ "open_in_browser": "Abrir no navegador",
+ "find_people": "Encontre pessoas para seguir",
+ "manually_search": "Procure manualmente em vez disso",
+ "skip": "Pular",
+ "reply": "Responder",
+ "report_user": "Denunciar %s",
+ "block_domain": "Bloquear %s",
+ "unblock_domain": "Desbloquear %s",
+ "settings": "Configurações",
+ "delete": "Excluir"
},
"tabs": {
- "home": "Home",
- "search": "Search",
- "notification": "Notification",
- "profile": "Profile"
+ "home": "Início",
+ "search": "Buscar",
+ "notification": "Notificação",
+ "profile": "Perfil"
},
"keyboard": {
"common": {
- "switch_to_tab": "Switch to %s",
- "compose_new_post": "Compose New Post",
- "show_favorites": "Show Favorites",
- "open_settings": "Open Settings"
+ "switch_to_tab": "Mudar para %s",
+ "compose_new_post": "Compor novo toot",
+ "show_favorites": "Mostrar favoritos",
+ "open_settings": "Abrir configurações"
},
"timeline": {
- "previous_status": "Previous Post",
- "next_status": "Next Post",
- "open_status": "Open Post",
- "open_author_profile": "Open Author's Profile",
- "open_reblogger_profile": "Open Reblogger's Profile",
- "reply_status": "Reply to Post",
- "toggle_reblog": "Toggle Reblog on Post",
- "toggle_favorite": "Toggle Favorite on Post",
- "toggle_content_warning": "Toggle Content Warning",
- "preview_image": "Preview Image"
+ "previous_status": "Postagem anterior",
+ "next_status": "Próxima postagem",
+ "open_status": "Abrir toot",
+ "open_author_profile": "Abrir perfil do autor",
+ "open_reblogger_profile": "Abrir perfil do reblogger",
+ "reply_status": "Responder toot",
+ "toggle_reblog": "Ativar/desativar Reblog na postagem",
+ "toggle_favorite": "Ativar/desativar Favorito na postagem",
+ "toggle_content_warning": "Ativar/desativar Aviso de Conteúdo",
+ "preview_image": "Pré-visualizar imagem"
},
"segmented_control": {
- "previous_section": "Previous Section",
- "next_section": "Next Section"
+ "previous_section": "Seção anterior",
+ "next_section": "Próxima seção"
}
},
"status": {
- "user_reblogged": "%s reblogged",
- "user_replied_to": "Replied to %s",
- "show_post": "Show Post",
- "show_user_profile": "Show user profile",
- "content_warning": "Content Warning",
- "sensitive_content": "Sensitive Content",
- "media_content_warning": "Tap anywhere to reveal",
- "tap_to_reveal": "Tap to reveal",
+ "user_reblogged": "%s reblogou",
+ "user_replied_to": "Em resposta a %s",
+ "show_post": "Mostrar postagem",
+ "show_user_profile": "Mostrar perfil de usuário",
+ "content_warning": "Aviso de Conteúdo",
+ "sensitive_content": "Conteúdo sensível",
+ "media_content_warning": "Toque em qualquer lugar para revelar",
+ "tap_to_reveal": "Toque para revelar",
"poll": {
- "vote": "Vote",
- "closed": "Closed"
+ "vote": "Votar",
+ "closed": "Fechado"
+ },
+ "meta_entity": {
+ "url": "Link: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Mostrar perfil: %s",
+ "email": "Endereço de e-mail: %s"
},
"actions": {
"reply": "Responder",
- "reblog": "Reblog",
- "unreblog": "Undo reblog",
- "favorite": "Favorite",
- "unfavorite": "Unfavorite",
+ "reblog": "Reblogar",
+ "unreblog": "Desfazer reblog",
+ "favorite": "Favoritar",
+ "unfavorite": "Remover favorito",
"menu": "Menu",
- "hide": "Hide",
- "show_image": "Show image",
- "show_gif": "Show GIF",
- "show_video_player": "Show video player",
- "tap_then_hold_to_show_menu": "Tap then hold to show menu"
+ "hide": "Ocultar",
+ "show_image": "Exibir imagem",
+ "show_gif": "Exibir GIF",
+ "show_video_player": "Mostrar reprodutor de vídeo",
+ "tap_then_hold_to_show_menu": "Toque e em seguida segure para exibir o menu"
},
"tag": {
"url": "URL",
- "mention": "Mention",
+ "mention": "Mencionar",
"link": "Link",
"hashtag": "Hashtag",
- "email": "Email",
+ "email": "E-mail",
"emoji": "Emoji"
},
"visibility": {
- "unlisted": "Everyone can see this post but not display in the public timeline.",
- "private": "Only their followers can see this post.",
- "private_from_me": "Only my followers can see this post.",
- "direct": "Only mentioned user can see this post."
+ "unlisted": "Todos podem ver esta postagem, mas não são exibidos na linha do tempo pública.",
+ "private": "Somente seus seguidores podem ver essa postagem.",
+ "private_from_me": "Somente meus seguidores podem ver essa postagem.",
+ "direct": "Somente o usuário mencionado pode ver essa postagem."
}
},
"friendship": {
- "follow": "Follow",
- "following": "Following",
- "request": "Request",
- "pending": "Pending",
- "block": "Block",
- "block_user": "Block %s",
- "block_domain": "Block %s",
- "unblock": "Unblock",
- "unblock_user": "Unblock %s",
- "blocked": "Blocked",
- "mute": "Mute",
- "mute_user": "Mute %s",
- "unmute": "Unmute",
- "unmute_user": "Unmute %s",
- "muted": "Muted",
- "edit_info": "Edit Info",
- "show_reblogs": "Show Reblogs",
- "hide_reblogs": "Hide Reblogs"
+ "follow": "Seguir",
+ "following": "Seguindo",
+ "request": "Solicitação",
+ "pending": "Pendente",
+ "block": "Bloquear",
+ "block_user": "Bloquear %s",
+ "block_domain": "Bloquear %s",
+ "unblock": "Desbloquear",
+ "unblock_user": "Desbloquear %s",
+ "blocked": "Bloqueado",
+ "mute": "Silenciar",
+ "mute_user": "Silenciar %s",
+ "unmute": "Remover silenciado",
+ "unmute_user": "Remover silenciado %s",
+ "muted": "Silenciado",
+ "edit_info": "Editar informação",
+ "show_reblogs": "Mostrar Reblogs",
+ "hide_reblogs": "Ocultar Reblogs"
},
"timeline": {
- "filtered": "Filtered",
+ "filtered": "Filtrado",
"timestamp": {
- "now": "Now"
+ "now": "Agora"
},
"loader": {
- "load_missing_posts": "Load missing posts",
- "loading_missing_posts": "Loading missing posts...",
- "show_more_replies": "Show more replies"
+ "load_missing_posts": "Carregar postagens em falta",
+ "loading_missing_posts": "Carregando postagens em falta...",
+ "show_more_replies": "Exibir mais respostas"
},
"header": {
- "no_status_found": "No Post Found",
- "blocking_warning": "You can’t view this user's profile\nuntil you unblock them.\nYour profile looks like this to them.",
- "user_blocking_warning": "You can’t view %s’s profile\nuntil you unblock them.\nYour profile looks like this to them.",
- "blocked_warning": "You can’t view this user’s profile\nuntil they unblock you.",
- "user_blocked_warning": "You can’t view %s’s profile\nuntil they unblock you.",
- "suspended_warning": "This user has been suspended.",
- "user_suspended_warning": "%s’s account has been suspended."
+ "no_status_found": "Nenhuma postagem encontrada",
+ "blocking_warning": "Você não pode ver o perfil deste usuário até desbloqueá-lo.\nSeu perfil aparece assim para esse usuário.",
+ "user_blocking_warning": "Você não pode ver o perfil de %s até desbloqueá-lo.\nSeu perfil aparece assim para esse usuário.",
+ "blocked_warning": "Você não pode ver o perfil desse usuário até que ele o desbloqueie.",
+ "user_blocked_warning": "Você não pode ver o perfil de %s até que ele o desbloqueie.",
+ "suspended_warning": "Esse usuário foi suspenso.",
+ "user_suspended_warning": "A conta de %s foi suspensa."
}
}
}
},
"scene": {
"welcome": {
- "slogan": "Social networking\nback in your hands.",
- "get_started": "Get Started",
- "log_in": "Log In"
+ "slogan": "Você no controle de sua rede social.",
+ "get_started": "Comece já",
+ "log_in": "Entrar"
+ },
+ "login": {
+ "title": "Bem-vindo de volta",
+ "subtitle": "Logado na instância em que você criou a sua conta.",
+ "server_search_field": {
+ "placeholder": "Insira a URL ou procure pela sua instância"
+ }
},
"server_picker": {
- "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.",
+ "title": "Mastodon é feito de usuários em instâncias diferentes.",
+ "subtitle": "Escolha uma instância baseada na sua região, interesses, ou uma de uso geral. Você ainda poderá conversar com qualquer um no Mastodon, independente da instância.",
"button": {
"category": {
- "all": "All",
- "all_accessiblity_description": "Category: All",
- "academia": "academia",
- "activism": "activism",
- "food": "food",
+ "all": "Todos",
+ "all_accessiblity_description": "Categoria: Todos",
+ "academia": "acadêmico",
+ "activism": "ativismo",
+ "food": "comida",
"furry": "furry",
- "games": "games",
- "general": "general",
- "journalism": "journalism",
+ "games": "jogos",
+ "general": "geral",
+ "journalism": "jornalismo",
"lgbt": "lgbt",
"regional": "regional",
- "art": "art",
- "music": "music",
- "tech": "tech"
+ "art": "arte",
+ "music": "música",
+ "tech": "tecnologia"
},
- "see_less": "See Less",
- "see_more": "See More"
+ "see_less": "Ver menos",
+ "see_more": "Ver mais"
},
"label": {
- "language": "LANGUAGE",
- "users": "USERS",
- "category": "CATEGORY"
+ "language": "Idioma",
+ "users": "Usuários",
+ "category": "Categoria"
},
"input": {
- "placeholder": "Search servers",
- "search_servers_or_enter_url": "Search servers or enter URL"
+ "search_servers_or_enter_url": "Procurar comunidades ou inserir URL"
},
"empty_state": {
- "finding_servers": "Finding available servers...",
- "bad_network": "Something went wrong while loading the data. Check your internet connection.",
- "no_results": "No results"
+ "finding_servers": "Procurando instâncias disponíveis...",
+ "bad_network": "Algo deu errado ao carregar os dados. Verifique sua conexão com a internet.",
+ "no_results": "Sem resultados"
}
},
"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",
+ "title": "Vamos configurar você em %s",
+ "lets_get_you_set_up_on_domain": "Vamos configurar você em %s",
"input": {
"avatar": {
- "delete": "Delete"
+ "delete": "Excluir"
},
"username": {
- "placeholder": "username",
- "duplicate_prompt": "This username is taken."
+ "placeholder": "nome de usuário",
+ "duplicate_prompt": "Esse nome de usuário já está sendo usado."
},
"display_name": {
- "placeholder": "display name"
+ "placeholder": "nome de exibição"
},
"email": {
- "placeholder": "email"
+ "placeholder": "e-mail"
},
"password": {
- "placeholder": "password",
- "require": "Your password needs at least:",
- "character_limit": "8 characters",
+ "placeholder": "senha",
+ "require": "Sua senha deve ter pelo menos:",
+ "character_limit": "8 carácteres",
"accessibility": {
- "checked": "checked",
- "unchecked": "unchecked"
+ "checked": "marcado",
+ "unchecked": "desmarcado"
},
- "hint": "Your password needs at least eight characters"
+ "hint": "Sua senha precisa ter pelo menos oito carácteres"
},
"invite": {
- "registration_user_invite_request": "Why do you want to join?"
+ "registration_user_invite_request": "Por que você deseja se inscrever?"
}
},
"error": {
"item": {
- "username": "Username",
- "email": "Email",
- "password": "Password",
- "agreement": "Agreement",
- "locale": "Locale",
- "reason": "Reason"
+ "username": "Nome de usuário",
+ "email": "E-mail",
+ "password": "Senha",
+ "agreement": "Termos de uso",
+ "locale": "Localidade",
+ "reason": "Motivo"
},
"reason": {
- "blocked": "%s contains a disallowed email provider",
- "unreachable": "%s does not seem to exist",
- "taken": "%s is already in use",
- "reserved": "%s is a reserved keyword",
- "accepted": "%s must be accepted",
- "blank": "%s is required",
- "invalid": "%s is invalid",
- "too_long": "%s is too long",
- "too_short": "%s is too short",
- "inclusion": "%s is not a supported value"
+ "blocked": "%s contém um provedor de e-mail não permitido",
+ "unreachable": "%s parece não existir",
+ "taken": "%s já está em uso",
+ "reserved": "%s é uma palavra-chave reservada",
+ "accepted": "%s deve ser aceite",
+ "blank": "%s é obrigatório",
+ "invalid": "%s é inválido",
+ "too_long": "%s é muito longo",
+ "too_short": "%s é muito curto",
+ "inclusion": "%s não é um valor suportado"
},
"special": {
- "username_invalid": "Username must only contain alphanumeric characters and underscores",
- "username_too_long": "Username is too long (can’t be longer than 30 characters)",
- "email_invalid": "This is not a valid email address",
- "password_too_short": "Password is too short (must be at least 8 characters)"
+ "username_invalid": "O nome de usuário só pode conter caracteres alfanuméricos e underlines (_)",
+ "username_too_long": "Nome de usuário é muito longo (não pode ter mais de 30 caracteres)",
+ "email_invalid": "Este não é um endereço de e-mail válido",
+ "password_too_short": "A senha é muito curta (deve ter pelo menos 8 caracteres)"
}
}
},
"server_rules": {
- "title": "Some ground rules.",
- "subtitle": "These are set and enforced by the %s moderators.",
- "prompt": "By continuing, you’re subject to the terms of service and privacy policy for %s.",
- "terms_of_service": "terms of service",
- "privacy_policy": "privacy policy",
+ "title": "Algumas regras básicas.",
+ "subtitle": "Estes são definidos e aplicados pelos moderadores da %s.",
+ "prompt": "Ao continuar, você estará sujeito aos termos de serviço e política de privacidade para %s.",
+ "terms_of_service": "termos de serviço",
+ "privacy_policy": "política de privacidade",
"button": {
- "confirm": "I Agree"
+ "confirm": "Eu concordo"
}
},
"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",
+ "title": "Uma última coisa.",
+ "subtitle": "Clique no link que te enviamos por e-mail para verificar a sua conta.",
+ "tap_the_link_we_emailed_to_you_to_verify_your_account": "Clique no link que te enviamos por e-mail para verificar a sua conta",
"button": {
- "open_email_app": "Open Email App",
- "resend": "Resend"
+ "open_email_app": "Abrir aplicativo de e-mail",
+ "resend": "Reenviar"
},
"dont_receive_email": {
- "title": "Check your email",
- "description": "Check if your email address is correct as well as your junk folder if you haven’t.",
- "resend_email": "Resend Email"
+ "title": "Verifique o seu e-mail",
+ "description": "Verifique se o seu endereço de e-mail está correto, e também a sua pasta de spam caso não tenha verificado.",
+ "resend_email": "Reenviar e-mail"
},
"open_email_app": {
- "title": "Check your inbox.",
- "description": "We just sent you an email. Check your junk folder if you haven’t.",
- "mail": "Mail",
- "open_email_client": "Open Email Client"
+ "title": "Verifique sua caixa de entrada.",
+ "description": "Enviamos um e-mail para você. Verifique sua pasta de spam caso ainda tenha verificado.",
+ "mail": "Correio",
+ "open_email_client": "Abrir Cliente de Email"
}
},
"home_timeline": {
- "title": "Home",
+ "title": "Início",
"navigation_bar_state": {
- "offline": "Offline",
- "new_posts": "See new posts",
- "published": "Published!",
- "Publishing": "Publishing post...",
+ "offline": "Desconectado",
+ "new_posts": "Ver novas postagens",
+ "published": "Publicado!",
+ "Publishing": "Publicando toot...",
"accessibility": {
- "logo_label": "Logo Button",
- "logo_hint": "Tap to scroll to top and tap again to previous location"
+ "logo_label": "Botão do logotipo",
+ "logo_hint": "Toque para rolar para o topo e toque novamente para a localização anterior"
}
}
},
"suggestion_account": {
- "title": "Find People to Follow",
- "follow_explain": "When you follow someone, you’ll see their posts in your home feed."
+ "title": "Encontre pessoas para seguir",
+ "follow_explain": "Ao seguir alguém, você verá as publicações dessa pessoa na sua página inicial."
},
"compose": {
"title": {
- "new_post": "New Post",
- "new_reply": "New Reply"
+ "new_post": "Novo toot",
+ "new_reply": "Nova resposta"
},
"media_selection": {
- "camera": "Take Photo",
- "photo_library": "Photo Library",
- "browse": "Browse"
+ "camera": "Tirar foto",
+ "photo_library": "Galeria",
+ "browse": "Navegar"
},
- "content_input_placeholder": "Type or paste what’s on your mind",
- "compose_action": "Publish",
- "replying_to_user": "replying to %s",
+ "content_input_placeholder": "Digite ou cole o que está na sua mente",
+ "compose_action": "Publicar",
+ "replying_to_user": "em resposta a %s",
"attachment": {
- "photo": "photo",
- "video": "video",
- "attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.",
- "description_photo": "Describe the photo for the visually-impaired...",
- "description_video": "Describe the video for the visually-impaired..."
+ "photo": "foto",
+ "video": "vídeo",
+ "attachment_broken": "Este %s está quebrado e não pode ser\nenviado para o Mastodon.",
+ "description_photo": "Descreva a foto para deficientes visuais...",
+ "description_video": "Descreva o vídeo para os deficientes visuais...",
+ "load_failed": "Falha ao carregar",
+ "upload_failed": "Falha no carregamento",
+ "can_not_recognize_this_media_attachment": "Não é possível reconhecer este anexo de mídia",
+ "attachment_too_large": "O anexo é muito grande",
+ "compressing_state": "Compactando...",
+ "server_processing_state": "Servidor processando..."
},
"poll": {
- "duration_time": "Duration: %s",
- "thirty_minutes": "30 minutes",
- "one_hour": "1 Hour",
- "six_hours": "6 Hours",
- "one_day": "1 Day",
- "three_days": "3 Days",
- "seven_days": "7 Days",
- "option_number": "Option %ld"
+ "duration_time": "Duração: %s",
+ "thirty_minutes": "30 minutos",
+ "one_hour": "1 hora",
+ "six_hours": "6 horas",
+ "one_day": "1 dia",
+ "three_days": "3 dias",
+ "seven_days": "7 dias",
+ "option_number": "Opção %ld",
+ "the_poll_is_invalid": "A enquete é inválida",
+ "the_poll_has_empty_option": "A enquete tem uma opção vazia"
},
"content_warning": {
- "placeholder": "Write an accurate warning here..."
+ "placeholder": "Escreva um aviso de conteúdo preciso aqui..."
},
"visibility": {
- "public": "Public",
- "unlisted": "Unlisted",
- "private": "Followers only",
- "direct": "Only people I mention"
+ "public": "Público",
+ "unlisted": "Não listado",
+ "private": "Apenas seguidores",
+ "direct": "Apenas pessoas que menciono"
},
"auto_complete": {
- "space_to_add": "Space to add"
+ "space_to_add": "Espaço a adicionar"
},
"accessibility": {
- "append_attachment": "Add Attachment",
- "append_poll": "Add Poll",
- "remove_poll": "Remove Poll",
- "custom_emoji_picker": "Custom Emoji Picker",
- "enable_content_warning": "Enable Content Warning",
- "disable_content_warning": "Disable Content Warning",
- "post_visibility_menu": "Post Visibility Menu"
+ "append_attachment": "Adicionar anexo",
+ "append_poll": "Adicionar enquete",
+ "remove_poll": "Remover enquete",
+ "custom_emoji_picker": "Seletor de emoji personalizado",
+ "enable_content_warning": "Ativar Aviso de Conteúdo",
+ "disable_content_warning": "Desativar Aviso de Conteúdo",
+ "post_visibility_menu": "Menu de Visibilidade do Post",
+ "post_options": "Opções de postagem",
+ "posting_as": "Publicando como %s"
},
"keyboard": {
- "discard_post": "Discard Post",
- "publish_post": "Publish Post",
- "toggle_poll": "Toggle Poll",
- "toggle_content_warning": "Toggle Content Warning",
- "append_attachment_entry": "Add Attachment - %s",
- "select_visibility_entry": "Select Visibility - %s"
+ "discard_post": "Descartar postagem",
+ "publish_post": "Publicar postagem",
+ "toggle_poll": "Alternar enquete",
+ "toggle_content_warning": "Ativar/desativar Aviso de Conteúdo",
+ "append_attachment_entry": "Adicionar Anexo - %s",
+ "select_visibility_entry": "Selecionar Visibilidade - %s"
}
},
"profile": {
"header": {
- "follows_you": "Follows You"
+ "follows_you": "Segue você"
},
"dashboard": {
- "posts": "posts",
- "following": "following",
- "followers": "followers"
+ "posts": "toots",
+ "following": "seguindo",
+ "followers": "seguidores"
},
"fields": {
- "add_row": "Add Row",
+ "add_row": "Adicionar linha",
"placeholder": {
- "label": "Label",
- "content": "Content"
+ "label": "Descrição",
+ "content": "Conteúdo"
+ },
+ "verified": {
+ "short": "Verificado em %s",
+ "long": "O link foi verificado em %s"
}
},
"segmented_control": {
- "posts": "Posts",
- "replies": "Replies",
- "posts_and_replies": "Posts and Replies",
- "media": "Media",
- "about": "About"
+ "posts": "Toots",
+ "replies": "Respostas",
+ "posts_and_replies": "Toots e respostas",
+ "media": "Mídia",
+ "about": "Sobre"
},
"relationship_action_alert": {
"confirm_mute_user": {
- "title": "Mute Account",
- "message": "Confirm to mute %s"
+ "title": "Silenciar conta",
+ "message": "Confirme para silenciar %s"
},
"confirm_unmute_user": {
- "title": "Unmute Account",
- "message": "Confirm to unmute %s"
+ "title": "Tirar conta do silenciado",
+ "message": "Confirme para tirar %s do silenciado"
},
"confirm_block_user": {
- "title": "Block Account",
- "message": "Confirm to block %s"
+ "title": "Bloquear conta",
+ "message": "Confirme para bloquear %s"
},
"confirm_unblock_user": {
- "title": "Unblock Account",
- "message": "Confirm to unblock %s"
+ "title": "Desbloquear conta",
+ "message": "Confirme para desbloquear %s"
},
"confirm_show_reblogs": {
- "title": "Show Reblogs",
- "message": "Confirm to show reblogs"
+ "title": "Mostrar reblogs",
+ "message": "Confirmar para mostrar reblogs"
},
"confirm_hide_reblogs": {
- "title": "Hide Reblogs",
- "message": "Confirm to hide reblogs"
+ "title": "Ocultar reblogs",
+ "message": "Confirmar para ocultar reblogs"
}
},
"accessibility": {
- "show_avatar_image": "Show avatar image",
- "edit_avatar_image": "Edit avatar image",
- "show_banner_image": "Show banner image",
- "double_tap_to_open_the_list": "Double tap to open the list"
+ "show_avatar_image": "Mostrar foto de perfil",
+ "edit_avatar_image": "Editar foto de perfil",
+ "show_banner_image": "Mostrar foto de capa",
+ "double_tap_to_open_the_list": "Toque duas vezes para abrir a lista"
}
},
"follower": {
- "title": "follower",
- "footer": "Followers from other servers are not displayed."
+ "title": "seguidor",
+ "footer": "Seguidores de outras instâncias não são exibidos."
},
"following": {
- "title": "following",
- "footer": "Follows from other servers are not displayed."
+ "title": "seguindo",
+ "footer": "Contas que você segue de outras instâncias não são exibidas."
},
"familiarFollowers": {
- "title": "Followers you familiar",
- "followed_by_names": "Followed by %s"
+ "title": "Seguidores que você conhece",
+ "followed_by_names": "Seguido por %s"
},
"favorited_by": {
- "title": "Favorited By"
+ "title": "Favoritado por"
},
"reblogged_by": {
- "title": "Reblogged By"
+ "title": "Reblogado por"
},
"search": {
- "title": "Search",
+ "title": "Buscar",
"search_bar": {
- "placeholder": "Search hashtags and users",
- "cancel": "Cancel"
+ "placeholder": "Buscar hashtags e usuários",
+ "cancel": "Cancelar"
},
"recommend": {
- "button_text": "See All",
+ "button_text": "Ver tudo",
"hash_tag": {
- "title": "Trending on Mastodon",
- "description": "Hashtags that are getting quite a bit of attention",
- "people_talking": "%s people are talking"
+ "title": "Em tendência no Mastodon",
+ "description": "Hashtags que estão recebendo bastante atenção",
+ "people_talking": "%s pessoas estão falando"
},
"accounts": {
- "title": "Accounts you might like",
- "description": "You may like to follow these accounts",
- "follow": "Follow"
+ "title": "Contas que você deve gostar",
+ "description": "Você pode gostar de seguir estas contas",
+ "follow": "Seguir"
}
},
"searching": {
"segment": {
- "all": "All",
- "people": "People",
+ "all": "Todos",
+ "people": "Pessoas",
"hashtags": "Hashtags",
- "posts": "Posts"
+ "posts": "Toots"
},
"empty_state": {
- "no_results": "No results"
+ "no_results": "Sem resultados"
},
- "recent_search": "Recent searches",
- "clear": "Clear"
+ "recent_search": "Pesquisas recentes",
+ "clear": "Limpar"
}
},
"discovery": {
"tabs": {
- "posts": "Posts",
+ "posts": "Toots",
"hashtags": "Hashtags",
- "news": "News",
- "community": "Community",
- "for_you": "For You"
+ "news": "Notícias",
+ "community": "Comunidade",
+ "for_you": "Para você"
},
- "intro": "These are the posts gaining traction in your corner of Mastodon."
+ "intro": "Esses são os posts que estão ganhando força no seu canto do Mastodon."
},
"favorite": {
- "title": "Your Favorites"
+ "title": "Seus favoritos"
},
"notification": {
"title": {
- "Everything": "Everything",
- "Mentions": "Mentions"
+ "Everything": "Tudo",
+ "Mentions": "Menções"
},
"notification_description": {
- "followed_you": "followed you",
- "favorited_your_post": "favorited your post",
- "reblogged_your_post": "reblogged your post",
- "mentioned_you": "mentioned you",
- "request_to_follow_you": "request to follow you",
- "poll_has_ended": "poll has ended"
+ "followed_you": "seguiu você",
+ "favorited_your_post": "favoritou seu toot",
+ "reblogged_your_post": "reblogou seu toot",
+ "mentioned_you": "te mencionou",
+ "request_to_follow_you": "solicitação para te seguir",
+ "poll_has_ended": "enquete encerrada"
},
"keyobard": {
- "show_everything": "Show Everything",
- "show_mentions": "Show Mentions"
+ "show_everything": "Mostrar tudo",
+ "show_mentions": "Mostrar menções"
},
"follow_request": {
- "accept": "Accept",
- "accepted": "Accepted",
- "reject": "reject",
- "rejected": "Rejected"
+ "accept": "Aceitar",
+ "accepted": "Aceito",
+ "reject": "rejeitar",
+ "rejected": "Rejeitado"
}
},
"thread": {
- "back_title": "Post",
- "title": "Post from %s"
+ "back_title": "Toot",
+ "title": "Publicação de %s"
},
"settings": {
- "title": "Settings",
+ "title": "Configurações",
"section": {
"appearance": {
- "title": "Appearance",
- "automatic": "Automatic",
- "light": "Always Light",
- "dark": "Always Dark"
+ "title": "Aparência",
+ "automatic": "Automático",
+ "light": "Sempre Claro",
+ "dark": "Sempre Escuro"
},
"look_and_feel": {
- "title": "Look and Feel",
- "use_system": "Use System",
- "really_dark": "Really Dark",
- "sorta_dark": "Sorta Dark",
- "light": "Light"
+ "title": "Aparência e Comportamento",
+ "use_system": "Usar configuração do sistema",
+ "really_dark": "Bem escuro",
+ "sorta_dark": "Meio escuro",
+ "light": "Claro"
},
"notifications": {
- "title": "Notifications",
- "favorites": "Favorites my post",
- "follows": "Follows me",
- "boosts": "Reblogs my post",
- "mentions": "Mentions me",
+ "title": "Notificações",
+ "favorites": "Favoritaram minha publicação",
+ "follows": "Me segue",
+ "boosts": "Rebloga minha publicação",
+ "mentions": "Me menciona",
"trigger": {
- "anyone": "anyone",
- "follower": "a follower",
- "follow": "anyone I follow",
- "noone": "no one",
- "title": "Notify me when"
+ "anyone": "qualquer pessoa",
+ "follower": "um seguidor",
+ "follow": "qualquer um que eu siga",
+ "noone": "ninguém",
+ "title": "Me notificar quando"
}
},
"preference": {
- "title": "Preferences",
- "true_black_dark_mode": "True black dark mode",
- "disable_avatar_animation": "Disable animated avatars",
- "disable_emoji_animation": "Disable animated emojis",
- "using_default_browser": "Use default browser to open links",
- "open_links_in_mastodon": "Open links in Mastodon"
+ "title": "Preferências",
+ "true_black_dark_mode": "Modo preto",
+ "disable_avatar_animation": "Desativar fotos animadas",
+ "disable_emoji_animation": "Desativar emojis animados",
+ "using_default_browser": "Usar o navegador padrão pra abrir links",
+ "open_links_in_mastodon": "Abrir links no Mastodon"
},
"boring_zone": {
- "title": "The Boring Zone",
- "account_settings": "Account Settings",
- "terms": "Terms of Service",
- "privacy": "Privacy Policy"
+ "title": "A zona chata",
+ "account_settings": "Configurações da conta",
+ "terms": "Termos de serviço",
+ "privacy": "Política de privacidade"
},
"spicy_zone": {
- "title": "The Spicy Zone",
- "clear": "Clear Media Cache",
- "signout": "Sign Out"
+ "title": "A zona apimentada",
+ "clear": "Limpar cachê de mídia",
+ "signout": "Sair"
}
},
"footer": {
- "mastodon_description": "Mastodon is open source software. You can report issues on GitHub at %s (%s)"
+ "mastodon_description": "Mastodon é um software de código aberto. Você pode reportar problemas no GitHub em %s (%s)"
},
"keyboard": {
- "close_settings_window": "Close Settings Window"
+ "close_settings_window": "Fechar janela de configurações"
}
},
"report": {
- "title_report": "Report",
- "title": "Report %s",
- "step1": "Step 1 of 2",
- "step2": "Step 2 of 2",
- "content1": "Are there any other posts you’d like to add to the report?",
- "content2": "Is there anything the moderators should know about this report?",
- "report_sent_title": "Thanks for reporting, we’ll look into this.",
- "send": "Send Report",
- "skip_to_send": "Send without comment",
- "text_placeholder": "Type or paste additional comments",
- "reported": "REPORTED",
+ "title_report": "Denunciar",
+ "title": "Denunciar %s",
+ "step1": "Passo 1 de 2",
+ "step2": "Passo 2 de 2",
+ "content1": "Há outras postagens que você gostaria de adicionar na denúncia?",
+ "content2": "Há algo que os moderadores deveriam saber sobre esta denúncia?",
+ "report_sent_title": "Obrigado por denunciar, iremos analisar.",
+ "send": "Enviar denúncia",
+ "skip_to_send": "Enviar sem comentário",
+ "text_placeholder": "Digite ou cole comentários adicionais",
+ "reported": "DENUNCIADO",
"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_1_of_4": "Passo 1 de 4",
+ "whats_wrong_with_this_post": "O que há de errado com essa publicação?",
+ "whats_wrong_with_this_account": "O que há de errado com essa conta?",
+ "whats_wrong_with_this_username": "O que há de errado com %s?",
+ "select_the_best_match": "Selecione a melhor alternativa",
+ "i_dont_like_it": "Eu não gosto disso",
+ "it_is_not_something_you_want_to_see": "Não é algo que você gostaria de ver",
+ "its_spam": "É spam",
+ "malicious_links_fake_engagement_or_repetetive_replies": "Links maliciosos, engajamento falso, ou respostas repetitivas",
+ "it_violates_server_rules": "Isso viola as regras do servidor",
+ "you_are_aware_that_it_breaks_specific_rules": "Você está ciente que isso quebra regras específicas",
+ "its_something_else": "É outra coisa",
+ "the_issue_does_not_fit_into_other_categories": "O problema não se encaixa em outras categorias"
},
"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_2_of_4": "Passo 2 de 4",
+ "which_rules_are_being_violated": "Quais regras estão sendo violadas?",
+ "select_all_that_apply": "Selecione todas que se aplicam",
+ "i_just_don’t_like_it": "Simplesmente não gosto"
},
"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_3_of_4": "Passo 3 de 4",
+ "are_there_any_posts_that_back_up_this_report": "Existem postagens que apoiam essa denúncia?",
+ "select_all_that_apply": "Selecione todos que se aplicam"
},
"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_4_of_4": "Passo 4 de 4",
+ "is_there_anything_else_we_should_know": "Há algo a mais que deveríamos saber?"
},
"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"
+ "dont_want_to_see_this": "Não quer ver isso?",
+ "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "Quando você vê algo que não gosta no Mastodon, você pode remover essa pessoa da sua experiência.",
+ "unfollow": "Deixar de seguir",
+ "unfollowed": "Deixou de seguir",
+ "unfollow_user": "Deixar de seguir %s",
+ "mute_user": "Silenciar %s",
+ "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "Você não verá as postagens ou reblogs dessa conta na sua pagina inicial. Essa pessoa não saberá que foi silenciada.",
+ "block_user": "Bloquear %s",
+ "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "Essa conta não poderá mais te seguir ou ver suas postagens, mas ela poderá ver que foi bloqueada.",
+ "while_we_review_this_you_can_take_action_against_user": "Enquanto revisamos isso, você pode tomar medidas contra %s"
}
},
"preview": {
"keyboard": {
- "close_preview": "Close Preview",
- "show_next": "Show Next",
- "show_previous": "Show Previous"
+ "close_preview": "Fechar prévia",
+ "show_next": "Mostrar a próxima",
+ "show_previous": "Mostrar a anterior"
}
},
"account_list": {
- "tab_bar_hint": "Current selected profile: %s. Double tap then hold to show account switcher",
- "dismiss_account_switcher": "Dismiss Account Switcher",
- "add_account": "Add Account"
+ "tab_bar_hint": "Perfil selecionado nesse momento: %s. Toque duas vezes e segure para mostrar o alternador de conta",
+ "dismiss_account_switcher": "Descartar alternador de conta",
+ "add_account": "Adicionar conta"
},
"wizard": {
- "new_in_mastodon": "New in Mastodon",
- "multiple_account_switch_intro_description": "Switch between multiple accounts by holding the profile button.",
- "accessibility_hint": "Double tap to dismiss this wizard"
+ "new_in_mastodon": "Novo no Mastodon",
+ "multiple_account_switch_intro_description": "Alterne entre múltiplas contas segurando o botão de perfil.",
+ "accessibility_hint": "Toque duas vezes para descartar este assistente"
},
"bookmark": {
- "title": "Bookmarks"
+ "title": "Marcados"
}
}
}
diff --git a/Localization/StringsConvertor/input/pt-BR.lproj/ios-infoPlist.json b/Localization/StringsConvertor/input/pt-BR.lproj/ios-infoPlist.json
index c6db73de0..04b53a160 100644
--- a/Localization/StringsConvertor/input/pt-BR.lproj/ios-infoPlist.json
+++ b/Localization/StringsConvertor/input/pt-BR.lproj/ios-infoPlist.json
@@ -1,6 +1,6 @@
{
- "NSCameraUsageDescription": "Used to take photo for post status",
- "NSPhotoLibraryAddUsageDescription": "Used to save photo into the Photo Library",
- "NewPostShortcutItemTitle": "New Post",
- "SearchShortcutItemTitle": "Search"
+ "NSCameraUsageDescription": "Usado para tirar uma foto para postagem",
+ "NSPhotoLibraryAddUsageDescription": "Usado para salvar foto na Galeria",
+ "NewPostShortcutItemTitle": "Novo Toot",
+ "SearchShortcutItemTitle": "Buscar"
}
diff --git a/Localization/StringsConvertor/input/pt.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/pt.lproj/Localizable.stringsdict
index bdcae6ac9..eabdc3c32 100644
--- a/Localization/StringsConvertor/input/pt.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/pt.lproj/Localizable.stringsdict
@@ -50,6 +50,22 @@
%ld characters
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character
+ other
+ %ld characters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/pt.lproj/app.json b/Localization/StringsConvertor/input/pt.lproj/app.json
index 80b0882d9..3113ada74 100644
--- a/Localization/StringsConvertor/input/pt.lproj/app.json
+++ b/Localization/StringsConvertor/input/pt.lproj/app.json
@@ -74,8 +74,8 @@
"take_photo": "Take Photo",
"save_photo": "Save Photo",
"copy_photo": "Copy Photo",
- "sign_in": "Sign In",
- "sign_up": "Sign Up",
+ "sign_in": "Log in",
+ "sign_up": "Create account",
"see_more": "See More",
"preview": "Preview",
"share": "Share",
@@ -136,6 +136,12 @@
"vote": "Vote",
"closed": "Closed"
},
+ "meta_entity": {
+ "url": "Link: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Show Profile: %s",
+ "email": "Email address: %s"
+ },
"actions": {
"reply": "Reply",
"reblog": "Reblog",
@@ -212,10 +218,16 @@
"get_started": "Get Started",
"log_in": "Log In"
},
+ "login": {
+ "title": "Welcome back",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Enter URL or search for your server"
+ }
+ },
"server_picker": {
"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.",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
"button": {
"category": {
"all": "All",
@@ -242,8 +254,7 @@
"category": "CATEGORY"
},
"input": {
- "placeholder": "Search servers",
- "search_servers_or_enter_url": "Search servers or enter URL"
+ "search_servers_or_enter_url": "Search communities or enter URL"
},
"empty_state": {
"finding_servers": "Finding available servers...",
@@ -376,7 +387,13 @@
"video": "video",
"attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.",
"description_photo": "Describe the photo for the visually-impaired...",
- "description_video": "Describe the video for the visually-impaired..."
+ "description_video": "Describe the video for the visually-impaired...",
+ "load_failed": "Load Failed",
+ "upload_failed": "Upload Failed",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "Attachment too large",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "Duration: %s",
@@ -386,7 +403,9 @@
"one_day": "1 Day",
"three_days": "3 Days",
"seven_days": "7 Days",
- "option_number": "Option %ld"
+ "option_number": "Option %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "Write an accurate warning here..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Custom Emoji Picker",
"enable_content_warning": "Enable Content Warning",
"disable_content_warning": "Disable Content Warning",
- "post_visibility_menu": "Post Visibility Menu"
+ "post_visibility_menu": "Post Visibility Menu",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "Discard Post",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Label",
"content": "Content"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
diff --git a/Localization/StringsConvertor/input/ro.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/ro.lproj/Localizable.stringsdict
index 7ae5a1c79..9df2162b0 100644
--- a/Localization/StringsConvertor/input/ro.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/ro.lproj/Localizable.stringsdict
@@ -56,6 +56,24 @@
%ld characters
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character
+ few
+ %ld characters
+ other
+ %ld characters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/ro.lproj/app.json b/Localization/StringsConvertor/input/ro.lproj/app.json
index 8b9da0903..75a77184c 100644
--- a/Localization/StringsConvertor/input/ro.lproj/app.json
+++ b/Localization/StringsConvertor/input/ro.lproj/app.json
@@ -32,9 +32,9 @@
"message": "Nu se poate edita profilul. Vă rugăm să încercaţi din nou."
},
"sign_out": {
- "title": "Deconectați-vă",
+ "title": "Deconectare",
"message": "Sigur doriți să vă deconectați?",
- "confirm": "Deconectați-vă"
+ "confirm": "Deconectare"
},
"block_domain": {
"title": "Are you really, really sure you want to block the entire %s? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain and any of your followers from that domain will be removed.",
@@ -74,8 +74,8 @@
"take_photo": "Take Photo",
"save_photo": "Save Photo",
"copy_photo": "Copy Photo",
- "sign_in": "Sign In",
- "sign_up": "Sign Up",
+ "sign_in": "Log in",
+ "sign_up": "Create account",
"see_more": "See More",
"preview": "Preview",
"share": "Share",
@@ -136,6 +136,12 @@
"vote": "Vote",
"closed": "Closed"
},
+ "meta_entity": {
+ "url": "Link: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Show Profile: %s",
+ "email": "Email address: %s"
+ },
"actions": {
"reply": "Reply",
"reblog": "Reblog",
@@ -212,10 +218,16 @@
"get_started": "Get Started",
"log_in": "Log In"
},
+ "login": {
+ "title": "Welcome back",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Enter URL or search for your server"
+ }
+ },
"server_picker": {
"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.",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
"button": {
"category": {
"all": "All",
@@ -242,8 +254,7 @@
"category": "CATEGORY"
},
"input": {
- "placeholder": "Search servers",
- "search_servers_or_enter_url": "Search servers or enter URL"
+ "search_servers_or_enter_url": "Search communities or enter URL"
},
"empty_state": {
"finding_servers": "Finding available servers...",
@@ -376,7 +387,13 @@
"video": "video",
"attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.",
"description_photo": "Describe the photo for the visually-impaired...",
- "description_video": "Describe the video for the visually-impaired..."
+ "description_video": "Describe the video for the visually-impaired...",
+ "load_failed": "Load Failed",
+ "upload_failed": "Upload Failed",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "Attachment too large",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "Duration: %s",
@@ -386,7 +403,9 @@
"one_day": "1 Day",
"three_days": "3 Days",
"seven_days": "7 Days",
- "option_number": "Option %ld"
+ "option_number": "Option %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "Write an accurate warning here..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Custom Emoji Picker",
"enable_content_warning": "Enable Content Warning",
"disable_content_warning": "Disable Content Warning",
- "post_visibility_menu": "Post Visibility Menu"
+ "post_visibility_menu": "Post Visibility Menu",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "Discard Post",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Label",
"content": "Content"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
diff --git a/Localization/StringsConvertor/input/ru.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/ru.lproj/Localizable.stringsdict
index afb29a6aa..c9552a9e4 100644
--- a/Localization/StringsConvertor/input/ru.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/ru.lproj/Localizable.stringsdict
@@ -62,6 +62,26 @@
%ld символа осталось
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character
+ few
+ %ld characters
+ many
+ %ld characters
+ other
+ %ld characters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/ru.lproj/app.json b/Localization/StringsConvertor/input/ru.lproj/app.json
index 7a4833554..25314102a 100644
--- a/Localization/StringsConvertor/input/ru.lproj/app.json
+++ b/Localization/StringsConvertor/input/ru.lproj/app.json
@@ -74,8 +74,8 @@
"take_photo": "Сделать фото",
"save_photo": "Сохранить изображение",
"copy_photo": "Скопировать изображение",
- "sign_in": "Войти",
- "sign_up": "Зарегистрироваться",
+ "sign_in": "Log in",
+ "sign_up": "Create account",
"see_more": "Ещё",
"preview": "Предпросмотр",
"share": "Поделиться",
@@ -136,6 +136,12 @@
"vote": "Проголосовать",
"closed": "Завершён"
},
+ "meta_entity": {
+ "url": "Link: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Show Profile: %s",
+ "email": "Email address: %s"
+ },
"actions": {
"reply": "Ответить",
"reblog": "Продвинуть",
@@ -212,10 +218,16 @@
"get_started": "Присоединиться",
"log_in": "Вход"
},
+ "login": {
+ "title": "Welcome back",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Enter URL or search for your server"
+ }
+ },
"server_picker": {
"title": "Выберите сервер,\nлюбой сервер.",
- "subtitle": "Выберите сообщество на основе своих интересов, региона или общей тематики.",
- "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.",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
"button": {
"category": {
"all": "Все",
@@ -242,8 +254,7 @@
"category": "КАТЕГОРИЯ"
},
"input": {
- "placeholder": "Найдите сервер или присоединитесь к своему...",
- "search_servers_or_enter_url": "Поиск по серверам или ссылке"
+ "search_servers_or_enter_url": "Search communities or enter URL"
},
"empty_state": {
"finding_servers": "Ищем доступные сервера...",
@@ -376,7 +387,13 @@
"video": "видео",
"attachment_broken": "Это %s повреждено и не может\nбыть отправлено в Mastodon.",
"description_photo": "Опишите фото для людей с нарушениями зрения...",
- "description_video": "Опишите видео для людей с нарушениями зрения..."
+ "description_video": "Опишите видео для людей с нарушениями зрения...",
+ "load_failed": "Load Failed",
+ "upload_failed": "Upload Failed",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "Attachment too large",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "Продолжительность: %s",
@@ -386,7 +403,9 @@
"one_day": "1 день",
"three_days": "3 дня",
"seven_days": "7 дней",
- "option_number": "Вариант %ld"
+ "option_number": "Вариант %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "Напишите предупреждение здесь..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Меню пользовательских эмодзи",
"enable_content_warning": "Добавить предупреждение о содержании",
"disable_content_warning": "Убрать предупреждение о содержании",
- "post_visibility_menu": "Меню видимости поста"
+ "post_visibility_menu": "Меню видимости поста",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "Удалить пост",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Ярлык",
"content": "Содержимое"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
diff --git a/Localization/StringsConvertor/input/si.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/si.lproj/Localizable.stringsdict
index bdcae6ac9..eabdc3c32 100644
--- a/Localization/StringsConvertor/input/si.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/si.lproj/Localizable.stringsdict
@@ -50,6 +50,22 @@
%ld characters
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character
+ other
+ %ld characters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/si.lproj/app.json b/Localization/StringsConvertor/input/si.lproj/app.json
index f42e91ae1..a4542b9e9 100644
--- a/Localization/StringsConvertor/input/si.lproj/app.json
+++ b/Localization/StringsConvertor/input/si.lproj/app.json
@@ -74,8 +74,8 @@
"take_photo": "Take Photo",
"save_photo": "Save Photo",
"copy_photo": "Copy Photo",
- "sign_in": "පිවිසෙන්න",
- "sign_up": "ලියාපදිංචිය",
+ "sign_in": "Log in",
+ "sign_up": "Create account",
"see_more": "තව බලන්න",
"preview": "පෙරදසුන",
"share": "බෙදාගන්න",
@@ -136,6 +136,12 @@
"vote": "ඡන්දය",
"closed": "වසා ඇත"
},
+ "meta_entity": {
+ "url": "Link: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Show Profile: %s",
+ "email": "Email address: %s"
+ },
"actions": {
"reply": "පිළිතුරු",
"reblog": "Reblog",
@@ -212,10 +218,16 @@
"get_started": "පටන් ගන්න",
"log_in": "පිවිසෙන්න"
},
+ "login": {
+ "title": "Welcome back",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Enter URL or search for your server"
+ }
+ },
"server_picker": {
"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.",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
"button": {
"category": {
"all": "සියල්ල",
@@ -242,8 +254,7 @@
"category": "CATEGORY"
},
"input": {
- "placeholder": "Search servers",
- "search_servers_or_enter_url": "Search servers or enter URL"
+ "search_servers_or_enter_url": "Search communities or enter URL"
},
"empty_state": {
"finding_servers": "Finding available servers...",
@@ -376,7 +387,13 @@
"video": "video",
"attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.",
"description_photo": "Describe the photo for the visually-impaired...",
- "description_video": "Describe the video for the visually-impaired..."
+ "description_video": "Describe the video for the visually-impaired...",
+ "load_failed": "Load Failed",
+ "upload_failed": "Upload Failed",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "Attachment too large",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "Duration: %s",
@@ -386,7 +403,9 @@
"one_day": "1 Day",
"three_days": "3 Days",
"seven_days": "7 Days",
- "option_number": "Option %ld"
+ "option_number": "Option %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "Write an accurate warning here..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Custom Emoji Picker",
"enable_content_warning": "Enable Content Warning",
"disable_content_warning": "Disable Content Warning",
- "post_visibility_menu": "Post Visibility Menu"
+ "post_visibility_menu": "Post Visibility Menu",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "Discard Post",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "නම්පත",
"content": "අන්තර්ගතය"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
diff --git a/Localization/StringsConvertor/input/sl.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/sl.lproj/Localizable.stringsdict
index 8f0bcb42b..87cc42142 100644
--- a/Localization/StringsConvertor/input/sl.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/sl.lproj/Localizable.stringsdict
@@ -62,6 +62,26 @@
%ld znakov
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ preostaja %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ %ld znak
+ two
+ %ld znaka
+ few
+ %ld znaki
+ other
+ %ld znakov
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/sl.lproj/app.json b/Localization/StringsConvertor/input/sl.lproj/app.json
index 99a823feb..0aed7bc15 100644
--- a/Localization/StringsConvertor/input/sl.lproj/app.json
+++ b/Localization/StringsConvertor/input/sl.lproj/app.json
@@ -75,7 +75,7 @@
"save_photo": "Shrani fotografijo",
"copy_photo": "Kopiraj fotografijo",
"sign_in": "Prijava",
- "sign_up": "Registracija",
+ "sign_up": "Ustvari račun",
"see_more": "Pokaži več",
"preview": "Predogled",
"share": "Deli",
@@ -136,6 +136,12 @@
"vote": "Glasuj",
"closed": "Zaprto"
},
+ "meta_entity": {
+ "url": "Povezava: %s",
+ "hashtag": "Ključnik: %s",
+ "mention": "Pokaži profil: %s",
+ "email": "E-naslov: %s"
+ },
"actions": {
"reply": "Odgovori",
"reblog": "Poobjavi",
@@ -212,10 +218,16 @@
"get_started": "Začnite",
"log_in": "Prijava"
},
+ "login": {
+ "title": "Dobrodošli nazaj",
+ "subtitle": "Prijavite se na strežniku, na katerem ste ustvarili račun.",
+ "server_search_field": {
+ "placeholder": "Vnesite URL ali poiščite svoj strežnik"
+ }
+ },
"server_picker": {
"title": "Mastodon tvorijo uporabniki z različnih strežnikov.",
- "subtitle": "Strežnik izberite glede na svoje interese, regijo ali pa izberite splošnega.",
- "subtitle_extend": "Strežnik izberite glede na svoje interese, regijo ali pa izberite splošnega. Z vsakim strežnikom upravlja povsem neodvisna organizacija ali posameznik.",
+ "subtitle": "Strežnik izberite glede na svojo regijo, zanimanje ali pa kar splošno. Še vedno lahko klepetate s komer koli na Mastodonu, ne glede na strežnik.",
"button": {
"category": {
"all": "Vse",
@@ -242,8 +254,7 @@
"category": "KATEGORIJA"
},
"input": {
- "placeholder": "Išči strežnike",
- "search_servers_or_enter_url": "Iščite strežnike ali vnesite URL"
+ "search_servers_or_enter_url": "Iščite po skupnostih ali vnesite URL"
},
"empty_state": {
"finding_servers": "Iskanje razpoložljivih strežnikov ...",
@@ -376,7 +387,13 @@
"video": "video",
"attachment_broken": "To %s je okvarjeno in ga ni\nmožno naložiti v Mastodon.",
"description_photo": "Opiši fotografijo za slabovidne in osebe z okvaro vida ...",
- "description_video": "Opiši video za slabovidne in osebe z okvaro vida ..."
+ "description_video": "Opiši video za slabovidne in osebe z okvaro vida ...",
+ "load_failed": "Nalaganje ni uspelo",
+ "upload_failed": "Nalaganje na strežnik ni uspelo",
+ "can_not_recognize_this_media_attachment": "Te medijske priponke ni mogoče prepoznati",
+ "attachment_too_large": "Priponka je prevelika",
+ "compressing_state": "Stiskanje ...",
+ "server_processing_state": "Obdelovanje na strežniku ..."
},
"poll": {
"duration_time": "Trajanje: %s",
@@ -386,7 +403,9 @@
"one_day": "1 dan",
"three_days": "3 dni",
"seven_days": "7 dni",
- "option_number": "Možnost %ld"
+ "option_number": "Možnost %ld",
+ "the_poll_is_invalid": "Anketa je neveljavna",
+ "the_poll_has_empty_option": "Anketa ima prazno izbiro"
},
"content_warning": {
"placeholder": "Tukaj zapišite opozorilo ..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Izbirnik čustvenčkov po meri",
"enable_content_warning": "Omogoči opozorilo o vsebini",
"disable_content_warning": "Onemogoči opozorilo o vsebini",
- "post_visibility_menu": "Meni vidnosti objave"
+ "post_visibility_menu": "Meni vidnosti objave",
+ "post_options": "Možnosti objave",
+ "posting_as": "Objavljate kot %s"
},
"keyboard": {
"discard_post": "Opusti objavo",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Oznaka",
"content": "Vsebina"
+ },
+ "verified": {
+ "short": "Preverjeno %s",
+ "long": "Lastništvo te povezave je bilo preverjeno %s"
}
},
"segmented_control": {
@@ -696,7 +721,7 @@
"accessibility_hint": "Dvakrat tapnite, da zapustite tega čarovnika"
},
"bookmark": {
- "title": "Bookmarks"
+ "title": "Zaznamki"
}
}
}
diff --git a/Localization/StringsConvertor/input/sv.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/sv.lproj/Localizable.stringsdict
index 048af4732..3cbfeae6d 100644
--- a/Localization/StringsConvertor/input/sv.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/sv.lproj/Localizable.stringsdict
@@ -50,6 +50,22 @@
%ld tecken
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ kvar
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ %ld tecken
+ other
+ %ld tecken
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
@@ -152,9 +168,9 @@
NSStringFormatValueTypeKey
ld
one
- %ld puff
+ %ld boost
other
- %ld puffar
+ %ld boostar
plural.count.reply
diff --git a/Localization/StringsConvertor/input/sv.lproj/app.json b/Localization/StringsConvertor/input/sv.lproj/app.json
index 85f243b03..7951e1958 100644
--- a/Localization/StringsConvertor/input/sv.lproj/app.json
+++ b/Localization/StringsConvertor/input/sv.lproj/app.json
@@ -75,7 +75,7 @@
"save_photo": "Spara foto",
"copy_photo": "Kopiera foto",
"sign_in": "Logga in",
- "sign_up": "Registrera dig",
+ "sign_up": "Skapa konto",
"see_more": "Visa mer",
"preview": "Förhandsvisa",
"share": "Dela",
@@ -111,9 +111,9 @@
"next_status": "Nästa inlägg",
"open_status": "Öppna inlägg",
"open_author_profile": "Öppna författarens profil",
- "open_reblogger_profile": "Öppna ompostarens profil",
+ "open_reblogger_profile": "Öppna boostarens profil",
"reply_status": "Svara på inlägg",
- "toggle_reblog": "Växla puff på inlägg",
+ "toggle_reblog": "Växla boost på inlägg",
"toggle_favorite": "Växla favorit på inlägg",
"toggle_content_warning": "Växla innehållsvarning",
"preview_image": "Förhandsgranska bild"
@@ -124,7 +124,7 @@
}
},
"status": {
- "user_reblogged": "%s puffade",
+ "user_reblogged": "%s boostade",
"user_replied_to": "Svarade på %s",
"show_post": "Visa inlägg",
"show_user_profile": "Visa användarprofil",
@@ -136,10 +136,16 @@
"vote": "Rösta",
"closed": "Stängd"
},
+ "meta_entity": {
+ "url": "Länk: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Visa profil: %s",
+ "email": "E-postadress: %s"
+ },
"actions": {
"reply": "Svara",
- "reblog": "Puffa",
- "unreblog": "Ångra puff",
+ "reblog": "Boosta",
+ "unreblog": "Ångra boost",
"favorite": "Favorit",
"unfavorite": "Ta bort favorit",
"menu": "Meny",
@@ -181,8 +187,8 @@
"unmute_user": "Avtysta %s",
"muted": "Tystad",
"edit_info": "Redigera info",
- "show_reblogs": "Visa knuffar",
- "hide_reblogs": "Dölj puffar"
+ "show_reblogs": "Visa boostar",
+ "hide_reblogs": "Dölj boostar"
},
"timeline": {
"filtered": "Filtrerat",
@@ -212,10 +218,16 @@
"get_started": "Kom igång",
"log_in": "Logga in"
},
+ "login": {
+ "title": "Välkommen tillbaka",
+ "subtitle": "Logga in på servern där du skapade ditt konto.",
+ "server_search_field": {
+ "placeholder": "Ange URL eller sök efter din server"
+ }
+ },
"server_picker": {
"title": "Mastodon utgörs av användare på olika servrar.",
- "subtitle": "Välj en server baserat på dina intressen, region eller ett allmänt syfte.",
- "subtitle_extend": "Välj en server baserat på dina intressen, region eller ett allmänt syfte. Varje server drivs av en helt oberoende organisation eller individ.",
+ "subtitle": "Välj en server baserat på dina intressen, region eller en allmän server. Du kan fortfarande nå alla, oavsett server.",
"button": {
"category": {
"all": "Alla",
@@ -242,8 +254,7 @@
"category": "KATEGORI"
},
"input": {
- "placeholder": "Sök gemenskaper",
- "search_servers_or_enter_url": "Sök servrar eller ange URL"
+ "search_servers_or_enter_url": "Sök gemenskaper eller ange URL"
},
"empty_state": {
"finding_servers": "Söker tillgängliga servrar...",
@@ -376,7 +387,13 @@
"video": "video",
"attachment_broken": "Denna %s är trasig och kan inte\nladdas upp till Mastodon.",
"description_photo": "Beskriv fotot för synskadade...",
- "description_video": "Beskriv videon för de synskadade..."
+ "description_video": "Beskriv videon för de synskadade...",
+ "load_failed": "Det gick inte att läsa in",
+ "upload_failed": "Uppladdning misslyckades",
+ "can_not_recognize_this_media_attachment": "Känner inte igen mediebilagan",
+ "attachment_too_large": "Bilagan är för stor",
+ "compressing_state": "Komprimerar...",
+ "server_processing_state": "Behandlas av servern..."
},
"poll": {
"duration_time": "Längd: %s",
@@ -386,7 +403,9 @@
"one_day": "1 dag",
"three_days": "3 dagar",
"seven_days": "7 dagar",
- "option_number": "Alternativ %ld"
+ "option_number": "Alternativ %ld",
+ "the_poll_is_invalid": "Undersökningen är ogiltig",
+ "the_poll_has_empty_option": "Undersökningen har ett tomt alternativ"
},
"content_warning": {
"placeholder": "Skriv en noggrann varning här..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Anpassad emoji-väljare",
"enable_content_warning": "Aktivera innehållsvarning",
"disable_content_warning": "Inaktivera innehållsvarning",
- "post_visibility_menu": "Inläggssynlighetsmeny"
+ "post_visibility_menu": "Inläggssynlighetsmeny",
+ "post_options": "Inläggsalternativ",
+ "posting_as": "Postar som %s"
},
"keyboard": {
"discard_post": "Släng inlägget",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Etikett",
"content": "Innehåll"
+ },
+ "verified": {
+ "short": "Verifierad på %s",
+ "long": "Ägarskap för denna länk kontrollerades den %s"
}
},
"segmented_control": {
@@ -459,12 +484,12 @@
"message": "Bekräfta för att avblockera %s"
},
"confirm_show_reblogs": {
- "title": "Visa puffar",
- "message": "Bekräfta för att visa puffar"
+ "title": "Visa boostar",
+ "message": "Bekräfta för att visa boostar"
},
"confirm_hide_reblogs": {
- "title": "Dölj puffar",
- "message": "Bekräfta för att dölja puffar"
+ "title": "Dölj boostar",
+ "message": "Bekräfta för att dölja boostar"
}
},
"accessibility": {
@@ -490,7 +515,7 @@
"title": "Favoriserad av"
},
"reblogged_by": {
- "title": "Puffat av"
+ "title": "Boostat av"
},
"search": {
"title": "Sök",
@@ -546,7 +571,7 @@
"notification_description": {
"followed_you": "följde dig",
"favorited_your_post": "favoriserade ditt inlägg",
- "reblogged_your_post": "puffade ditt inlägg",
+ "reblogged_your_post": "boostade ditt inlägg",
"mentioned_you": "nämnde dig",
"request_to_follow_you": "begär att följa dig",
"poll_has_ended": "omröstningen har avslutats"
@@ -586,7 +611,7 @@
"title": "Notiser",
"favorites": "Favoriserar mitt inlägg",
"follows": "Följer mig",
- "boosts": "Ompostar mitt inlägg",
+ "boosts": "Boostar mitt inlägg",
"mentions": "Nämner mig",
"trigger": {
"anyone": "alla",
@@ -672,7 +697,7 @@
"unfollowed": "Slutade följa",
"unfollow_user": "Avfölj %s",
"mute_user": "Tysta %s",
- "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "Du kommer inte att se deras inlägg eller ompostningar i ditt hemflöde. De kommer inte att veta att de har blivit tystade.",
+ "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "Du kommer inte att se deras inlägg eller boostar i ditt hemflöde. De kommer inte att veta att de har blivit tystade.",
"block_user": "Blockera %s",
"they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "De kommer inte längre att kunna följa eller se dina inlägg, men de kan se om de har blockerats.",
"while_we_review_this_you_can_take_action_against_user": "Medan vi granskar detta kan du vidta åtgärder mot %s"
@@ -696,7 +721,7 @@
"accessibility_hint": "Dubbeltryck för att avvisa den här guiden"
},
"bookmark": {
- "title": "Bookmarks"
+ "title": "Bokmärken"
}
}
}
diff --git a/Localization/StringsConvertor/input/th.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/th.lproj/Localizable.stringsdict
index 897d07eca..f25561ad6 100644
--- a/Localization/StringsConvertor/input/th.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/th.lproj/Localizable.stringsdict
@@ -44,6 +44,20 @@
%ld ตัวอักษร
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ เหลืออีก %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld ตัวอักษร
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/th.lproj/app.json b/Localization/StringsConvertor/input/th.lproj/app.json
index 763b827cd..7b1a3d08e 100644
--- a/Localization/StringsConvertor/input/th.lproj/app.json
+++ b/Localization/StringsConvertor/input/th.lproj/app.json
@@ -74,8 +74,8 @@
"take_photo": "ถ่ายรูป",
"save_photo": "บันทึกรูปภาพ",
"copy_photo": "คัดลอกรูปภาพ",
- "sign_in": "ลงชื่อเข้า",
- "sign_up": "ลงทะเบียน",
+ "sign_in": "เข้าสู่ระบบ",
+ "sign_up": "สร้างบัญชี",
"see_more": "ดูเพิ่มเติม",
"preview": "แสดงตัวอย่าง",
"share": "แบ่งปัน",
@@ -136,6 +136,12 @@
"vote": "ลงคะแนน",
"closed": "ปิดแล้ว"
},
+ "meta_entity": {
+ "url": "ลิงก์: %s",
+ "hashtag": "แฮชแท็ก: %s",
+ "mention": "โปรไฟล์ที่แสดง: %s",
+ "email": "ที่อยู่อีเมล: %s"
+ },
"actions": {
"reply": "ตอบกลับ",
"reblog": "ดัน",
@@ -181,8 +187,8 @@
"unmute_user": "เลิกซ่อน %s",
"muted": "ซ่อนอยู่",
"edit_info": "แก้ไขข้อมูล",
- "show_reblogs": "Show Reblogs",
- "hide_reblogs": "Hide Reblogs"
+ "show_reblogs": "แสดงการดัน",
+ "hide_reblogs": "ซ่อนการดัน"
},
"timeline": {
"filtered": "กรองอยู่",
@@ -212,10 +218,16 @@
"get_started": "เริ่มต้นใช้งาน",
"log_in": "เข้าสู่ระบบ"
},
+ "login": {
+ "title": "ยินดีต้อนรับกลับมา",
+ "subtitle": "นำคุณเข้าสู่ระบบในเซิร์ฟเวอร์ที่คุณได้สร้างบัญชีของคุณไว้ใน",
+ "server_search_field": {
+ "placeholder": "ป้อน URL หรือค้นหาสำหรับเซิร์ฟเวอร์ของคุณ"
+ }
+ },
"server_picker": {
"title": "Mastodon ประกอบด้วยผู้ใช้ในเซิร์ฟเวอร์ต่าง ๆ",
- "subtitle": "เลือกเซิร์ฟเวอร์ตามความสนใจ, ภูมิภาค หรือวัตถุประสงค์ทั่วไปของคุณ",
- "subtitle_extend": "เลือกเซิร์ฟเวอร์ตามความสนใจ, ภูมิภาค หรือวัตถุประสงค์ทั่วไปของคุณ แต่ละเซิร์ฟเวอร์ดำเนินการโดยองค์กรหรือบุคคลที่เป็นอิสระโดยสิ้นเชิง",
+ "subtitle": "เลือกเซิร์ฟเวอร์ตามภูมิภาค, ความสนใจ หรือวัตถุประสงค์ทั่วไปของคุณ คุณยังคงสามารถแชทกับใครก็ตามใน Mastodon โดยไม่คำนึงถึงเซิร์ฟเวอร์ของคุณ",
"button": {
"category": {
"all": "ทั้งหมด",
@@ -242,8 +254,7 @@
"category": "หมวดหมู่"
},
"input": {
- "placeholder": "ค้นหาเซิร์ฟเวอร์",
- "search_servers_or_enter_url": "ค้นหาเซิร์ฟเวอร์หรือป้อน URL"
+ "search_servers_or_enter_url": "ค้นหาชุมชนหรือป้อน URL"
},
"empty_state": {
"finding_servers": "กำลังค้นหาเซิร์ฟเวอร์ที่พร้อมใช้งาน...",
@@ -293,7 +304,7 @@
},
"reason": {
"blocked": "%s มีผู้ให้บริการอีเมลที่ไม่ได้รับอนุญาต",
- "unreachable": "ดูเหมือนว่า %s จะไม่มีอยู่",
+ "unreachable": "ดูเหมือนว่าจะไม่มี %s อยู่",
"taken": "%s ถูกใช้งานแล้ว",
"reserved": "%s เป็นคำสงวน",
"accepted": "ต้องยอมรับ %s",
@@ -331,12 +342,12 @@
},
"dont_receive_email": {
"title": "ตรวจสอบอีเมลของคุณ",
- "description": "หากคุณยังไม่ได้รับอีเมล ตรวจสอบว่าที่อยู่อีเมลของคุณถูกต้อง รวมถึงโฟลเดอร์อีเมลขยะของคุณ",
+ "description": "ตรวจสอบว่าที่อยู่อีเมลของคุณถูกต้องเช่นเดียวกับโฟลเดอร์อีเมลขยะหากคุณยังไม่ได้ทำ",
"resend_email": "ส่งอีเมลใหม่"
},
"open_email_app": {
"title": "ตรวจสอบกล่องขาเข้าของคุณ",
- "description": "เราเพิ่งส่งอีเมลหาคุณ หากคุณยังไม่ได้รับอีเมล โปรดตรวจสอบโฟลเดอร์อีเมลขยะ",
+ "description": "เราเพิ่งส่งอีเมลถึงคุณ ตรวจสอบโฟลเดอร์อีเมลขยะของคุณหากคุณยังไม่ได้ทำ",
"mail": "จดหมาย",
"open_email_client": "เปิดไคลเอ็นต์อีเมล"
}
@@ -376,7 +387,13 @@
"video": "วิดีโอ",
"attachment_broken": "%s นี้เสียหายและไม่สามารถ\nอัปโหลดไปยัง Mastodon",
"description_photo": "อธิบายรูปภาพสำหรับผู้บกพร่องทางการมองเห็น...",
- "description_video": "อธิบายวิดีโอสำหรับผู้บกพร่องทางการมองเห็น..."
+ "description_video": "อธิบายวิดีโอสำหรับผู้บกพร่องทางการมองเห็น...",
+ "load_failed": "การโหลดล้มเหลว",
+ "upload_failed": "การอัปโหลดล้มเหลว",
+ "can_not_recognize_this_media_attachment": "ไม่สามารถระบุไฟล์แนบสื่อนี้",
+ "attachment_too_large": "ไฟล์แนบใหญ่เกินไป",
+ "compressing_state": "กำลังบีบอัด...",
+ "server_processing_state": "เซิร์ฟเวอร์กำลังประมวลผล..."
},
"poll": {
"duration_time": "ระยะเวลา: %s",
@@ -386,7 +403,9 @@
"one_day": "1 วัน",
"three_days": "3 วัน",
"seven_days": "7 วัน",
- "option_number": "ตัวเลือก %ld"
+ "option_number": "ตัวเลือก %ld",
+ "the_poll_is_invalid": "การสำรวจความคิดเห็นไม่ถูกต้อง",
+ "the_poll_has_empty_option": "การสำรวจความคิดเห็นมีตัวเลือกที่ว่างเปล่า"
},
"content_warning": {
"placeholder": "เขียนคำเตือนที่ถูกต้องที่นี่..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "ตัวเลือกอีโมจิที่กำหนดเอง",
"enable_content_warning": "เปิดใช้งานคำเตือนเนื้อหา",
"disable_content_warning": "ปิดใช้งานคำเตือนเนื้อหา",
- "post_visibility_menu": "เมนูการมองเห็นโพสต์"
+ "post_visibility_menu": "เมนูการมองเห็นโพสต์",
+ "post_options": "ตัวเลือกโพสต์",
+ "posting_as": "กำลังโพสต์เป็น %s"
},
"keyboard": {
"discard_post": "ละทิ้งโพสต์",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "ป้ายชื่อ",
"content": "เนื้อหา"
+ },
+ "verified": {
+ "short": "ตรวจสอบเมื่อ %s",
+ "long": "ตรวจสอบความเป็นเจ้าของของลิงก์นี้เมื่อ %s"
}
},
"segmented_control": {
@@ -459,12 +484,12 @@
"message": "ยืนยันเพื่อเลิกปิดกั้น %s"
},
"confirm_show_reblogs": {
- "title": "Show Reblogs",
- "message": "Confirm to show reblogs"
+ "title": "แสดงการดัน",
+ "message": "ยืนยันเพื่อแสดงการดัน"
},
"confirm_hide_reblogs": {
- "title": "Hide Reblogs",
- "message": "Confirm to hide reblogs"
+ "title": "ซ่อนการดัน",
+ "message": "ยืนยันเพื่อซ่อนการดัน"
}
},
"accessibility": {
@@ -696,7 +721,7 @@
"accessibility_hint": "แตะสองครั้งเพื่อปิดตัวช่วยสร้างนี้"
},
"bookmark": {
- "title": "Bookmarks"
+ "title": "ที่คั่นหน้า"
}
}
}
diff --git a/Localization/StringsConvertor/input/tr.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/tr.lproj/Localizable.stringsdict
index 29df92c2b..6ef7f4c75 100644
--- a/Localization/StringsConvertor/input/tr.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/tr.lproj/Localizable.stringsdict
@@ -50,6 +50,22 @@
%ld karakter
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character
+ other
+ %ld characters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/tr.lproj/app.json b/Localization/StringsConvertor/input/tr.lproj/app.json
index cef7fd7f4..37325cf05 100644
--- a/Localization/StringsConvertor/input/tr.lproj/app.json
+++ b/Localization/StringsConvertor/input/tr.lproj/app.json
@@ -74,8 +74,8 @@
"take_photo": "Fotoğraf Çek",
"save_photo": "Fotoğrafı Kaydet",
"copy_photo": "Fotoğrafı Kopyala",
- "sign_in": "Giriş Yap",
- "sign_up": "Kaydol",
+ "sign_in": "Log in",
+ "sign_up": "Create account",
"see_more": "Daha Fazla Gör",
"preview": "Önizleme",
"share": "Paylaş",
@@ -136,6 +136,12 @@
"vote": "Oy ver",
"closed": "Kapandı"
},
+ "meta_entity": {
+ "url": "Link: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Show Profile: %s",
+ "email": "Email address: %s"
+ },
"actions": {
"reply": "Yanıtla",
"reblog": "Yeniden paylaş",
@@ -212,10 +218,16 @@
"get_started": "Başlayın",
"log_in": "Oturum Aç"
},
+ "login": {
+ "title": "Welcome back",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Enter URL or search for your server"
+ }
+ },
"server_picker": {
"title": "Mastodon, farklı topluluklardaki kullanıcılardan oluşur.",
- "subtitle": "İlgi alanlarınıza, bölgenize veya genel amaçlı bir topluluk seçin.",
- "subtitle_extend": "İ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.",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
"button": {
"category": {
"all": "Tümü",
@@ -242,8 +254,7 @@
"category": "KATEGORİ"
},
"input": {
- "placeholder": "Toplulukları ara",
- "search_servers_or_enter_url": "Sunucuları ara ya da bir bağlantı gir"
+ "search_servers_or_enter_url": "Search communities or enter URL"
},
"empty_state": {
"finding_servers": "Mevcut sunucular aranıyor...",
@@ -376,7 +387,13 @@
"video": "video",
"attachment_broken": "Bu %s bozuk ve Mastodon'a\nyüklenemiyor.",
"description_photo": "Görme engelliler için fotoğrafı tarif edin...",
- "description_video": "Görme engelliler için videoyu tarif edin..."
+ "description_video": "Görme engelliler için videoyu tarif edin...",
+ "load_failed": "Load Failed",
+ "upload_failed": "Upload Failed",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "Attachment too large",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "Süre: %s",
@@ -386,7 +403,9 @@
"one_day": "1 Gün",
"three_days": "3 Gün",
"seven_days": "7 Gün",
- "option_number": "Seçenek %ld"
+ "option_number": "Seçenek %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "Buraya kesin bir uyarı yazın..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Özel Emoji Seçici",
"enable_content_warning": "İçerik Uyarısını Etkinleştir",
"disable_content_warning": "İçerik Uyarısını Kapat",
- "post_visibility_menu": "Gönderi Görünürlüğü Menüsü"
+ "post_visibility_menu": "Gönderi Görünürlüğü Menüsü",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "Gönderiyi İptal Et",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Etiket",
"content": "İçerik"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
diff --git a/Localization/StringsConvertor/input/uk.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/uk.lproj/Localizable.stringsdict
index cdf35477e..32e4cf9aa 100644
--- a/Localization/StringsConvertor/input/uk.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/uk.lproj/Localizable.stringsdict
@@ -62,6 +62,26 @@
%ld characters
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ left
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character
+ few
+ %ld characters
+ many
+ %ld characters
+ other
+ %ld characters
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/uk.lproj/app.json b/Localization/StringsConvertor/input/uk.lproj/app.json
index 80b0882d9..3113ada74 100644
--- a/Localization/StringsConvertor/input/uk.lproj/app.json
+++ b/Localization/StringsConvertor/input/uk.lproj/app.json
@@ -74,8 +74,8 @@
"take_photo": "Take Photo",
"save_photo": "Save Photo",
"copy_photo": "Copy Photo",
- "sign_in": "Sign In",
- "sign_up": "Sign Up",
+ "sign_in": "Log in",
+ "sign_up": "Create account",
"see_more": "See More",
"preview": "Preview",
"share": "Share",
@@ -136,6 +136,12 @@
"vote": "Vote",
"closed": "Closed"
},
+ "meta_entity": {
+ "url": "Link: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Show Profile: %s",
+ "email": "Email address: %s"
+ },
"actions": {
"reply": "Reply",
"reblog": "Reblog",
@@ -212,10 +218,16 @@
"get_started": "Get Started",
"log_in": "Log In"
},
+ "login": {
+ "title": "Welcome back",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Enter URL or search for your server"
+ }
+ },
"server_picker": {
"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.",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
"button": {
"category": {
"all": "All",
@@ -242,8 +254,7 @@
"category": "CATEGORY"
},
"input": {
- "placeholder": "Search servers",
- "search_servers_or_enter_url": "Search servers or enter URL"
+ "search_servers_or_enter_url": "Search communities or enter URL"
},
"empty_state": {
"finding_servers": "Finding available servers...",
@@ -376,7 +387,13 @@
"video": "video",
"attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.",
"description_photo": "Describe the photo for the visually-impaired...",
- "description_video": "Describe the video for the visually-impaired..."
+ "description_video": "Describe the video for the visually-impaired...",
+ "load_failed": "Load Failed",
+ "upload_failed": "Upload Failed",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "Attachment too large",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "Duration: %s",
@@ -386,7 +403,9 @@
"one_day": "1 Day",
"three_days": "3 Days",
"seven_days": "7 Days",
- "option_number": "Option %ld"
+ "option_number": "Option %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "Write an accurate warning here..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Custom Emoji Picker",
"enable_content_warning": "Enable Content Warning",
"disable_content_warning": "Disable Content Warning",
- "post_visibility_menu": "Post Visibility Menu"
+ "post_visibility_menu": "Post Visibility Menu",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "Discard Post",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Label",
"content": "Content"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
diff --git a/Localization/StringsConvertor/input/vi.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/vi.lproj/Localizable.stringsdict
index 6905b240e..4c772f014 100644
--- a/Localization/StringsConvertor/input/vi.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/vi.lproj/Localizable.stringsdict
@@ -44,6 +44,20 @@
%ld ký tự
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ còn lại
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld ký tự
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/vi.lproj/app.json b/Localization/StringsConvertor/input/vi.lproj/app.json
index b857399b3..963be39c9 100644
--- a/Localization/StringsConvertor/input/vi.lproj/app.json
+++ b/Localization/StringsConvertor/input/vi.lproj/app.json
@@ -75,7 +75,7 @@
"save_photo": "Lưu ảnh",
"copy_photo": "Sao chép ảnh",
"sign_in": "Đăng nhập",
- "sign_up": "Đăng ký",
+ "sign_up": "Tạo tài khoản",
"see_more": "Xem thêm",
"preview": "Xem trước",
"share": "Chia sẻ",
@@ -136,6 +136,12 @@
"vote": "Bình chọn",
"closed": "Kết thúc"
},
+ "meta_entity": {
+ "url": "Link: %s",
+ "hashtag": "Hashtag: %s",
+ "mention": "Hiện hồ sơ: %s",
+ "email": "Email: %s"
+ },
"actions": {
"reply": "Trả lời",
"reblog": "Đăng lại",
@@ -212,10 +218,16 @@
"get_started": "Bắt đầu",
"log_in": "Đăng nhập"
},
+ "login": {
+ "title": "Chào mừng trở lại!",
+ "subtitle": "Đăng nhập vào máy chủ mà bạn đã tạo tài khoản.",
+ "server_search_field": {
+ "placeholder": "Nhập URL hoặc tìm máy chủ"
+ }
+ },
"server_picker": {
"title": "Mastodon gồm nhiều máy chủ với thành viên riêng.",
- "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.",
- "subtitle_extend": "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.",
+ "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. Bạn vẫn có thể giao tiếp với bất cứ ai mà không phụ thuộc vào máy chủ của họ.",
"button": {
"category": {
"all": "Toàn bộ",
@@ -242,8 +254,7 @@
"category": "PHÂN LOẠI"
},
"input": {
- "placeholder": "Tìm máy chủ",
- "search_servers_or_enter_url": "Tìm máy chủ hoặc nhập URL"
+ "search_servers_or_enter_url": "Tìm một máy chủ hoặc nhập URL"
},
"empty_state": {
"finding_servers": "Đang tìm máy chủ hoạt động...",
@@ -376,7 +387,13 @@
"video": "video",
"attachment_broken": "%s này bị lỗi và không thể\ntải lên Mastodon.",
"description_photo": "Mô tả hình ảnh cho người khiếm thị...",
- "description_video": "Mô tả video cho người khiếm thị..."
+ "description_video": "Mô tả video cho người khiếm thị...",
+ "load_failed": "Tải thất bại",
+ "upload_failed": "Tải lên thất bại",
+ "can_not_recognize_this_media_attachment": "Không xem được tập tin đính kèm",
+ "attachment_too_large": "Tập tin đính kèm quá lớn",
+ "compressing_state": "Đang nén...",
+ "server_processing_state": "Máy chủ đang xử lý..."
},
"poll": {
"duration_time": "Thời hạn: %s",
@@ -386,7 +403,9 @@
"one_day": "1 ngày",
"three_days": "3 ngày",
"seven_days": "7 ngày",
- "option_number": "Lựa chọn %ld"
+ "option_number": "Lựa chọn %ld",
+ "the_poll_is_invalid": "Bình chọn không hợp lệ",
+ "the_poll_has_empty_option": "Thiếu lựa chọn"
},
"content_warning": {
"placeholder": "Viết nội dung ẩn của bạn ở đây..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "Chọn emoji",
"enable_content_warning": "Bật nội dung ẩn",
"disable_content_warning": "Tắt nội dung ẩn",
- "post_visibility_menu": "Menu hiển thị tút"
+ "post_visibility_menu": "Menu hiển thị tút",
+ "post_options": "Tùy chọn đăng",
+ "posting_as": "Đăng dưới dạng %s"
},
"keyboard": {
"discard_post": "Hủy đăng tút",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "Nhãn",
"content": "Nội dung"
+ },
+ "verified": {
+ "short": "Đã xác minh %s",
+ "long": "Liên kết này đã được xác minh trên %s"
}
},
"segmented_control": {
@@ -696,7 +721,7 @@
"accessibility_hint": "Nhấn hai lần để bỏ qua"
},
"bookmark": {
- "title": "Bookmarks"
+ "title": "Tút đã lưu"
}
}
}
diff --git a/Localization/StringsConvertor/input/zh-Hans.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/zh-Hans.lproj/Localizable.stringsdict
index 5a7af3752..362d55c4f 100644
--- a/Localization/StringsConvertor/input/zh-Hans.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/zh-Hans.lproj/Localizable.stringsdict
@@ -44,6 +44,20 @@
%ld 个字符
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ %#@character_count@ 剩余
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld 个字符
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/zh-Hans.lproj/app.json b/Localization/StringsConvertor/input/zh-Hans.lproj/app.json
index 7f3703b8a..c503c5186 100644
--- a/Localization/StringsConvertor/input/zh-Hans.lproj/app.json
+++ b/Localization/StringsConvertor/input/zh-Hans.lproj/app.json
@@ -75,7 +75,7 @@
"save_photo": "保存照片",
"copy_photo": "拷贝照片",
"sign_in": "登录",
- "sign_up": "注册",
+ "sign_up": "创建账户",
"see_more": "查看更多",
"preview": "预览",
"share": "分享",
@@ -136,6 +136,12 @@
"vote": "投票",
"closed": "已关闭"
},
+ "meta_entity": {
+ "url": "链接:%s",
+ "hashtag": "话题:%s",
+ "mention": "显示用户资料:%s",
+ "email": "邮箱地址:%s"
+ },
"actions": {
"reply": "回复",
"reblog": "转发",
@@ -181,8 +187,8 @@
"unmute_user": "取消静音 %s",
"muted": "已静音",
"edit_info": "编辑",
- "show_reblogs": "Show Reblogs",
- "hide_reblogs": "Hide Reblogs"
+ "show_reblogs": "显示转发",
+ "hide_reblogs": "隐藏转发"
},
"timeline": {
"filtered": "已过滤",
@@ -212,10 +218,16 @@
"get_started": "开始使用",
"log_in": "登录"
},
+ "login": {
+ "title": "欢迎回来",
+ "subtitle": "登入您账户所在的服务器。",
+ "server_search_field": {
+ "placeholder": "输入网址或搜索您的服务器"
+ }
+ },
"server_picker": {
"title": "挑选一个服务器,\n任意服务器。",
- "subtitle": "根据你的兴趣、区域或一般目的选择一个社区。",
- "subtitle_extend": "根据你的兴趣、区域或一般目的选择一个社区。每个社区都由完全独立的组织或个人管理。",
+ "subtitle": "根据你的地区、兴趣挑选一个服务器。无论你选择哪个服务器,你都可以跟其他服务器的任何人一起聊天。",
"button": {
"category": {
"all": "全部",
@@ -242,8 +254,7 @@
"category": "类别"
},
"input": {
- "placeholder": "查找或加入你自己的服务器...",
- "search_servers_or_enter_url": "搜索服务器或输入 URL"
+ "search_servers_or_enter_url": "搜索社区或输入 URL"
},
"empty_state": {
"finding_servers": "正在查找可用的服务器...",
@@ -376,7 +387,13 @@
"video": "视频",
"attachment_broken": "%s已损坏\n无法上传到 Mastodon",
"description_photo": "为视觉障碍人士添加照片的文字说明...",
- "description_video": "为视觉障碍人士添加视频的文字说明..."
+ "description_video": "为视觉障碍人士添加视频的文字说明...",
+ "load_failed": "加载失败",
+ "upload_failed": "上传失败",
+ "can_not_recognize_this_media_attachment": "无法识别此媒体",
+ "attachment_too_large": "附件太大",
+ "compressing_state": "压缩中...",
+ "server_processing_state": "服务器正在处理..."
},
"poll": {
"duration_time": "时长:%s",
@@ -386,7 +403,9 @@
"one_day": "1 天",
"three_days": "3 天",
"seven_days": "7 天",
- "option_number": "选项 %ld"
+ "option_number": "选项 %ld",
+ "the_poll_is_invalid": "投票无效",
+ "the_poll_has_empty_option": "投票含有空选项"
},
"content_warning": {
"placeholder": "在这里写下内容的警告消息..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "自定义表情选择器",
"enable_content_warning": "启用内容警告",
"disable_content_warning": "关闭内容警告",
- "post_visibility_menu": "帖子可见性"
+ "post_visibility_menu": "帖子可见性",
+ "post_options": "帖子选项",
+ "posting_as": "以 %s 身份发布"
},
"keyboard": {
"discard_post": "丢弃帖子",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "标签",
"content": "内容"
+ },
+ "verified": {
+ "short": "验证于 %s",
+ "long": "此链接的所有权已在 %s 上检查通过"
}
},
"segmented_control": {
@@ -459,12 +484,12 @@
"message": "确认取消屏蔽 %s"
},
"confirm_show_reblogs": {
- "title": "Show Reblogs",
- "message": "Confirm to show reblogs"
+ "title": "显示转发",
+ "message": "确认显示转发"
},
"confirm_hide_reblogs": {
- "title": "Hide Reblogs",
- "message": "Confirm to hide reblogs"
+ "title": "隐藏转发",
+ "message": "确认隐藏转发"
}
},
"accessibility": {
@@ -696,7 +721,7 @@
"accessibility_hint": "双击关闭此向导"
},
"bookmark": {
- "title": "Bookmarks"
+ "title": "书签"
}
}
}
diff --git a/Localization/StringsConvertor/input/zh-Hant.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/zh-Hant.lproj/Localizable.stringsdict
index c0ce0f9a2..d545fd6a4 100644
--- a/Localization/StringsConvertor/input/zh-Hant.lproj/Localizable.stringsdict
+++ b/Localization/StringsConvertor/input/zh-Hant.lproj/Localizable.stringsdict
@@ -44,6 +44,20 @@
%ld 個字
+ a11y.plural.count.characters_left
+
+ NSStringLocalizedFormatKey
+ 剩餘 %#@character_count@ 字
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld 個字
+
+
plural.count.followed_by_and_mutual
NSStringLocalizedFormatKey
diff --git a/Localization/StringsConvertor/input/zh-Hant.lproj/app.json b/Localization/StringsConvertor/input/zh-Hant.lproj/app.json
index ae497109e..e2dfaad64 100644
--- a/Localization/StringsConvertor/input/zh-Hant.lproj/app.json
+++ b/Localization/StringsConvertor/input/zh-Hant.lproj/app.json
@@ -75,7 +75,7 @@
"save_photo": "儲存照片",
"copy_photo": "複製照片",
"sign_in": "登入",
- "sign_up": "註冊",
+ "sign_up": "新增帳號",
"see_more": "檢視更多",
"preview": "預覽",
"share": "分享",
@@ -136,6 +136,12 @@
"vote": "投票",
"closed": "已關閉"
},
+ "meta_entity": {
+ "url": "連結:%s",
+ "hashtag": "主題標籤: %s",
+ "mention": "顯示個人檔案:%s",
+ "email": "電子郵件地址:%s"
+ },
"actions": {
"reply": "回覆",
"reblog": "轉嘟",
@@ -212,10 +218,16 @@
"get_started": "新手上路",
"log_in": "登入"
},
+ "login": {
+ "title": "歡迎回來",
+ "subtitle": "登入您新增帳號之伺服器",
+ "server_search_field": {
+ "placeholder": "請輸入 URL 或搜尋您的伺服器"
+ }
+ },
"server_picker": {
"title": "Mastodon 由不同伺服器的使用者組成。",
- "subtitle": "基於您的興趣、地區、或一般用途選定一個伺服器。",
- "subtitle_extend": "基於您的興趣、地區、或一般用途選定一個伺服器。每個伺服器是由完全獨立的組織或個人營運。",
+ "subtitle": "基於您的興趣、地區、或一般用途選定一個伺服器。您仍會與任何伺服器中的每個人連結。",
"button": {
"category": {
"all": "全部",
@@ -242,8 +254,7 @@
"category": "分類"
},
"input": {
- "placeholder": "搜尋伺服器",
- "search_servers_or_enter_url": "搜尋伺服器或輸入網址"
+ "search_servers_or_enter_url": "搜尋社群或輸入 URL 地址"
},
"empty_state": {
"finding_servers": "尋找可用的伺服器...",
@@ -376,7 +387,13 @@
"video": "影片",
"attachment_broken": "此 %s 已損毀,並無法被上傳至 Mastodon。",
"description_photo": "為視障人士提供圖片說明...",
- "description_video": "為視障人士提供影片說明..."
+ "description_video": "為視障人士提供影片說明...",
+ "load_failed": "讀取失敗",
+ "upload_failed": "上傳失敗",
+ "can_not_recognize_this_media_attachment": "無法識別此媒體附加檔案",
+ "attachment_too_large": "附加檔案大小過大",
+ "compressing_state": "正在壓縮...",
+ "server_processing_state": "伺服器處理中..."
},
"poll": {
"duration_time": "持續時間:%s",
@@ -386,7 +403,9 @@
"one_day": "一天",
"three_days": "三天",
"seven_days": "七天",
- "option_number": "選項 %ld"
+ "option_number": "選項 %ld",
+ "the_poll_is_invalid": "此投票是無效的",
+ "the_poll_has_empty_option": "此投票有空白選項"
},
"content_warning": {
"placeholder": "請於此處寫下精準的警告..."
@@ -407,7 +426,9 @@
"custom_emoji_picker": "自訂 emoji 選擇器",
"enable_content_warning": "啟用內容警告",
"disable_content_warning": "停用內容警告",
- "post_visibility_menu": "嘟文可見性選單"
+ "post_visibility_menu": "嘟文可見性選單",
+ "post_options": "嘟文選項",
+ "posting_as": "以 %s 發嘟"
},
"keyboard": {
"discard_post": "捨棄嘟文",
@@ -432,6 +453,10 @@
"placeholder": {
"label": "標籤",
"content": "內容"
+ },
+ "verified": {
+ "short": "於 %s 上已驗證",
+ "long": "已在 %s 檢查此連結的擁有者權限"
}
},
"segmented_control": {
@@ -696,7 +721,7 @@
"accessibility_hint": "點兩下以關閉此設定精靈"
},
"bookmark": {
- "title": "Bookmarks"
+ "title": "書籤"
}
}
}
diff --git a/Localization/app.json b/Localization/app.json
index c5a3dac74..ea046bfbc 100644
--- a/Localization/app.json
+++ b/Localization/app.json
@@ -74,8 +74,8 @@
"take_photo": "Take Photo",
"save_photo": "Save Photo",
"copy_photo": "Copy Photo",
- "sign_in": "Sign In",
- "sign_up": "Sign Up",
+ "sign_in": "Log in",
+ "sign_up": "Create account",
"see_more": "See More",
"preview": "Preview",
"share": "Share",
@@ -96,7 +96,7 @@
"tabs": {
"home": "Home",
"search": "Search",
- "notification": "Notification",
+ "notifications": "Notifications",
"profile": "Profile"
},
"keyboard": {
@@ -218,10 +218,16 @@
"get_started": "Get Started",
"log_in": "Log In"
},
+ "login": {
+ "title": "Welcome back",
+ "subtitle": "Log you in on the server you created your account on.",
+ "server_search_field": {
+ "placeholder": "Enter URL or search for your server"
+ }
+ },
"server_picker": {
"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.",
+ "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.",
"button": {
"category": {
"all": "All",
@@ -248,8 +254,7 @@
"category": "CATEGORY"
},
"input": {
- "placeholder": "Search servers",
- "search_servers_or_enter_url": "Search servers or enter URL"
+ "search_servers_or_enter_url": "Search communities or enter URL"
},
"empty_state": {
"finding_servers": "Finding available servers...",
@@ -382,7 +387,13 @@
"video": "video",
"attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.",
"description_photo": "Describe the photo for the visually-impaired...",
- "description_video": "Describe the video for the visually-impaired..."
+ "description_video": "Describe the video for the visually-impaired...",
+ "load_failed": "Load Failed",
+ "upload_failed": "Upload Failed",
+ "can_not_recognize_this_media_attachment": "Can not recognize this media attachment",
+ "attachment_too_large": "Attachment too large",
+ "compressing_state": "Compressing...",
+ "server_processing_state": "Server Processing..."
},
"poll": {
"duration_time": "Duration: %s",
@@ -392,7 +403,9 @@
"one_day": "1 Day",
"three_days": "3 Days",
"seven_days": "7 Days",
- "option_number": "Option %ld"
+ "option_number": "Option %ld",
+ "the_poll_is_invalid": "The poll is invalid",
+ "the_poll_has_empty_option": "The poll has empty option"
},
"content_warning": {
"placeholder": "Write an accurate warning here..."
@@ -413,7 +426,9 @@
"custom_emoji_picker": "Custom Emoji Picker",
"enable_content_warning": "Enable Content Warning",
"disable_content_warning": "Disable Content Warning",
- "post_visibility_menu": "Post Visibility Menu"
+ "post_visibility_menu": "Post Visibility Menu",
+ "post_options": "Post Options",
+ "posting_as": "Posting as %s"
},
"keyboard": {
"discard_post": "Discard Post",
@@ -438,6 +453,10 @@
"placeholder": {
"label": "Label",
"content": "Content"
+ },
+ "verified": {
+ "short": "Verified on %s",
+ "long": "Ownership of this link was checked on %s"
}
},
"segmented_control": {
diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj
index 2085c8901..0c43b71df 100644
--- a/Mastodon.xcodeproj/project.pbxproj
+++ b/Mastodon.xcodeproj/project.pbxproj
@@ -23,6 +23,8 @@
0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D33725E6401400AAD544 /* PickServerCell.swift */; };
164F0EBC267D4FE400249499 /* BoopSound.caf in Resources */ = {isa = PBXBuildFile; fileRef = 164F0EBB267D4FE400249499 /* BoopSound.caf */; };
18BC7629F65E6DB12CB8416D /* Pods_Mastodon_MastodonUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */; };
+ 2A82294F29262EE000D2A1F7 /* AppContext+NextAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A82294E29262EE000D2A1F7 /* AppContext+NextAccount.swift */; };
+ 2AE244482927831100BDBF7C /* UIImage+SFSymbols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AE244472927831100BDBF7C /* UIImage+SFSymbols.swift */; };
2D198643261BF09500F0B013 /* SearchResultItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D198642261BF09500F0B013 /* SearchResultItem.swift */; };
2D198649261C0B8500F0B013 /* SearchResultSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D198648261C0B8500F0B013 /* SearchResultSection.swift */; };
2D206B8625F5FB0900143C56 /* Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B8525F5FB0900143C56 /* Double.swift */; };
@@ -87,6 +89,12 @@
62FD27D32893707B00B205C5 /* BookmarkViewController+DataSourceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FD27D22893707B00B205C5 /* BookmarkViewController+DataSourceProvider.swift */; };
62FD27D52893708A00B205C5 /* BookmarkViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FD27D42893708A00B205C5 /* BookmarkViewModel+Diffable.swift */; };
87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */; };
+ C24C97032922F30500BAE8CB /* RefreshControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24C97022922F30500BAE8CB /* RefreshControl.swift */; };
+ D87BFC8B291D5C6B00FEE264 /* MastodonLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */; };
+ D87BFC8D291EB81200FEE264 /* MastodonLoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */; };
+ D87BFC8F291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8E291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift */; };
+ D8916DC029211BE500124085 /* ContentSizedTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8916DBF29211BE500124085 /* ContentSizedTableView.swift */; };
+ D8A6AB6C291C5136003AB663 /* MastodonLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */; };
DB0009A626AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; settings = {ATTRIBUTES = (codegen, ); }; };
DB0009A726AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; };
DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140CE25C42AEE00F9F3CF /* OSLog.swift */; };
@@ -151,7 +159,6 @@
DB1FD44425F26CCC004CFCFC /* PickServerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD44325F26CCC004CFCFC /* PickServerSection.swift */; };
DB1FD45025F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD44F25F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift */; };
DB1FD45A25F27898004CFCFC /* CategoryPickerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD45925F27898004CFCFC /* CategoryPickerItem.swift */; };
- DB221B16260C395900AEFE46 /* CustomEmojiPickerInputViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB221B15260C395900AEFE46 /* CustomEmojiPickerInputViewModel.swift */; };
DB22C92228E700A10082A9E9 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = DB22C92128E700A10082A9E9 /* MastodonSDK */; };
DB22C92428E700A80082A9E9 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = DB22C92328E700A80082A9E9 /* MastodonSDK */; };
DB22C92628E700AF0082A9E9 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = DB22C92528E700AF0082A9E9 /* MastodonSDK */; };
@@ -185,9 +192,6 @@
DB427DED25BAA00100D1B89D /* MastodonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DEC25BAA00100D1B89D /* MastodonTests.swift */; };
DB427DF825BAA00100D1B89D /* MastodonUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DF725BAA00100D1B89D /* MastodonUITests.swift */; };
DB443CD42694627B00159B29 /* AppearanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB443CD32694627B00159B29 /* AppearanceView.swift */; };
- DB44767B260B3B8C00B66B82 /* CustomEmojiPickerInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB44767A260B3B8C00B66B82 /* CustomEmojiPickerInputView.swift */; };
- DB447691260B406600B66B82 /* CustomEmojiPickerItemCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB447690260B406600B66B82 /* CustomEmojiPickerItemCollectionViewCell.swift */; };
- DB447697260B439000B66B82 /* CustomEmojiPickerHeaderCollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB447696260B439000B66B82 /* CustomEmojiPickerHeaderCollectionReusableView.swift */; };
DB4481B925EE289600BEFB67 /* UITableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4481B825EE289600BEFB67 /* UITableView.swift */; };
DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FAB525CA5485005A8AC7 /* UIAlertController.swift */; };
DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FAD625CA6C76005A8AC7 /* UIBarButtonItem.swift */; };
@@ -257,7 +261,6 @@
DB64BA452851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB64BA442851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift */; };
DB64BA482851F29300ADF1B7 /* Account+Fetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB64BA472851F29300ADF1B7 /* Account+Fetch.swift */; };
DB65C63727A2AF6C008BAC2E /* ReportItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB65C63627A2AF6C008BAC2E /* ReportItem.swift */; };
- DB66728C25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */; };
DB6746EB278ED8B0008A6B94 /* PollOptionView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6746EA278ED8B0008A6B94 /* PollOptionView+Configuration.swift */; };
DB6746ED278F45F0008A6B94 /* AutoGenerateProtocolRelayDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6746EC278F45F0008A6B94 /* AutoGenerateProtocolRelayDelegate.swift */; };
DB6746F0278F463B008A6B94 /* AutoGenerateProtocolDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6746EF278F463B008A6B94 /* AutoGenerateProtocolDelegate.swift */; };
@@ -337,7 +340,6 @@
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 */; };
- DB9A488A26034D40008B817C /* ComposeViewModel+PublishState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A488926034D40008B817C /* ComposeViewModel+PublishState.swift */; };
DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6BE825E4F5340051B173 /* SearchViewController.swift */; };
DB9D6BF825E4F5690051B173 /* NotificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6BF725E4F5690051B173 /* NotificationViewController.swift */; };
DB9D6BFF25E4F5940051B173 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6BFE25E4F5940051B173 /* ProfileViewController.swift */; };
@@ -376,12 +378,11 @@
DBB8AB4626AECDE200F6D281 /* SendPostIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB8AB4526AECDE200F6D281 /* SendPostIntentHandler.swift */; };
DBB8AB4F26AED13F00F6D281 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB427DDE25BAA00100D1B89D /* Assets.xcassets */; };
DBB9759C262462E1004620BD /* ThreadMetaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB9759B262462E1004620BD /* ThreadMetaView.swift */; };
- DBBC24DC26A54BCB00398BB9 /* MastodonRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */; };
DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */; };
- DBC6461526A170AB00B0E31B /* ComposeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC6461426A170AB00B0E31B /* ComposeViewController.swift */; };
+ DBC3872429214121001EC0FD /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC3872329214121001EC0FD /* ShareViewController.swift */; };
DBC6461826A170AB00B0E31B /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DBC6461626A170AB00B0E31B /* MainInterface.storyboard */; };
DBC6461C26A170AB00B0E31B /* ShareActionExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = DBC6461226A170AB00B0E31B /* ShareActionExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
- DBC6462326A1712000B0E31B /* ComposeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC6462226A1712000B0E31B /* ComposeViewModel.swift */; };
+ DBC6462326A1712000B0E31B /* ShareViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC6462226A1712000B0E31B /* ShareViewModel.swift */; };
DBC6462826A1736300B0E31B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB427DDE25BAA00100D1B89D /* Assets.xcassets */; };
DBC7A672260C897100E57475 /* StatusContentWarningEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC7A671260C897100E57475 /* StatusContentWarningEditorView.swift */; };
DBCA0EBC282BB38A0029E2B0 /* PageboyNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCA0EBB282BB38A0029E2B0 /* PageboyNavigateable.swift */; };
@@ -519,6 +520,8 @@
0FB3D33725E6401400AAD544 /* PickServerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCell.swift; 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 = ""; };
+ 2A82294E29262EE000D2A1F7 /* AppContext+NextAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppContext+NextAccount.swift"; sourceTree = ""; };
+ 2AE244472927831100BDBF7C /* UIImage+SFSymbols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+SFSymbols.swift"; 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 = ""; };
@@ -610,9 +613,15 @@
B44342AC2B6585F8295F1DDF /* Pods-Mastodon-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-Mastodon-NotificationService/Pods-Mastodon-NotificationService.release.xcconfig"; sourceTree = ""; };
BB482D32A7B9825BF5327C4F /* Pods-Mastodon-MastodonUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.release.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.release.xcconfig"; sourceTree = ""; };
BD7598A87F4497045EDEF252 /* Pods-Mastodon.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.asdk - release.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.asdk - release.xcconfig"; sourceTree = ""; };
+ C24C97022922F30500BAE8CB /* RefreshControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshControl.swift; sourceTree = ""; };
C3789232A52F43529CA67E95 /* Pods-MastodonIntent.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonIntent.asdk - debug.xcconfig"; path = "Target Support Files/Pods-MastodonIntent/Pods-MastodonIntent.asdk - debug.xcconfig"; sourceTree = ""; };
CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MastodonTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D7D7CF93E262178800077512 /* Pods-Mastodon-AppShared.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-AppShared.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-AppShared/Pods-Mastodon-AppShared.debug.xcconfig"; sourceTree = ""; };
+ D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginView.swift; sourceTree = ""; };
+ D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginViewModel.swift; sourceTree = ""; };
+ D87BFC8E291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginServerTableViewCell.swift; sourceTree = ""; };
+ D8916DBF29211BE500124085 /* ContentSizedTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentSizedTableView.swift; sourceTree = ""; };
+ D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginViewController.swift; sourceTree = ""; };
DB0009A826AEE5DC009B9D2D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = ""; };
DB0009AD26AEE5E4009B9D2D /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Intents.strings; sourceTree = ""; };
DB0140CE25C42AEE00F9F3CF /* OSLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = ""; };
@@ -637,7 +646,6 @@
DB0618022785A7100030EE79 /* RegisterSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterSection.swift; sourceTree = ""; };
DB0618042785A73D0030EE79 /* RegisterItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterItem.swift; sourceTree = ""; };
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 = ""; };
DB0A322D280EE9FD001729D2 /* DiscoveryIntroBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryIntroBannerView.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 = ""; };
@@ -681,7 +689,6 @@
DB1FD44F25F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonPickServerViewModel+Diffable.swift"; sourceTree = ""; };
DB1FD45925F27898004CFCFC /* CategoryPickerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryPickerItem.swift; sourceTree = ""; };
DB1FD45F25F278AF004CFCFC /* CategoryPickerSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryPickerSection.swift; sourceTree = ""; };
- DB221B15260C395900AEFE46 /* CustomEmojiPickerInputViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiPickerInputViewModel.swift; sourceTree = ""; };
DB2B3ABD25E37E15007045F9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; };
DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationViewModel.swift; sourceTree = ""; };
DB2FF50F260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollExpiresOptionCollectionViewCell.swift; sourceTree = ""; };
@@ -718,9 +725,6 @@
DB427DF725BAA00100D1B89D /* MastodonUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonUITests.swift; sourceTree = ""; };
DB427DF925BAA00100D1B89D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
DB443CD32694627B00159B29 /* AppearanceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceView.swift; sourceTree = ""; };
- DB44767A260B3B8C00B66B82 /* CustomEmojiPickerInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiPickerInputView.swift; sourceTree = ""; };
- DB447690260B406600B66B82 /* CustomEmojiPickerItemCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiPickerItemCollectionViewCell.swift; sourceTree = ""; };
- DB447696260B439000B66B82 /* CustomEmojiPickerHeaderCollectionReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiPickerHeaderCollectionReusableView.swift; sourceTree = ""; };
DB4481B825EE289600BEFB67 /* UITableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITableView.swift; sourceTree = ""; };
DB45FAB525CA5485005A8AC7 /* UIAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertController.swift; sourceTree = ""; };
DB45FAD625CA6C76005A8AC7 /* UIBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIBarButtonItem.swift; sourceTree = ""; };
@@ -820,7 +824,6 @@
DB64BA442851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonAuthentication+Fetch.swift"; sourceTree = ""; };
DB64BA472851F29300ADF1B7 /* Account+Fetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account+Fetch.swift"; sourceTree = ""; };
DB65C63627A2AF6C008BAC2E /* ReportItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportItem.swift; sourceTree = ""; };
- DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+DataSource.swift"; sourceTree = ""; };
DB6746EA278ED8B0008A6B94 /* PollOptionView+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PollOptionView+Configuration.swift"; sourceTree = ""; };
DB6746EC278F45F0008A6B94 /* AutoGenerateProtocolRelayDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoGenerateProtocolRelayDelegate.swift; sourceTree = ""; };
DB6746EF278F463B008A6B94 /* AutoGenerateProtocolDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoGenerateProtocolDelegate.swift; sourceTree = ""; };
@@ -865,8 +868,6 @@
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 = ""; };
@@ -898,6 +899,12 @@
DB938F0826240F3C00E5B6C1 /* RemoteThreadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteThreadViewModel.swift; sourceTree = ""; };
DB938F0E2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadViewModel+LoadThreadState.swift"; sourceTree = ""; };
DB938F1E2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadViewModel+Diffable.swift"; sourceTree = ""; };
+ DB96C25D292505FE00F3B85D /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Intents.strings; sourceTree = ""; };
+ DB96C25E292505FF00F3B85D /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/InfoPlist.strings; sourceTree = ""; };
+ DB96C25F292505FF00F3B85D /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = cs; path = cs.lproj/Intents.stringsdict; sourceTree = ""; };
+ DB96C260292506D600F3B85D /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Intents.strings; sourceTree = ""; };
+ DB96C261292506D700F3B85D /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/InfoPlist.strings; sourceTree = ""; };
+ DB96C262292506D700F3B85D /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sl; path = sl.lproj/Intents.stringsdict; 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 = ""; };
@@ -912,7 +919,6 @@
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 = ""; };
- DB9A488926034D40008B817C /* ComposeViewModel+PublishState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+PublishState.swift"; sourceTree = ""; };
DB9D6BE825E4F5340051B173 /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = ""; };
DB9D6BF725E4F5690051B173 /* NotificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationViewController.swift; sourceTree = ""; };
DB9D6BFE25E4F5940051B173 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; };
@@ -961,14 +967,12 @@
DBB525632612C988002F1F29 /* MeProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeProfileViewModel.swift; sourceTree = ""; };
DBB8AB4526AECDE200F6D281 /* SendPostIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendPostIntentHandler.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 = ""; };
- DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonRegex.swift; sourceTree = ""; };
DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerAppearance.swift; sourceTree = ""; };
+ DBC3872329214121001EC0FD /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; };
DBC6461226A170AB00B0E31B /* ShareActionExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareActionExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
- DBC6461426A170AB00B0E31B /* ComposeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewController.swift; sourceTree = ""; };
DBC6461726A170AB00B0E31B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; };
DBC6461926A170AB00B0E31B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- DBC6462226A1712000B0E31B /* ComposeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComposeViewModel.swift; sourceTree = ""; };
+ 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 = ""; };
DBC9E3A3282E13BB0063A4D9 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/Intents.strings; sourceTree = ""; };
DBC9E3A4282E13BB0063A4D9 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/InfoPlist.strings; sourceTree = ""; };
@@ -1042,15 +1046,7 @@
DBFEEC98279BDCDE004F81DD /* ProfileAboutViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileAboutViewModel.swift; sourceTree = ""; };
DBFEEC9A279BDDD9004F81DD /* ProfileAboutViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileAboutViewModel+Diffable.swift"; sourceTree = ""; };
DBFEEC9C279C12C1004F81DD /* ProfileFieldEditCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldEditCollectionViewCell.swift; sourceTree = ""; };
- DBFEF05526A576EE006D7ED1 /* StatusEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditorView.swift; sourceTree = ""; };
- DBFEF05626A576EE006D7ED1 /* ComposeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeView.swift; sourceTree = ""; };
- DBFEF05726A576EE006D7ED1 /* ComposeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewModel.swift; sourceTree = ""; };
- DBFEF05826A576EE006D7ED1 /* ContentWarningEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentWarningEditorView.swift; sourceTree = ""; };
- DBFEF05926A576EE006D7ED1 /* StatusAuthorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusAuthorView.swift; sourceTree = ""; };
- DBFEF05A26A576EE006D7ED1 /* StatusAttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusAttachmentView.swift; sourceTree = ""; };
- DBFEF06226A577F2006D7ED1 /* StatusAttachmentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusAttachmentViewModel.swift; sourceTree = ""; };
DBFEF06726A58D07006D7ED1 /* ShareActionExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareActionExtension.entitlements; sourceTree = ""; };
- DBFEF06C26A67FB7006D7ED1 /* StatusAttachmentViewModel+UploadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusAttachmentViewModel+UploadState.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 = ""; };
@@ -1518,9 +1514,22 @@
path = Bookmark;
sourceTree = "";
};
+ D8A6AB68291C50F3003AB663 /* Login */ = {
+ isa = PBXGroup;
+ children = (
+ D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */,
+ D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */,
+ D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */,
+ D87BFC8E291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift */,
+ D8916DBF29211BE500124085 /* ContentSizedTableView.swift */,
+ );
+ path = Login;
+ sourceTree = "";
+ };
DB01409B25C40BB600F9F3CF /* Onboarding */ = {
isa = PBXGroup;
children = (
+ D8A6AB68291C50F3003AB663 /* Login */,
DB68A03825E900CC00CFDF14 /* Share */,
0FAA0FDD25E0B5700017CCDE /* Welcome */,
0FAA102525E1125D0017CCDE /* PickServer */,
@@ -1585,16 +1594,6 @@
path = Cell;
sourceTree = "";
};
- DB06180B2785B2AF0030EE79 /* Cell */ = {
- isa = PBXGroup;
- children = (
- DB0618092785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift */,
- DB8481142788121200BBEABA /* MastodonRegisterTextFieldTableViewCell.swift */,
- DB84811627883C2600BBEABA /* MastodonRegisterPasswordHintTableViewCell.swift */,
- );
- path = Cell;
- sourceTree = "";
- };
DB0A322F280EEA00001729D2 /* View */ = {
isa = PBXGroup;
children = (
@@ -1878,8 +1877,6 @@
DBA0A11225FB3FC10079C110 /* ComposeToolbarView.swift */,
DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */,
DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */,
- DB44767A260B3B8C00B66B82 /* CustomEmojiPickerInputView.swift */,
- DB221B15260C395900AEFE46 /* CustomEmojiPickerInputViewModel.swift */,
DBC7A671260C897100E57475 /* StatusContentWarningEditorView.swift */,
);
path = View;
@@ -2146,8 +2143,6 @@
DB789A2125F9F76D0071ACA0 /* CollectionViewCell */,
DB789A0A25F9F2950071ACA0 /* ComposeViewController.swift */,
DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */,
- DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */,
- DB9A488926034D40008B817C /* ComposeViewModel+PublishState.swift */,
);
path = Compose;
sourceTree = "";
@@ -2159,8 +2154,6 @@
DB87D4442609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift */,
DB87D4502609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift */,
DB2FF50F260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift */,
- DB447690260B406600B66B82 /* CustomEmojiPickerItemCollectionViewCell.swift */,
- DB447696260B439000B66B82 /* CustomEmojiPickerHeaderCollectionReusableView.swift */,
);
path = CollectionViewCell;
sourceTree = "";
@@ -2233,6 +2226,7 @@
isa = PBXGroup;
children = (
2DF123A625C3B0210020F248 /* ActiveLabel.swift */,
+ 2A82294E29262EE000D2A1F7 /* AppContext+NextAccount.swift */,
5DF1056325F887CB00D6C0D4 /* AVPlayer.swift */,
2D206B8525F5FB0900143C56 /* Double.swift */,
DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */,
@@ -2252,6 +2246,7 @@
2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.swift */,
2D84350425FF858100EECE90 /* UIScrollView.swift */,
DB9E0D6E25EE008500CFDD76 /* UIInterpolatingMotionEffect.swift */,
+ 2AE244472927831100BDBF7C /* UIImage+SFSymbols.swift */,
DBCC3B2F261440A50045B23D /* UITabBarController.swift */,
DB73BF4827140BA300781945 /* UICollectionViewDiffableDataSource.swift */,
DB73BF4A27140C0800781945 /* UITableViewDiffableDataSource.swift */,
@@ -2452,6 +2447,7 @@
isa = PBXGroup;
children = (
DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */,
+ C24C97022922F30500BAE8CB /* RefreshControl.swift */,
);
path = Control;
sourceTree = "";
@@ -2520,7 +2516,6 @@
DBBC24D526A54BCB00398BB9 /* Helper */ = {
isa = PBXGroup;
children = (
- DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */,
DBF3B7402733EB9400E21627 /* MastodonLocalCode.swift */,
);
path = Helper;
@@ -2567,7 +2562,6 @@
DBE0821A25CD382900FD6BBD /* Register */ = {
isa = PBXGroup;
children = (
- DB06180B2785B2AF0030EE79 /* Cell */,
DBE0821425CD382600FD6BBD /* MastodonRegisterViewController.swift */,
2D939AE725EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift */,
DBE0822325CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift */,
@@ -2687,28 +2681,11 @@
path = Cell;
sourceTree = "";
};
- DBFEF05426A576EE006D7ED1 /* View */ = {
- isa = PBXGroup;
- children = (
- DBBC24A726A52F9000398BB9 /* ComposeToolbarView.swift */,
- DBFEF05526A576EE006D7ED1 /* StatusEditorView.swift */,
- DBFEF05626A576EE006D7ED1 /* ComposeView.swift */,
- DBFEF05726A576EE006D7ED1 /* ComposeViewModel.swift */,
- DBFEF05826A576EE006D7ED1 /* ContentWarningEditorView.swift */,
- DBFEF05926A576EE006D7ED1 /* StatusAuthorView.swift */,
- DBFEF05A26A576EE006D7ED1 /* StatusAttachmentView.swift */,
- DBFEF06226A577F2006D7ED1 /* StatusAttachmentViewModel.swift */,
- DBFEF06C26A67FB7006D7ED1 /* StatusAttachmentViewModel+UploadState.swift */,
- );
- path = View;
- sourceTree = "";
- };
DBFEF06126A57721006D7ED1 /* Scene */ = {
isa = PBXGroup;
children = (
- DBFEF05426A576EE006D7ED1 /* View */,
- DBC6462226A1712000B0E31B /* ComposeViewModel.swift */,
- DBC6461426A170AB00B0E31B /* ComposeViewController.swift */,
+ DBC6462226A1712000B0E31B /* ShareViewModel.swift */,
+ DBC3872329214121001EC0FD /* ShareViewController.swift */,
);
path = Scene;
sourceTree = "";
@@ -2727,9 +2704,9 @@
DB427DCF25BAA00100D1B89D /* Frameworks */,
DB89BA0825C10FD0008580ED /* Embed Frameworks */,
DBF8AE1B263293E400C9C23C /* Embed Foundation Extensions */,
- DB3D100425BAA71500EAA174 /* ShellScript */,
- DB025B8E278D6448002F581E /* ShellScript */,
- DB697DD2278F48D5004EF2F7 /* ShellScript */,
+ DB3D100425BAA71500EAA174 /* Run SwiftGen */,
+ DB025B8E278D6448002F581E /* Run Sourcery: Core Data */,
+ DB697DD2278F48D5004EF2F7 /* Run Sourcery */,
);
buildRules = (
);
@@ -2907,6 +2884,8 @@
gd,
"es-AR",
fi,
+ cs,
+ sl,
);
mainGroup = DB427DC925BAA00100D1B89D;
packageReferences = (
@@ -3045,7 +3024,7 @@
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;
};
- DB025B8E278D6448002F581E /* ShellScript */ = {
+ DB025B8E278D6448002F581E /* Run Sourcery: Core Data */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 12;
@@ -3055,6 +3034,7 @@
);
inputPaths = (
);
+ name = "Run Sourcery: Core Data";
outputFileListPaths = (
);
outputPaths = (
@@ -3063,7 +3043,7 @@
shellPath = /bin/sh;
shellScript = "if [[ -f \"${PODS_ROOT}/Sourcery/bin/sourcery\" ]]; then\n \"${PODS_ROOT}/Sourcery/bin/sourcery\" --config ./MastodonSDK/Sources/CoreDataStack\nelse\n echo \"warning: Sourcery is not installed. Run 'pod install --repo-update' to install it.\"\nfi\n";
};
- DB3D100425BAA71500EAA174 /* ShellScript */ = {
+ DB3D100425BAA71500EAA174 /* Run SwiftGen */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 12;
@@ -3073,6 +3053,7 @@
);
inputPaths = (
);
+ name = "Run SwiftGen";
outputFileListPaths = (
);
outputPaths = (
@@ -3081,7 +3062,7 @@
shellPath = /bin/sh;
shellScript = "if [[ -f \"${PODS_ROOT}/SwiftGen/bin/swiftgen\" ]]; then\n \"${PODS_ROOT}/SwiftGen/bin/swiftgen\" \nelse\n echo \"warning: SwiftGen is not installed. Run 'pod install --repo-update' to install it.\"\nfi\n";
};
- DB697DD2278F48D5004EF2F7 /* ShellScript */ = {
+ DB697DD2278F48D5004EF2F7 /* Run Sourcery */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 12;
@@ -3091,6 +3072,7 @@
);
inputPaths = (
);
+ name = "Run Sourcery";
outputFileListPaths = (
);
outputPaths = (
@@ -3198,7 +3180,6 @@
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 */,
2D8434F525FF465D00EECE90 /* HomeTimelineNavigationBarTitleViewModel.swift in Sources */,
DB938F0F2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift in Sources */,
@@ -3230,6 +3211,7 @@
DB5B54A32833BD1A00DEF8B2 /* UserListViewModel.swift in Sources */,
DBF156DF2701B17600EC00B7 /* SidebarAddAccountCollectionViewCell.swift in Sources */,
DB0617F1278413D00030EE79 /* PickServerServerSectionTableHeaderView.swift in Sources */,
+ D87BFC8F291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift in Sources */,
DB0FCB7E27958957006C02E2 /* StatusThreadRootTableViewCell+ViewModel.swift in Sources */,
DB789A0B25F9F2950071ACA0 /* ComposeViewController.swift in Sources */,
DB938F0926240F3C00E5B6C1 /* RemoteThreadViewModel.swift in Sources */,
@@ -3246,7 +3228,6 @@
DBFEEC9D279C12C1004F81DD /* ProfileFieldEditCollectionViewCell.swift in Sources */,
DB3E6FEC2806D7F100B035AE /* DiscoveryNewsViewController.swift in Sources */,
DB2FF510260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift in Sources */,
- DBBC24DC26A54BCB00398BB9 /* MastodonRegex.swift in Sources */,
DBCBED1726132DB500B49291 /* UserTimelineViewModel+Diffable.swift in Sources */,
2DE0FACE2615F7AD00CDF649 /* RecommendAccountSection.swift in Sources */,
2DAC9E3E262FC2400062E1A6 /* SuggestionAccountViewModel.swift in Sources */,
@@ -3298,10 +3279,10 @@
DB73BF4927140BA300781945 /* UICollectionViewDiffableDataSource.swift in Sources */,
DBA5E7AB263BD3F5004598BB /* TimelineTableViewCellContextMenuConfiguration.swift in Sources */,
DB73B490261F030A002E9E9F /* SafariActivity.swift in Sources */,
+ 2AE244482927831100BDBF7C /* UIImage+SFSymbols.swift in Sources */,
DB63F7492799126300455B82 /* FollowerListViewController+DataSourceProvider.swift in Sources */,
2D198643261BF09500F0B013 /* SearchResultItem.swift in Sources */,
2DAC9E38262FC2320062E1A6 /* SuggestionAccountViewController.swift in Sources */,
- DB66728C25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift in Sources */,
DB6180E02639194B0018D199 /* MediaPreviewPagingViewController.swift in Sources */,
DBE0822425CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift in Sources */,
5B90C45E262599800002E742 /* SettingsViewModel.swift in Sources */,
@@ -3339,7 +3320,6 @@
DB98EB6227B215EB0082E365 /* ReportResultViewController.swift in Sources */,
DB6B74F4272FBAE700C70B6E /* FollowerListViewModel+Diffable.swift in Sources */,
DB6B74F2272FB67600C70B6E /* FollowerListViewModel.swift in Sources */,
- DB44767B260B3B8C00B66B82 /* CustomEmojiPickerInputView.swift in Sources */,
0F20222D261457EE000C64BF /* HashtagTimelineViewModel+State.swift in Sources */,
DB0009A626AEE5DC009B9D2D /* Intents.intentdefinition in Sources */,
5B90C462262599800002E742 /* SettingsSectionHeader.swift in Sources */,
@@ -3352,10 +3332,11 @@
DB63F769279A5EBB00455B82 /* NotificationTimelineViewModel+Diffable.swift in Sources */,
DBFEEC9B279BDDD9004F81DD /* ProfileAboutViewModel+Diffable.swift in Sources */,
DBB525562611EDCA002F1F29 /* UserTimelineViewModel.swift in Sources */,
+ D8916DC029211BE500124085 /* ContentSizedTableView.swift in Sources */,
DB0618012785732C0030EE79 /* ServerRulesTableViewCell.swift in Sources */,
- DB221B16260C395900AEFE46 /* CustomEmojiPickerInputViewModel.swift in Sources */,
DB98EB5C27B10A730082E365 /* ReportSupplementaryViewModel.swift in Sources */,
DB0617EF277F12720030EE79 /* NavigationActionView.swift in Sources */,
+ D87BFC8D291EB81200FEE264 /* MastodonLoginViewModel.swift in Sources */,
DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */,
2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */,
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */,
@@ -3424,6 +3405,7 @@
DBCC3B8F26148F7B0045B23D /* CachedProfileViewModel.swift in Sources */,
DB4F097526A037F500D62E92 /* SearchHistoryViewModel.swift in Sources */,
DB3EA8E9281B7A3700598866 /* DiscoveryCommunityViewModel.swift in Sources */,
+ D87BFC8B291D5C6B00FEE264 /* MastodonLoginView.swift in Sources */,
DB6180F826391D660018D199 /* MediaPreviewingViewController.swift in Sources */,
DBEFCD71282A12B200C0ABEA /* ReportReasonViewController.swift in Sources */,
DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */,
@@ -3437,7 +3419,6 @@
2D35237A26256D920031AF25 /* NotificationSection.swift in Sources */,
2D4AD89C263165B500613EFC /* SuggestionAccountCollectionViewCell.swift in Sources */,
DB98EB6927B21A7C0082E365 /* ReportResultActionTableViewCell.swift in Sources */,
- DB447691260B406600B66B82 /* CustomEmojiPickerItemCollectionViewCell.swift in Sources */,
DB9282B225F3222800823B15 /* PickServerEmptyStateView.swift in Sources */,
DB697DDF278F524F004EF2F7 /* DataSourceFacade+Profile.swift in Sources */,
DB1FD45025F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift in Sources */,
@@ -3461,7 +3442,6 @@
DB63F74D27993F5B00455B82 /* SearchHistoryUserCollectionViewCell.swift in Sources */,
DB8AF54425C13647002E6C99 /* SceneCoordinator.swift in Sources */,
DB1D84382657B275000346B3 /* SegmentedControlNavigateable.swift in Sources */,
- DB447697260B439000B66B82 /* CustomEmojiPickerHeaderCollectionReusableView.swift in Sources */,
0F20220726134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift in Sources */,
DB7A9F932818F33C0016AF98 /* MastodonServerRulesViewController+Debug.swift in Sources */,
2D5A3D2825CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift in Sources */,
@@ -3487,6 +3467,7 @@
DB697DDB278F4DE3004EF2F7 /* DataSourceProvider+StatusTableViewCellDelegate.swift in Sources */,
DB87D4512609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift in Sources */,
DBB45B5627B39FC9002DC5A7 /* MediaPreviewVideoViewController.swift in Sources */,
+ D8A6AB6C291C5136003AB663 /* MastodonLoginViewController.swift in Sources */,
DB0FCB8027968F70006C02E2 /* MastodonStatusThreadViewModel.swift in Sources */,
DB67D08627312E67006A36CF /* WizardViewController.swift in Sources */,
DB6746EB278ED8B0008A6B94 /* PollOptionView+Configuration.swift in Sources */,
@@ -3494,6 +3475,7 @@
DBFEEC99279BDCDE004F81DD /* ProfileAboutViewModel.swift in Sources */,
2D198649261C0B8500F0B013 /* SearchResultSection.swift in Sources */,
DB4F097B26A039FF00D62E92 /* SearchHistorySection.swift in Sources */,
+ 2A82294F29262EE000D2A1F7 /* AppContext+NextAccount.swift in Sources */,
DBB525302611EBF3002F1F29 /* ProfilePagingViewModel.swift in Sources */,
DB9F58EC26EF435000E7BBE9 /* AccountViewController.swift in Sources */,
2D5A3D6225CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift in Sources */,
@@ -3520,6 +3502,7 @@
5BB04FF5262F0E6D0043BFF6 /* ReportSection.swift in Sources */,
DBEFCD82282A2AB100C0ABEA /* ReportServerRulesView.swift in Sources */,
DBA94436265CBB7400C537E1 /* ProfileFieldItem.swift in Sources */,
+ C24C97032922F30500BAE8CB /* RefreshControl.swift in Sources */,
DB023D2A27A0FE5C005AC798 /* DataSourceProvider+NotificationTableViewCellDelegate.swift in Sources */,
DB98EB6027B10E150082E365 /* ReportCommentTableViewCell.swift in Sources */,
DB0FCB962797E6C2006C02E2 /* SearchResultViewController+DataSourceProvider.swift in Sources */,
@@ -3567,9 +3550,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- DBC6462326A1712000B0E31B /* ComposeViewModel.swift in Sources */,
+ DBC6462326A1712000B0E31B /* ShareViewModel.swift in Sources */,
DBB3BA2B26A81D060004F2D4 /* FLAnimatedImageView.swift in Sources */,
- DBC6461526A170AB00B0E31B /* ComposeViewController.swift in Sources */,
+ DBC3872429214121001EC0FD /* ShareViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -3643,6 +3626,8 @@
DBC9E3A6282E15190063A4D9 /* gd */,
DBC9E3A9282E17DF0063A4D9 /* es-AR */,
DB8F40042835EE5E006E7513 /* fi */,
+ DB96C25D292505FE00F3B85D /* cs */,
+ DB96C260292506D600F3B85D /* sl */,
);
name = Intents.intentdefinition;
sourceTree = "";
@@ -3674,6 +3659,8 @@
DBC9E3A7282E15190063A4D9 /* gd */,
DBC9E3AA282E17DF0063A4D9 /* es-AR */,
DB8F40052835EE5E006E7513 /* fi */,
+ DB96C25E292505FF00F3B85D /* cs */,
+ DB96C261292506D700F3B85D /* sl */,
);
name = InfoPlist.strings;
sourceTree = "";
@@ -3721,6 +3708,8 @@
DBC9E3A8282E15190063A4D9 /* gd */,
DBC9E3AB282E17DF0063A4D9 /* es-AR */,
DB8F40062835EE5E006E7513 /* fi */,
+ DB96C25F292505FF00F3B85D /* cs */,
+ DB96C262292506D700F3B85D /* sl */,
);
name = Intents.stringsdict;
sourceTree = "";
@@ -3875,7 +3864,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.4.7;
+ MARKETING_VERSION = 1.4.8;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -3904,7 +3893,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.4.7;
+ MARKETING_VERSION = 1.4.8;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -4077,7 +4066,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.4.7;
+ MARKETING_VERSION = 1.4.8;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -4360,7 +4349,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.4.7;
+ MARKETING_VERSION = 1.4.8;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist
index 979c8c0e6..b2b5b312a 100644
--- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -117,12 +117,12 @@
NotificationService.xcscheme_^#shared#^_
orderHint
- 18
+ 17
ShareActionExtension.xcscheme_^#shared#^_
orderHint
- 17
+ 16
SuppressBuildableAutocreation
diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 64dc691bb..409b8820d 100644
--- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -90,6 +90,15 @@
"version" : "2.2.5"
}
},
+ {
+ "identity" : "nextlevelsessionexporter",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/NextLevel/NextLevelSessionExporter.git",
+ "state" : {
+ "revision" : "b6c0cce1aa37fe1547d694f958fac3c3524b74da",
+ "version" : "0.4.6"
+ }
+ },
{
"identity" : "nuke",
"kind" : "remoteSourceControl",
diff --git a/Mastodon/Activity/SafariActivity.swift b/Mastodon/Activity/SafariActivity.swift
index 62a193eaf..e20a9f815 100644
--- a/Mastodon/Activity/SafariActivity.swift
+++ b/Mastodon/Activity/SafariActivity.swift
@@ -58,7 +58,7 @@ final class SafariActivity: UIActivity {
}
Task {
- await sceneCoordinator?.present(scene: .safari(url: url as URL), from: nil, transition: .safariPresent(animated: true, completion: nil))
+ _ = await sceneCoordinator?.present(scene: .safari(url: url as URL), from: nil, transition: .safariPresent(animated: true, completion: nil))
activityDidFinish(true)
}
}
diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift
index 8a0825969..c3019010a 100644
--- a/Mastodon/Coordinator/SceneCoordinator.swift
+++ b/Mastodon/Coordinator/SceneCoordinator.swift
@@ -71,8 +71,8 @@ final public class SceneCoordinator {
self.setup()
try await Task.sleep(nanoseconds: .second * 1)
- // redirect to notification tab
- self.switchToTabBar(tab: .notification)
+ // redirect to notifications tab
+ self.switchToTabBar(tab: .notifications)
// Delay in next run loop
DispatchQueue.main.async { [weak self] in
@@ -149,6 +149,7 @@ extension SceneCoordinator {
case mastodonConfirmEmail(viewModel: MastodonConfirmEmailViewModel)
case mastodonResendEmail(viewModel: MastodonResendEmailViewModel)
case mastodonWebView(viewModel: WebViewModel)
+ case mastodonLogin
// search
case searchDetail(viewModel: SearchDetailViewModel)
@@ -199,6 +200,7 @@ extension SceneCoordinator {
case .welcome,
.mastodonPickServer,
.mastodonRegister,
+ .mastodonLogin,
.mastodonServerRules,
.mastodonConfirmEmail,
.mastodonResendEmail:
@@ -339,7 +341,7 @@ extension SceneCoordinator {
case .custom(let transitioningDelegate):
viewController.modalPresentationStyle = .custom
viewController.transitioningDelegate = transitioningDelegate
- // viewController.modalPresentationCapturesStatusBarAppearance = true
+ viewController.modalPresentationCapturesStatusBarAppearance = true
(splitViewController ?? presentingViewController)?.present(viewController, animated: true, completion: nil)
case .customPush(let animated):
@@ -403,6 +405,13 @@ private extension SceneCoordinator {
let _viewController = MastodonConfirmEmailViewController()
_viewController.viewModel = viewModel
viewController = _viewController
+ case .mastodonLogin:
+ let loginViewController = MastodonLoginViewController(appContext: appContext,
+ authenticationViewModel: AuthenticationViewModel(context: appContext, coordinator: self, isAuthenticationExist: false),
+ sceneCoordinator: self)
+ loginViewController.delegate = self
+
+ viewController = loginViewController
case .mastodonResendEmail(let viewModel):
let _viewController = MastodonResendEmailViewController()
_viewController.viewModel = viewModel
@@ -529,5 +538,16 @@ private extension SceneCoordinator {
needs?.context = appContext
needs?.coordinator = self
}
-
+}
+
+//MARK: - MastodonLoginViewControllerDelegate
+
+extension SceneCoordinator: MastodonLoginViewControllerDelegate {
+ func backButtonPressed(_ viewController: MastodonLoginViewController) {
+ viewController.navigationController?.popViewController(animated: true)
+ }
+
+ func nextButtonPressed(_ viewController: MastodonLoginViewController) {
+ viewController.login()
+ }
}
diff --git a/Mastodon/Diffable/Onboarding/CategoryPickerSection.swift b/Mastodon/Diffable/Onboarding/CategoryPickerSection.swift
index b53b378d6..191d4d166 100644
--- a/Mastodon/Diffable/Onboarding/CategoryPickerSection.swift
+++ b/Mastodon/Diffable/Onboarding/CategoryPickerSection.swift
@@ -21,7 +21,6 @@ extension CategoryPickerSection {
UICollectionViewDiffableDataSource(collectionView: collectionView) { [weak dependency] collectionView, indexPath, item -> UICollectionViewCell? in
guard let _ = dependency else { return nil }
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self), for: indexPath) as! PickServerCategoryCollectionViewCell
- cell.categoryView.emojiLabel.text = item.emoji
cell.categoryView.titleLabel.text = item.title
cell.observe(\.isSelected, options: [.initial, .new]) { cell, _ in
cell.categoryView.highlightedIndicatorView.alpha = cell.isSelected ? 1 : 0
diff --git a/Mastodon/Diffable/Onboarding/PickServerSection.swift b/Mastodon/Diffable/Onboarding/PickServerSection.swift
index 01a31f6f6..ef7ca5972 100644
--- a/Mastodon/Diffable/Onboarding/PickServerSection.swift
+++ b/Mastodon/Diffable/Onboarding/PickServerSection.swift
@@ -18,16 +18,14 @@ enum PickServerSection: Equatable, Hashable {
extension PickServerSection {
static func tableViewDiffableDataSource(
for tableView: UITableView,
- dependency: NeedsDependency,
- pickServerCellDelegate: PickServerCellDelegate
+ dependency: NeedsDependency
) -> UITableViewDiffableDataSource {
tableView.register(OnboardingHeadlineTableViewCell.self, forCellReuseIdentifier: String(describing: OnboardingHeadlineTableViewCell.self))
tableView.register(PickServerCell.self, forCellReuseIdentifier: String(describing: PickServerCell.self))
tableView.register(PickServerLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: PickServerLoaderTableViewCell.self))
return UITableViewDiffableDataSource(tableView: tableView) { [
- weak dependency,
- weak pickServerCellDelegate
+ weak dependency
] tableView, indexPath, item -> UITableViewCell? in
guard let _ = dependency else { return nil }
switch item {
@@ -37,7 +35,6 @@ extension PickServerSection {
case .server(let server, let attribute):
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerCell.self), for: indexPath) as! PickServerCell
PickServerSection.configure(cell: cell, server: server, attribute: attribute)
- cell.delegate = pickServerCellDelegate
return cell
case .loader(let attribute):
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerLoaderTableViewCell.self), for: indexPath) as! PickServerLoaderTableViewCell
diff --git a/Mastodon/Diffable/Profile/ProfileFieldItem.swift b/Mastodon/Diffable/Profile/ProfileFieldItem.swift
index 47848cc01..e33a2f883 100644
--- a/Mastodon/Diffable/Profile/ProfileFieldItem.swift
+++ b/Mastodon/Diffable/Profile/ProfileFieldItem.swift
@@ -23,6 +23,7 @@ extension ProfileFieldItem {
var name: CurrentValueSubject
var value: CurrentValueSubject
+ var verifiedAt: CurrentValueSubject
let emojiMeta: MastodonContent.Emojis
@@ -30,11 +31,13 @@ extension ProfileFieldItem {
id: UUID = UUID(),
name: String,
value: String,
+ verifiedAt: Date?,
emojiMeta: MastodonContent.Emojis
) {
self.id = id
self.name = CurrentValueSubject(name)
self.value = CurrentValueSubject(value)
+ self.verifiedAt = CurrentValueSubject(verifiedAt)
self.emojiMeta = emojiMeta
}
@@ -45,6 +48,7 @@ extension ProfileFieldItem {
return lhs.id == rhs.id
&& lhs.name.value == rhs.name.value
&& lhs.value.value == rhs.value.value
+ && lhs.verifiedAt.value == rhs.verifiedAt.value
&& lhs.emojiMeta == rhs.emojiMeta
}
diff --git a/Mastodon/Diffable/Profile/ProfileFieldSection.swift b/Mastodon/Diffable/Profile/ProfileFieldSection.swift
index 19771b5db..6e57e6af9 100644
--- a/Mastodon/Diffable/Profile/ProfileFieldSection.swift
+++ b/Mastodon/Diffable/Profile/ProfileFieldSection.swift
@@ -8,6 +8,7 @@
import os
import UIKit
import Combine
+import MastodonAsset
import MastodonCore
import MastodonMeta
import MastodonLocalization
@@ -48,6 +49,10 @@ extension ProfileFieldSection {
do {
let mastodonContent = MastodonContent(content: field.value.value, emojis: field.emojiMeta)
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
+ cell.valueMetaLabel.linkAttributes[.foregroundColor] = Asset.Colors.brand.color
+ if field.verifiedAt.value != nil {
+ cell.valueMetaLabel.linkAttributes[.foregroundColor] = Asset.Scene.Profile.About.bioAboutFieldVerifiedLink.color
+ }
cell.valueMetaLabel.configure(content: metaContent)
} catch {
let content = PlaintextMetaContent(string: field.value.value)
@@ -57,7 +62,23 @@ extension ProfileFieldSection {
// set background
var backgroundConfiguration = UIBackgroundConfiguration.listPlainCell()
backgroundConfiguration.backgroundColor = UIColor.secondarySystemBackground
+ if (field.verifiedAt.value != nil) {
+ backgroundConfiguration.backgroundColor = Asset.Scene.Profile.About.bioAboutFieldVerifiedBackground.color
+ }
cell.backgroundConfiguration = backgroundConfiguration
+
+ // set checkmark and edit menu label
+ cell.checkmark.isHidden = true
+ cell.checkmarkPopoverString = nil
+ if let verifiedAt = field.verifiedAt.value {
+ cell.checkmark.isHidden = false
+ let formatter = DateFormatter()
+ formatter.dateStyle = .medium
+ formatter.timeStyle = .short
+ let dateString = formatter.string(from: verifiedAt)
+ cell.checkmark.accessibilityLabel = L10n.Scene.Profile.Fields.Verified.long(dateString)
+ cell.checkmarkPopoverString = L10n.Scene.Profile.Fields.Verified.short(dateString)
+ }
cell.delegate = configuration.profileFieldCollectionViewCellDelegate
}
diff --git a/Mastodon/Extension/AppContext+NextAccount.swift b/Mastodon/Extension/AppContext+NextAccount.swift
new file mode 100644
index 000000000..a8eae1e13
--- /dev/null
+++ b/Mastodon/Extension/AppContext+NextAccount.swift
@@ -0,0 +1,47 @@
+//
+// AppContext+NextAccount.swift
+// Mastodon
+//
+// Created by Marcus Kida on 17.11.22.
+//
+
+import CoreData
+import CoreDataStack
+import MastodonCore
+import MastodonSDK
+
+extension AppContext {
+ func nextAccount(in authContext: AuthContext) -> MastodonAuthentication? {
+ let request = MastodonAuthentication.sortedFetchRequest
+ guard
+ let accounts = try? managedObjectContext.fetch(request),
+ accounts.count > 1
+ else { return nil }
+
+ let nextSelectedAccountIndex: Int? = {
+ for (index, account) in accounts.enumerated() {
+ guard account == authContext.mastodonAuthenticationBox
+ .authenticationRecord
+ .object(in: managedObjectContext)
+ else { continue }
+
+ let nextAccountIndex = index + 1
+
+ if accounts.count > nextAccountIndex {
+ return nextAccountIndex
+ } else {
+ return 0
+ }
+ }
+
+ return nil
+ }()
+
+ guard
+ let nextSelectedAccountIndex = nextSelectedAccountIndex,
+ accounts.count > nextSelectedAccountIndex
+ else { return nil }
+
+ return accounts[nextSelectedAccountIndex]
+ }
+}
diff --git a/Mastodon/Extension/String.swift b/Mastodon/Extension/String.swift
index bf70c8937..0aa8acb3a 100644
--- a/Mastodon/Extension/String.swift
+++ b/Mastodon/Extension/String.swift
@@ -15,6 +15,8 @@ extension String {
mutating func capitalizeFirstLetter() {
self = self.capitalizingFirstLetter()
}
+
+ static let empty = ""
}
extension String {
diff --git a/Mastodon/Extension/UIImage+SFSymbols.swift b/Mastodon/Extension/UIImage+SFSymbols.swift
new file mode 100644
index 000000000..cf20055ea
--- /dev/null
+++ b/Mastodon/Extension/UIImage+SFSymbols.swift
@@ -0,0 +1,12 @@
+//
+// UIImage+SFSymbols.swift
+// Mastodon
+//
+// Created by Marcus Kida on 18.11.22.
+//
+
+import UIKit
+
+extension UIImage {
+ static let chevronUpChevronDown = UIImage(systemName: "chevron.up.chevron.down")
+}
diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Follow.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Follow.swift
index b3812f198..a85de703a 100644
--- a/Mastodon/Protocol/Provider/DataSourceFacade+Follow.swift
+++ b/Mastodon/Protocol/Provider/DataSourceFacade+Follow.swift
@@ -94,7 +94,7 @@ extension DataSourceFacade {
let alertController = await UIAlertController(for: error, title: nil, preferredStyle: .alert)
let okAction = await UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default)
await alertController.addAction(okAction)
- await dependency.coordinator.present(
+ _ = await dependency.coordinator.present(
scene: .alertController(alertController: alertController),
from: nil,
transition: .alertController(animated: true, completion: nil)
diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Hashtag.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Hashtag.swift
index 43d6b954b..6135c904a 100644
--- a/Mastodon/Protocol/Provider/DataSourceFacade+Hashtag.swift
+++ b/Mastodon/Protocol/Provider/DataSourceFacade+Hashtag.swift
@@ -35,7 +35,7 @@ extension DataSourceFacade {
hashtag: tag.name
)
- provider.coordinator.present(
+ _ = provider.coordinator.present(
scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel),
from: provider,
transition: .show
@@ -61,7 +61,7 @@ extension DataSourceFacade {
hashtag: name
)
- provider.coordinator.present(
+ _ = provider.coordinator.present(
scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel),
from: provider,
transition: .show
diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Media.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Media.swift
index 3e767f2d3..9dd97f38a 100644
--- a/Mastodon/Protocol/Provider/DataSourceFacade+Media.swift
+++ b/Mastodon/Protocol/Provider/DataSourceFacade+Media.swift
@@ -24,7 +24,7 @@ extension DataSourceFacade {
item: mediaPreviewItem,
transitionItem: mediaPreviewTransitionItem
)
- dependency.coordinator.present(
+ _ = dependency.coordinator.present(
scene: .mediaPreview(viewModel: mediaPreviewViewModel),
from: dependency,
transition: .custom(transitioningDelegate: dependency.mediaPreviewTransitionController)
diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift
index 7e0ed37fc..dd3c4903a 100644
--- a/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift
+++ b/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift
@@ -56,13 +56,13 @@ extension DataSourceFacade {
url.pathComponents[2] == "statuses" {
let statusID = url.pathComponents[3]
let threadViewModel = RemoteThreadViewModel(context: provider.context, authContext: provider.authContext, statusID: statusID)
- await provider.coordinator.present(scene: .thread(viewModel: threadViewModel), from: nil, transition: .show)
+ _ = await provider.coordinator.present(scene: .thread(viewModel: threadViewModel), from: nil, transition: .show)
} else {
- await provider.coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
+ _ = await provider.coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
}
case .hashtag(_, let hashtag, _):
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: provider.context, authContext: provider.authContext, hashtag: hashtag)
- await provider.coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: provider, transition: .show)
+ _ = await provider.coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: provider, transition: .show)
case .mention(_, let mention, let userInfo):
await coordinateToProfileScene(
provider: provider,
diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift
index ef01b8394..3c0509d2c 100644
--- a/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift
+++ b/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift
@@ -47,7 +47,7 @@ extension DataSourceFacade {
mastodonUser: user
)
- provider.coordinator.present(
+ _ = provider.coordinator.present(
scene: .profile(viewModel: profileViewModel),
from: provider,
transition: .show
@@ -75,7 +75,7 @@ extension DataSourceFacade {
}
guard let mention = mentions?.first(where: { $0.username == mention }) else {
- await provider.coordinator.present(
+ _ = await provider.coordinator.present(
scene: .safari(url: url),
from: provider,
transition: .safariPresent(animated: true, completion: nil)
@@ -102,7 +102,7 @@ extension DataSourceFacade {
}
}()
- await provider.coordinator.present(
+ _ = await provider.coordinator.present(
scene: .profile(viewModel: profileViewModel),
from: provider,
transition: .show
diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift
index 332ed75e4..ac9da6e81 100644
--- a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift
+++ b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift
@@ -43,7 +43,7 @@ extension DataSourceFacade {
dependency: provider,
status: status
)
- provider.coordinator.present(
+ _ = provider.coordinator.present(
scene: .activityViewController(
activityViewController: activityViewController,
sourceView: button,
@@ -84,7 +84,7 @@ extension DataSourceFacade {
self.url = url
self.metadata = LPLinkMetadata()
metadata.url = url
- metadata.title = "\(status.author.displayName) (@\(status.author.username)@\(status.author.domain))"
+ metadata.title = "\(status.author.displayName) (@\(status.author.acctWithDomain))"
metadata.iconProvider = NSItemProvider(object: IconProvider(url: status.author.avatarImageURLWithFallback(domain: status.author.domain)))
}
@@ -358,7 +358,8 @@ extension DataSourceFacade {
dependency: dependency,
status: status
)
- await dependency.coordinator.present(
+
+ _ = dependency.coordinator.present(
scene: .activityViewController(
activityViewController: activityViewController,
sourceView: menuContext.button,
diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift
index 82c25d040..c157b7086 100644
--- a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift
+++ b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift
@@ -487,7 +487,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
authContext: authContext,
kind: .rebloggedBy(status: status)
)
- await coordinator.present(
+ _ = await coordinator.present(
scene: .rebloggedBy(viewModel: userListViewModel),
from: self,
transition: .show
@@ -511,7 +511,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
authContext: authContext,
kind: .favoritedBy(status: status)
)
- await coordinator.present(
+ _ = await coordinator.present(
scene: .favoritedBy(viewModel: userListViewModel),
from: self,
transition: .show
diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift
index 3a71e5346..6ced42601 100644
--- a/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift
+++ b/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift
@@ -143,7 +143,7 @@ extension UITableViewDelegate where Self: DataSourceProvider & MediaPreviewableV
title: L10n.Common.Alerts.SavePhotoFailure.title,
message: L10n.Common.Alerts.SavePhotoFailure.message
)
- self.coordinator.present(
+ _ = self.coordinator.present(
scene: .alertController(alertController: alertController),
from: self,
transition: .alertController(animated: true, completion: nil)
diff --git a/Mastodon/Resources/Base.lproj/infoPlist.strings b/Mastodon/Resources/Base.lproj/infoPlist.strings
new file mode 100644
index 000000000..710865573
--- /dev/null
+++ b/Mastodon/Resources/Base.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/ar.lproj/InfoPlist.strings b/Mastodon/Resources/ar.lproj/InfoPlist.strings
index c3b26f14a..ecb81ddd4 100644
--- a/Mastodon/Resources/ar.lproj/InfoPlist.strings
+++ b/Mastodon/Resources/ar.lproj/InfoPlist.strings
@@ -1,4 +1,4 @@
"NSCameraUsageDescription" = "يُستخدم لالتقاط الصورة عِندَ نشر الحالات";
"NSPhotoLibraryAddUsageDescription" = "يُستخدم لحِفظ الصورة في مكتبة الصور";
-"NewPostShortcutItemTitle" = "منشور جديد";
+"NewPostShortcutItemTitle" = "مَنشُورٌ جَديد";
"SearchShortcutItemTitle" = "البحث";
\ No newline at end of file
diff --git a/Mastodon/Resources/ckb.lproj/InfoPlist.strings b/Mastodon/Resources/ckb.lproj/InfoPlist.strings
index 710865573..8c4946d2d 100644
--- a/Mastodon/Resources/ckb.lproj/InfoPlist.strings
+++ b/Mastodon/Resources/ckb.lproj/InfoPlist.strings
@@ -1,4 +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
+"NSCameraUsageDescription" = "بەکار دێت بۆ گرتنی وێنەیەک بۆ پۆستەکە";
+"NSPhotoLibraryAddUsageDescription" = "بەکار دێت بۆ هەڵگرتنی وێنە";
+"NewPostShortcutItemTitle" = "پۆستی نوێ";
+"SearchShortcutItemTitle" = "بگەڕێ";
\ No newline at end of file
diff --git a/Mastodon/Resources/cs.lproj/InfoPlist.strings b/Mastodon/Resources/cs.lproj/InfoPlist.strings
new file mode 100644
index 000000000..6989cfcb1
--- /dev/null
+++ b/Mastodon/Resources/cs.lproj/InfoPlist.strings
@@ -0,0 +1,4 @@
+"NSCameraUsageDescription" = "Slouží k pořízení fotografie pro příspěvek";
+"NSPhotoLibraryAddUsageDescription" = "Slouží k uložení fotografie do knihovny fotografií";
+"NewPostShortcutItemTitle" = "Nový příspěvek";
+"SearchShortcutItemTitle" = "Hledat";
\ No newline at end of file
diff --git a/Mastodon/Resources/de.lproj/infoPlist.strings b/Mastodon/Resources/de.lproj/infoPlist.strings
index 9c5feae53..9c8438653 100644
--- a/Mastodon/Resources/de.lproj/infoPlist.strings
+++ b/Mastodon/Resources/de.lproj/infoPlist.strings
@@ -1,4 +1,4 @@
-"NSCameraUsageDescription" = "Verwendet um Fotos für neue Beiträge aufzunehmen";
-"NSPhotoLibraryAddUsageDescription" = "Verwendet um Fotos zu speichern";
+"NSCameraUsageDescription" = "Wird verwendet, um Fotos für neue Beiträge aufzunehmen";
+"NSPhotoLibraryAddUsageDescription" = "Wird verwendet, um Foto in der Foto-Mediathek zu speichern";
"NewPostShortcutItemTitle" = "Neuer Beitrag";
-"SearchShortcutItemTitle" = "Suche";
\ No newline at end of file
+"SearchShortcutItemTitle" = "Suchen";
\ No newline at end of file
diff --git a/Mastodon/Resources/eu.lproj/InfoPlist.strings b/Mastodon/Resources/eu.lproj/InfoPlist.strings
index 710865573..e9d36a901 100644
--- a/Mastodon/Resources/eu.lproj/InfoPlist.strings
+++ b/Mastodon/Resources/eu.lproj/InfoPlist.strings
@@ -1,4 +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
+"NSCameraUsageDescription" = "Bidalketetarako argazkiak ateratzeko erabiltzen da";
+"NSPhotoLibraryAddUsageDescription" = "Argazkiak Argazki-liburutegian gordetzeko erabiltzen da";
+"NewPostShortcutItemTitle" = "Bidalketa berria";
+"SearchShortcutItemTitle" = "Bilatu";
\ No newline at end of file
diff --git a/Mastodon/Resources/fi.lproj/InfoPlist.strings b/Mastodon/Resources/fi.lproj/InfoPlist.strings
index 710865573..040b25d69 100644
--- a/Mastodon/Resources/fi.lproj/InfoPlist.strings
+++ b/Mastodon/Resources/fi.lproj/InfoPlist.strings
@@ -1,4 +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
+"NSCameraUsageDescription" = "Käytetään kuvan ottamiseen julkaisua varten";
+"NSPhotoLibraryAddUsageDescription" = "Käytetään kuvan tallentamiseen kuvakirjastoon";
+"NewPostShortcutItemTitle" = "Uusi julkaisu";
+"SearchShortcutItemTitle" = "Haku";
\ No newline at end of file
diff --git a/Mastodon/Resources/gd.lproj/InfoPlist.strings b/Mastodon/Resources/gd.lproj/InfoPlist.strings
index 710865573..ccb39b44e 100644
--- a/Mastodon/Resources/gd.lproj/InfoPlist.strings
+++ b/Mastodon/Resources/gd.lproj/InfoPlist.strings
@@ -1,4 +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
+"NSCameraUsageDescription" = "’Ga chleachdadh airson dealbh a thogail do staid puist";
+"NSPhotoLibraryAddUsageDescription" = "’Ga chleachdadh airson dealbh a shàbhaladh ann an tasg-lann nan dealbhan";
+"NewPostShortcutItemTitle" = "Post ùr";
+"SearchShortcutItemTitle" = "Lorg";
\ No newline at end of file
diff --git a/Mastodon/Resources/gl.lproj/InfoPlist.strings b/Mastodon/Resources/gl.lproj/InfoPlist.strings
index 710865573..3d6df3f0f 100644
--- a/Mastodon/Resources/gl.lproj/InfoPlist.strings
+++ b/Mastodon/Resources/gl.lproj/InfoPlist.strings
@@ -1,4 +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
+"NSCameraUsageDescription" = "Utilizado para facer foto e publicar estado";
+"NSPhotoLibraryAddUsageDescription" = "Utilizado para gardar a foto na Biblioteca";
+"NewPostShortcutItemTitle" = "Nova publicación";
+"SearchShortcutItemTitle" = "Buscar";
\ No newline at end of file
diff --git a/Mastodon/Resources/it.lproj/InfoPlist.strings b/Mastodon/Resources/it.lproj/InfoPlist.strings
index 710865573..0da468639 100644
--- a/Mastodon/Resources/it.lproj/InfoPlist.strings
+++ b/Mastodon/Resources/it.lproj/InfoPlist.strings
@@ -1,4 +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
+"NSCameraUsageDescription" = "Usato per scattare foto per lo stato del post";
+"NSPhotoLibraryAddUsageDescription" = "Utilizzato per salvare la foto nella galleria immagini";
+"NewPostShortcutItemTitle" = "Nuovo post";
+"SearchShortcutItemTitle" = "Cerca";
\ No newline at end of file
diff --git a/Mastodon/Resources/kab.lproj/InfoPlist.strings b/Mastodon/Resources/kab.lproj/InfoPlist.strings
index 710865573..42adc4cfc 100644
--- a/Mastodon/Resources/kab.lproj/InfoPlist.strings
+++ b/Mastodon/Resources/kab.lproj/InfoPlist.strings
@@ -1,4 +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
+"NSCameraUsageDescription" = "Yettwaseqdac i tuṭṭfa n tewlafin deg usuffeɣ n waddaden";
+"NSPhotoLibraryAddUsageDescription" = "Yettwaseqdac i usekles n tewlafin deg temkarḍit n tewlafin";
+"NewPostShortcutItemTitle" = "Tasuffeɣt tamaynut";
+"SearchShortcutItemTitle" = "Nadi";
\ No newline at end of file
diff --git a/Mastodon/Resources/ku.lproj/InfoPlist.strings b/Mastodon/Resources/ku.lproj/InfoPlist.strings
index 710865573..669ecfacf 100644
--- a/Mastodon/Resources/ku.lproj/InfoPlist.strings
+++ b/Mastodon/Resources/ku.lproj/InfoPlist.strings
@@ -1,4 +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
+"NSCameraUsageDescription" = "Bo kişandina wêneyê ji bo rewşa şandiyan tê bikaranîn";
+"NSPhotoLibraryAddUsageDescription" = "Ji bo tomarkirina wêneyê di pirtûkxaneya wêneyan de tê bikaranîn";
+"NewPostShortcutItemTitle" = "Şandiya nû";
+"SearchShortcutItemTitle" = "Bigere";
\ No newline at end of file
diff --git a/Mastodon/Resources/nl.lproj/InfoPlist.strings b/Mastodon/Resources/nl.lproj/InfoPlist.strings
index a45991068..36b6e9802 100644
--- a/Mastodon/Resources/nl.lproj/InfoPlist.strings
+++ b/Mastodon/Resources/nl.lproj/InfoPlist.strings
@@ -1,4 +1,4 @@
"NSCameraUsageDescription" = "Gebruikt om foto's te nemen voor je berichten";
"NSPhotoLibraryAddUsageDescription" = "Gebruikt om foto's op te slaan in de fotobibliotheek";
"NewPostShortcutItemTitle" = "Nieuw Bericht";
-"SearchShortcutItemTitle" = "Zoeken";
\ No newline at end of file
+"SearchShortcutItemTitle" = "Zoek";
\ No newline at end of file
diff --git a/Mastodon/Resources/sl.lproj/InfoPlist.strings b/Mastodon/Resources/sl.lproj/InfoPlist.strings
new file mode 100644
index 000000000..57c8319f5
--- /dev/null
+++ b/Mastodon/Resources/sl.lproj/InfoPlist.strings
@@ -0,0 +1,4 @@
+"NSCameraUsageDescription" = "Uporabljeno za zajem fotografij za stanje objave";
+"NSPhotoLibraryAddUsageDescription" = "Uporabljeno za shranjevanje fotografije v knjižnico fotografij";
+"NewPostShortcutItemTitle" = "Nova objava";
+"SearchShortcutItemTitle" = "Iskanje";
\ No newline at end of file
diff --git a/Mastodon/Resources/sv.lproj/InfoPlist.strings b/Mastodon/Resources/sv.lproj/InfoPlist.strings
index 710865573..e0facc1d2 100644
--- a/Mastodon/Resources/sv.lproj/InfoPlist.strings
+++ b/Mastodon/Resources/sv.lproj/InfoPlist.strings
@@ -1,4 +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
+"NSCameraUsageDescription" = "Används för att ta foto till inlägg";
+"NSPhotoLibraryAddUsageDescription" = "Används för att spara foto till bildbiblioteket";
+"NewPostShortcutItemTitle" = "Nytt inlägg";
+"SearchShortcutItemTitle" = "Sök";
\ No newline at end of file
diff --git a/Mastodon/Resources/tr.lproj/InfoPlist.strings b/Mastodon/Resources/tr.lproj/InfoPlist.strings
index 710865573..1c4e05659 100644
--- a/Mastodon/Resources/tr.lproj/InfoPlist.strings
+++ b/Mastodon/Resources/tr.lproj/InfoPlist.strings
@@ -1,4 +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
+"NSCameraUsageDescription" = "Fotoğraf çekerek durum paylaşmak için kullanılır";
+"NSPhotoLibraryAddUsageDescription" = "Fotoğraf Albümü'ne fotoğraf kaydetmek için kullanılır";
+"NewPostShortcutItemTitle" = "Yeni Gönderi";
+"SearchShortcutItemTitle" = "Arama";
\ No newline at end of file
diff --git a/Mastodon/Resources/vi.lproj/InfoPlist.strings b/Mastodon/Resources/vi.lproj/InfoPlist.strings
index 710865573..5dd27b7bc 100644
--- a/Mastodon/Resources/vi.lproj/InfoPlist.strings
+++ b/Mastodon/Resources/vi.lproj/InfoPlist.strings
@@ -1,4 +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
+"NSCameraUsageDescription" = "Được sử dụng để chụp ảnh cho tút";
+"NSPhotoLibraryAddUsageDescription" = "Được sử dụng để lưu ảnh vào Thư viện ảnh";
+"NewPostShortcutItemTitle" = "Viết tút";
+"SearchShortcutItemTitle" = "Tìm kiếm";
\ No newline at end of file
diff --git a/Mastodon/Resources/zh-Hant.lproj/InfoPlist.strings b/Mastodon/Resources/zh-Hant.lproj/InfoPlist.strings
index 710865573..737d2f857 100644
--- a/Mastodon/Resources/zh-Hant.lproj/InfoPlist.strings
+++ b/Mastodon/Resources/zh-Hant.lproj/InfoPlist.strings
@@ -1,4 +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
+"NSCameraUsageDescription" = "用來拍照發嘟文";
+"NSPhotoLibraryAddUsageDescription" = "用來儲存照片到圖片庫";
+"NewPostShortcutItemTitle" = "新增嘟文";
+"SearchShortcutItemTitle" = "搜尋";
\ No newline at end of file
diff --git a/Mastodon/Scene/Account/AccountListViewModel.swift b/Mastodon/Scene/Account/AccountListViewModel.swift
index e0aaf97fc..2b8c75b8d 100644
--- a/Mastodon/Scene/Account/AccountListViewModel.swift
+++ b/Mastodon/Scene/Account/AccountListViewModel.swift
@@ -52,7 +52,7 @@ final class AccountListViewModel: NSObject {
mastodonAuthenticationFetchedResultsController.delegate = self
do {
try mastodonAuthenticationFetchedResultsController.performFetch()
- authentications = mastodonAuthenticationFetchedResultsController.fetchedObjects?.compactMap { $0.asRecrod } ?? []
+ authentications = mastodonAuthenticationFetchedResultsController.fetchedObjects?.compactMap { $0.asRecord } ?? []
} catch {
assertionFailure(error.localizedDescription)
}
@@ -166,7 +166,7 @@ extension AccountListViewModel {
cell.badgeButton.accessibilityLabel
]
.compactMap { $0 }
- .joined(separator: " ")
+ .joined(separator: ", ")
}
}
@@ -183,7 +183,7 @@ extension AccountListViewModel: NSFetchedResultsControllerDelegate {
return
}
- authentications = mastodonAuthenticationFetchedResultsController.fetchedObjects?.compactMap { $0.asRecrod } ?? []
+ authentications = mastodonAuthenticationFetchedResultsController.fetchedObjects?.compactMap { $0.asRecord } ?? []
}
}
diff --git a/Mastodon/Scene/Account/AccountViewController.swift b/Mastodon/Scene/Account/AccountViewController.swift
index e25c75b01..7a0e529cc 100644
--- a/Mastodon/Scene/Account/AccountViewController.swift
+++ b/Mastodon/Scene/Account/AccountViewController.swift
@@ -34,7 +34,9 @@ final class AccountListViewController: UIViewController, NeedsDependency {
return barButtonItem
}()
- let dragIndicatorView = DragIndicatorView()
+ lazy var dragIndicatorView = DragIndicatorView { [weak self] in
+ self?.dismiss(animated: true, completion: nil)
+ }
var hasLoaded = false
private(set) lazy var tableView: UITableView = {
@@ -130,14 +132,6 @@ extension AccountListViewController {
self.panModalTransition(to: .shortForm)
}
.store(in: &disposeBag)
-
- if UIAccessibility.isVoiceOverRunning {
- let dragIndicatorTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
- dragIndicatorView.addGestureRecognizer(dragIndicatorTapGestureRecognizer)
- dragIndicatorTapGestureRecognizer.addTarget(self, action: #selector(AccountListViewController.dragIndicatorTapGestureRecognizerHandler(_:)))
- dragIndicatorView.isAccessibilityElement = true
- dragIndicatorView.accessibilityLabel = L10n.Scene.AccountList.dismissAccountSwitcher
- }
}
private func setupBackgroundColor(theme: Theme) {
@@ -160,10 +154,11 @@ extension AccountListViewController {
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
_ = coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil))
}
-
- @objc private func dragIndicatorTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) {
+
+ override func accessibilityPerformEscape() -> Bool {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
dismiss(animated: true, completion: nil)
+ return true
}
}
diff --git a/Mastodon/Scene/Account/Cell/AccountListTableViewCell.swift b/Mastodon/Scene/Account/Cell/AccountListTableViewCell.swift
index 66f49efe8..cd214f2c7 100644
--- a/Mastodon/Scene/Account/Cell/AccountListTableViewCell.swift
+++ b/Mastodon/Scene/Account/Cell/AccountListTableViewCell.swift
@@ -69,6 +69,7 @@ extension AccountListTableViewCell {
])
avatarButton.setContentHuggingPriority(.defaultLow, for: .horizontal)
avatarButton.setContentHuggingPriority(.defaultLow, for: .vertical)
+ avatarButton.isAccessibilityElement = false
let labelContainerStackView = UIStackView()
labelContainerStackView.axis = .vertical
@@ -124,6 +125,8 @@ extension AccountListTableViewCell {
badgeButton.setBadge(number: 0)
checkmarkImageView.isHidden = true
+
+ accessibilityTraits.insert(.button)
}
}
diff --git a/Mastodon/Scene/Account/Cell/AddAccountTableViewCell.swift b/Mastodon/Scene/Account/Cell/AddAccountTableViewCell.swift
index 3ff3066a2..bc47aef43 100644
--- a/Mastodon/Scene/Account/Cell/AddAccountTableViewCell.swift
+++ b/Mastodon/Scene/Account/Cell/AddAccountTableViewCell.swift
@@ -108,6 +108,8 @@ extension AddAccountTableViewCell {
separatorLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)),
])
+
+ accessibilityTraits.insert(.button)
}
}
diff --git a/Mastodon/Scene/Account/View/DragIndicatorView.swift b/Mastodon/Scene/Account/View/DragIndicatorView.swift
index 9e0ab77d5..a04d9cd8c 100644
--- a/Mastodon/Scene/Account/View/DragIndicatorView.swift
+++ b/Mastodon/Scene/Account/View/DragIndicatorView.swift
@@ -15,17 +15,17 @@ final class DragIndicatorView: UIView {
let barView = UIView()
let separatorLine = UIView.separatorLine
+ let onDismiss: () -> Void
- override init(frame: CGRect) {
- super.init(frame: frame)
+ init(onDismiss: @escaping () -> Void) {
+ self.onDismiss = onDismiss
+ super.init(frame: .zero)
_init()
}
required init?(coder: NSCoder) {
- super.init(coder: coder)
- _init()
+ fatalError("init(coder:) is not supported")
}
-
}
extension DragIndicatorView {
@@ -52,6 +52,14 @@ extension DragIndicatorView {
separatorLine.bottomAnchor.constraint(equalTo: bottomAnchor),
separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: self)),
])
+
+ isAccessibilityElement = true
+ accessibilityTraits = .button
+ accessibilityLabel = L10n.Scene.AccountList.dismissAccountSwitcher
}
+ override func accessibilityActivate() -> Bool {
+ self.onDismiss()
+ return true
+ }
}
diff --git a/Mastodon/Scene/Compose/ComposeViewController.swift b/Mastodon/Scene/Compose/ComposeViewController.swift
index bf9145d6c..ca33487ab 100644
--- a/Mastodon/Scene/Compose/ComposeViewController.swift
+++ b/Mastodon/Scene/Compose/ComposeViewController.swift
@@ -44,18 +44,6 @@ final class ComposeViewController: UIViewController, NeedsDependency {
}()
private(set) lazy var cancelBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .plain, target: self, action: #selector(ComposeViewController.cancelBarButtonItemPressed(_:)))
- let characterCountLabel: UILabel = {
- let label = UILabel()
- label.font = .systemFont(ofSize: 15, weight: .regular)
- label.text = "500"
- label.textColor = Asset.Colors.Label.secondary.color
- label.accessibilityLabel = L10n.A11y.Plural.Count.inputLimitRemains(500)
- return label
- }()
- private(set) lazy var characterCountBarButtonItem: UIBarButtonItem = {
- let barButtonItem = UIBarButtonItem(customView: characterCountLabel)
- return barButtonItem
- }()
let publishButton: UIButton = {
let button = RoundedEdgesButton(type: .custom)
@@ -70,12 +58,7 @@ final class ComposeViewController: UIViewController, NeedsDependency {
let shadowBackgroundContainer = ShadowBackgroundContainer()
publishButton.translatesAutoresizingMaskIntoConstraints = false
shadowBackgroundContainer.addSubview(publishButton)
- NSLayoutConstraint.activate([
- publishButton.topAnchor.constraint(equalTo: shadowBackgroundContainer.topAnchor),
- publishButton.leadingAnchor.constraint(equalTo: shadowBackgroundContainer.leadingAnchor),
- publishButton.trailingAnchor.constraint(equalTo: shadowBackgroundContainer.trailingAnchor),
- publishButton.bottomAnchor.constraint(equalTo: shadowBackgroundContainer.bottomAnchor),
- ])
+ publishButton.pinToParent()
let barButtonItem = UIBarButtonItem(customView: shadowBackgroundContainer)
return barButtonItem
}()
@@ -86,54 +69,13 @@ final class ComposeViewController: UIViewController, NeedsDependency {
publishButton.setBackgroundImage(.placeholder(color: Asset.Colors.Button.disabled.color), for: .disabled)
publishButton.setTitleColor(Asset.Colors.Label.primaryReverse.color, for: .normal)
}
-
-// var systemKeyboardHeight: CGFloat = .zero {
-// didSet {
-// // note: some system AutoLayout warning here
-// let height = max(300, systemKeyboardHeight)
-// customEmojiPickerInputView.frame.size.height = height
-// }
-// }
-//
-// // CustomEmojiPickerView
-// let customEmojiPickerInputView: CustomEmojiPickerInputView = {
-// let view = CustomEmojiPickerInputView(frame: CGRect(x: 0, y: 0, width: 0, height: 300), inputViewStyle: .keyboard)
-// return view
-// }()
-//
-// let composeToolbarView = ComposeToolbarView()
-// var composeToolbarViewBottomLayoutConstraint: NSLayoutConstraint!
-// let composeToolbarBackgroundView = UIView()
-//
-//
-// private(set) lazy var autoCompleteViewController: AutoCompleteViewController = {
-// let viewController = AutoCompleteViewController()
-// viewController.viewModel = AutoCompleteViewModel(context: context, authContext: viewModel.authContext)
-// viewController.delegate = self
-// viewController.viewModel.customEmojiViewModel.value = viewModel.customEmojiViewModel
-// return viewController
-// }()
-
+
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
}
}
-extension ComposeViewController {
- private static func createLayout() -> UICollectionViewLayout {
- let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))
- let item = NSCollectionLayoutItem(layoutSize: itemSize)
- let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))
- let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
- let section = NSCollectionLayoutSection(group: group)
- section.contentInsetsReference = .readableContent
- // section.interGroupSpacing = 10
- // section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
- return UICollectionViewCompositionalLayout(section: section)
- }
-}
-
extension ComposeViewController {
override func viewDidLoad() {
@@ -146,10 +88,7 @@ extension ComposeViewController {
.sink { [weak self] _ in
guard let self = self else { return }
guard self.traitCollection.userInterfaceIdiom == .pad else { return }
- var items = [self.publishBarButtonItem]
- if self.traitCollection.horizontalSizeClass == .regular {
- items.append(self.characterCountBarButtonItem)
- }
+ let items = [self.publishBarButtonItem]
self.navigationItem.rightBarButtonItems = items
}
.store(in: &disposeBag)
@@ -158,466 +97,74 @@ extension ComposeViewController {
addChild(composeContentViewController)
composeContentViewController.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(composeContentViewController.view)
- NSLayoutConstraint.activate([
- composeContentViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
- composeContentViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- composeContentViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
- composeContentViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
- ])
+ composeContentViewController.view.pinToParent()
composeContentViewController.didMove(toParent: self)
-// configureNavigationBarTitleStyle()
-// viewModel.traitCollectionDidChangePublisher
-// .receive(on: DispatchQueue.main)
-// .sink { [weak self] _ in
-// guard let self = self else { return }
-// self.configureNavigationBarTitleStyle()
-// }
-// .store(in: &disposeBag)
-//
-// viewModel.$title
-// .receive(on: DispatchQueue.main)
-// .sink { [weak self] title in
-// guard let self = self else { return }
-// self.title = title
-// }
-// .store(in: &disposeBag)
-//
-// composeToolbarView.translatesAutoresizingMaskIntoConstraints = false
-// view.addSubview(composeToolbarView)
-// composeToolbarViewBottomLayoutConstraint = view.bottomAnchor.constraint(equalTo: composeToolbarView.bottomAnchor)
-// NSLayoutConstraint.activate([
-// composeToolbarView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
-// composeToolbarView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
-// composeToolbarViewBottomLayoutConstraint,
-// composeToolbarView.heightAnchor.constraint(equalToConstant: ComposeToolbarView.toolbarHeight),
-// ])
-// composeToolbarView.preservesSuperviewLayoutMargins = true
-// composeToolbarView.delegate = self
-//
-// composeToolbarBackgroundView.translatesAutoresizingMaskIntoConstraints = false
-// view.insertSubview(composeToolbarBackgroundView, belowSubview: composeToolbarView)
-// NSLayoutConstraint.activate([
-// composeToolbarBackgroundView.topAnchor.constraint(equalTo: composeToolbarView.topAnchor),
-// composeToolbarBackgroundView.leadingAnchor.constraint(equalTo: composeToolbarView.leadingAnchor),
-// composeToolbarBackgroundView.trailingAnchor.constraint(equalTo: composeToolbarView.trailingAnchor),
-// view.bottomAnchor.constraint(equalTo: composeToolbarBackgroundView.bottomAnchor),
-// ])
+ // bind title
+ viewModel.$title
+ .receive(on: DispatchQueue.main)
+ .sink { [weak self] title in
+ guard let self = self else { return }
+ self.title = title
+ }
+ .store(in: &disposeBag)
-// tableView.delegate = self
-// viewModel.setupDataSource(
-// tableView: tableView,
-// metaTextDelegate: self,
-// metaTextViewDelegate: self,
-// customEmojiPickerInputViewModel: viewModel.customEmojiPickerInputViewModel,
-// composeStatusAttachmentCollectionViewCellDelegate: self,
-// composeStatusPollOptionCollectionViewCellDelegate: self,
-// composeStatusPollOptionAppendEntryCollectionViewCellDelegate: self,
-// composeStatusPollExpiresOptionCollectionViewCellDelegate: self
-// )
-
-// viewModel.composeStatusAttribute.$composeContent
-// .removeDuplicates()
-// .receive(on: DispatchQueue.main)
-// .sink { [weak self] _ in
-// guard let self = self else { return }
-// guard self.view.window != nil else { return }
-// UIView.performWithoutAnimation {
-// self.tableView.beginUpdates()
-// self.tableView.setNeedsLayout()
-// self.tableView.layoutIfNeeded()
-// self.tableView.endUpdates()
-// }
-// }
-// .store(in: &disposeBag)
-
-// customEmojiPickerInputView.collectionView.delegate = self
-// viewModel.customEmojiPickerInputViewModel.customEmojiPickerInputView = customEmojiPickerInputView
-// viewModel.setupCustomEmojiPickerDiffableDataSource(
-// for: customEmojiPickerInputView.collectionView,
-// dependency: self
-// )
-
-// viewModel.composeStatusContentTableViewCell.delegate = self
-//
-// // update layout when keyboard show/dismiss
-// view.layoutIfNeeded()
-//
-
-//
-// // bind auto-complete
-// viewModel.$autoCompleteInfo
-// .receive(on: DispatchQueue.main)
-// .sink { [weak self] info in
-// guard let self = self else { return }
-// let textEditorView = self.textEditorView
-// if self.autoCompleteViewController.view.superview == nil {
-// self.autoCompleteViewController.view.frame = self.view.bounds
-// // add to container view. seealso: `viewDidLayoutSubviews()`
-// self.viewModel.composeStatusContentTableViewCell.textEditorViewContainerView.addSubview(self.autoCompleteViewController.view)
-// self.addChild(self.autoCompleteViewController)
-// self.autoCompleteViewController.didMove(toParent: self)
-// self.autoCompleteViewController.view.isHidden = true
-// self.tableView.autoCompleteViewController = self.autoCompleteViewController
-// }
-// self.updateAutoCompleteViewControllerLayout()
-// self.autoCompleteViewController.view.isHidden = info == nil
-// guard let info = info else { return }
-// let symbolBoundingRectInContainer = textEditorView.textView.convert(info.symbolBoundingRect, to: self.autoCompleteViewController.chevronView)
-// self.autoCompleteViewController.view.frame.origin.y = info.textBoundingRect.maxY
-// self.autoCompleteViewController.viewModel.symbolBoundingRect.value = symbolBoundingRectInContainer
-// self.autoCompleteViewController.viewModel.inputText.value = String(info.inputText)
-// }
-// .store(in: &disposeBag)
-//
-// // bind publish bar button state
-// viewModel.$isPublishBarButtonItemEnabled
-// .receive(on: DispatchQueue.main)
-// .assign(to: \.isEnabled, on: publishButton)
-// .store(in: &disposeBag)
-//
-// // bind media button toolbar state
-// viewModel.$isMediaToolbarButtonEnabled
-// .receive(on: DispatchQueue.main)
-// .sink { [weak self] isMediaToolbarButtonEnabled in
-// guard let self = self else { return }
-// self.composeToolbarView.mediaBarButtonItem.isEnabled = isMediaToolbarButtonEnabled
-// self.composeToolbarView.mediaButton.isEnabled = isMediaToolbarButtonEnabled
-// }
-// .store(in: &disposeBag)
-//
-// // bind poll button toolbar state
-// viewModel.$isPollToolbarButtonEnabled
-// .receive(on: DispatchQueue.main)
-// .sink { [weak self] isPollToolbarButtonEnabled in
-// guard let self = self else { return }
-// self.composeToolbarView.pollBarButtonItem.isEnabled = isPollToolbarButtonEnabled
-// self.composeToolbarView.pollButton.isEnabled = isPollToolbarButtonEnabled
-// }
-// .store(in: &disposeBag)
-//
-// Publishers.CombineLatest(
-// viewModel.$isPollComposing,
-// viewModel.$isPollToolbarButtonEnabled
-// )
-// .receive(on: DispatchQueue.main)
-// .sink { [weak self] isPollComposing, isPollToolbarButtonEnabled in
-// guard let self = self else { return }
-// guard isPollToolbarButtonEnabled else {
-// let accessibilityLabel = L10n.Scene.Compose.Accessibility.appendPoll
-// self.composeToolbarView.pollBarButtonItem.accessibilityLabel = accessibilityLabel
-// self.composeToolbarView.pollButton.accessibilityLabel = accessibilityLabel
-// return
-// }
-// let accessibilityLabel = isPollComposing ? L10n.Scene.Compose.Accessibility.removePoll : L10n.Scene.Compose.Accessibility.appendPoll
-// self.composeToolbarView.pollBarButtonItem.accessibilityLabel = accessibilityLabel
-// self.composeToolbarView.pollButton.accessibilityLabel = accessibilityLabel
-// }
-// .store(in: &disposeBag)
-//
-// // bind image picker toolbar state
-// viewModel.$attachmentServices
-// .receive(on: DispatchQueue.main)
-// .sink { [weak self] attachmentServices in
-// guard let self = self else { return }
-// let isEnabled = attachmentServices.count < self.viewModel.maxMediaAttachments
-// self.composeToolbarView.mediaBarButtonItem.isEnabled = isEnabled
-// self.composeToolbarView.mediaButton.isEnabled = isEnabled
-// self.resetImagePicker()
-// }
-// .store(in: &disposeBag)
-//
-// // bind content warning button state
-// viewModel.$isContentWarningComposing
-// .receive(on: DispatchQueue.main)
-// .sink { [weak self] isContentWarningComposing in
-// guard let self = self else { return }
-// let accessibilityLabel = isContentWarningComposing ? L10n.Scene.Compose.Accessibility.disableContentWarning : L10n.Scene.Compose.Accessibility.enableContentWarning
-// self.composeToolbarView.contentWarningBarButtonItem.accessibilityLabel = accessibilityLabel
-// self.composeToolbarView.contentWarningButton.accessibilityLabel = accessibilityLabel
-// }
-// .store(in: &disposeBag)
-//
-// // bind visibility toolbar UI
-// Publishers.CombineLatest(
-// viewModel.$selectedStatusVisibility,
-// viewModel.traitCollectionDidChangePublisher
-// )
-// .receive(on: DispatchQueue.main)
-// .sink { [weak self] type, _ in
-// guard let self = self else { return }
-// let image = type.image(interfaceStyle: self.traitCollection.userInterfaceStyle)
-// self.composeToolbarView.visibilityBarButtonItem.image = image
-// self.composeToolbarView.visibilityButton.setImage(image, for: .normal)
-// self.composeToolbarView.activeVisibilityType.value = type
-// }
-// .store(in: &disposeBag)
-//
-// viewModel.$characterCount
-// .receive(on: DispatchQueue.main)
-// .sink { [weak self] characterCount in
-// guard let self = self else { return }
-// let count = self.viewModel.composeContentLimit - characterCount
-// self.composeToolbarView.characterCountLabel.text = "\(count)"
-// self.characterCountLabel.text = "\(count)"
-// let font: UIFont
-// let textColor: UIColor
-// let accessibilityLabel: String
-// switch count {
-// case _ where count < 0:
-// font = .monospacedDigitSystemFont(ofSize: 24, weight: .bold)
-// textColor = Asset.Colors.danger.color
-// accessibilityLabel = L10n.A11y.Plural.Count.inputLimitExceeds(abs(count))
-// default:
-// font = .monospacedDigitSystemFont(ofSize: 15, weight: .regular)
-// textColor = Asset.Colors.Label.secondary.color
-// accessibilityLabel = L10n.A11y.Plural.Count.inputLimitRemains(count)
-// }
-// self.composeToolbarView.characterCountLabel.font = font
-// self.composeToolbarView.characterCountLabel.textColor = textColor
-// self.composeToolbarView.characterCountLabel.accessibilityLabel = accessibilityLabel
-// self.characterCountLabel.font = font
-// self.characterCountLabel.textColor = textColor
-// self.characterCountLabel.accessibilityLabel = accessibilityLabel
-// self.characterCountLabel.sizeToFit()
-// }
-// .store(in: &disposeBag)
-//
-// // bind custom emoji picker UI
-// viewModel.customEmojiViewModel?.emojis
-// .receive(on: DispatchQueue.main)
-// .sink(receiveValue: { [weak self] emojis in
-// guard let self = self else { return }
-// if emojis.isEmpty {
-// self.customEmojiPickerInputView.activityIndicatorView.startAnimating()
-// } else {
-// self.customEmojiPickerInputView.activityIndicatorView.stopAnimating()
-// }
-// })
-// .store(in: &disposeBag)
-//
-// configureToolbarDisplay(keyboardHasShortcutBar: keyboardHasShortcutBar.value)
-// Publishers.CombineLatest(
-// keyboardHasShortcutBar,
-// viewModel.traitCollectionDidChangePublisher
-// )
-// .receive(on: DispatchQueue.main)
-// .sink { [weak self] keyboardHasShortcutBar, _ in
-// guard let self = self else { return }
-// self.configureToolbarDisplay(keyboardHasShortcutBar: keyboardHasShortcutBar)
-// }
-// .store(in: &disposeBag)
- }
-
- override func viewWillAppear(_ animated: Bool) {
- super.viewWillAppear(animated)
-
-// // update MetaText without trigger call underlaying `UITextStorage.processEditing`
-// _ = textEditorView.processEditing(textEditorView.textStorage)
-
-// markTextEditorViewBecomeFirstResponser()
- }
-
- override func viewDidAppear(_ animated: Bool) {
- super.viewDidAppear(animated)
-
-// viewModel.isViewAppeared = true
+ // bind publish bar button state
+ composeContentViewModel.$isPublishBarButtonItemEnabled
+ .receive(on: DispatchQueue.main)
+ .assign(to: \.isEnabled, on: publishButton)
+ .store(in: &disposeBag)
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
-// configurePublishButtonApperance()
-// viewModel.traitCollectionDidChangePublisher.send()
- }
-
- override func viewDidLayoutSubviews() {
- super.viewDidLayoutSubviews()
-
- updateAutoCompleteViewControllerLayout()
- }
-
- private func updateAutoCompleteViewControllerLayout() {
- // pin autoCompleteViewController frame to current view
-// if let containerView = autoCompleteViewController.view.superview {
-// let viewFrameInWindow = containerView.convert(autoCompleteViewController.view.frame, to: view)
-// if viewFrameInWindow.origin.x != 0 {
-// autoCompleteViewController.view.frame.origin.x = -viewFrameInWindow.origin.x
-// }
-// autoCompleteViewController.view.frame.size.width = view.frame.width
-// }
+ configurePublishButtonApperance()
+ viewModel.traitCollectionDidChangePublisher.send()
}
}
-//extension ComposeViewController {
-//
-// private var textEditorView: MetaText {
-// return viewModel.composeStatusContentTableViewCell.metaText
-// }
-//
-// private func markTextEditorViewBecomeFirstResponser() {
-// textEditorView.textView.becomeFirstResponder()
-// }
-//
-// private func contentWarningEditorTextView() -> UITextView? {
-// viewModel.composeStatusContentTableViewCell.statusContentWarningEditorView.textView
-// }
-//
-// private func pollOptionCollectionViewCell(of item: ComposeStatusPollItem) -> ComposeStatusPollOptionCollectionViewCell? {
-// guard case .pollOption = item else { return nil }
-// guard let dataSource = viewModel.composeStatusPollTableViewCell.dataSource else { return nil }
-// guard let indexPath = dataSource.indexPath(for: item),
-// let cell = viewModel.composeStatusPollTableViewCell.collectionView.cellForItem(at: indexPath) as? ComposeStatusPollOptionCollectionViewCell else {
-// return nil
-// }
-//
-// return cell
-// }
-//
-// private func firstPollOptionCollectionViewCell() -> ComposeStatusPollOptionCollectionViewCell? {
-// guard let dataSource = viewModel.composeStatusPollTableViewCell.dataSource else { return nil }
-// let items = dataSource.snapshot().itemIdentifiers(inSection: .main)
-// let firstPollItem = items.first { item -> Bool in
-// guard case .pollOption = item else { return false }
-// return true
-// }
-//
-// guard let item = firstPollItem else {
-// return nil
-// }
-//
-// return pollOptionCollectionViewCell(of: item)
-// }
-//
-// private func lastPollOptionCollectionViewCell() -> ComposeStatusPollOptionCollectionViewCell? {
-// guard let dataSource = viewModel.composeStatusPollTableViewCell.dataSource else { return nil }
-// let items = dataSource.snapshot().itemIdentifiers(inSection: .main)
-// let lastPollItem = items.last { item -> Bool in
-// guard case .pollOption = item else { return false }
-// return true
-// }
-//
-// guard let item = lastPollItem else {
-// return nil
-// }
-//
-// return pollOptionCollectionViewCell(of: item)
-// }
-//
-// private func markFirstPollOptionCollectionViewCellBecomeFirstResponser() {
-// guard let cell = firstPollOptionCollectionViewCell() else { return }
-// cell.pollOptionView.optionTextField.becomeFirstResponder()
-// }
-//
-// private func markLastPollOptionCollectionViewCellBecomeFirstResponser() {
-// guard let cell = lastPollOptionCollectionViewCell() else { return }
-// cell.pollOptionView.optionTextField.becomeFirstResponder()
-// }
-//
-// private func showDismissConfirmAlertController() {
-// let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
-// let discardAction = UIAlertAction(title: L10n.Common.Controls.Actions.discard, style: .destructive) { [weak self] _ in
-// guard let self = self else { return }
-// self.dismiss(animated: true, completion: nil)
-// }
-// alertController.addAction(discardAction)
-// let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel)
-// alertController.addAction(cancelAction)
-// alertController.popoverPresentationController?.barButtonItem = cancelBarButtonItem
-// present(alertController, animated: true, completion: nil)
-// }
-//
-// private func resetImagePicker() {
-// let selectionLimit = max(1, viewModel.maxMediaAttachments - viewModel.attachmentServices.count)
-// let configuration = ComposeViewController.createPhotoLibraryPickerConfiguration(selectionLimit: selectionLimit)
-// photoLibraryPicker = createImagePicker(configuration: configuration)
-// }
-//
-// private func createImagePicker(configuration: PHPickerConfiguration) -> PHPickerViewController {
-// let imagePicker = PHPickerViewController(configuration: configuration)
-// imagePicker.delegate = self
-// return imagePicker
-// }
-//
-// private func setupBackgroundColor(theme: Theme) {
-// let backgroundColor = UIColor(dynamicProvider: { traitCollection in
-// switch traitCollection.userInterfaceStyle {
-// case .light:
-// return .systemBackground
-// default:
-// return theme.systemElevatedBackgroundColor
-// }
-// })
-// view.backgroundColor = backgroundColor
-//// tableView.backgroundColor = backgroundColor
-//// composeToolbarBackgroundView.backgroundColor = theme.composeToolbarBackgroundColor
-// }
-//
-// // keyboard shortcutBar
-// private func setupInputAssistantItem(item: UITextInputAssistantItem) {
-// let barButtonItems = [
-// composeToolbarView.mediaBarButtonItem,
-// composeToolbarView.pollBarButtonItem,
-// composeToolbarView.contentWarningBarButtonItem,
-// composeToolbarView.visibilityBarButtonItem,
-// ]
-// let group = UIBarButtonItemGroup(barButtonItems: barButtonItems, representativeItem: nil)
-//
-// item.trailingBarButtonGroups = [group]
-// }
-//
-// private func configureToolbarDisplay(keyboardHasShortcutBar: Bool) {
-// switch self.traitCollection.userInterfaceIdiom {
-// case .pad:
-// let shouldHideToolbar = keyboardHasShortcutBar && self.traitCollection.horizontalSizeClass == .regular
-// self.composeToolbarView.alpha = shouldHideToolbar ? 0 : 1
-// self.composeToolbarBackgroundView.alpha = shouldHideToolbar ? 0 : 1
-// default:
-// break
-// }
-// }
-//
-// private func configureNavigationBarTitleStyle() {
-// switch traitCollection.userInterfaceIdiom {
-// case .pad:
-// navigationController?.navigationBar.prefersLargeTitles = traitCollection.horizontalSizeClass == .regular
-// default:
-// break
-// }
-// }
-//
-//}
-//
+extension ComposeViewController {
+
+ private func showDismissConfirmAlertController() {
+ let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
+ let discardAction = UIAlertAction(title: L10n.Common.Controls.Actions.discard, style: .destructive) { [weak self] _ in
+ guard let self = self else { return }
+ self.dismiss(animated: true, completion: nil)
+ }
+ alertController.addAction(discardAction)
+ let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel)
+ alertController.addAction(cancelAction)
+ alertController.popoverPresentationController?.barButtonItem = cancelBarButtonItem
+ present(alertController, animated: true, completion: nil)
+ }
+
+}
+
extension ComposeViewController {
@objc private func cancelBarButtonItemPressed(_ sender: UIBarButtonItem) {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
-// guard viewModel.shouldDismiss else {
-// showDismissConfirmAlertController()
-// return
-// }
+ guard composeContentViewModel.shouldDismiss else {
+ showDismissConfirmAlertController()
+ return
+ }
dismiss(animated: true, completion: nil)
}
@objc private func publishBarButtonItemPressed(_ sender: UIBarButtonItem) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
-// do {
-// try viewModel.checkAttachmentPrecondition()
-// } 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)
-// coordinator.present(scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil))
-// return
-// }
-// guard viewModel.publishStateMachine.enter(ComposeViewModel.PublishState.Publishing.self) else {
-// // TODO: handle error
-// return
-// }
-
- // context.statusPublishService.publish(composeViewModel: viewModel)
+ do {
+ try composeContentViewModel.checkAttachmentPrecondition()
+ } 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)
+ _ = coordinator.present(scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil))
+ return
+ }
do {
let statusPublisher = try composeContentViewModel.statusPublisher()
@@ -640,249 +187,35 @@ extension ComposeViewController {
}
-//// MARK: - MetaTextDelegate
-//extension ComposeViewController: MetaTextDelegate {
-// func metaText(_ metaText: MetaText, processEditing textStorage: MetaTextStorage) -> MetaContent? {
-// let string = metaText.textStorage.string
-// let content = MastodonContent(
-// content: string,
-// emojis: viewModel.customEmojiViewModel?.emojiMapping.value ?? [:]
-// )
-// let metaContent = MastodonMetaContent.convert(text: content)
-// return metaContent
-// }
-//}
-//
-//// MARK: - UITextViewDelegate
-//extension ComposeViewController: UITextViewDelegate {
-//
-// func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
-// setupInputAssistantItem(item: textView.inputAssistantItem)
-// return true
-// }
-//
-// func textViewDidChange(_ textView: UITextView) {
-// switch textView {
-// case textEditorView.textView:
-// // update model
-// let metaText = self.textEditorView
-// let backedString = metaText.backedString
-// viewModel.composeStatusAttribute.composeContent = backedString
-// logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(backedString)")
-//
-// // configure auto completion
-// setupAutoComplete(for: textView)
-// default:
-// assertionFailure()
-// }
-// }
-//
-// struct AutoCompleteInfo {
-// // model
-// let inputText: Substring
-// // range
-// let symbolRange: Range
-// let symbolString: Substring
-// let toCursorRange: Range
-// let toCursorString: Substring
-// let toHighlightEndRange: Range
-// let toHighlightEndString: Substring
-// // geometry
-// var textBoundingRect: CGRect = .zero
-// var symbolBoundingRect: CGRect = .zero
-// }
-//
-// private func setupAutoComplete(for textView: UITextView) {
-// guard var autoCompletion = ComposeViewController.scanAutoCompleteInfo(textView: textView) else {
-// viewModel.autoCompleteInfo = nil
-// return
-// }
-// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: auto complete %s (%s)", ((#file as NSString).lastPathComponent), #line, #function, String(autoCompletion.toHighlightEndString), String(autoCompletion.toCursorString))
-//
-// // get layout text bounding rect
-// var glyphRange = NSRange()
-// textView.layoutManager.characterRange(forGlyphRange: NSRange(autoCompletion.toCursorRange, in: textView.text), actualGlyphRange: &glyphRange)
-// let textContainer = textView.layoutManager.textContainers[0]
-// let textBoundingRect = textView.layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
-//
-// let retryLayoutTimes = viewModel.autoCompleteRetryLayoutTimes
-// guard textBoundingRect.size != .zero else {
-// viewModel.autoCompleteRetryLayoutTimes += 1
-// // avoid infinite loop
-// guard retryLayoutTimes < 3 else { return }
-// // needs retry calculate layout when the rect position changing
-// DispatchQueue.main.async {
-// self.setupAutoComplete(for: textView)
-// }
-// return
-// }
-// viewModel.autoCompleteRetryLayoutTimes = 0
-//
-// // get symbol bounding rect
-// textView.layoutManager.characterRange(forGlyphRange: NSRange(autoCompletion.symbolRange, in: textView.text), actualGlyphRange: &glyphRange)
-// let symbolBoundingRect = textView.layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
-//
-// // set bounding rect and trigger layout
-// autoCompletion.textBoundingRect = textBoundingRect
-// autoCompletion.symbolBoundingRect = symbolBoundingRect
-// viewModel.autoCompleteInfo = autoCompletion
-// }
-//
-// private static func scanAutoCompleteInfo(textView: UITextView) -> AutoCompleteInfo? {
-// guard let text = textView.text,
-// textView.selectedRange.location > 0, !text.isEmpty,
-// let selectedRange = Range(textView.selectedRange, in: text) else {
-// return nil
-// }
-// let cursorIndex = selectedRange.upperBound
-// let _highlightStartIndex: String.Index? = {
-// var index = text.index(before: cursorIndex)
-// while index > text.startIndex {
-// let char = text[index]
-// if char == "@" || char == "#" || char == ":" {
-// return index
-// }
-// index = text.index(before: index)
-// }
-// assert(index == text.startIndex)
-// let char = text[index]
-// if char == "@" || char == "#" || char == ":" {
-// return index
-// } else {
-// return nil
-// }
-// }()
-//
-// guard let highlightStartIndex = _highlightStartIndex else { return nil }
-// let scanRange = NSRange(highlightStartIndex..= cursorIndex else { return nil }
-// let symbolRange = highlightStartIndex.. Bool {
-// switch textView {
-// case textEditorView.textView:
-// return false
-// default:
-// return true
-// }
-// }
-//
-// func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
-// switch textView {
-// case textEditorView.textView:
-// return false
-// default:
-// return true
-// }
-// }
-//
-//}
-//
-//// MARK: - ComposeToolbarViewDelegate
-//extension ComposeViewController: ComposeToolbarViewDelegate {
+extension ComposeViewController {
+ public override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
+
+ // Enable pasting images
+ if (action == #selector(UIResponderStandardEditActions.paste(_:))) {
+ return UIPasteboard.general.hasStrings || UIPasteboard.general.hasImages;
+ }
-// func composeToolbarView(_ composeToolbarView: ComposeToolbarView, pollButtonDidPressed sender: Any) {
-// // toggle poll composing state
-// viewModel.isPollComposing.toggle()
-//
-// // cancel custom picker input
-// viewModel.isCustomEmojiComposing = false
-//
-// // setup initial poll option if needs
-// if viewModel.isPollComposing, viewModel.pollOptionAttributes.isEmpty {
-// viewModel.pollOptionAttributes = [ComposeStatusPollItem.PollOptionAttribute(), ComposeStatusPollItem.PollOptionAttribute()]
-// }
-//
-// if viewModel.isPollComposing {
-// // Magic RunLoop
-// DispatchQueue.main.async {
-// self.markFirstPollOptionCollectionViewCellBecomeFirstResponser()
-// }
-// } else {
-// markTextEditorViewBecomeFirstResponser()
-// }
-// }
-//
-// func composeToolbarView(_ composeToolbarView: ComposeToolbarView, emojiButtonDidPressed sender: Any) {
-// viewModel.isCustomEmojiComposing.toggle()
-// }
-//
-// func composeToolbarView(_ composeToolbarView: ComposeToolbarView, contentWarningButtonDidPressed sender: Any) {
-// // cancel custom picker input
-// viewModel.isCustomEmojiComposing = false
-//
-// // restore first responder for text editor when content warning dismiss
-// if viewModel.isContentWarningComposing {
-// if contentWarningEditorTextView()?.isFirstResponder == true {
-// markTextEditorViewBecomeFirstResponser()
-// }
-// }
-//
-// // toggle composing status
-// viewModel.isContentWarningComposing.toggle()
-//
-// // active content warning after toggled
-// if viewModel.isContentWarningComposing {
-// contentWarningEditorTextView()?.becomeFirstResponder()
-// }
-// }
-//
-// func composeToolbarView(_ composeToolbarView: ComposeToolbarView, visibilityButtonDidPressed sender: Any, visibilitySelectionType type: ComposeToolbarView.VisibilitySelectionType) {
-// viewModel.selectedStatusVisibility = type
-// }
-//
-//}
+ return super.canPerformAction(action, withSender: sender);
+ }
+
+ override func paste(_ sender: Any?) {
+ logger.debug("Paste event received")
-//// MARK: - UITableViewDelegate
-//extension ComposeViewController: UITableViewDelegate { }
-//
-//// MARK: - UICollectionViewDelegate
-//extension ComposeViewController: UICollectionViewDelegate {
-//
-// func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
-// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: select %s", ((#file as NSString).lastPathComponent), #line, #function, indexPath.debugDescription)
-//
-// if collectionView === customEmojiPickerInputView.collectionView {
-// guard let diffableDataSource = viewModel.customEmojiPickerDiffableDataSource else { return }
-// let item = diffableDataSource.itemIdentifier(for: indexPath)
-// guard case let .emoji(attribute) = item else { return }
-// let emoji = attribute.emoji
-//
-// // make click sound
-// UIDevice.current.playInputClick()
-//
-// // retrieve active text input and insert emoji
-// // the trailing space is REQUIRED to make regex happy
-// _ = viewModel.customEmojiPickerInputViewModel.insertText(":\(emoji.shortcode): ")
-// } else {
-// // do nothing
-// }
-// }
-//}
+ // Look for images on the clipboard
+ if UIPasteboard.general.hasImages, let images = UIPasteboard.general.images {
+ logger.warning("Got image paste event, however attachments are not yet re-implemented.");
+ let attachmentViewModels = images.map { image in
+ return AttachmentViewModel(
+ api: viewModel.context.apiService,
+ authContext: viewModel.authContext,
+ input: .image(image),
+ delegate: composeContentViewModel
+ )
+ }
+ composeContentViewModel.attachmentViewModels += attachmentViewModels
+ }
+ }
+}
// MARK: - UIAdaptivePresentationControllerDelegate
extension ComposeViewController: UIAdaptivePresentationControllerDelegate {
@@ -895,15 +228,15 @@ extension ComposeViewController: UIAdaptivePresentationControllerDelegate {
return .pageSheet
}
}
-
-// func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
-// return viewModel.shouldDismiss
-// }
-// func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
-// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
-// showDismissConfirmAlertController()
-// }
+ func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
+ return composeContentViewModel.shouldDismiss
+ }
+
+ func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
+ os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
+ showDismissConfirmAlertController()
+ }
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
@@ -911,304 +244,137 @@ extension ComposeViewController: UIAdaptivePresentationControllerDelegate {
}
-//// MARK: - ComposeStatusAttachmentTableViewCellDelegate
-//extension ComposeViewController: ComposeStatusAttachmentCollectionViewCellDelegate {
-//
-// func composeStatusAttachmentCollectionViewCell(_ cell: ComposeStatusAttachmentCollectionViewCell, removeButtonDidPressed button: UIButton) {
-// guard let diffableDataSource = viewModel.composeStatusAttachmentTableViewCell.dataSource else { return }
-// guard let indexPath = viewModel.composeStatusAttachmentTableViewCell.collectionView.indexPath(for: cell) else { return }
-// guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
-// guard case let .attachment(attachmentService) = item else { return }
-//
-// var attachmentServices = viewModel.attachmentServices
-// guard let index = attachmentServices.firstIndex(of: attachmentService) else { return }
-// let removedItem = attachmentServices[index]
-// attachmentServices.remove(at: index)
-// viewModel.attachmentServices = attachmentServices
-//
-// // cancel task
-// removedItem.disposeBag.removeAll()
-// }
-//
-//}
-//
-//// MARK: - ComposeStatusPollOptionCollectionViewCellDelegate
-//extension ComposeViewController: ComposeStatusPollOptionCollectionViewCellDelegate {
-//
-// func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, textFieldDidBeginEditing textField: UITextField) {
-//
-// setupInputAssistantItem(item: textField.inputAssistantItem)
-//
-// // FIXME: make poll section visible
-// // DispatchQueue.main.async {
-// // self.collectionView.scroll(to: .bottom, animated: true)
-// // }
-// }
-//
-//
-// // handle delete backward event for poll option input
-// func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, textBeforeDeleteBackward text: String?) {
-// guard (text ?? "").isEmpty else { return }
-// guard let dataSource = viewModel.composeStatusPollTableViewCell.dataSource else { return }
-// guard let indexPath = viewModel.composeStatusPollTableViewCell.collectionView.indexPath(for: cell) else { return }
-// guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
-// guard case let .pollOption(attribute) = item else { return }
-//
-// var pollAttributes = viewModel.pollOptionAttributes
-// guard let index = pollAttributes.firstIndex(of: attribute) else { return }
-//
-// // mark previous (fallback to next) item of removed middle poll option become first responder
-// let pollItems = dataSource.snapshot().itemIdentifiers(inSection: .main)
-// if let indexOfItem = pollItems.firstIndex(of: item), index > 0 {
-// func cellBeforeRemoved() -> ComposeStatusPollOptionCollectionViewCell? {
-// guard index > 0 else { return nil }
-// let indexBeforeRemoved = pollItems.index(before: indexOfItem)
-// let itemBeforeRemoved = pollItems[indexBeforeRemoved]
-// return pollOptionCollectionViewCell(of: itemBeforeRemoved)
-// }
-//
-// func cellAfterRemoved() -> ComposeStatusPollOptionCollectionViewCell? {
-// guard index < pollItems.count - 1 else { return nil }
-// let indexAfterRemoved = pollItems.index(after: index)
-// let itemAfterRemoved = pollItems[indexAfterRemoved]
-// return pollOptionCollectionViewCell(of: itemAfterRemoved)
-// }
-//
-// var cell: ComposeStatusPollOptionCollectionViewCell? = cellBeforeRemoved()
-// if cell == nil {
-// cell = cellAfterRemoved()
-// }
-// cell?.pollOptionView.optionTextField.becomeFirstResponder()
-// }
-//
-// guard pollAttributes.count > 2 else {
-// return
-// }
-// pollAttributes.remove(at: index)
-//
-// // update data source
-// viewModel.pollOptionAttributes = pollAttributes
-// }
-//
-// // handle keyboard return event for poll option input
-// func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, pollOptionTextFieldDidReturn: UITextField) {
-// guard let dataSource = viewModel.composeStatusPollTableViewCell.dataSource else { return }
-// guard let indexPath = viewModel.composeStatusPollTableViewCell.collectionView.indexPath(for: cell) else { return }
-// let pollItems = dataSource.snapshot().itemIdentifiers(inSection: .main).filter { item in
-// guard case .pollOption = item else { return false }
-// return true
-// }
-// guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
-// guard let index = pollItems.firstIndex(of: item) else { return }
-//
-// if index == pollItems.count - 1 {
-// // is the last
-// viewModel.createNewPollOptionIfPossible()
-// DispatchQueue.main.async {
-// self.markLastPollOptionCollectionViewCellBecomeFirstResponser()
-// }
-// } else {
-// // not the last
-// let indexAfter = pollItems.index(after: index)
-// let itemAfter = pollItems[indexAfter]
-// let cell = pollOptionCollectionViewCell(of: itemAfter)
-// cell?.pollOptionView.optionTextField.becomeFirstResponder()
-// }
-// }
-//
-//}
-//
-//// MARK: - ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate
-//extension ComposeViewController: ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate {
-// func composeStatusPollOptionAppendEntryCollectionViewCellDidPressed(_ cell: ComposeStatusPollOptionAppendEntryCollectionViewCell) {
-// viewModel.createNewPollOptionIfPossible()
-// DispatchQueue.main.async {
-// self.markLastPollOptionCollectionViewCellBecomeFirstResponser()
-// }
-// }
-//}
-//
-//// MARK: - ComposeStatusPollExpiresOptionCollectionViewCellDelegate
-//extension ComposeViewController: ComposeStatusPollExpiresOptionCollectionViewCellDelegate {
-// func composeStatusPollExpiresOptionCollectionViewCell(_ cell: ComposeStatusPollExpiresOptionCollectionViewCell, didSelectExpiresOption expiresOption: ComposeStatusPollItem.PollExpiresOptionAttribute.ExpiresOption) {
-// viewModel.pollExpiresOptionAttribute.expiresOption.value = expiresOption
-// }
-//}
-//
-//// MARK: - ComposeStatusContentTableViewCellDelegate
-//extension ComposeViewController: ComposeStatusContentTableViewCellDelegate {
-// func composeStatusContentTableViewCell(_ cell: ComposeStatusContentTableViewCell, textViewShouldBeginEditing textView: UITextView) -> Bool {
-// setupInputAssistantItem(item: textView.inputAssistantItem)
-// return true
-// }
-//}
-//
-//// MARK: - AutoCompleteViewControllerDelegate
-//extension ComposeViewController: AutoCompleteViewControllerDelegate {
-// func autoCompleteViewController(_ viewController: AutoCompleteViewController, didSelectItem item: AutoCompleteItem) {
-// guard let info = viewModel.autoCompleteInfo else { return }
-// let _replacedText: String? = {
-// var text: String
-// switch item {
-// case .hashtag(let hashtag):
-// text = "#" + hashtag.name
-// case .hashtagV1(let hashtagName):
-// text = "#" + hashtagName
-// case .account(let account):
-// text = "@" + account.acct
-// case .emoji(let emoji):
-// text = ":" + emoji.shortcode + ":"
-// case .bottomLoader:
-// return nil
-// }
-// return text
-// }()
-// guard let replacedText = _replacedText else { return }
-// guard let text = textEditorView.textView.text else { return }
-//
-// let range = NSRange(info.toHighlightEndRange, in: text)
-// textEditorView.textStorage.replaceCharacters(in: range, with: replacedText)
-// DispatchQueue.main.async {
-// self.textEditorView.textView.insertText(" ") // trigger textView delegate update
-// }
-// viewModel.autoCompleteInfo = nil
-//
-// switch item {
-// case .emoji, .bottomLoader:
-// break
-// default:
-// // set selected range except emoji
-// let newRange = NSRange(location: range.location + (replacedText as NSString).length, length: 0)
-// guard textEditorView.textStorage.length <= newRange.location else { return }
-// textEditorView.textView.selectedRange = newRange
-// }
-// }
-//}
-//
-//extension ComposeViewController {
-// override var keyCommands: [UIKeyCommand]? {
-// composeKeyCommands
-// }
-//}
-//
-//extension ComposeViewController {
-//
-// enum ComposeKeyCommand: String, CaseIterable {
-// case discardPost
-// case publishPost
-// case mediaBrowse
-// case mediaPhotoLibrary
-// case mediaCamera
-// case togglePoll
-// case toggleContentWarning
-// case selectVisibilityPublic
-// // TODO: remove selectVisibilityUnlisted from codebase
-// // case selectVisibilityUnlisted
-// case selectVisibilityPrivate
-// case selectVisibilityDirect
-//
-// var title: String {
-// switch self {
-// case .discardPost: return L10n.Scene.Compose.Keyboard.discardPost
-// case .publishPost: return L10n.Scene.Compose.Keyboard.publishPost
-// case .mediaBrowse: return L10n.Scene.Compose.Keyboard.appendAttachmentEntry(L10n.Scene.Compose.MediaSelection.browse)
-// case .mediaPhotoLibrary: return L10n.Scene.Compose.Keyboard.appendAttachmentEntry(L10n.Scene.Compose.MediaSelection.photoLibrary)
-// case .mediaCamera: return L10n.Scene.Compose.Keyboard.appendAttachmentEntry(L10n.Scene.Compose.MediaSelection.camera)
-// case .togglePoll: return L10n.Scene.Compose.Keyboard.togglePoll
-// case .toggleContentWarning: return L10n.Scene.Compose.Keyboard.toggleContentWarning
-// case .selectVisibilityPublic: return L10n.Scene.Compose.Keyboard.selectVisibilityEntry(L10n.Scene.Compose.Visibility.public)
-// // case .selectVisibilityUnlisted: return L10n.Scene.Compose.Keyboard.selectVisibilityEntry(L10n.Scene.Compose.Visibility.unlisted)
-// case .selectVisibilityPrivate: return L10n.Scene.Compose.Keyboard.selectVisibilityEntry(L10n.Scene.Compose.Visibility.private)
-// case .selectVisibilityDirect: return L10n.Scene.Compose.Keyboard.selectVisibilityEntry(L10n.Scene.Compose.Visibility.direct)
-// }
-// }
-//
-// // UIKeyCommand input
-// var input: String {
-// switch self {
-// case .discardPost: return "w" // + command
-// case .publishPost: return "\r" // (enter) + command
-// case .mediaBrowse: return "b" // + option + command
-// case .mediaPhotoLibrary: return "p" // + option + command
-// case .mediaCamera: return "c" // + option + command
-// case .togglePoll: return "p" // + shift + command
-// case .toggleContentWarning: return "c" // + shift + command
-// case .selectVisibilityPublic: return "1" // + command
-// // case .selectVisibilityUnlisted: return "2" // + command
-// case .selectVisibilityPrivate: return "2" // + command
-// case .selectVisibilityDirect: return "3" // + command
-// }
-// }
-//
-// var modifierFlags: UIKeyModifierFlags {
-// switch self {
-// case .discardPost: return [.command]
-// case .publishPost: return [.command]
-// case .mediaBrowse: return [.alternate, .command]
-// case .mediaPhotoLibrary: return [.alternate, .command]
-// case .mediaCamera: return [.alternate, .command]
-// case .togglePoll: return [.shift, .command]
-// case .toggleContentWarning: return [.shift, .command]
-// case .selectVisibilityPublic: return [.command]
-// // case .selectVisibilityUnlisted: return [.command]
-// case .selectVisibilityPrivate: return [.command]
-// case .selectVisibilityDirect: return [.command]
-// }
-// }
-//
-// var propertyList: Any {
-// return rawValue
-// }
-// }
-//
-// var composeKeyCommands: [UIKeyCommand]? {
-// ComposeKeyCommand.allCases.map { command in
-// UIKeyCommand(
-// title: command.title,
-// image: nil,
-// action: #selector(Self.composeKeyCommandHandler(_:)),
-// input: command.input,
-// modifierFlags: command.modifierFlags,
-// propertyList: command.propertyList,
-// alternates: [],
-// discoverabilityTitle: nil,
-// attributes: [],
-// state: .off
-// )
-// }
-// }
-//
-// @objc private func composeKeyCommandHandler(_ sender: UIKeyCommand) {
-// guard let rawValue = sender.propertyList as? String,
-// let command = ComposeKeyCommand(rawValue: rawValue) else { return }
-//
-// switch command {
-// case .discardPost:
-// cancelBarButtonItemPressed(cancelBarButtonItem)
-// case .publishPost:
-// publishBarButtonItemPressed(publishBarButtonItem)
-// case .mediaBrowse:
-// present(documentPickerController, animated: true, completion: nil)
-// case .mediaPhotoLibrary:
-// present(photoLibraryPicker, animated: true, completion: nil)
-// case .mediaCamera:
-// guard UIImagePickerController.isSourceTypeAvailable(.camera) else {
-// return
-// }
-// present(imagePickerController, animated: true, completion: nil)
-// case .togglePoll:
-// composeToolbarView.pollButton.sendActions(for: .touchUpInside)
-// case .toggleContentWarning:
-// composeToolbarView.contentWarningButton.sendActions(for: .touchUpInside)
-// case .selectVisibilityPublic:
-// viewModel.selectedStatusVisibility = .public
-// // case .selectVisibilityUnlisted:
-// // viewModel.selectedStatusVisibility.value = .unlisted
-// case .selectVisibilityPrivate:
-// viewModel.selectedStatusVisibility = .private
-// case .selectVisibilityDirect:
-// viewModel.selectedStatusVisibility = .direct
-// }
-// }
-//
-//}
+extension ComposeViewController {
+ override var keyCommands: [UIKeyCommand]? {
+ composeKeyCommands
+ }
+}
+
+extension ComposeViewController {
+
+ enum ComposeKeyCommand: String, CaseIterable {
+ case discardPost
+ case publishPost
+ case mediaBrowse
+ case mediaPhotoLibrary
+ case mediaCamera
+ case togglePoll
+ case toggleContentWarning
+ case selectVisibilityPublic
+ // TODO: remove selectVisibilityUnlisted from codebase
+ // case selectVisibilityUnlisted
+ case selectVisibilityPrivate
+ case selectVisibilityDirect
+
+ var title: String {
+ switch self {
+ case .discardPost: return L10n.Scene.Compose.Keyboard.discardPost
+ case .publishPost: return L10n.Scene.Compose.Keyboard.publishPost
+ case .mediaBrowse: return L10n.Scene.Compose.Keyboard.appendAttachmentEntry(L10n.Scene.Compose.MediaSelection.browse)
+ case .mediaPhotoLibrary: return L10n.Scene.Compose.Keyboard.appendAttachmentEntry(L10n.Scene.Compose.MediaSelection.photoLibrary)
+ case .mediaCamera: return L10n.Scene.Compose.Keyboard.appendAttachmentEntry(L10n.Scene.Compose.MediaSelection.camera)
+ case .togglePoll: return L10n.Scene.Compose.Keyboard.togglePoll
+ case .toggleContentWarning: return L10n.Scene.Compose.Keyboard.toggleContentWarning
+ case .selectVisibilityPublic: return L10n.Scene.Compose.Keyboard.selectVisibilityEntry(L10n.Scene.Compose.Visibility.public)
+ // case .selectVisibilityUnlisted: return L10n.Scene.Compose.Keyboard.selectVisibilityEntry(L10n.Scene.Compose.Visibility.unlisted)
+ case .selectVisibilityPrivate: return L10n.Scene.Compose.Keyboard.selectVisibilityEntry(L10n.Scene.Compose.Visibility.private)
+ case .selectVisibilityDirect: return L10n.Scene.Compose.Keyboard.selectVisibilityEntry(L10n.Scene.Compose.Visibility.direct)
+ }
+ }
+
+ // UIKeyCommand input
+ var input: String {
+ switch self {
+ case .discardPost: return "w" // + command
+ case .publishPost: return "\r" // (enter) + command
+ case .mediaBrowse: return "b" // + option + command
+ case .mediaPhotoLibrary: return "p" // + option + command
+ case .mediaCamera: return "c" // + option + command
+ case .togglePoll: return "p" // + shift + command
+ case .toggleContentWarning: return "c" // + shift + command
+ case .selectVisibilityPublic: return "1" // + command
+ // case .selectVisibilityUnlisted: return "2" // + command
+ case .selectVisibilityPrivate: return "2" // + command
+ case .selectVisibilityDirect: return "3" // + command
+ }
+ }
+
+ var modifierFlags: UIKeyModifierFlags {
+ switch self {
+ case .discardPost: return [.command]
+ case .publishPost: return [.command]
+ case .mediaBrowse: return [.alternate, .command]
+ case .mediaPhotoLibrary: return [.alternate, .command]
+ case .mediaCamera: return [.alternate, .command]
+ case .togglePoll: return [.shift, .command]
+ case .toggleContentWarning: return [.shift, .command]
+ case .selectVisibilityPublic: return [.command]
+ // case .selectVisibilityUnlisted: return [.command]
+ case .selectVisibilityPrivate: return [.command]
+ case .selectVisibilityDirect: return [.command]
+ }
+ }
+
+ var propertyList: Any {
+ return rawValue
+ }
+ }
+
+ var composeKeyCommands: [UIKeyCommand]? {
+ ComposeKeyCommand.allCases.map { command in
+ UIKeyCommand(
+ title: command.title,
+ image: nil,
+ action: #selector(Self.composeKeyCommandHandler(_:)),
+ input: command.input,
+ modifierFlags: command.modifierFlags,
+ propertyList: command.propertyList,
+ alternates: [],
+ discoverabilityTitle: nil,
+ attributes: [],
+ state: .off
+ )
+ }
+ }
+
+ @objc private func composeKeyCommandHandler(_ sender: UIKeyCommand) {
+ guard let rawValue = sender.propertyList as? String,
+ let command = ComposeKeyCommand(rawValue: rawValue) else { return }
+
+ switch command {
+ case .discardPost:
+ cancelBarButtonItemPressed(cancelBarButtonItem)
+ case .publishPost:
+ publishBarButtonItemPressed(publishBarButtonItem)
+ case .mediaBrowse:
+ guard !isViewControllerIsAlreadyModal(composeContentViewController.documentPickerController) else { return }
+ present(composeContentViewController.documentPickerController, animated: true, completion: nil)
+ case .mediaPhotoLibrary:
+ guard !isViewControllerIsAlreadyModal(composeContentViewController.photoLibraryPicker) else { return }
+ present(composeContentViewController.photoLibraryPicker, animated: true, completion: nil)
+ case .mediaCamera:
+ guard UIImagePickerController.isSourceTypeAvailable(.camera) else {
+ return
+ }
+ guard !isViewControllerIsAlreadyModal(composeContentViewController.imagePickerController) else { return }
+ present(composeContentViewController.imagePickerController, animated: true, completion: nil)
+ case .togglePoll:
+ composeContentViewModel.isPollActive.toggle()
+ case .toggleContentWarning:
+ composeContentViewModel.isContentWarningActive.toggle()
+ case .selectVisibilityPublic:
+ composeContentViewModel.visibility = .public
+ // case .selectVisibilityUnlisted:
+ // viewModel.selectedStatusVisibility.value = .unlisted
+ case .selectVisibilityPrivate:
+ composeContentViewModel.visibility = .private
+ case .selectVisibilityDirect:
+ composeContentViewModel.visibility = .direct
+ }
+ }
+
+ private func isViewControllerIsAlreadyModal(_ viewController: UIViewController) -> Bool {
+ return viewController.presentingViewController != nil
+ }
+
+}
diff --git a/Mastodon/Scene/Compose/ComposeViewModel+DataSource.swift b/Mastodon/Scene/Compose/ComposeViewModel+DataSource.swift
deleted file mode 100644
index b3d8f52dc..000000000
--- a/Mastodon/Scene/Compose/ComposeViewModel+DataSource.swift
+++ /dev/null
@@ -1,490 +0,0 @@
-//
-// ComposeViewModel+Diffable.swift
-// Mastodon
-//
-// Created by MainasuK Cirno on 2021-3-11.
-//
-
-import os.log
-import UIKit
-import Combine
-import CoreDataStack
-import MetaTextKit
-import MastodonMeta
-import MastodonAsset
-import MastodonCore
-import MastodonLocalization
-import MastodonSDK
-
-extension ComposeViewModel {
-
-// func setupDataSource(
-// tableView: UITableView,
-// metaTextDelegate: MetaTextDelegate,
-// metaTextViewDelegate: UITextViewDelegate,
-// customEmojiPickerInputViewModel: CustomEmojiPickerInputViewModel,
-// composeStatusAttachmentCollectionViewCellDelegate: ComposeStatusAttachmentCollectionViewCellDelegate,
-// composeStatusPollOptionCollectionViewCellDelegate: ComposeStatusPollOptionCollectionViewCellDelegate,
-// composeStatusPollOptionAppendEntryCollectionViewCellDelegate: ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate,
-// composeStatusPollExpiresOptionCollectionViewCellDelegate: ComposeStatusPollExpiresOptionCollectionViewCellDelegate
-// ) {
-// // UI
-// bind()
-//
-// // content
-// bind(cell: composeStatusContentTableViewCell, tableView: tableView)
-// composeStatusContentTableViewCell.metaText.delegate = metaTextDelegate
-// composeStatusContentTableViewCell.metaText.textView.delegate = metaTextViewDelegate
-//
-// // attachment
-// bind(cell: composeStatusAttachmentTableViewCell, tableView: tableView)
-// composeStatusAttachmentTableViewCell.composeStatusAttachmentCollectionViewCellDelegate = composeStatusAttachmentCollectionViewCellDelegate
-//
-// // poll
-// bind(cell: composeStatusPollTableViewCell, tableView: tableView)
-// composeStatusPollTableViewCell.delegate = self
-// composeStatusPollTableViewCell.customEmojiPickerInputViewModel = customEmojiPickerInputViewModel
-// composeStatusPollTableViewCell.composeStatusPollOptionCollectionViewCellDelegate = composeStatusPollOptionCollectionViewCellDelegate
-// composeStatusPollTableViewCell.composeStatusPollOptionAppendEntryCollectionViewCellDelegate = composeStatusPollOptionAppendEntryCollectionViewCellDelegate
-// composeStatusPollTableViewCell.composeStatusPollExpiresOptionCollectionViewCellDelegate = composeStatusPollExpiresOptionCollectionViewCellDelegate
-//
-// // setup data source
-// tableView.dataSource = self
-// }
-//
-// func setupCustomEmojiPickerDiffableDataSource(
-// for collectionView: UICollectionView,
-// dependency: NeedsDependency
-// ) {
-// let diffableDataSource = CustomEmojiPickerSection.collectionViewDiffableDataSource(
-// for: collectionView,
-// dependency: dependency
-// )
-// self.customEmojiPickerDiffableDataSource = diffableDataSource
-//
-// let _domain = customEmojiViewModel?.domain
-// customEmojiViewModel?.emojis
-// .receive(on: DispatchQueue.main)
-// .sink { [weak self, weak diffableDataSource] emojis in
-// guard let _ = self else { return }
-// guard let diffableDataSource = diffableDataSource else { return }
-//
-// var snapshot = NSDiffableDataSourceSnapshot()
-// let domain = _domain?.uppercased() ?? " "
-// let customEmojiSection = CustomEmojiPickerSection.emoji(name: domain)
-// snapshot.appendSections([customEmojiSection])
-// let items: [CustomEmojiPickerItem] = {
-// var items = [CustomEmojiPickerItem]()
-// for emoji in emojis where emoji.visibleInPicker {
-// let attribute = CustomEmojiPickerItem.CustomEmojiAttribute(emoji: emoji)
-// let item = CustomEmojiPickerItem.emoji(attribute: attribute)
-// items.append(item)
-// }
-// return items
-// }()
-// snapshot.appendItems(items, toSection: customEmojiSection)
-//
-// diffableDataSource.apply(snapshot)
-// }
-// .store(in: &disposeBag)
-// }
-
-}
-
-//// MARK: - UITableViewDataSource
-//extension ComposeViewModel: UITableViewDataSource {
-
-// func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-// switch Section.allCases[indexPath.section] {
-// case .repliedTo:
-// let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ComposeRepliedToStatusContentTableViewCell.self), for: indexPath) as! ComposeRepliedToStatusContentTableViewCell
-// guard case let .reply(record) = composeKind else { return cell }
-//
-// // bind frame publisher
-// cell.framePublisher
-// .receive(on: DispatchQueue.main)
-// .assign(to: \.repliedToCellFrame, on: self)
-// .store(in: &cell.disposeBag)
-//
-// // set initial width
-// if cell.statusView.frame.width == .zero {
-// cell.statusView.frame.size.width = tableView.frame.width
-// }
-//
-// // configure status
-// context.managedObjectContext.performAndWait {
-// guard let replyTo = record.object(in: context.managedObjectContext) else { return }
-// cell.statusView.configure(status: replyTo)
-// }
-//
-// return cell
-// case .status:
-// return composeStatusContentTableViewCell
-// case .attachment:
-// return composeStatusAttachmentTableViewCell
-// case .poll:
-// return composeStatusPollTableViewCell
-// }
-// }
-//}
-
-//// MARK: - ComposeStatusPollTableViewCellDelegate
-//extension ComposeViewModel: ComposeStatusPollTableViewCellDelegate {
-// func composeStatusPollTableViewCell(_ cell: ComposeStatusPollTableViewCell, pollOptionAttributesDidReorder options: [ComposeStatusPollItem.PollOptionAttribute]) {
-// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
-//
-// self.pollOptionAttributes = options
-// }
-//}
-//
-//extension ComposeViewModel {
-// private func bind() {
-// $isCustomEmojiComposing
-// .assign(to: \.value, on: customEmojiPickerInputViewModel.isCustomEmojiComposing)
-// .store(in: &disposeBag)
-//
-// $isContentWarningComposing
-// .assign(to: \.isContentWarningComposing, on: composeStatusAttribute)
-// .store(in: &disposeBag)
-//
-// // bind compose toolbar UI state
-// Publishers.CombineLatest(
-// $isPollComposing,
-// $attachmentServices
-// )
-// .receive(on: DispatchQueue.main)
-// .sink(receiveValue: { [weak self] isPollComposing, attachmentServices in
-// guard let self = self else { return }
-// let shouldMediaDisable = isPollComposing || attachmentServices.count >= self.maxMediaAttachments
-// let shouldPollDisable = attachmentServices.count > 0
-//
-// self.isMediaToolbarButtonEnabled = !shouldMediaDisable
-// self.isPollToolbarButtonEnabled = !shouldPollDisable
-// })
-// .store(in: &disposeBag)
-//
-// // calculate `Idempotency-Key`
-// let content = Publishers.CombineLatest3(
-// composeStatusAttribute.$isContentWarningComposing,
-// composeStatusAttribute.$contentWarningContent,
-// composeStatusAttribute.$composeContent
-// )
-// .map { isContentWarningComposing, contentWarningContent, composeContent -> String in
-// if isContentWarningComposing {
-// return contentWarningContent + (composeContent ?? "")
-// } else {
-// return composeContent ?? ""
-// }
-// }
-// let attachmentIDs = $attachmentServices.map { attachments -> String in
-// let attachmentIDs = attachments.compactMap { $0.attachment.value?.id }
-// return attachmentIDs.joined(separator: ",")
-// }
-// let pollOptionsAndDuration = Publishers.CombineLatest3(
-// $isPollComposing,
-// $pollOptionAttributes,
-// pollExpiresOptionAttribute.expiresOption
-// )
-// .map { isPollComposing, pollOptionAttributes, expiresOption -> String in
-// guard isPollComposing else {
-// return ""
-// }
-//
-// let pollOptions = pollOptionAttributes.map { $0.option.value }.joined(separator: ",")
-// return pollOptions + expiresOption.rawValue
-// }
-//
-// Publishers.CombineLatest4(
-// content,
-// attachmentIDs,
-// pollOptionsAndDuration,
-// $selectedStatusVisibility
-// )
-// .map { content, attachmentIDs, pollOptionsAndDuration, selectedStatusVisibility -> String in
-// var hasher = Hasher()
-// hasher.combine(content)
-// hasher.combine(attachmentIDs)
-// hasher.combine(pollOptionsAndDuration)
-// hasher.combine(selectedStatusVisibility.visibility.rawValue)
-// let hashValue = hasher.finalize()
-// return "\(hashValue)"
-// }
-// .assign(to: \.value, on: idempotencyKey)
-// .store(in: &disposeBag)
-//
-// // bind modal dismiss state
-// composeStatusAttribute.$composeContent
-// .receive(on: DispatchQueue.main)
-// .map { [weak self] content in
-// let content = content ?? ""
-// if content.isEmpty {
-// return true
-// }
-// // if preInsertedContent plus a space is equal to the content, simply dismiss the modal
-// if let preInsertedContent = self?.preInsertedContent {
-// return content == preInsertedContent
-// }
-// return false
-// }
-// .assign(to: &$shouldDismiss)
-//
-// // bind compose bar button item UI state
-// let isComposeContentEmpty = composeStatusAttribute.$composeContent
-// .map { ($0 ?? "").isEmpty }
-// let isComposeContentValid = $characterCount
-// .compactMap { [weak self] characterCount -> Bool in
-// guard let self = self else { return characterCount <= 500 }
-// return characterCount <= self.composeContentLimit
-// }
-// let isMediaEmpty = $attachmentServices
-// .map { $0.isEmpty }
-// let isMediaUploadAllSuccess = $attachmentServices
-// .map { services in
-// services.allSatisfy { $0.uploadStateMachineSubject.value is MastodonAttachmentService.UploadState.Finish }
-// }
-// let isPollAttributeAllValid = $pollOptionAttributes
-// .map { pollAttributes in
-// pollAttributes.allSatisfy { attribute -> Bool in
-// !attribute.option.value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
-// }
-// }
-//
-// let isPublishBarButtonItemEnabledPrecondition1 = Publishers.CombineLatest4(
-// isComposeContentEmpty,
-// isComposeContentValid,
-// isMediaEmpty,
-// isMediaUploadAllSuccess
-// )
-// .map { isComposeContentEmpty, isComposeContentValid, isMediaEmpty, isMediaUploadAllSuccess -> Bool in
-// if isMediaEmpty {
-// return isComposeContentValid && !isComposeContentEmpty
-// } else {
-// return isComposeContentValid && isMediaUploadAllSuccess
-// }
-// }
-// .eraseToAnyPublisher()
-//
-// let isPublishBarButtonItemEnabledPrecondition2 = Publishers.CombineLatest4(
-// isComposeContentEmpty,
-// isComposeContentValid,
-// $isPollComposing,
-// isPollAttributeAllValid
-// )
-// .map { isComposeContentEmpty, isComposeContentValid, isPollComposing, isPollAttributeAllValid -> Bool in
-// if isPollComposing {
-// return isComposeContentValid && !isComposeContentEmpty && isPollAttributeAllValid
-// } else {
-// return isComposeContentValid && !isComposeContentEmpty
-// }
-// }
-// .eraseToAnyPublisher()
-//
-// Publishers.CombineLatest(
-// isPublishBarButtonItemEnabledPrecondition1,
-// isPublishBarButtonItemEnabledPrecondition2
-// )
-// .map { $0 && $1 }
-// .assign(to: &$isPublishBarButtonItemEnabled)
-// }
-//}
-//
-//extension ComposeViewModel {
-// private func bind(
-// cell: ComposeStatusContentTableViewCell,
-// tableView: UITableView
-// ) {
-// // bind status content character count
-// Publishers.CombineLatest3(
-// composeStatusAttribute.$composeContent,
-// composeStatusAttribute.$isContentWarningComposing,
-// composeStatusAttribute.$contentWarningContent
-// )
-// .map { composeContent, isContentWarningComposing, contentWarningContent -> Int in
-// let composeContent = composeContent ?? ""
-// var count = composeContent.count
-// if isContentWarningComposing {
-// count += contentWarningContent.count
-// }
-// return count
-// }
-// .assign(to: &$characterCount)
-//
-// // bind content warning
-// composeStatusAttribute.$isContentWarningComposing
-// .receive(on: DispatchQueue.main)
-// .sink { [weak cell, weak tableView] isContentWarningComposing in
-// guard let cell = cell else { return }
-// guard let tableView = tableView else { return }
-//
-// // self size input cell
-// cell.statusContentWarningEditorView.isHidden = !isContentWarningComposing
-// cell.statusContentWarningEditorView.alpha = 0
-// UIView.animate(withDuration: 0.33, delay: 0, options: [.curveEaseOut]) {
-// cell.statusContentWarningEditorView.alpha = 1
-// tableView.beginUpdates()
-// tableView.endUpdates()
-// } completion: { _ in
-// // do nothing
-// }
-// }
-// .store(in: &disposeBag)
-//
-// cell.contentWarningContent
-// .removeDuplicates()
-// .receive(on: DispatchQueue.main)
-// .sink { [weak tableView, weak self] text in
-// guard let self = self else { return }
-// // bind input data
-// self.composeStatusAttribute.contentWarningContent = text
-//
-// // self size input cell
-// guard let tableView = tableView else { return }
-// UIView.performWithoutAnimation {
-// tableView.beginUpdates()
-// tableView.endUpdates()
-// }
-// }
-// .store(in: &cell.disposeBag)
-//
-// // configure custom emoji picker
-// ComposeStatusSection.configureCustomEmojiPicker(
-// viewModel: customEmojiPickerInputViewModel,
-// customEmojiReplaceableTextInput: cell.metaText.textView,
-// disposeBag: &disposeBag
-// )
-// ComposeStatusSection.configureCustomEmojiPicker(
-// viewModel: customEmojiPickerInputViewModel,
-// customEmojiReplaceableTextInput: cell.statusContentWarningEditorView.textView,
-// disposeBag: &disposeBag
-// )
-// }
-//}
-//
-//extension ComposeViewModel {
-// private func bind(
-// cell: ComposeStatusPollTableViewCell,
-// tableView: UITableView
-// ) {
-// Publishers.CombineLatest(
-// $isPollComposing,
-// $pollOptionAttributes
-// )
-// .receive(on: DispatchQueue.main)
-// .sink { [weak self] isPollComposing, pollOptionAttributes in
-// guard let self = self else { return }
-// guard self.isViewAppeared else { return }
-//
-// let cell = self.composeStatusPollTableViewCell
-// guard let dataSource = cell.dataSource else { return }
-//
-// var snapshot = NSDiffableDataSourceSnapshot()
-// snapshot.appendSections([.main])
-// var items: [ComposeStatusPollItem] = []
-// if isPollComposing {
-// for attribute in pollOptionAttributes {
-// items.append(.pollOption(attribute: attribute))
-// }
-// if pollOptionAttributes.count < self.maxPollOptions {
-// items.append(.pollOptionAppendEntry)
-// }
-// items.append(.pollExpiresOption(attribute: self.pollExpiresOptionAttribute))
-// }
-// snapshot.appendItems(items, toSection: .main)
-//
-// tableView.performBatchUpdates {
-// if #available(iOS 15.0, *) {
-// dataSource.apply(snapshot, animatingDifferences: false)
-// } else {
-// dataSource.apply(snapshot, animatingDifferences: true)
-// }
-// }
-// }
-// .store(in: &disposeBag)
-//
-// // bind delegate
-// $pollOptionAttributes
-// .sink { [weak self] pollAttributes in
-// guard let self = self else { return }
-// pollAttributes.forEach { $0.delegate = self }
-// }
-// .store(in: &disposeBag)
-// }
-//}
-//
-//extension ComposeViewModel {
-// private func bind(
-// cell: ComposeStatusAttachmentTableViewCell,
-// tableView: UITableView
-// ) {
-// cell.collectionViewHeightDidUpdate
-// .receive(on: DispatchQueue.main)
-// .sink { [weak self] _ in
-// guard let _ = self else { return }
-// tableView.beginUpdates()
-// tableView.endUpdates()
-// }
-// .store(in: &disposeBag)
-//
-// $attachmentServices
-// .removeDuplicates()
-// .receive(on: DispatchQueue.main)
-// .sink { [weak self] attachmentServices in
-// guard let self = self else { return }
-// guard self.isViewAppeared else { return }
-//
-// let cell = self.composeStatusAttachmentTableViewCell
-// guard let dataSource = cell.dataSource else { return }
-//
-// var snapshot = NSDiffableDataSourceSnapshot()
-// snapshot.appendSections([.main])
-// let items = attachmentServices.map { ComposeStatusAttachmentItem.attachment(attachmentService: $0) }
-// snapshot.appendItems(items, toSection: .main)
-//
-// if #available(iOS 15.0, *) {
-// dataSource.applySnapshotUsingReloadData(snapshot)
-// } else {
-// dataSource.apply(snapshot, animatingDifferences: false)
-// }
-// }
-// .store(in: &disposeBag)
-//
-// // setup attribute updater
-// $attachmentServices
-// .receive(on: DispatchQueue.main)
-// .debounce(for: 0.3, scheduler: DispatchQueue.main)
-// .sink { attachmentServices in
-// // drive service upload state
-// // make image upload in the queue
-// for attachmentService in attachmentServices {
-// // skip when prefix N task when task finish OR fail OR uploading
-// guard let currentState = attachmentService.uploadStateMachine.currentState else { break }
-// if currentState is MastodonAttachmentService.UploadState.Fail {
-// continue
-// }
-// if currentState is MastodonAttachmentService.UploadState.Finish {
-// continue
-// }
-// if currentState is MastodonAttachmentService.UploadState.Processing {
-// continue
-// }
-// if currentState is MastodonAttachmentService.UploadState.Uploading {
-// break
-// }
-// // trigger uploading one by one
-// if currentState is MastodonAttachmentService.UploadState.Initial {
-// attachmentService.uploadStateMachine.enter(MastodonAttachmentService.UploadState.Uploading.self)
-// break
-// }
-// }
-// }
-// .store(in: &disposeBag)
-//
-// // bind delegate
-// $attachmentServices
-// .sink { [weak self] attachmentServices in
-// guard let self = self else { return }
-// attachmentServices.forEach { $0.delegate = self }
-// }
-// .store(in: &disposeBag)
-// }
-//}
diff --git a/Mastodon/Scene/Compose/ComposeViewModel+PublishState.swift b/Mastodon/Scene/Compose/ComposeViewModel+PublishState.swift
deleted file mode 100644
index b9ed18c45..000000000
--- a/Mastodon/Scene/Compose/ComposeViewModel+PublishState.swift
+++ /dev/null
@@ -1,164 +0,0 @@
-//
-// ComposeViewModel+PublishState.swift
-// Mastodon
-//
-// Created by MainasuK Cirno on 2021-3-18.
-//
-
-import os.log
-import Foundation
-import Combine
-import CoreDataStack
-import GameplayKit
-import MastodonSDK
-
-//extension ComposeViewModel {
-// class PublishState: GKState {
-// weak var viewModel: ComposeViewModel?
-//
-// init(viewModel: ComposeViewModel) {
-// self.viewModel = viewModel
-// }
-//
-// override func didEnter(from previousState: GKState?) {
-// os_log("%{public}s[%{public}ld], %{public}s: enter %s, previous: %s", ((#file as NSString).lastPathComponent), #line, #function, self.debugDescription, previousState.debugDescription)
-// viewModel?.publishStateMachinePublisher.value = self
-// }
-// }
-//}
-
-//extension ComposeViewModel.PublishState {
-// class Initial: ComposeViewModel.PublishState {
-// override func isValidNextState(_ stateClass: AnyClass) -> Bool {
-// return stateClass == Publishing.self
-// }
-// }
-//
-// class Publishing: ComposeViewModel.PublishState {
-//
-// var publishingSubscription: AnyCancellable?
-//
-// override func isValidNextState(_ stateClass: AnyClass) -> Bool {
-// return stateClass == Fail.self || stateClass == Finish.self
-// }
-//
-// override func didEnter(from previousState: GKState?) {
-// super.didEnter(from: previousState)
-// guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
-//
-// viewModel.updatePublishDate()
-//
-// let authenticationBox = viewModel.authenticationBox
-// let domain = authenticationBox.domain
-// let attachmentServices = viewModel.attachmentServices
-// let mediaIDs = attachmentServices.compactMap { attachmentService in
-// attachmentService.attachment.value?.id
-// }
-// let pollOptions: [String]? = {
-// guard viewModel.isPollComposing else { return nil }
-// return viewModel.pollOptionAttributes.map { attribute in attribute.option.value }
-// }()
-// let pollExpiresIn: Int? = {
-// guard viewModel.isPollComposing else { return nil }
-// return viewModel.pollExpiresOptionAttribute.expiresOption.value.seconds
-// }()
-// let inReplyToID: Mastodon.Entity.Status.ID? = {
-// guard case let .reply(status) = viewModel.composeKind else { return nil }
-// var id: Mastodon.Entity.Status.ID?
-// viewModel.context.managedObjectContext.performAndWait {
-// guard let replyTo = status.object(in: viewModel.context.managedObjectContext) else { return }
-// id = replyTo.id
-// }
-// return id
-// }()
-// let sensitive: Bool = viewModel.isContentWarningComposing
-// let spoilerText: String? = {
-// let text = viewModel.composeStatusAttribute.contentWarningContent.trimmingCharacters(in: .whitespacesAndNewlines)
-// guard !text.isEmpty else {
-// return nil
-// }
-// return text
-// }()
-// let visibility = viewModel.selectedStatusVisibility.visibility
-//
-// let updateMediaQuerySubscriptions: [AnyPublisher, Error>] = {
-// var subscriptions: [AnyPublisher, Error>] = []
-// for attachmentService in attachmentServices {
-// guard let attachmentID = attachmentService.attachment.value?.id else { continue }
-// let description = attachmentService.description.value?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
-// guard !description.isEmpty else { continue }
-// let query = Mastodon.API.Media.UpdateMediaQuery(
-// file: nil,
-// thumbnail: nil,
-// description: description,
-// focus: nil
-// )
-// let subscription = viewModel.context.apiService.updateMedia(
-// domain: domain,
-// attachmentID: attachmentID,
-// query: query,
-// mastodonAuthenticationBox: authenticationBox
-// )
-// subscriptions.append(subscription)
-// }
-// return subscriptions
-// }()
-//
-// let idempotencyKey = viewModel.idempotencyKey.value
-//
-// publishingSubscription = Publishers.MergeMany(updateMediaQuerySubscriptions)
-// .collect()
-// .asyncMap { attachments -> Mastodon.Response.Content in
-// let query = Mastodon.API.Statuses.PublishStatusQuery(
-// status: viewModel.composeStatusAttribute.composeContent,
-// mediaIDs: mediaIDs.isEmpty ? nil : mediaIDs,
-// pollOptions: pollOptions,
-// pollExpiresIn: pollExpiresIn,
-// inReplyToID: inReplyToID,
-// sensitive: sensitive,
-// spoilerText: spoilerText,
-// visibility: visibility
-// )
-// return try await viewModel.context.apiService.publishStatus(
-// domain: domain,
-// idempotencyKey: idempotencyKey,
-// query: query,
-// authenticationBox: authenticationBox
-// )
-// }
-// .receive(on: DispatchQueue.main)
-// .sink { completion in
-// switch completion {
-// case .failure(let error):
-// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: publish status %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
-// stateMachine.enter(Fail.self)
-// case .finished:
-// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: publish status success", ((#file as NSString).lastPathComponent), #line, #function)
-// stateMachine.enter(Finish.self)
-// }
-// } receiveValue: { response in
-// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: status %s published: %s", ((#file as NSString).lastPathComponent), #line, #function, response.value.id, response.value.uri)
-// }
-// }
-// }
-//
-// class Fail: ComposeViewModel.PublishState {
-// override func isValidNextState(_ stateClass: AnyClass) -> Bool {
-// // allow discard publishing
-// return stateClass == Publishing.self || stateClass == Discard.self
-// }
-// }
-//
-// class Discard: ComposeViewModel.PublishState {
-// override func isValidNextState(_ stateClass: AnyClass) -> Bool {
-// return false
-// }
-// }
-//
-// class Finish: ComposeViewModel.PublishState {
-// override func isValidNextState(_ stateClass: AnyClass) -> Bool {
-// return false
-// }
-// }
-//
-//}
diff --git a/Mastodon/Scene/Compose/ComposeViewModel.swift b/Mastodon/Scene/Compose/ComposeViewModel.swift
index 45c9f1e93..bf234b095 100644
--- a/Mastodon/Scene/Compose/ComposeViewModel.swift
+++ b/Mastodon/Scene/Compose/ComposeViewModel.swift
@@ -18,7 +18,7 @@ import MastodonLocalization
import MastodonMeta
import MastodonUI
-final class ComposeViewModel: NSObject {
+final class ComposeViewModel {
let logger = Logger(subsystem: "ComposeViewModel", category: "ViewModel")
@@ -30,91 +30,13 @@ final class ComposeViewModel: NSObject {
let context: AppContext
let authContext: AuthContext
let kind: ComposeContentViewModel.Kind
-
-// var authenticationBox: MastodonAuthenticationBox {
-// authContext.mastodonAuthenticationBox
-// }
-//
-// @Published var isPollComposing = false
-// @Published var isCustomEmojiComposing = false
-// @Published var isContentWarningComposing = false
-//
-// @Published var selectedStatusVisibility: ComposeToolbarView.VisibilitySelectionType
-// @Published var repliedToCellFrame: CGRect = .zero
-// @Published var autoCompleteRetryLayoutTimes = 0
-// @Published var autoCompleteInfo: ComposeViewController.AutoCompleteInfo? = nil
let traitCollectionDidChangePublisher = CurrentValueSubject(Void()) // use CurrentValueSubject to make initial event emit
-// var isViewAppeared = false
// output
-// let instanceConfiguration: Mastodon.Entity.Instance.Configuration?
-// var composeContentLimit: Int {
-// guard let maxCharacters = instanceConfiguration?.statuses?.maxCharacters else { return 500 }
-// return max(1, maxCharacters)
-// }
-// var maxMediaAttachments: Int {
-// guard let maxMediaAttachments = instanceConfiguration?.statuses?.maxMediaAttachments else {
-// return 4
-// }
-// // FIXME: update timeline media preview UI
-// return min(4, max(1, maxMediaAttachments))
-// // return max(1, maxMediaAttachments)
-// }
-// var maxPollOptions: Int {
-// guard let maxOptions = instanceConfiguration?.polls?.maxOptions else { return 4 }
-// return max(2, maxOptions)
-// }
-//
-// let composeStatusAttribute = ComposeStatusItem.ComposeStatusAttribute()
-// let composeStatusContentTableViewCell = ComposeStatusContentTableViewCell()
-// let composeStatusAttachmentTableViewCell = ComposeStatusAttachmentTableViewCell()
-// let composeStatusPollTableViewCell = ComposeStatusPollTableViewCell()
-//
-// // var dataSource: UITableViewDiffableDataSource?
-// var customEmojiPickerDiffableDataSource: UICollectionViewDiffableDataSource?
-// private(set) lazy var publishStateMachine: GKStateMachine = {
-// // exclude timeline middle fetcher state
-// let stateMachine = GKStateMachine(states: [
-// PublishState.Initial(viewModel: self),
-// PublishState.Publishing(viewModel: self),
-// PublishState.Fail(viewModel: self),
-// PublishState.Discard(viewModel: self),
-// PublishState.Finish(viewModel: self),
-// ])
-// stateMachine.enter(PublishState.Initial.self)
-// return stateMachine
-// }()
-// private(set) lazy var publishStateMachinePublisher = CurrentValueSubject(nil)
-// private(set) var publishDate = Date() // update it when enter Publishing state
-//
-// // TODO: group post material into Hashable class
-// var idempotencyKey = CurrentValueSubject(UUID().uuidString)
-//
-// // UI & UX
-// @Published var title: String
-// @Published var shouldDismiss = true
-// @Published var isPublishBarButtonItemEnabled = false
-// @Published var isMediaToolbarButtonEnabled = true
-// @Published var isPollToolbarButtonEnabled = true
-// @Published var characterCount = 0
-// @Published var collectionViewState: CollectionViewState = .fold
-//
-// // for hashtag: "# "
-// // for mention: "@ "
-// var preInsertedContent: String?
-//
-// // custom emojis
-// let customEmojiViewModel: EmojiService.CustomEmojiViewModel?
-// let customEmojiPickerInputViewModel = CustomEmojiPickerInputViewModel()
-// @Published var isLoadingCustomEmoji = false
-//
-// // attachment
-// @Published var attachmentServices: [MastodonAttachmentService] = []
-//
-// // polls
-// @Published var pollOptionAttributes: [ComposeStatusPollItem.PollOptionAttribute] = []
-// let pollExpiresOptionAttribute = ComposeStatusPollItem.PollExpiresOptionAttribute()
+
+ // UI & UX
+ @Published var title: String
init(
context: AppContext,
@@ -124,63 +46,14 @@ final class ComposeViewModel: NSObject {
self.context = context
self.authContext = authContext
self.kind = kind
+ // end init
-// self.title = {
-// switch composeKind {
-// case .post, .hashtag, .mention: return L10n.Scene.Compose.Title.newPost
-// case .reply: return L10n.Scene.Compose.Title.newReply
-// }
-// }()
-// self.selectedStatusVisibility = {
-// // default private when user locked
-// var visibility: ComposeToolbarView.VisibilitySelectionType = {
-// guard let author = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
-// else {
-// return .public
-// }
-// return author.locked ? .private : .public
-// }()
-// // set visibility for reply post
-// switch composeKind {
-// case .reply(let record):
-// context.managedObjectContext.performAndWait {
-// guard let status = record.object(in: context.managedObjectContext) else {
-// assertionFailure()
-// return
-// }
-// let repliedStatusVisibility = status.visibility
-// switch repliedStatusVisibility {
-// case .public, .unlisted:
-// // keep default
-// break
-// case .private:
-// visibility = .private
-// case .direct:
-// visibility = .direct
-// case ._other:
-// assertionFailure()
-// break
-// }
-// }
-// default:
-// break
-// }
-// return visibility
-// }()
-// // set limit
-// self.instanceConfiguration = {
-// var configuration: Mastodon.Entity.Instance.Configuration? = nil
-// context.managedObjectContext.performAndWait {
-// guard let authentication = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext) else { return }
-// configuration = authentication.instance?.configuration
-// }
-// return configuration
-// }()
-// self.customEmojiViewModel = context.emojiService.dequeueCustomEmojiViewModel(for: authContext.mastodonAuthenticationBox.domain)
-// super.init()
-// // end init
-//
-// setup(cell: composeStatusContentTableViewCell)
+ self.title = {
+ switch kind {
+ case .post, .hashtag, .mention: return L10n.Scene.Compose.Title.newPost
+ case .reply: return L10n.Scene.Compose.Title.newReply
+ }
+ }()
}
deinit {
@@ -188,194 +61,3 @@ final class ComposeViewModel: NSObject {
}
}
-
-extension ComposeViewModel {
-// func createNewPollOptionIfPossible() {
-// guard pollOptionAttributes.count < maxPollOptions else { return }
-//
-// let attribute = ComposeStatusPollItem.PollOptionAttribute()
-// pollOptionAttributes = pollOptionAttributes + [attribute]
-// }
-//
-// func updatePublishDate() {
-// publishDate = Date()
-// }
-}
-
-//extension ComposeViewModel {
-//
-// enum AttachmentPrecondition: Error, LocalizedError {
-// case videoAttachWithPhoto
-// case moreThanOneVideo
-//
-// var errorDescription: String? {
-// return L10n.Common.Alerts.PublishPostFailure.title
-// }
-//
-// var failureReason: String? {
-// switch self {
-// case .videoAttachWithPhoto:
-// return L10n.Common.Alerts.PublishPostFailure.AttachmentsMessage.videoAttachWithPhoto
-// case .moreThanOneVideo:
-// return L10n.Common.Alerts.PublishPostFailure.AttachmentsMessage.moreThanOneVideo
-// }
-// }
-// }
-//
-// // check exclusive limit:
-// // - up to 1 video
-// // - up to N photos
-// func checkAttachmentPrecondition() throws {
-// let attachmentServices = self.attachmentServices
-// guard !attachmentServices.isEmpty else { return }
-// var photoAttachmentServices: [MastodonAttachmentService] = []
-// var videoAttachmentServices: [MastodonAttachmentService] = []
-// attachmentServices.forEach { service in
-// guard let file = service.file.value else {
-// assertionFailure()
-// return
-// }
-// switch file {
-// case .jpeg, .png, .gif:
-// photoAttachmentServices.append(service)
-// case .other:
-// videoAttachmentServices.append(service)
-// }
-// }
-//
-// if !videoAttachmentServices.isEmpty {
-// guard videoAttachmentServices.count == 1 else {
-// throw AttachmentPrecondition.moreThanOneVideo
-// }
-// guard photoAttachmentServices.isEmpty else {
-// throw AttachmentPrecondition.videoAttachWithPhoto
-// }
-// }
-// }
-//
-//}
-//
-//// MARK: - MastodonAttachmentServiceDelegate
-//extension ComposeViewModel: MastodonAttachmentServiceDelegate {
-// func mastodonAttachmentService(_ service: MastodonAttachmentService, uploadStateDidChange state: MastodonAttachmentService.UploadState?) {
-// // trigger new output event
-// attachmentServices = attachmentServices
-// }
-//}
-//
-//// MARK: - ComposePollAttributeDelegate
-//extension ComposeViewModel: ComposePollAttributeDelegate {
-// func composePollAttribute(_ attribute: ComposeStatusPollItem.PollOptionAttribute, pollOptionDidChange: String?) {
-// // trigger update
-// pollOptionAttributes = pollOptionAttributes
-// }
-//}
-//
-//extension ComposeViewModel {
-// private func setup(
-// cell: ComposeStatusContentTableViewCell
-// ) {
-// setupStatusHeader(cell: cell)
-// setupStatusAuthor(cell: cell)
-// setupStatusContent(cell: cell)
-// }
-//
-// private func setupStatusHeader(
-// cell: ComposeStatusContentTableViewCell
-// ) {
-// // configure header
-// let managedObjectContext = context.managedObjectContext
-// managedObjectContext.performAndWait {
-// guard case let .reply(record) = self.composeKind,
-// let replyTo = record.object(in: managedObjectContext)
-// else {
-// cell.statusView.viewModel.header = .none
-// return
-// }
-//
-// let info: StatusView.ViewModel.Header.ReplyInfo
-// do {
-// let content = MastodonContent(
-// content: replyTo.author.displayNameWithFallback,
-// emojis: replyTo.author.emojis.asDictionary
-// )
-// let metaContent = try MastodonMetaContent.convert(document: content)
-// info = .init(header: metaContent)
-// } catch {
-// let metaContent = PlaintextMetaContent(string: replyTo.author.displayNameWithFallback)
-// info = .init(header: metaContent)
-// }
-// cell.statusView.viewModel.header = .reply(info: info)
-// }
-// }
-//
-// private func setupStatusAuthor(
-// cell: ComposeStatusContentTableViewCell
-// ) {
-// self.context.managedObjectContext.performAndWait {
-// guard let author = authenticationBox.authenticationRecord.object(in: self.context.managedObjectContext)?.user else { return }
-// cell.statusView.configureAuthor(author: author)
-// }
-// }
-//
-// private func setupStatusContent(
-// cell: ComposeStatusContentTableViewCell
-// ) {
-// switch composeKind {
-// case .reply(let record):
-// context.managedObjectContext.performAndWait {
-// guard let status = record.object(in: context.managedObjectContext) else { return }
-// let author = self.authenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
-//
-// var mentionAccts: [String] = []
-// if author?.id != status.author.id {
-// mentionAccts.append("@" + status.author.acct)
-// }
-// let mentions = status.mentions
-// .filter { author?.id != $0.id }
-// for mention in mentions {
-// let acct = "@" + mention.acct
-// guard !mentionAccts.contains(acct) else { continue }
-// mentionAccts.append(acct)
-// }
-// for acct in mentionAccts {
-// UITextChecker.learnWord(acct)
-// }
-// if let spoilerText = status.spoilerText, !spoilerText.isEmpty {
-// self.isContentWarningComposing = true
-// self.composeStatusAttribute.contentWarningContent = spoilerText
-// }
-//
-// let initialComposeContent = mentionAccts.joined(separator: " ")
-// let preInsertedContent: String? = initialComposeContent.isEmpty ? nil : initialComposeContent + " "
-// self.preInsertedContent = preInsertedContent
-// self.composeStatusAttribute.composeContent = preInsertedContent
-// }
-// case .hashtag(let hashtag):
-// let initialComposeContent = "#" + hashtag
-// UITextChecker.learnWord(initialComposeContent)
-// let preInsertedContent = initialComposeContent + " "
-// self.preInsertedContent = preInsertedContent
-// self.composeStatusAttribute.composeContent = preInsertedContent
-// case .mention(let record):
-// context.managedObjectContext.performAndWait {
-// guard let user = record.object(in: context.managedObjectContext) else { return }
-// let initialComposeContent = "@" + user.acct
-// UITextChecker.learnWord(initialComposeContent)
-// let preInsertedContent = initialComposeContent + " "
-// self.preInsertedContent = preInsertedContent
-// self.composeStatusAttribute.composeContent = preInsertedContent
-// }
-// case .post:
-// self.preInsertedContent = nil
-// }
-//
-// // configure content warning
-// if let composeContent = composeStatusAttribute.composeContent {
-// cell.metaText.textView.text = composeContent
-// }
-//
-// // configure content warning
-// cell.statusContentWarningEditorView.textView.text = composeStatusAttribute.contentWarningContent
-// }
-//}
diff --git a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewController.swift b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewController.swift
index d592c3033..870bfc72a 100644
--- a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewController.swift
+++ b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewController.swift
@@ -33,7 +33,7 @@ final class DiscoveryCommunityViewController: UIViewController, NeedsDependency,
return tableView
}()
- let refreshControl = UIRefreshControl()
+ let refreshControl = RefreshControl()
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
@@ -57,12 +57,7 @@ extension DiscoveryCommunityViewController {
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.pinToParent()
tableView.refreshControl = refreshControl
refreshControl.addTarget(self, action: #selector(DiscoveryCommunityViewController.refreshControlValueChanged(_:)), for: .valueChanged)
@@ -108,7 +103,7 @@ extension DiscoveryCommunityViewController {
extension DiscoveryCommunityViewController {
- @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) {
+ @objc private func refreshControlValueChanged(_ sender: RefreshControl) {
if !viewModel.stateMachine.enter(DiscoveryCommunityViewModel.State.Reloading.self) {
refreshControl.endRefreshing()
}
diff --git a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+State.swift b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+State.swift
index 476832b69..5045e825a 100644
--- a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+State.swift
+++ b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+State.swift
@@ -125,7 +125,7 @@ extension DiscoveryCommunityViewModel.State {
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
- guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
+ guard let viewModel else { return }
switch previousState {
case is Reloading:
diff --git a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift
index ce1aadbb4..dec1d9c72 100644
--- a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift
+++ b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift
@@ -32,7 +32,7 @@ final class DiscoveryForYouViewController: UIViewController, NeedsDependency, Me
return tableView
}()
- let refreshControl = UIRefreshControl()
+ let refreshControl = RefreshControl()
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
@@ -56,12 +56,7 @@ extension DiscoveryForYouViewController {
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.pinToParent()
tableView.delegate = self
viewModel.setupDiffableDataSource(
@@ -93,7 +88,7 @@ extension DiscoveryForYouViewController {
extension DiscoveryForYouViewController {
- @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) {
+ @objc private func refreshControlValueChanged(_ sender: RefreshControl) {
Task {
try await viewModel.fetch()
}
diff --git a/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewController.swift b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewController.swift
index e315f04ee..64d1231fe 100644
--- a/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewController.swift
+++ b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewController.swift
@@ -32,7 +32,7 @@ final class DiscoveryHashtagsViewController: UIViewController, NeedsDependency,
return tableView
}()
- let refreshControl = UIRefreshControl()
+ let refreshControl = RefreshControl()
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
@@ -56,12 +56,7 @@ extension DiscoveryHashtagsViewController {
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.pinToParent()
tableView.refreshControl = refreshControl
refreshControl.addTarget(self, action: #selector(DiscoveryHashtagsViewController.refreshControlValueChanged(_:)), for: .valueChanged)
@@ -88,7 +83,7 @@ extension DiscoveryHashtagsViewController {
extension DiscoveryHashtagsViewController {
- @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) {
+ @objc private func refreshControlValueChanged(_ sender: RefreshControl) {
Task { @MainActor in
do {
try await viewModel.fetch()
@@ -108,7 +103,7 @@ extension DiscoveryHashtagsViewController: UITableViewDelegate {
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, authContext: viewModel.authContext, hashtag: tag.name)
- coordinator.present(
+ _ = coordinator.present(
scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel),
from: self,
transition: .show
@@ -218,7 +213,7 @@ extension DiscoveryHashtagsViewController: TableViewControllerNavigateable {
guard case let .hashtag(tag) = item else { return }
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, authContext: viewModel.authContext, hashtag: tag.name)
- coordinator.present(
+ _ = coordinator.present(
scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel),
from: self,
transition: .show
diff --git a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewController.swift b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewController.swift
index 7f9efb0d9..4884aafac 100644
--- a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewController.swift
+++ b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewController.swift
@@ -32,7 +32,7 @@ final class DiscoveryNewsViewController: UIViewController, NeedsDependency, Medi
return tableView
}()
- let refreshControl = UIRefreshControl()
+ let refreshControl = RefreshControl()
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
@@ -56,12 +56,7 @@ extension DiscoveryNewsViewController {
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.pinToParent()
tableView.delegate = self
viewModel.setupDiffableDataSource(
@@ -101,7 +96,7 @@ extension DiscoveryNewsViewController {
extension DiscoveryNewsViewController {
- @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) {
+ @objc private func refreshControlValueChanged(_ sender: RefreshControl) {
guard viewModel.stateMachine.enter(DiscoveryNewsViewModel.State.Reloading.self) else {
sender.endRefreshing()
return
@@ -117,7 +112,7 @@ extension DiscoveryNewsViewController: UITableViewDelegate {
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(
+ _ = coordinator.present(
scene: .safari(url: url),
from: self,
transition: .safariPresent(animated: true, completion: nil)
@@ -214,7 +209,7 @@ extension DiscoveryNewsViewController: TableViewControllerNavigateable {
guard case let .link(link) = item else { return }
guard let url = URL(string: link.url) else { return }
- coordinator.present(
+ _ = coordinator.present(
scene: .safari(url: url),
from: self,
transition: .safariPresent(animated: true, completion: nil)
diff --git a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+State.swift b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+State.swift
index 7c802cde3..8170d5b1c 100644
--- a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+State.swift
+++ b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+State.swift
@@ -125,8 +125,7 @@ extension DiscoveryNewsViewModel.State {
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
- guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
-
+ guard let viewModel else { return }
switch previousState {
case is Reloading:
diff --git a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift
index ee3538885..9d772a27e 100644
--- a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift
+++ b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift
@@ -32,7 +32,7 @@ final class DiscoveryPostsViewController: UIViewController, NeedsDependency, Med
return tableView
}()
- let refreshControl = UIRefreshControl()
+ let refreshControl = RefreshControl()
let discoveryIntroBannerView = DiscoveryIntroBannerView()
@@ -58,12 +58,7 @@ extension DiscoveryPostsViewController {
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.pinToParent()
discoveryIntroBannerView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(discoveryIntroBannerView)
@@ -119,7 +114,7 @@ extension DiscoveryPostsViewController {
extension DiscoveryPostsViewController {
- @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) {
+ @objc private func refreshControlValueChanged(_ sender: RefreshControl) {
guard viewModel.stateMachine.enter(DiscoveryPostsViewModel.State.Reloading.self) else {
sender.endRefreshing()
return
diff --git a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+State.swift b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+State.swift
index 3ed245e99..fd413927e 100644
--- a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+State.swift
+++ b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+State.swift
@@ -126,7 +126,7 @@ extension DiscoveryPostsViewModel.State {
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
- guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
+ guard let viewModel else { return }
switch previousState {
case is Reloading:
diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift
index 42079a91e..4a0be3816 100644
--- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift
+++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift
@@ -48,7 +48,7 @@ final class HashtagTimelineViewController: UIViewController, NeedsDependency, Me
return tableView
}()
- let refreshControl = UIRefreshControl()
+ let refreshControl = RefreshControl()
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s:", ((#file as NSString).lastPathComponent), #line, #function)
@@ -80,12 +80,7 @@ extension HashtagTimelineViewController {
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.pinToParent()
tableView.delegate = self
viewModel.setupDiffableDataSource(
@@ -158,7 +153,7 @@ extension HashtagTimelineViewController {
extension HashtagTimelineViewController {
- @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) {
+ @objc private func refreshControlValueChanged(_ sender: RefreshControl) {
guard viewModel.stateMachine.enter(HashtagTimelineViewModel.State.Reloading.self) else {
sender.endRefreshing()
return
diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+State.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+State.swift
index deb9de0a2..c35715f1e 100644
--- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+State.swift
+++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+State.swift
@@ -70,7 +70,7 @@ extension HashtagTimelineViewModel.State {
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
- guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
+ guard let stateMachine = stateMachine else { return }
stateMachine.enter(Loading.self)
}
@@ -127,7 +127,7 @@ extension HashtagTimelineViewModel.State {
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
- guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
+ guard let viewModel else { return }
switch previousState {
case is Reloading:
diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift
index d057c0376..ff90775ff 100644
--- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift
+++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift
@@ -300,7 +300,7 @@ extension HomeTimelineViewController {
}
@objc private func showWelcomeAction(_ sender: UIAction) {
- coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil))
+ _ = coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil))
}
@objc private func showRegisterAction(_ sender: UIAction) {
@@ -332,12 +332,12 @@ extension HomeTimelineViewController {
@objc private func showConfirmEmail(_ sender: UIAction) {
let mastodonConfirmEmailViewModel = MastodonConfirmEmailViewModel()
- coordinator.present(scene: .mastodonConfirmEmail(viewModel: mastodonConfirmEmailViewModel), from: nil, transition: .modal(animated: true, completion: nil))
+ _ = coordinator.present(scene: .mastodonConfirmEmail(viewModel: mastodonConfirmEmailViewModel), from: nil, transition: .modal(animated: true, completion: nil))
}
@objc private func showAccountList(_ sender: UIAction) {
let accountListViewModel = AccountListViewModel(context: context, authContext: viewModel.authContext)
- coordinator.present(scene: .accountList(viewModel: accountListViewModel), from: self, transition: .modal(animated: true, completion: nil))
+ _ = coordinator.present(scene: .accountList(viewModel: accountListViewModel), from: self, transition: .modal(animated: true, completion: nil))
}
@objc private func showProfileAction(_ sender: UIAction) {
@@ -347,12 +347,12 @@ extension HomeTimelineViewController {
guard let self = self else { return }
guard let textField = alertController?.textFields?.first else { return }
let profileViewModel = RemoteProfileViewModel(context: self.context, authContext: self.viewModel.authContext, userID: textField.text ?? "")
- self.coordinator.present(scene: .profile(viewModel: profileViewModel), from: self, transition: .show)
+ _ = self.coordinator.present(scene: .profile(viewModel: profileViewModel), from: self, transition: .show)
}
alertController.addAction(showAction)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
- coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
+ _ = coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
}
@objc private func showThreadAction(_ sender: UIAction) {
@@ -362,12 +362,12 @@ extension HomeTimelineViewController {
guard let self = self else { return }
guard let textField = alertController?.textFields?.first else { return }
let threadViewModel = RemoteThreadViewModel(context: self.context, authContext: self.viewModel.authContext, statusID: textField.text ?? "")
- self.coordinator.present(scene: .thread(viewModel: threadViewModel), from: self, transition: .show)
+ _ = self.coordinator.present(scene: .thread(viewModel: threadViewModel), from: self, transition: .show)
}
alertController.addAction(showAction)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
- coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
+ _ = coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
}
private func showNotification(_ sender: UIAction, notificationType: Mastodon.Entity.Notification.NotificationType) {
@@ -436,7 +436,7 @@ extension HomeTimelineViewController {
authContext: viewModel.authContext,
setting: currentSetting
)
- coordinator.present(
+ _ = coordinator.present(
scene: .settings(viewModel: settingsViewModel),
from: self,
transition: .modal(animated: true, completion: nil)
diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift
index 3efcd5cbe..3caafef3d 100644
--- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift
+++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift
@@ -74,7 +74,7 @@ final class HomeTimelineViewController: UIViewController, NeedsDependency, Media
return progressView
}()
- let refreshControl = UIRefreshControl()
+ let refreshControl = RefreshControl()
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s:", ((#file as NSString).lastPathComponent), #line, #function)
@@ -158,12 +158,7 @@ extension HomeTimelineViewController {
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.pinToParent()
// // layout publish progress
publishProgressView.translatesAutoresizingMaskIntoConstraints = false
@@ -388,17 +383,17 @@ extension HomeTimelineViewController {
@objc private func manuallySearchButtonPressed(_ sender: UIButton) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
let searchDetailViewModel = SearchDetailViewModel(authContext: viewModel.authContext)
- coordinator.present(scene: .searchDetail(viewModel: searchDetailViewModel), from: self, transition: .modal(animated: true, completion: nil))
+ _ = coordinator.present(scene: .searchDetail(viewModel: searchDetailViewModel), from: self, transition: .modal(animated: true, completion: nil))
}
@objc private func settingBarButtonItemPressed(_ sender: UIBarButtonItem) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
guard let setting = context.settingService.currentSetting.value else { return }
let settingsViewModel = SettingsViewModel(context: context, authContext: viewModel.authContext, setting: setting)
- coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil))
+ _ = coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil))
}
- @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) {
+ @objc private func refreshControlValueChanged(_ sender: RefreshControl) {
guard viewModel.loadLatestStateMachine.enter(HomeTimelineViewModel.LoadLatestState.Loading.self) else {
sender.endRefreshing()
return
diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadLatestState.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadLatestState.swift
index 41cea6b58..8bf1421b1 100644
--- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadLatestState.swift
+++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadLatestState.swift
@@ -62,7 +62,7 @@ extension HomeTimelineViewModel.LoadLatestState {
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
- guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
+ guard let viewModel else { return }
let latestFeedRecords = viewModel.fetchedResultsController.records.prefix(APIService.onceRequestStatusMaxCount)
let parentManagedObjectContext = viewModel.fetchedResultsController.fetchedResultsController.managedObjectContext
diff --git a/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift b/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift
index 400b4ee98..38b80fe28 100644
--- a/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift
+++ b/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift
@@ -47,12 +47,7 @@ extension HomeTimelineNavigationBarTitleView {
private func _init() {
containerView.translatesAutoresizingMaskIntoConstraints = false
addSubview(containerView)
- NSLayoutConstraint.activate([
- containerView.topAnchor.constraint(equalTo: topAnchor),
- containerView.leadingAnchor.constraint(equalTo: leadingAnchor),
- containerView.trailingAnchor.constraint(equalTo: trailingAnchor),
- containerView.bottomAnchor.constraint(equalTo: bottomAnchor),
- ])
+ containerView.pinToParent()
containerView.addArrangedSubview(logoButton)
button.translatesAutoresizingMaskIntoConstraints = false
diff --git a/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageView.swift b/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageView.swift
index 05f2ce70f..7df8861b0 100644
--- a/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageView.swift
+++ b/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageView.swift
@@ -9,6 +9,7 @@ import os.log
import func AVFoundation.AVMakeRect
import UIKit
import FLAnimatedImage
+import VisionKit
final class MediaPreviewImageView: UIScrollView {
@@ -28,9 +29,21 @@ final class MediaPreviewImageView: UIScrollView {
tapGestureRecognizer.numberOfTapsRequired = 2
return tapGestureRecognizer
}()
-
+
private var containerFrame: CGRect?
-
+
+ private var _interaction: UIInteraction? = {
+ if #available(iOS 16.0, *) {
+ return ImageAnalysisInteraction()
+ } else {
+ return nil
+ }
+ }()
+ @available(iOS 16.0, *)
+ var liveTextInteraction: ImageAnalysisInteraction {
+ _interaction as! ImageAnalysisInteraction
+ }
+
override init(frame: CGRect) {
super.init(frame: frame)
_init()
@@ -55,10 +68,13 @@ extension MediaPreviewImageView {
maximumZoomScale = 4.0
addSubview(imageView)
-
+
doubleTapGestureRecognizer.addTarget(self, action: #selector(MediaPreviewImageView.doubleTapGestureRecognizerHandler(_:)))
imageView.addGestureRecognizer(doubleTapGestureRecognizer)
-
+ if #available(iOS 16.0, *) {
+ imageView.addInteraction(liveTextInteraction)
+ }
+
delegate = self
}
@@ -112,23 +128,30 @@ extension MediaPreviewImageView {
// reset to normal
zoomScale = minimumZoomScale
- let imageViewSize = AVMakeRect(aspectRatio: image.size, insideRect: container.bounds).size
- let imageContentInset: UIEdgeInsets = {
- if imageViewSize.width == container.bounds.width {
- return UIEdgeInsets(top: 0.5 * (container.bounds.height - imageViewSize.height), left: 0, bottom: 0, right: 0)
- } else {
- return UIEdgeInsets(top: 0, left: 0.5 * (container.bounds.width - imageViewSize.width), bottom: 0, right: 0)
- }
- }()
+ let imageViewSize = AVMakeRect(aspectRatio: image.size, insideRect: container.bounds.inset(by: container.safeAreaInsets)).size
imageView.frame = CGRect(origin: .zero, size: imageViewSize)
if imageView.image == nil {
imageView.image = image
}
contentSize = imageViewSize
- contentInset = imageContentInset
centerScrollViewContents()
- contentOffset = CGPoint(x: -contentInset.left, y: -contentInset.top)
+
+ if #available(iOS 16.0, *) {
+ Task.detached(priority: .userInitiated) {
+ do {
+ let analysis = try await ImageAnalyzer.shared.analyze(image, configuration: ImageAnalyzer.Configuration([.text, .machineReadableCode]))
+ await MainActor.run {
+ self.liveTextInteraction.analysis = analysis
+ self.liveTextInteraction.preferredInteractionTypes = .automatic
+ }
+ } catch {
+ await MainActor.run {
+ self.liveTextInteraction.preferredInteractionTypes = []
+ }
+ }
+ }
+ }
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: setup image for container %s", ((#file as NSString).lastPathComponent), #line, #function, container.frame.debugDescription)
}
@@ -192,10 +215,7 @@ extension MediaPreviewImageView {
frame.size = realImageSize
imageView.frame = frame
- let screenSize = self.frame.size
- let offsetX = screenSize.width > realImageSize.width ? (screenSize.width - realImageSize.width) / 2 : 0
- let offsetY = screenSize.height > realImageSize.height ? (screenSize.height - realImageSize.height) / 2 : 0
- contentInset = UIEdgeInsets(top: offsetY, left: offsetX, bottom: offsetY, right: offsetX)
+ contentInset = self.safeAreaInsets
// The scroll view has zoomed, so you need to re-center the contents
let scrollViewSize = scrollViewVisibleSize
diff --git a/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageViewController.swift b/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageViewController.swift
index 127c4c0c0..513110d29 100644
--- a/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageViewController.swift
+++ b/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageViewController.swift
@@ -11,6 +11,7 @@ import Combine
import MastodonAsset
import MastodonLocalization
import FLAnimatedImage
+import VisionKit
protocol MediaPreviewImageViewControllerDelegate: AnyObject {
func mediaPreviewImageViewController(_ viewController: MediaPreviewImageViewController, tapGestureRecognizerDidTrigger tapGestureRecognizer: UITapGestureRecognizer)
@@ -31,7 +32,7 @@ final class MediaPreviewImageViewController: UIViewController {
let tapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
let longPressGestureRecognizer = UILongPressGestureRecognizer()
-
+
deinit {
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
previewImageView.imageView.af.cancelImageRequest()
@@ -42,7 +43,10 @@ extension MediaPreviewImageViewController {
override func viewDidLoad() {
super.viewDidLoad()
-
+
+ if #available(iOS 16.0, *) {
+ previewImageView.liveTextInteraction.delegate = self
+ }
previewImageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(previewImageView)
NSLayoutConstraint.activate([
@@ -53,7 +57,9 @@ extension MediaPreviewImageViewController {
])
tapGestureRecognizer.addTarget(self, action: #selector(MediaPreviewImageViewController.tapGestureRecognizerHandler(_:)))
+ tapGestureRecognizer.delegate = self
longPressGestureRecognizer.addTarget(self, action: #selector(MediaPreviewImageViewController.longPressGestureRecognizerHandler(_:)))
+ longPressGestureRecognizer.delegate = self
tapGestureRecognizer.require(toFail: previewImageView.doubleTapGestureRecognizer)
tapGestureRecognizer.require(toFail: longPressGestureRecognizer)
previewImageView.addGestureRecognizer(tapGestureRecognizer)
@@ -105,10 +111,54 @@ extension MediaPreviewImageViewController {
}
+extension MediaPreviewImageViewController: MediaPreviewPage {
+ func setShowingChrome(_ showingChrome: Bool) {
+ if #available(iOS 16.0, *) {
+ UIView.animate(withDuration: 0.3) {
+ self.previewImageView.liveTextInteraction.setSupplementaryInterfaceHidden(!showingChrome, animated: true)
+ }
+ }
+ }
+}
+
+// MARK: - ImageAnalysisInteractionDelegate
+@available(iOS 16.0, *)
+extension MediaPreviewImageViewController: ImageAnalysisInteractionDelegate {
+ func presentingViewController(for interaction: ImageAnalysisInteraction) -> UIViewController? {
+ self
+ }
+}
+
+// MARK: - UIGestureRecognizerDelegate
+extension MediaPreviewImageViewController: UIGestureRecognizerDelegate {
+ func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
+ if #available(iOS 16.0, *) {
+ let location = touch.location(in: previewImageView.imageView)
+ // for tap gestures, only items that can be tapped are relevant
+ if gestureRecognizer is UITapGestureRecognizer {
+ return !previewImageView.liveTextInteraction.hasSupplementaryInterface(at: location)
+ && !previewImageView.liveTextInteraction.hasDataDetector(at: location)
+ } else {
+ // for long press, block out everything
+ return !previewImageView.liveTextInteraction.hasInteractiveItem(at: location)
+ }
+ } else {
+ return true
+ }
+ }
+}
+
// MARK: - UIContextMenuInteractionDelegate
extension MediaPreviewImageViewController: UIContextMenuInteractionDelegate {
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
+
+ if #available(iOS 16.0, *) {
+ if previewImageView.liveTextInteraction.hasInteractiveItem(at: previewImageView.imageView.convert(location, from: previewImageView)) {
+ return nil
+ }
+ }
+
let previewProvider: UIContextMenuContentPreviewProvider = { () -> UIViewController? in
return nil
diff --git a/Mastodon/Scene/MediaPreview/MediaPreviewViewController.swift b/Mastodon/Scene/MediaPreview/MediaPreviewViewController.swift
index c6552bcba..dee6c00ea 100644
--- a/Mastodon/Scene/MediaPreview/MediaPreviewViewController.swift
+++ b/Mastodon/Scene/MediaPreview/MediaPreviewViewController.swift
@@ -65,12 +65,7 @@ extension MediaPreviewViewController {
pagingViewController.view.translatesAutoresizingMaskIntoConstraints = false
addChild(pagingViewController)
visualEffectView.contentView.addSubview(pagingViewController.view)
- NSLayoutConstraint.activate([
- visualEffectView.topAnchor.constraint(equalTo: pagingViewController.view.topAnchor),
- visualEffectView.bottomAnchor.constraint(equalTo: pagingViewController.view.bottomAnchor),
- visualEffectView.leadingAnchor.constraint(equalTo: pagingViewController.view.leadingAnchor),
- visualEffectView.trailingAnchor.constraint(equalTo: pagingViewController.view.trailingAnchor),
- ])
+ visualEffectView.pinTo(to: pagingViewController.view)
pagingViewController.didMove(toParent: self)
closeButtonBackground.translatesAutoresizingMaskIntoConstraints = false
@@ -137,6 +132,17 @@ extension MediaPreviewViewController {
}
}
.store(in: &disposeBag)
+
+ viewModel.$showingChrome
+ .receive(on: DispatchQueue.main)
+ .removeDuplicates()
+ .sink { [weak self] showingChrome in
+ UIView.animate(withDuration: 0.3) {
+ self?.setNeedsStatusBarAppearanceUpdate()
+ self?.closeButtonBackground.alpha = showingChrome ? 1 : 0
+ }
+ }
+ .store(in: &disposeBag)
// viewModel.$isPoping
// .receive(on: DispatchQueue.main)
@@ -153,6 +159,14 @@ extension MediaPreviewViewController {
}
+extension MediaPreviewViewController {
+
+ override var prefersStatusBarHidden: Bool {
+ !viewModel.showingChrome
+ }
+
+}
+
extension MediaPreviewViewController {
@objc private func closeButtonPressed(_ sender: UIButton) {
@@ -239,8 +253,11 @@ extension MediaPreviewViewController: MediaPreviewImageViewControllerDelegate {
let location = tapGestureRecognizer.location(in: viewController.previewImageView.imageView)
let isContainsTap = viewController.previewImageView.imageView.frame.contains(location)
- guard !isContainsTap else { return }
- dismiss(animated: true, completion: nil)
+ if isContainsTap {
+ self.viewModel.showingChrome.toggle()
+ } else {
+ dismiss(animated: true, completion: nil)
+ }
}
func mediaPreviewImageViewController(_ viewController: MediaPreviewImageViewController, longPressGestureRecognizerDidTrigger longPressGestureRecognizer: UILongPressGestureRecognizer) {
@@ -276,7 +293,7 @@ extension MediaPreviewViewController: MediaPreviewImageViewControllerDelegate {
title: L10n.Common.Alerts.SavePhotoFailure.title,
message: L10n.Common.Alerts.SavePhotoFailure.message
)
- self.coordinator.present(
+ _ = self.coordinator.present(
scene: .alertController(alertController: alertController),
from: self,
transition: .alertController(animated: true, completion: nil)
diff --git a/Mastodon/Scene/MediaPreview/MediaPreviewViewModel.swift b/Mastodon/Scene/MediaPreview/MediaPreviewViewModel.swift
index c43d24945..3f60b19e5 100644
--- a/Mastodon/Scene/MediaPreview/MediaPreviewViewModel.swift
+++ b/Mastodon/Scene/MediaPreview/MediaPreviewViewModel.swift
@@ -12,6 +12,10 @@ import CoreDataStack
import Pageboy
import MastodonCore
+protocol MediaPreviewPage: UIViewController {
+ func setShowingChrome(_ showingChrome: Bool)
+}
+
final class MediaPreviewViewModel: NSObject {
weak var mediaPreviewImageViewControllerDelegate: MediaPreviewImageViewControllerDelegate?
@@ -22,9 +26,12 @@ final class MediaPreviewViewModel: NSObject {
let transitionItem: MediaPreviewTransitionItem
@Published var currentPage: Int
+ @Published var showingChrome = true
// output
let viewControllers: [UIViewController]
+
+ private var disposeBag: Set = []
init(
context: AppContext,
@@ -34,7 +41,7 @@ final class MediaPreviewViewModel: NSObject {
self.context = context
self.item = item
var currentPage = 0
- var viewControllers: [UIViewController] = []
+ var viewControllers: [MediaPreviewPage] = []
switch item {
case .attachment(let previewContext):
currentPage = previewContext.initialIndex
@@ -106,6 +113,14 @@ final class MediaPreviewViewModel: NSObject {
self.currentPage = currentPage
self.transitionItem = transitionItem
super.init()
+
+ for viewController in viewControllers {
+ self.$showingChrome
+ .sink { [weak viewController] showingChrome in
+ viewController?.setShowingChrome(showingChrome)
+ }
+ .store(in: &disposeBag)
+ }
}
}
diff --git a/Mastodon/Scene/MediaPreview/Video/MediaPreviewVideoViewController.swift b/Mastodon/Scene/MediaPreview/Video/MediaPreviewVideoViewController.swift
index 7bdbbfed2..e924f38d4 100644
--- a/Mastodon/Scene/MediaPreview/Video/MediaPreviewVideoViewController.swift
+++ b/Mastodon/Scene/MediaPreview/Video/MediaPreviewVideoViewController.swift
@@ -39,23 +39,13 @@ extension MediaPreviewVideoViewController {
addChild(playerViewController)
playerViewController.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(playerViewController.view)
- NSLayoutConstraint.activate([
- playerViewController.view.centerXAnchor.constraint(equalTo: view.centerXAnchor),
- playerViewController.view.centerYAnchor.constraint(equalTo: view.centerYAnchor),
- playerViewController.view.widthAnchor.constraint(equalTo: view.widthAnchor),
- playerViewController.view.heightAnchor.constraint(equalTo: view.heightAnchor),
- ])
+ playerViewController.view.pinToParent()
playerViewController.didMove(toParent: self)
if let contentOverlayView = playerViewController.contentOverlayView {
previewImageView.translatesAutoresizingMaskIntoConstraints = false
contentOverlayView.addSubview(previewImageView)
- NSLayoutConstraint.activate([
- previewImageView.topAnchor.constraint(equalTo: contentOverlayView.topAnchor),
- previewImageView.leadingAnchor.constraint(equalTo: contentOverlayView.leadingAnchor),
- previewImageView.trailingAnchor.constraint(equalTo: contentOverlayView.trailingAnchor),
- previewImageView.bottomAnchor.constraint(equalTo: contentOverlayView.bottomAnchor),
- ])
+ previewImageView.pinToParent()
}
playerViewController.delegate = self
@@ -90,6 +80,12 @@ extension MediaPreviewVideoViewController {
}
}
+ override func viewDidLayoutSubviews() {
+ super.viewDidLayoutSubviews()
+
+ playerViewController.didMove(toParent: self)
+ }
+
}
// MARK: - ShareActivityProvider
@@ -111,6 +107,12 @@ extension MediaPreviewVideoViewController {
// }
//}
+extension MediaPreviewVideoViewController: MediaPreviewPage {
+ func setShowingChrome(_ showingChrome: Bool) {
+ // TODO: does this do anything?
+ }
+}
+
// MARK: - AVPlayerViewControllerDelegate
extension MediaPreviewVideoViewController: AVPlayerViewControllerDelegate {
diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift
index 00e2a9fc8..9b7416afc 100644
--- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift
+++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift
@@ -26,8 +26,8 @@ final class NotificationTimelineViewController: UIViewController, NeedsDependenc
var viewModel: NotificationTimelineViewModel!
- private(set) lazy var refreshControl: UIRefreshControl = {
- let refreshControl = UIRefreshControl()
+ private(set) lazy var refreshControl: RefreshControl = {
+ let refreshControl = RefreshControl()
refreshControl.addTarget(self, action: #selector(NotificationTimelineViewController.refreshControlValueChanged(_:)), for: .valueChanged)
return refreshControl
}()
@@ -55,12 +55,7 @@ extension NotificationTimelineViewController {
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.pinToParent()
tableView.delegate = self
viewModel.setupDiffableDataSource(
@@ -137,7 +132,7 @@ extension NotificationTimelineViewController: CellFrameCacheContainer {
extension NotificationTimelineViewController {
- @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) {
+ @objc private func refreshControlValueChanged(_ sender: RefreshControl) {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
Task {
diff --git a/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift b/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift
index a90da1e81..ae8a4933e 100644
--- a/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift
+++ b/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift
@@ -189,7 +189,7 @@ extension MastodonConfirmEmailViewController {
alertController.addAction(openEmailAction)
alertController.addAction(cancelAction)
alertController.preferredAction = openEmailAction
- self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
+ _ = self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
}
@objc private func resendButtonPressed(_ sender: UIButton) {
@@ -197,18 +197,17 @@ extension MastodonConfirmEmailViewController {
let resendAction = UIAlertAction(title: L10n.Scene.ConfirmEmail.DontReceiveEmail.resendEmail, style: .default) { _ in
let url = Mastodon.API.resendEmailURL(domain: self.viewModel.authenticateInfo.domain)
let viewModel = MastodonResendEmailViewModel(resendEmailURL: url, email: self.viewModel.email)
- self.coordinator.present(scene: .mastodonResendEmail(viewModel: viewModel), from: self, transition: .modal(animated: true, completion: nil))
+ _ = self.coordinator.present(scene: .mastodonResendEmail(viewModel: viewModel), from: self, transition: .modal(animated: true, completion: nil))
}
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default) { _ in
}
alertController.addAction(resendAction)
alertController.addAction(okAction)
- self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
+ _ = self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
}
func showEmailAppAlert() {
let clients = ThirdPartyMailClient.clients
- let application = UIApplication.shared
let availableClients = clients.filter { client -> Bool in
ThirdPartyMailer.isMailClientAvailable(client)
}
@@ -220,14 +219,14 @@ extension MastodonConfirmEmailViewController {
alertController.addAction(alertAction)
_ = availableClients.compactMap { client -> UIAlertAction in
let alertAction = UIAlertAction(title: client.name, style: .default) { _ in
- _ = ThirdPartyMailer.open(client, completionHandler: nil)
+ ThirdPartyMailer.open(client, completionHandler: nil)
}
alertController.addAction(alertAction)
return alertAction
}
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel, handler: nil)
alertController.addAction(cancelAction)
- self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
+ _ = self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
}
}
diff --git a/Mastodon/Scene/Onboarding/Login/ContentSizedTableView.swift b/Mastodon/Scene/Onboarding/Login/ContentSizedTableView.swift
new file mode 100644
index 000000000..e0c8a9228
--- /dev/null
+++ b/Mastodon/Scene/Onboarding/Login/ContentSizedTableView.swift
@@ -0,0 +1,22 @@
+//
+// MastodonLoginTableView.swift
+// Mastodon
+//
+// Created by Nathan Mattes on 13.11.22.
+//
+
+import UIKit
+
+// Source: https://stackoverflow.com/a/48623673
+final class ContentSizedTableView: UITableView {
+ override var contentSize:CGSize {
+ didSet {
+ invalidateIntrinsicContentSize()
+ }
+ }
+
+ override var intrinsicContentSize: CGSize {
+ layoutIfNeeded()
+ return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
+ }
+}
diff --git a/Mastodon/Scene/Onboarding/Login/MastodonLoginServerTableViewCell.swift b/Mastodon/Scene/Onboarding/Login/MastodonLoginServerTableViewCell.swift
new file mode 100644
index 000000000..2fb599015
--- /dev/null
+++ b/Mastodon/Scene/Onboarding/Login/MastodonLoginServerTableViewCell.swift
@@ -0,0 +1,12 @@
+//
+// MastodonLoginServerTableViewCell.swift
+// Mastodon
+//
+// Created by Nathan Mattes on 11.11.22.
+//
+
+import UIKit
+
+class MastodonLoginServerTableViewCell: UITableViewCell {
+ static let reuseIdentifier = "MastodonLoginServerTableViewCell"
+}
diff --git a/Mastodon/Scene/Onboarding/Login/MastodonLoginView.swift b/Mastodon/Scene/Onboarding/Login/MastodonLoginView.swift
new file mode 100644
index 000000000..d5f8b51d4
--- /dev/null
+++ b/Mastodon/Scene/Onboarding/Login/MastodonLoginView.swift
@@ -0,0 +1,151 @@
+//
+// MastodonLoginView.swift
+// Mastodon
+//
+// Created by Nathan Mattes on 10.11.22.
+//
+
+import UIKit
+import MastodonAsset
+import MastodonLocalization
+
+class MastodonLoginView: UIView {
+
+ // List with (filtered) domains
+
+ let titleLabel: UILabel
+ let subtitleLabel: UILabel
+ private let headerStackView: UIStackView
+
+ let searchTextField: UITextField
+ private let searchTextFieldLeftView: UIView
+ private let searchTextFieldMagnifyingGlass: UIImageView
+ private let searchContainerLeftPaddingView: UIView
+
+ let tableView: UITableView
+ let navigationActionView: NavigationActionView
+ var bottomConstraint: NSLayoutConstraint?
+
+ override init(frame: CGRect) {
+
+ titleLabel = UILabel()
+ titleLabel.font = MastodonLoginViewController.largeTitleFont
+ titleLabel.textColor = MastodonLoginViewController.largeTitleTextColor
+ titleLabel.text = L10n.Scene.Login.title
+ titleLabel.numberOfLines = 0
+
+ subtitleLabel = UILabel()
+ subtitleLabel.font = MastodonLoginViewController.subTitleFont
+ subtitleLabel.textColor = MastodonLoginViewController.subTitleTextColor
+ subtitleLabel.text = L10n.Scene.Login.subtitle
+ subtitleLabel.numberOfLines = 0
+
+ headerStackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
+ headerStackView.axis = .vertical
+ headerStackView.spacing = 16
+ headerStackView.translatesAutoresizingMaskIntoConstraints = false
+
+ searchTextFieldMagnifyingGlass = UIImageView(image: UIImage(
+ systemName: "magnifyingglass",
+ withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .regular)
+ ))
+ searchTextFieldMagnifyingGlass.tintColor = Asset.Colors.Label.secondary.color.withAlphaComponent(0.6)
+ searchTextFieldMagnifyingGlass.translatesAutoresizingMaskIntoConstraints = false
+
+ searchContainerLeftPaddingView = UIView()
+ searchContainerLeftPaddingView.translatesAutoresizingMaskIntoConstraints = false
+
+ searchTextFieldLeftView = UIView()
+ searchTextFieldLeftView.addSubview(searchTextFieldMagnifyingGlass)
+ searchTextFieldLeftView.addSubview(searchContainerLeftPaddingView)
+
+ searchTextField = UITextField()
+ searchTextField.translatesAutoresizingMaskIntoConstraints = false
+ searchTextField.backgroundColor = Asset.Scene.Onboarding.searchBarBackground.color
+ searchTextField.placeholder = L10n.Scene.Login.ServerSearchField.placeholder
+ searchTextField.leftView = searchTextFieldLeftView
+ searchTextField.leftViewMode = .always
+ searchTextField.layer.cornerRadius = 10
+ searchTextField.keyboardType = .URL
+ searchTextField.autocorrectionType = .no
+ searchTextField.autocapitalizationType = .none
+
+ tableView = ContentSizedTableView()
+ tableView.translatesAutoresizingMaskIntoConstraints = false
+ tableView.backgroundColor = Asset.Scene.Onboarding.textFieldBackground.color
+ tableView.keyboardDismissMode = .onDrag
+ tableView.layer.cornerRadius = 10
+
+ navigationActionView = NavigationActionView()
+ navigationActionView.translatesAutoresizingMaskIntoConstraints = false
+
+ super.init(frame: frame)
+
+ addSubview(headerStackView)
+ addSubview(searchTextField)
+ addSubview(tableView)
+ addSubview(navigationActionView)
+ backgroundColor = Asset.Scene.Onboarding.background.color
+
+ setupConstraints()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func setupConstraints() {
+
+ let bottomConstraint = safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: navigationActionView.bottomAnchor)
+
+ let constraints = [
+
+ headerStackView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor),
+ headerStackView.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor),
+ headerStackView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),
+
+ searchTextField.topAnchor.constraint(equalTo: headerStackView.bottomAnchor, constant: 32),
+ searchTextField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
+ searchTextField.heightAnchor.constraint(equalToConstant: 55),
+ trailingAnchor.constraint(equalTo: searchTextField.trailingAnchor, constant: 16),
+
+ searchTextFieldMagnifyingGlass.topAnchor.constraint(equalTo: searchTextFieldLeftView.topAnchor),
+ searchTextFieldMagnifyingGlass.leadingAnchor.constraint(equalTo: searchTextFieldLeftView.leadingAnchor, constant: 8),
+ searchTextFieldMagnifyingGlass.bottomAnchor.constraint(equalTo: searchTextFieldLeftView.bottomAnchor),
+
+ searchContainerLeftPaddingView.topAnchor.constraint(equalTo: searchTextFieldLeftView.topAnchor),
+ searchContainerLeftPaddingView.leadingAnchor.constraint(equalTo: searchTextFieldMagnifyingGlass.trailingAnchor),
+ searchContainerLeftPaddingView.trailingAnchor.constraint(equalTo: searchTextFieldLeftView.trailingAnchor),
+ searchContainerLeftPaddingView.bottomAnchor.constraint(equalTo: searchTextFieldLeftView.bottomAnchor),
+ searchContainerLeftPaddingView.widthAnchor.constraint(equalToConstant: 4).priority(.defaultHigh),
+
+ tableView.topAnchor.constraint(equalTo: searchTextField.bottomAnchor, constant: 2),
+ tableView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
+ trailingAnchor.constraint(equalTo: tableView.trailingAnchor, constant: 16),
+ tableView.bottomAnchor.constraint(lessThanOrEqualTo: navigationActionView.topAnchor),
+
+ navigationActionView.leadingAnchor.constraint(equalTo: leadingAnchor),
+ navigationActionView.trailingAnchor.constraint(equalTo: trailingAnchor),
+ bottomConstraint,
+ ]
+
+ self.bottomConstraint = bottomConstraint
+ NSLayoutConstraint.activate(constraints)
+ }
+
+ func updateCorners(numberOfResults: Int = 0) {
+
+ tableView.isHidden = (numberOfResults == 0)
+ tableView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
+
+ let maskedCorners: CACornerMask
+
+ if numberOfResults == 0 {
+ maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner]
+ } else {
+ maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
+ }
+
+ searchTextField.layer.maskedCorners = maskedCorners
+ }
+}
diff --git a/Mastodon/Scene/Onboarding/Login/MastodonLoginViewController.swift b/Mastodon/Scene/Onboarding/Login/MastodonLoginViewController.swift
new file mode 100644
index 000000000..e9965fdde
--- /dev/null
+++ b/Mastodon/Scene/Onboarding/Login/MastodonLoginViewController.swift
@@ -0,0 +1,288 @@
+//
+// MastodonLoginViewController.swift
+// Mastodon
+//
+// Created by Nathan Mattes on 09.11.22.
+//
+
+import UIKit
+import MastodonSDK
+import MastodonCore
+import MastodonAsset
+import Combine
+import AuthenticationServices
+
+protocol MastodonLoginViewControllerDelegate: AnyObject {
+ func backButtonPressed(_ viewController: MastodonLoginViewController)
+ func nextButtonPressed(_ viewController: MastodonLoginViewController)
+}
+
+enum MastodonLoginViewSection: Hashable {
+ case servers
+}
+
+class MastodonLoginViewController: UIViewController, NeedsDependency {
+
+ weak var delegate: MastodonLoginViewControllerDelegate?
+ var dataSource: UITableViewDiffableDataSource?
+ let viewModel: MastodonLoginViewModel
+ let authenticationViewModel: AuthenticationViewModel
+ var mastodonAuthenticationController: MastodonAuthenticationController?
+
+ weak var context: AppContext!
+ weak var coordinator: SceneCoordinator!
+
+ var disposeBag = Set()
+
+ var contentView: MastodonLoginView {
+ view as! MastodonLoginView
+ }
+
+ init(appContext: AppContext, authenticationViewModel: AuthenticationViewModel, sceneCoordinator: SceneCoordinator) {
+
+ viewModel = MastodonLoginViewModel(appContext: appContext)
+ self.authenticationViewModel = authenticationViewModel
+ self.context = appContext
+ self.coordinator = sceneCoordinator
+
+ super.init(nibName: nil, bundle: nil)
+ viewModel.delegate = self
+
+ navigationItem.hidesBackButton = true
+ }
+
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
+
+ override func loadView() {
+ let loginView = MastodonLoginView()
+
+ loginView.navigationActionView.nextButton.addTarget(self, action: #selector(MastodonLoginViewController.nextButtonPressed(_:)), for: .touchUpInside)
+ loginView.navigationActionView.backButton.addTarget(self, action: #selector(MastodonLoginViewController.backButtonPressed(_:)), for: .touchUpInside)
+ loginView.searchTextField.addTarget(self, action: #selector(MastodonLoginViewController.textfieldDidChange(_:)), for: .editingChanged)
+ loginView.tableView.delegate = self
+ loginView.tableView.register(MastodonLoginServerTableViewCell.self, forCellReuseIdentifier: MastodonLoginServerTableViewCell.reuseIdentifier)
+ loginView.navigationActionView.nextButton.isEnabled = false
+
+ view = loginView
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShowNotification(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
+ NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHideNotification(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
+
+ let dataSource = UITableViewDiffableDataSource(tableView: contentView.tableView) { [weak self] tableView, indexPath, itemIdentifier in
+ guard let cell = tableView.dequeueReusableCell(withIdentifier: MastodonLoginServerTableViewCell.reuseIdentifier, for: indexPath) as? MastodonLoginServerTableViewCell,
+ let self = self else {
+ fatalError("Wrong cell")
+ }
+
+ let server = self.viewModel.filteredServers[indexPath.row]
+ var configuration = cell.defaultContentConfiguration()
+ configuration.text = server.domain
+
+ cell.contentConfiguration = configuration
+ cell.accessoryType = .disclosureIndicator
+
+ cell.backgroundColor = Asset.Scene.Onboarding.textFieldBackground.color
+
+ return cell
+ }
+
+ contentView.tableView.dataSource = dataSource
+ self.dataSource = dataSource
+
+ contentView.updateCorners()
+
+ defer { setupNavigationBarBackgroundView() }
+ setupOnboardingAppearance()
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+
+ viewModel.updateServers()
+ }
+
+ override func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+
+ contentView.searchTextField.becomeFirstResponder()
+ }
+
+ //MARK: - Actions
+
+ @objc func backButtonPressed(_ sender: Any) {
+ contentView.searchTextField.resignFirstResponder()
+ delegate?.backButtonPressed(self)
+ }
+
+ @objc func nextButtonPressed(_ sender: Any) {
+ contentView.searchTextField.resignFirstResponder()
+ delegate?.nextButtonPressed(self)
+ }
+
+ @objc func login() {
+ guard let server = viewModel.selectedServer else { return }
+
+ authenticationViewModel
+ .authenticated
+ .asyncMap { domain, user -> Result in
+ do {
+ let result = try await self.context.authenticationService.activeMastodonUser(domain: domain, userID: user.id)
+ return .success(result)
+ } catch {
+ return .failure(error)
+ }
+ }
+ .receive(on: DispatchQueue.main)
+ .sink { [weak self] result in
+ guard let self = self else { return }
+ switch result {
+ case .failure(let error):
+ assertionFailure(error.localizedDescription)
+ case .success(let isActived):
+ assert(isActived)
+ self.coordinator.setup()
+ }
+ }
+ .store(in: &disposeBag)
+
+ authenticationViewModel.isAuthenticating.send(true)
+ context.apiService.createApplication(domain: server.domain)
+ .tryMap { response -> AuthenticationViewModel.AuthenticateInfo in
+ let application = response.value
+ guard let info = AuthenticationViewModel.AuthenticateInfo(
+ domain: server.domain,
+ application: application,
+ redirectURI: response.value.redirectURI ?? APIService.oauthCallbackURL
+ ) else {
+ throw APIService.APIError.explicit(.badResponse)
+ }
+ return info
+ }
+ .receive(on: DispatchQueue.main)
+ .sink { [weak self] completion in
+ guard let self = self else { return }
+ self.authenticationViewModel.isAuthenticating.send(false)
+
+ switch completion {
+ case .failure(let error):
+ let alert = UIAlertController.standardAlert(of: error)
+ self.present(alert, animated: true)
+ case .finished:
+ // do nothing. There's a subscriber above resulting in `coordinator.setup()`
+ break
+ }
+ } receiveValue: { [weak self] info in
+ guard let self else { return }
+ let authenticationController = MastodonAuthenticationController(
+ context: self.context,
+ authenticateURL: info.authorizeURL
+ )
+
+ self.mastodonAuthenticationController = authenticationController
+ authenticationController.authenticationSession?.presentationContextProvider = self
+ authenticationController.authenticationSession?.start()
+
+ self.authenticationViewModel.authenticate(
+ info: info,
+ pinCodePublisher: authenticationController.pinCodePublisher
+ )
+ }
+ .store(in: &disposeBag)
+ }
+
+ @objc func textfieldDidChange(_ textField: UITextField) {
+ viewModel.filterServers(withText: textField.text)
+
+
+ if let text = textField.text,
+ let domain = AuthenticationViewModel.parseDomain(from: text) {
+
+ viewModel.selectedServer = .init(domain: domain, instance: .init(domain: domain))
+ contentView.navigationActionView.nextButton.isEnabled = true
+ } else {
+ viewModel.selectedServer = nil
+ contentView.navigationActionView.nextButton.isEnabled = false
+ }
+ }
+
+ // MARK: - Notifications
+ @objc func keyboardWillShowNotification(_ notification: Notification) {
+
+ guard let userInfo = notification.userInfo,
+ let keyboardFrameValue = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue,
+ let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber
+ else { return }
+
+ // inspired by https://stackoverflow.com/a/30245044
+ let keyboardFrame = keyboardFrameValue.cgRectValue
+
+ let keyboardOrigin = view.convert(keyboardFrame.origin, from: nil)
+ let intersectionY = CGRectGetMaxY(view.frame) - keyboardOrigin.y;
+
+ if intersectionY >= 0 {
+ contentView.bottomConstraint?.constant = intersectionY - view.safeAreaInsets.bottom
+ }
+
+ UIView.animate(withDuration: duration.doubleValue, delay: 0, options: .curveEaseInOut) {
+ self.view.layoutIfNeeded()
+ }
+ }
+
+ @objc func keyboardWillHideNotification(_ notification: Notification) {
+
+ guard let userInfo = notification.userInfo,
+ let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber
+ else { return }
+
+ contentView.bottomConstraint?.constant = 0
+
+ UIView.animate(withDuration: duration.doubleValue, delay: 0, options: .curveEaseInOut) {
+ self.view.layoutIfNeeded()
+ }
+ }
+}
+
+// MARK: - OnboardingViewControllerAppearance
+extension MastodonLoginViewController: OnboardingViewControllerAppearance { }
+
+// MARK: - UITableViewDelegate
+extension MastodonLoginViewController: UITableViewDelegate {
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+ let server = viewModel.filteredServers[indexPath.row]
+ viewModel.selectedServer = server
+
+ contentView.searchTextField.text = server.domain
+ viewModel.filterServers(withText: " ")
+
+ contentView.navigationActionView.nextButton.isEnabled = true
+ tableView.deselectRow(at: indexPath, animated: true)
+ }
+}
+
+// MARK: - MastodonLoginViewModelDelegate
+extension MastodonLoginViewController: MastodonLoginViewModelDelegate {
+ func serversUpdated(_ viewModel: MastodonLoginViewModel) {
+ var snapshot = NSDiffableDataSourceSnapshot()
+
+ snapshot.appendSections([MastodonLoginViewSection.servers])
+ snapshot.appendItems(viewModel.filteredServers)
+
+ dataSource?.applySnapshot(snapshot, animated: false)
+
+ OperationQueue.main.addOperation {
+ let numberOfResults = viewModel.filteredServers.count
+ self.contentView.updateCorners(numberOfResults: numberOfResults)
+ }
+ }
+}
+
+// MARK: - ASWebAuthenticationPresentationContextProviding
+extension MastodonLoginViewController: ASWebAuthenticationPresentationContextProviding {
+ func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
+ return view.window!
+ }
+}
diff --git a/Mastodon/Scene/Onboarding/Login/MastodonLoginViewModel.swift b/Mastodon/Scene/Onboarding/Login/MastodonLoginViewModel.swift
new file mode 100644
index 000000000..61311a1b9
--- /dev/null
+++ b/Mastodon/Scene/Onboarding/Login/MastodonLoginViewModel.swift
@@ -0,0 +1,57 @@
+//
+// MastodonLoginViewModel.swift
+// Mastodon
+//
+// Created by Nathan Mattes on 11.11.22.
+//
+
+import Foundation
+import MastodonSDK
+import MastodonCore
+import Combine
+
+protocol MastodonLoginViewModelDelegate: AnyObject {
+ func serversUpdated(_ viewModel: MastodonLoginViewModel)
+}
+
+class MastodonLoginViewModel {
+
+ private var serverList: [Mastodon.Entity.Server] = []
+ var selectedServer: Mastodon.Entity.Server?
+ var filteredServers: [Mastodon.Entity.Server] = []
+
+ weak var appContext: AppContext?
+ weak var delegate: MastodonLoginViewModelDelegate?
+ var disposeBag = Set()
+
+ init(appContext: AppContext) {
+ self.appContext = appContext
+ }
+
+ func updateServers() {
+ appContext?.apiService.servers().sink(receiveCompletion: { [weak self] completion in
+ switch completion {
+ case .finished:
+ guard let self = self else { return }
+
+ self.delegate?.serversUpdated(self)
+ case .failure(let error):
+ print(error)
+ }
+ }, receiveValue: { content in
+ let servers = content.value
+ self.serverList = servers
+ }).store(in: &disposeBag)
+ }
+
+ func filterServers(withText query: String?) {
+ guard let query else {
+ filteredServers = serverList
+ delegate?.serversUpdated(self)
+ return
+ }
+
+ filteredServers = serverList.filter { $0.domain.lowercased().contains(query) }.sorted {$0.totalUsers > $1.totalUsers }
+ delegate?.serversUpdated(self)
+ }
+}
diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift
index eb26f75be..7e832ddca 100644
--- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift
+++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift
@@ -143,8 +143,7 @@ extension MastodonPickServerViewController {
viewModel.setupDiffableDataSource(
for: tableView,
dependency: self,
- pickServerServerSectionTableHeaderViewDelegate: self,
- pickServerCellDelegate: self
+ pickServerServerSectionTableHeaderViewDelegate: self
)
KeyboardResponderService
@@ -172,7 +171,7 @@ extension MastodonPickServerViewController {
let alertController = UIAlertController(for: error, title: "Error", preferredStyle: .alert)
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil)
alertController.addAction(okAction)
- self.coordinator.present(
+ _ = self.coordinator.present(
scene: .alertController(alertController: alertController),
from: nil,
transition: .alertController(animated: true, completion: nil)
@@ -271,59 +270,6 @@ extension MastodonPickServerViewController {
}
@objc private func nextButtonDidPressed(_ sender: UIButton) {
- switch viewModel.mode {
- case .signIn: doSignIn()
- case .signUp: doSignUp()
- }
- }
-
- private func doSignIn() {
- guard let server = viewModel.selectedServer.value else { return }
- authenticationViewModel.isAuthenticating.send(true)
- context.apiService.createApplication(domain: server.domain)
- .tryMap { response -> AuthenticationViewModel.AuthenticateInfo in
- let application = response.value
- guard let info = AuthenticationViewModel.AuthenticateInfo(
- domain: server.domain,
- application: application,
- redirectURI: response.value.redirectURI ?? APIService.oauthCallbackURL
- ) else {
- throw APIService.APIError.explicit(.badResponse)
- }
- return info
- }
- .receive(on: DispatchQueue.main)
- .sink { [weak self] completion in
- guard let self = self else { return }
- self.authenticationViewModel.isAuthenticating.send(false)
-
- switch completion {
- case .failure(let error):
- os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: sign in fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
- self.viewModel.error.send(error)
- case .finished:
- break
- }
- } receiveValue: { [weak self] info in
- guard let self = self else { return }
- let authenticationController = MastodonAuthenticationController(
- context: self.context,
- authenticateURL: info.authorizeURL
- )
-
- self.mastodonAuthenticationController = authenticationController
- authenticationController.authenticationSession?.presentationContextProvider = self
- authenticationController.authenticationSession?.start()
-
- self.authenticationViewModel.authenticate(
- info: info,
- pinCodePublisher: authenticationController.pinCodePublisher
- )
- }
- .store(in: &disposeBag)
- }
-
- private func doSignUp() {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
guard let server = viewModel.selectedServer.value else { return }
authenticationViewModel.isAuthenticating.send(true)
@@ -394,7 +340,7 @@ extension MastodonPickServerViewController {
instance: response.instance.value,
applicationToken: response.applicationToken.value
)
- self.coordinator.present(scene: .mastodonServerRules(viewModel: mastodonServerRulesViewModel), from: self, transition: .show)
+ _ = self.coordinator.present(scene: .mastodonServerRules(viewModel: mastodonServerRulesViewModel), from: self, transition: .show)
} else {
let mastodonRegisterViewModel = MastodonRegisterViewModel(
context: self.context,
@@ -403,7 +349,7 @@ extension MastodonPickServerViewController {
instance: response.instance.value,
applicationToken: response.applicationToken.value
)
- self.coordinator.present(scene: .mastodonRegister(viewModel: mastodonRegisterViewModel), from: nil, transition: .show)
+ _ = self.coordinator.present(scene: .mastodonRegister(viewModel: mastodonRegisterViewModel), from: nil, transition: .show)
}
}
.store(in: &disposeBag)
@@ -503,17 +449,5 @@ extension MastodonPickServerViewController: PickServerServerSectionTableHeaderVi
}
}
-// MARK: - PickServerCellDelegate
-extension MastodonPickServerViewController: PickServerCellDelegate {
-
-}
-
// MARK: - OnboardingViewControllerAppearance
extension MastodonPickServerViewController: OnboardingViewControllerAppearance { }
-
-// MARK: - ASWebAuthenticationPresentationContextProviding
-extension MastodonPickServerViewController: ASWebAuthenticationPresentationContextProviding {
- func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
- return view.window!
- }
-}
diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift
index 35de40b8f..7edbfd2ac 100644
--- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift
+++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift
@@ -13,8 +13,7 @@ extension MastodonPickServerViewModel {
func setupDiffableDataSource(
for tableView: UITableView,
dependency: NeedsDependency,
- pickServerServerSectionTableHeaderViewDelegate: PickServerServerSectionTableHeaderViewDelegate,
- pickServerCellDelegate: PickServerCellDelegate
+ pickServerServerSectionTableHeaderViewDelegate: PickServerServerSectionTableHeaderViewDelegate
) {
// set section header
serverSectionHeaderView.diffableDataSource = CategoryPickerSection.collectionViewDiffableDataSource(
@@ -34,8 +33,7 @@ extension MastodonPickServerViewModel {
// set tableView
diffableDataSource = PickServerSection.tableViewDiffableDataSource(
for: tableView,
- dependency: dependency,
- pickServerCellDelegate: pickServerCellDelegate
+ dependency: dependency
)
var snapshot = NSDiffableDataSourceSnapshot()
diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift
index 50c1d7aac..ebbcfe7fd 100644
--- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift
+++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift
@@ -17,12 +17,7 @@ import MastodonCore
import MastodonUI
class MastodonPickServerViewModel: NSObject {
-
- enum PickServerMode {
- case signUp
- case signIn
- }
-
+
enum EmptyStateViewState {
case none
case loading
@@ -34,7 +29,6 @@ class MastodonPickServerViewModel: NSObject {
let serverSectionHeaderView = PickServerServerSectionTableHeaderView()
// input
- let mode: PickServerMode
let context: AppContext
var categoryPickerItems: [CategoryPickerItem] = {
var items: [CategoryPickerItem] = []
@@ -72,9 +66,8 @@ class MastodonPickServerViewModel: NSObject {
let loadingIndexedServersError = CurrentValueSubject(nil)
let emptyStateViewState = CurrentValueSubject(.none)
- init(context: AppContext, mode: PickServerMode) {
+ init(context: AppContext) {
self.context = context
- self.mode = mode
super.init()
configure()
@@ -115,9 +108,7 @@ extension MastodonPickServerViewModel {
.map { indexedServers, selectCategoryItem, searchText -> [Mastodon.Entity.Server] in
// ignore approval required servers when sign-up
var indexedServers = indexedServers
- if self.mode == .signUp {
- indexedServers = indexedServers.filter { !$0.approvalRequired }
- }
+ indexedServers = indexedServers.filter { !$0.approvalRequired }
// Note:
// sort by calculate last week users count
// and make medium size (~800) server to top
diff --git a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift
index 669067770..4cbe77b9c 100644
--- a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift
+++ b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift
@@ -14,14 +14,8 @@ import Kanna
import MastodonAsset
import MastodonLocalization
-protocol PickServerCellDelegate: AnyObject {
-// func pickServerCell(_ cell: PickServerCell, expandButtonPressed button: UIButton)
-}
-
class PickServerCell: UITableViewCell {
- weak var delegate: PickServerCellDelegate?
-
var disposeBag = Set()
let containerView: UIStackView = {
@@ -88,7 +82,7 @@ class PickServerCell: UITableViewCell {
label.adjustsFontForContentSizeCategory = true
return label
}()
-
+
private var collapseConstraints: [NSLayoutConstraint] = []
private var expandConstraints: [NSLayoutConstraint] = []
diff --git a/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift b/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift
index 9d6cfc85f..aba5a87dc 100644
--- a/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift
+++ b/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift
@@ -19,13 +19,6 @@ class PickServerCategoryView: UIView {
return view
}()
- let emojiLabel: UILabel = {
- let label = UILabel()
- label.textAlignment = .center
- label.font = .systemFont(ofSize: 34, weight: .regular)
- return label
- }()
-
let titleLabel: UILabel = {
let label = UILabel()
label.textAlignment = .center
@@ -50,23 +43,18 @@ extension PickServerCategoryView {
private func configure() {
let container = UIStackView()
container.axis = .vertical
+ container.spacing = 2
container.distribution = .fillProportionally
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),
- ])
+ container.pinToParent()
- container.addArrangedSubview(emojiLabel)
container.addArrangedSubview(titleLabel)
highlightedIndicatorView.translatesAutoresizingMaskIntoConstraints = false
container.addArrangedSubview(highlightedIndicatorView)
NSLayoutConstraint.activate([
- highlightedIndicatorView.heightAnchor.constraint(equalToConstant: 3).priority(.required - 1),
+ highlightedIndicatorView.heightAnchor.constraint(equalToConstant: 3)//.priority(.required - 1),
])
titleLabel.setContentHuggingPriority(.required - 1, for: .vertical)
}
diff --git a/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift b/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift
index d50c62afe..ae8c16bb8 100644
--- a/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift
+++ b/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift
@@ -19,7 +19,7 @@ protocol PickServerServerSectionTableHeaderViewDelegate: AnyObject {
final class PickServerServerSectionTableHeaderView: UIView {
- static let collectionViewHeight: CGFloat = 88
+ static let collectionViewHeight: CGFloat = 30
static let searchTextFieldHeight: CGFloat = 38
static let spacing: CGFloat = 11
@@ -177,7 +177,6 @@ extension PickServerServerSectionTableHeaderView {
extension PickServerServerSectionTableHeaderView: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
- os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: indexPath: %s", ((#file as NSString).lastPathComponent), #line, #function, indexPath.debugDescription)
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
delegate?.pickServerServerSectionTableHeaderView(self, collectionView: collectionView, didSelectItemAt: indexPath)
}
@@ -205,5 +204,5 @@ extension PickServerServerSectionTableHeaderView: UITextFieldDelegate {
textField.resignFirstResponder()
return false
}
-
+
}
diff --git a/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterAvatarTableViewCell.swift b/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterAvatarTableViewCell.swift
deleted file mode 100644
index 154385e6a..000000000
--- a/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterAvatarTableViewCell.swift
+++ /dev/null
@@ -1,116 +0,0 @@
-//
-// MastodonRegisterAvatarTableViewCell.swift
-// Mastodon
-//
-// Created by MainasuK on 2022-1-5.
-//
-
-import UIKit
-import Combine
-import MastodonAsset
-import MastodonLocalization
-
-final class MastodonRegisterAvatarTableViewCell: UITableViewCell {
-
- static let containerSize = CGSize(width: 88, height: 88)
-
- var disposeBag = Set()
-
- let containerView: UIView = {
- let view = UIView()
- view.backgroundColor = .clear
- view.layer.masksToBounds = true
- view.layer.cornerCurve = .continuous
- view.layer.cornerRadius = 22
- return view
- }()
-
- let avatarButton: HighlightDimmableButton = {
- let button = HighlightDimmableButton()
- button.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
- button.setImage(Asset.Scene.Onboarding.avatarPlaceholder.image, for: .normal)
- return button
- }()
-
- let editBannerView: UIView = {
- let bannerView = UIView()
- bannerView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
- bannerView.isUserInteractionEnabled = false
-
- let label: UILabel = {
- let label = UILabel()
- label.textColor = .white
- label.text = L10n.Common.Controls.Actions.edit
- label.font = .systemFont(ofSize: 13, weight: .semibold)
- label.textAlignment = .center
- label.minimumScaleFactor = 0.5
- label.adjustsFontSizeToFitWidth = true
- return label
- }()
-
- label.translatesAutoresizingMaskIntoConstraints = false
- bannerView.addSubview(label)
- NSLayoutConstraint.activate([
- label.topAnchor.constraint(equalTo: bannerView.topAnchor),
- label.leadingAnchor.constraint(equalTo: bannerView.leadingAnchor),
- label.trailingAnchor.constraint(equalTo: bannerView.trailingAnchor),
- label.bottomAnchor.constraint(equalTo: bannerView.bottomAnchor),
- ])
-
- return bannerView
- }()
-
- override func prepareForReuse() {
- super.prepareForReuse()
-
- disposeBag.removeAll()
- }
-
- override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
- super.init(style: style, reuseIdentifier: reuseIdentifier)
- _init()
- }
-
- required init?(coder: NSCoder) {
- super.init(coder: coder)
- _init()
- }
-
-}
-
-extension MastodonRegisterAvatarTableViewCell {
-
- private func _init() {
- selectionStyle = .none
- backgroundColor = .clear
-
- containerView.translatesAutoresizingMaskIntoConstraints = false
- contentView.addSubview(containerView)
- NSLayoutConstraint.activate([
- containerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 22),
- containerView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
- contentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 8),
- containerView.widthAnchor.constraint(equalToConstant: MastodonRegisterAvatarTableViewCell.containerSize.width).priority(.required - 1),
- containerView.heightAnchor.constraint(equalToConstant: MastodonRegisterAvatarTableViewCell.containerSize.height).priority(.required - 1),
- ])
-
- avatarButton.translatesAutoresizingMaskIntoConstraints = false
- containerView.addSubview(avatarButton)
- NSLayoutConstraint.activate([
- avatarButton.topAnchor.constraint(equalTo: containerView.topAnchor),
- avatarButton.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
- avatarButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
- avatarButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
- ])
-
- editBannerView.translatesAutoresizingMaskIntoConstraints = false
- containerView.addSubview(editBannerView)
- NSLayoutConstraint.activate([
- editBannerView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
- editBannerView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
- editBannerView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
- editBannerView.heightAnchor.constraint(equalToConstant: 22),
- ])
- }
-
-}
diff --git a/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterPasswordHintTableViewCell.swift b/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterPasswordHintTableViewCell.swift
deleted file mode 100644
index 1324c2822..000000000
--- a/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterPasswordHintTableViewCell.swift
+++ /dev/null
@@ -1,50 +0,0 @@
-//
-// MastodonRegisterPasswordHintTableViewCell.swift
-// Mastodon
-//
-// Created by MainasuK on 2022-1-7.
-//
-
-import UIKit
-import MastodonAsset
-import MastodonLocalization
-
-final class MastodonRegisterPasswordHintTableViewCell: UITableViewCell {
-
- let passwordRuleLabel: UILabel = {
- let label = UILabel()
- label.font = .preferredFont(forTextStyle: .footnote)
- label.textColor = Asset.Colors.Label.secondary.color
- label.text = L10n.Scene.Register.Input.Password.hint
- return label
- }()
-
- override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
- super.init(style: style, reuseIdentifier: reuseIdentifier)
- _init()
- }
-
- required init?(coder: NSCoder) {
- super.init(coder: coder)
- _init()
- }
-
-}
-
-extension MastodonRegisterPasswordHintTableViewCell {
-
- private func _init() {
- selectionStyle = .none
- backgroundColor = .clear
-
- passwordRuleLabel.translatesAutoresizingMaskIntoConstraints = false
- contentView.addSubview(passwordRuleLabel)
- NSLayoutConstraint.activate([
- passwordRuleLabel.topAnchor.constraint(equalTo: contentView.topAnchor),
- passwordRuleLabel.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
- passwordRuleLabel.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
- passwordRuleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
- ])
- }
-
-}
diff --git a/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterTextFieldTableViewCell.swift b/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterTextFieldTableViewCell.swift
deleted file mode 100644
index 15f234834..000000000
--- a/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterTextFieldTableViewCell.swift
+++ /dev/null
@@ -1,142 +0,0 @@
-//
-// MastodonRegisterTextFieldTableViewCell.swift
-// Mastodon
-//
-// Created by MainasuK on 2022-1-7.
-//
-
-import UIKit
-import Combine
-import MastodonUI
-import MastodonAsset
-import MastodonLocalization
-
-final class MastodonRegisterTextFieldTableViewCell: UITableViewCell {
-
- static let textFieldHeight: CGFloat = 50
- static let textFieldLabelFont = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold), maximumPointSize: 22)
-
- var disposeBag = Set()
-
- let textFieldShadowContainer = ShadowBackgroundContainer()
- let textField: UITextField = {
- let textField = UITextField()
- textField.font = MastodonRegisterTextFieldTableViewCell.textFieldLabelFont
- textField.backgroundColor = Asset.Scene.Onboarding.textFieldBackground.color
- textField.layer.masksToBounds = true
- textField.layer.cornerRadius = 10
- textField.layer.cornerCurve = .continuous
- return textField
- }()
-
- override func prepareForReuse() {
- super.prepareForReuse()
-
- disposeBag.removeAll()
- textFieldShadowContainer.shadowColor = .black
- textFieldShadowContainer.shadowAlpha = 0.25
- resetTextField()
- }
-
- override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
- super.init(style: style, reuseIdentifier: reuseIdentifier)
- _init()
- }
-
- required init?(coder: NSCoder) {
- super.init(coder: coder)
- _init()
- }
-
-}
-
-extension MastodonRegisterTextFieldTableViewCell {
-
- private func _init() {
- selectionStyle = .none
- backgroundColor = .clear
-
- textFieldShadowContainer.translatesAutoresizingMaskIntoConstraints = false
- contentView.addSubview(textFieldShadowContainer)
- NSLayoutConstraint.activate([
- textFieldShadowContainer.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 6),
- textFieldShadowContainer.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
- textFieldShadowContainer.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
- contentView.bottomAnchor.constraint(equalTo: textFieldShadowContainer.bottomAnchor, constant: 6),
- ])
-
- textField.translatesAutoresizingMaskIntoConstraints = false
- textFieldShadowContainer.addSubview(textField)
- NSLayoutConstraint.activate([
- textField.topAnchor.constraint(equalTo: textFieldShadowContainer.topAnchor),
- textField.leadingAnchor.constraint(equalTo: textFieldShadowContainer.leadingAnchor),
- textField.trailingAnchor.constraint(equalTo: textFieldShadowContainer.trailingAnchor),
- textField.bottomAnchor.constraint(equalTo: textFieldShadowContainer.bottomAnchor),
- textField.heightAnchor.constraint(equalToConstant: MastodonRegisterTextFieldTableViewCell.textFieldHeight).priority(.required - 1),
- ])
-
- resetTextField()
- }
-
-}
-
-extension MastodonRegisterTextFieldTableViewCell {
- func resetTextField() {
- textField.keyboardType = .default
- textField.autocorrectionType = .default
- textField.autocapitalizationType = .none
- textField.attributedPlaceholder = nil
- textField.isSecureTextEntry = false
- textField.textAlignment = .natural
- textField.semanticContentAttribute = .unspecified
-
- let paddingRect = CGRect(x: 0, y: 0, width: 16, height: 10)
- textField.leftView = UIView(frame: paddingRect)
- textField.leftViewMode = .always
- textField.rightView = UIView(frame: paddingRect)
- textField.rightViewMode = .always
- }
-
- func setupTextViewRightView(text: String) {
- textField.rightView = {
- let containerView = UIView()
-
- let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 8, height: MastodonRegisterTextFieldTableViewCell.textFieldHeight))
- paddingView.translatesAutoresizingMaskIntoConstraints = false
- containerView.addSubview(paddingView)
- NSLayoutConstraint.activate([
- paddingView.topAnchor.constraint(equalTo: containerView.topAnchor),
- paddingView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
- paddingView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
- paddingView.widthAnchor.constraint(equalToConstant: 8).priority(.defaultHigh),
- ])
-
- let label = UILabel()
- label.font = MastodonRegisterTextFieldTableViewCell.textFieldLabelFont
- label.textColor = Asset.Colors.Label.primary.color
- label.text = text
- label.lineBreakMode = .byTruncatingMiddle
-
- label.translatesAutoresizingMaskIntoConstraints = false
- containerView.addSubview(label)
- NSLayoutConstraint.activate([
- label.topAnchor.constraint(equalTo: containerView.topAnchor),
- 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
- }()
- }
-
- func setupTextViewPlaceholder(text: String) {
- textField.attributedPlaceholder = NSAttributedString(
- string: text,
- attributes: [
- .foregroundColor: Asset.Colors.Label.secondary.color,
- .font: MastodonRegisterTextFieldTableViewCell.textFieldLabelFont
- ]
- )
- }
-}
diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterView.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterView.swift
index 2be7c61d7..4f28353b0 100644
--- a/Mastodon/Scene/Onboarding/Register/MastodonRegisterView.swift
+++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterView.swift
@@ -168,21 +168,27 @@ struct MastodonRegisterView: View {
func body(content: Content) -> some View {
ZStack {
- let shadowColor: Color = {
+ let borderColor: 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)
+ case .empty: return Color(Asset.Scene.Onboarding.textFieldBackground.color)
+ case .invalid: return Color(Asset.Colors.TextField.invalid.color)
+ case .valid: return Color(Asset.Scene.Onboarding.textFieldBackground.color)
}
}()
+
Color(Asset.Scene.Onboarding.textFieldBackground.color)
.cornerRadius(10)
- .shadow(color: shadowColor, radius: 1, x: 0, y: 2)
- .animation(.easeInOut, value: validateState)
+ .shadow(color: .black.opacity(0.125), radius: 1, x: 0, y: 2)
+
content
.padding()
.background(Color(Asset.Scene.Onboarding.textFieldBackground.color))
.cornerRadius(10)
+ .overlay(
+ RoundedRectangle(cornerRadius: 10)
+ .stroke(borderColor, lineWidth: 1)
+ .animation(.easeInOut, value: validateState)
+ )
}
}
}
diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController+Avatar.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController+Avatar.swift
index 9260f9e21..81924d812 100644
--- a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController+Avatar.swift
+++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController+Avatar.swift
@@ -49,7 +49,7 @@ extension MastodonRegisterViewController: PHPickerViewControllerDelegate {
let alertController = UIAlertController(for: error, title: "", preferredStyle: .alert)
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil)
alertController.addAction(okAction)
- self.coordinator.present(
+ _ = self.coordinator.present(
scene: .alertController(alertController: alertController),
from: nil,
transition: .alertController(animated: true, completion: nil)
diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift
index 9b10bd48e..f239e59b7 100644
--- a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift
+++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift
@@ -85,12 +85,7 @@ extension MastodonRegisterViewController {
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),
- ])
+ hostingViewController.view.pinToParent()
navigationActionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(navigationActionView)
@@ -145,7 +140,7 @@ extension MastodonRegisterViewController {
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(
+ _ = self.coordinator.present(
scene: .alertController(alertController: alertController),
from: nil,
transition: .alertController(animated: true, completion: nil)
@@ -322,7 +317,7 @@ extension MastodonRegisterViewController {
)
}()
let viewModel = MastodonConfirmEmailViewModel(context: self.context, email: email, authenticateInfo: self.viewModel.authenticateInfo, userToken: userToken, updateCredentialQuery: updateCredentialQuery)
- self.coordinator.present(scene: .mastodonConfirmEmail(viewModel: viewModel), from: self, transition: .show)
+ _ = self.coordinator.present(scene: .mastodonConfirmEmail(viewModel: viewModel), from: self, transition: .show)
}
.store(in: &disposeBag)
}
diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel+Diffable.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel+Diffable.swift
index dda59843a..b3e2a2cdb 100644
--- a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel+Diffable.swift
+++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel+Diffable.swift
@@ -10,145 +10,6 @@ import Combine
import MastodonAsset
import MastodonLocalization
-extension MastodonRegisterViewModel {
- func setupDiffableDataSource(
- tableView: UITableView
- ) {
- tableView.register(OnboardingHeadlineTableViewCell.self, forCellReuseIdentifier: String(describing: OnboardingHeadlineTableViewCell.self))
- tableView.register(MastodonRegisterAvatarTableViewCell.self, forCellReuseIdentifier: String(describing: MastodonRegisterAvatarTableViewCell.self))
- tableView.register(MastodonRegisterTextFieldTableViewCell.self, forCellReuseIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self))
- tableView.register(MastodonRegisterPasswordHintTableViewCell.self, forCellReuseIdentifier: String(describing: MastodonRegisterPasswordHintTableViewCell.self))
-
- diffableDataSource = UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item in
- switch item {
- case .header(let domain):
- let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: OnboardingHeadlineTableViewCell.self), for: indexPath) as! OnboardingHeadlineTableViewCell
- cell.titleLabel.text = L10n.Scene.Register.letsGetYouSetUpOnDomain(domain)
- cell.subTitleLabel.isHidden = true
- return cell
- case .avatar:
- let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterAvatarTableViewCell.self), for: indexPath) as! MastodonRegisterAvatarTableViewCell
- self.configureAvatar(cell: cell)
- return cell
- case .name:
- let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self), for: indexPath) as! MastodonRegisterTextFieldTableViewCell
- cell.setupTextViewPlaceholder(text: L10n.Scene.Register.Input.DisplayName.placeholder)
- cell.textField.keyboardType = .default
- cell.textField.autocapitalizationType = .words
- cell.textField.text = self.name
- NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.textField)
- .receive(on: DispatchQueue.main)
- .compactMap { notification in
- guard let textField = notification.object as? UITextField else {
- assertionFailure()
- return nil
- }
- return textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
- }
- .assign(to: \.name, on: self)
- .store(in: &cell.disposeBag)
- return cell
- case .username:
- let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self), for: indexPath) as! MastodonRegisterTextFieldTableViewCell
- cell.setupTextViewRightView(text: "@" + self.domain)
- cell.setupTextViewPlaceholder(text: L10n.Scene.Register.Input.Username.placeholder)
- cell.textField.keyboardType = .alphabet
- cell.textField.autocorrectionType = .no
- cell.textField.text = self.username
- cell.textField.textAlignment = .left
- cell.textField.semanticContentAttribute = .forceLeftToRight
- NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.textField)
- .receive(on: DispatchQueue.main)
- .compactMap { notification in
- guard let textField = notification.object as? UITextField else {
- assertionFailure()
- return nil
- }
- return textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
- }
- .assign(to: \.username, on: self)
- .store(in: &cell.disposeBag)
- self.configureTextFieldCell(cell: cell, validateState: self.$usernameValidateState)
- return cell
- case .email:
- let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self), for: indexPath) as! MastodonRegisterTextFieldTableViewCell
- cell.setupTextViewPlaceholder(text: L10n.Scene.Register.Input.Email.placeholder)
- cell.textField.keyboardType = .emailAddress
- cell.textField.autocorrectionType = .no
- cell.textField.text = self.email
- NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.textField)
- .receive(on: DispatchQueue.main)
- .compactMap { notification in
- guard let textField = notification.object as? UITextField else {
- assertionFailure()
- return nil
- }
- return textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
- }
- .assign(to: \.email, on: self)
- .store(in: &cell.disposeBag)
- self.configureTextFieldCell(cell: cell, validateState: self.$emailValidateState)
- return cell
- case .password:
- let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self), for: indexPath) as! MastodonRegisterTextFieldTableViewCell
- cell.setupTextViewPlaceholder(text: L10n.Scene.Register.Input.Password.placeholder)
- cell.textField.keyboardType = .alphabet
- cell.textField.autocorrectionType = .no
- cell.textField.isSecureTextEntry = true
- cell.textField.text = self.password
- cell.textField.textAlignment = .left
- cell.textField.semanticContentAttribute = .forceLeftToRight
- NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.textField)
- .receive(on: DispatchQueue.main)
- .compactMap { notification in
- guard let textField = notification.object as? UITextField else {
- assertionFailure()
- return nil
- }
- return textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
- }
- .assign(to: \.password, on: self)
- .store(in: &cell.disposeBag)
- self.configureTextFieldCell(cell: cell, validateState: self.$passwordValidateState)
- return cell
- case .hint:
- let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterPasswordHintTableViewCell.self), for: indexPath) as! MastodonRegisterPasswordHintTableViewCell
- return cell
- case .reason:
- let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self), for: indexPath) as! MastodonRegisterTextFieldTableViewCell
- cell.setupTextViewPlaceholder(text: L10n.Scene.Register.Input.Invite.registrationUserInviteRequest)
- cell.textField.keyboardType = .default
- cell.textField.text = self.reason
- NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.textField)
- .receive(on: DispatchQueue.main)
- .compactMap { notification in
- guard let textField = notification.object as? UITextField else {
- assertionFailure()
- return nil
- }
- return textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
- }
- .assign(to: \.reason, on: self)
- .store(in: &cell.disposeBag)
- self.configureTextFieldCell(cell: cell, validateState: self.$reasonValidateState)
- return cell
- default:
- assertionFailure()
- return UITableViewCell()
- }
- }
-
- var snapshot = NSDiffableDataSourceSnapshot()
- snapshot.appendSections([.main])
- snapshot.appendItems([.header(domain: domain)], toSection: .main)
- snapshot.appendItems([.avatar, .name, .username, .email, .password, .hint], toSection: .main)
- if approvalRequired {
- snapshot.appendItems([.reason], toSection: .main)
- }
- diffableDataSource?.applySnapshot(snapshot, animated: false, completion: nil)
- }
-}
-
extension MastodonRegisterViewModel {
private func configureAvatar(cell: MastodonRegisterAvatarTableViewCell) {
self.$avatarImage
diff --git a/Mastodon/Scene/Onboarding/ResendEmail/MastodonResendEmailViewController.swift b/Mastodon/Scene/Onboarding/ResendEmail/MastodonResendEmailViewController.swift
index 178d489be..d6870785c 100644
--- a/Mastodon/Scene/Onboarding/ResendEmail/MastodonResendEmailViewController.swift
+++ b/Mastodon/Scene/Onboarding/ResendEmail/MastodonResendEmailViewController.swift
@@ -49,12 +49,7 @@ extension MastodonResendEmailViewController {
webView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(webView)
- NSLayoutConstraint.activate([
- webView.topAnchor.constraint(equalTo: view.topAnchor),
- webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
- webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
- ])
+ webView.pinToParent()
let request = URLRequest(url: viewModel.resendEmailURL)
webView.navigationDelegate = self.viewModel.navigationDelegate
diff --git a/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewController.swift b/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewController.swift
index 0d4e27d98..ea0b58e28 100644
--- a/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewController.swift
+++ b/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewController.swift
@@ -69,12 +69,7 @@ extension MastodonServerRulesViewController {
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.pinToParent()
navigationActionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(navigationActionView)
@@ -127,7 +122,7 @@ extension MastodonServerRulesViewController {
instance: viewModel.instance,
applicationToken: viewModel.applicationToken
)
- coordinator.present(scene: .mastodonRegister(viewModel: viewModel), from: self, transition: .show)
+ _ = coordinator.present(scene: .mastodonRegister(viewModel: viewModel), from: self, transition: .show)
}
}
diff --git a/Mastodon/Scene/Onboarding/Share/NavigationActionView.swift b/Mastodon/Scene/Onboarding/Share/NavigationActionView.swift
index 47a1afbfc..fce853959 100644
--- a/Mastodon/Scene/Onboarding/Share/NavigationActionView.swift
+++ b/Mastodon/Scene/Onboarding/Share/NavigationActionView.swift
@@ -88,21 +88,11 @@ extension NavigationActionView {
backButton.translatesAutoresizingMaskIntoConstraints = false
backButtonShadowContainer.addSubview(backButton)
- NSLayoutConstraint.activate([
- backButton.topAnchor.constraint(equalTo: backButtonShadowContainer.topAnchor),
- backButton.leadingAnchor.constraint(equalTo: backButtonShadowContainer.leadingAnchor),
- backButton.trailingAnchor.constraint(equalTo: backButtonShadowContainer.trailingAnchor),
- backButton.bottomAnchor.constraint(equalTo: backButtonShadowContainer.bottomAnchor),
- ])
+ backButton.pinToParent()
nextButton.translatesAutoresizingMaskIntoConstraints = false
- nextButtonShadowContainer.addSubview(nextButton)
- NSLayoutConstraint.activate([
- nextButton.topAnchor.constraint(equalTo: nextButtonShadowContainer.topAnchor),
- nextButton.leadingAnchor.constraint(equalTo: nextButtonShadowContainer.leadingAnchor),
- nextButton.trailingAnchor.constraint(equalTo: nextButtonShadowContainer.trailingAnchor),
- nextButton.bottomAnchor.constraint(equalTo: nextButtonShadowContainer.bottomAnchor),
- ])
+ nextButtonShadowContainer.addSubview(nextButton)
+ nextButton.pinToParent()
// We want the back button to be as small as possible, allowing the next button to take up
// any remaining space. .defaultLow is "the priority level at which a button hugs its
diff --git a/Mastodon/Scene/Onboarding/Share/OnboardingNavigationController.swift b/Mastodon/Scene/Onboarding/Share/OnboardingNavigationController.swift
index 537102dc9..ac2e5b171 100644
--- a/Mastodon/Scene/Onboarding/Share/OnboardingNavigationController.swift
+++ b/Mastodon/Scene/Onboarding/Share/OnboardingNavigationController.swift
@@ -20,12 +20,7 @@ extension OnboardingNavigationController {
gradientBorderView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(gradientBorderView)
- NSLayoutConstraint.activate([
- gradientBorderView.topAnchor.constraint(equalTo: view.topAnchor),
- gradientBorderView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- gradientBorderView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
- gradientBorderView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
- ])
+ gradientBorderView.pinToParent()
updateBorderViewDisplay()
}
diff --git a/Mastodon/Scene/Onboarding/Welcome/View/WelcomeIllustrationView.swift b/Mastodon/Scene/Onboarding/Welcome/View/WelcomeIllustrationView.swift
index 0530539a2..1fca1afef 100644
--- a/Mastodon/Scene/Onboarding/Welcome/View/WelcomeIllustrationView.swift
+++ b/Mastodon/Scene/Onboarding/Welcome/View/WelcomeIllustrationView.swift
@@ -96,12 +96,7 @@ extension WelcomeIllustrationView {
].forEach { imageView in
imageView.translatesAutoresizingMaskIntoConstraints = false
addSubview(imageView)
- NSLayoutConstraint.activate([
- imageView.topAnchor.constraint(equalTo: cloudBaseImageView.topAnchor),
- imageView.leadingAnchor.constraint(equalTo: cloudBaseImageView.leadingAnchor),
- imageView.trailingAnchor.constraint(equalTo: cloudBaseImageView.trailingAnchor),
- imageView.bottomAnchor.constraint(equalTo: cloudBaseImageView.bottomAnchor),
- ])
+ imageView.pinTo(to: cloudBaseImageView)
}
aspectLayoutConstraint = cloudBaseImageView.widthAnchor.constraint(equalTo: cloudBaseImageView.heightAnchor, multiplier: layout.artworkImageSize.width / layout.artworkImageSize.height)
diff --git a/Mastodon/Scene/Onboarding/Welcome/WelcomeViewController.swift b/Mastodon/Scene/Onboarding/Welcome/WelcomeViewController.swift
index a7703bfaa..241b1eacc 100644
--- a/Mastodon/Scene/Onboarding/Welcome/WelcomeViewController.swift
+++ b/Mastodon/Scene/Onboarding/Welcome/WelcomeViewController.swift
@@ -128,22 +128,12 @@ extension WelcomeViewController {
signUpButtonShadowView.translatesAutoresizingMaskIntoConstraints = false
buttonContainer.addSubview(signUpButtonShadowView)
buttonContainer.sendSubviewToBack(signUpButtonShadowView)
- NSLayoutConstraint.activate([
- signUpButtonShadowView.topAnchor.constraint(equalTo: signUpButton.topAnchor),
- signUpButtonShadowView.leadingAnchor.constraint(equalTo: signUpButton.leadingAnchor),
- signUpButtonShadowView.trailingAnchor.constraint(equalTo: signUpButton.trailingAnchor),
- signUpButtonShadowView.bottomAnchor.constraint(equalTo: signUpButton.bottomAnchor),
- ])
+ signUpButtonShadowView.pinTo(to: signUpButton)
signInButtonShadowView.translatesAutoresizingMaskIntoConstraints = false
buttonContainer.addSubview(signInButtonShadowView)
buttonContainer.sendSubviewToBack(signInButtonShadowView)
- NSLayoutConstraint.activate([
- signInButtonShadowView.topAnchor.constraint(equalTo: signInButton.topAnchor),
- signInButtonShadowView.leadingAnchor.constraint(equalTo: signInButton.leadingAnchor),
- signInButtonShadowView.trailingAnchor.constraint(equalTo: signInButton.trailingAnchor),
- signInButtonShadowView.bottomAnchor.constraint(equalTo: signInButton.bottomAnchor),
- ])
+ signInButtonShadowView.pinTo(to: signInButton)
signUpButton.addTarget(self, action: #selector(signUpButtonDidClicked(_:)), for: .touchUpInside)
signInButton.addTarget(self, action: #selector(signInButtonDidClicked(_:)), for: .touchUpInside)
@@ -249,7 +239,7 @@ extension WelcomeViewController {
logoImageView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
logoImageView.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor, constant: 35),
view.readableContentGuide.trailingAnchor.constraint(equalTo: logoImageView.trailingAnchor, constant: 35),
- logoImageView.heightAnchor.constraint(equalTo: logoImageView.widthAnchor, multiplier: 65.4/265.1),
+ logoImageView.heightAnchor.constraint(equalTo: logoImageView.widthAnchor, multiplier: 75.0/269.0),
])
logoImageView.setContentHuggingPriority(.defaultHigh, for: .vertical)
}
@@ -323,12 +313,12 @@ extension WelcomeViewController {
extension WelcomeViewController {
@objc
private func signUpButtonDidClicked(_ sender: UIButton) {
- coordinator.present(scene: .mastodonPickServer(viewMode: MastodonPickServerViewModel(context: context, mode: .signUp)), from: self, transition: .show)
+ _ = coordinator.present(scene: .mastodonPickServer(viewMode: MastodonPickServerViewModel(context: context)), from: self, transition: .show)
}
@objc
private func signInButtonDidClicked(_ sender: UIButton) {
- coordinator.present(scene: .mastodonPickServer(viewMode: MastodonPickServerViewModel(context: context, mode: .signIn)), from: self, transition: .show)
+ _ = coordinator.present(scene: .mastodonLogin, from: self, transition: .show)
}
@objc
diff --git a/Mastodon/Scene/Profile/About/Cell/ProfileFieldCollectionViewCell.swift b/Mastodon/Scene/Profile/About/Cell/ProfileFieldCollectionViewCell.swift
index ed6f68fec..1ed76a485 100644
--- a/Mastodon/Scene/Profile/About/Cell/ProfileFieldCollectionViewCell.swift
+++ b/Mastodon/Scene/Profile/About/Cell/ProfileFieldCollectionViewCell.swift
@@ -26,6 +26,16 @@ final class ProfileFieldCollectionViewCell: UICollectionViewCell {
let keyMetaLabel = MetaLabel(style: .profileFieldName)
let valueMetaLabel = MetaLabel(style: .profileFieldValue)
+ let checkmark = UIImageView(image: Asset.Editing.checkmark.image.withRenderingMode(.alwaysTemplate))
+ var checkmarkPopoverString: String? = nil;
+ let tapGesture = UITapGestureRecognizer();
+ private var _editMenuInteraction: Any? = nil
+ @available(iOS 16, *)
+ fileprivate var editMenuInteraction: UIEditMenuInteraction {
+ _editMenuInteraction = _editMenuInteraction ?? UIEditMenuInteraction(delegate: self)
+ return _editMenuInteraction as! UIEditMenuInteraction
+ }
+
override func prepareForReuse() {
super.prepareForReuse()
@@ -47,6 +57,17 @@ final class ProfileFieldCollectionViewCell: UICollectionViewCell {
extension ProfileFieldCollectionViewCell {
private func _init() {
+ // Setup colors
+ checkmark.tintColor = Asset.Scene.Profile.About.bioAboutFieldVerifiedCheckmark.color;
+
+ // Setup gestures
+ tapGesture.addTarget(self, action: #selector(ProfileFieldCollectionViewCell.didTapCheckmark(_:)))
+ checkmark.addGestureRecognizer(tapGesture)
+ checkmark.isUserInteractionEnabled = true
+ if #available(iOS 16, *) {
+ checkmark.addInteraction(editMenuInteraction)
+ }
+
// containerStackView: V - [ metaContainer | plainContainer ]
let containerStackView = UIStackView()
containerStackView.axis = .vertical
@@ -63,19 +84,62 @@ extension ProfileFieldCollectionViewCell {
bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor, constant: 11),
])
- // metaContainer: V - [ keyMetaLabel | valueMetaLabel ]
+ // metaContainer: V - [ keyMetaLabel | valueContainer ]
let metaContainer = UIStackView()
metaContainer.axis = .vertical
metaContainer.spacing = 2
containerStackView.addArrangedSubview(metaContainer)
+ // valueContainer: H - [ valueMetaLabel | checkmark ]
+ let valueContainer = UIStackView()
+ valueContainer.axis = .horizontal
+ valueContainer.spacing = 2
+
metaContainer.addArrangedSubview(keyMetaLabel)
- metaContainer.addArrangedSubview(valueMetaLabel)
+ valueContainer.addArrangedSubview(valueMetaLabel)
+ valueContainer.addArrangedSubview(checkmark)
+ metaContainer.addArrangedSubview(valueContainer)
keyMetaLabel.linkDelegate = self
valueMetaLabel.linkDelegate = self
}
+ @objc public func didTapCheckmark(_ recognizer: UITapGestureRecognizer) {
+ if #available(iOS 16, *) {
+ editMenuInteraction.presentEditMenu(with: UIEditMenuConfiguration(identifier: nil, sourcePoint: recognizer.location(in: checkmark)))
+ } else {
+ guard let editMenuLabel = checkmarkPopoverString else { return }
+
+ self.isUserInteractionEnabled = true
+ self.becomeFirstResponder()
+
+ UIMenuController.shared.menuItems = [
+ UIMenuItem(
+ title: editMenuLabel,
+ action: #selector(dismissVerifiedMenu)
+ )
+ ]
+ UIMenuController.shared.showMenu(from: checkmark, rect: checkmark.bounds)
+ }
+ }
+}
+
+// UIMenuController boilerplate
+@available(iOS, deprecated: 16, message: "Can be removed when target version is >=16 -- boilerplate to maintain compatibility with UIMenuController")
+extension ProfileFieldCollectionViewCell {
+ override var canBecomeFirstResponder: Bool { true }
+
+ override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
+ if action == #selector(dismissVerifiedMenu) {
+ return true
+ }
+
+ return super.canPerformAction(action, withSender: sender)
+ }
+
+ @objc public func dismissVerifiedMenu() {
+ UIMenuController.shared.hideMenu()
+ }
}
// MARK: - MetaLabelDelegate
@@ -85,3 +149,16 @@ extension ProfileFieldCollectionViewCell: MetaLabelDelegate {
delegate?.profileFieldCollectionViewCell(self, metaLebel: metaLabel, didSelectMeta: meta)
}
}
+
+// MARK: UIEditMenuInteractionDelegate
+@available(iOS 16.0, *)
+extension ProfileFieldCollectionViewCell: UIEditMenuInteractionDelegate {
+ func editMenuInteraction(_ interaction: UIEditMenuInteraction, menuFor configuration: UIEditMenuConfiguration, suggestedActions: [UIMenuElement]) -> UIMenu? {
+ guard let editMenuLabel = checkmarkPopoverString else { return UIMenu(children: []) }
+ return UIMenu(children: [UIAction(title: editMenuLabel) { _ in return }])
+ }
+
+ func editMenuInteraction(_ interaction: UIEditMenuInteraction, targetRectFor configuration: UIEditMenuConfiguration) -> CGRect {
+ return checkmark.frame
+ }
+}
diff --git a/Mastodon/Scene/Profile/About/ProfileAboutViewController.swift b/Mastodon/Scene/Profile/About/ProfileAboutViewController.swift
index eb1e6b39c..fe92d2ac4 100644
--- a/Mastodon/Scene/Profile/About/ProfileAboutViewController.swift
+++ b/Mastodon/Scene/Profile/About/ProfileAboutViewController.swift
@@ -60,12 +60,7 @@ extension ProfileAboutViewController {
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.pinToParent()
collectionView.delegate = self
viewModel.setupDiffableDataSource(
diff --git a/Mastodon/Scene/Profile/About/ProfileAboutViewModel.swift b/Mastodon/Scene/Profile/About/ProfileAboutViewModel.swift
index 68a3d0fea..044894b8a 100644
--- a/Mastodon/Scene/Profile/About/ProfileAboutViewModel.swift
+++ b/Mastodon/Scene/Profile/About/ProfileAboutViewModel.swift
@@ -52,7 +52,7 @@ final class ProfileAboutViewModel {
$emojiMeta
)
.map { fields, emojiMeta in
- fields.map { ProfileFieldItem.FieldValue(name: $0.name, value: $0.value, emojiMeta: emojiMeta) }
+ fields.map { ProfileFieldItem.FieldValue(name: $0.name, value: $0.value, verifiedAt: $0.verifiedAt, emojiMeta: emojiMeta) }
}
.assign(to: &profileInfo.$fields)
@@ -72,6 +72,7 @@ final class ProfileAboutViewModel {
ProfileFieldItem.FieldValue(
name: field.name,
value: field.value,
+ verifiedAt: field.verifiedAt,
emojiMeta: [:] // no use for editing
)
} ?? []
@@ -92,7 +93,7 @@ extension ProfileAboutViewModel {
func appendFieldItem() {
var fields = profileInfoEditing.fields
guard fields.count < ProfileHeaderViewModel.maxProfileFieldCount else { return }
- fields.append(ProfileFieldItem.FieldValue(name: "", value: "", emojiMeta: [:]))
+ fields.append(ProfileFieldItem.FieldValue(name: "", value: "", verifiedAt: nil, emojiMeta: [:]))
profileInfoEditing.fields = fields
}
@@ -112,7 +113,7 @@ extension ProfileAboutViewModel: ProfileViewModelEditable {
let isFieldsEqual: Bool = {
let originalFields = self.accountForEdit?.source?.fields?.compactMap { field in
- ProfileFieldItem.FieldValue(name: field.name, value: field.value, emojiMeta: [:])
+ ProfileFieldItem.FieldValue(name: field.name, value: field.value, verifiedAt: nil, emojiMeta: [:])
} ?? []
let editFields = profileInfoEditing.fields
guard editFields.count == originalFields.count else { return false }
diff --git a/Mastodon/Scene/Profile/Bookmark/BookmarkViewController.swift b/Mastodon/Scene/Profile/Bookmark/BookmarkViewController.swift
index 5edec2618..458c47335 100644
--- a/Mastodon/Scene/Profile/Bookmark/BookmarkViewController.swift
+++ b/Mastodon/Scene/Profile/Bookmark/BookmarkViewController.swift
@@ -64,12 +64,7 @@ extension BookmarkViewController {
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.pinToParent()
tableView.delegate = self
viewModel.setupDiffableDataSource(
diff --git a/Mastodon/Scene/Profile/FamiliarFollowers/FamiliarFollowersViewController.swift b/Mastodon/Scene/Profile/FamiliarFollowers/FamiliarFollowersViewController.swift
index b6d6f8313..3595303d9 100644
--- a/Mastodon/Scene/Profile/FamiliarFollowers/FamiliarFollowersViewController.swift
+++ b/Mastodon/Scene/Profile/FamiliarFollowers/FamiliarFollowersViewController.swift
@@ -53,12 +53,7 @@ extension FamiliarFollowersViewController {
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.pinToParent()
tableView.delegate = self
viewModel.setupDiffableDataSource(
diff --git a/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift b/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift
index c15adcf83..19f25f3ad 100644
--- a/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift
+++ b/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift
@@ -67,12 +67,7 @@ extension FavoriteViewController {
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.pinToParent()
tableView.delegate = self
viewModel.setupDiffableDataSource(
diff --git a/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+State.swift b/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+State.swift
index 803a9d45e..c2dc27201 100644
--- a/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+State.swift
+++ b/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+State.swift
@@ -46,7 +46,7 @@ extension FavoriteViewModel {
extension FavoriteViewModel.State {
class Initial: FavoriteViewModel.State {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
- guard let viewModel = viewModel else { return false }
+ guard viewModel != nil else { return false }
switch stateClass {
case is Reloading.Type:
return true
@@ -130,13 +130,12 @@ extension FavoriteViewModel.State {
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
- guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
+ guard let viewModel else { return }
if previousState is Reloading {
maxID = nil
}
-
Task {
do {
let response = try await viewModel.context.apiService.favoritedStatuses(
diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift
index 190fa27e5..c111d13d0 100644
--- a/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift
+++ b/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift
@@ -58,12 +58,7 @@ extension FollowerListViewController {
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.pinToParent()
tableView.delegate = self
viewModel.setupDiffableDataSource(
diff --git a/Mastodon/Scene/Profile/Following/FollowingListViewController.swift b/Mastodon/Scene/Profile/Following/FollowingListViewController.swift
index e16b600c2..3fa65918a 100644
--- a/Mastodon/Scene/Profile/Following/FollowingListViewController.swift
+++ b/Mastodon/Scene/Profile/Following/FollowingListViewController.swift
@@ -58,12 +58,7 @@ extension FollowingListViewController {
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.pinToParent()
tableView.delegate = self
viewModel.setupDiffableDataSource(
diff --git a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift
index c5dbbecd4..7ca819b41 100644
--- a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift
+++ b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift
@@ -60,7 +60,8 @@ final class ProfileHeaderViewController: UIViewController, NeedsDependency, Medi
// private var isAdjustBannerImageViewForSafeAreaInset = false
private var containerSafeAreaInset: UIEdgeInsets = .zero
-
+
+ private var currentImageType = ImageType.avatar
private(set) lazy var imagePicker: PHPickerViewController = {
var configuration = PHPickerConfiguration()
configuration.filter = .images
@@ -125,7 +126,9 @@ extension ProfileHeaderViewController {
}
.store(in: &disposeBag)
- profileHeaderView.editAvatarButtonOverlayIndicatorView.menu = createAvatarContextMenu()
+ profileHeaderView.editBannerButton.menu = createImageContextMenu(.banner)
+ profileHeaderView.editBannerButton.showsMenuAsPrimaryAction = true
+ profileHeaderView.editAvatarButtonOverlayIndicatorView.menu = createImageContextMenu(.avatar)
profileHeaderView.editAvatarButtonOverlayIndicatorView.showsMenuAsPrimaryAction = true
profileHeaderView.delegate = self
@@ -156,6 +159,9 @@ extension ProfileHeaderViewController {
viewModel.$isUpdating
.assign(to: \.isUpdating, on: profileHeaderView.viewModel)
.store(in: &disposeBag)
+ viewModel.profileInfoEditing.$header
+ .assign(to: \.headerImageEditing, on: profileHeaderView.viewModel)
+ .store(in: &disposeBag)
viewModel.profileInfoEditing.$avatar
.assign(to: \.avatarImageEditing, on: profileHeaderView.viewModel)
.store(in: &disposeBag)
@@ -173,7 +179,7 @@ extension ProfileHeaderViewController {
profileHeaderView.viewModel.viewDidAppear.send()
// set display after view appear
- profileHeaderView.setupAvatarOverlayViews()
+ profileHeaderView.setupImageOverlayViews()
}
override func viewDidLayoutSubviews() {
@@ -185,11 +191,16 @@ extension ProfileHeaderViewController {
}
extension ProfileHeaderViewController {
- private func createAvatarContextMenu() -> UIMenu {
+ fileprivate enum ImageType {
+ case avatar
+ case banner
+ }
+ private func createImageContextMenu(_ type: ImageType) -> UIMenu {
var children: [UIMenuElement] = []
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 }
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: mediaSelectionType: .photoLibaray", ((#file as NSString).lastPathComponent), #line, #function)
+ self.currentImageType = type
self.present(self.imagePicker, animated: true, completion: nil)
}
children.append(photoLibraryAction)
@@ -197,6 +208,7 @@ extension ProfileHeaderViewController {
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 }
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: mediaSelectionType: .camera", ((#file as NSString).lastPathComponent), #line, #function)
+ self.currentImageType = type
self.present(self.imagePickerController, animated: true, completion: nil)
})
children.append(cameraAction)
@@ -204,6 +216,7 @@ extension ProfileHeaderViewController {
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 }
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: mediaSelectionType: .browse", ((#file as NSString).lastPathComponent), #line, #function)
+ self.currentImageType = type
self.present(self.documentPickerController, animated: true, completion: nil)
}
children.append(browseAction)
@@ -215,7 +228,13 @@ extension ProfileHeaderViewController {
DispatchQueue.main.async {
let cropController = CropViewController(croppingStyle: .default, image: image)
cropController.delegate = self
- cropController.setAspectRatioPreset(.presetSquare, animated: true)
+ switch self.currentImageType {
+ case .banner:
+ cropController.customAspectRatio = CGSize(width: 3, height: 1)
+ cropController.setAspectRatioPreset(.presetCustom, animated: true)
+ case .avatar:
+ cropController.setAspectRatioPreset(.presetSquare, animated: true)
+ }
cropController.aspectRatioPickerButtonHidden = true
cropController.aspectRatioLockEnabled = true
pickerViewController.dismiss(animated: true, completion: {
@@ -443,7 +462,12 @@ extension ProfileHeaderViewController: UIDocumentPickerDelegate {
// MARK: - CropViewControllerDelegate
extension ProfileHeaderViewController: CropViewControllerDelegate {
public func cropViewController(_ cropViewController: CropViewController, didCropToImage image: UIImage, withRect cropRect: CGRect, angle: Int) {
- viewModel.profileInfoEditing.avatar = image
+ switch currentImageType {
+ case .banner:
+ viewModel.profileInfoEditing.header = image
+ case .avatar:
+ viewModel.profileInfoEditing.avatar = image
+ }
cropViewController.dismiss(animated: true, completion: nil)
}
}
diff --git a/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift b/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift
index 65f15efa7..c66789cca 100644
--- a/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift
+++ b/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift
@@ -18,6 +18,7 @@ import MastodonUI
final class ProfileHeaderViewModel {
static let avatarImageMaxSizeInPixel = CGSize(width: 400, height: 400)
+ static let bannerImageMaxSizeInPixel = CGSize(width: 1500, height: 500)
static let maxProfileFieldCount = 4
var disposeBag = Set()
@@ -52,6 +53,9 @@ final class ProfileHeaderViewModel {
.sink { [weak self] account in
guard let self = self else { return }
guard let account = account else { return }
+ // banner
+ self.profileInfo.header = nil
+ self.profileInfoEditing.header = nil
// avatar
self.profileInfo.avatar = nil
self.profileInfoEditing.avatar = nil
@@ -72,6 +76,7 @@ final class ProfileHeaderViewModel {
extension ProfileHeaderViewModel {
class ProfileInfo {
// input
+ @Published var header: UIImage?
@Published var avatar: UIImage?
@Published var name: String?
@Published var note: String?
@@ -99,6 +104,7 @@ extension ProfileHeaderViewModel: ProfileViewModelEditable {
var isEdited: Bool {
guard isEditing else { return false }
+ guard profileInfoEditing.header == nil else { return true }
guard profileInfoEditing.avatar == nil else { return true }
guard profileInfo.name == profileInfoEditing.name else { return true }
guard profileInfo.note == profileInfoEditing.note else { return true }
diff --git a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView+ViewModel.swift b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView+ViewModel.swift
index c51ccfab3..5095d7ac0 100644
--- a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView+ViewModel.swift
+++ b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView+ViewModel.swift
@@ -28,6 +28,7 @@ extension ProfileHeaderView {
@Published var emojiMeta: MastodonContent.Emojis = [:]
@Published var headerImageURL: URL?
+ @Published var headerImageEditing: UIImage?
@Published var avatarImageURL: URL?
@Published var avatarImageEditing: UIImage?
@@ -61,14 +62,19 @@ extension ProfileHeaderView.ViewModel {
func bind(view: ProfileHeaderView) {
// header
- Publishers.CombineLatest(
+ Publishers.CombineLatest4(
$headerImageURL,
+ $headerImageEditing,
+ $isEditing,
viewDidAppear
)
- .sink { headerImageURL, _ in
+ .sink { headerImageURL, headerImageEditing, isEditing, _ in
view.bannerImageView.af.cancelImageRequest()
- let placeholder = UIImage.placeholder(color: ProfileHeaderView.bannerImageViewPlaceholderColor)
- guard let bannerImageURL = headerImageURL else {
+ let defaultPlaceholder = UIImage.placeholder(color: ProfileHeaderView.bannerImageViewPlaceholderColor)
+ let placeholder = isEditing ? (headerImageEditing ?? defaultPlaceholder) : defaultPlaceholder
+ guard let bannerImageURL = headerImageURL,
+ !isEditing || headerImageEditing == nil
+ else {
view.bannerImageView.image = placeholder
return
}
@@ -262,22 +268,29 @@ extension ProfileHeaderView {
animator.addAnimations {
self.bannerImageViewOverlayVisualEffectView.backgroundColor = ProfileHeaderView.bannerImageViewOverlayViewBackgroundNormalColor
self.nameTextFieldBackgroundView.backgroundColor = .clear
+ self.editBannerButton.alpha = 0
self.editAvatarBackgroundView.alpha = 0
}
animator.addCompletion { _ in
+ self.editBannerButton.isHidden = true
self.editAvatarBackgroundView.isHidden = true
+ self.bannerImageViewSingleTapGestureRecognizer.isEnabled = true
}
case .editing:
nameMetaText.textView.alpha = 0
nameTextField.isEnabled = true
nameTextField.alpha = 1
+ editBannerButton.isHidden = false
+ editBannerButton.alpha = 0
editAvatarBackgroundView.isHidden = false
editAvatarBackgroundView.alpha = 0
bioMetaText.textView.backgroundColor = .clear
+ bannerImageViewSingleTapGestureRecognizer.isEnabled = false
animator.addAnimations {
self.bannerImageViewOverlayVisualEffectView.backgroundColor = ProfileHeaderView.bannerImageViewOverlayViewBackgroundEditingColor
self.nameTextFieldBackgroundView.backgroundColor = Asset.Scene.Profile.Banner.nameEditBackgroundGray.color
+ self.editBannerButton.alpha = 1
self.editAvatarBackgroundView.alpha = 1
self.bioMetaText.textView.backgroundColor = Asset.Scene.Profile.Banner.bioEditBackgroundGray.color
}
diff --git a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift
index a2194758b..cf2443552 100644
--- a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift
+++ b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift
@@ -50,6 +50,7 @@ final class ProfileHeaderView: UIView {
return viewModel
}()
+ let bannerImageViewSingleTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
let bannerContainerView = UIView()
let bannerImageView: UIImageView = {
let imageView = UIImageView()
@@ -101,7 +102,9 @@ final class ProfileHeaderView: UIView {
return button
}()
- func setupAvatarOverlayViews() {
+ func setupImageOverlayViews() {
+ editBannerButton.tintColor = .white
+
editAvatarBackgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.6)
editAvatarButtonOverlayIndicatorView.tintColor = .white
}
@@ -113,6 +116,13 @@ final class ProfileHeaderView: UIView {
return visualEffectView
}()
+ let editBannerButton: HighlightDimmableButton = {
+ let button = HighlightDimmableButton()
+ button.setImage(UIImage(systemName: "photo", withConfiguration: UIImage.SymbolConfiguration(pointSize: 28)), for: .normal)
+ button.tintColor = .clear
+ return button
+ }()
+
let editAvatarBackgroundView: UIView = {
let view = UIView()
view.backgroundColor = .clear // set value after view appeared
@@ -265,13 +275,13 @@ extension ProfileHeaderView {
bannerImageViewOverlayVisualEffectView.translatesAutoresizingMaskIntoConstraints = false
bannerImageView.addSubview(bannerImageViewOverlayVisualEffectView)
- NSLayoutConstraint.activate([
- bannerImageViewOverlayVisualEffectView.topAnchor.constraint(equalTo: bannerImageView.topAnchor),
- bannerImageViewOverlayVisualEffectView.leadingAnchor.constraint(equalTo: bannerImageView.leadingAnchor),
- bannerImageViewOverlayVisualEffectView.trailingAnchor.constraint(equalTo: bannerImageView.trailingAnchor),
- bannerImageViewOverlayVisualEffectView.bottomAnchor.constraint(equalTo: bannerImageView.bottomAnchor),
- ])
-
+ bannerImageViewOverlayVisualEffectView.pinToParent()
+
+ editBannerButton.translatesAutoresizingMaskIntoConstraints = false
+ bannerContainerView.addSubview(editBannerButton)
+ editBannerButton.pinTo(to: bannerImageView)
+ bannerContainerView.isUserInteractionEnabled = true
+
// follows you
followsYouBlurEffectView.translatesAutoresizingMaskIntoConstraints = false
addSubview(followsYouBlurEffectView)
@@ -286,12 +296,7 @@ extension ProfileHeaderView {
followsYouVibrantEffectView.translatesAutoresizingMaskIntoConstraints = false
followsYouBlurEffectView.contentView.addSubview(followsYouVibrantEffectView)
- NSLayoutConstraint.activate([
- followsYouVibrantEffectView.topAnchor.constraint(equalTo: followsYouBlurEffectView.topAnchor),
- followsYouVibrantEffectView.leadingAnchor.constraint(equalTo: followsYouBlurEffectView.leadingAnchor),
- followsYouVibrantEffectView.trailingAnchor.constraint(equalTo: followsYouBlurEffectView.trailingAnchor),
- followsYouVibrantEffectView.bottomAnchor.constraint(equalTo: followsYouBlurEffectView.bottomAnchor),
- ])
+ followsYouVibrantEffectView.pinTo(to: followsYouBlurEffectView)
followsYouLabel.translatesAutoresizingMaskIntoConstraints = false
followsYouVibrantEffectView.contentView.addSubview(followsYouLabel)
@@ -327,30 +332,15 @@ extension ProfileHeaderView {
avatarImageViewOverlayVisualEffectView.translatesAutoresizingMaskIntoConstraints = false
avatarImageViewBackgroundView.addSubview(avatarImageViewOverlayVisualEffectView)
- NSLayoutConstraint.activate([
- avatarImageViewOverlayVisualEffectView.topAnchor.constraint(equalTo: avatarImageViewBackgroundView.topAnchor),
- avatarImageViewOverlayVisualEffectView.leadingAnchor.constraint(equalTo: avatarImageViewBackgroundView.leadingAnchor),
- avatarImageViewOverlayVisualEffectView.trailingAnchor.constraint(equalTo: avatarImageViewBackgroundView.trailingAnchor),
- avatarImageViewOverlayVisualEffectView.bottomAnchor.constraint(equalTo: avatarImageViewBackgroundView.bottomAnchor),
- ])
+ avatarImageViewOverlayVisualEffectView.pinToParent()
editAvatarBackgroundView.translatesAutoresizingMaskIntoConstraints = false
avatarButton.addSubview(editAvatarBackgroundView)
- NSLayoutConstraint.activate([
- editAvatarBackgroundView.topAnchor.constraint(equalTo: avatarButton.topAnchor),
- editAvatarBackgroundView.leadingAnchor.constraint(equalTo: avatarButton.leadingAnchor),
- editAvatarBackgroundView.trailingAnchor.constraint(equalTo: avatarButton.trailingAnchor),
- editAvatarBackgroundView.bottomAnchor.constraint(equalTo: avatarButton.bottomAnchor),
- ])
+ editAvatarBackgroundView.pinToParent()
editAvatarButtonOverlayIndicatorView.translatesAutoresizingMaskIntoConstraints = false
editAvatarBackgroundView.addSubview(editAvatarButtonOverlayIndicatorView)
- NSLayoutConstraint.activate([
- editAvatarButtonOverlayIndicatorView.topAnchor.constraint(equalTo: editAvatarBackgroundView.topAnchor),
- editAvatarButtonOverlayIndicatorView.leadingAnchor.constraint(equalTo: editAvatarBackgroundView.leadingAnchor),
- editAvatarButtonOverlayIndicatorView.trailingAnchor.constraint(equalTo: editAvatarBackgroundView.trailingAnchor),
- editAvatarButtonOverlayIndicatorView.bottomAnchor.constraint(equalTo: editAvatarBackgroundView.bottomAnchor),
- ])
+ editAvatarButtonOverlayIndicatorView.pinToParent()
editAvatarBackgroundView.isUserInteractionEnabled = true
avatarButton.isUserInteractionEnabled = true
@@ -436,11 +426,8 @@ extension ProfileHeaderView {
relationshipActionButton.translatesAutoresizingMaskIntoConstraints = false
relationshipActionButtonShadowContainer.addSubview(relationshipActionButton)
+ relationshipActionButton.pinToParent()
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),
relationshipActionButton.widthAnchor.constraint(greaterThanOrEqualToConstant: ProfileHeaderView.friendshipActionButtonSize.width).priority(.required - 1),
relationshipActionButton.heightAnchor.constraint(equalToConstant: ProfileHeaderView.friendshipActionButtonSize.height).priority(.defaultHigh),
])
@@ -455,8 +442,7 @@ extension ProfileHeaderView {
statusDashboardView.delegate = self
bioMetaText.textView.delegate = self
bioMetaText.textView.linkDelegate = self
-
- let bannerImageViewSingleTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
+
bannerImageView.addGestureRecognizer(bannerImageViewSingleTapGestureRecognizer)
bannerImageViewSingleTapGestureRecognizer.addTarget(self, action: #selector(ProfileHeaderView.bannerImageViewDidPressed(_:)))
diff --git a/Mastodon/Scene/Profile/Paging/ProfilePagingViewController.swift b/Mastodon/Scene/Profile/Paging/ProfilePagingViewController.swift
index db92617b7..27c539d18 100644
--- a/Mastodon/Scene/Profile/Paging/ProfilePagingViewController.swift
+++ b/Mastodon/Scene/Profile/Paging/ProfilePagingViewController.swift
@@ -99,12 +99,7 @@ extension ProfilePagingViewController {
if let buttonBarView = self.buttonBarView {
buttonBarShadowView.translatesAutoresizingMaskIntoConstraints = false
view.insertSubview(buttonBarShadowView, belowSubview: buttonBarView)
- NSLayoutConstraint.activate([
- buttonBarShadowView.topAnchor.constraint(equalTo: buttonBarView.topAnchor),
- buttonBarShadowView.leadingAnchor.constraint(equalTo: buttonBarView.leadingAnchor),
- buttonBarShadowView.trailingAnchor.constraint(equalTo: buttonBarView.trailingAnchor),
- buttonBarShadowView.bottomAnchor.constraint(equalTo: buttonBarView.bottomAnchor),
- ])
+ buttonBarShadowView.pinTo(to: buttonBarView)
viewModel.$needsSetupBottomShadow
.receive(on: DispatchQueue.main)
diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift
index 3ce1fd33a..1184fb3d7 100644
--- a/Mastodon/Scene/Profile/ProfileViewController.swift
+++ b/Mastodon/Scene/Profile/ProfileViewController.swift
@@ -51,6 +51,7 @@ final class ProfileViewController: UIViewController, NeedsDependency, MediaPrevi
action: #selector(ProfileViewController.settingBarButtonItemPressed(_:))
)
barButtonItem.tintColor = .white
+ barButtonItem.accessibilityLabel = L10n.Common.Controls.Actions.settings
return barButtonItem
}()
@@ -62,6 +63,7 @@ final class ProfileViewController: UIViewController, NeedsDependency, MediaPrevi
action: #selector(ProfileViewController.shareBarButtonItemPressed(_:))
)
barButtonItem.tintColor = .white
+ barButtonItem.accessibilityLabel = L10n.Common.Controls.Actions.share
return barButtonItem
}()
@@ -73,6 +75,7 @@ final class ProfileViewController: UIViewController, NeedsDependency, MediaPrevi
action: #selector(ProfileViewController.favoriteBarButtonItemPressed(_:))
)
barButtonItem.tintColor = .white
+ barButtonItem.accessibilityLabel = L10n.Scene.Favorite.title
return barButtonItem
}()
@@ -84,23 +87,26 @@ final class ProfileViewController: UIViewController, NeedsDependency, MediaPrevi
action: #selector(ProfileViewController.bookmarkBarButtonItemPressed(_:))
)
barButtonItem.tintColor = .white
+ barButtonItem.accessibilityLabel = L10n.Scene.Bookmark.title
return barButtonItem
}()
private(set) lazy var replyBarButtonItem: UIBarButtonItem = {
let barButtonItem = UIBarButtonItem(image: UIImage(systemName: "arrowshape.turn.up.left"), style: .plain, target: self, action: #selector(ProfileViewController.replyBarButtonItemPressed(_:)))
barButtonItem.tintColor = .white
+ barButtonItem.accessibilityLabel = L10n.Common.Controls.Actions.reply
return barButtonItem
}()
let moreMenuBarButtonItem: UIBarButtonItem = {
let barButtonItem = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .plain, target: nil, action: nil)
barButtonItem.tintColor = .white
+ barButtonItem.accessibilityLabel = L10n.Common.Controls.Actions.seeMore
return barButtonItem
}()
- let refreshControl: UIRefreshControl = {
- let refreshControl = UIRefreshControl()
+ let refreshControl: RefreshControl = {
+ let refreshControl = RefreshControl()
refreshControl.tintColor = .white
return refreshControl
}()
@@ -253,12 +259,7 @@ extension ProfileViewController {
tabBarPagerController.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tabBarPagerController.view)
tabBarPagerController.didMove(toParent: self)
- NSLayoutConstraint.activate([
- tabBarPagerController.view.topAnchor.constraint(equalTo: view.topAnchor),
- tabBarPagerController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- tabBarPagerController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
- tabBarPagerController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
- ])
+ tabBarPagerController.view.pinToParent()
tabBarPagerController.delegate = self
tabBarPagerController.dataSource = self
@@ -497,7 +498,7 @@ extension ProfileViewController {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
guard let setting = context.settingService.currentSetting.value else { return }
let settingsViewModel = SettingsViewModel(context: context, authContext: viewModel.authContext, setting: setting)
- coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil))
+ _ = coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil))
}
@objc private func shareBarButtonItemPressed(_ sender: UIBarButtonItem) {
@@ -540,18 +541,21 @@ extension ProfileViewController {
let composeViewModel = ComposeViewModel(
context: context,
authContext: viewModel.authContext,
- kind: .mention(user: mastodonUser.asRecrod)
+ kind: .mention(user: mastodonUser.asRecord)
)
_ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil))
}
- @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) {
+ @objc private func refreshControlValueChanged(_ sender: RefreshControl) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
if let userTimelineViewController = profilePagingViewController.currentViewController as? UserTimelineViewController {
userTimelineViewController.viewModel.stateMachine.enter(UserTimelineViewModel.State.Reloading.self)
}
+ // trigger authenticated user account update
+ viewModel.context.authenticationService.updateActiveUserAccountPublisher.send()
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
sender.endRefreshing()
}
diff --git a/Mastodon/Scene/Profile/ProfileViewModel.swift b/Mastodon/Scene/Profile/ProfileViewModel.swift
index e23b465d4..5c81c7920 100644
--- a/Mastodon/Scene/Profile/ProfileViewModel.swift
+++ b/Mastodon/Scene/Profile/ProfileViewModel.swift
@@ -215,8 +215,17 @@ extension ProfileViewModel {
let authenticationBox = authContext.mastodonAuthenticationBox
let domain = authenticationBox.domain
let authorization = authenticationBox.userAuthorization
-
- let _image: UIImage? = {
+
+ // TODO: constrain size?
+ let _header: UIImage? = {
+ guard let image = headerProfileInfo.header else { return nil }
+ guard image.size.width <= ProfileHeaderViewModel.bannerImageMaxSizeInPixel.width else {
+ return image.af.imageScaled(to: ProfileHeaderViewModel.bannerImageMaxSizeInPixel)
+ }
+ return image
+ }()
+
+ let _avatar: UIImage? = {
guard let image = headerProfileInfo.avatar else { return nil }
guard image.size.width <= ProfileHeaderViewModel.avatarImageMaxSizeInPixel.width else {
return image.af.imageScaled(to: ProfileHeaderViewModel.avatarImageMaxSizeInPixel)
@@ -233,8 +242,8 @@ extension ProfileViewModel {
bot: nil,
displayName: headerProfileInfo.name,
note: headerProfileInfo.note,
- avatar: _image.flatMap { Mastodon.Query.MediaAttachment.png($0.pngData()) },
- header: nil,
+ avatar: _avatar.flatMap { Mastodon.Query.MediaAttachment.png($0.pngData()) },
+ header: _header.flatMap { Mastodon.Query.MediaAttachment.png($0.pngData()) },
locked: nil,
source: nil,
fieldsAttributes: fieldsAttributes
diff --git a/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift b/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift
index 8a983da33..6cdd6a594 100644
--- a/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift
+++ b/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift
@@ -60,12 +60,7 @@ extension UserTimelineViewController {
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.pinToParent()
tableView.delegate = self
viewModel.setupDiffableDataSource(
diff --git a/Mastodon/Scene/Profile/UserLIst/FavoritedBy/FavoritedByViewController.swift b/Mastodon/Scene/Profile/UserLIst/FavoritedBy/FavoritedByViewController.swift
index ebce374e7..07316cd3d 100644
--- a/Mastodon/Scene/Profile/UserLIst/FavoritedBy/FavoritedByViewController.swift
+++ b/Mastodon/Scene/Profile/UserLIst/FavoritedBy/FavoritedByViewController.swift
@@ -61,12 +61,7 @@ extension FavoritedByViewController {
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.pinToParent()
tableView.delegate = self
viewModel.setupDiffableDataSource(
diff --git a/Mastodon/Scene/Profile/UserLIst/RebloggedBy/RebloggedByViewController.swift b/Mastodon/Scene/Profile/UserLIst/RebloggedBy/RebloggedByViewController.swift
index 0688bcccb..a45c491fc 100644
--- a/Mastodon/Scene/Profile/UserLIst/RebloggedBy/RebloggedByViewController.swift
+++ b/Mastodon/Scene/Profile/UserLIst/RebloggedBy/RebloggedByViewController.swift
@@ -61,12 +61,7 @@ extension RebloggedByViewController {
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.pinToParent()
tableView.delegate = self
viewModel.setupDiffableDataSource(
diff --git a/Mastodon/Scene/Profile/UserLIst/UserListViewModel+State.swift b/Mastodon/Scene/Profile/UserLIst/UserListViewModel+State.swift
index c7b3e20cd..d0f7feb88 100644
--- a/Mastodon/Scene/Profile/UserLIst/UserListViewModel+State.swift
+++ b/Mastodon/Scene/Profile/UserLIst/UserListViewModel+State.swift
@@ -134,7 +134,7 @@ extension UserListViewModel.State {
maxID = nil
}
- guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
+ guard let viewModel = viewModel else { return }
let maxID = self.maxID
diff --git a/Mastodon/Scene/Report/Report/ReportViewController.swift b/Mastodon/Scene/Report/Report/ReportViewController.swift
index f1418c5a1..fb0417a7f 100644
--- a/Mastodon/Scene/Report/Report/ReportViewController.swift
+++ b/Mastodon/Scene/Report/Report/ReportViewController.swift
@@ -61,12 +61,7 @@ extension ReportViewController {
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),
- ])
+ reportReasonViewController.view.pinToParent()
}
}
@@ -126,7 +121,7 @@ extension ReportViewController: ReportServerRulesViewControllerDelegate {
return
}
- coordinator.present(
+ _ = coordinator.present(
scene: .reportStatus(viewModel: viewModel.reportStatusViewModel),
from: self,
transition: .show
diff --git a/Mastodon/Scene/Report/ReportReason/ReportReasonViewController.swift b/Mastodon/Scene/Report/ReportReason/ReportReasonViewController.swift
index 2e8e53d18..517873abe 100644
--- a/Mastodon/Scene/Report/ReportReason/ReportReasonViewController.swift
+++ b/Mastodon/Scene/Report/ReportReason/ReportReasonViewController.swift
@@ -57,12 +57,7 @@ extension ReportReasonViewController {
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),
- ])
+ hostingViewController.view.pinToParent()
navigationActionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(navigationActionView)
diff --git a/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift b/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift
index 10dcdf373..1d67ad6c3 100644
--- a/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift
+++ b/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift
@@ -60,12 +60,7 @@ extension ReportResultViewController {
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),
- ])
+ hostingViewController.view.pinToParent()
navigationActionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(navigationActionView)
diff --git a/Mastodon/Scene/Report/ReportServerRules/ReportServerRulesViewController.swift b/Mastodon/Scene/Report/ReportServerRules/ReportServerRulesViewController.swift
index 3f1cdf331..00be4d800 100644
--- a/Mastodon/Scene/Report/ReportServerRules/ReportServerRulesViewController.swift
+++ b/Mastodon/Scene/Report/ReportServerRules/ReportServerRulesViewController.swift
@@ -63,12 +63,7 @@ extension ReportServerRulesViewController {
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),
- ])
+ hostingViewController.view.pinToParent()
navigationActionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(navigationActionView)
diff --git a/Mastodon/Scene/Report/ReportStatus/ReportStatusViewController.swift b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewController.swift
index d45c196cd..10e1ec3cd 100644
--- a/Mastodon/Scene/Report/ReportStatus/ReportStatusViewController.swift
+++ b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewController.swift
@@ -80,12 +80,7 @@ extension ReportStatusViewController {
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.pinToParent()
tableView.delegate = self
viewModel.setupDiffableDataSource(
@@ -128,6 +123,10 @@ extension ReportStatusViewController {
.assign(to: \.isEnabled, on: navigationActionView.nextButton)
.store(in: &disposeBag)
+ if !viewModel.selectStatuses.isEmpty {
+ navigationActionView.hidesBackButton = true
+ }
+
navigationActionView.backButton.addTarget(self, action: #selector(ReportStatusViewController.skipButtonDidPressed(_:)), for: .touchUpInside)
navigationActionView.nextButton.addTarget(self, action: #selector(ReportStatusViewController.nextButtonDidPressed(_:)), for: .touchUpInside)
}
diff --git a/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+State.swift b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+State.swift
index 01e8715d1..79807cf0f 100644
--- a/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+State.swift
+++ b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+State.swift
@@ -75,7 +75,7 @@ extension ReportStatusViewModel.State {
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
- guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
+ guard let viewModel else { return }
let maxID = viewModel.statusFetchedResultsController.statusIDs.last
diff --git a/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel.swift b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel.swift
index 5b80a9f3a..c1c79af48 100644
--- a/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel.swift
+++ b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel.swift
@@ -71,9 +71,7 @@ class ReportStatusViewModel {
}
$selectStatuses
- .map { statuses -> Bool in
- return status == nil ? !statuses.isEmpty : statuses.count > 1
- }
+ .map { !$0.isEmpty }
.assign(to: &$isNextButtonEnabled)
}
diff --git a/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewController.swift b/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewController.swift
index e644c29ea..76f312273 100644
--- a/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewController.swift
+++ b/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewController.swift
@@ -93,12 +93,7 @@ extension ReportSupplementaryViewController {
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.pinToParent()
tableView.delegate = self
viewModel.setupDiffableDataSource(
diff --git a/Mastodon/Scene/Report/Share/Cell/ReportCommentTableViewCell.swift b/Mastodon/Scene/Report/Share/Cell/ReportCommentTableViewCell.swift
index d735a094c..ad6ce252a 100644
--- a/Mastodon/Scene/Report/Share/Cell/ReportCommentTableViewCell.swift
+++ b/Mastodon/Scene/Report/Share/Cell/ReportCommentTableViewCell.swift
@@ -74,11 +74,8 @@ extension ReportCommentTableViewCell {
commentTextView.translatesAutoresizingMaskIntoConstraints = false
commentTextViewShadowBackgroundContainer.addSubview(commentTextView)
+ commentTextView.pinToParent()
NSLayoutConstraint.activate([
- commentTextView.topAnchor.constraint(equalTo: commentTextViewShadowBackgroundContainer.topAnchor),
- commentTextView.leadingAnchor.constraint(equalTo: commentTextViewShadowBackgroundContainer.leadingAnchor),
- commentTextView.trailingAnchor.constraint(equalTo: commentTextViewShadowBackgroundContainer.trailingAnchor),
- commentTextView.bottomAnchor.constraint(equalTo: commentTextViewShadowBackgroundContainer.bottomAnchor),
commentTextView.heightAnchor.constraint(greaterThanOrEqualToConstant: 100).priority(.defaultHigh),
])
}
diff --git a/Mastodon/Scene/Report/Share/Cell/ReportResultActionTableViewCell.swift b/Mastodon/Scene/Report/Share/Cell/ReportResultActionTableViewCell.swift
index 9b605a0c7..1828035a6 100644
--- a/Mastodon/Scene/Report/Share/Cell/ReportResultActionTableViewCell.swift
+++ b/Mastodon/Scene/Report/Share/Cell/ReportResultActionTableViewCell.swift
@@ -103,12 +103,7 @@ extension ReportResultActionTableViewCell {
reportBannerLabel.translatesAutoresizingMaskIntoConstraints = false
reportBannerShadowContainer.addSubview(reportBannerLabel)
- NSLayoutConstraint.activate([
- reportBannerLabel.topAnchor.constraint(equalTo: reportBannerShadowContainer.topAnchor),
- reportBannerLabel.leadingAnchor.constraint(equalTo: reportBannerShadowContainer.leadingAnchor),
- reportBannerLabel.trailingAnchor.constraint(equalTo: reportBannerShadowContainer.trailingAnchor),
- reportBannerLabel.bottomAnchor.constraint(equalTo: reportBannerShadowContainer.bottomAnchor),
- ])
+ reportBannerLabel.pinToParent()
}
diff --git a/Mastodon/Scene/Root/ContentSplitViewController.swift b/Mastodon/Scene/Root/ContentSplitViewController.swift
index 3f4758e8e..a10f0ed9b 100644
--- a/Mastodon/Scene/Root/ContentSplitViewController.swift
+++ b/Mastodon/Scene/Root/ContentSplitViewController.swift
@@ -124,4 +124,17 @@ extension ContentSplitViewController: SidebarViewControllerDelegate {
accountListViewController.preferredContentSize = CGSize(width: 375, height: 400)
}
+ func sidebarViewController(_ sidebarViewController: SidebarViewController, didDoubleTapItem item: SidebarViewModel.Item, sourceView: UIView) {
+ guard case let .tab(tab) = item, tab == .me else { return }
+ guard let authContext = authContext else { return }
+ assert(Thread.isMainThread)
+
+ guard let nextAccount = context.nextAccount(in: authContext) else { return }
+
+ Task { @MainActor in
+ let isActive = try await context.authenticationService.activeMastodonUser(domain: nextAccount.domain, userID: nextAccount.userID)
+ guard isActive else { return }
+ self.coordinator.setup()
+ }
+ }
}
diff --git a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift
index c49dcc1a1..2e5d5ae58 100644
--- a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift
+++ b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift
@@ -8,6 +8,7 @@
import os.log
import UIKit
import Combine
+import CoreDataStack
import SafariServices
import MastodonAsset
import MastodonCore
@@ -42,6 +43,7 @@ class MainTabBarController: UITabBarController {
static let avatarButtonSize = CGSize(width: 25, height: 25)
let avatarButton = CircleAvatarButton()
+ let accountSwitcherChevron = UIImageView(image: .chevronUpChevronDown)
@Published var currentTab: Tab = .home
@@ -49,7 +51,7 @@ class MainTabBarController: UITabBarController {
case home
case search
case compose
- case notification
+ case notifications
case me
var tag: Int {
@@ -61,7 +63,7 @@ class MainTabBarController: UITabBarController {
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 .notifications: return L10n.Common.Controls.Tabs.notifications
case .me: return L10n.Common.Controls.Tabs.profile
}
}
@@ -71,7 +73,7 @@ class MainTabBarController: UITabBarController {
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 .notifications: return Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate)
case .me: return UIImage(systemName: "person")!
}
}
@@ -81,7 +83,7 @@ class MainTabBarController: UITabBarController {
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 .notifications: return Asset.ObjectsAndTools.bellFill.image.withRenderingMode(.alwaysTemplate)
case .me: return UIImage(systemName: "person.fill")!
}
}
@@ -91,7 +93,7 @@ class MainTabBarController: UITabBarController {
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 .notifications: 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))!
}
}
@@ -101,7 +103,7 @@ class MainTabBarController: UITabBarController {
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 .notifications: return Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate)
case .me: return UIImage(systemName: "person")!
}
}
@@ -127,7 +129,7 @@ class MainTabBarController: UITabBarController {
viewController = _viewController
case .compose:
viewController = UIViewController()
- case .notification:
+ case .notifications:
let _viewController = NotificationViewController()
_viewController.context = context
_viewController.coordinator = coordinator
@@ -218,7 +220,7 @@ extension MainTabBarController {
let alertController = UIAlertController(for: error, title: nil, preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(okAction)
- coordinator.present(
+ _ = coordinator.present(
scene: .alertController(alertController: alertController),
from: nil,
transition: .alertController(animated: true, completion: nil)
@@ -272,7 +274,7 @@ extension MainTabBarController {
} ?? false
let image: UIImage = {
- if currentTab == .notification {
+ if currentTab == .notifications {
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)
@@ -306,13 +308,17 @@ extension MainTabBarController {
guard user.managedObjectContext != nil else { return }
self.avatarURL = user.avatarImageURL()
}
-
+
// a11y
let _profileTabItem = self.tabBar.items?.first { item in item.tag == Tab.me.tag }
guard let profileTabItem = _profileTabItem else { return }
- let currentUserDisplayName = user.displayNameWithFallback ?? "no user"
- profileTabItem.accessibilityHint = L10n.Scene.AccountList.tabBarHint(currentUserDisplayName)
-
+ profileTabItem.accessibilityHint = L10n.Scene.AccountList.tabBarHint(user.displayNameWithFallback)
+
+ context.authenticationService.updateActiveUserAccountPublisher
+ .sink { [weak self] in
+ self?.updateUserAccount()
+ }
+ .store(in: &disposeBag)
} else {
self.avatarURLObserver = nil
}
@@ -320,7 +326,14 @@ extension MainTabBarController {
let tabBarLongPressGestureRecognizer = UILongPressGestureRecognizer()
tabBarLongPressGestureRecognizer.addTarget(self, action: #selector(MainTabBarController.tabBarLongPressGestureRecognizerHandler(_:)))
tabBar.addGestureRecognizer(tabBarLongPressGestureRecognizer)
-
+
+ // todo: reconsider the "double tap to change account" feature -> https://github.com/mastodon/mastodon-ios/issues/628
+// let tabBarDoubleTapGestureRecognizer = UITapGestureRecognizer()
+// tabBarDoubleTapGestureRecognizer.numberOfTapsRequired = 2
+// tabBarDoubleTapGestureRecognizer.addTarget(self, action: #selector(MainTabBarController.tabBarDoubleTapGestureRecognizerHandler(_:)))
+// tabBarDoubleTapGestureRecognizer.delaysTouchesEnded = false
+// tabBar.addGestureRecognizer(tabBarDoubleTapGestureRecognizer)
+
self.isReadyForWizardAvatarButton = authContext != nil
$currentTab
@@ -371,9 +384,7 @@ extension MainTabBarController {
_ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: nil, transition: .modal(animated: true, completion: nil))
}
- @objc private func tabBarLongPressGestureRecognizerHandler(_ sender: UILongPressGestureRecognizer) {
- guard sender.state == .began else { return }
-
+ private func touchedTab(by sender: UIGestureRecognizer) -> Tab? {
var _tab: Tab?
let location = sender.location(in: tabBar)
for item in tabBar.items ?? [] {
@@ -385,7 +396,34 @@ extension MainTabBarController {
break
}
- guard let tab = _tab else { return }
+ return _tab
+ }
+
+ @objc private func tabBarDoubleTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) {
+ guard sender.state == .ended else { return }
+ guard let tab = touchedTab(by: sender) else { return }
+ logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): double tap \(tab.title) tab")
+
+ switch tab {
+ case .me:
+ guard let authContext = authContext else { return }
+ assert(Thread.isMainThread)
+
+ guard let nextAccount = context.nextAccount(in: authContext) else { return }
+
+ Task { @MainActor in
+ let isActive = try await context.authenticationService.activeMastodonUser(domain: nextAccount.domain, userID: nextAccount.userID)
+ guard isActive else { return }
+ self.coordinator.setup()
+ }
+ default:
+ break
+ }
+ }
+
+ @objc private func tabBarLongPressGestureRecognizerHandler(_ sender: UILongPressGestureRecognizer) {
+ guard sender.state == .began else { return }
+ guard let tab = touchedTab(by: sender) else { return }
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): long press \(tab.title) tab")
switch tab {
@@ -438,12 +476,7 @@ extension MainTabBarController {
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.pinToParent()
composeButton.setContentHuggingPriority(.required - 1, for: .horizontal)
composeButton.setContentHuggingPriority(.required - 1, for: .vertical)
}
@@ -469,13 +502,20 @@ extension MainTabBarController {
}
anchorImageView.alpha = 0
+ accountSwitcherChevron.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(accountSwitcherChevron)
+
self.avatarButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(self.avatarButton)
NSLayoutConstraint.activate([
- self.avatarButton.centerXAnchor.constraint(equalTo: anchorImageView.centerXAnchor),
+ self.avatarButton.centerXAnchor.constraint(equalTo: anchorImageView.centerXAnchor, constant: -16),
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),
+ accountSwitcherChevron.widthAnchor.constraint(equalToConstant: 10),
+ accountSwitcherChevron.heightAnchor.constraint(equalToConstant: 18),
+ accountSwitcherChevron.leadingAnchor.constraint(equalTo: avatarButton.trailingAnchor, constant: 8),
+ accountSwitcherChevron.centerYAnchor.constraint(equalTo: avatarButton.centerYAnchor)
])
self.avatarButton.setContentHuggingPriority(.required - 1, for: .horizontal)
self.avatarButton.setContentHuggingPriority(.required - 1, for: .vertical)
@@ -483,10 +523,31 @@ extension MainTabBarController {
}
private func updateAvatarButtonAppearance() {
+ accountSwitcherChevron.tintColor = currentTab == .me ? .label : .secondaryLabel
avatarButton.borderColor = currentTab == .me ? .label : .systemFill
avatarButton.setNeedsLayout()
}
+ private func updateUserAccount() {
+ guard let authContext = authContext else { return }
+
+ Task { @MainActor in
+ let profileResponse = try await context.apiService.authenticatedUserInfo(
+ authenticationBox: authContext.mastodonAuthenticationBox
+ )
+
+ if let user = authContext.mastodonAuthenticationBox.authenticationRecord.object(
+ in: context.managedObjectContext
+ )?.user {
+ user.update(
+ property: .init(
+ entity: profileResponse.value,
+ domain: authContext.mastodonAuthenticationBox.domain
+ )
+ )
+ }
+ }
+ }
}
extension MainTabBarController {
@@ -593,7 +654,7 @@ extension MainTabBarController {
let tabs: [Tab] = [
.home,
.search,
- .notification,
+ .notifications,
.me
]
for (i, tab) in tabs.enumerated() {
diff --git a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift
index 70e1239b6..7c76585a6 100644
--- a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift
+++ b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift
@@ -15,6 +15,7 @@ import MastodonUI
protocol SidebarViewControllerDelegate: AnyObject {
func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab)
func sidebarViewController(_ sidebarViewController: SidebarViewController, didLongPressItem item: SidebarViewModel.Item, sourceView: UIView)
+ func sidebarViewController(_ sidebarViewController: SidebarViewController, didDoubleTapItem item: SidebarViewModel.Item, sourceView: UIView)
}
final class SidebarViewController: UIViewController, NeedsDependency {
@@ -101,12 +102,7 @@ extension SidebarViewController {
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.pinToParent()
secondaryCollectionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(secondaryCollectionView)
@@ -143,6 +139,15 @@ extension SidebarViewController {
let sidebarLongPressGestureRecognizer = UILongPressGestureRecognizer()
sidebarLongPressGestureRecognizer.addTarget(self, action: #selector(SidebarViewController.sidebarLongPressGestureRecognizerHandler(_:)))
collectionView.addGestureRecognizer(sidebarLongPressGestureRecognizer)
+
+ // todo: reconsider the "double tap to change account" feature -> https://github.com/mastodon/mastodon-ios/issues/628
+// let sidebarDoubleTapGestureRecognizer = UITapGestureRecognizer()
+// sidebarDoubleTapGestureRecognizer.numberOfTapsRequired = 2
+// sidebarDoubleTapGestureRecognizer.addTarget(self, action: #selector(SidebarViewController.sidebarDoubleTapGestureRecognizerHandler(_:)))
+// sidebarDoubleTapGestureRecognizer.delaysTouchesEnded = false
+// sidebarDoubleTapGestureRecognizer.cancelsTouchesInView = true
+// collectionView.addGestureRecognizer(sidebarDoubleTapGestureRecognizer)
+
}
private func setupBackground(theme: Theme) {
@@ -176,6 +181,20 @@ extension SidebarViewController {
guard let cell = collectionView.cellForItem(at: indexPath) else { return }
delegate?.sidebarViewController(self, didLongPressItem: item, sourceView: cell)
}
+
+ @objc private func sidebarDoubleTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) {
+ guard sender.state == .ended else { return }
+
+ logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
+ assert(sender.view === collectionView)
+
+ let position = sender.location(in: collectionView)
+ guard let indexPath = collectionView.indexPathForItem(at: position) else { return }
+ guard let diffableDataSource = viewModel.diffableDataSource else { return }
+ guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
+ guard let cell = collectionView.cellForItem(at: indexPath) else { return }
+ delegate?.sidebarViewController(self, didDoubleTapItem: item, sourceView: cell)
+ }
}
diff --git a/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift b/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift
index c3f9e3e36..f6e90dcd9 100644
--- a/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift
+++ b/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift
@@ -16,7 +16,6 @@ import MastodonCore
import MastodonLocalization
final class SidebarViewModel {
-
var disposeBag = Set()
// input
@@ -80,6 +79,7 @@ extension SidebarViewModel {
}()
cell.item = SidebarListContentView.Item(
isActive: false,
+ accessoryImage: item == .me ? .chevronUpChevronDown : nil,
title: item.title,
image: item.image,
activeImage: item.selectedImage,
@@ -100,7 +100,7 @@ extension SidebarViewModel {
.store(in: &cell.disposeBag)
switch item {
- case .notification:
+ case .notifications:
Publishers.CombineLatest(
self.context.notificationService.unreadNotificationCountDidUpdate,
self.$currentTab
@@ -116,7 +116,7 @@ extension SidebarViewModel {
}()
let image: UIImage = {
- if currentTab == .notification {
+ if currentTab == .notifications {
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)
@@ -166,6 +166,7 @@ extension SidebarViewModel {
case .compose:
let item = SidebarListContentView.Item(
isActive: false,
+ accessoryImage: self.currentTab == .me ? .chevronUpChevronDown : nil,
title: L10n.Common.Controls.Actions.compose,
image: Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate),
activeImage: Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate),
@@ -192,7 +193,7 @@ extension SidebarViewModel {
let items: [Item] = [
.tab(.home),
.tab(.search),
- .tab(.notification),
+ .tab(.notifications),
.tab(.me),
.setting,
]
diff --git a/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift b/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift
index 515988405..c2aa1a4f5 100644
--- a/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift
+++ b/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift
@@ -23,7 +23,8 @@ final class SidebarListContentView: UIView, UIContentView {
button.borderColor = UIColor.label
return button
}()
-
+ private let accessoryImageView = UIImageView(image: nil)
+
private var currentConfiguration: ContentConfiguration!
var configuration: UIContentConfiguration {
get {
@@ -60,6 +61,9 @@ extension SidebarListContentView {
imageView.widthAnchor.constraint(equalToConstant: 40).priority(.required - 1),
imageView.heightAnchor.constraint(equalToConstant: 40).priority(.required - 1),
])
+
+ accessoryImageView.translatesAutoresizingMaskIntoConstraints = false
+ addSubview(accessoryImageView)
avatarButton.translatesAutoresizingMaskIntoConstraints = false
addSubview(avatarButton)
@@ -68,6 +72,10 @@ extension SidebarListContentView {
avatarButton.centerYAnchor.constraint(equalTo: imageView.centerYAnchor),
avatarButton.widthAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 1.0).priority(.required - 2),
avatarButton.heightAnchor.constraint(equalTo: imageView.heightAnchor, multiplier: 1.0).priority(.required - 2),
+ accessoryImageView.widthAnchor.constraint(equalToConstant: 12),
+ accessoryImageView.heightAnchor.constraint(equalToConstant: 22),
+ accessoryImageView.leadingAnchor.constraint(equalTo: avatarButton.trailingAnchor, constant: 4),
+ accessoryImageView.centerYAnchor.constraint(equalTo: avatarButton.centerYAnchor)
])
avatarButton.setContentHuggingPriority(.defaultLow - 10, for: .vertical)
avatarButton.setContentHuggingPriority(.defaultLow - 10, for: .horizontal)
@@ -96,6 +104,9 @@ extension SidebarListContentView {
imageView.isHidden = item.imageURL != nil
avatarButton.isHidden = item.imageURL == nil
imageView.image = item.isActive ? item.activeImage.withRenderingMode(.alwaysTemplate) : item.image.withRenderingMode(.alwaysTemplate)
+ accessoryImageView.image = item.accessoryImage
+ accessoryImageView.isHidden = item.accessoryImage == nil
+ accessoryImageView.tintColor = item.isActive ? .label : .secondaryLabel
avatarButton.avatarImageView.setImage(
url: item.imageURL,
placeholder: avatarButton.avatarImageView.image ?? .placeholder(color: .systemFill), // reuse to avoid blink
@@ -112,7 +123,8 @@ extension SidebarListContentView {
var isSelected: Bool = false
var isHighlighted: Bool = false
var isActive: Bool
-
+ var accessoryImage: UIImage? = nil
+
// model
let title: String
var image: UIImage
@@ -124,6 +136,7 @@ extension SidebarListContentView {
return lhs.isSelected == rhs.isSelected
&& lhs.isHighlighted == rhs.isHighlighted
&& lhs.isActive == rhs.isActive
+ && lhs.accessoryImage == rhs.accessoryImage
&& lhs.title == rhs.title
&& lhs.image == rhs.image
&& lhs.activeImage == rhs.activeImage
@@ -134,6 +147,7 @@ extension SidebarListContentView {
hasher.combine(isSelected)
hasher.combine(isHighlighted)
hasher.combine(isActive)
+ hasher.combine(accessoryImage)
hasher.combine(title)
hasher.combine(image)
hasher.combine(activeImage)
diff --git a/Mastodon/Scene/Search/Search/Cell/TrendCollectionViewCell.swift b/Mastodon/Scene/Search/Search/Cell/TrendCollectionViewCell.swift
index 30f618625..4436459fb 100644
--- a/Mastodon/Scene/Search/Search/Cell/TrendCollectionViewCell.swift
+++ b/Mastodon/Scene/Search/Search/Cell/TrendCollectionViewCell.swift
@@ -48,12 +48,7 @@ extension TrendCollectionViewCell {
trendView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(trendView)
- NSLayoutConstraint.activate([
- trendView.topAnchor.constraint(equalTo: contentView.topAnchor),
- trendView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
- trendView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
- trendView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
- ])
+ trendView.pinToParent()
}
override func updateConfiguration(using state: UICellConfigurationState) {
diff --git a/Mastodon/Scene/Search/Search/SearchViewController.swift b/Mastodon/Scene/Search/Search/SearchViewController.swift
index 7efef2c00..c190a1a4c 100644
--- a/Mastodon/Scene/Search/Search/SearchViewController.swift
+++ b/Mastodon/Scene/Search/Search/SearchViewController.swift
@@ -23,15 +23,15 @@ final class HeightFixedSearchBar: UISearchBar {
final class SearchViewController: UIViewController, NeedsDependency {
let logger = Logger(subsystem: "SearchViewController", category: "ViewController")
-
+
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var searchTransitionController = SearchTransitionController()
-
+
var disposeBag = Set()
var viewModel: SearchViewModel!
-
+
// use AutoLayout could set search bar margin automatically to
// layout alongside with split mode button (on iPad)
let titleViewContainer = UIView()
@@ -73,19 +73,19 @@ extension SearchViewController {
override func viewDidLoad() {
super.viewDidLoad()
- setupBackgroundColor(theme: ThemeService.shared.currentTheme.value)
+ 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.setupBackgroundColor(theme: theme)
+ self.setupAppearance(theme: theme)
}
.store(in: &disposeBag)
title = L10n.Scene.Search.title
setupSearchBar()
-
+
// collectionView.translatesAutoresizingMaskIntoConstraints = false
// view.addSubview(collectionView)
// NSLayoutConstraint.activate([
@@ -101,25 +101,21 @@ extension SearchViewController {
// )
guard let discoveryViewController = self.discoveryViewController else { return }
-
+
addChild(discoveryViewController)
discoveryViewController.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(discoveryViewController.view)
- NSLayoutConstraint.activate([
- 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),
- ])
-
+ discoveryViewController.view.pinToParent()
+
// discoveryViewController.view.isHidden = true
+
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
viewModel.viewDidAppeared.send()
-
+
// note:
// need set alpha because (maybe) SDK forget set alpha back
titleViewContainer.alpha = 1
@@ -127,8 +123,22 @@ extension SearchViewController {
}
extension SearchViewController {
- private func setupBackgroundColor(theme: Theme) {
+ private func setupAppearance(theme: Theme) {
view.backgroundColor = theme.systemGroupedBackgroundColor
+
+ // Match the DiscoveryViewController tab color and remove the double separator.
+ let navigationBarAppearance = UINavigationBarAppearance()
+ navigationBarAppearance.configureWithOpaqueBackground()
+ navigationBarAppearance.backgroundColor = theme.systemBackgroundColor
+ navigationBarAppearance.shadowColor = nil
+
+ navigationItem.standardAppearance = navigationBarAppearance
+ navigationItem.scrollEdgeAppearance = navigationBarAppearance
+ navigationItem.compactAppearance = navigationBarAppearance
+
+ if #available(iOS 15, *) {
+ navigationItem.compactScrollEdgeAppearance = navigationBarAppearance
+ }
}
private func setupSearchBar() {
@@ -136,12 +146,7 @@ extension SearchViewController {
searchBar.delegate = self
searchBar.translatesAutoresizingMaskIntoConstraints = false
titleViewContainer.addSubview(searchBar)
- NSLayoutConstraint.activate([
- searchBar.topAnchor.constraint(equalTo: titleViewContainer.topAnchor),
- searchBar.leadingAnchor.constraint(equalTo: titleViewContainer.leadingAnchor),
- searchBar.trailingAnchor.constraint(equalTo: titleViewContainer.trailingAnchor),
- searchBar.bottomAnchor.constraint(equalTo: titleViewContainer.bottomAnchor),
- ])
+ searchBar.pinToParent()
searchBar.setContentHuggingPriority(.required, for: .horizontal)
searchBar.setContentHuggingPriority(.required, for: .vertical)
navigationItem.titleView = titleViewContainer
@@ -159,7 +164,7 @@ extension SearchViewController {
// 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))
+ _ = self.coordinator.present(scene: .searchDetail(viewModel: searchDetailViewModel), from: self, transition: .customPush(animated: false))
}
.store(in: &disposeBag)
}
diff --git a/Mastodon/Scene/Search/Search/View/SearchRecommendCollectionHeader.swift b/Mastodon/Scene/Search/Search/View/SearchRecommendCollectionHeader.swift
index 1d4788ac8..0a65c6a25 100644
--- a/Mastodon/Scene/Search/Search/View/SearchRecommendCollectionHeader.swift
+++ b/Mastodon/Scene/Search/Search/View/SearchRecommendCollectionHeader.swift
@@ -62,12 +62,7 @@ extension SearchRecommendCollectionHeader {
containerStackView.isLayoutMarginsRelativeArrangement = true
containerStackView.translatesAutoresizingMaskIntoConstraints = false
addSubview(containerStackView)
- NSLayoutConstraint.activate([
- containerStackView.topAnchor.constraint(equalTo: topAnchor),
- containerStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
- containerStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
- containerStackView.trailingAnchor.constraint(equalTo: trailingAnchor)
- ])
+ containerStackView.pinToParent()
let horizontalStackView = UIStackView()
horizontalStackView.spacing = 8
diff --git a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift
index 62dd1a2cc..4ec9bce31 100644
--- a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift
+++ b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift
@@ -60,7 +60,7 @@ final class SearchDetailViewController: PageboyViewController, NeedsDependency {
let searchController: CustomSearchController = {
let searchController = CustomSearchController()
searchController.automaticallyShowsScopeBar = false
- searchController.dimsBackgroundDuringPresentation = false
+ searchController.obscuresBackgroundDuringPresentation = false
return searchController
}()
private(set) lazy var searchBar: UISearchBar = {
@@ -116,12 +116,7 @@ extension SearchDetailViewController {
searchHistoryViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
} else {
- NSLayoutConstraint.activate([
- searchHistoryViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
- searchHistoryViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- searchHistoryViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
- searchHistoryViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
- ])
+ searchHistoryViewController.view.pinToParent()
}
transition = Transition(style: .fade, duration: 0.1)
@@ -289,12 +284,7 @@ extension SearchDetailViewController {
navigationBarVisualEffectBackgroundView.translatesAutoresizingMaskIntoConstraints = false
view.insertSubview(navigationBarVisualEffectBackgroundView, belowSubview: navigationBarBackgroundView)
- NSLayoutConstraint.activate([
- navigationBarVisualEffectBackgroundView.topAnchor.constraint(equalTo: navigationBarBackgroundView.topAnchor),
- navigationBarVisualEffectBackgroundView.leadingAnchor.constraint(equalTo: navigationBarBackgroundView.leadingAnchor),
- navigationBarVisualEffectBackgroundView.trailingAnchor.constraint(equalTo: navigationBarBackgroundView.trailingAnchor),
- navigationBarVisualEffectBackgroundView.bottomAnchor.constraint(equalTo: navigationBarBackgroundView.bottomAnchor),
- ])
+ navigationBarVisualEffectBackgroundView.pinTo(to: navigationBarBackgroundView)
} else {
navigationItem.setHidesBackButton(true, animated: false)
navigationItem.titleView = nil
diff --git a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift
index 52d0ffb9c..ac08e7906 100644
--- a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift
+++ b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift
@@ -47,12 +47,7 @@ extension SearchHistoryViewController {
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.pinToParent()
collectionView.delegate = self
viewModel.setupDiffableDataSource(
diff --git a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController.swift b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController.swift
index 67de62bf3..6a840f9b5 100644
--- a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController.swift
+++ b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController.swift
@@ -49,12 +49,7 @@ extension SearchResultViewController {
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.pinToParent()
tableView.delegate = self
// tableView.prefetchDataSource = self
diff --git a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+Diffable.swift b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+Diffable.swift
index 7d243b1fa..b9eb5a9ae 100644
--- a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+Diffable.swift
+++ b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+Diffable.swift
@@ -33,7 +33,7 @@ extension SearchResultViewModel {
userFetchedResultsController.$records,
$hashtags
)
- .map { statusRecrods, userRecords, hashtags in
+ .map { statusRecords, userRecords, hashtags in
var items: [SearchResultItem] = []
let userItems = userRecords.map { SearchResultItem.user($0) }
@@ -42,7 +42,7 @@ extension SearchResultViewModel {
let hashtagItems = hashtags.map { SearchResultItem.hashtag(tag: $0) }
items.append(contentsOf: hashtagItems)
- let statusItems = statusRecrods.map { SearchResultItem.status($0) }
+ let statusItems = statusRecords.map { SearchResultItem.status($0) }
items.append(contentsOf: statusItems)
return items
diff --git a/Mastodon/Scene/Settings/Cell/SettingsAppearanceTableViewCell.swift b/Mastodon/Scene/Settings/Cell/SettingsAppearanceTableViewCell.swift
index 44b6b39c3..efad9693d 100644
--- a/Mastodon/Scene/Settings/Cell/SettingsAppearanceTableViewCell.swift
+++ b/Mastodon/Scene/Settings/Cell/SettingsAppearanceTableViewCell.swift
@@ -101,12 +101,7 @@ extension SettingsAppearanceTableViewCell {
stackView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(stackView)
- NSLayoutConstraint.activate([
- stackView.topAnchor.constraint(equalTo: contentView.topAnchor),
- stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
- stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
- stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
- ])
+ stackView.pinToParent()
stackView.addArrangedSubview(systemAppearanceView)
stackView.addArrangedSubview(darkAppearanceView)
diff --git a/Mastodon/Scene/Settings/SettingsViewController.swift b/Mastodon/Scene/Settings/SettingsViewController.swift
index 53a856fd0..fa6948ced 100644
--- a/Mastodon/Scene/Settings/SettingsViewController.swift
+++ b/Mastodon/Scene/Settings/SettingsViewController.swift
@@ -225,12 +225,7 @@ extension SettingsViewController {
setupNavigation()
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.pinToParent()
setupTableView()
updateSectionHeaderStackViewLayout()
@@ -401,7 +396,7 @@ extension SettingsViewController: UITableViewDelegate {
)
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))
+ _ = self.coordinator.present(scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil))
}
.store(in: &disposeBag)
case .signOut:
@@ -549,7 +544,7 @@ extension SettingsViewController: MetaLabelDelegate {
switch meta {
case .url(_, _, let url, _):
guard let url = URL(string: url) else { return }
- coordinator.present(scene: .safari(url: url), from: self, transition: .safariPresent(animated: true, completion: nil))
+ _ = coordinator.present(scene: .safari(url: url), from: self, transition: .safariPresent(animated: true, completion: nil))
default:
assertionFailure()
}
diff --git a/Mastodon/Scene/Settings/View/AppearanceView.swift b/Mastodon/Scene/Settings/View/AppearanceView.swift
index cdc29100b..a09401537 100644
--- a/Mastodon/Scene/Settings/View/AppearanceView.swift
+++ b/Mastodon/Scene/Settings/View/AppearanceView.swift
@@ -85,12 +85,7 @@ extension AppearanceView {
private func setupUI() {
imageView.translatesAutoresizingMaskIntoConstraints = false
imageViewShadowBackgroundContainer.addSubview(imageView)
- NSLayoutConstraint.activate([
- imageView.topAnchor.constraint(equalTo: imageViewShadowBackgroundContainer.topAnchor),
- imageView.leadingAnchor.constraint(equalTo: imageViewShadowBackgroundContainer.leadingAnchor),
- imageView.trailingAnchor.constraint(equalTo: imageViewShadowBackgroundContainer.trailingAnchor),
- imageView.bottomAnchor.constraint(equalTo: imageViewShadowBackgroundContainer.bottomAnchor),
- ])
+ imageView.pinToParent()
imageViewShadowBackgroundContainer.cornerRadius = 4
stackView.addArrangedSubview(imageViewShadowBackgroundContainer)
@@ -100,11 +95,8 @@ extension AppearanceView {
addSubview(stackView)
translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
+ stackView.pinToParent()
NSLayoutConstraint.activate([
- stackView.topAnchor.constraint(equalTo: self.topAnchor),
- stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
- stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
- stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 121.0 / 100.0), // height / width
])
}
diff --git a/Mastodon/Scene/Share/ContextMenu/ImagePreview/ContextMenuImagePreviewViewController.swift b/Mastodon/Scene/Share/ContextMenu/ImagePreview/ContextMenuImagePreviewViewController.swift
index 0d3c4f574..12820e627 100644
--- a/Mastodon/Scene/Share/ContextMenu/ImagePreview/ContextMenuImagePreviewViewController.swift
+++ b/Mastodon/Scene/Share/ContextMenu/ImagePreview/ContextMenuImagePreviewViewController.swift
@@ -31,12 +31,7 @@ extension ContextMenuImagePreviewViewController {
imageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(imageView)
- NSLayoutConstraint.activate([
- imageView.topAnchor.constraint(equalTo: view.topAnchor),
- imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
- imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
- ])
+ imageView.pinToParent()
imageView.image = viewModel.thumbnail
diff --git a/Mastodon/Scene/Share/NavigationController/AdaptiveStatusBarStyleNavigationController.swift b/Mastodon/Scene/Share/NavigationController/AdaptiveStatusBarStyleNavigationController.swift
index 80df8938e..88441f9ad 100644
--- a/Mastodon/Scene/Share/NavigationController/AdaptiveStatusBarStyleNavigationController.swift
+++ b/Mastodon/Scene/Share/NavigationController/AdaptiveStatusBarStyleNavigationController.swift
@@ -14,7 +14,7 @@ class AdaptiveStatusBarStyleNavigationController: UINavigationController {
// Make status bar style adaptive for child view controller
// SeeAlso: `modalPresentationCapturesStatusBarAppearance`
override var childForStatusBarStyle: UIViewController? {
- visibleViewController
+ topViewController
}
}
diff --git a/Mastodon/Scene/Share/View/Content/ContentWarningOverlayView.swift b/Mastodon/Scene/Share/View/Content/ContentWarningOverlayView.swift
index ca46193b6..eb55dc575 100644
--- a/Mastodon/Scene/Share/View/Content/ContentWarningOverlayView.swift
+++ b/Mastodon/Scene/Share/View/Content/ContentWarningOverlayView.swift
@@ -93,12 +93,7 @@ extension ContentWarningOverlayView {
vibrancyVisualEffectView.translatesAutoresizingMaskIntoConstraints = false
blurVisualEffectView.contentView.addSubview(vibrancyVisualEffectView)
- NSLayoutConstraint.activate([
- vibrancyVisualEffectView.topAnchor.constraint(equalTo: blurVisualEffectView.topAnchor),
- vibrancyVisualEffectView.leadingAnchor.constraint(equalTo: blurVisualEffectView.leadingAnchor),
- vibrancyVisualEffectView.trailingAnchor.constraint(equalTo: blurVisualEffectView.trailingAnchor),
- vibrancyVisualEffectView.bottomAnchor.constraint(equalTo: blurVisualEffectView.bottomAnchor),
- ])
+ vibrancyVisualEffectView.pinTo(to: blurVisualEffectView)
vibrancyContentWarningLabel.translatesAutoresizingMaskIntoConstraints = false
vibrancyVisualEffectView.contentView.addSubview(vibrancyContentWarningLabel)
@@ -110,12 +105,7 @@ extension ContentWarningOverlayView {
blurVisualEffectView.translatesAutoresizingMaskIntoConstraints = false
addSubview(blurVisualEffectView)
- NSLayoutConstraint.activate([
- blurVisualEffectView.topAnchor.constraint(equalTo: topAnchor),
- blurVisualEffectView.leadingAnchor.constraint(equalTo: leadingAnchor),
- blurVisualEffectView.trailingAnchor.constraint(equalTo: trailingAnchor),
- blurVisualEffectView.bottomAnchor.constraint(equalTo: bottomAnchor),
- ])
+ blurVisualEffectView.pinToParent()
// blur image style
contentOverlayView.translatesAutoresizingMaskIntoConstraints = false
@@ -134,12 +124,7 @@ extension ContentWarningOverlayView {
blurContentWarningLabelContainer.translatesAutoresizingMaskIntoConstraints = false
contentOverlayView.addSubview(blurContentWarningLabelContainer)
- NSLayoutConstraint.activate([
- blurContentWarningLabelContainer.topAnchor.constraint(equalTo: topAnchor),
- blurContentWarningLabelContainer.leadingAnchor.constraint(equalTo: leadingAnchor),
- blurContentWarningLabelContainer.trailingAnchor.constraint(equalTo: trailingAnchor),
- blurContentWarningLabelContainer.bottomAnchor.constraint(equalTo: bottomAnchor),
- ])
+ blurContentWarningLabelContainer.pinTo(to: self)
let topPaddingView = UIView()
let bottomPaddingView = UIView()
diff --git a/Mastodon/Scene/Share/View/Content/DoubleTitleLabelNavigationBarTitleView.swift b/Mastodon/Scene/Share/View/Content/DoubleTitleLabelNavigationBarTitleView.swift
index 6734b7b77..f5ac0f8ec 100644
--- a/Mastodon/Scene/Share/View/Content/DoubleTitleLabelNavigationBarTitleView.swift
+++ b/Mastodon/Scene/Share/View/Content/DoubleTitleLabelNavigationBarTitleView.swift
@@ -47,12 +47,7 @@ extension DoubleTitleLabelNavigationBarTitleView {
containerView.distribution = .fill
containerView.translatesAutoresizingMaskIntoConstraints = false
addSubview(containerView)
- NSLayoutConstraint.activate([
- containerView.topAnchor.constraint(equalTo: topAnchor),
- containerView.leadingAnchor.constraint(equalTo: leadingAnchor),
- containerView.trailingAnchor.constraint(equalTo: trailingAnchor),
- containerView.bottomAnchor.constraint(equalTo: bottomAnchor),
- ])
+ containerView.pinToParent()
containerView.addArrangedSubview(titleLabel)
containerView.addArrangedSubview(subtitleLabel)
diff --git a/Mastodon/Scene/Share/View/Content/NotificationView+Configuration.swift b/Mastodon/Scene/Share/View/Content/NotificationView+Configuration.swift
index 98d06fd92..018628473 100644
--- a/Mastodon/Scene/Share/View/Content/NotificationView+Configuration.swift
+++ b/Mastodon/Scene/Share/View/Content/NotificationView+Configuration.swift
@@ -111,6 +111,7 @@ extension NotificationView {
self.viewModel.notificationIndicatorText = nil
return
}
+ self.viewModel.type = type
func createMetaContent(text: String, emojis: MastodonContent.Emojis) -> MetaContent {
let content = MastodonContent(content: text, emojis: emojis)
@@ -154,7 +155,7 @@ extension NotificationView {
)
case .status:
self.viewModel.notificationIndicatorText = createMetaContent(
- text: L10n.Scene.Notification.NotificationDescription.mentionedYou,
+ text: .empty,
emojis: emojis.asDictionary
)
case ._other:
diff --git a/Mastodon/Scene/Share/View/Control/RefreshControl.swift b/Mastodon/Scene/Share/View/Control/RefreshControl.swift
new file mode 100644
index 000000000..afac5a1a1
--- /dev/null
+++ b/Mastodon/Scene/Share/View/Control/RefreshControl.swift
@@ -0,0 +1,28 @@
+//
+// RefreshControl.swift
+// Mastodon
+//
+// Created by Kyle Bashour on 11/14/22.
+//
+
+import UIKit
+
+/// RefreshControl subclass that properly displays itself behind table view contents.
+class RefreshControl: UIRefreshControl {
+ override init() {
+ super.init()
+ }
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ override func didMoveToSuperview() {
+ super.didMoveToSuperview()
+ layer.zPosition = -1
+ }
+}
diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift
index 64f3456b5..350bf8660 100644
--- a/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift
+++ b/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift
@@ -96,7 +96,6 @@ extension StatusThreadRootTableViewCell {
override var accessibilityElements: [Any]? {
get {
var elements = [
- statusView.headerContainerView,
statusView.authorView,
statusView.viewModel.isContentReveal
? statusView.contentMetaText.textView
diff --git a/Mastodon/Scene/Share/Webview/WebViewController.swift b/Mastodon/Scene/Share/Webview/WebViewController.swift
index cb690de93..209ee483d 100644
--- a/Mastodon/Scene/Share/Webview/WebViewController.swift
+++ b/Mastodon/Scene/Share/Webview/WebViewController.swift
@@ -49,12 +49,7 @@ extension WebViewController {
webView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(webView)
- NSLayoutConstraint.activate([
- webView.topAnchor.constraint(equalTo: view.topAnchor),
- webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
- webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
- ])
+ webView.pinToParent()
let request = URLRequest(url: viewModel.url)
webView.load(request)
diff --git a/Mastodon/Scene/SuggestionAccount/CollectionViewCell/SuggestionAccountCollectionViewCell.swift b/Mastodon/Scene/SuggestionAccount/CollectionViewCell/SuggestionAccountCollectionViewCell.swift
index 2fb467fbd..296935521 100644
--- a/Mastodon/Scene/SuggestionAccount/CollectionViewCell/SuggestionAccountCollectionViewCell.swift
+++ b/Mastodon/Scene/SuggestionAccount/CollectionViewCell/SuggestionAccountCollectionViewCell.swift
@@ -53,11 +53,6 @@ extension SuggestionAccountCollectionViewCell {
private func configure() {
contentView.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
- NSLayoutConstraint.activate([
- imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
- imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
- imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
- imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
- ])
+ imageView.pinToParent()
}
}
diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift
index 13c8311c7..7eacdc205 100644
--- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift
+++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift
@@ -161,7 +161,7 @@ extension SuggestionAccountViewController: UITableViewDelegate {
case .account(let record):
guard let account = record.object(in: context.managedObjectContext) else { return }
let cachedProfileViewModel = CachedProfileViewModel(context: context, authContext: viewModel.authContext, mastodonUser: account)
- coordinator.present(
+ _ = coordinator.present(
scene: .profile(viewModel: cachedProfileViewModel),
from: self,
transition: .show
diff --git a/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift b/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift
index 47bd9d6b3..d259ebed4 100644
--- a/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift
+++ b/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift
@@ -97,12 +97,7 @@ extension SuggestionAccountTableViewCell {
containerStackView.isLayoutMarginsRelativeArrangement = true
containerStackView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(containerStackView)
- NSLayoutConstraint.activate([
- containerStackView.topAnchor.constraint(equalTo: contentView.topAnchor),
- containerStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
- containerStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
- containerStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
- ])
+ containerStackView.pinToParent()
avatarButton.translatesAutoresizingMaskIntoConstraints = false
containerStackView.addArrangedSubview(avatarButton)
diff --git a/Mastodon/Scene/Thread/ThreadViewController.swift b/Mastodon/Scene/Thread/ThreadViewController.swift
index 5dee182c6..e8e6ce130 100644
--- a/Mastodon/Scene/Thread/ThreadViewController.swift
+++ b/Mastodon/Scene/Thread/ThreadViewController.swift
@@ -87,12 +87,7 @@ extension ThreadViewController {
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.pinToParent()
tableView.delegate = self
viewModel.setupDiffableDataSource(
diff --git a/Mastodon/Scene/Thread/ThreadViewModel+LoadThreadState.swift b/Mastodon/Scene/Thread/ThreadViewModel+LoadThreadState.swift
index 050670be7..1c6c40190 100644
--- a/Mastodon/Scene/Thread/ThreadViewModel+LoadThreadState.swift
+++ b/Mastodon/Scene/Thread/ThreadViewModel+LoadThreadState.swift
@@ -92,19 +92,27 @@ extension ThreadViewModel.LoadThreadState {
from: response.value.ancestors
)
)
+ // deprecated: Tree mode replies
+ // viewModel.mastodonStatusThreadViewModel.appendDescendant(
+ // domain: threadContext.domain,
+ // nodes: MastodonStatusThreadViewModel.Node.children(
+ // of: threadContext.statusID,
+ // from: response.value.descendants
+ // )
+ // )
+
+ // new: the same order from API
viewModel.mastodonStatusThreadViewModel.appendDescendant(
domain: threadContext.domain,
- nodes: MastodonStatusThreadViewModel.Node.children(
- of: threadContext.statusID,
- from: response.value.descendants
- )
+ nodes: response.value.descendants.map { status in
+ return .init(statusID: status.id, children: [])
+ }
)
} catch {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch status context for \(threadContext.statusID) fail: \(error.localizedDescription)")
await enter(state: Fail.self)
}
-
- }
+ } // end Task
}
}
diff --git a/Mastodon/Scene/Thread/ThreadViewModel.swift b/Mastodon/Scene/Thread/ThreadViewModel.swift
index 735d85cd4..c845ce64c 100644
--- a/Mastodon/Scene/Thread/ThreadViewModel.swift
+++ b/Mastodon/Scene/Thread/ThreadViewModel.swift
@@ -28,12 +28,6 @@ class ThreadViewModel {
let context: AppContext
let authContext: AuthContext
let mastodonStatusThreadViewModel: MastodonStatusThreadViewModel
-
-// let cellFrameCache = NSCache()
-// let existStatusFetchedResultsController: StatusFetchedResultsController
-
-// weak var contentOffsetAdjustableTimelineViewControllerDelegate: ContentOffsetAdjustableTimelineViewControllerDelegate?
-// weak var tableView: UITableView?
// output
var diffableDataSource: UITableViewDiffableDataSource?
@@ -62,12 +56,6 @@ class ThreadViewModel {
self.authContext = authContext
self.root = optionalRoot
self.mastodonStatusThreadViewModel = MastodonStatusThreadViewModel(context: context)
-// self.rootNode = CurrentValueSubject(optionalStatus.flatMap { RootNode(domain: $0.domain, statusID: $0.id, replyToID: $0.inReplyToID) })
-// self.rootItem = CurrentValueSubject(optionalStatus.flatMap { Item.root(statusObjectID: $0.objectID, attribute: Item.StatusAttribute()) })
-// self.existStatusFetchedResultsController = StatusFetchedResultsController(managedObjectContext: context.managedObjectContext, domain: nil, additionalTweetPredicate: nil)
-// self.navigationBarTitle = CurrentValueSubject(
-// optionalStatus.flatMap { L10n.Scene.Thread.title($0.author.displayNameWithFallback) })
-// self.navigationBarTitleEmojiMeta = CurrentValueSubject(optionalStatus.flatMap { $0.author.emojis.asDictionary } ?? [:])
// end init
ManagedObjectObserver.observe(context: context.managedObjectContext)
@@ -85,24 +73,6 @@ class ThreadViewModel {
})
.store(in: &disposeBag)
-// // bind fetcher domain
-// context.authenticationService.activeMastodonAuthenticationBox
-// .receive(on: RunLoop.main)
-// .sink { [weak self] box in
-// guard let self = self else { return }
-// self.existStatusFetchedResultsController.domain.value = box?.domain
-// }
-// .store(in: &disposeBag)
-//
-// rootNode
-// .receive(on: DispatchQueue.main)
-// .sink { [weak self] rootNode in
-// guard let self = self else { return }
-// guard rootNode != nil else { return }
-// self.loadThreadStateMachine.enter(LoadThreadState.Loading.self)
-// }
-// .store(in: &disposeBag)
-
$root
.receive(on: DispatchQueue.main)
.sink { [weak self] root in
@@ -125,102 +95,6 @@ class ThreadViewModel {
}()
}
.store(in: &disposeBag)
-
-// rootItem
-// .receive(on: DispatchQueue.main)
-// .sink { [weak self] rootItem in
-// guard let self = self else { return }
-// guard case let .root(objectID, _) = rootItem else { return }
-// self.context.managedObjectContext.perform {
-// guard let status = self.context.managedObjectContext.object(with: objectID) as? Status else {
-// return
-// }
-// self.rootItemObserver = ManagedObjectObserver.observe(object: status)
-// .receive(on: DispatchQueue.main)
-// .sink(receiveCompletion: { _ in
-// // do nothing
-// }, receiveValue: { [weak self] change in
-// guard let self = self else { return }
-// switch change.changeType {
-// case .delete:
-// self.rootItem.value = nil
-// default:
-// break
-// }
-// })
-// }
-// }
-// .store(in: &disposeBag)
-//
-// ancestorNodes
-// .receive(on: DispatchQueue.main)
-// .compactMap { [weak self] nodes -> [Item]? in
-// guard let self = self else { return nil }
-// guard !nodes.isEmpty else { return [] }
-//
-// guard let diffableDataSource = self.diffableDataSource else { return nil }
-// let oldSnapshot = diffableDataSource.snapshot()
-// var oldSnapshotAttributeDict: [NSManagedObjectID : Item.StatusAttribute] = [:]
-// for item in oldSnapshot.itemIdentifiers {
-// switch item {
-// case .reply(let objectID, let attribute):
-// oldSnapshotAttributeDict[objectID] = attribute
-// default:
-// break
-// }
-// }
-//
-// var items: [Item] = []
-// for node in nodes {
-// let attribute = oldSnapshotAttributeDict[node.statusObjectID] ?? Item.StatusAttribute()
-// items.append(Item.reply(statusObjectID: node.statusObjectID, attribute: attribute))
-// }
-//
-// return items.reversed()
-// }
-// .assign(to: \.value, on: ancestorItems)
-// .store(in: &disposeBag)
-//
-// descendantNodes
-// .receive(on: DispatchQueue.main)
-// .compactMap { [weak self] nodes -> [Item]? in
-// guard let self = self else { return nil }
-// guard !nodes.isEmpty else { return [] }
-//
-// guard let diffableDataSource = self.diffableDataSource else { return nil }
-// let oldSnapshot = diffableDataSource.snapshot()
-// var oldSnapshotAttributeDict: [NSManagedObjectID : Item.StatusAttribute] = [:]
-// for item in oldSnapshot.itemIdentifiers {
-// switch item {
-// case .leaf(let objectID, let attribute):
-// oldSnapshotAttributeDict[objectID] = attribute
-// default:
-// break
-// }
-// }
-//
-// var items: [Item] = []
-//
-// func buildThread(node: LeafNode) {
-// let attribute = oldSnapshotAttributeDict[node.objectID] ?? Item.StatusAttribute()
-// items.append(Item.leaf(statusObjectID: node.objectID, attribute: attribute))
-// // only expand the first child
-// if let firstChild = node.children.first {
-// if !node.isChildrenExpanded {
-// items.append(Item.leafBottomLoader(statusObjectID: node.objectID))
-// } else {
-// buildThread(node: firstChild)
-// }
-// }
-// }
-//
-// for node in nodes {
-// buildThread(node: node)
-// }
-// return items
-// }
-// .assign(to: \.value, on: descendantItems)
-// .store(in: &disposeBag)
}
deinit {
diff --git a/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift b/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift
index 5381feb0d..ace6048c5 100644
--- a/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift
+++ b/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift
@@ -65,7 +65,7 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning {
let initialFrame = transitionItem.initialFrame ?? toViewEndFrame
let transitionTargetFrame: CGRect = {
let aspectRatio = transitionItem.aspectRatio ?? CGSize(width: initialFrame.width, height: initialFrame.height)
- return AVMakeRect(aspectRatio: aspectRatio, insideRect: toView.bounds)
+ return AVMakeRect(aspectRatio: aspectRatio, insideRect: toView.bounds.inset(by: toView.safeAreaInsets))
}()
let transitionImageView: UIImageView = {
let imageView = UIImageView(frame: transitionContext.containerView.convert(initialFrame, from: nil))
@@ -131,6 +131,12 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning {
}
return animator
}
+
+ if let toVC = transitionContext.viewController(forKey: .to) {
+ animator.addCompletion { _ in
+ toVC.setNeedsStatusBarAppearanceUpdate()
+ }
+ }
// update close button
UIView.animate(withDuration: 0.33, delay: 0, options: [.curveEaseInOut]) {
@@ -263,7 +269,7 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning {
}()
// FIXME:
- let maskLayerToFinalPath = maskLayerToFinalRect.flatMap { UIBezierPath(rect: $0) }?.cgPath
+ _ = maskLayerToFinalRect.flatMap { UIBezierPath(rect: $0) }?.cgPath
if let maskLayerToPath = maskLayerToPath {
maskLayer.path = maskLayerToPath
diff --git a/Mastodon/Supporting Files/AppDelegate.swift b/Mastodon/Supporting Files/AppDelegate.swift
index 3bf38f6da..84b819a5c 100644
--- a/Mastodon/Supporting Files/AppDelegate.swift
+++ b/Mastodon/Supporting Files/AppDelegate.swift
@@ -65,11 +65,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
extension AppDelegate {
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
- #if DEBUG
- return .all
- #else
return UIDevice.current.userInterfaceIdiom == .phone ? .portrait : .all
- #endif
}
}
diff --git a/Mastodon/Supporting Files/SceneDelegate.swift b/Mastodon/Supporting Files/SceneDelegate.swift
index 1e97fb179..4476477fd 100644
--- a/Mastodon/Supporting Files/SceneDelegate.swift
+++ b/Mastodon/Supporting Files/SceneDelegate.swift
@@ -109,6 +109,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
// trigger status filter update
AppContext.shared.statusFilterService.filterUpdatePublisher.send()
+
+ // trigger authenticated user account update
+ AppContext.shared.authenticationService.updateActiveUserAccountPublisher.send()
if let shortcutItem = savedShortCutItem {
Task {
@@ -172,7 +175,7 @@ extension SceneDelegate {
return false
}
- coordinator.switchToTabBar(tab: .notification)
+ coordinator.switchToTabBar(tab: .notifications)
case "org.joinmastodon.app.new-post":
if coordinator?.tabBarController.topMost is ComposeViewController {
diff --git a/MastodonIntent/cs.lproj/Intents.strings b/MastodonIntent/cs.lproj/Intents.strings
new file mode 100644
index 000000000..6f29830a1
--- /dev/null
+++ b/MastodonIntent/cs.lproj/Intents.strings
@@ -0,0 +1,51 @@
+"16wxgf" = "Příspěvek na Mastodon";
+
+"751xkl" = "Textový obsah";
+
+"CsR7G2" = "Příspěvek na Mastodon";
+
+"HZSGTr" = "Jaký obsah se má přidat?";
+
+"HdGikU" = "Odeslání se nezdařilo";
+
+"KDNTJ4" = "Důvod selhání";
+
+"RHxKOw" = "Odeslat příspěvek s textovým obsahem";
+
+"RxSqsb" = "Příspěvek";
+
+"WCIR3D" = "Zveřejnit ${content} na Mastodon";
+
+"ZKJSNu" = "Příspěvek";
+
+"ZS1XaK" = "${content}";
+
+"ZbSjzC" = "Viditelnost";
+
+"Zo4jgJ" = "Viditelnost příspěvku";
+
+"apSxMG-dYQ5NN" = "Existuje ${count} možností odpovídajících 'Veřejný'.";
+
+"apSxMG-ehFLjY" = "Existuje ${count} možností, které odpovídají „jen sledujícím“.";
+
+"ayoYEb-dYQ5NN" = "${content}, veřejné";
+
+"ayoYEb-ehFLjY" = "${content}, pouze sledující";
+
+"dUyuGg" = "Příspěvek na Mastodon";
+
+"dYQ5NN" = "Veřejný";
+
+"ehFLjY" = "Pouze sledující";
+
+"gfePDu" = "Odeslání se nezdařilo. ${failureReason}";
+
+"k7dbKQ" = "Příspěvek byl úspěšně odeslán.";
+
+"oGiqmY-dYQ5NN" = "Jen pro kontrolu, chtěli jste „Veřejný“?";
+
+"oGiqmY-ehFLjY" = "Jen pro kontrolu, chtěli jste „Pouze sledující“?";
+
+"rM6dvp" = "URL";
+
+"ryJLwG" = "Příspěvek byl úspěšně odeslán. ";
diff --git a/MastodonIntent/cs.lproj/Intents.stringsdict b/MastodonIntent/cs.lproj/Intents.stringsdict
new file mode 100644
index 000000000..5a39d5e64
--- /dev/null
+++ b/MastodonIntent/cs.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/sl.lproj/Intents.strings b/MastodonIntent/sl.lproj/Intents.strings
new file mode 100644
index 000000000..72de87df2
--- /dev/null
+++ b/MastodonIntent/sl.lproj/Intents.strings
@@ -0,0 +1,51 @@
+"16wxgf" = "Objavi na Mastodonu";
+
+"751xkl" = "besedilo";
+
+"CsR7G2" = "Objavi na Mastodonu";
+
+"HZSGTr" = "Kakšno vsebino želite objaviti?";
+
+"HdGikU" = "Objava ni uspela";
+
+"KDNTJ4" = "Vzrok za neuspeh";
+
+"RHxKOw" = "Pošlji objavo z besedilom";
+
+"RxSqsb" = "Objavi";
+
+"WCIR3D" = "Objavite ${content} na Mastodonu";
+
+"ZKJSNu" = "Objavi";
+
+"ZS1XaK" = "${content}";
+
+"ZbSjzC" = "Vidnost";
+
+"Zo4jgJ" = "Vidnost objave";
+
+"apSxMG-dYQ5NN" = "Z \"Javno\" se ujema ${count} možnosti.";
+
+"apSxMG-ehFLjY" = "S \"Samo sledilci\" se ujema ${count} možnosti.";
+
+"ayoYEb-dYQ5NN" = "${content}, javno";
+
+"ayoYEb-ehFLjY" = "${content}, samo sledilci";
+
+"dUyuGg" = "Objavi na Mastodonu";
+
+"dYQ5NN" = "Javno";
+
+"ehFLjY" = "Samo sledilci";
+
+"gfePDu" = "Objava je spodletela. ${failureReason}";
+
+"k7dbKQ" = "Uspešno poslana objava.";
+
+"oGiqmY-dYQ5NN" = "Da ne bo nesporazuma - želeli ste \"Javno\"?";
+
+"oGiqmY-ehFLjY" = "Da ne bo nesporazuma - želeli ste \"Samo sledilci\"?";
+
+"rM6dvp" = "URL";
+
+"ryJLwG" = "Uspešno poslana objava. ";
diff --git a/MastodonIntent/sl.lproj/Intents.stringsdict b/MastodonIntent/sl.lproj/Intents.stringsdict
new file mode 100644
index 000000000..5a39d5e64
--- /dev/null
+++ b/MastodonIntent/sl.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.resolved b/MastodonSDK/Package.resolved
index 06843faa3..4a4fa51de 100644
--- a/MastodonSDK/Package.resolved
+++ b/MastodonSDK/Package.resolved
@@ -1,241 +1,257 @@
{
- "object": {
- "pins": [
- {
- "package": "Alamofire",
- "repositoryURL": "https://github.com/Alamofire/Alamofire.git",
- "state": {
- "branch": null,
- "revision": "8dd85aee02e39dd280c75eef88ffdb86eed4b07b",
- "version": "5.6.2"
- }
- },
- {
- "package": "AlamofireImage",
- "repositoryURL": "https://github.com/Alamofire/AlamofireImage.git",
- "state": {
- "branch": null,
- "revision": "98cbb00ce0ec5fc8e52a5b50a6bfc08d3e5aee10",
- "version": "4.2.0"
- }
- },
- {
- "package": "CommonOSLog",
- "repositoryURL": "https://github.com/MainasuK/CommonOSLog",
- "state": {
- "branch": null,
- "revision": "c121624a30698e9886efe38aebb36ff51c01b6c2",
- "version": "0.1.1"
- }
- },
- {
- "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",
- "state": {
- "branch": null,
- "revision": "d4f07b6f164d53c1212c3e54d6460738b1981e9f",
- "version": "1.0.17"
- }
- },
- {
- "package": "FPSIndicator",
- "repositoryURL": "https://github.com/MainasuK/FPSIndicator.git",
- "state": {
- "branch": null,
- "revision": "e4a5067ccd5293b024c767f09e51056afd4a4796",
- "version": "1.1.0"
- }
- },
- {
- "package": "Fuzi",
- "repositoryURL": "https://github.com/cezheng/Fuzi.git",
- "state": {
- "branch": null,
- "revision": "f08c8323da21e985f3772610753bcfc652c2103f",
- "version": "3.1.3"
- }
- },
- {
- "package": "KeychainAccess",
- "repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess.git",
- "state": {
- "branch": null,
- "revision": "84e546727d66f1adc5439debad16270d0fdd04e7",
- "version": "4.2.2"
- }
- },
- {
- "package": "MetaTextKit",
- "repositoryURL": "https://github.com/TwidereProject/MetaTextKit.git",
- "state": {
- "branch": null,
- "revision": "dcd5255d6930c2fab408dc8562c577547e477624",
- "version": "2.2.5"
- }
- },
- {
- "package": "Nuke",
- "repositoryURL": "https://github.com/kean/Nuke.git",
- "state": {
- "branch": null,
- "revision": "a002b7fd786f2df2ed4333fe73a9727499fd9d97",
- "version": "10.11.2"
- }
- },
- {
- "package": "NukeFLAnimatedImagePlugin",
- "repositoryURL": "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git",
- "state": {
- "branch": null,
- "revision": "b59c346a7d536336db3b0f12c72c6e53ee709e16",
- "version": "8.0.0"
- }
- },
- {
- "package": "Pageboy",
- "repositoryURL": "https://github.com/uias/Pageboy",
- "state": {
- "branch": null,
- "revision": "af8fa81788b893205e1ff42ddd88c5b0b315d7c5",
- "version": "3.7.0"
- }
- },
- {
- "package": "PanModal",
- "repositoryURL": "https://github.com/slackhq/PanModal.git",
- "state": {
- "branch": null,
- "revision": "b012aecb6b67a8e46369227f893c12544846613f",
- "version": "1.2.7"
- }
- },
- {
- "package": "SDWebImage",
- "repositoryURL": "https://github.com/SDWebImage/SDWebImage.git",
- "state": {
- "branch": null,
- "revision": "9248fe561a2a153916fb9597e3af4434784c6d32",
- "version": "5.13.4"
- }
- },
- {
- "package": "swift-collections",
- "repositoryURL": "https://github.com/apple/swift-collections.git",
- "state": {
- "branch": null,
- "revision": "f504716c27d2e5d4144fa4794b12129301d17729",
- "version": "1.0.3"
- }
- },
- {
- "package": "swift-nio",
- "repositoryURL": "https://github.com/apple/swift-nio.git",
- "state": {
- "branch": null,
- "revision": "546610d52b19be3e19935e0880bb06b9c03f5cef",
- "version": "1.14.4"
- }
- },
- {
- "package": "swift-nio-zlib-support",
- "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git",
- "state": {
- "branch": null,
- "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd",
- "version": "1.0.0"
- }
- },
- {
- "package": "SwiftSoup",
- "repositoryURL": "https://github.com/scinfu/SwiftSoup.git",
- "state": {
- "branch": null,
- "revision": "6778575285177365cbad3e5b8a72f2a20583cfec",
- "version": "2.4.3"
- }
- },
- {
- "package": "Introspect",
- "repositoryURL": "https://github.com/siteline/SwiftUI-Introspect.git",
- "state": {
- "branch": null,
- "revision": "f2616860a41f9d9932da412a8978fec79c06fe24",
- "version": "0.1.4"
- }
- },
- {
- "package": "SwiftyJSON",
- "repositoryURL": "https://github.com/SwiftyJSON/SwiftyJSON.git",
- "state": {
- "branch": null,
- "revision": "b3dcd7dbd0d488e1a7077cb33b00f2083e382f07",
- "version": "5.0.1"
- }
- },
- {
- "package": "TabBarPager",
- "repositoryURL": "https://github.com/TwidereProject/TabBarPager.git",
- "state": {
- "branch": null,
- "revision": "488aa66d157a648901b61721212c0dec23d27ee5",
- "version": "0.1.0"
- }
- },
- {
- "package": "Tabman",
- "repositoryURL": "https://github.com/uias/Tabman",
- "state": {
- "branch": null,
- "revision": "4a4f7c755b875ffd4f9ef10d67a67883669d2465",
- "version": "2.13.0"
- }
- },
- {
- "package": "ThirdPartyMailer",
- "repositoryURL": "https://github.com/vtourraine/ThirdPartyMailer.git",
- "state": {
- "branch": null,
- "revision": "44c1cfaa6969963f22691aa67f88a69e3b6d651f",
- "version": "2.1.0"
- }
- },
- {
- "package": "TOCropViewController",
- "repositoryURL": "https://github.com/TimOliver/TOCropViewController.git",
- "state": {
- "branch": null,
- "revision": "d0470491f56e734731bbf77991944c0dfdee3e0e",
- "version": "2.6.1"
- }
- },
- {
- "package": "UIHostingConfigurationBackport",
- "repositoryURL": "https://github.com/woxtu/UIHostingConfigurationBackport.git",
- "state": {
- "branch": null,
- "revision": "6091f2d38faa4b24fc2ca0389c651e2f666624a3",
- "version": "0.1.0"
- }
- },
- {
- "package": "UITextView+Placeholder",
- "repositoryURL": "https://github.com/MainasuK/UITextView-Placeholder.git",
- "state": {
- "branch": null,
- "revision": "20f513ded04a040cdf5467f0891849b1763ede3b",
- "version": "1.4.1"
- }
+ "pins" : [
+ {
+ "identity" : "alamofire",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/Alamofire/Alamofire.git",
+ "state" : {
+ "revision" : "8dd85aee02e39dd280c75eef88ffdb86eed4b07b",
+ "version" : "5.6.2"
}
- ]
- },
- "version": 1
+ },
+ {
+ "identity" : "alamofireimage",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/Alamofire/AlamofireImage.git",
+ "state" : {
+ "revision" : "98cbb00ce0ec5fc8e52a5b50a6bfc08d3e5aee10",
+ "version" : "4.2.0"
+ }
+ },
+ {
+ "identity" : "commonoslog",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/MainasuK/CommonOSLog",
+ "state" : {
+ "revision" : "c121624a30698e9886efe38aebb36ff51c01b6c2",
+ "version" : "0.1.1"
+ }
+ },
+ {
+ "identity" : "faviconfinder",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/will-lumley/FaviconFinder.git",
+ "state" : {
+ "revision" : "1f74844f77f79b95c0bb0130b3a87d4f340e6d3a",
+ "version" : "3.3.0"
+ }
+ },
+ {
+ "identity" : "flanimatedimage",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/Flipboard/FLAnimatedImage.git",
+ "state" : {
+ "revision" : "d4f07b6f164d53c1212c3e54d6460738b1981e9f",
+ "version" : "1.0.17"
+ }
+ },
+ {
+ "identity" : "fpsindicator",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/MainasuK/FPSIndicator.git",
+ "state" : {
+ "revision" : "e4a5067ccd5293b024c767f09e51056afd4a4796",
+ "version" : "1.1.0"
+ }
+ },
+ {
+ "identity" : "fuzi",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/cezheng/Fuzi.git",
+ "state" : {
+ "revision" : "f08c8323da21e985f3772610753bcfc652c2103f",
+ "version" : "3.1.3"
+ }
+ },
+ {
+ "identity" : "keychainaccess",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/kishikawakatsumi/KeychainAccess.git",
+ "state" : {
+ "revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
+ "version" : "4.2.2"
+ }
+ },
+ {
+ "identity" : "kingfisher",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/onevcat/Kingfisher.git",
+ "state" : {
+ "revision" : "44e891bdb61426a95e31492a67c7c0dfad1f87c5",
+ "version" : "7.4.1"
+ }
+ },
+ {
+ "identity" : "metatextkit",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/TwidereProject/MetaTextKit.git",
+ "state" : {
+ "revision" : "dcd5255d6930c2fab408dc8562c577547e477624",
+ "version" : "2.2.5"
+ }
+ },
+ {
+ "identity" : "nextlevelsessionexporter",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/NextLevel/NextLevelSessionExporter.git",
+ "state" : {
+ "revision" : "b6c0cce1aa37fe1547d694f958fac3c3524b74da",
+ "version" : "0.4.6"
+ }
+ },
+ {
+ "identity" : "nuke",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/kean/Nuke.git",
+ "state" : {
+ "revision" : "a002b7fd786f2df2ed4333fe73a9727499fd9d97",
+ "version" : "10.11.2"
+ }
+ },
+ {
+ "identity" : "nuke-flanimatedimage-plugin",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git",
+ "state" : {
+ "revision" : "b59c346a7d536336db3b0f12c72c6e53ee709e16",
+ "version" : "8.0.0"
+ }
+ },
+ {
+ "identity" : "pageboy",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/uias/Pageboy",
+ "state" : {
+ "revision" : "af8fa81788b893205e1ff42ddd88c5b0b315d7c5",
+ "version" : "3.7.0"
+ }
+ },
+ {
+ "identity" : "panmodal",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/slackhq/PanModal.git",
+ "state" : {
+ "revision" : "b012aecb6b67a8e46369227f893c12544846613f",
+ "version" : "1.2.7"
+ }
+ },
+ {
+ "identity" : "sdwebimage",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/SDWebImage/SDWebImage.git",
+ "state" : {
+ "revision" : "9248fe561a2a153916fb9597e3af4434784c6d32",
+ "version" : "5.13.4"
+ }
+ },
+ {
+ "identity" : "stripes",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/eneko/Stripes.git",
+ "state" : {
+ "revision" : "d533fd44b8043a3abbf523e733599173d6f98c11",
+ "version" : "0.2.0"
+ }
+ },
+ {
+ "identity" : "swift-collections",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-collections.git",
+ "state" : {
+ "revision" : "f504716c27d2e5d4144fa4794b12129301d17729",
+ "version" : "1.0.3"
+ }
+ },
+ {
+ "identity" : "swift-nio",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-nio.git",
+ "state" : {
+ "revision" : "546610d52b19be3e19935e0880bb06b9c03f5cef",
+ "version" : "1.14.4"
+ }
+ },
+ {
+ "identity" : "swift-nio-zlib-support",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-nio-zlib-support.git",
+ "state" : {
+ "revision" : "37760e9a52030bb9011972c5213c3350fa9d41fd",
+ "version" : "1.0.0"
+ }
+ },
+ {
+ "identity" : "swiftsoup",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/scinfu/SwiftSoup.git",
+ "state" : {
+ "revision" : "6778575285177365cbad3e5b8a72f2a20583cfec",
+ "version" : "2.4.3"
+ }
+ },
+ {
+ "identity" : "swiftui-introspect",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/siteline/SwiftUI-Introspect.git",
+ "state" : {
+ "revision" : "f2616860a41f9d9932da412a8978fec79c06fe24",
+ "version" : "0.1.4"
+ }
+ },
+ {
+ "identity" : "tabbarpager",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/TwidereProject/TabBarPager.git",
+ "state" : {
+ "revision" : "488aa66d157a648901b61721212c0dec23d27ee5",
+ "version" : "0.1.0"
+ }
+ },
+ {
+ "identity" : "tabman",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/uias/Tabman",
+ "state" : {
+ "revision" : "4a4f7c755b875ffd4f9ef10d67a67883669d2465",
+ "version" : "2.13.0"
+ }
+ },
+ {
+ "identity" : "thirdpartymailer",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/vtourraine/ThirdPartyMailer.git",
+ "state" : {
+ "revision" : "44c1cfaa6969963f22691aa67f88a69e3b6d651f",
+ "version" : "2.1.0"
+ }
+ },
+ {
+ "identity" : "tocropviewcontroller",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/TimOliver/TOCropViewController.git",
+ "state" : {
+ "revision" : "d0470491f56e734731bbf77991944c0dfdee3e0e",
+ "version" : "2.6.1"
+ }
+ },
+ {
+ "identity" : "uihostingconfigurationbackport",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/woxtu/UIHostingConfigurationBackport.git",
+ "state" : {
+ "revision" : "6091f2d38faa4b24fc2ca0389c651e2f666624a3",
+ "version" : "0.1.0"
+ }
+ },
+ {
+ "identity" : "uitextview-placeholder",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/MainasuK/UITextView-Placeholder.git",
+ "state" : {
+ "revision" : "20f513ded04a040cdf5467f0891849b1763ede3b",
+ "version" : "1.4.1"
+ }
+ }
+ ],
+ "version" : 2
}
diff --git a/MastodonSDK/Package.swift b/MastodonSDK/Package.swift
index 8db2a1ca5..1ac22e6a7 100644
--- a/MastodonSDK/Package.swift
+++ b/MastodonSDK/Package.swift
@@ -32,7 +32,6 @@ let package = Package(
.package(url: "https://github.com/Alamofire/AlamofireImage.git", from: "4.1.0"),
.package(url: "https://github.com/apple/swift-collections.git", from: "1.0.3"),
.package(url: "https://github.com/apple/swift-nio.git", from: "1.0.0"),
- .package(url: "https://github.com/cezheng/Fuzi.git", from: "3.1.3"),
.package(url: "https://github.com/Flipboard/FLAnimatedImage.git", from: "1.0.0"),
.package(url: "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git", from: "8.0.0"),
.package(url: "https://github.com/kean/Nuke.git", from: "10.3.1"),
@@ -49,6 +48,7 @@ let package = Package(
.package(url: "https://github.com/SDWebImage/SDWebImage.git", from: "5.12.0"),
.package(url: "https://github.com/eneko/Stripes.git", from: "0.2.0"),
.package(url: "https://github.com/onevcat/Kingfisher.git", from: "7.4.1"),
+ .package(url: "https://github.com/NextLevel/NextLevelSessionExporter.git", from: "0.4.6"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
@@ -124,6 +124,7 @@ let package = Package(
.product(name: "PanModal", package: "PanModal"),
.product(name: "Stripes", package: "Stripes"),
.product(name: "Kingfisher", package: "Kingfisher"),
+ .product(name: "NextLevelSessionExporter", package: "NextLevelSessionExporter"),
]
),
.testTarget(
diff --git a/MastodonSDK/Sources/CoreDataStack/Entity/Transient/MastodonNotificationType.swift b/MastodonSDK/Sources/CoreDataStack/Entity/Transient/MastodonNotificationType.swift
index 9e3029f26..455230d5e 100644
--- a/MastodonSDK/Sources/CoreDataStack/Entity/Transient/MastodonNotificationType.swift
+++ b/MastodonSDK/Sources/CoreDataStack/Entity/Transient/MastodonNotificationType.swift
@@ -7,7 +7,7 @@
import Foundation
-public enum MastodonNotificationType: RawRepresentable {
+public enum MastodonNotificationType: RawRepresentable, Equatable {
case follow
case followRequest
case mention
diff --git a/MastodonSDK/Sources/CoreDataStack/Utility/ManagedObjectRecord.swift b/MastodonSDK/Sources/CoreDataStack/Utility/ManagedObjectRecord.swift
index 4910145cf..88b08fc3c 100644
--- a/MastodonSDK/Sources/CoreDataStack/Utility/ManagedObjectRecord.swift
+++ b/MastodonSDK/Sources/CoreDataStack/Utility/ManagedObjectRecord.swift
@@ -32,7 +32,7 @@ public class ManagedObjectRecord: Hashable {
}
extension Managed where Self: NSManagedObject {
- public var asRecrod: ManagedObjectRecord {
+ public var asRecord: ManagedObjectRecord {
return .init(objectID: objectID)
}
}
diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Attachment/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Attachment/Contents.json
new file mode 100644
index 000000000..6e965652d
--- /dev/null
+++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Attachment/Contents.json
@@ -0,0 +1,9 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "provides-namespace" : true
+ }
+}
diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Attachment/indicator.button.background.colorset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Attachment/indicator.button.background.colorset/Contents.json
new file mode 100644
index 000000000..7a1c8d9e2
--- /dev/null
+++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Attachment/indicator.button.background.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.400",
+ "green" : "0.275",
+ "red" : "0.275"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.400",
+ "green" : "0.275",
+ "red" : "0.275"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Attachment/retry.imageset/Arrow Clockwise.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Attachment/retry.imageset/Arrow Clockwise.pdf
new file mode 100644
index 000000000..a15c522d8
--- /dev/null
+++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Attachment/retry.imageset/Arrow Clockwise.pdf
@@ -0,0 +1,91 @@
+%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.750000 cm
+0.000000 0.000000 0.000000 scn
+9.250000 16.500000 m
+5.245935 16.500000 2.000000 13.254065 2.000000 9.250000 c
+2.000000 5.245935 5.245935 2.000000 9.250000 2.000000 c
+13.254065 2.000000 16.500000 5.245935 16.500000 9.250000 c
+16.500000 9.535608 16.483484 9.817360 16.451357 10.094351 c
+16.383255 10.681498 16.809317 11.250000 17.400400 11.250000 c
+17.916018 11.250000 18.369314 10.891933 18.431660 10.380100 c
+18.476776 10.009713 18.500000 9.632568 18.500000 9.250000 c
+18.500000 4.141366 14.358634 0.000000 9.250000 0.000000 c
+4.141366 0.000000 0.000000 4.141366 0.000000 9.250000 c
+0.000000 14.358634 4.141366 18.500000 9.250000 18.500000 c
+11.423139 18.500000 13.421247 17.750608 15.000000 16.496151 c
+15.000000 17.000000 l
+15.000000 17.552284 15.447716 18.000000 16.000000 18.000000 c
+16.552284 18.000000 17.000000 17.552284 17.000000 17.000000 c
+17.000000 14.301708 l
+17.011232 14.284512 17.022409 14.267276 17.033529 14.250000 c
+17.000000 14.250000 l
+17.000000 14.000000 l
+17.000000 13.447716 16.552284 13.000000 16.000000 13.000000 c
+13.000000 13.000000 l
+12.447715 13.000000 12.000000 13.447716 12.000000 14.000000 c
+12.000000 14.552284 12.447715 15.000000 13.000000 15.000000 c
+13.666476 15.000000 l
+12.443584 15.940684 10.912110 16.500000 9.250000 16.500000 c
+h
+f
+n
+Q
+
+endstream
+endobj
+
+3 0 obj
+ 1365
+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
+0000001455 00000 n
+0000001478 00000 n
+0000001651 00000 n
+0000001725 00000 n
+trailer
+<< /ID [ (some) (id) ]
+ /Root 6 0 R
+ /Size 7
+>>
+startxref
+1784
+%%EOF
\ No newline at end of file
diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.large.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Attachment/retry.imageset/Contents.json
similarity index 52%
rename from MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.large.imageset/Contents.json
rename to MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Attachment/retry.imageset/Contents.json
index fefc19832..92bff3aca 100644
--- a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.large.imageset/Contents.json
+++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Attachment/retry.imageset/Contents.json
@@ -1,12 +1,15 @@
{
"images" : [
{
- "filename" : "mastodon.logo.black.large.pdf",
+ "filename" : "Arrow Clockwise.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true
}
}
diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Attachment/stop.imageset/Contents.json
similarity index 54%
rename from MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.imageset/Contents.json
rename to MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Attachment/stop.imageset/Contents.json
index 3018f4b9a..b2b588d4d 100644
--- a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.imageset/Contents.json
+++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Attachment/stop.imageset/Contents.json
@@ -1,12 +1,15 @@
{
"images" : [
{
- "filename" : "mastodon.logo.black.pdf",
+ "filename" : "Dismiss.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true
}
}
diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Attachment/stop.imageset/Dismiss.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Attachment/stop.imageset/Dismiss.pdf
new file mode 100644
index 000000000..0616f6275
--- /dev/null
+++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Attachment/stop.imageset/Dismiss.pdf
@@ -0,0 +1,89 @@
+%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.804749 cm
+0.000000 0.000000 0.000000 scn
+0.209704 15.808150 m
+0.292893 15.902358 l
+0.653377 16.262842 1.220608 16.290571 1.612899 15.985547 c
+1.707107 15.902358 l
+8.000000 9.610251 l
+14.292892 15.902358 l
+14.683416 16.292883 15.316584 16.292883 15.707108 15.902358 c
+16.097631 15.511834 16.097631 14.878669 15.707108 14.488145 c
+9.415000 8.195251 l
+15.707108 1.902359 l
+16.067591 1.541875 16.095320 0.974643 15.790295 0.582352 c
+15.707108 0.488144 l
+15.346623 0.127661 14.779391 0.099932 14.387100 0.404957 c
+14.292892 0.488144 l
+8.000000 6.780252 l
+1.707107 0.488144 l
+1.316582 0.097620 0.683418 0.097620 0.292893 0.488144 c
+-0.097631 0.878668 -0.097631 1.511835 0.292893 1.902359 c
+6.585000 8.195251 l
+0.292893 14.488145 l
+-0.067591 14.848629 -0.095320 15.415859 0.209704 15.808150 c
+0.292893 15.902358 l
+0.209704 15.808150 l
+h
+f
+n
+Q
+
+endstream
+endobj
+
+3 0 obj
+ 914
+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
+0000001004 00000 n
+0000001026 00000 n
+0000001199 00000 n
+0000001273 00000 n
+trailer
+<< /ID [ (some) (id) ]
+ /Root 6 0 R
+ /Size 7
+>>
+startxref
+1332
+%%EOF
\ No newline at end of file
diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Profile/About/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Profile/About/Contents.json
new file mode 100644
index 000000000..6e965652d
--- /dev/null
+++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Profile/About/Contents.json
@@ -0,0 +1,9 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "provides-namespace" : true
+ }
+}
diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Profile/About/bio.about.field.verified.background.colorset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Profile/About/bio.about.field.verified.background.colorset/Contents.json
new file mode 100644
index 000000000..86944ced3
--- /dev/null
+++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Profile/About/bio.about.field.verified.background.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.852",
+ "green" : "0.894",
+ "red" : "0.835"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.354",
+ "green" : "0.353",
+ "red" : "0.268"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Profile/About/bio.about.field.verified.checkmark.colorset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Profile/About/bio.about.field.verified.checkmark.colorset/Contents.json
new file mode 100644
index 000000000..f5112f04f
--- /dev/null
+++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Profile/About/bio.about.field.verified.checkmark.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.371",
+ "green" : "0.565",
+ "red" : "0.290"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.603",
+ "green" : "0.742",
+ "red" : "0.476"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Profile/About/bio.about.field.verified.link.colorset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Profile/About/bio.about.field.verified.link.colorset/Contents.json
new file mode 100644
index 000000000..f5112f04f
--- /dev/null
+++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Profile/About/bio.about.field.verified.link.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.371",
+ "green" : "0.565",
+ "red" : "0.290"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.603",
+ "green" : "0.742",
+ "red" : "0.476"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.imageset/mastodon.logo.black.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.imageset/mastodon.logo.black.pdf
deleted file mode 100644
index 773ed5e77..000000000
--- a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.imageset/mastodon.logo.black.pdf
+++ /dev/null
@@ -1,339 +0,0 @@
-%PDF-1.7
-
-1 0 obj
- << /BBox [ 0.000000 0.000000 480.000000 119.097778 ]
- /Resources << >>
- /Subtype /Form
- /Length 2 0 R
- /Group << /Type /Group
- /S /Transparency
- >>
- /Type /XObject
- >>
-stream
-/DeviceRGB CS
-/DeviceRGB cs
-q
-1.000000 0.000000 -0.000000 1.000000 0.001709 -0.290527 cm
-0.188235 0.533333 0.831373 scn
-107.682541 47.532677 m
-106.063889 39.212074 93.195923 30.106506 78.416977 28.341690 c
-70.709908 27.421394 63.121937 26.576881 55.030510 26.946808 c
-41.798027 27.553123 31.357124 30.104706 31.357124 30.104706 c
-31.357124 28.818085 31.436523 27.591019 31.595320 26.443352 c
-33.315022 13.385902 44.544495 12.602745 55.180283 12.238235 c
-65.917122 11.870117 75.475624 14.885460 75.475624 14.885460 c
-75.917725 5.177185 l
-75.917725 5.177185 68.407349 1.147720 55.030510 0.406067 c
-47.655472 0.000053 38.495773 0.590118 27.827501 3.414185 c
-4.693666 9.538696 0.712913 34.197334 0.106597 59.224106 c
--0.079267 66.653275 0.034417 73.660202 0.034417 79.517647 c
-0.034417 105.105621 16.798328 112.606972 16.798328 112.606972 c
-25.250660 116.488472 39.757122 118.121559 54.837425 118.244263 c
-55.207348 118.244263 l
-70.287651 118.121559 84.801338 116.488472 93.255470 112.606972 c
-93.255470 112.606972 110.019386 105.105621 110.019386 79.517647 c
-110.019386 79.517647 110.230507 60.638844 107.682541 47.532677 c
-h
-f
-n
-Q
-q
-1.000000 0.000000 -0.000000 1.000000 23.245789 45.780518 cm
-0.121569 0.137255 0.168627 scn
-0.000000 39.648720 m
-0.000000 43.373230 3.018947 46.392181 6.743458 46.392181 c
-10.467969 46.392181 13.486919 43.373230 13.486919 39.648720 c
-13.486919 35.924210 10.467969 32.905262 6.743458 32.905262 c
-3.018947 32.905262 0.000000 35.924210 0.000000 39.648720 c
-h
-96.718201 31.461655 m
-96.718201 0.478188 l
-84.443916 0.478188 l
-84.443916 30.553986 l
-84.443916 36.893234 81.776840 40.110676 76.440903 40.110676 c
-70.541954 40.110676 67.586166 36.292332 67.586166 28.745865 c
-67.586166 12.286915 l
-55.384064 12.286915 l
-55.384064 28.745865 l
-55.384064 36.294136 52.426468 40.110676 46.529324 40.110676 c
-41.193382 40.110676 38.524509 36.893234 38.524509 30.553986 c
-38.524509 0.481804 l
-26.252031 0.481804 l
-26.252031 31.465263 l
-26.252031 37.795486 27.865263 42.828270 31.104361 46.550980 c
-34.442707 50.273685 38.816845 52.181053 44.246616 52.181053 c
-50.526318 52.181053 55.284817 49.768425 58.430077 44.939552 c
-61.486919 39.814735 l
-64.543762 44.939552 l
-67.689026 49.768425 72.445709 52.182858 78.727219 52.182858 c
-84.156990 52.182858 88.529327 50.273685 91.869476 46.552784 c
-95.108574 42.828270 96.720009 37.795490 96.720009 31.463459 c
-96.718201 31.461655 l
-h
-139.005112 16.060150 m
-141.536835 18.736240 142.758499 22.105263 142.758499 26.170826 c
-142.758499 30.236389 141.536835 33.605415 139.005112 36.184063 c
-136.565414 38.860153 133.468872 40.148571 129.715500 40.148571 c
-125.962112 40.148571 122.867371 38.860153 120.427673 36.184063 c
-117.987968 33.605415 116.768120 30.236389 116.768120 26.170826 c
-116.768120 22.107067 117.987968 18.736240 120.427673 16.060150 c
-122.867371 13.483311 125.962112 12.194881 129.715500 12.194881 c
-133.468872 12.194881 136.565414 13.483311 139.005112 16.060150 c
-139.005112 16.060150 l
-h
-142.758499 50.953987 m
-154.859543 50.953987 l
-154.859543 1.389473 l
-142.754898 1.389473 l
-142.754898 7.236088 l
-139.097153 2.378342 134.030075 -0.000008 127.463463 -0.000008 c
-121.176544 -0.000008 115.827965 2.477585 111.323906 7.533829 c
-106.915489 12.590069 104.665268 18.835487 104.665268 26.169022 c
-104.665268 33.405113 106.915489 39.650528 111.323906 44.706768 c
-115.827965 49.763008 121.176544 52.339851 127.463463 52.339851 c
-134.030075 52.339851 139.097153 49.959702 142.754898 45.103760 c
-142.754898 50.950378 l
-142.758499 50.953987 l
-h
-195.581955 27.062256 m
-199.147659 24.387970 200.930527 20.620148 200.836685 15.863457 c
-200.836685 10.807217 199.053848 6.840900 195.394302 4.065559 c
-191.734756 1.389473 187.326309 0.001801 181.977737 0.001801 c
-172.314575 0.001801 165.746170 3.966316 162.274292 11.797894 c
-172.783768 18.041504 l
-174.191299 13.781052 177.286011 11.599396 181.977737 11.599396 c
-186.294128 11.599396 188.452347 12.988873 188.452347 15.863457 c
-188.452347 17.944057 185.637283 19.827969 179.913376 21.313084 c
-177.755188 21.908573 175.972336 22.504059 174.566620 23.000301 c
-172.596085 23.792480 170.907074 24.685715 169.499557 25.775639 c
-166.027664 28.451729 164.246628 32.019249 164.246628 36.579250 c
-164.246628 41.436996 165.933823 45.302254 169.311874 48.079399 c
-172.783752 50.953987 177.004501 52.341656 182.071579 52.341656 c
-190.141342 52.341656 196.051117 48.871578 199.898331 41.833984 c
-189.578354 35.886318 l
-188.076996 39.255341 185.543457 40.940754 182.071579 40.940754 c
-178.412018 40.940754 176.630966 39.553085 176.630966 36.876991 c
-176.630966 34.796391 179.444214 32.912483 185.168121 31.425564 c
-189.578339 30.433083 193.048416 28.947971 195.581955 27.064060 c
-195.581955 27.062256 l
-h
-234.052322 38.661655 m
-223.449036 38.661655 l
-223.449036 18.043308 l
-223.449036 15.563908 224.389175 14.078796 226.170227 13.384060 c
-227.483917 12.887817 230.111267 12.788570 234.052322 12.987068 c
-234.052322 1.389473 l
-225.890518 0.396988 219.978958 1.190971 216.507080 3.868866 c
-213.037003 6.445709 211.346176 11.202404 211.346176 18.043308 c
-211.346176 38.661655 l
-203.184372 38.661655 l
-203.184372 50.953987 l
-211.346176 50.953987 l
-211.346176 60.965416 l
-223.449036 64.830681 l
-223.449036 50.953987 l
-234.052322 50.953987 l
-234.052322 38.661655 l
-234.052322 38.661655 l
-h
-272.614716 16.357895 m
-275.054413 18.936543 276.274261 22.208122 276.274261 26.172634 c
-276.274261 30.137146 275.054413 33.408722 272.614716 35.985565 c
-270.176819 38.562408 267.174133 39.850830 263.514587 39.850830 c
-259.855011 39.850830 256.854126 38.562408 254.414429 35.985565 c
-252.068558 33.309475 250.848709 30.037895 250.848709 26.172634 c
-250.848709 22.305565 252.068558 19.033985 254.414429 16.357895 c
-256.854126 13.781052 259.855011 12.492626 263.514587 12.492626 c
-267.174133 12.492626 270.176819 13.781052 272.614716 16.357895 c
-h
-245.875488 7.535637 m
-241.091721 12.590076 238.745850 18.736240 238.745850 26.172634 c
-238.745850 33.507973 241.091721 39.652328 245.875488 44.708572 c
-250.661057 49.763008 256.570801 52.341656 263.514587 52.341656 c
-270.458344 52.341656 276.368134 49.763008 281.153687 44.708572 c
-285.939240 39.652328 288.377136 33.408722 288.377136 26.172634 c
-288.377136 18.835491 285.939240 12.590076 281.153687 7.535637 c
-276.368134 2.479401 270.552155 0.001801 263.514587 0.001801 c
-256.476990 0.001801 250.661057 2.479401 245.875488 7.535637 c
-h
-328.818054 16.060150 m
-331.257751 18.736240 332.475769 22.105263 332.475769 26.170826 c
-332.475769 30.236389 331.257751 33.605415 328.818054 36.184063 c
-326.378357 38.860153 323.281799 40.148571 319.528412 40.148571 c
-315.775024 40.148571 312.680298 38.860153 310.146759 36.184063 c
-307.708893 33.605415 306.487213 30.236389 306.487213 26.170826 c
-306.487213 22.107067 307.708893 18.736240 310.146759 16.060150 c
-312.680298 13.483311 315.870667 12.194881 319.528412 12.194881 c
-323.281799 12.194881 326.378357 13.483311 328.818054 16.060150 c
-328.818054 16.060150 l
-h
-332.475769 70.780151 m
-344.580475 70.780151 l
-344.580475 1.389473 l
-332.475769 1.389473 l
-332.475769 7.236088 l
-328.911865 2.378342 323.844818 -0.000008 317.278198 -0.000008 c
-310.991272 -0.000008 305.550690 2.477585 301.046631 7.533829 c
-296.636414 12.590069 294.384369 18.835487 294.384369 26.169022 c
-294.384369 33.405113 296.636414 39.650528 301.046631 44.706768 c
-305.550690 49.763008 310.991272 52.339851 317.278198 52.339851 c
-323.844818 52.339851 328.911865 49.959702 332.475769 45.103760 c
-332.475769 70.780151 l
-332.475769 70.780151 l
-h
-387.083923 16.357895 m
-389.523621 18.936543 390.743469 22.208122 390.743469 26.172634 c
-390.743469 30.137146 389.523621 33.408722 387.083923 35.985565 c
-384.644226 38.562408 381.643311 39.850830 377.983765 39.850830 c
-374.324219 39.850830 371.321503 38.562408 368.881805 35.985565 c
-366.535950 33.309475 365.316101 30.037895 365.316101 26.172634 c
-365.316101 22.305565 366.535950 19.033985 368.881805 16.357895 c
-371.321503 13.781052 374.324219 12.492626 377.983765 12.492626 c
-381.643311 12.492626 384.644226 13.781052 387.083923 16.357895 c
-387.083923 16.357895 l
-h
-360.344666 7.535637 m
-355.559082 12.590076 353.215027 18.736240 353.215027 26.172634 c
-353.215027 33.507973 355.559082 39.652328 360.344666 44.708572 c
-365.130219 49.763008 371.040009 52.341656 377.983765 52.341656 c
-384.925720 52.341656 390.837280 49.763008 395.622864 44.708572 c
-400.408417 39.652328 402.846313 33.408722 402.846313 26.172634 c
-402.846313 18.835491 400.408417 12.590076 395.622864 7.535637 c
-390.837280 2.479401 385.019562 0.001801 377.983765 0.001801 c
-370.946167 0.001801 365.130219 2.479401 360.344666 7.535637 c
-360.344666 7.535637 l
-h
-455.202423 31.822556 m
-455.202423 1.389473 l
-443.097748 1.389473 l
-443.097748 30.236389 l
-443.097748 33.507969 442.255035 35.985565 440.566010 37.869476 c
-438.970825 39.553085 436.718811 40.446320 433.809937 40.446320 c
-426.960022 40.446320 423.489929 36.382557 423.489929 28.153984 c
-423.489929 1.389473 l
-411.387054 1.389473 l
-411.387054 50.953987 l
-423.489929 50.953987 l
-423.489929 45.403309 l
-426.398773 50.060753 430.994904 52.341656 437.469482 52.341656 c
-442.630371 52.341656 446.851135 50.556992 450.135345 46.888420 c
-453.513397 43.221657 455.202423 38.264664 455.202423 31.820751 c
-455.202423 31.822556 l
-h
-f
-n
-Q
-
-endstream
-endobj
-
-2 0 obj
- 9224
-endobj
-
-3 0 obj
- << /BBox [ 0.000000 0.000000 480.000000 119.097778 ]
- /Resources << >>
- /Subtype /Form
- /Length 4 0 R
- /Group << /Type /Group
- /S /Transparency
- >>
- /Type /XObject
- >>
-stream
-/DeviceRGB CS
-/DeviceRGB cs
-q
-1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
-0.000000 0.000000 0.000000 scn
-0.000000 119.097778 m
-480.000000 119.097778 l
-480.000000 0.000031 l
-0.000000 0.000031 l
-0.000000 119.097778 l
-h
-f
-n
-Q
-
-endstream
-endobj
-
-4 0 obj
- 237
-endobj
-
-5 0 obj
- << /XObject << /X1 1 0 R >>
- /ExtGState << /E1 << /SMask << /Type /Mask
- /G 3 0 R
- /S /Alpha
- >>
- /Type /ExtGState
- >> >>
- >>
-endobj
-
-6 0 obj
- << /Length 7 0 R >>
-stream
-/DeviceRGB CS
-/DeviceRGB cs
-q
-/E1 gs
-/X1 Do
-Q
-
-endstream
-endobj
-
-7 0 obj
- 46
-endobj
-
-8 0 obj
- << /Annots []
- /Type /Page
- /MediaBox [ 0.000000 0.000000 480.000000 119.097778 ]
- /Resources 5 0 R
- /Contents 6 0 R
- /Parent 9 0 R
- >>
-endobj
-
-9 0 obj
- << /Kids [ 8 0 R ]
- /Count 1
- /Type /Pages
- >>
-endobj
-
-10 0 obj
- << /Type /Catalog
- /Pages 9 0 R
- >>
-endobj
-
-xref
-0 11
-0000000000 65535 f
-0000000010 00000 n
-0000009484 00000 n
-0000009507 00000 n
-0000009994 00000 n
-0000010016 00000 n
-0000010314 00000 n
-0000010416 00000 n
-0000010437 00000 n
-0000010612 00000 n
-0000010686 00000 n
-trailer
-<< /ID [ (some) (id) ]
- /Root 10 0 R
- /Size 11
->>
-startxref
-10746
-%%EOF
\ No newline at end of file
diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.large.imageset/mastodon.logo.black.large.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.large.imageset/mastodon.logo.black.large.pdf
deleted file mode 100644
index b6244d04e..000000000
--- a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.large.imageset/mastodon.logo.black.large.pdf
+++ /dev/null
@@ -1,339 +0,0 @@
-%PDF-1.7
-
-1 0 obj
- << /BBox [ 0.000000 0.000000 960.000000 238.195496 ]
- /Resources << >>
- /Subtype /Form
- /Length 2 0 R
- /Group << /Type /Group
- /S /Transparency
- >>
- /Type /XObject
- >>
-stream
-/DeviceRGB CS
-/DeviceRGB cs
-q
-1.000000 0.000000 -0.000000 1.000000 0.003357 -0.580933 cm
-0.188235 0.533333 0.831373 scn
-215.365082 95.065247 m
-212.127777 78.424042 186.391846 60.212906 156.833954 56.683273 c
-141.419815 54.842682 126.243874 53.153656 110.061020 53.893509 c
-83.596054 55.106140 62.714249 60.209290 62.714249 60.209290 c
-62.714249 57.636063 62.873047 55.181931 63.190639 52.886597 c
-66.630043 26.771698 89.088989 25.205383 110.360565 24.476364 c
-131.834244 23.740112 150.951248 29.770813 150.951248 29.770813 c
-151.835449 10.354263 l
-151.835449 10.354263 136.814697 2.295334 110.061020 0.812027 c
-95.310944 0.000000 76.991547 1.180130 55.655003 6.828262 c
-9.387332 19.077271 1.425826 68.394562 0.213194 118.448097 c
--0.158535 133.306442 0.068834 147.320282 0.068834 159.035172 c
-0.068834 210.211121 33.596657 225.213821 33.596657 225.213821 c
-50.501320 232.976822 79.514244 236.242996 109.674850 236.488403 c
-110.414696 236.488403 l
-140.575302 236.242996 169.602676 232.976822 186.510941 225.213821 c
-186.510941 225.213821 220.038773 210.211121 220.038773 159.035172 c
-220.038773 159.035172 220.461014 121.277573 215.365082 95.065247 c
-h
-f
-n
-Q
-q
-1.000000 0.000000 -0.000000 1.000000 46.491440 91.561096 cm
-0.121569 0.137255 0.168627 scn
-0.000000 79.297432 m
-0.000000 86.746460 6.037893 92.784363 13.486916 92.784363 c
-20.935938 92.784363 26.973839 86.746460 26.973839 79.297432 c
-26.973839 71.848412 20.935938 65.810516 13.486916 65.810516 c
-6.037893 65.810516 0.000000 71.848412 0.000000 79.297432 c
-h
-193.436401 62.923302 m
-193.436401 0.956360 l
-168.887833 0.956360 l
-168.887833 61.107964 l
-168.887833 73.786461 163.553680 80.221344 152.881805 80.221344 c
-141.083908 80.221344 135.172333 72.584648 135.172333 57.491714 c
-135.172333 24.573814 l
-110.768127 24.573814 l
-110.768127 57.491714 l
-110.768127 72.588257 104.852936 80.221344 93.058647 80.221344 c
-82.386765 80.221344 77.049019 73.786461 77.049019 61.107964 c
-77.049019 0.963593 l
-52.504063 0.963593 l
-52.504063 62.930511 l
-52.504063 75.590965 55.730526 85.656540 62.208721 93.101944 c
-68.885414 100.547363 77.633690 104.362106 88.493233 104.362106 c
-101.052635 104.362106 110.569633 99.536835 116.860153 89.879089 c
-122.973839 79.629471 l
-129.087524 89.879089 l
-135.378052 99.536835 144.891418 104.365707 157.454437 104.365707 c
-168.313980 104.365707 177.058655 100.547363 183.738953 93.105560 c
-190.217148 85.656540 193.440018 75.590973 193.440018 62.926910 c
-193.436401 62.923302 l
-h
-278.010223 32.120285 m
-283.073669 37.472473 285.516998 44.210510 285.516998 52.341637 c
-285.516998 60.472771 283.073669 67.210823 278.010223 72.368118 c
-273.130829 77.720299 266.937744 80.297134 259.431000 80.297134 c
-251.924225 80.297134 245.734741 77.720299 240.855347 72.368118 c
-235.975937 67.210823 233.536240 60.472771 233.536240 52.341637 c
-233.536240 44.214119 235.975937 37.472473 240.855347 32.120285 c
-245.734741 26.966606 251.924225 24.389748 259.431000 24.389748 c
-266.937744 24.389748 273.130829 26.966606 278.010223 32.120285 c
-278.010223 32.120285 l
-h
-285.516998 101.907967 m
-309.719086 101.907967 l
-309.719086 2.778915 l
-285.509796 2.778915 l
-285.509796 14.472160 l
-278.194305 4.756668 268.060150 -0.000031 254.926926 -0.000031 c
-242.353088 -0.000031 231.655930 4.955154 222.647812 15.067642 c
-213.830978 25.180122 209.330536 37.670959 209.330536 52.338036 c
-209.330536 66.810219 213.830978 79.301048 222.647812 89.413528 c
-231.655930 99.526016 242.353088 104.679695 254.926926 104.679695 c
-268.060150 104.679695 278.194305 99.919395 285.509796 90.207512 c
-285.509796 101.900742 l
-285.516998 101.907967 l
-h
-391.163910 54.124504 m
-398.295319 48.775932 401.861053 41.240288 401.673370 31.726898 c
-401.673370 21.614418 398.107697 13.681786 390.788605 8.131104 c
-383.469513 2.778931 374.652618 0.003571 363.955475 0.003571 c
-344.629150 0.003571 331.492340 7.932617 324.548584 23.595772 c
-345.567535 36.082993 l
-348.382599 27.562088 354.572021 23.198776 363.955475 23.198776 c
-372.588257 23.198776 376.904694 25.977730 376.904694 31.726898 c
-376.904694 35.888107 371.274567 39.655930 359.826752 42.626152 c
-355.510376 43.817131 351.944672 45.008102 349.133240 46.000587 c
-345.192169 47.584946 341.814148 49.371414 338.999115 51.551262 c
-332.055328 56.903450 328.493256 64.038490 328.493256 73.158493 c
-328.493256 82.873978 331.867645 90.604507 338.623749 96.158791 c
-345.567505 101.907967 354.009003 104.683304 364.143158 104.683304 c
-380.282684 104.683304 392.102234 97.743149 399.796661 83.667961 c
-379.156708 71.772621 l
-376.153992 78.510666 371.086914 81.881500 364.143158 81.881500 c
-356.824036 81.881500 353.261932 79.106155 353.261932 73.753975 c
-353.261932 69.592773 358.888428 65.824951 370.336243 62.851120 c
-379.156677 60.866158 386.096832 57.895927 391.163910 54.128113 c
-391.163910 54.124504 l
-h
-468.104645 77.323303 m
-446.898071 77.323303 l
-446.898071 36.086601 l
-446.898071 31.127808 448.778351 28.157578 452.340454 26.768105 c
-454.967834 25.775620 460.222534 25.577126 468.104645 25.974121 c
-468.104645 2.778915 l
-451.781036 0.793961 439.957916 2.381927 433.014160 7.737717 c
-426.074005 12.891403 422.692352 22.404793 422.692352 36.086601 c
-422.692352 77.323303 l
-406.368744 77.323303 l
-406.368744 101.907967 l
-422.692352 101.907967 l
-422.692352 121.930832 l
-446.898071 129.661346 l
-446.898071 101.907967 l
-468.104645 101.907967 l
-468.104645 77.323303 l
-468.104645 77.323303 l
-h
-545.229431 32.715775 m
-550.108826 37.873070 552.548523 44.416229 552.548523 52.345253 c
-552.548523 60.274277 550.108826 66.817436 545.229431 71.971123 c
-540.353638 77.124809 534.348267 79.701645 527.029175 79.701645 c
-519.710022 79.701645 513.708252 77.124809 508.828857 71.971123 c
-504.137115 66.618942 501.697418 60.075783 501.697418 52.345253 c
-501.697418 44.611115 504.137115 38.067955 508.828857 32.715775 c
-513.708252 27.562088 519.710022 24.985237 527.029175 24.985237 c
-534.348267 24.985237 540.353638 27.562088 545.229431 32.715775 c
-h
-491.750977 15.071259 m
-482.183441 25.180138 477.491699 37.472473 477.491699 52.345253 c
-477.491699 67.015930 482.183441 79.304657 491.750977 89.417137 c
-501.322113 99.526016 513.141602 104.683304 527.029175 104.683304 c
-540.916687 104.683304 552.736267 99.526016 562.307373 89.417137 c
-571.878479 79.304657 576.754272 66.817436 576.754272 52.345253 c
-576.754272 37.670967 571.878479 25.180138 562.307373 15.071259 c
-552.736267 4.958771 541.104309 0.003571 527.029175 0.003571 c
-512.953979 0.003571 501.322113 4.958771 491.750977 15.071259 c
-h
-657.636108 32.120285 m
-662.515503 37.472473 664.951538 44.210510 664.951538 52.341637 c
-664.951538 60.472771 662.515503 67.210823 657.636108 72.368118 c
-652.756714 77.720299 646.563599 80.297134 639.056824 80.297134 c
-631.550049 80.297134 625.360596 77.720299 620.293518 72.368118 c
-615.417786 67.210823 612.974426 60.472771 612.974426 52.341637 c
-612.974426 44.214119 615.417786 37.472473 620.293518 32.120285 c
-625.360596 26.966606 631.741333 24.389748 639.056824 24.389748 c
-646.563599 24.389748 652.756714 26.966606 657.636108 32.120285 c
-657.636108 32.120285 l
-h
-664.951538 141.560303 m
-689.160950 141.560303 l
-689.160950 2.778915 l
-664.951538 2.778915 l
-664.951538 14.472160 l
-657.823730 4.756668 647.689636 -0.000031 634.556396 -0.000031 c
-621.982544 -0.000031 611.101379 4.955154 602.093262 15.067642 c
-593.272827 25.180122 588.768738 37.670959 588.768738 52.338036 c
-588.768738 66.810219 593.272827 79.301048 602.093262 89.413528 c
-611.101379 99.526016 621.982544 104.679695 634.556396 104.679695 c
-647.689636 104.679695 657.823730 99.919395 664.951538 90.207512 c
-664.951538 141.560303 l
-664.951538 141.560303 l
-h
-774.167847 32.715775 m
-779.047241 37.873070 781.486938 44.416229 781.486938 52.345253 c
-781.486938 60.274277 779.047241 66.817436 774.167847 71.971123 c
-769.288452 77.124809 763.286621 79.701645 755.967529 79.701645 c
-748.648438 79.701645 742.643005 77.124809 737.763611 71.971123 c
-733.071899 66.618942 730.632202 60.075783 730.632202 52.345253 c
-730.632202 44.611115 733.071899 38.067955 737.763611 32.715775 c
-742.643005 27.562088 748.648438 24.985237 755.967529 24.985237 c
-763.286621 24.985237 769.288452 27.562088 774.167847 32.715775 c
-774.167847 32.715775 l
-h
-720.689331 15.071259 m
-711.118164 25.180138 706.430054 37.472473 706.430054 52.345253 c
-706.430054 67.015930 711.118164 79.304657 720.689331 89.417137 c
-730.260437 99.526016 742.080017 104.683304 755.967529 104.683304 c
-769.851440 104.683304 781.674561 99.526016 791.245728 89.417137 c
-800.816833 79.304657 805.692627 66.817436 805.692627 52.345253 c
-805.692627 37.670967 800.816833 25.180138 791.245728 15.071259 c
-781.674561 4.958771 770.039124 0.003571 755.967529 0.003571 c
-741.892334 0.003571 730.260437 4.958771 720.689331 15.071259 c
-720.689331 15.071259 l
-h
-910.404846 63.645103 m
-910.404846 2.778915 l
-886.195496 2.778915 l
-886.195496 60.472771 l
-886.195496 67.015930 884.510071 71.971123 881.132019 75.738945 c
-877.941650 79.106163 873.437622 80.892624 867.619873 80.892624 c
-853.920044 80.892624 846.979858 72.765106 846.979858 56.307961 c
-846.979858 2.778915 l
-822.774109 2.778915 l
-822.774109 101.907967 l
-846.979858 101.907967 l
-846.979858 90.806610 l
-852.797546 100.121506 861.989807 104.683304 874.938965 104.683304 c
-885.260742 104.683304 893.702271 101.113983 900.270691 93.776840 c
-907.026794 86.443298 910.404846 76.529312 910.404846 63.641495 c
-910.404846 63.645103 l
-h
-f
-n
-Q
-
-endstream
-endobj
-
-2 0 obj
- 9343
-endobj
-
-3 0 obj
- << /BBox [ 0.000000 0.000000 960.000000 238.195496 ]
- /Resources << >>
- /Subtype /Form
- /Length 4 0 R
- /Group << /Type /Group
- /S /Transparency
- >>
- /Type /XObject
- >>
-stream
-/DeviceRGB CS
-/DeviceRGB cs
-q
-1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
-0.000000 0.000000 0.000000 scn
-0.000000 238.195496 m
-960.000000 238.195496 l
-960.000000 0.000000 l
-0.000000 0.000000 l
-0.000000 238.195496 l
-h
-f
-n
-Q
-
-endstream
-endobj
-
-4 0 obj
- 237
-endobj
-
-5 0 obj
- << /XObject << /X1 1 0 R >>
- /ExtGState << /E1 << /SMask << /Type /Mask
- /G 3 0 R
- /S /Alpha
- >>
- /Type /ExtGState
- >> >>
- >>
-endobj
-
-6 0 obj
- << /Length 7 0 R >>
-stream
-/DeviceRGB CS
-/DeviceRGB cs
-q
-/E1 gs
-/X1 Do
-Q
-
-endstream
-endobj
-
-7 0 obj
- 46
-endobj
-
-8 0 obj
- << /Annots []
- /Type /Page
- /MediaBox [ 0.000000 0.000000 960.000000 238.195496 ]
- /Resources 5 0 R
- /Contents 6 0 R
- /Parent 9 0 R
- >>
-endobj
-
-9 0 obj
- << /Kids [ 8 0 R ]
- /Count 1
- /Type /Pages
- >>
-endobj
-
-10 0 obj
- << /Type /Catalog
- /Pages 9 0 R
- >>
-endobj
-
-xref
-0 11
-0000000000 65535 f
-0000000010 00000 n
-0000009603 00000 n
-0000009626 00000 n
-0000010113 00000 n
-0000010135 00000 n
-0000010433 00000 n
-0000010535 00000 n
-0000010556 00000 n
-0000010731 00000 n
-0000010805 00000 n
-trailer
-<< /ID [ (some) (id) ]
- /Root 10 0 R
- /Size 11
->>
-startxref
-10865
-%%EOF
\ No newline at end of file
diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.imageset/Contents.json
index 6a0bfc87a..76d42c15e 100644
--- a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.imageset/Contents.json
+++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.imageset/Contents.json
@@ -1,7 +1,7 @@
{
"images" : [
{
- "filename" : "logotypeFull1.pdf",
+ "filename" : "logo.small.pdf",
"idiom" : "universal"
}
],
diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.imageset/logo.small.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.imageset/logo.small.pdf
new file mode 100644
index 000000000..581801f38
--- /dev/null
+++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.imageset/logo.small.pdf
@@ -0,0 +1,648 @@
+%PDF-1.7
+
+1 0 obj
+ << /Type /XObject
+ /Length 2 0 R
+ /Group << /Type /Group
+ /S /Transparency
+ >>
+ /Subtype /Form
+ /Resources << >>
+ /BBox [ 0.000000 0.000000 269.000000 75.000000 ]
+ >>
+stream
+/DeviceRGB CS
+/DeviceRGB cs
+q
+1.000000 0.000000 -0.000000 1.000000 4.000000 4.000000 cm
+0.000000 0.000000 0.000000 scn
+0.000000 67.000000 m
+261.000000 67.000000 l
+261.000000 0.000000 l
+0.000000 0.000000 l
+0.000000 67.000000 l
+h
+f
+n
+Q
+
+endstream
+endobj
+
+2 0 obj
+ 234
+endobj
+
+3 0 obj
+ << /Length 4 0 R
+ /Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ]
+ /Domain [ 0.000000 1.000000 ]
+ /FunctionType 4
+ >>
+stream
+{ 0.388235 exch 0.392157 exch 1.000000 exch dup 0.000000 gt { exch pop exch pop exch pop dup 0.000000 sub -0.050980 mul 0.388235 add exch dup 0.000000 sub -0.164706 mul 0.392157 add exch dup 0.000000 sub -0.200000 mul 1.000000 add exch } if dup 1.000000 gt { exch pop exch pop exch pop 0.337255 exch 0.227451 exch 0.800000 exch } if pop }
+endstream
+endobj
+
+4 0 obj
+ 339
+endobj
+
+5 0 obj
+ << /Type /XObject
+ /Length 6 0 R
+ /Group << /Type /Group
+ /S /Transparency
+ >>
+ /Subtype /Form
+ /Resources << /Pattern << /P1 << /Matrix [ 0.000000 -65.993195 65.993195 0.000000 -61.993195 70.224548 ]
+ /Shading << /Coords [ 0.000000 0.000000 1.000000 0.000000 ]
+ /ColorSpace /DeviceRGB
+ /Function 3 0 R
+ /Domain [ 0.000000 1.000000 ]
+ /ShadingType 2
+ /Extend [ true true ]
+ >>
+ /PatternType 2
+ /Type /Pattern
+ >> >> >>
+ /BBox [ 0.000000 0.000000 269.000000 75.000000 ]
+ >>
+stream
+/DeviceRGB CS
+/DeviceRGB cs
+q
+1.000000 0.000000 -0.000000 1.000000 4.000000 3.632446 cm
+/Pattern cs
+/P1 scn
+60.826767 51.982300 m
+59.885666 59.068882 53.787518 64.663071 46.568073 65.739265 c
+45.346546 65.922020 40.730431 66.592102 30.036348 66.592102 c
+29.956215 66.592102 l
+19.252211 66.592102 16.959164 65.922020 15.737550 65.739265 c
+8.708311 64.683380 2.299911 59.667900 0.737860 52.489872 c
+-0.003115 48.956699 -0.083220 45.037697 0.056964 41.443653 c
+0.257227 36.286026 0.297280 31.148746 0.757885 26.011383 c
+1.078305 22.600037 1.629032 19.219128 2.420071 15.889084 c
+3.902017 9.736488 9.889900 4.619389 15.757648 2.538147 c
+22.035824 0.365425 28.794806 0.000015 35.263187 1.492470 c
+35.974140 1.665001 36.675091 1.857872 37.376038 2.081261 c
+38.948124 2.588921 40.790466 3.157455 42.152252 4.152424 c
+42.172348 4.162594 42.182354 4.182858 42.192364 4.203201 c
+42.202370 4.223465 42.212376 4.243816 42.212376 4.274334 c
+42.212376 9.249176 l
+42.212376 9.249176 42.212376 9.289791 42.192364 9.310051 c
+42.192364 9.330315 42.172348 9.350658 42.152252 9.360832 c
+42.132240 9.370922 42.112228 9.381180 42.092300 9.391270 c
+42.072121 9.391270 42.052189 9.391270 42.032177 9.391270 c
+37.886612 8.386127 33.631145 7.878468 29.375511 7.888641 c
+22.035824 7.888641 20.063231 11.421898 19.502539 12.883831 c
+19.051918 14.152893 18.761566 15.482990 18.641405 16.823097 c
+18.641405 16.843441 18.641405 16.863705 18.651411 16.884052 c
+18.651411 16.904316 18.671425 16.924664 18.691521 16.934837 c
+18.711451 16.944927 18.731462 16.955097 18.751476 16.965271 c
+18.821604 16.965271 l
+22.896957 15.970303 27.082464 15.462643 31.277977 15.462643 c
+32.289371 15.462643 33.290596 15.462646 34.301991 15.493084 c
+38.517517 15.614994 42.963440 15.828209 47.118843 16.650486 c
+47.218906 16.670830 47.329144 16.691097 47.419201 16.711441 c
+53.967884 17.990677 60.196030 21.990898 60.826767 32.123367 c
+60.846947 32.519287 60.906982 36.306290 60.906982 36.712379 c
+60.906982 38.123699 61.357521 46.692589 60.836857 51.961868 c
+60.826767 51.982300 l
+h
+f
+n
+Q
+q
+1.000000 0.000000 -0.000000 1.000000 16.353134 47.918640 cm
+1.000000 1.000000 1.000000 scn
+0.000000 3.736245 m
+0.000000 5.807401 1.632209 7.472382 3.654834 7.472382 c
+5.677542 7.472382 7.309585 5.797228 7.309585 3.736245 c
+7.309585 1.675262 5.677542 0.000028 3.654834 0.000028 c
+1.632209 0.000028 0.000000 1.675262 0.000000 3.736245 c
+h
+f
+n
+Q
+q
+1.000000 0.000000 -0.000000 1.000000 32.524460 30.973694 cm
+1.000000 1.000000 1.000000 scn
+38.200230 17.462723 m
+38.200230 0.284256 l
+31.541471 0.284256 l
+31.541471 16.955149 l
+31.541471 20.467976 30.099640 22.244776 27.205791 22.244776 c
+24.011585 22.244776 22.399469 20.122755 22.399469 15.950090 c
+22.399469 6.822678 l
+15.790834 6.822678 l
+15.790834 15.950090 l
+15.790834 20.143185 14.198734 22.244776 10.984432 22.244776 c
+8.100758 22.244776 6.648746 20.467976 6.648746 16.955149 c
+6.648746 0.294346 l
+0.000000 0.294346 l
+0.000000 17.462723 l
+0.000000 20.965542 0.871139 23.757576 2.623425 25.828648 c
+4.435832 27.899887 6.808930 28.945639 9.742804 28.945639 c
+13.147311 28.945639 15.730712 27.605450 17.432966 24.925154 c
+19.095194 22.082256 l
+20.757338 24.925154 l
+22.459507 27.595276 25.032986 28.945639 28.447416 28.945639 c
+31.381290 28.945639 33.754387 27.889629 35.566792 25.828648 c
+37.319077 23.757576 38.190220 20.985806 38.190220 17.462723 c
+38.200230 17.462723 l
+h
+61.110268 8.924355 m
+62.491982 10.416807 63.143234 12.264570 63.143234 14.518509 c
+63.143234 16.772449 62.481976 18.640476 61.110268 20.061882 c
+59.788589 21.554337 58.106689 22.265121 56.073723 22.265121 c
+54.041008 22.265121 52.368858 21.554337 51.037174 20.061882 c
+49.715416 18.640476 49.054577 16.772449 49.054577 14.518509 c
+49.054577 12.264570 49.715416 10.396461 51.037174 8.924355 c
+52.358852 7.502947 54.041008 6.781986 56.073723 6.781986 c
+58.106689 6.781986 59.778584 7.492773 61.110268 8.924355 c
+h
+63.143234 28.255198 m
+69.701591 28.255198 l
+69.701591 0.781738 l
+63.143234 0.781738 l
+63.143234 4.020473 l
+61.160301 1.330006 58.416882 -0.000011 54.852188 -0.000011 c
+51.287495 -0.000011 48.543831 1.370613 46.110611 4.172821 c
+43.717499 6.974941 42.505974 10.437069 42.505974 14.498163 c
+42.505974 18.559340 43.727505 21.970684 46.110611 24.772808 c
+48.553837 27.574930 51.457687 28.996338 54.852188 28.996338 c
+58.246773 28.996338 61.160301 27.676495 63.143234 24.996117 c
+63.143234 28.234852 l
+63.143234 28.255198 l
+h
+91.770676 15.036257 m
+93.703575 13.543886 94.665031 11.462475 94.615005 8.832880 c
+94.615005 6.030758 93.653549 3.827599 91.670616 2.304710 c
+89.688515 0.812176 87.294495 0.050774 84.390968 0.050774 c
+79.154297 0.050774 75.599525 2.253845 73.716660 6.579025 c
+79.405289 10.041067 l
+80.164940 7.685646 81.837677 6.467285 84.390968 6.467285 c
+86.734138 6.467285 87.895714 7.228771 87.895714 8.822790 c
+87.895714 9.980196 86.373070 11.025948 83.269424 11.838133 c
+82.097839 12.163090 81.127220 12.498138 80.375908 12.772230 c
+79.303558 13.208757 78.392975 13.706240 77.631653 14.315380 c
+75.749619 15.807833 74.789009 17.777590 74.789009 20.305622 c
+74.789009 22.996006 75.699593 25.138290 77.531593 26.681526 c
+79.415298 28.275459 81.706749 29.037029 84.451004 29.037029 c
+88.826294 29.037029 92.020851 27.118137 94.113853 23.219398 c
+88.526115 19.929964 l
+87.715591 21.798073 86.333038 22.732088 84.451004 22.732088 c
+82.468071 22.732088 81.506630 21.970686 81.506630 20.478231 c
+81.506630 19.320744 83.029274 18.274992 86.132919 17.462723 c
+88.526115 16.914539 90.408142 16.092180 91.770676 15.036257 c
+91.780693 15.036257 l
+91.770676 15.036257 l
+h
+112.618164 21.452854 m
+106.870323 21.452854 l
+106.870323 10.020803 l
+106.870323 8.650179 107.381485 7.817646 108.352104 7.441906 c
+109.063393 7.167816 110.485130 7.117115 112.628166 7.218597 c
+112.628166 0.791912 l
+108.212013 0.243645 105.008308 0.690430 103.126274 2.162537 c
+101.243401 3.583942 100.331985 6.233803 100.331985 10.010632 c
+100.331985 21.452854 l
+95.915833 21.452854 l
+95.915833 28.265369 l
+100.331985 28.265369 l
+100.331985 33.808784 l
+106.890335 35.951027 l
+106.890335 28.255198 l
+112.638176 28.255198 l
+112.638176 21.442680 l
+112.628166 21.442680 l
+112.618164 21.452854 l
+h
+133.525681 9.086706 m
+134.847351 10.508114 135.507782 12.325527 135.507782 14.528599 c
+135.507782 16.731840 134.847351 18.528820 133.525681 19.970573 c
+132.193161 21.391897 130.571289 22.112776 128.589188 22.112776 c
+126.606262 22.112776 124.984390 21.402071 123.651871 19.970573 c
+122.381058 18.478121 121.719803 16.680973 121.719803 14.528599 c
+121.719803 12.376308 122.381058 10.579159 123.651871 9.086706 c
+124.974380 7.665382 126.606262 6.944508 128.589188 6.944508 c
+130.571289 6.944508 132.193161 7.655127 133.525681 9.086706 c
+h
+119.037262 4.203255 m
+116.443100 7.005379 115.171455 10.406551 115.171455 14.528599 c
+115.171455 18.650732 116.443100 22.001038 119.037262 24.803242 c
+121.629745 27.605450 124.834297 29.026855 128.589188 29.026855 c
+132.343262 29.026855 135.558640 27.605450 138.141953 24.803242 c
+140.725266 22.001038 142.056961 18.538994 142.056961 14.528599 c
+142.056961 10.518290 140.725266 7.005379 138.141953 4.203255 c
+135.547806 1.401051 132.394119 0.030510 128.589188 0.030510 c
+124.784264 0.030510 121.619743 1.401051 119.037262 4.203255 c
+h
+163.985123 8.934444 m
+165.306808 10.426897 165.968063 12.274660 165.968063 14.528599 c
+165.968063 16.782539 165.306808 18.650732 163.985123 20.072056 c
+162.664291 21.564592 160.982376 22.275211 158.948578 22.275211 c
+156.916458 22.275211 155.233719 21.564592 153.862839 20.072056 c
+152.540329 18.650732 151.879898 16.782539 151.879898 14.528599 c
+151.879898 12.274660 152.540329 10.406551 153.862839 8.934444 c
+155.243713 7.513037 156.966492 6.792244 158.948578 6.792244 c
+160.932358 6.792244 162.654282 7.502947 163.985123 8.934444 c
+h
+165.968063 39.260834 m
+172.526413 39.260834 l
+172.526413 0.791912 l
+165.968063 0.791912 l
+165.968063 4.030647 l
+164.035995 1.340179 161.291748 0.010162 157.727798 0.010162 c
+154.163025 0.010162 151.379578 1.380703 148.925522 4.182991 c
+146.532318 6.985115 145.321548 10.447161 145.321548 14.508337 c
+145.321548 18.569429 146.542328 21.980774 148.925522 24.782898 c
+151.358734 27.585186 154.312286 29.006512 157.727798 29.006512 c
+161.141647 29.006512 164.035995 27.686666 165.968063 25.006289 c
+165.968063 39.250683 l
+165.968063 39.260834 l
+h
+195.566971 9.117228 m
+196.888641 10.538635 197.549896 12.355879 197.549896 14.559118 c
+197.549896 16.762192 196.888641 18.559340 195.566971 20.001011 c
+194.245285 21.422335 192.623413 22.143211 190.630478 22.143211 c
+188.638367 22.143211 187.026520 21.432508 185.694000 20.001011 c
+184.422363 18.508474 183.761093 16.711493 183.761093 14.559118 c
+183.761093 12.406662 184.422363 10.609680 185.694000 9.117228 c
+187.016510 7.695736 188.648376 6.974945 190.630478 6.974945 c
+192.613403 6.974945 194.235291 7.685648 195.566971 9.117228 c
+h
+181.077713 4.233692 m
+178.495224 7.035900 177.212753 10.437071 177.212753 14.559118 c
+177.212753 18.681084 178.485229 22.031557 181.077713 24.833679 c
+183.671036 27.635885 186.875580 29.057293 190.630478 29.057293 c
+194.385376 29.057293 197.599915 27.635885 200.183228 24.833679 c
+202.775726 22.031557 204.098236 18.569429 204.098236 14.559118 c
+204.098236 10.548725 202.775726 7.035900 200.183228 4.233692 c
+197.589905 1.431572 194.435410 0.060863 190.630478 0.060863 c
+186.825546 0.060863 183.661026 1.431572 181.077713 4.233692 c
+h
+232.475540 17.696289 m
+232.475540 0.822433 l
+225.917206 0.822433 l
+225.917206 16.812975 l
+225.917206 18.630386 225.466064 20.001011 224.535477 21.036505 c
+223.674088 21.970686 222.452469 22.457994 220.879807 22.457994 c
+217.175766 22.457994 215.292923 20.204056 215.292923 15.645479 c
+215.292923 0.812172 l
+208.734528 0.812172 l
+208.734528 28.265369 l
+215.292923 28.265369 l
+215.292923 25.178900 l
+216.864761 27.757797 219.367996 29.026855 222.862732 29.026855 c
+225.656174 29.026855 227.949310 28.041977 229.732117 26.011431 c
+231.564957 23.980885 232.475540 21.229462 232.475540 17.665854 c
+f
+n
+Q
+
+endstream
+endobj
+
+6 0 obj
+ 9965
+endobj
+
+7 0 obj
+ << /Type /XObject
+ /Subtype /Image
+ /BitsPerComponent 8
+ /Length 8 0 R
+ /Height 148
+ /Width 538
+ /ColorSpace /DeviceGray
+ /Filter [ /FlateDecode ]
+ >>
+stream
+xC#[ֆ;ݍ;$$@pwwwwww~wW̽LPU[}ӿ>_a?CL]0qoAtUǏ>QoN|ȥ\Q;iϢАPCa6OP8u!!\@?Qw
3̡̞ǯIL 4DFFDEECQ11(66&6oGEGErN 5?%fSHlDEEBDDd$6vdq.11!!!>..N0
<~ՑqDdt\BfіkΗ` .dNMMK@^×E/zҠTt$;2)1!.&:2tpP;?G&:4&1͕+J
O!1q IvGM3|9yy~*r ?//77';;+3ӗ e6~ }gsz|yy>k-$ŧEL|-gfWVVUT։E7TWUUVVfefx)(A+(iчq6WFnQyUM]mUyqn_ PZϖddWU646wtvwYo~0/Mu:bPLf|1$<:˯klb"~).z";pxdtl|brrjjzzzffv2 LOMMMNLtu46TWfz]ShW"#P@K2"cщɉс<#."XCqI73pdlbjfn~~qiyeuu
ZX:0?7;=59>:4TWU
+6#|@sI9bxbDdZ~Jp:VV:|΄пMfv.ovAiuckW*@;888<<:<:::X萓{{;;[dianzbd('HD02X0s4xkY>|
+Jtg5O.mn.M
gѼ` 8['odrf~emc{wJtͷ!>\svvzrrrR @(!6/
M0X'B DucKͫ<`F;sla1Ca1Ԍ殁sp}ss'7B
X&gLJkKC=exEaEF&$=wпn~
+{
+j'wOnNV&k_CfhB#cS<9EUMKG'gW7@Woo|0Ĺ/ϟ??~6`gcynb8ۓ+@a˲Mz}YDbS 1Hw>ڽ뇗,t{ˇsb¢bmΌÓ˛ϰ~,?'tpngd@{}Y7%)NRE£ѠiB"6eYH5! }2f6XNW3
E_7awgV6u
O-n]b4 O?!.7(pwceA+)6ׯ0nT pgW5( &eO1!cvl}Q=6,/odjXT=5wlnm\FPi?}Ï!_<^͍TZ֠)1n ݙm==y丈пo& q2$332^eԶL.n\ DAHx^lLJ˓Bb|#bəARB!3&&B
ذ)!SrRK:Wvϯ%.`%?E rws~4eKx7o|vy}}ea`Bb_@B2a!HO`KD-5cxfuᵼy;h{Y(HID!JQ\DWficp[eaE?GdFQ2mſUY~Wd
+o>c]YhgvO.Ӣ|gh@/J(Jɷ't5q8P(?5&º뛫=:\]D*El\կ*M u_ 3eFQd+ϙ&T
+hUP G9c*haNеO~e^ Eg`ڐZi(r%My9x8h9c,S)2S r#sf4 /o 3yNT ˹BGk*VVK_giӧPDY(