Compare commits
245 Commits
2.3.0+fork
...
v2.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0dc23789f5 | ||
|
|
3f0ebd4aed | ||
|
|
1e501c707c | ||
|
|
d57f97b492 | ||
|
|
29f9214869 | ||
|
|
c6c1fe3595 | ||
|
|
2ba4c6f443 | ||
|
|
a9ff948818 | ||
|
|
7dd9cfa7f0 | ||
|
|
15a415558b | ||
|
|
b3e53bc48d | ||
|
|
d0e33c8a12 | ||
|
|
060db42e47 | ||
|
|
6bcf259de9 | ||
|
|
4d0a673209 | ||
|
|
be9be6dc35 | ||
|
|
3f47497c12 | ||
|
|
268e0af7cb | ||
|
|
c611f723b7 | ||
|
|
4c9f500122 | ||
|
|
15147dddf5 | ||
|
|
747c81c269 | ||
|
|
63ad076046 | ||
|
|
f109033ed2 | ||
|
|
9445d3383a | ||
|
|
5db5537685 | ||
|
|
1149d1c37f | ||
|
|
e4b77551f7 | ||
|
|
401fd298f5 | ||
|
|
6a1f7f7238 | ||
|
|
7c1c0894a8 | ||
|
|
cb96ae6cbc | ||
|
|
c15d972359 | ||
|
|
c010e2371c | ||
|
|
f88a244594 | ||
|
|
e2dccda205 | ||
|
|
bc6c6bc9a5 | ||
|
|
3281eabbe1 | ||
|
|
92de091228 | ||
|
|
94ab8a4e8b | ||
|
|
799987205e | ||
|
|
f5dfe70ac6 | ||
|
|
69b03e2e59 | ||
|
|
588ebe11f4 | ||
|
|
4fe0d6e893 | ||
|
|
787c20794f | ||
|
|
c462e72279 | ||
|
|
be7e6afce2 | ||
|
|
feb9920829 | ||
|
|
4d6a1a705d | ||
|
|
59d54469dc | ||
|
|
9cccd28447 | ||
|
|
da13b4602c | ||
|
|
2084a33192 | ||
|
|
542ce40c1a | ||
|
|
ddd4ea329c | ||
|
|
b5c3875ce9 | ||
|
|
ec8387d9db | ||
|
|
cf464e8eea | ||
|
|
7b66f1c398 | ||
|
|
814e11c1f4 | ||
|
|
647277444b | ||
|
|
bd4ff9c7ec | ||
|
|
7ed6195cca | ||
|
|
43397f069a | ||
|
|
8e34e31b35 | ||
|
|
7ef1bdfc2a | ||
|
|
726f91ec75 | ||
|
|
55a5268a3c | ||
|
|
4777e3d0b7 | ||
|
|
bfc970abe5 | ||
|
|
d2d35f4f39 | ||
|
|
49cef5ea0a | ||
|
|
f2fbf55c53 | ||
|
|
eacfd2fa4f | ||
|
|
51f87848f4 | ||
|
|
c3f4637ddc | ||
|
|
2361394391 | ||
|
|
38fdf8d53e | ||
|
|
0cd3cab65d | ||
|
|
142c8b55a1 | ||
|
|
3274acaba8 | ||
|
|
99b7d46ddf | ||
|
|
4f7e45211f | ||
|
|
221773bf71 | ||
|
|
305ce2ca42 | ||
|
|
7dc2480400 | ||
|
|
9634ec6616 | ||
|
|
6f29540170 | ||
|
|
18485c1cf8 | ||
|
|
7c36c32e4b | ||
|
|
1d0af51813 | ||
|
|
d50060d602 | ||
|
|
9d455b2331 | ||
|
|
5f3b537f7d | ||
|
|
1d43ed4c8a | ||
|
|
e839323575 | ||
|
|
7ee657012e | ||
|
|
847fbed956 | ||
|
|
a89d5df313 | ||
|
|
5f58789008 | ||
|
|
7b64cad668 | ||
|
|
70f60f094d | ||
|
|
a5eff14552 | ||
|
|
36fb929387 | ||
|
|
66aabfb386 | ||
|
|
f503973db1 | ||
|
|
7cec835509 | ||
|
|
e300942455 | ||
|
|
79476eff85 | ||
|
|
b4ec470691 | ||
|
|
4f72a0c74e | ||
|
|
bcbb41aa43 | ||
|
|
5cc38d845a | ||
|
|
36acc1588a | ||
|
|
349c5200ed | ||
|
|
ff71f6a092 | ||
|
|
8863284970 | ||
|
|
d5feb4e9f9 | ||
|
|
4c284226e5 | ||
|
|
fef9cf5e64 | ||
|
|
93f3c9a9eb | ||
|
|
5827c77b0c | ||
|
|
1b36866fba | ||
|
|
ebc10d5052 | ||
|
|
8953fa48c7 | ||
|
|
59e7a296ca | ||
|
|
86432228a3 | ||
|
|
4518566c37 | ||
|
|
b392a89350 | ||
|
|
225682f35d | ||
|
|
b41ff2e18f | ||
|
|
6ef76fb5bb | ||
|
|
a36679b032 | ||
|
|
dde91778a2 | ||
|
|
1cdc6f4fcf | ||
|
|
b8a5346631 | ||
|
|
5cf222379a | ||
|
|
8f4ff49b32 | ||
|
|
3a68ca3cc0 | ||
|
|
116dc68a38 | ||
|
|
df84b0ac34 | ||
|
|
5ef737766c | ||
|
|
be17ba870b | ||
|
|
747b2d5801 | ||
|
|
b84c9bf948 | ||
|
|
b1e999cc9c | ||
|
|
89951a8547 | ||
|
|
1bb0ac1110 | ||
|
|
5bb51901f7 | ||
|
|
18562cd3ee | ||
|
|
9e01270b1e | ||
|
|
0b1ff9730c | ||
|
|
845cfde58e | ||
|
|
f81f264b37 | ||
|
|
5e14270c47 | ||
|
|
6d2427b336 | ||
|
|
d89562a4c0 | ||
|
|
08389b023d | ||
|
|
797c4d6baa | ||
|
|
9458ddd490 | ||
|
|
8f3a8af35e | ||
|
|
d0f927c8d2 | ||
|
|
2de44c8d7f | ||
|
|
9a3ab2f4d2 | ||
|
|
7cecd689bb | ||
|
|
b79cf4e087 | ||
|
|
63a9ce6eb6 | ||
|
|
1f960e8631 | ||
|
|
6dc059646e | ||
|
|
5b37db0f8e | ||
|
|
2789dd9fd1 | ||
|
|
a2a72a4aee | ||
|
|
b79fc8132a | ||
|
|
91b8735a4c | ||
|
|
313d81ffe1 | ||
|
|
192b32c1c6 | ||
|
|
2ef8be7c59 | ||
|
|
94e8d5e6d9 | ||
|
|
445600653e | ||
|
|
4349c7a9e7 | ||
|
|
c2b2c39c8a | ||
|
|
9dee6eea24 | ||
|
|
53355b31ea | ||
|
|
c2f55675a8 | ||
|
|
42f3c58d02 | ||
|
|
dac2c413d6 | ||
|
|
a539eb3768 | ||
|
|
481610cd10 | ||
|
|
fa78a0f6ca | ||
|
|
bcc96ff329 | ||
|
|
bb3028fff6 | ||
|
|
3e66ce8949 | ||
|
|
5f89fb1e49 | ||
|
|
b5b3c2671a | ||
|
|
6a8c09c113 | ||
|
|
9660a2a019 | ||
|
|
f667b657f6 | ||
|
|
9db309634e | ||
|
|
8a96762bcc | ||
|
|
d58b24722e | ||
|
|
fe8904b7a5 | ||
|
|
6f3404aac9 | ||
|
|
9a81f720c2 | ||
|
|
3605ad4616 | ||
|
|
490ecfcb43 | ||
|
|
02b8ac55d5 | ||
|
|
fd71f04ca5 | ||
|
|
091953ada8 | ||
|
|
8707db891a | ||
|
|
1c67cb5edb | ||
|
|
a96567c329 | ||
|
|
691372119a | ||
|
|
25734af54e | ||
|
|
b37f9abeae | ||
|
|
f502374533 | ||
|
|
f10da18272 | ||
|
|
940f2ca73f | ||
|
|
db3192e75a | ||
|
|
1841568e7e | ||
|
|
f4ce0e67ac | ||
|
|
92b34f085e | ||
|
|
4e4eb05526 | ||
|
|
4dc707871d | ||
|
|
e9562378b4 | ||
|
|
c1b9aa7826 | ||
|
|
e979a348be | ||
|
|
89c5787ad6 | ||
|
|
f0b2329656 | ||
|
|
6cf9969220 | ||
|
|
1f37e7605e | ||
|
|
cf6af6f912 | ||
|
|
2c91adb03e | ||
|
|
911da90854 | ||
|
|
27f0235055 | ||
|
|
f3764222d8 | ||
|
|
64cb8c4a9a | ||
|
|
de78356f5a | ||
|
|
8a9d39397c | ||
|
|
2d89fd0cf0 | ||
|
|
402620dbe4 | ||
|
|
32776db395 | ||
|
|
4523ab8a67 | ||
|
|
760106bf5b | ||
|
|
f705afcafc |
@@ -4,12 +4,12 @@
|
||||
|
||||
Mastodon is built with a focus on privacy and safety. Decide whether your posts are shared with your followers, just the people you mention, or the whole world. Զգայուն կամ հրահրող թեմաներով գրառումները կարելի է թաքցնել նախազգուշացումներով։ Each community has its own guidelines and moderators to keep its members safe, and robust blocking and reporting tools help prevent abuse.
|
||||
|
||||
Ավելին՝
|
||||
Ավելին.
|
||||
|
||||
• Մուգ տարբերակ՝ կարդացեք գրառումներ մուգ, բաց կամ իսկական սև տարբերակներում
|
||||
• Հարցումներ՝ իմացեք ձեր հետևորդների կարծիքը և հաշվեք ձայները
|
||||
• Մուգ տարբերակ. կարդացեք գրառումներ մուգ, բաց կամ իսկական սև տարբերակներում
|
||||
• Հարցումներ. իմացեք ձեր հետևորդների կարծիքը և հաշվեք ձայները
|
||||
• Explore: Trending hashtags and accounts are a tap away
|
||||
• Notifications: Get notified about new follows, replies, and reblogs
|
||||
• Հիշեցումներ` ստացեք հիշեցումներ նոր հետևումների, մեկնաբանությունների և կիսումների մասին
|
||||
• Sharing: Post directly to Mastodon from any share sheet in any app
|
||||
• Cuteness: Our mascot is an adorable elephant, and you'll see them pop up from time to time
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ Mastodon เป็นเครือข่ายสังคมแบบกร
|
||||
|
||||
เข้าร่วมชุมชนและสร้างโปรไฟล์ ค้นหาและติดตามผู้คนที่น่าสนใจและอ่านโพสต์ของเขาในเส้นเวลาที่ไม่มีโฆษณาและเรียงตามลำดับเวลา แสดงความรู้สึกของตัวคุณเองด้วยอีโมจิที่กำหนดเอง รูปภาพ GIF วิดีโอ และเสียงในโพสต์ 500 ตัวอักษร ตอบกลับและดันโพสต์จากคนอื่น ๆ เพื่อแชร์สิ่งดี ๆ และค้นหาบัญชีใหม่ ๆ ที่จะติดตามและแฮชแท็กที่เป็นที่นิยมเพื่อขยายเครือข่ายของคุณ
|
||||
|
||||
Mastodon สร้างขึ้นโดยเน้นความเป็นส่วนตัวและความปลอดภัยเป็นสำคัญ คุณสามารถตัดสินใจได้ว่าโพสต์ของคุณจะถูกแชร์กับผู้ติดตามของคุณ คนที่คุณกล่าวถึง หรือคนทั้งโลกก็ได้ ฟังก์ชั่นคำเตือนเนื้อหาช่วยให้คุณซ่อนโพสต์ที่มีเนื้อหาที่ละเอียดอ่อนจนกว่าคุณจะพร้อมเห็นเนื้อหานั้น แต่ละชุมชนจะมีหลักเกณฑ์และผู้ควบคุมเป็นของตนเองเพื่อรักษาความปลอดภัยของสมาชิก รวมถึงเครื่องมือการปิดกั้นและรายงานที่มีประสิทธิภาพเพื่อช่วยป้องกันการกระทำผิด
|
||||
Mastodon สร้างขึ้นโดยเน้นความเป็นส่วนตัวและความปลอดภัยเป็นสำคัญ คุณสามารถตัดสินใจได้ว่าโพสต์ของคุณจะถูกแชร์กับผู้ติดตามของคุณ คนที่คุณกล่าวถึง หรือคนทั้งโลกก็ได้ ฟังก์ชั่นคำเตือนเนื้อหาช่วยให้คุณซ่อนโพสต์ที่มีเนื้อหาที่ละเอียดอ่อนจนกว่าคุณจะพร้อมเห็นเนื้อหานั้น แต่ละชุมชนจะมีหลักเกณฑ์และผู้กลั่นกรองเป็นของตนเองเพื่อรักษาความปลอดภัยของสมาชิก รวมถึงเครื่องมือการปิดกั้นและรายงานที่มีประสิทธิภาพเพื่อช่วยป้องกันการกระทำผิด
|
||||
|
||||
คุณสมบัติอื่น ๆ:
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
Mastodon 是網際網路上最大的去中心化社群網路。 它是一個由能無縫互動的獨立社群中,數百萬使用者組成的網路,而非單一網站。 無論您對什麼事情感興趣,您都能在 Mastodon 上遇到充滿熱情的人們討論該話題。
|
||||
Mastodon 是網際網路上最大的去中心化社群網路。 它是一個由能無縫互動的獨立社群中,數百萬使用者組成的網路,而非單一網站。 無論您對什麼事情感興趣,您都能於 Mastodon 上遇到充滿熱情的人們討論該話題。
|
||||
|
||||
加入社群並建立您的個人檔案。 尋找並跟隨迷人的朋友們,並在無廣告、按時間順序排列的時間軸上閱讀他們的嘟文。 在 500 個字元的嘟文中使用自訂表情符號、GIF、視訊與音訊來表達您自己。 回覆任何人的話題與轉發貼文以分享精彩內容。 尋找要跟隨的新帳號與熱門主題標籤來拓展您的網路。
|
||||
加入社群並建立您的個人檔案。 尋找並跟隨迷人的朋友們,並於無廣告、按時間順序排列的時間軸上閱讀他們的嘟文。 於 500 個字元的嘟文中使用自訂表情符號、GIF、視訊與音訊表達您自己。 回覆任何人的話題與轉發貼文以分享精彩內容。 尋找要跟隨的新帳號與熱門主題標籤以拓展您的網路。
|
||||
|
||||
Mastodon 以隱私與安全為要。 決定您的嘟文要與您的跟隨者分享、只與您提及的人們分享,又或是與全世界分享。 內容警告可讓您隱藏包含敏感或可能觸發強烈情緒反應的嘟文,直到您準備好與它們進行互動。 每個社群都有它們自己的指導方針與管理原來確保其成員安全,強大的封鎖與回報工具有助於防止濫用。
|
||||
Mastodon 以隱私與安全為要。 決定您的嘟文要與您的跟隨者分享、只與您提及的人們分享,又或是與全世界分享。 內容警告可讓您隱藏包含敏感或可能觸發強烈情緒反應的嘟文,直到您準備好與它們進行互動。 每個社群都有它們自己的指導方針與管理員以確保其成員安全,強大的封鎖與回報工具有助於防止濫用。
|
||||
|
||||
更多功能:
|
||||
|
||||
@@ -10,7 +10,7 @@ Mastodon 以隱私與安全為要。 決定您的嘟文要與您的跟隨者分
|
||||
• 投票:詢問跟隨者們的意見並計票
|
||||
• 探索:僅需輕點一下,即可看到熱門主題標籤與帳號
|
||||
• 通知:取得關於新跟隨者們、回覆與轉發的通知
|
||||
• 分享:從任何應用程式中的分享表中直接發表嘟文到 Mastodon 中
|
||||
• 分享:自任何應用程式中的分享表中直接發表嘟文到 Mastodon 中
|
||||
• 可愛:我們的吉祥物是一隻可愛的大象,您會不時看到牠出現
|
||||
|
||||
Mastodon 是一家註冊的非營利組織,您的捐款會直接支援開發工作。 沒有廣告、沒有貨幣化、沒有風險投資,我們計畫維持這種狀態。 沒有廣告、沒有貨幣化、沒有風險投資,我們計畫維持這種狀態。
|
||||
|
||||
@@ -9,8 +9,8 @@ android {
|
||||
applicationId "org.joinmastodon.android"
|
||||
minSdk 23
|
||||
targetSdk 33
|
||||
versionCode 84
|
||||
versionName "2.3.0"
|
||||
versionCode 86
|
||||
versionName "2.4.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "da-rDK", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fa-rIR", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "ig-rNG", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "ka-rGE", "kab", "ko-rKR", "lt-rLT", "my-rMM", "nl-rNL", "no-rNO", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "ur-rIN", "vi-rVN", "zh-rCN", "zh-rTW"
|
||||
}
|
||||
@@ -64,6 +64,9 @@ android {
|
||||
checkReleaseBuilds false
|
||||
abortOnError false
|
||||
}
|
||||
buildFeatures{
|
||||
aidl true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -81,6 +84,8 @@ dependencies {
|
||||
implementation 'org.jsoup:jsoup:1.14.3'
|
||||
implementation 'com.squareup:otto:1.3.8'
|
||||
implementation 'de.psdev:async-otto:1.0.3'
|
||||
implementation 'com.google.zxing:core:3.5.3'
|
||||
implementation 'org.microg:safe-parcel:1.5.0'
|
||||
implementation 'org.parceler:parceler-api:1.1.12'
|
||||
annotationProcessor 'org.parceler:parceler:1.1.12'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
<intent>
|
||||
<action android:name="android.intent.action.TRANSLATE" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<data android:scheme="http"/>
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<application
|
||||
@@ -31,6 +35,10 @@
|
||||
android:theme="@style/Theme.Mastodon.AutoLightDark"
|
||||
android:largeHeap="true">
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.mlkit.vision.DEPENDENCIES"
|
||||
android:value="barcode_ui"/>
|
||||
|
||||
<activity android:name=".MainActivity" android:exported="true" android:configChanges="orientation|screenSize" android:windowSoftInputMode="adjustResize" android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
@@ -84,6 +92,14 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<provider
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:name=".TweakedFileProvider"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false">
|
||||
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/fileprovider_paths"/>
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.google.android.gms.common.api;
|
||||
|
||||
parcelable Status;
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.google.android.gms.common.api.internal;
|
||||
|
||||
import com.google.android.gms.common.api.Status;
|
||||
|
||||
interface IStatusCallback {
|
||||
void onResult(in Status status);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.common.internal;
|
||||
parcelable ConnectionInfo;
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.google.android.gms.common.internal;
|
||||
|
||||
parcelable GetServiceRequest;
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.common.internal;
|
||||
|
||||
import android.os.Bundle;
|
||||
import com.google.android.gms.common.internal.ConnectionInfo;
|
||||
|
||||
interface IGmsCallbacks {
|
||||
void onPostInitComplete(int statusCode, IBinder binder, in Bundle params);
|
||||
void onAccountValidationComplete(int statusCode, in Bundle params);
|
||||
void onPostInitCompleteWithConnectionInfo(int statusCode, IBinder binder, in ConnectionInfo info);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.google.android.gms.common.internal;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.google.android.gms.common.internal.IGmsCallbacks;
|
||||
import com.google.android.gms.common.internal.GetServiceRequest;
|
||||
|
||||
interface IGmsServiceBroker {
|
||||
void getService(IGmsCallbacks callback, in GetServiceRequest request) = 45;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.google.android.gms.common.moduleinstall;
|
||||
|
||||
parcelable ModuleAvailabilityResponse;
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.google.android.gms.common.moduleinstall;
|
||||
|
||||
parcelable ModuleInstallIntentResponse;
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.google.android.gms.common.moduleinstall;
|
||||
|
||||
parcelable ModuleInstallResponse;
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.google.android.gms.common.moduleinstall;
|
||||
|
||||
parcelable ModuleInstallStatusUpdate;
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.google.android.gms.common.moduleinstall.internal;
|
||||
|
||||
parcelable ApiFeatureRequest;
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.google.android.gms.common.moduleinstall.internal;
|
||||
|
||||
import com.google.android.gms.common.api.Status;
|
||||
import com.google.android.gms.common.moduleinstall.ModuleAvailabilityResponse;
|
||||
import com.google.android.gms.common.moduleinstall.ModuleInstallIntentResponse;
|
||||
import com.google.android.gms.common.moduleinstall.ModuleInstallResponse;
|
||||
|
||||
interface IModuleInstallCallbacks {
|
||||
void onModuleAvailabilityResponse(in Status status, in ModuleAvailabilityResponse response) = 0;
|
||||
void onModuleInstallResponse(in Status status, in ModuleInstallResponse response) = 1;
|
||||
void onModuleInstallIntentResponse(in Status status, in ModuleInstallIntentResponse response) = 2;
|
||||
void onStatus(in Status status) = 3;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.google.android.gms.common.moduleinstall.internal;
|
||||
|
||||
import com.google.android.gms.common.api.internal.IStatusCallback;
|
||||
import com.google.android.gms.common.moduleinstall.internal.ApiFeatureRequest;
|
||||
import com.google.android.gms.common.moduleinstall.internal.IModuleInstallCallbacks;
|
||||
import com.google.android.gms.common.moduleinstall.internal.IModuleInstallStatusListener;
|
||||
|
||||
interface IModuleInstallService {
|
||||
void areModulesAvailable(IModuleInstallCallbacks callbacks, in ApiFeatureRequest request) = 0;
|
||||
void installModules(IModuleInstallCallbacks callbacks, in ApiFeatureRequest request, IModuleInstallStatusListener listener) = 1;
|
||||
void getInstallModulesIntent(IModuleInstallCallbacks callbacks, in ApiFeatureRequest request) = 2;
|
||||
void releaseModules(IStatusCallback callback, in ApiFeatureRequest request) = 3;
|
||||
void unregisterListener(IStatusCallback callback, IModuleInstallStatusListener listener) = 5;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.google.android.gms.common.moduleinstall.internal;
|
||||
|
||||
import com.google.android.gms.common.moduleinstall.ModuleInstallStatusUpdate;
|
||||
|
||||
interface IModuleInstallStatusListener {
|
||||
void onModuleInstallStatusUpdate(in ModuleInstallStatusUpdate statusUpdate) = 0;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.google.android.gms.common;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
import org.microg.safeparcel.SafeParceled;
|
||||
|
||||
public class Feature extends AutoSafeParcelable{
|
||||
@SafeParceled(1)
|
||||
public String name;
|
||||
@SafeParceled(2)
|
||||
public int oldVersion;
|
||||
@SafeParceled(3)
|
||||
public long version=-1;
|
||||
|
||||
public static final Creator<Feature> CREATOR=new AutoCreator<>(Feature.class);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.google.android.gms.common.api;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
import org.microg.safeparcel.SafeParceled;
|
||||
|
||||
public class Scope extends AutoSafeParcelable{
|
||||
@SafeParceled(1)
|
||||
public int versionCode=1;
|
||||
@SafeParceled(2)
|
||||
public String scopeUri;
|
||||
|
||||
public static final Creator<Scope> CREATOR=new AutoCreator<>(Scope.class);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.google.android.gms.common.api;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
|
||||
import org.joinmastodon.android.googleservices.ConnectionResult;
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
import org.microg.safeparcel.SafeParceled;
|
||||
|
||||
public class Status extends AutoSafeParcelable{
|
||||
@SafeParceled(1000)
|
||||
public int versionCode;
|
||||
@SafeParceled(1)
|
||||
public int statusCode;
|
||||
@SafeParceled(2)
|
||||
public String statusMessage;
|
||||
@SafeParceled(3)
|
||||
public PendingIntent pendingIntent;
|
||||
@SafeParceled(4)
|
||||
public ConnectionResult connectionResult;
|
||||
|
||||
public static final Creator<Status> CREATOR=new AutoCreator<>(Status.class);
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return "Status{"+
|
||||
"versionCode="+versionCode+
|
||||
", statusCode="+statusCode+
|
||||
", statusMessage='"+statusMessage+'\''+
|
||||
", pendingIntent="+pendingIntent+
|
||||
", connectionResult="+connectionResult+
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.google.android.gms.common.internal;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.google.android.gms.common.Feature;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
import org.microg.safeparcel.SafeParceled;
|
||||
|
||||
public class ConnectionInfo extends AutoSafeParcelable{
|
||||
@SafeParceled(1)
|
||||
public Bundle params;
|
||||
@SafeParceled(2)
|
||||
public Feature[] features;
|
||||
@SafeParceled(3)
|
||||
public int unknown3;
|
||||
|
||||
public static final Creator<ConnectionInfo> CREATOR=new AutoCreator<>(ConnectionInfo.class);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.google.android.gms.common.internal;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.accounts.Account;
|
||||
|
||||
import com.google.android.gms.common.Feature;
|
||||
import com.google.android.gms.common.api.Scope;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
import org.microg.safeparcel.SafeParceled;
|
||||
|
||||
public class GetServiceRequest extends AutoSafeParcelable{
|
||||
@SafeParceled(1)
|
||||
int versionCode=6;
|
||||
@SafeParceled(2)
|
||||
public int serviceId;
|
||||
@SafeParceled(3)
|
||||
public int gmsVersion;
|
||||
@SafeParceled(4)
|
||||
public String packageName;
|
||||
@SafeParceled(5)
|
||||
public IBinder accountAccessor;
|
||||
@SafeParceled(6)
|
||||
public Scope[] scopes;
|
||||
@SafeParceled(7)
|
||||
public Bundle extras;
|
||||
@SafeParceled(8)
|
||||
public Account account;
|
||||
@SafeParceled(9)
|
||||
@Deprecated
|
||||
long field9;
|
||||
@SafeParceled(10)
|
||||
public Feature[] defaultFeatures;
|
||||
@SafeParceled(11)
|
||||
public Feature[] apiFeatures;
|
||||
@SafeParceled(12)
|
||||
boolean supportsConnectionInfo;
|
||||
@SafeParceled(13)
|
||||
int field13;
|
||||
@SafeParceled(14)
|
||||
boolean field14;
|
||||
@SafeParceled(15)
|
||||
String attributionTag;
|
||||
|
||||
public static final Creator<GetServiceRequest> CREATOR=new AutoCreator<>(GetServiceRequest.class);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.google.android.gms.common.moduleinstall;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
import org.microg.safeparcel.SafeParceled;
|
||||
|
||||
public class ModuleAvailabilityResponse extends AutoSafeParcelable{
|
||||
@SafeParceled(1)
|
||||
public boolean modulesAvailable;
|
||||
@SafeParceled(2)
|
||||
public int availabilityStatus;
|
||||
|
||||
public static final Creator<ModuleAvailabilityResponse> CREATOR=new AutoCreator<>(ModuleAvailabilityResponse.class);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.google.android.gms.common.moduleinstall;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
import org.microg.safeparcel.SafeParceled;
|
||||
|
||||
public class ModuleInstallIntentResponse extends AutoSafeParcelable{
|
||||
@SafeParceled(1)
|
||||
public PendingIntent pendingIntent;
|
||||
|
||||
public static final Creator<ModuleInstallIntentResponse> CREATOR=new AutoCreator<>(ModuleInstallIntentResponse.class);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.google.android.gms.common.moduleinstall;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
import org.microg.safeparcel.SafeParceled;
|
||||
|
||||
public class ModuleInstallResponse extends AutoSafeParcelable{
|
||||
@SafeParceled(1)
|
||||
public int sessionID;
|
||||
@SafeParceled(2)
|
||||
public boolean shouldUnregisterListener;
|
||||
|
||||
public static final Creator<ModuleInstallResponse> CREATOR=new AutoCreator<>(ModuleInstallResponse.class);
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return "ModuleInstallResponse{"+
|
||||
"sessionID="+sessionID+
|
||||
", shouldUnregisterListener="+shouldUnregisterListener+
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.google.android.gms.common.moduleinstall;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
import org.microg.safeparcel.SafeParceled;
|
||||
|
||||
public class ModuleInstallStatusUpdate extends AutoSafeParcelable{
|
||||
public static final int STATE_UNKNOWN = 0;
|
||||
/**
|
||||
* The request is pending and will be processed soon.
|
||||
*/
|
||||
public static final int STATE_PENDING = 1;
|
||||
/**
|
||||
* The optional module download is in progress.
|
||||
*/
|
||||
public static final int STATE_DOWNLOADING = 2;
|
||||
/**
|
||||
* The optional module download has been canceled.
|
||||
*/
|
||||
public static final int STATE_CANCELED = 3;
|
||||
/**
|
||||
* Installation is completed; the optional modules are available to the client app.
|
||||
*/
|
||||
public static final int STATE_COMPLETED = 4;
|
||||
/**
|
||||
* The optional module download or installation has failed.
|
||||
*/
|
||||
public static final int STATE_FAILED = 5;
|
||||
/**
|
||||
* The optional modules have been downloaded and the installation is in progress.
|
||||
*/
|
||||
public static final int STATE_INSTALLING = 6;
|
||||
/**
|
||||
* The optional module download has been paused.
|
||||
* <p>
|
||||
* This usually happens when connectivity requirements can't be met during download. Once the connectivity requirements
|
||||
* are met, the download will be resumed automatically.
|
||||
*/
|
||||
public static final int STATE_DOWNLOAD_PAUSED = 7;
|
||||
|
||||
@SafeParceled(1)
|
||||
public int sessionID;
|
||||
@SafeParceled(2)
|
||||
public int installState;
|
||||
@SafeParceled(3)
|
||||
public Long bytesDownloaded;
|
||||
@SafeParceled(4)
|
||||
public Long totalBytesToDownload;
|
||||
@SafeParceled(5)
|
||||
public int errorCode;
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return "ModuleInstallStatusUpdate{"+
|
||||
"sessionID="+sessionID+
|
||||
", installState="+installState+
|
||||
", bytesDownloaded="+bytesDownloaded+
|
||||
", totalBytesToDownload="+totalBytesToDownload+
|
||||
", errorCode="+errorCode+
|
||||
'}';
|
||||
}
|
||||
|
||||
public static final Creator<ModuleInstallStatusUpdate> CREATOR=new AutoCreator<>(ModuleInstallStatusUpdate.class);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.google.android.gms.common.moduleinstall.internal;
|
||||
|
||||
import com.google.android.gms.common.Feature;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
import org.microg.safeparcel.SafeParceled;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ApiFeatureRequest extends AutoSafeParcelable{
|
||||
@SafeParceled(value=1, subClass=Feature.class)
|
||||
public List<Feature> features;
|
||||
@SafeParceled(2)
|
||||
public boolean urgent;
|
||||
@SafeParceled(3)
|
||||
public String sessionId;
|
||||
@SafeParceled(4)
|
||||
public String callingPackage;
|
||||
|
||||
public static final Creator<ApiFeatureRequest> CREATOR=new AutoCreator<>(ApiFeatureRequest.class);
|
||||
}
|
||||
@@ -0,0 +1,841 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.joinmastodon.android;
|
||||
|
||||
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
|
||||
import static org.xmlpull.v1.XmlPullParser.START_TAG;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ProviderInfo;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.text.TextUtils;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* FileProvider is a special subclass of {@link ContentProvider} that facilitates secure sharing
|
||||
* of files associated with an app by creating a <code>content://</code> {@link Uri} for a file
|
||||
* instead of a <code>file:///</code> {@link Uri}.
|
||||
* <p>
|
||||
* A content URI allows you to grant read and write access using
|
||||
* temporary access permissions. When you create an {@link Intent} containing
|
||||
* a content URI, in order to send the content URI
|
||||
* to a client app, you can also call {@link Intent#setFlags(int) Intent.setFlags()} to add
|
||||
* permissions. These permissions are available to the client app for as long as the stack for
|
||||
* a receiving {@link android.app.Activity} is active. For an {@link Intent} going to a
|
||||
* {@link android.app.Service}, the permissions are available as long as the
|
||||
* {@link android.app.Service} is running.
|
||||
* <p>
|
||||
* In comparison, to control access to a <code>file:///</code> {@link Uri} you have to modify the
|
||||
* file system permissions of the underlying file. The permissions you provide become available to
|
||||
* <em>any</em> app, and remain in effect until you change them. This level of access is
|
||||
* fundamentally insecure.
|
||||
* <p>
|
||||
* The increased level of file access security offered by a content URI
|
||||
* makes FileProvider a key part of Android's security infrastructure.
|
||||
* <p>
|
||||
* This overview of FileProvider includes the following topics:
|
||||
* </p>
|
||||
* <ol>
|
||||
* <li><a href="#ProviderDefinition">Defining a FileProvider</a></li>
|
||||
* <li><a href="#SpecifyFiles">Specifying Available Files</a></li>
|
||||
* <li><a href="#GetUri">Retrieving the Content URI for a File</li>
|
||||
* <li><a href="#Permissions">Granting Temporary Permissions to a URI</a></li>
|
||||
* <li><a href="#ServeUri">Serving a Content URI to Another App</a></li>
|
||||
* </ol>
|
||||
* <h3 id="ProviderDefinition">Defining a FileProvider</h3>
|
||||
* <p>
|
||||
* Since the default functionality of FileProvider includes content URI generation for files, you
|
||||
* don't need to define a subclass in code. Instead, you can include a FileProvider in your app
|
||||
* by specifying it entirely in XML. To specify the FileProvider component itself, add a
|
||||
* <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"><provider></a></code>
|
||||
* element to your app manifest. Set the <code>android:name</code> attribute to
|
||||
* <code>androidx.core.content.FileProvider</code>. Set the <code>android:authorities</code>
|
||||
* attribute to a URI authority based on a domain you control; for example, if you control the
|
||||
* domain <code>mydomain.com</code> you should use the authority
|
||||
* <code>com.mydomain.fileprovider</code>. Set the <code>android:exported</code> attribute to
|
||||
* <code>false</code>; the FileProvider does not need to be public. Set the
|
||||
* <a href="{@docRoot}guide/topics/manifest/provider-element.html#gprmsn"
|
||||
* >android:grantUriPermissions</a> attribute to <code>true</code>, to allow you
|
||||
* to grant temporary access to files. For example:
|
||||
* <pre class="prettyprint">
|
||||
*<manifest>
|
||||
* ...
|
||||
* <application>
|
||||
* ...
|
||||
* <provider
|
||||
* android:name="androidx.core.content.FileProvider"
|
||||
* android:authorities="com.mydomain.fileprovider"
|
||||
* android:exported="false"
|
||||
* android:grantUriPermissions="true">
|
||||
* ...
|
||||
* </provider>
|
||||
* ...
|
||||
* </application>
|
||||
*</manifest></pre>
|
||||
* <p>
|
||||
* If you want to override any of the default behavior of FileProvider methods, extend
|
||||
* the FileProvider class and use the fully-qualified class name in the <code>android:name</code>
|
||||
* attribute of the <code><provider></code> element.
|
||||
* <h3 id="SpecifyFiles">Specifying Available Files</h3>
|
||||
* A FileProvider can only generate a content URI for files in directories that you specify
|
||||
* beforehand. To specify a directory, specify the its storage area and path in XML, using child
|
||||
* elements of the <code><paths></code> element.
|
||||
* For example, the following <code>paths</code> element tells FileProvider that you intend to
|
||||
* request content URIs for the <code>images/</code> subdirectory of your private file area.
|
||||
* <pre class="prettyprint">
|
||||
*<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
* <files-path name="my_images" path="images/"/>
|
||||
* ...
|
||||
*</paths>
|
||||
*</pre>
|
||||
* <p>
|
||||
* The <code><paths></code> element must contain one or more of the following child elements:
|
||||
* </p>
|
||||
* <dl>
|
||||
* <dt>
|
||||
* <pre class="prettyprint">
|
||||
*<files-path name="<i>name</i>" path="<i>path</i>" />
|
||||
*</pre>
|
||||
* </dt>
|
||||
* <dd>
|
||||
* Represents files in the <code>files/</code> subdirectory of your app's internal storage
|
||||
* area. This subdirectory is the same as the value returned by {@link Context#getFilesDir()
|
||||
* Context.getFilesDir()}.
|
||||
* </dd>
|
||||
* <dt>
|
||||
* <pre>
|
||||
*<cache-path name="<i>name</i>" path="<i>path</i>" />
|
||||
*</pre>
|
||||
* <dt>
|
||||
* <dd>
|
||||
* Represents files in the cache subdirectory of your app's internal storage area. The root path
|
||||
* of this subdirectory is the same as the value returned by {@link Context#getCacheDir()
|
||||
* getCacheDir()}.
|
||||
* </dd>
|
||||
* <dt>
|
||||
* <pre class="prettyprint">
|
||||
*<external-path name="<i>name</i>" path="<i>path</i>" />
|
||||
*</pre>
|
||||
* </dt>
|
||||
* <dd>
|
||||
* Represents files in the root of the external storage area. The root path of this subdirectory
|
||||
* is the same as the value returned by
|
||||
* {@link Environment#getExternalStorageDirectory() Environment.getExternalStorageDirectory()}.
|
||||
* </dd>
|
||||
* <dt>
|
||||
* <pre class="prettyprint">
|
||||
*<external-files-path name="<i>name</i>" path="<i>path</i>" />
|
||||
*</pre>
|
||||
* </dt>
|
||||
* <dd>
|
||||
* Represents files in the root of your app's external storage area. The root path of this
|
||||
* subdirectory is the same as the value returned by
|
||||
* {@code Context#getExternalFilesDir(String) Context.getExternalFilesDir(null)}.
|
||||
* </dd>
|
||||
* <dt>
|
||||
* <pre class="prettyprint">
|
||||
*<external-cache-path name="<i>name</i>" path="<i>path</i>" />
|
||||
*</pre>
|
||||
* </dt>
|
||||
* <dd>
|
||||
* Represents files in the root of your app's external cache area. The root path of this
|
||||
* subdirectory is the same as the value returned by
|
||||
* {@link Context#getExternalCacheDir() Context.getExternalCacheDir()}.
|
||||
* </dd>
|
||||
* <dt>
|
||||
* <pre class="prettyprint">
|
||||
*<external-media-path name="<i>name</i>" path="<i>path</i>" />
|
||||
*</pre>
|
||||
* </dt>
|
||||
* <dd>
|
||||
* Represents files in the root of your app's external media area. The root path of this
|
||||
* subdirectory is the same as the value returned by the first result of
|
||||
* {@link Context#getExternalMediaDirs() Context.getExternalMediaDirs()}.
|
||||
* <p><strong>Note:</strong> this directory is only available on API 21+ devices.</p>
|
||||
* </dd>
|
||||
* </dl>
|
||||
* <p>
|
||||
* These child elements all use the same attributes:
|
||||
* </p>
|
||||
* <dl>
|
||||
* <dt>
|
||||
* <code>name="<i>name</i>"</code>
|
||||
* </dt>
|
||||
* <dd>
|
||||
* A URI path segment. To enforce security, this value hides the name of the subdirectory
|
||||
* you're sharing. The subdirectory name for this value is contained in the
|
||||
* <code>path</code> attribute.
|
||||
* </dd>
|
||||
* <dt>
|
||||
* <code>path="<i>path</i>"</code>
|
||||
* </dt>
|
||||
* <dd>
|
||||
* The subdirectory you're sharing. While the <code>name</code> attribute is a URI path
|
||||
* segment, the <code>path</code> value is an actual subdirectory name. Notice that the
|
||||
* value refers to a <b>subdirectory</b>, not an individual file or files. You can't
|
||||
* share a single file by its file name, nor can you specify a subset of files using
|
||||
* wildcards.
|
||||
* </dd>
|
||||
* </dl>
|
||||
* <p>
|
||||
* You must specify a child element of <code><paths></code> for each directory that contains
|
||||
* files for which you want content URIs. For example, these XML elements specify two directories:
|
||||
* <pre class="prettyprint">
|
||||
*<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
* <files-path name="my_images" path="images/"/>
|
||||
* <files-path name="my_docs" path="docs/"/>
|
||||
*</paths>
|
||||
*</pre>
|
||||
* <p>
|
||||
* Put the <code><paths></code> element and its children in an XML file in your project.
|
||||
* For example, you can add them to a new file called <code>res/xml/file_paths.xml</code>.
|
||||
* To link this file to the FileProvider, add a
|
||||
* <a href="{@docRoot}guide/topics/manifest/meta-data-element.html"><meta-data></a> element
|
||||
* as a child of the <code><provider></code> element that defines the FileProvider. Set the
|
||||
* <code><meta-data></code> element's "android:name" attribute to
|
||||
* <code>android.support.FILE_PROVIDER_PATHS</code>. Set the element's "android:resource" attribute
|
||||
* to <code>@xml/file_paths</code> (notice that you don't specify the <code>.xml</code>
|
||||
* extension). For example:
|
||||
* <pre class="prettyprint">
|
||||
*<provider
|
||||
* android:name="androidx.core.content.FileProvider"
|
||||
* android:authorities="com.mydomain.fileprovider"
|
||||
* android:exported="false"
|
||||
* android:grantUriPermissions="true">
|
||||
* <meta-data
|
||||
* android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
* android:resource="@xml/file_paths" />
|
||||
*</provider>
|
||||
*</pre>
|
||||
* <h3 id="GetUri">Generating the Content URI for a File</h3>
|
||||
* <p>
|
||||
* To share a file with another app using a content URI, your app has to generate the content URI.
|
||||
* To generate the content URI, create a new {@link File} for the file, then pass the {@link File}
|
||||
* to {@link #getUriForFile(Context, String, File) getUriForFile()}. You can send the content URI
|
||||
* returned by {@link #getUriForFile(Context, String, File) getUriForFile()} to another app in an
|
||||
* {@link Intent}. The client app that receives the content URI can open the file
|
||||
* and access its contents by calling
|
||||
* {@link android.content.ContentResolver#openFileDescriptor(Uri, String)
|
||||
* ContentResolver.openFileDescriptor} to get a {@link ParcelFileDescriptor}.
|
||||
* <p>
|
||||
* For example, suppose your app is offering files to other apps with a FileProvider that has the
|
||||
* authority <code>com.mydomain.fileprovider</code>. To get a content URI for the file
|
||||
* <code>default_image.jpg</code> in the <code>images/</code> subdirectory of your internal storage
|
||||
* add the following code:
|
||||
* <pre class="prettyprint">
|
||||
*File imagePath = new File(Context.getFilesDir(), "images");
|
||||
*File newFile = new File(imagePath, "default_image.jpg");
|
||||
*Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);
|
||||
*</pre>
|
||||
* As a result of the previous snippet,
|
||||
* {@link #getUriForFile(Context, String, File) getUriForFile()} returns the content URI
|
||||
* <code>content://com.mydomain.fileprovider/my_images/default_image.jpg</code>.
|
||||
* <h3 id="Permissions">Granting Temporary Permissions to a URI</h3>
|
||||
* To grant an access permission to a content URI returned from
|
||||
* {@link #getUriForFile(Context, String, File) getUriForFile()}, do one of the following:
|
||||
* <ul>
|
||||
* <li>
|
||||
* Call the method
|
||||
* {@link Context#grantUriPermission(String, Uri, int)
|
||||
* Context.grantUriPermission(package, Uri, mode_flags)} for the <code>content://</code>
|
||||
* {@link Uri}, using the desired mode flags. This grants temporary access permission for the
|
||||
* content URI to the specified package, according to the value of the
|
||||
* the <code>mode_flags</code> parameter, which you can set to
|
||||
* {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}, {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}
|
||||
* or both. The permission remains in effect until you revoke it by calling
|
||||
* {@link Context#revokeUriPermission(Uri, int) revokeUriPermission()} or until the device
|
||||
* reboots.
|
||||
* </li>
|
||||
* <li>
|
||||
* Put the content URI in an {@link Intent} by calling {@link Intent#setData(Uri) setData()}.
|
||||
* </li>
|
||||
* <li>
|
||||
* Next, call the method {@link Intent#setFlags(int) Intent.setFlags()} with either
|
||||
* {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} or
|
||||
* {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION} or both.
|
||||
* </li>
|
||||
* <li>
|
||||
* Finally, send the {@link Intent} to
|
||||
* another app. Most often, you do this by calling
|
||||
* {@link android.app.Activity#setResult(int, Intent) setResult()}.
|
||||
* <p>
|
||||
* Permissions granted in an {@link Intent} remain in effect while the stack of the receiving
|
||||
* {@link android.app.Activity} is active. When the stack finishes, the permissions are
|
||||
* automatically removed. Permissions granted to one {@link android.app.Activity} in a client
|
||||
* app are automatically extended to other components of that app.
|
||||
* </p>
|
||||
* </li>
|
||||
* </ul>
|
||||
* <h3 id="ServeUri">Serving a Content URI to Another App</h3>
|
||||
* <p>
|
||||
* There are a variety of ways to serve the content URI for a file to a client app. One common way
|
||||
* is for the client app to start your app by calling
|
||||
* {@link android.app.Activity#startActivityForResult(Intent, int, Bundle) startActivityResult()},
|
||||
* which sends an {@link Intent} to your app to start an {@link android.app.Activity} in your app.
|
||||
* In response, your app can immediately return a content URI to the client app or present a user
|
||||
* interface that allows the user to pick a file. In the latter case, once the user picks the file
|
||||
* your app can return its content URI. In both cases, your app returns the content URI in an
|
||||
* {@link Intent} sent via {@link android.app.Activity#setResult(int, Intent) setResult()}.
|
||||
* </p>
|
||||
* <p>
|
||||
* You can also put the content URI in a {@link android.content.ClipData} object and then add the
|
||||
* object to an {@link Intent} you send to a client app. To do this, call
|
||||
* {@link Intent#setClipData(ClipData) Intent.setClipData()}. When you use this approach, you can
|
||||
* add multiple {@link android.content.ClipData} objects to the {@link Intent}, each with its own
|
||||
* content URI. When you call {@link Intent#setFlags(int) Intent.setFlags()} on the {@link Intent}
|
||||
* to set temporary access permissions, the same permissions are applied to all of the content
|
||||
* URIs.
|
||||
* </p>
|
||||
* <p class="note">
|
||||
* <strong>Note:</strong> The {@link Intent#setClipData(ClipData) Intent.setClipData()} method is
|
||||
* only available in platform version 16 (Android 4.1) and later. If you want to maintain
|
||||
* compatibility with previous versions, you should send one content URI at a time in the
|
||||
* {@link Intent}. Set the action to {@link Intent#ACTION_SEND} and put the URI in data by calling
|
||||
* {@link Intent#setData setData()}.
|
||||
* </p>
|
||||
* <h3 id="">More Information</h3>
|
||||
* <p>
|
||||
* To learn more about FileProvider, see the Android training class
|
||||
* <a href="{@docRoot}training/secure-file-sharing/index.html">Sharing Files Securely with URIs</a>.
|
||||
* </p>
|
||||
*/
|
||||
public class FileProvider extends ContentProvider {
|
||||
private static final String[] COLUMNS = {
|
||||
OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE };
|
||||
|
||||
private static final String
|
||||
META_DATA_FILE_PROVIDER_PATHS = "android.support.FILE_PROVIDER_PATHS";
|
||||
|
||||
private static final String TAG_ROOT_PATH = "root-path";
|
||||
private static final String TAG_FILES_PATH = "files-path";
|
||||
private static final String TAG_CACHE_PATH = "cache-path";
|
||||
private static final String TAG_EXTERNAL = "external-path";
|
||||
private static final String TAG_EXTERNAL_FILES = "external-files-path";
|
||||
private static final String TAG_EXTERNAL_CACHE = "external-cache-path";
|
||||
private static final String TAG_EXTERNAL_MEDIA = "external-media-path";
|
||||
|
||||
private static final String ATTR_NAME = "name";
|
||||
private static final String ATTR_PATH = "path";
|
||||
|
||||
private static final File DEVICE_ROOT = new File("/");
|
||||
|
||||
@GuardedBy("sCache")
|
||||
private static HashMap<String, PathStrategy> sCache = new HashMap<String, PathStrategy>();
|
||||
|
||||
private PathStrategy mStrategy;
|
||||
|
||||
/**
|
||||
* The default FileProvider implementation does not need to be initialized. If you want to
|
||||
* override this method, you must provide your own subclass of FileProvider.
|
||||
*/
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* After the FileProvider is instantiated, this method is called to provide the system with
|
||||
* information about the provider.
|
||||
*
|
||||
* @param context A {@link Context} for the current component.
|
||||
* @param info A {@link ProviderInfo} for the new provider.
|
||||
*/
|
||||
@Override
|
||||
public void attachInfo(@NonNull Context context, @NonNull ProviderInfo info) {
|
||||
super.attachInfo(context, info);
|
||||
|
||||
// Sanity check our security
|
||||
if (info.exported) {
|
||||
throw new SecurityException("Provider must not be exported");
|
||||
}
|
||||
if (!info.grantUriPermissions) {
|
||||
throw new SecurityException("Provider must grant uri permissions");
|
||||
}
|
||||
|
||||
mStrategy = getPathStrategy(context, info.authority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a content URI for a given {@link File}. Specific temporary
|
||||
* permissions for the content URI can be set with
|
||||
* {@link Context#grantUriPermission(String, Uri, int)}, or added
|
||||
* to an {@link Intent} by calling {@link Intent#setData(Uri) setData()} and then
|
||||
* {@link Intent#setFlags(int) setFlags()}; in both cases, the applicable flags are
|
||||
* {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
|
||||
* {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. A FileProvider can only return a
|
||||
* <code>content</code> {@link Uri} for file paths defined in their <code><paths></code>
|
||||
* meta-data element. See the Class Overview for more information.
|
||||
*
|
||||
* @param context A {@link Context} for the current component.
|
||||
* @param authority The authority of a {@link FileProvider} defined in a
|
||||
* {@code <provider>} element in your app's manifest.
|
||||
* @param file A {@link File} pointing to the filename for which you want a
|
||||
* <code>content</code> {@link Uri}.
|
||||
* @return A content URI for the file.
|
||||
* @throws IllegalArgumentException When the given {@link File} is outside
|
||||
* the paths supported by the provider.
|
||||
*/
|
||||
public static Uri getUriForFile(@NonNull Context context, @NonNull String authority,
|
||||
@NonNull File file) {
|
||||
final PathStrategy strategy = getPathStrategy(context, authority);
|
||||
return strategy.getUriForFile(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a content URI returned by
|
||||
* {@link #getUriForFile(Context, String, File) getUriForFile()} to get information about a file
|
||||
* managed by the FileProvider.
|
||||
* FileProvider reports the column names defined in {@link OpenableColumns}:
|
||||
* <ul>
|
||||
* <li>{@link OpenableColumns#DISPLAY_NAME}</li>
|
||||
* <li>{@link OpenableColumns#SIZE}</li>
|
||||
* </ul>
|
||||
* For more information, see
|
||||
* {@link ContentProvider#query(Uri, String[], String, String[], String)
|
||||
* ContentProvider.query()}.
|
||||
*
|
||||
* @param uri A content URI returned by {@link #getUriForFile}.
|
||||
* @param projection The list of columns to put into the {@link Cursor}. If null all columns are
|
||||
* included.
|
||||
* @param selection Selection criteria to apply. If null then all data that matches the content
|
||||
* URI is returned.
|
||||
* @param selectionArgs An array of {@link String}, containing arguments to bind to
|
||||
* the <i>selection</i> parameter. The <i>query</i> method scans <i>selection</i> from left to
|
||||
* right and iterates through <i>selectionArgs</i>, replacing the current "?" character in
|
||||
* <i>selection</i> with the value at the current position in <i>selectionArgs</i>. The
|
||||
* values are bound to <i>selection</i> as {@link String} values.
|
||||
* @param sortOrder A {@link String} containing the column name(s) on which to sort
|
||||
* the resulting {@link Cursor}.
|
||||
* @return A {@link Cursor} containing the results of the query.
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
|
||||
@Nullable String[] selectionArgs,
|
||||
@Nullable String sortOrder) {
|
||||
// ContentProvider has already checked granted permissions
|
||||
final File file = mStrategy.getFileForUri(uri);
|
||||
|
||||
if (projection == null) {
|
||||
projection = COLUMNS;
|
||||
}
|
||||
|
||||
String[] cols = new String[projection.length];
|
||||
Object[] values = new Object[projection.length];
|
||||
int i = 0;
|
||||
for (String col : projection) {
|
||||
if (OpenableColumns.DISPLAY_NAME.equals(col)) {
|
||||
cols[i] = OpenableColumns.DISPLAY_NAME;
|
||||
values[i++] = file.getName();
|
||||
} else if (OpenableColumns.SIZE.equals(col)) {
|
||||
cols[i] = OpenableColumns.SIZE;
|
||||
values[i++] = file.length();
|
||||
}
|
||||
}
|
||||
|
||||
cols = copyOf(cols, i);
|
||||
values = copyOf(values, i);
|
||||
|
||||
final MatrixCursor cursor = new MatrixCursor(cols, 1);
|
||||
cursor.addRow(values);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MIME type of a content URI returned by
|
||||
* {@link #getUriForFile(Context, String, File) getUriForFile()}.
|
||||
*
|
||||
* @param uri A content URI returned by
|
||||
* {@link #getUriForFile(Context, String, File) getUriForFile()}.
|
||||
* @return If the associated file has an extension, the MIME type associated with that
|
||||
* extension; otherwise <code>application/octet-stream</code>.
|
||||
*/
|
||||
@Override
|
||||
public String getType(@NonNull Uri uri) {
|
||||
// ContentProvider has already checked granted permissions
|
||||
final File file = mStrategy.getFileForUri(uri);
|
||||
|
||||
final int lastDot = file.getName().lastIndexOf('.');
|
||||
if (lastDot >= 0) {
|
||||
final String extension = file.getName().substring(lastDot + 1);
|
||||
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
if (mime != null) {
|
||||
return mime;
|
||||
}
|
||||
}
|
||||
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
/**
|
||||
* By default, this method throws an {@link UnsupportedOperationException}. You must
|
||||
* subclass FileProvider if you want to provide different functionality.
|
||||
*/
|
||||
@Override
|
||||
public Uri insert(@NonNull Uri uri, ContentValues values) {
|
||||
throw new UnsupportedOperationException("No external inserts");
|
||||
}
|
||||
|
||||
/**
|
||||
* By default, this method throws an {@link UnsupportedOperationException}. You must
|
||||
* subclass FileProvider if you want to provide different functionality.
|
||||
*/
|
||||
@Override
|
||||
public int update(@NonNull Uri uri, ContentValues values, @Nullable String selection,
|
||||
@Nullable String[] selectionArgs) {
|
||||
throw new UnsupportedOperationException("No external updates");
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the file associated with the specified content URI, as
|
||||
* returned by {@link #getUriForFile(Context, String, File) getUriForFile()}. Notice that this
|
||||
* method does <b>not</b> throw an {@link IOException}; you must check its return value.
|
||||
*
|
||||
* @param uri A content URI for a file, as returned by
|
||||
* {@link #getUriForFile(Context, String, File) getUriForFile()}.
|
||||
* @param selection Ignored. Set to {@code null}.
|
||||
* @param selectionArgs Ignored. Set to {@code null}.
|
||||
* @return 1 if the delete succeeds; otherwise, 0.
|
||||
*/
|
||||
@Override
|
||||
public int delete(@NonNull Uri uri, @Nullable String selection,
|
||||
@Nullable String[] selectionArgs) {
|
||||
// ContentProvider has already checked granted permissions
|
||||
final File file = mStrategy.getFileForUri(uri);
|
||||
return file.delete() ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* By default, FileProvider automatically returns the
|
||||
* {@link ParcelFileDescriptor} for a file associated with a <code>content://</code>
|
||||
* {@link Uri}. To get the {@link ParcelFileDescriptor}, call
|
||||
* {@link android.content.ContentResolver#openFileDescriptor(Uri, String)
|
||||
* ContentResolver.openFileDescriptor}.
|
||||
*
|
||||
* To override this method, you must provide your own subclass of FileProvider.
|
||||
*
|
||||
* @param uri A content URI associated with a file, as returned by
|
||||
* {@link #getUriForFile(Context, String, File) getUriForFile()}.
|
||||
* @param mode Access mode for the file. May be "r" for read-only access, "rw" for read and
|
||||
* write access, or "rwt" for read and write access that truncates any existing file.
|
||||
* @return A new {@link ParcelFileDescriptor} with which you can access the file.
|
||||
*/
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
|
||||
throws FileNotFoundException {
|
||||
// ContentProvider has already checked granted permissions
|
||||
final File file = mStrategy.getFileForUri(uri);
|
||||
final int fileMode = modeToMode(mode);
|
||||
return ParcelFileDescriptor.open(file, fileMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@link PathStrategy} for given authority, either by parsing or
|
||||
* returning from cache.
|
||||
*/
|
||||
private static PathStrategy getPathStrategy(Context context, String authority) {
|
||||
PathStrategy strat;
|
||||
synchronized (sCache) {
|
||||
strat = sCache.get(authority);
|
||||
if (strat == null) {
|
||||
try {
|
||||
strat = parsePathStrategy(context, authority);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Failed to parse " + META_DATA_FILE_PROVIDER_PATHS + " meta-data", e);
|
||||
} catch (XmlPullParserException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Failed to parse " + META_DATA_FILE_PROVIDER_PATHS + " meta-data", e);
|
||||
}
|
||||
sCache.put(authority, strat);
|
||||
}
|
||||
}
|
||||
return strat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and return {@link PathStrategy} for given authority as defined in
|
||||
* {@link #META_DATA_FILE_PROVIDER_PATHS} {@code <meta-data>}.
|
||||
*
|
||||
* @see #getPathStrategy(Context, String)
|
||||
*/
|
||||
private static PathStrategy parsePathStrategy(Context context, String authority)
|
||||
throws IOException, XmlPullParserException {
|
||||
final SimplePathStrategy strat = new SimplePathStrategy(authority);
|
||||
|
||||
final ProviderInfo info = context.getPackageManager()
|
||||
.resolveContentProvider(authority, PackageManager.GET_META_DATA);
|
||||
if (info == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Couldn't find meta-data for provider with authority " + authority);
|
||||
}
|
||||
|
||||
final XmlResourceParser in = info.loadXmlMetaData(
|
||||
context.getPackageManager(), META_DATA_FILE_PROVIDER_PATHS);
|
||||
if (in == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Missing " + META_DATA_FILE_PROVIDER_PATHS + " meta-data");
|
||||
}
|
||||
|
||||
int type;
|
||||
while ((type = in.next()) != END_DOCUMENT) {
|
||||
if (type == START_TAG) {
|
||||
final String tag = in.getName();
|
||||
|
||||
final String name = in.getAttributeValue(null, ATTR_NAME);
|
||||
String path = in.getAttributeValue(null, ATTR_PATH);
|
||||
|
||||
File target = null;
|
||||
if (TAG_ROOT_PATH.equals(tag)) {
|
||||
target = DEVICE_ROOT;
|
||||
} else if (TAG_FILES_PATH.equals(tag)) {
|
||||
target = context.getFilesDir();
|
||||
} else if (TAG_CACHE_PATH.equals(tag)) {
|
||||
target = context.getCacheDir();
|
||||
} else if (TAG_EXTERNAL.equals(tag)) {
|
||||
target = Environment.getExternalStorageDirectory();
|
||||
} else if (TAG_EXTERNAL_FILES.equals(tag)) {
|
||||
File[] externalFilesDirs = context.getExternalFilesDirs(null);
|
||||
if (externalFilesDirs.length > 0) {
|
||||
target = externalFilesDirs[0];
|
||||
}
|
||||
} else if (TAG_EXTERNAL_CACHE.equals(tag)) {
|
||||
File[] externalCacheDirs = context.getExternalCacheDirs();
|
||||
if (externalCacheDirs.length > 0) {
|
||||
target = externalCacheDirs[0];
|
||||
}
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
|
||||
&& TAG_EXTERNAL_MEDIA.equals(tag)) {
|
||||
File[] externalMediaDirs = context.getExternalMediaDirs();
|
||||
if (externalMediaDirs.length > 0) {
|
||||
target = externalMediaDirs[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (target != null) {
|
||||
strat.addRoot(name, buildPath(target, path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return strat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strategy for mapping between {@link File} and {@link Uri}.
|
||||
* <p>
|
||||
* Strategies must be symmetric so that mapping a {@link File} to a
|
||||
* {@link Uri} and then back to a {@link File} points at the original
|
||||
* target.
|
||||
* <p>
|
||||
* Strategies must remain consistent across app launches, and not rely on
|
||||
* dynamic state. This ensures that any generated {@link Uri} can still be
|
||||
* resolved if your process is killed and later restarted.
|
||||
*
|
||||
* @see SimplePathStrategy
|
||||
*/
|
||||
interface PathStrategy {
|
||||
/**
|
||||
* Return a {@link Uri} that represents the given {@link File}.
|
||||
*/
|
||||
Uri getUriForFile(File file);
|
||||
|
||||
/**
|
||||
* Return a {@link File} that represents the given {@link Uri}.
|
||||
*/
|
||||
File getFileForUri(Uri uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strategy that provides access to files living under a narrow whitelist of
|
||||
* filesystem roots. It will throw {@link SecurityException} if callers try
|
||||
* accessing files outside the configured roots.
|
||||
* <p>
|
||||
* For example, if configured with
|
||||
* {@code addRoot("myfiles", context.getFilesDir())}, then
|
||||
* {@code context.getFileStreamPath("foo.txt")} would map to
|
||||
* {@code content://myauthority/myfiles/foo.txt}.
|
||||
*/
|
||||
static class SimplePathStrategy implements PathStrategy {
|
||||
private final String mAuthority;
|
||||
private final HashMap<String, File> mRoots = new HashMap<String, File>();
|
||||
|
||||
SimplePathStrategy(String authority) {
|
||||
mAuthority = authority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a mapping from a name to a filesystem root. The provider only offers
|
||||
* access to files that live under configured roots.
|
||||
*/
|
||||
void addRoot(String name, File root) {
|
||||
if (TextUtils.isEmpty(name)) {
|
||||
throw new IllegalArgumentException("Name must not be empty");
|
||||
}
|
||||
|
||||
try {
|
||||
// Resolve to canonical path to keep path checking fast
|
||||
root = root.getCanonicalFile();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Failed to resolve canonical path for " + root, e);
|
||||
}
|
||||
|
||||
mRoots.put(name, root);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getUriForFile(File file) {
|
||||
String path;
|
||||
try {
|
||||
path = file.getCanonicalPath();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
|
||||
}
|
||||
|
||||
// Find the most-specific root path
|
||||
Map.Entry<String, File> mostSpecific = null;
|
||||
for (Map.Entry<String, File> root : mRoots.entrySet()) {
|
||||
final String rootPath = root.getValue().getPath();
|
||||
if (path.startsWith(rootPath) && (mostSpecific == null
|
||||
|| rootPath.length() > mostSpecific.getValue().getPath().length())) {
|
||||
mostSpecific = root;
|
||||
}
|
||||
}
|
||||
|
||||
if (mostSpecific == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Failed to find configured root that contains " + path);
|
||||
}
|
||||
|
||||
// Start at first char of path under root
|
||||
final String rootPath = mostSpecific.getValue().getPath();
|
||||
if (rootPath.endsWith("/")) {
|
||||
path = path.substring(rootPath.length());
|
||||
} else {
|
||||
path = path.substring(rootPath.length() + 1);
|
||||
}
|
||||
|
||||
// Encode the tag and path separately
|
||||
path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/");
|
||||
return new Uri.Builder().scheme("content")
|
||||
.authority(mAuthority).encodedPath(path).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFileForUri(Uri uri) {
|
||||
String path = uri.getEncodedPath();
|
||||
|
||||
final int splitIndex = path.indexOf('/', 1);
|
||||
final String tag = Uri.decode(path.substring(1, splitIndex));
|
||||
path = Uri.decode(path.substring(splitIndex + 1));
|
||||
|
||||
final File root = mRoots.get(tag);
|
||||
if (root == null) {
|
||||
throw new IllegalArgumentException("Unable to find configured root for " + uri);
|
||||
}
|
||||
|
||||
File file = new File(root, path);
|
||||
try {
|
||||
file = file.getCanonicalFile();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
|
||||
}
|
||||
|
||||
if (!file.getPath().startsWith(root.getPath())) {
|
||||
throw new SecurityException("Resolved path jumped beyond configured root");
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copied from ContentResolver.java
|
||||
*/
|
||||
private static int modeToMode(String mode) {
|
||||
int modeBits;
|
||||
if ("r".equals(mode)) {
|
||||
modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
|
||||
} else if ("w".equals(mode) || "wt".equals(mode)) {
|
||||
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
|
||||
| ParcelFileDescriptor.MODE_CREATE
|
||||
| ParcelFileDescriptor.MODE_TRUNCATE;
|
||||
} else if ("wa".equals(mode)) {
|
||||
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
|
||||
| ParcelFileDescriptor.MODE_CREATE
|
||||
| ParcelFileDescriptor.MODE_APPEND;
|
||||
} else if ("rw".equals(mode)) {
|
||||
modeBits = ParcelFileDescriptor.MODE_READ_WRITE
|
||||
| ParcelFileDescriptor.MODE_CREATE;
|
||||
} else if ("rwt".equals(mode)) {
|
||||
modeBits = ParcelFileDescriptor.MODE_READ_WRITE
|
||||
| ParcelFileDescriptor.MODE_CREATE
|
||||
| ParcelFileDescriptor.MODE_TRUNCATE;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid mode: " + mode);
|
||||
}
|
||||
return modeBits;
|
||||
}
|
||||
|
||||
private static File buildPath(File base, String... segments) {
|
||||
File cur = base;
|
||||
for (String segment : segments) {
|
||||
if (segment != null) {
|
||||
cur = new File(cur, segment);
|
||||
}
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
private static String[] copyOf(String[] original, int newLength) {
|
||||
final String[] result = new String[newLength];
|
||||
System.arraycopy(original, 0, result, 0, newLength);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Object[] copyOf(Object[] original, int newLength) {
|
||||
final Object[] result = new Object[newLength];
|
||||
System.arraycopy(original, 0, result, 0, newLength);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.joinmastodon.android;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class TweakedFileProvider extends FileProvider{
|
||||
private static final String TAG="TweakedFileProvider";
|
||||
|
||||
@Override
|
||||
public String getType(@NonNull Uri uri){
|
||||
Log.d(TAG, "getType() called with: uri = ["+uri+"]");
|
||||
if(uri.getPathSegments().get(0).equals("image_cache")){
|
||||
Log.i(TAG, "getType: HERE!");
|
||||
return "image/jpeg"; // might as well be a png but image decoding APIs don't care, needs to be image/* though
|
||||
}
|
||||
return super.getType(uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder){
|
||||
Log.d(TAG, "query() called with: uri = ["+uri+"], projection = ["+Arrays.toString(projection)+"], selection = ["+selection+"], selectionArgs = ["+Arrays.toString(selectionArgs)+"], sortOrder = ["+sortOrder+"]");
|
||||
return super.query(uri, projection, selection, selectionArgs, sortOrder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException{
|
||||
Log.d(TAG, "openFile() called with: uri = ["+uri+"], mode = ["+mode+"]");
|
||||
return super.openFile(uri, mode);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ClipData;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
@@ -1120,12 +1121,15 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
private void showLanguageAlert(){
|
||||
Preferences prefs=AccountSessionManager.getInstance().getAccount(accountID).preferences;
|
||||
ComposeLanguageAlertViewController vc=new ComposeLanguageAlertViewController(getActivity(), prefs!=null ? prefs.postingDefaultLanguage : null, postLang, mainEditText.getText().toString());
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
final AlertDialog dlg=new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.language)
|
||||
.setView(vc.getView())
|
||||
.setPositiveButton(R.string.ok, (dialog, which)->setPostLanguage(vc.getSelectedOption()))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.cancel, null)
|
||||
.show();
|
||||
vc.setSelectionListener(opt->{
|
||||
setPostLanguage(opt);
|
||||
dlg.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
private void setPostLanguage(ComposeLanguageAlertViewController.SelectedOption language){
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.ActionMode;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
@@ -19,7 +24,7 @@ import org.joinmastodon.android.api.requests.lists.GetListAccounts;
|
||||
import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
|
||||
import org.joinmastodon.android.events.AccountAddedToListEvent;
|
||||
import org.joinmastodon.android.events.AccountRemovedFromListEvent;
|
||||
import org.joinmastodon.android.fragments.account_list.AddListMembersFragment;
|
||||
import org.joinmastodon.android.fragments.account_list.AddNewListMembersFragment;
|
||||
import org.joinmastodon.android.fragments.account_list.PaginatedAccountListFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.FollowList;
|
||||
@@ -33,24 +38,31 @@ import org.parceler.Parcels;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
|
||||
public class ListMembersFragment extends PaginatedAccountListFragment{
|
||||
private static final int ADD_MEMBER_RESULT=600;
|
||||
|
||||
public class ListMembersFragment extends PaginatedAccountListFragment implements AddNewListMembersFragment.Listener, OnBackPressedListener{
|
||||
private ImageButton fab;
|
||||
private FollowList followList;
|
||||
private boolean inSelectionMode;
|
||||
private Set<String> selectedAccounts=new HashSet<>();
|
||||
private ActionMode actionMode;
|
||||
private MenuItem deleteItem;
|
||||
private FrameLayout searchFragmentContainer;
|
||||
private FrameLayout fragmentContentWrap;
|
||||
private AddNewListMembersFragment searchFragment;
|
||||
private FragmentRootLinearLayout rootView;
|
||||
private WindowInsets lastInsets;
|
||||
private HashSet<String> accountIDsInList=new HashSet<>();
|
||||
private boolean dismissingSearchFragment;
|
||||
|
||||
public ListMembersFragment(){
|
||||
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
||||
@@ -76,6 +88,26 @@ public class ListMembersFragment extends PaginatedAccountListFragment{
|
||||
return new GetListAccounts(followList.id, maxID, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDataLoaded(List<AccountViewModel> d, boolean more){
|
||||
if(refreshing)
|
||||
accountIDsInList.clear();
|
||||
for(AccountViewModel a:d){
|
||||
accountIDsInList.add(a.account.id);
|
||||
}
|
||||
super.onDataLoaded(d, more);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||
View view=super.onCreateView(inflater, container, savedInstanceState);
|
||||
FrameLayout wrapper=new FrameLayout(getActivity());
|
||||
wrapper.addView(view);
|
||||
rootView=(FragmentRootLinearLayout) view;
|
||||
fragmentContentWrap=wrapper;
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfigureViewHolder(AccountViewHolder holder){
|
||||
super.onConfigureViewHolder(holder);
|
||||
@@ -132,16 +164,19 @@ public class ListMembersFragment extends PaginatedAccountListFragment{
|
||||
|
||||
@Override
|
||||
public void onApplyWindowInsets(WindowInsets insets){
|
||||
super.onApplyWindowInsets(insets);
|
||||
lastInsets=insets;
|
||||
if(searchFragment!=null)
|
||||
searchFragment.onApplyWindowInsets(insets);
|
||||
UiUtils.applyBottomInsetToFAB(fab, insets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFragmentResult(int reqCode, boolean success, Bundle result){
|
||||
if(reqCode==ADD_MEMBER_RESULT && success){
|
||||
Account acc=Objects.requireNonNull(Parcels.unwrap(result.getParcelable("selectedAccount")));
|
||||
addAccounts(List.of(acc));
|
||||
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0){
|
||||
list.setPadding(0, 0, 0, insets.getSystemWindowInsetBottom());
|
||||
emptyView.setPadding(0, 0, 0, insets.getSystemWindowInsetBottom());
|
||||
progress.setPadding(0, 0, 0, insets.getSystemWindowInsetBottom());
|
||||
insets=insets.inset(0, 0, 0, insets.getSystemWindowInsetBottom());
|
||||
}else{
|
||||
list.setPadding(0, 0, 0, 0);
|
||||
}
|
||||
rootView.onApplyWindowInsets(insets);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
@@ -160,9 +195,25 @@ public class ListMembersFragment extends PaginatedAccountListFragment{
|
||||
}
|
||||
|
||||
private void onFabClick(){
|
||||
searchFragmentContainer=new FrameLayout(getActivity());
|
||||
searchFragmentContainer.setId(R.id.search_fragment);
|
||||
fragmentContentWrap.addView(searchFragmentContainer);
|
||||
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.goForResult(getActivity(), AddListMembersFragment.class, args, ADD_MEMBER_RESULT, this);
|
||||
args.putParcelable("list", Parcels.wrap(followList));
|
||||
args.putBoolean("_can_go_back", true);
|
||||
searchFragment=new AddNewListMembersFragment(this);
|
||||
searchFragment.setArguments(args);
|
||||
getChildFragmentManager().beginTransaction().add(R.id.search_fragment, searchFragment).commit();
|
||||
getChildFragmentManager().executePendingTransactions();
|
||||
if(lastInsets!=null)
|
||||
searchFragment.onApplyWindowInsets(lastInsets);
|
||||
searchFragmentContainer.setTranslationX(V.dp(100));
|
||||
searchFragmentContainer.setAlpha(0f);
|
||||
searchFragmentContainer.animate().translationX(0).alpha(1).setDuration(300).withLayer().setInterpolator(CubicBezierInterpolator.DEFAULT).withEndAction(()->{
|
||||
rootView.setVisibility(View.GONE);
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void onItemClick(AccountViewHolder holder){
|
||||
@@ -198,7 +249,7 @@ public class ListMembersFragment extends PaginatedAccountListFragment{
|
||||
if(id==R.id.remove_from_list){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.confirm_remove_list_member)
|
||||
.setPositiveButton(R.string.remove, (dlg, which)->removeAccounts(Set.of(holder.getItem().account.id)))
|
||||
.setPositiveButton(R.string.remove, (dlg, which)->removeAccounts(Set.of(holder.getItem().account.id), null))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
@@ -229,7 +280,7 @@ public class ListMembersFragment extends PaginatedAccountListFragment{
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.confirm_remove_list_members)
|
||||
.setPositiveButton(R.string.remove, (dlg, which)->removeAccounts(new HashSet<>(selectedAccounts)))
|
||||
.setPositiveButton(R.string.remove, (dlg, which)->removeAccounts(new HashSet<>(selectedAccounts), null))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
return true;
|
||||
@@ -251,13 +302,16 @@ public class ListMembersFragment extends PaginatedAccountListFragment{
|
||||
actionMode.setTitle(getResources().getQuantityString(R.plurals.x_items_selected, selectedAccounts.size(), selectedAccounts.size()));
|
||||
}
|
||||
|
||||
private void removeAccounts(Set<String> ids){
|
||||
private void removeAccounts(Set<String> ids, Runnable onDone){
|
||||
new RemoveAccountsFromList(followList.id, ids)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Void result){
|
||||
if(onDone!=null)
|
||||
onDone.run();
|
||||
if(inSelectionMode)
|
||||
actionMode.finish();
|
||||
accountIDsInList.removeAll(ids);
|
||||
removeAccountRows(ids);
|
||||
}
|
||||
|
||||
@@ -270,12 +324,15 @@ public class ListMembersFragment extends PaginatedAccountListFragment{
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
private void addAccounts(Collection<Account> accounts){
|
||||
private void addAccounts(Collection<Account> accounts, Runnable onDone){
|
||||
new AddAccountsToList(followList.id, accounts.stream().map(a->a.id).collect(Collectors.toSet()))
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Void result){
|
||||
if(onDone!=null)
|
||||
onDone.run();
|
||||
for(Account acc:accounts){
|
||||
accountIDsInList.add(acc.id);
|
||||
data.add(new AccountViewModel(acc, accountID));
|
||||
}
|
||||
list.getAdapter().notifyItemRangeInserted(data.size()-accounts.size(), accounts.size());
|
||||
@@ -298,4 +355,54 @@ public class ListMembersFragment extends PaginatedAccountListFragment{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountInList(AccountViewModel account){
|
||||
return accountIDsInList.contains(account.account.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAccountToList(AccountViewModel account, Runnable onDone){
|
||||
addAccounts(Set.of(account.account), onDone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAccountAccountFromList(AccountViewModel account, Runnable onDone){
|
||||
removeAccounts(Set.of(account.account.id), onDone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed(){
|
||||
if(searchFragment!=null){
|
||||
dismissSearchFragment();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void dismissSearchFragment(){
|
||||
if(searchFragment==null || dismissingSearchFragment)
|
||||
return;
|
||||
dismissingSearchFragment=true;
|
||||
rootView.setVisibility(View.VISIBLE);
|
||||
searchFragmentContainer.animate().translationX(V.dp(100)).alpha(0).setDuration(200).withLayer().setInterpolator(CubicBezierInterpolator.DEFAULT).withEndAction(()->{
|
||||
getChildFragmentManager().beginTransaction().remove(searchFragment).commit();
|
||||
getChildFragmentManager().executePendingTransactions();
|
||||
fragmentContentWrap.removeView(searchFragmentContainer);
|
||||
searchFragmentContainer=null;
|
||||
searchFragment=null;
|
||||
dismissingSearchFragment=false;
|
||||
}).start();
|
||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStatusBarColor(int color){
|
||||
rootView.setStatusBarColor(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setNavigationBarColor(int color){
|
||||
rootView.setNavigationBarColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,13 +18,18 @@ import android.graphics.drawable.LayerDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableString;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.SpannedString;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.ImageSpan;
|
||||
import android.transition.ChangeBounds;
|
||||
import android.transition.Fade;
|
||||
import android.transition.Transition;
|
||||
import android.transition.TransitionManager;
|
||||
import android.transition.TransitionSet;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -36,6 +41,7 @@ import android.view.ViewTreeObserver;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
@@ -129,6 +135,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
private View tabsDivider;
|
||||
private View actionButtonWrap;
|
||||
private CustomDrawingOrderLinearLayout scrollableContent;
|
||||
private ImageButton qrCodeButton;
|
||||
|
||||
private Account account;
|
||||
private String accountID;
|
||||
@@ -211,6 +218,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
tabsDivider=content.findViewById(R.id.tabs_divider);
|
||||
actionButtonWrap=content.findViewById(R.id.profile_action_btn_wrap);
|
||||
scrollableContent=content.findViewById(R.id.scrollable_content);
|
||||
qrCodeButton=content.findViewById(R.id.qr_code);
|
||||
|
||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(24));
|
||||
avatar.setClipToOutline(true);
|
||||
@@ -324,6 +332,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
bioEdit.addTextChangedListener(new SimpleTextWatcher(e->editDirty=true));
|
||||
|
||||
usernameDomain.setOnClickListener(v->new DecentralizationExplainerSheet(getActivity(), accountID, account).show());
|
||||
qrCodeButton.setOnClickListener(v->{
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("targetAccount", Parcels.wrap(account));
|
||||
ProfileQrCodeFragment qf=new ProfileQrCodeFragment();
|
||||
qf.setArguments(args);
|
||||
qf.show(getChildFragmentManager(), "qrDialog");
|
||||
});
|
||||
|
||||
return sizeWrapper;
|
||||
}
|
||||
@@ -583,6 +599,13 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
}
|
||||
}
|
||||
|
||||
private CharSequence makeRedString(CharSequence s){
|
||||
int color=UiUtils.getThemeColor(getActivity(), R.attr.colorM3Error);
|
||||
SpannableString ss=new SpannableString(s);
|
||||
ss.setSpan(new ForegroundColorSpan(color), 0, ss.length(), 0);
|
||||
return ss;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||
if(isOwnProfile && isInEditMode){
|
||||
@@ -599,28 +622,29 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
if(isOwnProfile)
|
||||
return;
|
||||
|
||||
menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.displayName));
|
||||
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.displayName));
|
||||
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.displayName));
|
||||
menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername()));
|
||||
menu.findItem(R.id.block).setTitle(makeRedString(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername())));
|
||||
menu.findItem(R.id.report).setTitle(makeRedString(getString(R.string.report_user, account.getDisplayUsername())));
|
||||
if(relationship.following)
|
||||
menu.findItem(R.id.hide_boosts).setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.displayName));
|
||||
menu.findItem(R.id.hide_boosts).setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user));
|
||||
else
|
||||
menu.findItem(R.id.hide_boosts).setVisible(false);
|
||||
if(!account.isLocal())
|
||||
menu.findItem(R.id.block_domain).setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
|
||||
menu.findItem(R.id.block_domain).setTitle(makeRedString(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain())));
|
||||
else
|
||||
menu.findItem(R.id.block_domain).setVisible(false);
|
||||
menu.findItem(R.id.add_to_list).setVisible(relationship.following);
|
||||
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P){
|
||||
menu.setGroupDividerEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item){
|
||||
int id=item.getItemId();
|
||||
if(id==R.id.share){
|
||||
Intent intent=new Intent(Intent.ACTION_SEND);
|
||||
intent.setType("text/plain");
|
||||
intent.putExtra(Intent.EXTRA_TEXT, account.url);
|
||||
startActivity(Intent.createChooser(intent, item.getTitle()));
|
||||
UiUtils.openSystemShareSheet(getActivity(), account);
|
||||
}else if(id==R.id.mute){
|
||||
confirmToggleMuted();
|
||||
}else if(id==R.id.block){
|
||||
@@ -836,17 +860,48 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
toolbar.setNavigationContentDescription(R.string.discard);
|
||||
|
||||
ViewGroup parent=contentView.findViewById(R.id.scrollable_content);
|
||||
Runnable updater=new Runnable(){
|
||||
@Override
|
||||
public void run(){
|
||||
// setPadding() calls nullLayouts() internally, forcing the text layout to update
|
||||
actionButton.setPadding(actionButton.getPaddingLeft(), 1, actionButton.getPaddingRight(), 0);
|
||||
actionButton.setPadding(actionButton.getPaddingLeft(), 0, actionButton.getPaddingRight(), 0);
|
||||
actionButton.measure(actionButton.getWidth()|View.MeasureSpec.EXACTLY, actionButton.getHeight()|View.MeasureSpec.EXACTLY);
|
||||
actionButton.postOnAnimation(this);
|
||||
}
|
||||
};
|
||||
actionButton.postOnAnimation(updater);
|
||||
TransitionManager.beginDelayedTransition(parent, new TransitionSet()
|
||||
.addTransition(new Fade(Fade.IN | Fade.OUT))
|
||||
.addTransition(new ChangeBounds())
|
||||
.setDuration(250)
|
||||
.setInterpolator(CubicBezierInterpolator.DEFAULT)
|
||||
.addListener(new Transition.TransitionListener(){
|
||||
@Override
|
||||
public void onTransitionStart(Transition transition){}
|
||||
|
||||
@Override
|
||||
public void onTransitionEnd(Transition transition){
|
||||
actionButton.removeCallbacks(updater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionCancel(Transition transition){}
|
||||
|
||||
@Override
|
||||
public void onTransitionPause(Transition transition){}
|
||||
|
||||
@Override
|
||||
public void onTransitionResume(Transition transition){}
|
||||
})
|
||||
);
|
||||
|
||||
name.setVisibility(View.GONE);
|
||||
username.setVisibility(View.GONE);
|
||||
name.setVisibility(View.INVISIBLE);
|
||||
username.setVisibility(View.INVISIBLE);
|
||||
bio.setVisibility(View.GONE);
|
||||
countersLayout.setVisibility(View.GONE);
|
||||
qrCodeButton.setVisibility(View.GONE);
|
||||
usernameDomain.setVisibility(View.INVISIBLE);
|
||||
|
||||
nameEditWrap.setVisibility(View.VISIBLE);
|
||||
nameEdit.setText(account.displayName);
|
||||
@@ -885,11 +940,40 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
editSaveMenuItem=null;
|
||||
|
||||
ViewGroup parent=contentView.findViewById(R.id.scrollable_content);
|
||||
Runnable updater=new Runnable(){
|
||||
@Override
|
||||
public void run(){
|
||||
// setPadding() calls nullLayouts() internally, forcing the text layout to update
|
||||
actionButton.setPadding(actionButton.getPaddingLeft(), 1, actionButton.getPaddingRight(), 0);
|
||||
actionButton.setPadding(actionButton.getPaddingLeft(), 0, actionButton.getPaddingRight(), 0);
|
||||
actionButton.measure(actionButton.getWidth()|View.MeasureSpec.EXACTLY, actionButton.getHeight()|View.MeasureSpec.EXACTLY);
|
||||
actionButton.postOnAnimation(this);
|
||||
}
|
||||
};
|
||||
actionButton.postOnAnimation(updater);
|
||||
TransitionManager.beginDelayedTransition(parent, new TransitionSet()
|
||||
.addTransition(new Fade(Fade.IN | Fade.OUT))
|
||||
.addTransition(new ChangeBounds())
|
||||
.setDuration(250)
|
||||
.setInterpolator(CubicBezierInterpolator.DEFAULT)
|
||||
.addListener(new Transition.TransitionListener(){
|
||||
@Override
|
||||
public void onTransitionStart(Transition transition){}
|
||||
|
||||
@Override
|
||||
public void onTransitionEnd(Transition transition){
|
||||
actionButton.removeCallbacks(updater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionCancel(Transition transition){}
|
||||
|
||||
@Override
|
||||
public void onTransitionPause(Transition transition){}
|
||||
|
||||
@Override
|
||||
public void onTransitionResume(Transition transition){}
|
||||
})
|
||||
);
|
||||
nameEditWrap.setVisibility(View.GONE);
|
||||
bioEditWrap.setVisibility(View.GONE);
|
||||
@@ -898,6 +982,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
bio.setVisibility(View.VISIBLE);
|
||||
countersLayout.setVisibility(View.VISIBLE);
|
||||
refreshLayout.setEnabled(true);
|
||||
usernameDomain.setVisibility(View.VISIBLE);
|
||||
qrCodeButton.setVisibility(View.VISIBLE);
|
||||
|
||||
bindHeaderView();
|
||||
V.setVisibilityAnimated(fab, View.VISIBLE);
|
||||
|
||||
@@ -0,0 +1,679 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.Manifest;
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.DownloadManager;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.DashPathEffect;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.media.MediaScannerConnection;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.gms.common.Feature;
|
||||
import com.google.android.gms.common.api.Status;
|
||||
import com.google.android.gms.common.moduleinstall.ModuleAvailabilityResponse;
|
||||
import com.google.android.gms.common.moduleinstall.ModuleInstallIntentResponse;
|
||||
import com.google.android.gms.common.moduleinstall.ModuleInstallResponse;
|
||||
import com.google.android.gms.common.moduleinstall.ModuleInstallStatusUpdate;
|
||||
import com.google.android.gms.common.moduleinstall.internal.ApiFeatureRequest;
|
||||
import com.google.android.gms.common.moduleinstall.internal.IModuleInstallCallbacks;
|
||||
import com.google.android.gms.common.moduleinstall.internal.IModuleInstallService;
|
||||
import com.google.android.gms.common.moduleinstall.internal.IModuleInstallStatusListener;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.qrcode.QRCodeWriter;
|
||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
|
||||
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.googleservices.GmsClient;
|
||||
import org.joinmastodon.android.googleservices.barcodescanner.Barcode;
|
||||
import org.joinmastodon.android.googleservices.barcodescanner.BarcodeScanner;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.Snackbar;
|
||||
import org.joinmastodon.android.ui.drawables.FancyQrCodeDrawable;
|
||||
import org.joinmastodon.android.ui.drawables.RadialParticleSystemDrawable;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.FixedAspectRatioFrameLayout;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringForce;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.CustomViewHelper;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class ProfileQrCodeFragment extends AppKitFragment{
|
||||
private static final String TAG="ProfileQrCodeFragment";
|
||||
private static final int PERMISSION_RESULT=388;
|
||||
private static final int SCAN_RESULT=439;
|
||||
|
||||
private Context themeWrapper;
|
||||
private GradientDrawable scrim=new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, new int[]{0xE6000000, 0xD9000000});
|
||||
private RadialParticleSystemDrawable particles;
|
||||
private View codeContainer;
|
||||
private View particleAnimContainer;
|
||||
private Animator currentTransition;
|
||||
private View saveBtn;
|
||||
private TextView saveBtnText;
|
||||
|
||||
private String accountID;
|
||||
private Account account;
|
||||
private String accountDomain;
|
||||
private Intent scannerIntent;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
setStyle(STYLE_NO_FRAME, 0);
|
||||
setHasOptionsMenu(true);
|
||||
accountID=getArguments().getString("account");
|
||||
account=Parcels.unwrap(getArguments().getParcelable("targetAccount"));
|
||||
setCancelable(false);
|
||||
scannerIntent=BarcodeScanner.createIntent(Barcode.FORMAT_QR_CODE, false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(){
|
||||
super.onStart();
|
||||
Dialog dlg=getDialog();
|
||||
dlg.getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT);
|
||||
dlg.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
|
||||
dlg.getWindow().setNavigationBarColor(0);
|
||||
dlg.getWindow().setStatusBarColor(0);
|
||||
WindowManager.LayoutParams lp=dlg.getWindow().getAttributes();
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P){
|
||||
lp.layoutInDisplayCutoutMode=WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||
}
|
||||
dlg.getWindow().setAttributes(lp);
|
||||
if(!isTablet){
|
||||
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
}
|
||||
dlg.setOnKeyListener((dialog, keyCode, event)->{
|
||||
if(keyCode==KeyEvent.KEYCODE_BACK && event.getAction()==KeyEvent.ACTION_DOWN){
|
||||
dismiss();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog){
|
||||
super.onDismiss(dialog);
|
||||
Activity activity=getActivity();
|
||||
if(activity!=null)
|
||||
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
themeWrapper=new ContextThemeWrapper(activity, R.style.Theme_Mastodon_Dark);
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
|
||||
View content=View.inflate(themeWrapper, R.layout.fragment_profile_qr, container);
|
||||
View decor=getDialog().getWindow().getDecorView();
|
||||
decor.setOnApplyWindowInsetsListener((v, insets)->{
|
||||
content.setPadding(insets.getStableInsetLeft(), insets.getStableInsetTop(), insets.getStableInsetRight(), insets.getStableInsetBottom());
|
||||
return insets.consumeStableInsets();
|
||||
});
|
||||
int flags=decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
|
||||
flags&=~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
|
||||
decor.setSystemUiVisibility(flags);
|
||||
content.setBackground(scrim);
|
||||
|
||||
String url=account.url;
|
||||
QRCodeWriter writer=new QRCodeWriter();
|
||||
BitMatrix code;
|
||||
try{
|
||||
code=writer.encode(url, BarcodeFormat.QR_CODE, 0, 0, Map.of(EncodeHintType.MARGIN, 0, EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H));
|
||||
}catch(WriterException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
View codeView=content.findViewById(R.id.code);
|
||||
ImageView avatar=content.findViewById(R.id.avatar);
|
||||
TextView username=content.findViewById(R.id.username);
|
||||
TextView domain=content.findViewById(R.id.domain);
|
||||
View share=content.findViewById(R.id.share_btn);
|
||||
saveBtn=content.findViewById(R.id.save_btn);
|
||||
saveBtnText=content.findViewById(R.id.save_text);
|
||||
View cornerAnimContainer=content.findViewById(R.id.corner_animation_container);
|
||||
particleAnimContainer=content.findViewById(R.id.particle_animation_container);
|
||||
codeContainer=content.findViewById(R.id.code_container);
|
||||
|
||||
if(!TextUtils.isEmpty(account.avatar)){
|
||||
ViewImageLoader.loadWithoutAnimation(avatar, getResources().getDrawable(R.drawable.image_placeholder, getActivity().getTheme()), new UrlImageLoaderRequest(Bitmap.Config.ARGB_8888, V.dp(24), V.dp(24), List.of(), Uri.parse(account.avatarStatic)));
|
||||
}
|
||||
username.setText(account.username);
|
||||
String accDomain=account.getDomain();
|
||||
domain.setText(accountDomain=TextUtils.isEmpty(accDomain) ? AccountSessionManager.get(accountID).domain : accDomain);
|
||||
Drawable logo=getResources().getDrawable(R.drawable.ic_ntf_logo, themeWrapper.getTheme()).mutate();
|
||||
logo.setTint(UiUtils.getThemeColor(themeWrapper, R.attr.colorM3OnPrimary));
|
||||
codeView.setBackground(new FancyQrCodeDrawable(code, UiUtils.getThemeColor(themeWrapper, R.attr.colorM3OnPrimary), logo));
|
||||
|
||||
share.setOnClickListener(v->{
|
||||
UiUtils.openSystemShareSheet(getActivity(), account);
|
||||
});
|
||||
saveBtn.setOnClickListener(v->saveCodeAsFile());
|
||||
|
||||
cornerAnimContainer.setBackground(new AnimatedCornersDrawable(themeWrapper));
|
||||
int particleColor=UiUtils.getThemeColor(themeWrapper, R.attr.colorM3Primary);
|
||||
particles=new RadialParticleSystemDrawable(5000, 200, (particleColor & 0xFFFFFF) | 0x80000000, particleColor & 0xFFFFFF, V.dp(65), V.dp(50), getResources().getDisplayMetrics().density);
|
||||
particleAnimContainer.setBackground(particles);
|
||||
content.setOnTouchListener(new TouchDismissListener());
|
||||
|
||||
int buttonExtraWidth=saveBtn.getPaddingLeft()+saveBtn.getPaddingRight()+saveBtnText.getCompoundDrawablesRelative()[0].getIntrinsicWidth()+saveBtnText.getCompoundDrawablePadding();
|
||||
saveBtn.getLayoutParams().width=(int)Math.max(saveBtnText.getPaint().measureText(getString(R.string.save)), saveBtnText.getPaint().measureText(getString(R.string.saved)))+buttonExtraWidth;
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
if(savedInstanceState==null){
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(
|
||||
ObjectAnimator.ofInt(scrim, "alpha", 0, 255),
|
||||
ObjectAnimator.ofFloat(particleAnimContainer, View.TRANSLATION_Y, V.dp(50), 0),
|
||||
ObjectAnimator.ofFloat(particleAnimContainer, View.ALPHA, 0, 1),
|
||||
ObjectAnimator.ofFloat(getToolbar(), View.ALPHA, 0, 1)
|
||||
);
|
||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
set.setDuration(350);
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
currentTransition=null;
|
||||
}
|
||||
});
|
||||
currentTransition=set;
|
||||
set.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dismiss(){
|
||||
dismissWithAnimation(super::dismiss, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||
if(GmsClient.isGooglePlayServicesAvailable(getActivity())){
|
||||
MenuItem item=menu.add(0, 0, 0, R.string.scan_qr_code);
|
||||
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
item.setIcon(R.drawable.ic_qr_code_scanner_24px);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item){
|
||||
if(scannerIntent.resolveActivity(getActivity().getPackageManager())!=null){
|
||||
startActivityForResult(scannerIntent, SCAN_RESULT);
|
||||
}else{
|
||||
ProgressDialog progress=new ProgressDialog(getActivity());
|
||||
progress.setMessage(getString(R.string.loading));
|
||||
progress.setCancelable(false);
|
||||
progress.show();
|
||||
GmsClient.getModuleInstallerService(getActivity(), new GmsClient.ServiceConnectionCallback<>(){
|
||||
@Override
|
||||
public void onSuccess(IModuleInstallService service, int connectionID){
|
||||
ApiFeatureRequest req=new ApiFeatureRequest();
|
||||
req.callingPackage=getActivity().getPackageName();
|
||||
Feature feature=new Feature();
|
||||
feature.name="mlkit.barcode.ui";
|
||||
feature.version=1;
|
||||
feature.oldVersion=-1;
|
||||
req.features=List.of(feature);
|
||||
req.urgent=true;
|
||||
try{
|
||||
service.installModules(new IModuleInstallCallbacks.Stub(){
|
||||
@Override
|
||||
public void onModuleAvailabilityResponse(Status status, ModuleAvailabilityResponse response) throws RemoteException{}
|
||||
|
||||
@Override
|
||||
public void onModuleInstallResponse(Status status, ModuleInstallResponse response) throws RemoteException{}
|
||||
|
||||
@Override
|
||||
public void onModuleInstallIntentResponse(Status status, ModuleInstallIntentResponse response) throws RemoteException{}
|
||||
|
||||
@Override
|
||||
public void onStatus(Status status) throws RemoteException{}
|
||||
}, req, new IModuleInstallStatusListener.Stub(){
|
||||
@Override
|
||||
public void onModuleInstallStatusUpdate(ModuleInstallStatusUpdate statusUpdate) throws RemoteException{
|
||||
if(statusUpdate.installState==ModuleInstallStatusUpdate.STATE_COMPLETED){
|
||||
Runnable r=new Runnable(){
|
||||
@Override
|
||||
public void run(){
|
||||
if(scannerIntent.resolveActivity(getActivity().getPackageManager())!=null){
|
||||
progress.dismiss();
|
||||
startActivityForResult(scannerIntent, SCAN_RESULT);
|
||||
}else{
|
||||
codeContainer.postDelayed(this, 100);
|
||||
}
|
||||
}
|
||||
};
|
||||
getActivity().runOnUiThread(r);
|
||||
GmsClient.disconnectFromService(getActivity(), connectionID);
|
||||
}else if(statusUpdate.installState==ModuleInstallStatusUpdate.STATE_FAILED || statusUpdate.installState==ModuleInstallStatusUpdate.STATE_CANCELED){
|
||||
getActivity().runOnUiThread(()->{
|
||||
progress.dismiss();
|
||||
Toast.makeText(themeWrapper, R.string.error, Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
GmsClient.disconnectFromService(getActivity(), connectionID);
|
||||
}
|
||||
}
|
||||
});
|
||||
}catch(RemoteException e){
|
||||
Log.e(TAG, "onSuccess: ", e);
|
||||
getActivity().runOnUiThread(()->{
|
||||
progress.dismiss();
|
||||
Toast.makeText(themeWrapper, R.string.error, Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
GmsClient.disconnectFromService(getActivity(), connectionID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception error){
|
||||
Log.e(TAG, "onError() called with: error = ["+error+"]");
|
||||
Toast.makeText(themeWrapper, R.string.error, Toast.LENGTH_SHORT).show();
|
||||
progress.dismiss();
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canGoBack(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onToolbarNavigationClick(){
|
||||
dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wantsCustomNavigationIcon(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getNavigationIconDrawableResource(){
|
||||
return R.drawable.ic_baseline_close_24;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LayoutInflater getToolbarLayoutInflater(){
|
||||
return LayoutInflater.from(themeWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getToolbarResource(){
|
||||
return R.layout.profile_qr_toolbar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults){
|
||||
if(requestCode==PERMISSION_RESULT){
|
||||
if(grantResults[0]==PackageManager.PERMISSION_GRANTED){
|
||||
doSaveCodeAsFile();
|
||||
}else if(!getActivity().shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.permission_required)
|
||||
.setMessage(R.string.storage_permission_to_download)
|
||||
.setPositiveButton(R.string.open_settings, (dialog, which)->getActivity().startActivity(new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", getActivity().getPackageName(), null))))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data){
|
||||
if(requestCode==SCAN_RESULT && resultCode==Activity.RESULT_OK && BarcodeScanner.isValidResult(data)){
|
||||
Barcode code=BarcodeScanner.getResult(data);
|
||||
if(code!=null){
|
||||
if(code.rawValue.startsWith("https:") || code.rawValue.startsWith("http:")){
|
||||
((MainActivity)getActivity()).handleURL(Uri.parse(code.rawValue), accountID);
|
||||
dismiss();
|
||||
}else{
|
||||
Toast.makeText(themeWrapper, R.string.link_not_supported, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void dismissWithAnimation(Runnable onDone, boolean animateTranslationDown){
|
||||
if(currentTransition!=null)
|
||||
currentTransition.cancel();
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(
|
||||
ObjectAnimator.ofInt(scrim, "alpha", 0),
|
||||
ObjectAnimator.ofFloat(particleAnimContainer, View.TRANSLATION_Y, particleAnimContainer.getTranslationY()+V.dp(animateTranslationDown ? 50 : -50)),
|
||||
ObjectAnimator.ofFloat(particleAnimContainer, View.ALPHA, 0),
|
||||
ObjectAnimator.ofFloat(getToolbar(), View.ALPHA, 0)
|
||||
);
|
||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
set.setDuration(200);
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
onDone.run();
|
||||
}
|
||||
});
|
||||
currentTransition=set;
|
||||
set.start();
|
||||
}
|
||||
|
||||
private void saveCodeAsFile(){
|
||||
if(Build.VERSION.SDK_INT>=29){
|
||||
doSaveCodeAsFile();
|
||||
}else{
|
||||
if(getActivity().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)!=PackageManager.PERMISSION_GRANTED){
|
||||
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_RESULT);
|
||||
}else{
|
||||
doSaveCodeAsFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void doSaveCodeAsFile(){
|
||||
Bitmap bmp=Bitmap.createBitmap(1080, 1080, Bitmap.Config.ARGB_8888);
|
||||
Canvas c=new Canvas(bmp);
|
||||
float factor=1080f/codeContainer.getWidth();
|
||||
c.scale(factor, factor);
|
||||
codeContainer.draw(c);
|
||||
Activity activity=getActivity();
|
||||
MastodonAPIController.runInBackground(()->{
|
||||
String fileName=account.username+"_"+accountDomain+".png";
|
||||
try(OutputStream os=destinationStreamForFile(fileName)){
|
||||
bmp.compress(Bitmap.CompressFormat.PNG, 100, os);
|
||||
activity.runOnUiThread(()->{
|
||||
saveBtn.setEnabled(false);
|
||||
saveBtnText.setText(R.string.saved);
|
||||
saveBtnText.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check_20px, 0, 0, 0);
|
||||
new Snackbar.Builder(activity)
|
||||
.setText(R.string.image_saved)
|
||||
.setAction(R.string.view_file, ()->startActivity(new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)))
|
||||
.show();
|
||||
});
|
||||
if(Build.VERSION.SDK_INT<29){
|
||||
File dstFile=new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName);
|
||||
MediaScannerConnection.scanFile(activity, new String[]{dstFile.getAbsolutePath()}, new String[]{"image/png"}, null);
|
||||
}
|
||||
}catch(IOException x){
|
||||
activity.runOnUiThread(()->{
|
||||
new Snackbar.Builder(activity)
|
||||
.setText(R.string.error_saving_file)
|
||||
.show();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private OutputStream destinationStreamForFile(String fileName) throws IOException{
|
||||
if(Build.VERSION.SDK_INT>=29){
|
||||
ContentValues values=new ContentValues();
|
||||
values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
|
||||
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
|
||||
values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png");
|
||||
ContentResolver cr=getActivity().getContentResolver();
|
||||
Uri itemUri=cr.insert(MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY), values);
|
||||
return cr.openOutputStream(itemUri);
|
||||
}else{
|
||||
return new FileOutputStream(new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig){
|
||||
super.onConfigurationChanged(newConfig);
|
||||
codeContainer.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||
@Override
|
||||
public boolean onPreDraw(){
|
||||
codeContainer.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
updateParticleEmitter();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateParticleEmitter(){
|
||||
int[] loc={0, 0};
|
||||
particleAnimContainer.getLocationInWindow(loc);
|
||||
int x=loc[0], y=loc[1];
|
||||
codeContainer.getLocationInWindow(loc);
|
||||
int cx=loc[0]-x+codeContainer.getWidth()/2;
|
||||
int cy=loc[1]-y+codeContainer.getHeight()/2;
|
||||
int r=codeContainer.getWidth()/2-V.dp(10);
|
||||
particles.setEmitterPosition(cx, cy);
|
||||
particles.setClipOutBounds(cx-r, cy-r, cx+r, cy+r);
|
||||
}
|
||||
|
||||
public static class CustomizedLinearLayout extends LinearLayout implements CustomViewHelper{
|
||||
public CustomizedLinearLayout(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CustomizedLinearLayout(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public CustomizedLinearLayout(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
||||
int maxW=dp(400);
|
||||
FixedAspectRatioFrameLayout aspectLayout=(FixedAspectRatioFrameLayout) getChildAt(0);
|
||||
if(MeasureSpec.getSize(widthMeasureSpec)>maxW){
|
||||
widthMeasureSpec=MeasureSpec.getMode(widthMeasureSpec) | maxW;
|
||||
aspectLayout.setUseHeight(MeasureSpec.getSize(heightMeasureSpec)<dp(464));
|
||||
}else{
|
||||
aspectLayout.setUseHeight(false);
|
||||
}
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
||||
|
||||
private class AnimatedCornersDrawable extends Drawable{
|
||||
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private RectF tmpRect=new RectF();
|
||||
|
||||
public AnimatedCornersDrawable(Context context){
|
||||
paint.setColor(UiUtils.getThemeColor(context, R.attr.colorM3Primary));
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setStrokeCap(Paint.Cap.ROUND);
|
||||
paint.setStrokeWidth(V.dp(4));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas){
|
||||
float inset=V.dp(24);
|
||||
float radius=V.dp(40);
|
||||
float animProgress=((float)Math.sin(Math.toRadians(SystemClock.uptimeMillis()/16.6%360.0))+1f)/2f;
|
||||
tmpRect.set(getBounds());
|
||||
tmpRect.inset(inset, inset);
|
||||
canvas.save();
|
||||
float factor=1f+0.025f*animProgress;
|
||||
paint.setStrokeWidth(V.dp(4)/factor);
|
||||
canvas.scale(factor, factor, tmpRect.centerX(), tmpRect.centerY());
|
||||
canvas.drawRoundRect(tmpRect, radius, radius, paint);
|
||||
canvas.restore();
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity(){
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(@NonNull Rect bounds){
|
||||
super.onBoundsChange(bounds);
|
||||
float inset=V.dp(24);
|
||||
float radius=V.dp(40);
|
||||
float additionalLength=V.dp(40);
|
||||
tmpRect.set(getBounds());
|
||||
tmpRect.inset(inset, inset);
|
||||
float[] intervals=new float[]{3.1415f*radius*0.5f+additionalLength*2f, tmpRect.width()-radius*2f-additionalLength*2f};
|
||||
paint.setPathEffect(new DashPathEffect(intervals, intervals[0]-additionalLength));
|
||||
updateParticleEmitter();
|
||||
}
|
||||
}
|
||||
|
||||
private class TouchDismissListener implements View.OnTouchListener{
|
||||
private Rect tmpRect=new Rect();
|
||||
private int[] xy={0, 0};
|
||||
private boolean dragging;
|
||||
private float dragDownY;
|
||||
private VelocityTracker velocityTracker;
|
||||
private SpringAnimation springBackAnim;
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent ev){
|
||||
if(ev.getAction()==MotionEvent.ACTION_DOWN){
|
||||
codeContainer.getLocationInWindow(xy);
|
||||
tmpRect.set(xy[0], xy[1], xy[0]+codeContainer.getWidth(), xy[1]+codeContainer.getHeight());
|
||||
if(springBackAnim!=null){
|
||||
springBackAnim.skipToEnd();
|
||||
}
|
||||
if(tmpRect.contains((int)ev.getX(), (int)ev.getY())){
|
||||
dragging=true;
|
||||
dragDownY=ev.getY();
|
||||
velocityTracker=VelocityTracker.obtain();
|
||||
velocityTracker.addMovement(ev);
|
||||
}else{
|
||||
dismiss();
|
||||
}
|
||||
}else if(dragging){
|
||||
if(ev.getAction()==MotionEvent.ACTION_MOVE){
|
||||
float transY=ev.getY()-dragDownY;
|
||||
particleAnimContainer.setTranslationY(transY);
|
||||
float alpha=1f-Math.abs(transY)/particleAnimContainer.getHeight();
|
||||
scrim.setAlpha(Math.round(alpha*255));
|
||||
getToolbar().setAlpha(alpha);
|
||||
velocityTracker.addMovement(ev);
|
||||
}else if(ev.getAction()==MotionEvent.ACTION_UP){
|
||||
dragging=false;
|
||||
velocityTracker.addMovement(ev);
|
||||
velocityTracker.computeCurrentVelocity(1000);
|
||||
float velocity=velocityTracker.getYVelocity();
|
||||
if(Math.abs(velocity)>=V.dp(1000) || Math.abs(particleAnimContainer.getTranslationY())>particleAnimContainer.getHeight()/4f){
|
||||
dismissWithAnimation(ProfileQrCodeFragment.super::dismiss, velocity>0);
|
||||
}else{
|
||||
springBack(velocity);
|
||||
}
|
||||
velocityTracker.recycle();
|
||||
velocityTracker=null;
|
||||
}else if(ev.getAction()==MotionEvent.ACTION_CANCEL){
|
||||
dragging=false;
|
||||
springBack(velocityTracker.getYVelocity());
|
||||
velocityTracker.recycle();
|
||||
velocityTracker=null;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void springBack(float velocityY){
|
||||
SpringAnimation anim=new SpringAnimation(particleAnimContainer, DynamicAnimation.TRANSLATION_Y, 0);
|
||||
anim.getSpring().setStiffness(SpringForce.STIFFNESS_LOW).setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY);
|
||||
anim.setStartVelocity(velocityY);
|
||||
anim.addEndListener((animation, canceled, value, velocity)->springBackAnim=null);
|
||||
anim.addUpdateListener((animation, value, velocity)->{
|
||||
float alpha=1f-Math.abs(particleAnimContainer.getTranslationY())/particleAnimContainer.getHeight();
|
||||
scrim.setAlpha(Math.round(alpha*255));
|
||||
getToolbar().setAlpha(alpha);
|
||||
});
|
||||
springBackAnim=anim;
|
||||
anim.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,7 +151,7 @@ public class ThreadFragment extends StatusListFragment{
|
||||
replyButton.setOnClickListener(v->openReply());
|
||||
Account self=AccountSessionManager.get(accountID).self;
|
||||
if(!TextUtils.isEmpty(self.avatar)){
|
||||
ViewImageLoader.loadWithoutAnimation(replyButtonAva, getResources().getDrawable(R.drawable.image_placeholder), new UrlImageLoaderRequest(self.avatar, V.dp(24), V.dp(24)));
|
||||
ViewImageLoader.loadWithoutAnimation(replyButtonAva, getResources().getDrawable(R.drawable.image_placeholder, getActivity().getTheme()), new UrlImageLoaderRequest(self.avatar, V.dp(24), V.dp(24)));
|
||||
}
|
||||
UiUtils.loadCustomEmojiInTextView(toolbarTitleView);
|
||||
showContent();
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package org.joinmastodon.android.fragments.account_list;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.SearchAccounts;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class AddListMembersFragment extends AccountSearchFragment{
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
dataLoaded();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
refreshing=true;
|
||||
currentRequest=new SearchAccounts(currentQuery, 0, 0, false, true)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Account> result){
|
||||
AddListMembersFragment.this.onSuccess(result);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSearchViewPlaceholder(){
|
||||
return getString(R.string.search_among_people_you_follow);
|
||||
}
|
||||
}
|
||||
@@ -125,41 +125,40 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
|
||||
ArrayList<String> options=Arrays.stream(durationOptions).mapToObj(d->UiUtils.formatDuration(getActivity(), d)).collect(Collectors.toCollection(ArrayList<String>::new));
|
||||
options.add(0, getString(R.string.filter_duration_forever));
|
||||
options.add(getString(R.string.filter_duration_custom));
|
||||
Instant[] newEnd={null};
|
||||
boolean[] isCustom={false};
|
||||
AlertDialog alert=new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.settings_filter_duration_title)
|
||||
.setSupportingText(endsAt==null ? null : getString(R.string.settings_filter_ends, UiUtils.formatRelativeTimestampAsMinutesAgo(getActivity(), endsAt, false)))
|
||||
.setSingleChoiceItems(options.toArray(new String[0]), -1, (dlg, item)->{
|
||||
AlertDialog a=(AlertDialog) dlg;
|
||||
if(item==options.size()-1){ // custom
|
||||
showCustomDurationAlert(isCustom[0] ? newEnd[0] : null, date->{
|
||||
showCustomDurationAlert(null, date->{
|
||||
if(date==null){
|
||||
a.getListView().setItemChecked(item, false);
|
||||
}else{
|
||||
isCustom[0]=true;
|
||||
newEnd[0]=date;
|
||||
a.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
|
||||
Instant newEnd=date;
|
||||
if(!Objects.equals(endsAt, newEnd)){
|
||||
endsAt=newEnd;
|
||||
updateDurationItem();
|
||||
dirty=true;
|
||||
}
|
||||
a.dismiss();
|
||||
}
|
||||
});
|
||||
}else{
|
||||
isCustom[0]=false;
|
||||
Instant newEnd;
|
||||
if(item==0){
|
||||
newEnd[0]=null;
|
||||
newEnd=null;
|
||||
}else{
|
||||
newEnd[0]=Instant.now().plusSeconds(durationOptions[item-1]);
|
||||
newEnd=Instant.now().plusSeconds(durationOptions[item-1]);
|
||||
}
|
||||
a.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
|
||||
if(!Objects.equals(endsAt, newEnd)){
|
||||
endsAt=newEnd;
|
||||
updateDurationItem();
|
||||
dirty=true;
|
||||
}
|
||||
a.dismiss();
|
||||
}
|
||||
})
|
||||
.setPositiveButton(R.string.ok, (dlg, item)->{
|
||||
if(!Objects.equals(endsAt, newEnd[0])){
|
||||
endsAt=newEnd[0];
|
||||
updateDurationItem();
|
||||
dirty=true;
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
alert.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
}
|
||||
|
||||
@@ -37,14 +37,14 @@ import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> implements OnBackPressedListener{
|
||||
private ImageButton fab;
|
||||
private Button fab;
|
||||
private ActionMode actionMode;
|
||||
private ArrayList<ListItem<FilterKeyword>> selectedItems=new ArrayList<>();
|
||||
private ArrayList<String> deletedItemIDs=new ArrayList<>();
|
||||
private MenuItem deleteItem;
|
||||
|
||||
public FilterWordsFragment(){
|
||||
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
||||
setListLayoutId(R.layout.recycler_fragment_with_text_fab);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,18 +92,14 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
fab=view.findViewById(R.id.fab);
|
||||
fab.setImageResource(R.drawable.ic_add_24px);
|
||||
fab.setContentDescription(getString(R.string.add_muted_word));
|
||||
fab.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_add_24px, 0, 0, 0);
|
||||
fab.setText(R.string.add_muted_word_short);
|
||||
fab.setOnClickListener(v->onFabClick());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyWindowInsets(WindowInsets insets){
|
||||
int fabInset=0;
|
||||
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0){
|
||||
fabInset=insets.getSystemWindowInsetBottom();
|
||||
}
|
||||
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(16)+fabInset;
|
||||
UiUtils.applyBottomInsetToFAB(fab, insets);
|
||||
super.onApplyWindowInsets(insets);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
package org.joinmastodon.android.fragments.settings;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
@@ -15,9 +24,12 @@ import org.joinmastodon.android.ui.viewcontrollers.ComposeLanguageAlertViewContr
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class SettingsBehaviorFragment extends BaseSettingsFragment<Void>{
|
||||
private ListItem<Void> languageItem;
|
||||
private CheckableListItem<Void> altTextItem, playGifsItem, customTabsItem, confirmUnfollowItem, confirmBoostItem, confirmDeleteItem;
|
||||
private ListItem<Void> languageItem, customTabsItem;
|
||||
private CheckableListItem<Void> altTextItem, playGifsItem, confirmUnfollowItem, confirmBoostItem, confirmDeleteItem;
|
||||
private Locale postLanguage;
|
||||
private ComposeLanguageAlertViewController.SelectedOption newPostLanguage;
|
||||
|
||||
@@ -33,9 +45,9 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void>{
|
||||
|
||||
onDataLoaded(List.of(
|
||||
languageItem=new ListItem<>(getString(R.string.default_post_language), postLanguage!=null ? postLanguage.getDisplayName(Locale.getDefault()) : null, R.drawable.ic_language_24px, this::onDefaultLanguageClick),
|
||||
customTabsItem=new ListItem<>(R.string.settings_custom_tabs, GlobalUserPreferences.useCustomTabs ? R.string.in_app_browser : R.string.system_browser, R.drawable.ic_open_in_browser_24px, this::onCustomTabsClick),
|
||||
altTextItem=new CheckableListItem<>(R.string.settings_alt_text_reminders, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.altTextReminders, R.drawable.ic_alt_24px, this::toggleCheckableItem),
|
||||
playGifsItem=new CheckableListItem<>(R.string.settings_gif, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.playGifs, R.drawable.ic_animation_24px, this::toggleCheckableItem),
|
||||
customTabsItem=new CheckableListItem<>(R.string.settings_custom_tabs, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.useCustomTabs, R.drawable.ic_open_in_browser_24px, this::toggleCheckableItem),
|
||||
confirmUnfollowItem=new CheckableListItem<>(R.string.settings_confirm_unfollow, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmUnfollow, R.drawable.ic_person_remove_24px, this::toggleCheckableItem),
|
||||
confirmBoostItem=new CheckableListItem<>(R.string.settings_confirm_boost, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmBoost, R.drawable.ic_repeat_24px, this::toggleCheckableItem),
|
||||
confirmDeleteItem=new CheckableListItem<>(R.string.settings_confirm_delete_post, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmDeletePost, R.drawable.ic_delete_24px, this::toggleCheckableItem)
|
||||
@@ -46,19 +58,61 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void>{
|
||||
protected void doLoadData(int offset, int count){}
|
||||
|
||||
private void onDefaultLanguageClick(ListItem<?> item){
|
||||
ComposeLanguageAlertViewController vc=new ComposeLanguageAlertViewController(getActivity(), null, new ComposeLanguageAlertViewController.SelectedOption(-1, postLanguage), null);
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
ComposeLanguageAlertViewController vc=new ComposeLanguageAlertViewController(getActivity(), null, newPostLanguage==null ? new ComposeLanguageAlertViewController.SelectedOption(-1, postLanguage, null) : newPostLanguage, null);
|
||||
AlertDialog dlg=new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.default_post_language)
|
||||
.setView(vc.getView())
|
||||
.setPositiveButton(R.string.ok, (dlg, which)->{
|
||||
ComposeLanguageAlertViewController.SelectedOption opt=vc.getSelectedOption();
|
||||
if(!opt.locale.equals(postLanguage)){
|
||||
newPostLanguage=opt;
|
||||
languageItem.subtitle=newPostLanguage.locale.getDisplayLanguage(Locale.getDefault());
|
||||
rebindItem(languageItem);
|
||||
}
|
||||
.setPositiveButton(R.string.cancel, null)
|
||||
.show();
|
||||
vc.setSelectionListener(opt->{
|
||||
if(!opt.locale.equals(postLanguage)){
|
||||
newPostLanguage=opt;
|
||||
languageItem.subtitle=newPostLanguage.locale.getDisplayLanguage(Locale.getDefault());
|
||||
rebindItem(languageItem);
|
||||
}
|
||||
dlg.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
private void onCustomTabsClick(ListItem<?> item){
|
||||
// GlobalUserPreferences.useCustomTabs=customTabsItem.checked;
|
||||
Intent intent=new Intent(Intent.ACTION_VIEW, Uri.parse("http://example.com"));
|
||||
ResolveInfo info=getActivity().getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
final String browserName;
|
||||
if(info==null){
|
||||
browserName="??";
|
||||
}else{
|
||||
browserName=info.loadLabel(getActivity().getPackageManager()).toString();
|
||||
}
|
||||
ArrayAdapter<CharSequence> adapter=new ArrayAdapter<>(getActivity(), R.layout.item_alert_single_choice_2lines_but_different, R.id.text,
|
||||
new String[]{getString(R.string.in_app_browser), getString(R.string.system_browser)}){
|
||||
@Override
|
||||
public boolean hasStableIds(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent){
|
||||
View view=super.getView(position, convertView, parent);
|
||||
TextView subtitle=view.findViewById(R.id.subtitle);
|
||||
if(position==0){
|
||||
subtitle.setVisibility(View.GONE);
|
||||
}else{
|
||||
subtitle.setVisibility(View.VISIBLE);
|
||||
subtitle.setText(browserName);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
};
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.settings_custom_tabs)
|
||||
.setSingleChoiceItems(adapter, GlobalUserPreferences.useCustomTabs ? 0 : 1, (dlg, which)->{
|
||||
GlobalUserPreferences.useCustomTabs=which==0;
|
||||
customTabsItem.subtitleRes=GlobalUserPreferences.useCustomTabs ? R.string.in_app_browser : R.string.system_browser;
|
||||
rebindItem(customTabsItem);
|
||||
dlg.dismiss();
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
@@ -66,9 +120,8 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void>{
|
||||
protected void onHidden(){
|
||||
super.onHidden();
|
||||
GlobalUserPreferences.playGifs=playGifsItem.checked;
|
||||
GlobalUserPreferences.useCustomTabs=customTabsItem.checked;
|
||||
GlobalUserPreferences.altTextReminders=altTextItem.checked;
|
||||
GlobalUserPreferences.confirmUnfollow=customTabsItem.checked;
|
||||
GlobalUserPreferences.confirmUnfollow=confirmUnfollowItem.checked;
|
||||
GlobalUserPreferences.confirmBoost=confirmBoostItem.checked;
|
||||
GlobalUserPreferences.confirmDeletePost=confirmDeleteItem.checked;
|
||||
GlobalUserPreferences.save();
|
||||
|
||||
@@ -86,28 +86,26 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
|
||||
case DARK -> 1;
|
||||
case AUTO -> 2;
|
||||
};
|
||||
int[] newSelected={selected};
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.settings_theme)
|
||||
.setSingleChoiceItems((String[])IntStream.of(R.string.theme_light, R.string.theme_dark, R.string.theme_auto).mapToObj(this::getString).toArray(String[]::new),
|
||||
selected, (dlg, item)->newSelected[0]=item)
|
||||
.setPositiveButton(R.string.ok, (dlg, item)->{
|
||||
GlobalUserPreferences.ThemePreference pref=switch(newSelected[0]){
|
||||
case 0 -> GlobalUserPreferences.ThemePreference.LIGHT;
|
||||
case 1 -> GlobalUserPreferences.ThemePreference.DARK;
|
||||
case 2 -> GlobalUserPreferences.ThemePreference.AUTO;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+newSelected[0]);
|
||||
};
|
||||
if(pref!=GlobalUserPreferences.theme){
|
||||
GlobalUserPreferences.ThemePreference prev=GlobalUserPreferences.theme;
|
||||
GlobalUserPreferences.theme=pref;
|
||||
GlobalUserPreferences.save();
|
||||
themeItem.subtitleRes=getAppearanceValue();
|
||||
rebindItem(themeItem);
|
||||
maybeApplyNewThemeRightNow(prev);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
selected, (dlg, item)->{
|
||||
GlobalUserPreferences.ThemePreference pref=switch(item){
|
||||
case 0 -> GlobalUserPreferences.ThemePreference.LIGHT;
|
||||
case 1 -> GlobalUserPreferences.ThemePreference.DARK;
|
||||
case 2 -> GlobalUserPreferences.ThemePreference.AUTO;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+item);
|
||||
};
|
||||
if(pref!=GlobalUserPreferences.theme){
|
||||
GlobalUserPreferences.ThemePreference prev=GlobalUserPreferences.theme;
|
||||
GlobalUserPreferences.theme=pref;
|
||||
GlobalUserPreferences.save();
|
||||
themeItem.subtitleRes=getAppearanceValue();
|
||||
rebindItem(themeItem);
|
||||
maybeApplyNewThemeRightNow(prev);
|
||||
}
|
||||
dlg.dismiss();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package org.joinmastodon.android.fragments.settings;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.Button;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
@@ -12,6 +15,7 @@ import org.joinmastodon.android.events.SettingsFilterDeletedEvent;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||
import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -24,6 +28,12 @@ import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
|
||||
public class SettingsFiltersFragment extends BaseSettingsFragment<Filter>{
|
||||
private Button fab;
|
||||
|
||||
public SettingsFiltersFragment(){
|
||||
setListLayoutId(R.layout.recycler_fragment_with_text_fab);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -44,6 +54,8 @@ public class SettingsFiltersFragment extends BaseSettingsFragment<Filter>{
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Filter> result){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
onDataLoaded(result.stream().map(f->makeListItem(f)).collect(Collectors.toList()));
|
||||
}
|
||||
})
|
||||
@@ -51,13 +63,12 @@ public class SettingsFiltersFragment extends BaseSettingsFragment<Filter>{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter<?> getAdapter(){
|
||||
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
|
||||
adapter.addAdapter(super.getAdapter());
|
||||
adapter.addAdapter(new GenericListItemsAdapter<>(Collections.singletonList(
|
||||
new ListItem<Void>(R.string.settings_add_filter, 0, R.drawable.ic_add_24px, this::onAddFilterClick)
|
||||
)));
|
||||
return adapter;
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
fab=view.findViewById(R.id.fab);
|
||||
fab.setText(R.string.settings_add_filter);
|
||||
fab.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_add_24px, 0, 0, 0);
|
||||
fab.setOnClickListener(v->onAddFilterClick());
|
||||
}
|
||||
|
||||
private void onFilterClick(ListItem<Filter> filter){
|
||||
@@ -67,7 +78,7 @@ public class SettingsFiltersFragment extends BaseSettingsFragment<Filter>{
|
||||
Nav.go(getActivity(), EditFilterFragment.class, args);
|
||||
}
|
||||
|
||||
private void onAddFilterClick(ListItem<?> item){
|
||||
private void onAddFilterClick(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), EditFilterFragment.class, args);
|
||||
@@ -107,4 +118,10 @@ public class SettingsFiltersFragment extends BaseSettingsFragment<Filter>{
|
||||
data.add(makeListItem(ev.filter));
|
||||
itemsAdapter.notifyItemInserted(data.size()-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyWindowInsets(WindowInsets insets){
|
||||
UiUtils.applyBottomInsetToFAB(fab, insets);
|
||||
super.onApplyWindowInsets(insets);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,18 +192,13 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
|
||||
3*24*3600,
|
||||
7*24*3600
|
||||
};
|
||||
int[] selectedOption={0};
|
||||
AlertDialog alert=new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.pause_all_notifications_title)
|
||||
.setSupportingText(time>System.currentTimeMillis() ? getString(R.string.pause_notifications_ends, UiUtils.formatRelativeTimestampAsMinutesAgo(getActivity(), Instant.ofEpochMilli(time), false)) : null)
|
||||
.setSingleChoiceItems((String[])Arrays.stream(durationOptions).mapToObj(d->UiUtils.formatDuration(getActivity(), d)).toArray(String[]::new), -1, (dlg, item)->{
|
||||
if(selectedOption[0]==0){
|
||||
((AlertDialog)dlg).getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
|
||||
}
|
||||
selectedOption[0]=durationOptions[item];
|
||||
AccountSessionManager.get(accountID).getLocalPreferences().setNotificationsPauseEndTime(System.currentTimeMillis()+durationOptions[item]*1000L);
|
||||
dlg.dismiss();
|
||||
})
|
||||
.setPositiveButton(R.string.ok, (dlg, item)->AccountSessionManager.get(accountID).getLocalPreferences().setNotificationsPauseEndTime(System.currentTimeMillis()+selectedOption[0]*1000L))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
alert.setOnDismissListener(dialog->updatePauseItem());
|
||||
alert.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
@@ -216,20 +211,18 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
|
||||
R.string.notifications_policy_follower,
|
||||
R.string.notifications_policy_no_one
|
||||
).map(this::getString).toArray(String[]::new);
|
||||
int[] selectedItem={getPushSubscription().policy.ordinal()};
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.settings_notifications_policy)
|
||||
.setSingleChoiceItems(items, selectedItem[0], (dlg, which)->selectedItem[0]=which)
|
||||
.setPositiveButton(R.string.ok, (dlg, which)->{
|
||||
.setSingleChoiceItems(items, getPushSubscription().policy.ordinal(), (dlg, which)->{
|
||||
dlg.dismiss();
|
||||
PushSubscription.Policy prevValue=getPushSubscription().policy;
|
||||
PushSubscription.Policy newValue=PushSubscription.Policy.values()[selectedItem[0]];
|
||||
PushSubscription.Policy newValue=PushSubscription.Policy.values()[which];
|
||||
if(prevValue==newValue)
|
||||
return;
|
||||
getPushSubscription().policy=newValue;
|
||||
updatePolicyItem(prevValue);
|
||||
needUpdateNotificationSettings=true;
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.joinmastodon.android.googleservices;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
import org.microg.safeparcel.SafeParceled;
|
||||
|
||||
public class ConnectionResult extends AutoSafeParcelable{
|
||||
public static final int UNKNOWN = -1;
|
||||
public static final int SUCCESS = 0;
|
||||
public static final int SERVICE_MISSING = 1;
|
||||
public static final int SERVICE_VERSION_UPDATE_REQUIRED = 2;
|
||||
public static final int SERVICE_DISABLED = 3;
|
||||
public static final int SIGN_IN_REQUIRED = 4;
|
||||
public static final int INVALID_ACCOUNT = 5;
|
||||
public static final int RESOLUTION_REQUIRED = 6;
|
||||
public static final int NETWORK_ERROR = 7;
|
||||
public static final int INTERNAL_ERROR = 8;
|
||||
public static final int SERVICE_INVALID = 9;
|
||||
public static final int DEVELOPER_ERROR = 10;
|
||||
public static final int LICENSE_CHECK_FAILED = 11;
|
||||
public static final int CANCELED = 13;
|
||||
public static final int TIMEOUT = 14;
|
||||
public static final int INTERRUPTED = 15;
|
||||
public static final int API_UNAVAILABLE = 16;
|
||||
public static final int SIGN_IN_FAILED = 17;
|
||||
public static final int SERVICE_UPDATING = 18;
|
||||
public static final int SERVICE_MISSING_PERMISSION = 19;
|
||||
public static final int RESTRICTED_PROFILE = 20;
|
||||
public static final int RESOLUTION_ACTIVITY_NOT_FOUND = 22;
|
||||
public static final int API_DISABLED = 23;
|
||||
public static final int API_DISABLED_FOR_CONNECTION = 24;
|
||||
@Deprecated
|
||||
public static final int DRIVE_EXTERNAL_STORAGE_REQUIRED = 1500;
|
||||
|
||||
|
||||
@SafeParceled(1)
|
||||
public int versionCode;
|
||||
@SafeParceled(2)
|
||||
public int errorCode;
|
||||
@SafeParceled(3)
|
||||
public PendingIntent resolution;
|
||||
@SafeParceled(4)
|
||||
public String errorMessage;
|
||||
|
||||
public static final Creator<ConnectionResult> CREATOR=new AutoCreator<>(ConnectionResult.class);
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package org.joinmastodon.android.googleservices;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.IInterface;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.google.android.gms.common.internal.ConnectionInfo;
|
||||
import com.google.android.gms.common.internal.GetServiceRequest;
|
||||
import com.google.android.gms.common.internal.IGmsCallbacks;
|
||||
import com.google.android.gms.common.internal.IGmsServiceBroker;
|
||||
import com.google.android.gms.common.moduleinstall.internal.IModuleInstallService;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public class GmsClient{
|
||||
private static final String TAG="GmsClient";
|
||||
private static final SparseArray<ServiceConnection> currentConnections=new SparseArray<>();
|
||||
private static int nextConnectionID=0;
|
||||
|
||||
public static <I extends IInterface> void connectToService(Context context, String action, int id, boolean useDynamicLookup, ServiceConnectionCallback<I> callback, Function<IBinder, I> asInterface){
|
||||
Intent intent;
|
||||
if(useDynamicLookup){
|
||||
try{
|
||||
Bundle args=new Bundle();
|
||||
args.putString("serviceActionBundleKey", action);
|
||||
Bundle result=context.getContentResolver().call(new Uri.Builder().scheme("content").authority("com.google.android.gms.chimera").build(), "serviceIntentCall", null, args);
|
||||
if(result==null)
|
||||
throw new IllegalStateException("Dynamic lookup failed");
|
||||
intent=result.getParcelable("serviceResponseIntentKey");
|
||||
if(intent==null)
|
||||
throw new IllegalStateException("Dynamic lookup returned null");
|
||||
}catch(Exception x){
|
||||
callback.onError(x);
|
||||
return;
|
||||
}
|
||||
}else{
|
||||
intent=new Intent(action);
|
||||
}
|
||||
intent.setPackage("com.google.android.gms");
|
||||
ServiceConnection conn=new ServiceConnection(){
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service){
|
||||
IGmsServiceBroker broker=IGmsServiceBroker.Stub.asInterface(service);
|
||||
GetServiceRequest req=new GetServiceRequest();
|
||||
req.serviceId=id;
|
||||
req.packageName=context.getPackageName();
|
||||
ServiceConnection serviceConnectionThis=this;
|
||||
try{
|
||||
broker.getService(new IGmsCallbacks.Stub(){
|
||||
@Override
|
||||
public void onPostInitComplete(int statusCode, IBinder binder, Bundle params) throws RemoteException{
|
||||
int connectionID=nextConnectionID++;
|
||||
currentConnections.put(connectionID, serviceConnectionThis);
|
||||
callback.onSuccess(asInterface.apply(binder), connectionID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccountValidationComplete(int statusCode, Bundle params) throws RemoteException{}
|
||||
|
||||
@Override
|
||||
public void onPostInitCompleteWithConnectionInfo(int statusCode, IBinder binder, ConnectionInfo info) throws RemoteException{
|
||||
onPostInitComplete(statusCode, binder, info!=null ? info.params : null);
|
||||
}
|
||||
}, req);
|
||||
}catch(Exception x){
|
||||
callback.onError(x);
|
||||
context.unbindService(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name){}
|
||||
};
|
||||
boolean res=context.bindService(intent, conn, Context.BIND_AUTO_CREATE | Context.BIND_DEBUG_UNBIND | Context.BIND_ADJUST_WITH_ACTIVITY);
|
||||
if(!res){
|
||||
context.unbindService(conn);
|
||||
callback.onError(new IllegalStateException("Service connection failed"));
|
||||
}
|
||||
}
|
||||
|
||||
public static void disconnectFromService(Context context, int connectionID){
|
||||
ServiceConnection conn=currentConnections.get(connectionID);
|
||||
if(conn!=null){
|
||||
currentConnections.remove(connectionID);
|
||||
context.unbindService(conn);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isGooglePlayServicesAvailable(Context context){
|
||||
PackageManager pm=context.getPackageManager();
|
||||
try{
|
||||
pm.getPackageInfo("com.google.android.gms", 0);
|
||||
return true;
|
||||
}catch(PackageManager.NameNotFoundException e){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void getModuleInstallerService(Context context, ServiceConnectionCallback<IModuleInstallService> callback){
|
||||
connectToService(context, "com.google.android.gms.chimera.container.moduleinstall.ModuleInstallService.START", 308, true, callback, IModuleInstallService.Stub::asInterface);
|
||||
}
|
||||
|
||||
public interface ServiceConnectionCallback<I extends IInterface>{
|
||||
void onSuccess(I service, int connectionID);
|
||||
void onError(Exception error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
package org.joinmastodon.android.googleservices.barcodescanner;
|
||||
|
||||
import android.graphics.Point;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
import org.microg.safeparcel.SafeParceled;
|
||||
|
||||
public class Barcode extends AutoSafeParcelable{
|
||||
public static final int FORMAT_UNKNOWN = -1;
|
||||
public static final int FORMAT_ALL_FORMATS = 0;
|
||||
public static final int FORMAT_CODE_128 = 1;
|
||||
public static final int FORMAT_CODE_39 = 2;
|
||||
public static final int FORMAT_CODE_93 = 4;
|
||||
public static final int FORMAT_CODABAR = 8;
|
||||
public static final int FORMAT_DATA_MATRIX = 16;
|
||||
public static final int FORMAT_EAN_13 = 32;
|
||||
public static final int FORMAT_EAN_8 = 64;
|
||||
public static final int FORMAT_ITF = 128;
|
||||
public static final int FORMAT_QR_CODE = 256;
|
||||
public static final int FORMAT_UPC_A = 512;
|
||||
public static final int FORMAT_UPC_E = 1024;
|
||||
public static final int FORMAT_PDF417 = 2048;
|
||||
public static final int FORMAT_AZTEC = 4096;
|
||||
public static final int TYPE_UNKNOWN = 0;
|
||||
public static final int TYPE_CONTACT_INFO = 1;
|
||||
public static final int TYPE_EMAIL = 2;
|
||||
public static final int TYPE_ISBN = 3;
|
||||
public static final int TYPE_PHONE = 4;
|
||||
public static final int TYPE_PRODUCT = 5;
|
||||
public static final int TYPE_SMS = 6;
|
||||
public static final int TYPE_TEXT = 7;
|
||||
public static final int TYPE_URL = 8;
|
||||
public static final int TYPE_WIFI = 9;
|
||||
public static final int TYPE_GEO = 10;
|
||||
public static final int TYPE_CALENDAR_EVENT = 11;
|
||||
public static final int TYPE_DRIVER_LICENSE = 12;
|
||||
|
||||
@SafeParceled(1)
|
||||
public int format;
|
||||
@SafeParceled(2)
|
||||
public String displayValue;
|
||||
@SafeParceled(3)
|
||||
public String rawValue;
|
||||
@SafeParceled(4)
|
||||
public byte[] rawBytes;
|
||||
@SafeParceled(5)
|
||||
public Point[] cornerPoints;
|
||||
@SafeParceled(6)
|
||||
public int valueType;
|
||||
@SafeParceled(7)
|
||||
public Email emailValue;
|
||||
@SafeParceled(8)
|
||||
public Phone phoneValue;
|
||||
@SafeParceled(9)
|
||||
public SMS smsValue;
|
||||
@SafeParceled(10)
|
||||
public WiFi wifiValue;
|
||||
@SafeParceled(11)
|
||||
public UrlBookmark urlBookmarkValue;
|
||||
@SafeParceled(12)
|
||||
public GeoPoint geoPointValue;
|
||||
@SafeParceled(13)
|
||||
public CalendarEvent calendarEventValue;
|
||||
@SafeParceled(14)
|
||||
public ContactInfo contactInfoValue;
|
||||
@SafeParceled(15)
|
||||
public DriverLicense driverLicenseValue;
|
||||
|
||||
public static final Creator<Barcode> CREATOR=new AutoCreator<>(Barcode.class);
|
||||
|
||||
// None of the following is needed or used in the Mastodon app and its use cases for QR code scanning,
|
||||
// but I'm putting it out there in case someone else is crazy enough to want to use Google Services without their libraries
|
||||
|
||||
public static class Email extends AutoSafeParcelable{
|
||||
@SafeParceled(1)
|
||||
public int type;
|
||||
@SafeParceled(2)
|
||||
public String address;
|
||||
@SafeParceled(3)
|
||||
public String subject;
|
||||
@SafeParceled(4)
|
||||
public String body;
|
||||
|
||||
public static final Creator<Email> CREATOR=new AutoCreator<>(Email.class);
|
||||
}
|
||||
|
||||
public static class Phone extends AutoSafeParcelable{
|
||||
@SafeParceled(1)
|
||||
public int type;
|
||||
@SafeParceled(2)
|
||||
public String number;
|
||||
|
||||
public static final Creator<Phone> CREATOR=new AutoCreator<>(Phone.class);
|
||||
}
|
||||
|
||||
public static class SMS extends AutoSafeParcelable{
|
||||
@SafeParceled(1)
|
||||
public String message;
|
||||
@SafeParceled(2)
|
||||
public String phoneNumber;
|
||||
|
||||
public static final Creator<SMS> CREATOR=new AutoCreator<>(SMS.class);
|
||||
}
|
||||
|
||||
public static class WiFi extends AutoSafeParcelable{
|
||||
@SafeParceled(1)
|
||||
public String ssid;
|
||||
@SafeParceled(2)
|
||||
public String password;
|
||||
@SafeParceled(3)
|
||||
public int encryptionType;
|
||||
|
||||
public static final Creator<WiFi> CREATOR=new AutoCreator<>(WiFi.class);
|
||||
}
|
||||
|
||||
public static class UrlBookmark extends AutoSafeParcelable{
|
||||
@SafeParceled(1)
|
||||
public String title;
|
||||
@SafeParceled(2)
|
||||
public String url;
|
||||
|
||||
public static final Creator<UrlBookmark> CREATOR=new AutoCreator<>(UrlBookmark.class);
|
||||
}
|
||||
|
||||
public static class GeoPoint extends AutoSafeParcelable{
|
||||
@SafeParceled(1)
|
||||
public double lat;
|
||||
@SafeParceled(2)
|
||||
public double lng;
|
||||
|
||||
public static final Creator<GeoPoint> CREATOR=new AutoCreator<>(GeoPoint.class);
|
||||
}
|
||||
|
||||
public static class EventDateTime extends AutoSafeParcelable{
|
||||
@SafeParceled(1)
|
||||
public int year;
|
||||
@SafeParceled(2)
|
||||
public int month;
|
||||
@SafeParceled(3)
|
||||
public int day;
|
||||
@SafeParceled(4)
|
||||
public int hours;
|
||||
@SafeParceled(5)
|
||||
public int minutes;
|
||||
@SafeParceled(6)
|
||||
public int seconds;
|
||||
@SafeParceled(7)
|
||||
public boolean isUtc;
|
||||
@SafeParceled(8)
|
||||
public String rawValue;
|
||||
|
||||
public static final Creator<EventDateTime> CREATOR=new AutoCreator<>(EventDateTime.class);
|
||||
}
|
||||
|
||||
public static class CalendarEvent extends AutoSafeParcelable{
|
||||
@SafeParceled(1)
|
||||
public String summary;
|
||||
@SafeParceled(2)
|
||||
public String description;
|
||||
@SafeParceled(3)
|
||||
public String location;
|
||||
@SafeParceled(4)
|
||||
public String organizer;
|
||||
@SafeParceled(5)
|
||||
public String status;
|
||||
@SafeParceled(6)
|
||||
public EventDateTime start;
|
||||
@SafeParceled(7)
|
||||
public EventDateTime end;
|
||||
|
||||
public static final Creator<CalendarEvent> CREATOR=new AutoCreator<>(CalendarEvent.class);
|
||||
}
|
||||
|
||||
public static class Address extends AutoSafeParcelable{
|
||||
@SafeParceled(1)
|
||||
public int type;
|
||||
@SafeParceled(2)
|
||||
public String[] addressLines;
|
||||
|
||||
public static final Creator<Address> CREATOR=new AutoCreator<>(Address.class);
|
||||
}
|
||||
|
||||
public static class PersonName extends AutoSafeParcelable{
|
||||
@SafeParceled(1)
|
||||
public String formattedName;
|
||||
@SafeParceled(2)
|
||||
public String pronunciation;
|
||||
@SafeParceled(3)
|
||||
public String prefix;
|
||||
@SafeParceled(4)
|
||||
public String first;
|
||||
@SafeParceled(5)
|
||||
public String middle;
|
||||
@SafeParceled(6)
|
||||
public String last;
|
||||
@SafeParceled(7)
|
||||
public String suffix;
|
||||
|
||||
public static final Creator<PersonName> CREATOR=new AutoCreator<>(PersonName.class);
|
||||
}
|
||||
|
||||
public static class ContactInfo extends AutoSafeParcelable{
|
||||
@SafeParceled(1)
|
||||
public PersonName name;
|
||||
@SafeParceled(2)
|
||||
public String organization;
|
||||
@SafeParceled(3)
|
||||
public String title;
|
||||
@SafeParceled(4)
|
||||
public Phone[] phones;
|
||||
@SafeParceled(5)
|
||||
public Email[] emails;
|
||||
@SafeParceled(6)
|
||||
public String[] urls;
|
||||
@SafeParceled(7)
|
||||
public Address[] addresses;
|
||||
|
||||
public static final Creator<ContactInfo> CREATOR=new AutoCreator<>(ContactInfo.class);
|
||||
}
|
||||
|
||||
public static class DriverLicense extends AutoSafeParcelable{
|
||||
@SafeParceled(1)
|
||||
public String documentType;
|
||||
@SafeParceled(2)
|
||||
public String firstName;
|
||||
@SafeParceled(3)
|
||||
public String middleName;
|
||||
@SafeParceled(4)
|
||||
public String lastName;
|
||||
@SafeParceled(5)
|
||||
public String gender;
|
||||
@SafeParceled(6)
|
||||
public String addressStreet;
|
||||
@SafeParceled(7)
|
||||
public String addressCity;
|
||||
@SafeParceled(8)
|
||||
public String addressState;
|
||||
@SafeParceled(9)
|
||||
public String addressZip;
|
||||
@SafeParceled(10)
|
||||
public String licenseNumber;
|
||||
@SafeParceled(11)
|
||||
public String issueDate;
|
||||
@SafeParceled(12)
|
||||
public String expiryDate;
|
||||
@SafeParceled(13)
|
||||
public String birthDate;
|
||||
@SafeParceled(14)
|
||||
public String issuingCountry;
|
||||
|
||||
public static final Creator<DriverLicense> CREATOR=new AutoCreator<>(DriverLicense.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.joinmastodon.android.googleservices.barcodescanner;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.os.Parcel;
|
||||
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
|
||||
public class BarcodeScanner{
|
||||
public static Intent createIntent(int formats, boolean allowManualInout, boolean enableAutoZoom){
|
||||
Intent intent=new Intent().setPackage("com.google.android.gms").setAction("com.google.android.gms.mlkit.ACTION_SCAN_BARCODE");
|
||||
String appName;
|
||||
ApplicationInfo appInfo=MastodonApp.context.getApplicationInfo();
|
||||
if(appInfo.labelRes!=0)
|
||||
appName=MastodonApp.context.getString(appInfo.labelRes);
|
||||
else
|
||||
appName=MastodonApp.context.getPackageManager().getApplicationLabel(appInfo).toString();
|
||||
intent.putExtra("extra_calling_app_name", appName);
|
||||
intent.putExtra("extra_supported_formats", formats);
|
||||
intent.putExtra("extra_allow_manual_input", allowManualInout);
|
||||
intent.putExtra("extra_enable_auto_zoom", enableAutoZoom);
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static boolean isValidResult(Intent intent){
|
||||
return intent!=null && intent.hasExtra("extra_barcode_result");
|
||||
}
|
||||
|
||||
public static Barcode getResult(Intent intent){
|
||||
byte[] serialized=intent.getByteArrayExtra("extra_barcode_result");
|
||||
Parcel parcel=Parcel.obtain();
|
||||
parcel.unmarshall(serialized, 0, serialized.length);
|
||||
parcel.setDataPosition(0);
|
||||
Barcode barcode=Barcode.CREATOR.createFromParcel(parcel);
|
||||
parcel.recycle();
|
||||
return barcode;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
@@ -59,6 +60,12 @@ public class M3AlertDialogBuilder extends AlertDialog.Builder{
|
||||
buttonBar.setPadding(V.dp(16), V.dp(16), V.dp(16), V.dp(16));
|
||||
((View)buttonBar.getParent()).setPadding(0, 0, 0, 0);
|
||||
}
|
||||
if(btn==null || btn.getVisibility()==View.GONE){
|
||||
ListView list=alert.getListView();
|
||||
if(list!=null){
|
||||
list.setPadding(list.getPaddingLeft(), list.getPaddingTop(), list.getPaddingRight(), V.dp(24));
|
||||
}
|
||||
}
|
||||
// hacc
|
||||
int titleID=getContext().getResources().getIdentifier("title_template", "id", "android");
|
||||
if(titleID!=0){
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.joinmastodon.android.ui;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Build;
|
||||
import android.util.Property;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
public class ViewProperties{
|
||||
public static final Property<TextView, Integer> FONT_WEIGHT=new Property<>(Integer.class, "fontWeight"){
|
||||
@RequiresApi(api = Build.VERSION_CODES.P)
|
||||
@Override
|
||||
public Integer get(TextView object){
|
||||
return object.getTypeface().getWeight();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.P)
|
||||
@Override
|
||||
public void set(TextView object, Integer value){
|
||||
// typeface objects are cached internally, I looked at AOSP sources to confirm that
|
||||
object.setTypeface(Typeface.create(null, value, false));
|
||||
}
|
||||
};
|
||||
|
||||
public static final Property<TextView, Integer> TEXT_COLOR=new Property<>(Integer.class, "textColor"){
|
||||
@Override
|
||||
public Integer get(TextView object){
|
||||
return object.getCurrentTextColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(TextView object, Integer value){
|
||||
object.setTextColor(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -169,7 +169,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
private void onShareClick(View v){
|
||||
UiUtils.openSystemShareSheet(v.getContext(), item.status.url);
|
||||
UiUtils.openSystemShareSheet(v.getContext(), item.status);
|
||||
}
|
||||
|
||||
private int descriptionForId(int id){
|
||||
|
||||
@@ -197,7 +197,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
}else if(id==R.id.bookmark){
|
||||
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setBookmarked(item.status, !item.status.bookmarked);
|
||||
}else if(id==R.id.share){
|
||||
UiUtils.openSystemShareSheet(activity, item.status.url);
|
||||
UiUtils.openSystemShareSheet(activity, item.status);
|
||||
}else if(id==R.id.translate){
|
||||
item.parentFragment.togglePostTranslation(item.status, item.parentID);
|
||||
}else if(id==R.id.add_to_list){
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package org.joinmastodon.android.ui.drawables;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class FancyQrCodeDrawable extends Drawable{
|
||||
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private Path path=new Path(), scaledPath=new Path();
|
||||
private int size, logoOffset, logoSize;
|
||||
private Drawable logo;
|
||||
|
||||
public FancyQrCodeDrawable(BitMatrix code, int color, Drawable logo){
|
||||
paint.setColor(color);
|
||||
this.logo=logo;
|
||||
size=code.getWidth();
|
||||
addMarker(0, 0);
|
||||
addMarker(size-7, 0);
|
||||
addMarker(0, size-7);
|
||||
float[] radii=new float[8];
|
||||
logoSize=size/3;
|
||||
if((size-logoSize)%2!=0){
|
||||
logoSize--;
|
||||
}
|
||||
logoOffset=(size-logoSize)/2;
|
||||
for(int y=0;y<logoSize;y++){
|
||||
for(int x=0;x<logoSize;x++){
|
||||
code.unset(x+logoOffset, y+logoOffset);
|
||||
}
|
||||
}
|
||||
for(int y=0;y<size;y++){
|
||||
for(int x=0;x<size;x++){
|
||||
// Skip corner markers because they turn out ugly with this algorithm
|
||||
if((x<7 && y<7) || (x>size-8 && y<7) || (x<7 && y>size-8)){
|
||||
continue;
|
||||
}
|
||||
|
||||
if(code.get(x, y)){
|
||||
boolean t=y>0 && code.get(x, y-1);
|
||||
boolean b=y<size-1 && code.get(x, y+1);
|
||||
boolean l=x>0 && code.get(x-1, y);
|
||||
boolean r=x<size-1 && code.get(x+1, y);
|
||||
int neighborCount=(l ? 1 : 0)+(t ? 1 : 0)+(r ? 1 : 0)+(b ? 1 : 0);
|
||||
// Special-case optimizations
|
||||
if(neighborCount>=3 || (neighborCount==2 && ((l && r) || (t && b)))){ // 3 or 4 neighbors, or part of a straight line
|
||||
path.addRect(x, y, x+1, y+1, Path.Direction.CW);
|
||||
continue;
|
||||
}else if(neighborCount==0){ // No neighbors
|
||||
path.addCircle(x+0.5f, y+0.5f, 0.5f, Path.Direction.CW);
|
||||
continue;
|
||||
}
|
||||
Arrays.fill(radii, 0);
|
||||
if(l && t){ // round bottom-right corner
|
||||
radii[4]=radii[5]=1;
|
||||
}else if(t && r){ // round bottom-left corner
|
||||
radii[6]=radii[7]=1;
|
||||
}else if(r && b){ // round top-left corner
|
||||
radii[0]=radii[1]=1;
|
||||
}else if(b && l){ // round top-right corner
|
||||
radii[2]=radii[3]=1;
|
||||
}else if(l){ // right side
|
||||
radii[2]=radii[3]=radii[4]=radii[5]=0.5f;
|
||||
}else if(t){ // bottom side
|
||||
radii[4]=radii[5]=radii[6]=radii[7]=0.5f;
|
||||
}else if(r){ // left side
|
||||
radii[6]=radii[7]=radii[1]=radii[0]=0.5f;
|
||||
}else{ // top side
|
||||
radii[0]=radii[1]=radii[2]=radii[3]=0.5f;
|
||||
}
|
||||
path.addRoundRect(x, y, x+1, y+1, radii, Path.Direction.CW);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addMarker(int x, int y){
|
||||
path.addRoundRect(x, y, x+7, y+7, 2.38f, 2.38f, Path.Direction.CW);
|
||||
path.addRoundRect(x+1, y+1, x+6, y+6, 1.33f, 1.33f, Path.Direction.CCW);
|
||||
path.addRoundRect(x+2, y+2, x+5, y+5, 0.8f, 0.8f, Path.Direction.CW);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas){
|
||||
Rect bounds=getBounds();
|
||||
float factor=Math.min(bounds.width(), bounds.height())/(float)size;
|
||||
float xOff=0, yOff=0;
|
||||
float bw=bounds.width(), bh=bounds.height();
|
||||
if(bw>bh){
|
||||
xOff=bw/2f-bh/2f;
|
||||
}else if(bw<bh){
|
||||
yOff=bh/2f-bw/2f;
|
||||
}
|
||||
canvas.save();
|
||||
canvas.translate(-bounds.left+xOff, -bounds.top+yOff);
|
||||
canvas.drawPath(scaledPath, paint);
|
||||
int scaledOffset=Math.round((logoOffset+1)*factor);
|
||||
int scaledSize=Math.round((logoSize-2)*factor);
|
||||
logo.setBounds(scaledOffset, scaledOffset, scaledOffset+scaledSize, scaledOffset+scaledSize);
|
||||
logo.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity(){
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth(){
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight(){
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(@NonNull Rect bounds){
|
||||
super.onBoundsChange(bounds);
|
||||
float factor=Math.min(bounds.width(), bounds.height())/(float)size;
|
||||
scaledPath.rewind();
|
||||
Matrix matrix=new Matrix();
|
||||
matrix.setScale(factor, factor);
|
||||
scaledPath.addPath(path, matrix);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package org.joinmastodon.android.ui.drawables;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class RadialParticleSystemDrawable extends Drawable{
|
||||
private long particleLifetime;
|
||||
private int birthRate;
|
||||
private int startColor, endColor;
|
||||
private float velocity, velocityVariance;
|
||||
private float size;
|
||||
private ArrayList<Particle> activeParticles=new ArrayList<>(), nextActiveParticles=new ArrayList<>(), pool=new ArrayList<>();
|
||||
private int emitterX, emitterY;
|
||||
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private float[] linearStartColor, linearEndColor;
|
||||
private long prevFrameTime;
|
||||
private Random rand=new Random();
|
||||
private Rect clipOutBounds=new Rect();
|
||||
|
||||
public RadialParticleSystemDrawable(long particleLifetime, int birthRate, int startColor, int endColor, float velocity, float velocityVariance, float size){
|
||||
this.particleLifetime=particleLifetime;
|
||||
this.birthRate=birthRate;
|
||||
this.startColor=startColor;
|
||||
this.endColor=endColor;
|
||||
this.velocity=velocity;
|
||||
this.velocityVariance=velocityVariance;
|
||||
this.size=size;
|
||||
|
||||
linearStartColor=new float[]{
|
||||
((startColor >> 24) & 0xFF)/255f,
|
||||
(float)Math.pow(((startColor >> 16) & 0xFF)/255f, 2.2),
|
||||
(float)Math.pow(((startColor >> 8) & 0xFF)/255f, 2.2),
|
||||
(float)Math.pow((startColor & 0xFF)/255f, 2.2)
|
||||
};
|
||||
linearEndColor=new float[]{
|
||||
((endColor >> 24) & 0xFF)/255f,
|
||||
(float)Math.pow(((endColor >> 16) & 0xFF)/255f, 2.2),
|
||||
(float)Math.pow(((endColor >> 8) & 0xFF)/255f, 2.2),
|
||||
(float)Math.pow((endColor & 0xFF)/255f, 2.2)
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas){
|
||||
long now=SystemClock.uptimeMillis();
|
||||
nextActiveParticles.clear();
|
||||
for(Particle p:activeParticles){
|
||||
int time=(int)(now-p.birthTime);
|
||||
if(time>particleLifetime){
|
||||
pool.add(p);
|
||||
continue;
|
||||
}
|
||||
nextActiveParticles.add(p);
|
||||
float x=emitterX+time/1000f*p.velX;
|
||||
float y=emitterY+time/1000f*p.velY;
|
||||
if(clipOutBounds.contains((int)x, (int)y)){
|
||||
continue;
|
||||
}
|
||||
float fraction=time/(float)particleLifetime;
|
||||
paint.setColor(interpolateColor(fraction));
|
||||
canvas.drawCircle(x, y, size, paint);
|
||||
}
|
||||
long timeDiff=Math.min(100, now-prevFrameTime);
|
||||
int newParticleCount=Math.round(timeDiff/1000f*birthRate);
|
||||
for(int i=0;i<newParticleCount;i++){
|
||||
Particle p;
|
||||
if(!pool.isEmpty())
|
||||
p=pool.remove(pool.size()-1);
|
||||
else
|
||||
p=new Particle();
|
||||
p.birthTime=now;
|
||||
double angle=rand.nextDouble()*Math.PI*2;
|
||||
float vel=velocity+velocityVariance*(rand.nextFloat()*2-1f);
|
||||
p.velX=vel*(float)Math.cos(angle);
|
||||
p.velY=vel*(float)Math.sin(angle);
|
||||
nextActiveParticles.add(p);
|
||||
}
|
||||
ArrayList<Particle> tmp=nextActiveParticles;
|
||||
nextActiveParticles=activeParticles;
|
||||
activeParticles=tmp;
|
||||
invalidateSelf();
|
||||
prevFrameTime=now;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity(){
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
public void setClipOutBounds(int l, int t, int r, int b){
|
||||
clipOutBounds.set(l, t, r, b);
|
||||
}
|
||||
|
||||
private int interpolateColor(float fraction){
|
||||
float a=(linearStartColor[0]+(linearEndColor[0]-linearStartColor[0])*fraction)*255f;
|
||||
float r=(float)Math.pow(linearStartColor[1]+(linearEndColor[1]-linearStartColor[1])*fraction, 1.0/2.2)*255f;
|
||||
float g=(float)Math.pow(linearStartColor[2]+(linearEndColor[2]-linearStartColor[2])*fraction, 1.0/2.2)*255f;
|
||||
float b=(float)Math.pow(linearStartColor[3]+(linearEndColor[3]-linearStartColor[3])*fraction, 1.0/2.2)*255f;
|
||||
return (Math.round(a) << 24) | (Math.round(r) << 16) | (Math.round(g) << 8) | Math.round(b);
|
||||
|
||||
}
|
||||
|
||||
public void setEmitterPosition(int x, int y){
|
||||
emitterX=x;
|
||||
emitterY=y;
|
||||
}
|
||||
|
||||
private static class Particle{
|
||||
public long birthTime;
|
||||
public float velX, velY;
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,12 @@ import android.animation.ObjectAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.DownloadManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Insets;
|
||||
@@ -59,6 +62,7 @@ import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.Snackbar;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.io.File;
|
||||
@@ -122,6 +126,8 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
private Runnable videoPositionUpdater=this::updateVideoPosition;
|
||||
private int videoDuration, videoInitialPosition, videoLastTimeUpdatePosition;
|
||||
private long videoInitialPositionTime;
|
||||
private long lastDownloadID;
|
||||
private boolean receiverRegistered;
|
||||
|
||||
private static final Property<FragmentRootLinearLayout, Integer> STATUS_BAR_COLOR_PROPERTY=new Property<>(Integer.class, "Fdsafdsa"){
|
||||
@Override
|
||||
@@ -135,6 +141,21 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
}
|
||||
};
|
||||
|
||||
private final BroadcastReceiver downloadCompletedReceiver=new BroadcastReceiver(){
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent){
|
||||
long id=intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
|
||||
if(id==lastDownloadID){
|
||||
new Snackbar.Builder(activity)
|
||||
.setText(R.string.video_saved)
|
||||
.setAction(R.string.view_file, ()->activity.startActivity(new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)))
|
||||
.show();
|
||||
activity.unregisterReceiver(this);
|
||||
receiverRegistered=false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public PhotoViewer(Activity activity, List<Attachment> attachments, int index, Status status, String accountID, Listener listener){
|
||||
this.activity=activity;
|
||||
this.attachments=attachments.stream().filter(a->a.type==Attachment.Type.IMAGE || a.type==Attachment.Type.GIFV || a.type==Attachment.Type.VIDEO).collect(Collectors.toList());
|
||||
@@ -370,6 +391,9 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
listener.setPhotoViewVisibility(pager.getCurrentItem(), true);
|
||||
wm.removeView(windowView);
|
||||
listener.photoViewerDismissed();
|
||||
if(receiverRegistered){
|
||||
activity.unregisterReceiver(downloadCompletedReceiver);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -531,7 +555,12 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
BufferedSink buf=Okio.buffer(sink);
|
||||
buf.writeAll(src);
|
||||
buf.flush();
|
||||
activity.runOnUiThread(()->Toast.makeText(activity, R.string.file_saved, Toast.LENGTH_SHORT).show());
|
||||
activity.runOnUiThread(()->{
|
||||
new Snackbar.Builder(activity)
|
||||
.setText(R.string.image_saved)
|
||||
.setAction(R.string.view_file, ()->activity.startActivity(new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)))
|
||||
.show();
|
||||
});
|
||||
if(Build.VERSION.SDK_INT<29){
|
||||
String fileName=Uri.parse(att.url).getLastPathSegment();
|
||||
File dstFile=new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName);
|
||||
@@ -539,12 +568,18 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
}
|
||||
}catch(IOException x){
|
||||
Log.w(TAG, "doSaveCurrentFile: ", x);
|
||||
activity.runOnUiThread(()->Toast.makeText(activity, R.string.error_saving_file, Toast.LENGTH_SHORT).show());
|
||||
activity.runOnUiThread(()->{
|
||||
new Snackbar.Builder(activity)
|
||||
.setText(R.string.error_saving_file)
|
||||
.show();
|
||||
});
|
||||
}
|
||||
});
|
||||
}catch(IOException x){
|
||||
Log.w(TAG, "doSaveCurrentFile: ", x);
|
||||
Toast.makeText(activity, R.string.error_saving_file, Toast.LENGTH_SHORT).show();
|
||||
new Snackbar.Builder(activity)
|
||||
.setText(R.string.error_saving_file)
|
||||
.show();
|
||||
}
|
||||
}else{
|
||||
saveViaDownloadManager(att);
|
||||
@@ -557,8 +592,12 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
req.allowScanningByMediaScanner();
|
||||
req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
|
||||
req.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, uri.getLastPathSegment());
|
||||
activity.getSystemService(DownloadManager.class).enqueue(req);
|
||||
Toast.makeText(activity, R.string.downloading, Toast.LENGTH_SHORT).show();
|
||||
activity.registerReceiver(downloadCompletedReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
||||
receiverRegistered=true;
|
||||
lastDownloadID=activity.getSystemService(DownloadManager.class).enqueue(req);
|
||||
new Snackbar.Builder(activity)
|
||||
.setText(R.string.downloading)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void onAudioFocusChanged(int change){
|
||||
@@ -690,7 +729,7 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
}
|
||||
}else if(id==R.id.btn_share){
|
||||
if(status!=null){
|
||||
UiUtils.openSystemShareSheet(activity, status.url);
|
||||
UiUtils.openSystemShareSheet(activity, status);
|
||||
}
|
||||
}else if(id==R.id.btn_bookmark){
|
||||
if(status!=null){
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.joinmastodon.android.ui.utils;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.ColorStateList;
|
||||
@@ -25,6 +26,8 @@ import android.os.SystemClock;
|
||||
import android.os.ext.SdkExtensions;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
@@ -34,6 +37,7 @@ import android.transition.ChangeScroll;
|
||||
import android.transition.Fade;
|
||||
import android.transition.TransitionManager;
|
||||
import android.transition.TransitionSet;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -51,6 +55,7 @@ import android.widget.Toast;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.FileProvider;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
@@ -84,6 +89,7 @@ import org.joinmastodon.android.ui.text.SpacerSpan;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
@@ -107,6 +113,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.imageloader.ImageCache;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
@@ -919,10 +926,48 @@ public class UiUtils{
|
||||
}
|
||||
}
|
||||
|
||||
public static void openSystemShareSheet(Context context, String url){
|
||||
public static Uri getFileProviderUri(Context context, File file){
|
||||
return FileProvider.getUriForFile(context, context.getPackageName()+".fileprovider", file);
|
||||
}
|
||||
|
||||
public static void openSystemShareSheet(Context context, Object obj){
|
||||
Intent intent=new Intent(Intent.ACTION_SEND);
|
||||
intent.setType("text/plain");
|
||||
Account account;
|
||||
String url;
|
||||
String previewTitle;
|
||||
|
||||
if(obj instanceof Account acc){
|
||||
account=acc;
|
||||
url=acc.url;
|
||||
previewTitle=context.getString(R.string.share_sheet_preview_profile, account.displayName);
|
||||
}else if(obj instanceof Status st){
|
||||
account=st.account;
|
||||
url=st.url;
|
||||
String postText=st.getStrippedText();
|
||||
if(TextUtils.isEmpty(postText)){
|
||||
previewTitle=context.getString(R.string.share_sheet_preview_profile, account.displayName);
|
||||
}else{
|
||||
if(postText.length()>100)
|
||||
postText=postText.substring(0, 100)+"...";
|
||||
previewTitle=context.getString(R.string.share_sheet_preview_post, account.displayName, postText);
|
||||
}
|
||||
}else{
|
||||
throw new IllegalArgumentException("Unsupported share object type");
|
||||
}
|
||||
|
||||
intent.putExtra(Intent.EXTRA_TEXT, url);
|
||||
intent.putExtra(Intent.EXTRA_TITLE, previewTitle);
|
||||
ImageCache cache=ImageCache.getInstance(context);
|
||||
try{
|
||||
File ava=cache.getFile(new UrlImageLoaderRequest(account.avatarStatic));
|
||||
if(!ava.exists())
|
||||
ava=cache.getFile(new UrlImageLoaderRequest(account.avatar));
|
||||
if(ava.exists()){
|
||||
intent.setClipData(ClipData.newRawUri(null, getFileProviderUri(context, ava)));
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
}
|
||||
}catch(IOException ignore){}
|
||||
context.startActivity(Intent.createChooser(intent, context.getString(R.string.share_toot_title)));
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -46,6 +47,8 @@ public class ComposeLanguageAlertViewController{
|
||||
private List<SpecialLocaleInfo> specialLocales=new ArrayList<>();
|
||||
private int selectedIndex=0;
|
||||
private Locale selectedLocale;
|
||||
private Locale detectedLocale;
|
||||
private Consumer<SelectedOption> selectionListener;
|
||||
|
||||
public ComposeLanguageAlertViewController(Context context, String preferred, SelectedOption previouslySelected, String postText){
|
||||
this.context=context;
|
||||
@@ -81,15 +84,22 @@ public class ComposeLanguageAlertViewController{
|
||||
|
||||
if(Build.VERSION.SDK_INT>=29 && !TextUtils.isEmpty(postText)){
|
||||
SpecialLocaleInfo detected=new SpecialLocaleInfo();
|
||||
detected.displayName=context.getString(R.string.language_detecting);
|
||||
detected.enabled=false;
|
||||
if(previouslySelected!=null && previouslySelected.detectedLocale!=null){
|
||||
detectedLocale=previouslySelected.detectedLocale;
|
||||
detected.locale=detectedLocale;
|
||||
detected.displayName=capitalizeLanguageName(detectedLocale.getDisplayName(Locale.getDefault()));
|
||||
detected.title=context.getString(R.string.language_detected);
|
||||
}else{
|
||||
detected.displayName=context.getString(R.string.language_detecting);
|
||||
detected.enabled=false;
|
||||
}
|
||||
specialLocales.add(detected);
|
||||
detectLanguage(detected, postText);
|
||||
}
|
||||
|
||||
if(previouslySelected!=null){
|
||||
if(previouslySelected.index!=-1 && ((previouslySelected.index<specialLocales.size() && Objects.equals(previouslySelected.locale, specialLocales.get(previouslySelected.index).locale)) ||
|
||||
(previouslySelected.index<specialLocales.size()+allLocales.size() && previouslySelected.index>-1 && Objects.equals(previouslySelected.locale, allLocales.get(previouslySelected.index-specialLocales.size()).locale)))){
|
||||
(previouslySelected.index<specialLocales.size()+allLocales.size() && previouslySelected.index>=specialLocales.size() && Objects.equals(previouslySelected.locale, allLocales.get(previouslySelected.index-specialLocales.size()).locale)))){
|
||||
selectedIndex=previouslySelected.index;
|
||||
selectedLocale=previouslySelected.locale;
|
||||
}else{
|
||||
@@ -174,6 +184,7 @@ public class ComposeLanguageAlertViewController{
|
||||
info.displayName=context.getString(R.string.language_cant_detect);
|
||||
}else{
|
||||
Locale locale=lang.getLocale(0).toLocale();
|
||||
detectedLocale=locale;
|
||||
info.locale=locale;
|
||||
info.displayName=capitalizeLanguageName(locale.getDisplayName(Locale.getDefault()));
|
||||
info.title=context.getString(R.string.language_detected);
|
||||
@@ -197,7 +208,7 @@ public class ComposeLanguageAlertViewController{
|
||||
}
|
||||
|
||||
public SelectedOption getSelectedOption(){
|
||||
return new SelectedOption(selectedIndex, selectedLocale);
|
||||
return new SelectedOption(selectedIndex, selectedLocale, detectedLocale);
|
||||
}
|
||||
|
||||
private void selectItem(int index){
|
||||
@@ -212,6 +223,12 @@ public class ComposeLanguageAlertViewController{
|
||||
if(holder!=null && holder.itemView instanceof Checkable checkable)
|
||||
checkable.setChecked(true);
|
||||
selectedIndex=index;
|
||||
if(selectionListener!=null)
|
||||
selectionListener.accept(getSelectedOption());
|
||||
}
|
||||
|
||||
public void setSelectionListener(Consumer<SelectedOption> selectionListener){
|
||||
this.selectionListener=selectionListener;
|
||||
}
|
||||
|
||||
private class AllLocalesAdapter extends RecyclerView.Adapter<SimpleLanguageViewHolder>{
|
||||
@@ -255,8 +272,8 @@ public class ComposeLanguageAlertViewController{
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
selectItem(getAbsoluteAdapterPosition());
|
||||
selectedLocale=item.locale;
|
||||
selectItem(getAbsoluteAdapterPosition());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,8 +329,8 @@ public class ComposeLanguageAlertViewController{
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
selectItem(getAbsoluteAdapterPosition());
|
||||
selectedLocale=item.locale;
|
||||
selectItem(getAbsoluteAdapterPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -343,12 +360,14 @@ public class ComposeLanguageAlertViewController{
|
||||
public static class SelectedOption{
|
||||
public int index;
|
||||
public Locale locale;
|
||||
public Locale detectedLocale;
|
||||
|
||||
public SelectedOption(){}
|
||||
|
||||
public SelectedOption(int index, Locale locale){
|
||||
public SelectedOption(int index, Locale locale, Locale detectedLocale){
|
||||
this.index=index;
|
||||
this.locale=locale;
|
||||
this.detectedLocale=detectedLocale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,28 +190,20 @@ public class ComposePollViewController{
|
||||
if(l==pollDuration)
|
||||
selectedOption=i;
|
||||
}
|
||||
int[] chosenOption={0};
|
||||
new M3AlertDialogBuilder(fragment.getActivity())
|
||||
.setSingleChoiceItems(options, selectedOption, (dialog, which)->chosenOption[0]=which)
|
||||
.setTitle(R.string.poll_length)
|
||||
.setPositiveButton(R.string.ok, (dialog, which)->{
|
||||
pollDuration=POLL_LENGTH_OPTIONS[chosenOption[0]];
|
||||
.setSingleChoiceItems(options, selectedOption, (dialog, which)->{
|
||||
pollDuration=POLL_LENGTH_OPTIONS[which];
|
||||
pollDurationValue.setText(UiUtils.formatDuration(fragment.getContext(), pollDuration));
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setTitle(R.string.poll_length)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showPollStyleAlert(){
|
||||
final int[] option={pollIsMultipleChoice ? R.id.multiple_choice : R.id.single_choice};
|
||||
AlertDialog alert=new M3AlertDialogBuilder(fragment.getActivity())
|
||||
.setView(R.layout.poll_style)
|
||||
.setTitle(R.string.poll_style_title)
|
||||
.setPositiveButton(R.string.ok, (dlg, which)->{
|
||||
pollIsMultipleChoice=option[0]==R.id.multiple_choice;
|
||||
pollStyleValue.setText(pollIsMultipleChoice ? R.string.compose_poll_multiple_choice : R.string.compose_poll_single_choice);
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
CheckableLinearLayout multiple=alert.findViewById(R.id.multiple_choice);
|
||||
CheckableLinearLayout single=alert.findViewById(R.id.single_choice);
|
||||
@@ -219,11 +211,9 @@ public class ComposePollViewController{
|
||||
multiple.setChecked(pollIsMultipleChoice);
|
||||
View.OnClickListener listener=v->{
|
||||
int id=v.getId();
|
||||
if(id==option[0])
|
||||
return;
|
||||
((Checkable) alert.findViewById(option[0])).setChecked(false);
|
||||
((Checkable) v).setChecked(true);
|
||||
option[0]=id;
|
||||
pollIsMultipleChoice=id==R.id.multiple_choice;
|
||||
pollStyleValue.setText(pollIsMultipleChoice ? R.string.compose_poll_multiple_choice : R.string.compose_poll_single_choice);
|
||||
alert.dismiss();
|
||||
};
|
||||
single.setOnClickListener(listener);
|
||||
multiple.setOnClickListener(listener);
|
||||
|
||||
@@ -235,10 +235,7 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
|
||||
|
||||
int id=item.getItemId();
|
||||
if(id==R.id.share){
|
||||
Intent intent=new Intent(Intent.ACTION_SEND);
|
||||
intent.setType("text/plain");
|
||||
intent.putExtra(Intent.EXTRA_TEXT, account.url);
|
||||
fragment.startActivity(Intent.createChooser(intent, item.getTitle()));
|
||||
UiUtils.openSystemShareSheet(fragment.getActivity(), account);
|
||||
}else if(id==R.id.mute){
|
||||
UiUtils.confirmToggleMuteUser(fragment.getActivity(), accountID, account, relationship.muting, this::updateRelationship);
|
||||
}else if(id==R.id.block){
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
|
||||
public class FixedAspectRatioFrameLayout extends FrameLayout{
|
||||
private float aspectRatio;
|
||||
private boolean useHeight;
|
||||
|
||||
public FixedAspectRatioFrameLayout(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public FixedAspectRatioFrameLayout(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public FixedAspectRatioFrameLayout(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.FixedAspectRatioImageView);
|
||||
aspectRatio=ta.getFloat(R.styleable.FixedAspectRatioImageView_aspectRatio, 1);
|
||||
useHeight=ta.getBoolean(R.styleable.FixedAspectRatioImageView_useHeight, false);
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
||||
if(useHeight){
|
||||
int height=MeasureSpec.getSize(heightMeasureSpec);
|
||||
widthMeasureSpec=Math.round(height*aspectRatio) | MeasureSpec.EXACTLY;
|
||||
}else{
|
||||
int width=MeasureSpec.getSize(widthMeasureSpec);
|
||||
heightMeasureSpec=Math.round(width/aspectRatio) | MeasureSpec.EXACTLY;
|
||||
}
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
public float getAspectRatio(){
|
||||
return aspectRatio;
|
||||
}
|
||||
|
||||
public void setAspectRatio(float aspectRatio){
|
||||
this.aspectRatio=aspectRatio;
|
||||
}
|
||||
|
||||
public boolean isUseHeight(){
|
||||
return useHeight;
|
||||
}
|
||||
|
||||
public void setUseHeight(boolean useHeight){
|
||||
this.useHeight=useHeight;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,35 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.ui.ViewProperties;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.IntConsumer;
|
||||
import java.util.function.IntPredicate;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
|
||||
public class TabBar extends LinearLayout{
|
||||
@IdRes
|
||||
private int selectedTabID;
|
||||
private IntConsumer listener;
|
||||
private IntPredicate longClickListener;
|
||||
private Typeface mediumFont=Typeface.create("sans-serif-medium", Typeface.NORMAL), boldFont=Typeface.DEFAULT_BOLD;
|
||||
|
||||
public TabBar(Context context){
|
||||
this(context, null);
|
||||
@@ -32,9 +47,14 @@ public class TabBar extends LinearLayout{
|
||||
public void onViewAdded(View child){
|
||||
super.onViewAdded(child);
|
||||
if(child.getId()!=0){
|
||||
ViewHolder holder=new ViewHolder();
|
||||
holder.label=child.findViewById(R.id.label);
|
||||
child.setTag(holder);
|
||||
if(selectedTabID==0){
|
||||
selectedTabID=child.getId();
|
||||
child.setSelected(true);
|
||||
setTabSelected(child, true);
|
||||
}else{
|
||||
holder.label.setTypeface(mediumFont);
|
||||
}
|
||||
child.setOnClickListener(this::onChildClick);
|
||||
child.setOnLongClickListener(this::onChildLongClick);
|
||||
@@ -45,8 +65,8 @@ public class TabBar extends LinearLayout{
|
||||
listener.accept(v.getId());
|
||||
if(v.getId()==selectedTabID)
|
||||
return;
|
||||
findViewById(selectedTabID).setSelected(false);
|
||||
v.setSelected(true);
|
||||
setTabSelected(findViewById(selectedTabID), false);
|
||||
setTabSelected(v, true);
|
||||
selectedTabID=v.getId();
|
||||
}
|
||||
|
||||
@@ -60,8 +80,40 @@ public class TabBar extends LinearLayout{
|
||||
}
|
||||
|
||||
public void selectTab(int id){
|
||||
findViewById(selectedTabID).setSelected(false);
|
||||
setTabSelected(findViewById(selectedTabID), false);
|
||||
selectedTabID=id;
|
||||
findViewById(selectedTabID).setSelected(true);
|
||||
setTabSelected(findViewById(selectedTabID), true);
|
||||
}
|
||||
|
||||
private void setTabSelected(View tab, boolean selected){
|
||||
tab.setSelected(selected);
|
||||
ViewHolder holder=(ViewHolder) tab.getTag();
|
||||
if(holder.currentAnim!=null)
|
||||
holder.currentAnim.cancel();
|
||||
ArrayList<Animator> anims=new ArrayList<>();
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P){
|
||||
anims.add(ObjectAnimator.ofInt(holder.label, ViewProperties.FONT_WEIGHT, selected ? 700 : 500));
|
||||
}else{
|
||||
holder.label.setTypeface(selected ? boldFont : mediumFont);
|
||||
}
|
||||
anims.add(ObjectAnimator.ofArgb(holder.label, ViewProperties.TEXT_COLOR, UiUtils.getThemeColor(getContext(), selected ? R.attr.colorM3OnSurface : R.attr.colorM3OnSurfaceVariant)));
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(anims);
|
||||
set.setDuration(400);
|
||||
// set.setInterpolator(AnimationUtils.loadInterpolator(getContext(), R.interpolator.m3_sys_motion_easing_standard));
|
||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
holder.currentAnim=set;
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
holder.currentAnim=null;
|
||||
}
|
||||
});
|
||||
set.start();
|
||||
}
|
||||
|
||||
private static class ViewHolder{
|
||||
public TextView label;
|
||||
public Animator currentAnim;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package org.joinmastodon.android.ui.wrapstodon;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
|
||||
/**
|
||||
* Software-rendering-friendly rounded-corners image view. Relies on arcane xrefmode magic.
|
||||
*/
|
||||
public class RoundedImageView extends ImageView{
|
||||
private int cornerRadius;
|
||||
private boolean roundBottomCorners=true;
|
||||
private Paint clearPaint=new Paint(Paint.ANTI_ALIAS_FLAG), paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
public RoundedImageView(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public RoundedImageView(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public RoundedImageView(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.RoundedImageView);
|
||||
cornerRadius=ta.getDimensionPixelOffset(R.styleable.RoundedImageView_cornerRadius, 0);
|
||||
roundBottomCorners=ta.getBoolean(R.styleable.RoundedImageView_roundBottomCorners, true);
|
||||
ta.recycle();
|
||||
setOutlineProvider(new ViewOutlineProvider(){
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline){
|
||||
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight()+(roundBottomCorners ? 0 : cornerRadius), cornerRadius);
|
||||
}
|
||||
});
|
||||
setClipToOutline(true);
|
||||
clearPaint.setColor(0xFFFFFFFF);
|
||||
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
|
||||
paint.setColor(0xFF0ff000);
|
||||
}
|
||||
|
||||
public void setCornerRadius(int cornerRadius){
|
||||
this.cornerRadius=cornerRadius;
|
||||
invalidateOutline();
|
||||
}
|
||||
|
||||
public void setRoundBottomCorners(boolean roundBottomCorners){
|
||||
this.roundBottomCorners=roundBottomCorners;
|
||||
invalidateOutline();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas){
|
||||
if(canvas.isHardwareAccelerated()){
|
||||
super.draw(canvas);
|
||||
return;
|
||||
}
|
||||
canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
|
||||
canvas.drawRoundRect(0, 0, getWidth(), getHeight()+(roundBottomCorners ? 0 : cornerRadius), cornerRadius, cornerRadius, paint);
|
||||
canvas.saveLayer(0, 0, getWidth(), getHeight(), clearPaint);
|
||||
super.draw(canvas);
|
||||
canvas.restore();
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
5
mastodon/src/main/res/color/tab_bar_icon.xml
Normal file
5
mastodon/src/main/res/color/tab_bar_icon.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?colorM3OnSecondaryContainer" android:state_selected="true"/>
|
||||
<item android:color="?colorM3OnSurfaceVariant"/>
|
||||
</selector>
|
||||
@@ -1,21 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/m3_on_surface_variant_overlay">
|
||||
<item android:gravity="center">
|
||||
<selector>
|
||||
<item android:state_selected="true">
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:gravity="top|center_horizontal" android:top="12dp">
|
||||
<animated-selector>
|
||||
<item android:id="@+id/selected" android:state_selected="true">
|
||||
<shape>
|
||||
<solid android:color="?colorM3SecondaryContainer"/>
|
||||
<size android:width="64dp" android:height="32dp"/>
|
||||
<corners android:radius="16dp"/>
|
||||
<solid android:color="?colorM3SecondaryContainer"/>
|
||||
<size android:width="64dp" android:height="32dp"/>
|
||||
<corners android:radius="16dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
<item android:id="@+id/unselected">
|
||||
<shape/>
|
||||
</item>
|
||||
<transition android:fromId="@+id/unselected" android:toId="@+id/selected" android:drawable="@drawable/bg_tabbar_tab_selected_anim"/>
|
||||
<transition android:fromId="@+id/selected" android:toId="@+id/unselected" android:drawable="@drawable/bg_tabbar_tab_unselected_anim"/>
|
||||
</animated-selector>
|
||||
</item>
|
||||
<item android:id="@android:id/mask" android:gravity="center">
|
||||
<shape>
|
||||
<solid android:color="#000"/>
|
||||
<size android:width="64dp" android:height="32dp"/>
|
||||
<corners android:radius="16dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:drawable="@drawable/bg_tabbar_tab_vector">
|
||||
<target
|
||||
android:name="shape">
|
||||
<aapt:attr name="android:animation">
|
||||
<set>
|
||||
<objectAnimator
|
||||
android:duration="500"
|
||||
android:interpolator="@interpolator/m3_sys_motion_easing_standard"
|
||||
android:propertyName="pathData"
|
||||
android:valueFrom="M32 0A16 16 0 0 0 16 16A16 16 0 0 0 32 32h0A16 16 0 0 0 48 16A16 16 0 0 0 32 0Z"
|
||||
android:valueTo="M16 0A16 16 0 0 0 0 16A16 16 0 0 0 16 32h32A16 16 0 0 0 64 16A16 16 0 0 0 48 0Z"
|
||||
android:valueType="pathType"/>
|
||||
<objectAnimator
|
||||
android:duration="250"
|
||||
android:interpolator="@interpolator/m3_sys_motion_easing_standard"
|
||||
android:propertyName="fillAlpha"
|
||||
android:valueFrom="0.0"
|
||||
android:valueTo="1.0"
|
||||
android:valueType="floatType"/>
|
||||
</set>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
</animated-vector>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:drawable="@drawable/bg_tabbar_tab_vector">
|
||||
<target
|
||||
android:name="shape">
|
||||
<aapt:attr name="android:animation">
|
||||
<objectAnimator
|
||||
android:duration="500"
|
||||
android:interpolator="@interpolator/m3_sys_motion_easing_standard"
|
||||
android:propertyName="fillAlpha"
|
||||
android:valueFrom="1.0"
|
||||
android:valueTo="0.0"
|
||||
android:valueType="floatType"/>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
</animated-vector>
|
||||
7
mastodon/src/main/res/drawable/bg_tabbar_tab_vector.xml
Normal file
7
mastodon/src/main/res/drawable/bg_tabbar_tab_vector.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:viewportWidth="64" android:viewportHeight="32" android:width="64dp" android:height="32dp">
|
||||
<path
|
||||
android:name="shape"
|
||||
android:fillColor="?colorM3SecondaryContainer"
|
||||
android:pathData="M16 0A16 16 0 0 0 0 16A16 16 0 0 0 16 32h32A16 16 0 0 0 64 16A16 16 0 0 0 48 0Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_download_20px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_download_20px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M5.5,16Q4.875,16 4.438,15.562Q4,15.125 4,14.5V13H5.5V14.5Q5.5,14.5 5.5,14.5Q5.5,14.5 5.5,14.5H14.5Q14.5,14.5 14.5,14.5Q14.5,14.5 14.5,14.5V13H16V14.5Q16,15.125 15.562,15.562Q15.125,16 14.5,16ZM10,13 L6,9 7.062,7.938 9.25,10.125V3H10.75V10.125L12.938,7.938L14,9Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_home_fill1_24px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_home_fill1_24px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M4,21V9L12,3L20,9V21H14V14H10V21Z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M4,19V17H6V10Q6,7.925 7.25,6.312Q8.5,4.7 10.5,4.2V3.5Q10.5,2.875 10.938,2.438Q11.375,2 12,2Q12.625,2 13.062,2.438Q13.5,2.875 13.5,3.5V4.2Q15.5,4.7 16.75,6.312Q18,7.925 18,10V17H20V19ZM12,22Q11.175,22 10.588,21.413Q10,20.825 10,20H14Q14,20.825 13.413,21.413Q12.825,22 12,22Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_qr_code_20px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_qr_code_20px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M2.5,9.125V2.5H9.125V9.125ZM4.25,7.375H7.375V4.25H4.25ZM2.5,17.5V10.875H9.125V17.5ZM4.25,15.75H7.417V12.625H4.25ZM10.875,9.125V2.5H17.5V9.125ZM12.625,7.375H15.75V4.25H12.625ZM15.854,17.5V15.833H17.5V17.5ZM10.875,12.521V10.875H12.542V12.521ZM12.542,14.167V12.521H14.208V14.167ZM10.875,15.833V14.167H12.542V15.833ZM12.542,17.5V15.833H14.208V17.5ZM14.208,15.833V14.167H15.854V15.833ZM14.208,12.521V10.875H15.854V12.521ZM15.854,14.167V12.521H17.5V14.167Z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M2,7V2H7V4H4V7ZM2,22V17H4V20H7V22ZM17,22V20H20V17H22V22ZM20,7V4H17V2H22V7ZM17.5,17.5H19V19H17.5ZM17.5,14.5H19V16H17.5ZM16,16H17.5V17.5H16ZM14.5,17.5H16V19H14.5ZM13,16H14.5V17.5H13ZM16,13H17.5V14.5H16ZM14.5,14.5H16V16H14.5ZM13,13H14.5V14.5H13ZM19,5V11H13V5ZM11,13V19H5V13ZM11,5V11H5V5ZM9.5,17.5V14.5H6.5V17.5ZM9.5,9.5V6.5H6.5V9.5ZM17.5,9.5V6.5H14.5V9.5Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_search_fill1_24px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_search_fill1_24px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19.6,21 L13.3,14.7Q12.55,15.3 11.575,15.65Q10.6,16 9.5,16Q6.775,16 4.888,14.113Q3,12.225 3,9.5Q3,6.775 4.888,4.887Q6.775,3 9.5,3Q12.225,3 14.113,4.887Q16,6.775 16,9.5Q16,10.6 15.65,11.575Q15.3,12.55 14.7,13.3L21,19.6ZM9.5,14Q11.375,14 12.688,12.688Q14,11.375 14,9.5Q14,7.625 12.688,6.312Q11.375,5 9.5,5Q7.625,5 6.312,6.312Q5,7.625 5,9.5Q5,11.375 6.312,12.688Q7.625,14 9.5,14Z"/>
|
||||
</vector>
|
||||
5
mastodon/src/main/res/drawable/ic_tab_home.xml
Normal file
5
mastodon/src/main/res/drawable/ic_tab_home.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/ic_home_fill1_24px" android:state_selected="true"/>
|
||||
<item android:drawable="@drawable/ic_home_24px"/>
|
||||
</selector>
|
||||
5
mastodon/src/main/res/drawable/ic_tab_notifications.xml
Normal file
5
mastodon/src/main/res/drawable/ic_tab_notifications.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/ic_notifications_fill1_24px" android:state_selected="true"/>
|
||||
<item android:drawable="@drawable/ic_notifications_24px"/>
|
||||
</selector>
|
||||
5
mastodon/src/main/res/drawable/ic_tab_search.xml
Normal file
5
mastodon/src/main/res/drawable/ic_tab_search.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/ic_search_fill1_24px" android:state_selected="true"/>
|
||||
<item android:drawable="@drawable/ic_search_24px"/>
|
||||
</selector>
|
||||
5
mastodon/src/main/res/drawable/rect_24dp.xml
Normal file
5
mastodon/src/main/res/drawable/rect_24dp.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="24dp"/>
|
||||
<solid android:color="#000"/>
|
||||
</shape>
|
||||
@@ -57,7 +57,7 @@
|
||||
app:tabIndicatorColor="?colorM3Primary"
|
||||
app:tabIndicatorFullWidth="false"
|
||||
app:tabMinWidth="90dp"
|
||||
app:tabMode="scrollable"
|
||||
app:tabMode="auto"
|
||||
android:background="?colorM3Surface"/>
|
||||
|
||||
<View
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/profile_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
@@ -291,8 +292,9 @@
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/profile_action_btn_wrap"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1">
|
||||
|
||||
<org.joinmastodon.android.ui.views.ProgressBarButton
|
||||
android:id="@+id/profile_action_btn"
|
||||
@@ -314,6 +316,20 @@
|
||||
android:outlineProvider="none"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/qr_code"
|
||||
android:layout_width="36.67dp"
|
||||
android:layout_height="36.67dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
style="@style/Widget.Mastodon.M3.Button.Outlined"
|
||||
android:tint="?colorM3OnSurfaceVariant"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="1.67dp"
|
||||
android:contentDescription="@string/qr_code"
|
||||
android:scaleType="centerCrop"
|
||||
android:padding="9dp"
|
||||
android:src="@drawable/ic_qr_code_20px"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
149
mastodon/src/main/res/layout/fragment_profile_qr.xml
Normal file
149
mastodon/src/main/res/layout/fragment_profile_qr.xml
Normal file
@@ -0,0 +1,149 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<include layout="@layout/profile_qr_toolbar" />
|
||||
|
||||
<view class="org.joinmastodon.android.fragments.ProfileQrCodeFragment$CustomizedLinearLayout"
|
||||
android:id="@+id/particle_animation_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.joinmastodon.android.ui.views.FixedAspectRatioFrameLayout
|
||||
android:id="@+id/corner_animation_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:aspectRatio="1">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/code_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="40dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="17dp"
|
||||
android:background="@drawable/rect_24dp"
|
||||
android:backgroundTint="?colorM3Primary">
|
||||
<View
|
||||
android:id="@+id/code"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<me.grishka.appkit.views.RoundedImageView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
app:cornerRadius="10dp"
|
||||
android:importantForAccessibility="no"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/username"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginHorizontal="4dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?colorM3OnPrimary"
|
||||
android:alpha="0.7"
|
||||
tools:text="Gargron"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/domain"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:alpha="0.7"
|
||||
android:textAppearance="@style/m3_label_small"
|
||||
android:textColor="?colorM3Primary"
|
||||
android:paddingHorizontal="4dp"
|
||||
android:background="@drawable/rect_4dp"
|
||||
android:backgroundTint="?colorM3OnPrimary"
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
tools:text="mastodon.social"/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</org.joinmastodon.android.ui.views.FixedAspectRatioFrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/share_btn"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_weight="1"
|
||||
android:paddingStart="16dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:drawableStart="@drawable/ic_share_20px"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||
android:background="@null"
|
||||
android:padding="0dp"
|
||||
android:drawablePadding="7dp"
|
||||
android:drawableTint="@color/button_text_m3_filled"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:text="@string/share_user"/>
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/save_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:paddingStart="16dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled">
|
||||
<TextView
|
||||
android:id="@+id/save_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:drawableStart="@drawable/ic_download_20px"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||
android:background="@null"
|
||||
android:padding="0dp"
|
||||
android:drawablePadding="7dp"
|
||||
android:drawableTint="@color/button_text_m3_filled"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:minWidth="0dp"
|
||||
android:duplicateParentState="true"
|
||||
android:text="@string/save"/>
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</view>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -93,7 +93,7 @@
|
||||
<org.joinmastodon.android.ui.views.ProgressBarButton
|
||||
android:id="@+id/button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:minWidth="96dp"
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.joinmastodon.android.ui.views.CheckableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="56dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="12dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<View
|
||||
android:id="@+id/radiobutton"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:background="?android:attr/listChoiceIndicatorSingle"
|
||||
android:duplicateParentState="true"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_vertical">
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="24dp"
|
||||
android:gravity="center_vertical|start"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="Title"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="20dp"
|
||||
android:gravity="center_vertical|start"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="Subtitle"/>
|
||||
</LinearLayout>
|
||||
|
||||
</org.joinmastodon.android.ui.views.CheckableLinearLayout>
|
||||
@@ -24,9 +24,8 @@
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/value"
|
||||
android:layout_toStartOf="@id/verified_icon"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textAppearance="@style/m3_label_medium"
|
||||
android:minHeight="20dp"
|
||||
android:textSize="14sp"
|
||||
android:gravity="center_vertical"
|
||||
@@ -39,6 +38,7 @@
|
||||
android:id="@+id/value"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/title"
|
||||
android:layout_toStartOf="@id/verified_icon"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
android:fontFamily="sans-serif-condensed"
|
||||
android:textStyle="bold"
|
||||
android:textSize="22dp"
|
||||
android:gravity="center"
|
||||
android:gravity="center_vertical|start"
|
||||
android:includeFontPadding="false"
|
||||
tools:text="1"/>
|
||||
|
||||
|
||||
@@ -36,8 +36,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_toEndOf="@id/dragger_thingy"
|
||||
android:layout_toStartOf="@id/delete"
|
||||
android:layout_below="@id/title"
|
||||
style="@style/Widget.Mastodon.M3.EditText"
|
||||
android:inputType="textCapSentences"
|
||||
android:hint="@string/field_content"
|
||||
@@ -49,10 +51,8 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_toEndOf="@id/dragger_thingy"
|
||||
android:layout_toStartOf="@id/delete"
|
||||
android:layout_below="@id/content"
|
||||
style="@style/Widget.Mastodon.M3.EditText"
|
||||
android:inputType="textCapSentences"
|
||||
android:hint="@string/field_label"
|
||||
|
||||
7
mastodon/src/main/res/layout/profile_qr_toolbar.xml
Normal file
7
mastodon/src/main/res/layout/profile_qr_toolbar.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:attr/actionBarSize"
|
||||
tools:showIn="@layout/fragment_profile_qr" />
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<me.grishka.appkit.views.RecursiveSwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/refresh_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/content_wrap"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<me.grishka.appkit.views.UsableRecyclerView
|
||||
android:id="@+id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="vertical"
|
||||
android:clipToPadding="false"/>
|
||||
|
||||
<ViewStub android:layout="?emptyViewLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/empty"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="56dp"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@drawable/bg_fab"
|
||||
android:textColor="?colorM3Primary"
|
||||
android:drawableTint="?colorM3Primary"
|
||||
android:stateListAnimator="@animator/fab_shadow"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:drawablePadding="12dp"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
tools:text="Some text"
|
||||
tools:drawableStart="@drawable/ic_edit_24px"/>
|
||||
|
||||
</FrameLayout>
|
||||
</me.grishka.appkit.views.RecursiveSwipeRefreshLayout>
|
||||
@@ -12,8 +12,7 @@
|
||||
<org.joinmastodon.android.ui.views.TabBar
|
||||
android:id="@+id/tabbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:layout_height="80dp"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry">
|
||||
|
||||
<FrameLayout
|
||||
@@ -21,18 +20,32 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginRight="8dp"
|
||||
android:background="@drawable/bg_tabbar_tab"
|
||||
android:paddingHorizontal="4dp"
|
||||
android:contentDescription="@string/home_timeline">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="top|center_horizontal"
|
||||
android:layout_marginTop="12dp"
|
||||
android:scaleType="center"
|
||||
android:importantForAccessibility="no"
|
||||
android:tint="?colorM3OnSurfaceVariant"
|
||||
android:src="@drawable/ic_home_24px"/>
|
||||
android:tint="@color/tab_bar_icon"
|
||||
android:src="@drawable/ic_tab_home"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:gravity="center"
|
||||
android:textSize="12dp"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:text="@string/tab_home"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
@@ -41,18 +54,32 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginRight="8dp"
|
||||
android:background="@drawable/bg_tabbar_tab"
|
||||
android:paddingHorizontal="4dp"
|
||||
android:contentDescription="@string/search_hint">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="top|center_horizontal"
|
||||
android:layout_marginTop="12dp"
|
||||
android:scaleType="center"
|
||||
android:importantForAccessibility="no"
|
||||
android:tint="?colorM3OnSurfaceVariant"
|
||||
android:src="@drawable/ic_search_24px"/>
|
||||
android:tint="@color/tab_bar_icon"
|
||||
android:src="@drawable/ic_tab_search"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:gravity="center"
|
||||
android:textSize="12dp"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:text="@string/tab_search"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
@@ -61,25 +88,26 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginRight="8dp"
|
||||
android:background="@drawable/bg_tabbar_tab"
|
||||
android:paddingHorizontal="4dp"
|
||||
android:contentDescription="@string/notifications">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="top|center_horizontal"
|
||||
android:layout_marginTop="12dp"
|
||||
android:scaleType="center"
|
||||
android:importantForAccessibility="no"
|
||||
android:tint="?colorM3OnSurfaceVariant"
|
||||
android:src="@drawable/ic_notifications_24px"/>
|
||||
android:tint="@color/tab_bar_icon"
|
||||
android:src="@drawable/ic_tab_notifications"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notifications_badge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="16dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="-6dp"
|
||||
android:layout_gravity="top|center_horizontal"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="-8dp"
|
||||
android:background="@drawable/bg_tabbar_badge"
|
||||
android:textColor="?colorM3OnPrimary"
|
||||
@@ -90,6 +118,19 @@
|
||||
android:paddingHorizontal="4dp"
|
||||
tools:text="222"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:gravity="center"
|
||||
android:textSize="12dp"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:text="@string/notifications"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
@@ -97,25 +138,41 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginRight="8dp"
|
||||
android:background="@drawable/bg_tabbar_tab"
|
||||
android:paddingHorizontal="4dp"
|
||||
android:contentDescription="@string/my_profile">
|
||||
<ImageView
|
||||
android:id="@+id/tab_profile_ava"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_gravity="top|center_horizontal"
|
||||
android:scaleType="centerCrop"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@null"/>
|
||||
<ImageView
|
||||
android:layout_width="8dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="top|center_horizontal"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:scaleType="center"
|
||||
android:tint="?colorM3OnSurfaceVariant"
|
||||
android:tint="@color/tab_bar_icon"
|
||||
android:src="@drawable/ic_unfold_more_24px"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:gravity="center"
|
||||
android:textSize="12dp"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:text="@string/tab_profile"/>
|
||||
</FrameLayout>
|
||||
</org.joinmastodon.android.ui.views.TabBar>
|
||||
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<group android:orderInCategory="1">
|
||||
<group android:orderInCategory="1" android:id="@+id/menu_group1">
|
||||
<item android:id="@+id/share" android:title="@string/share_user"/>
|
||||
<item android:id="@+id/add_to_list" android:title="@string/add_user_to_list"/>
|
||||
<item android:id="@+id/mute" android:title="@string/mute_user"/>
|
||||
<item android:id="@+id/block" android:title="@string/block_user"/>
|
||||
<item android:id="@+id/report" android:title="@string/report_user"/>
|
||||
<item android:id="@+id/block_domain" android:title="@string/block_domain"/>
|
||||
<item android:id="@+id/hide_boosts" android:title="@string/hide_boosts_from_user"/>
|
||||
<item android:id="@+id/copy_link" android:title="@string/copy_profile_link"/>
|
||||
<item android:id="@+id/open_in_browser" android:title="@string/open_in_browser"/>
|
||||
</group>
|
||||
<group android:orderInCategory="2" android:id="@+id/menu_group2">
|
||||
<item android:id="@+id/add_to_list" android:title="@string/add_user_to_list"/>
|
||||
</group>
|
||||
<group android:orderInCategory="3" android:id="@+id/menu_group3">
|
||||
<item android:id="@+id/hide_boosts" android:title="@string/hide_boosts_from_user"/>
|
||||
<item android:id="@+id/mute" android:title="@string/mute_user"/>
|
||||
</group>
|
||||
<group android:orderInCategory="4" android:id="@+id/menu_group4">
|
||||
<item android:id="@+id/block" android:title="@string/block_user"/>
|
||||
<item android:id="@+id/block_domain" android:title="@string/block_domain"/>
|
||||
<item android:id="@+id/report" android:title="@string/report_user"/>
|
||||
</group>
|
||||
</menu>
|
||||
@@ -39,13 +39,10 @@
|
||||
<item quantity="other">متابَع</item>
|
||||
</plurals>
|
||||
<string name="posts">منشورات</string>
|
||||
<string name="posts_and_replies">مَنشُوراتٌ وَرُدُود</string>
|
||||
<string name="media">وسائط</string>
|
||||
<string name="profile_about">حَول</string>
|
||||
<string name="button_follow">تابِع</string>
|
||||
<string name="button_following">مُتابَع</string>
|
||||
<string name="edit_profile">تعديل الملف الشخصي</string>
|
||||
<string name="share_user">شارك الصفحة الشخصية</string>
|
||||
<string name="mute_user">كَتمُ %s</string>
|
||||
<string name="unmute_user">إلغاء الكَتم عن @%s</string>
|
||||
<string name="block_user">حَظرُ %s</string>
|
||||
@@ -134,19 +131,9 @@
|
||||
<item quantity="other">%,d صوتا</item>
|
||||
</plurals>
|
||||
<string name="poll_closed">مغلق</string>
|
||||
<string name="confirm_mute_title">كتم الحساب</string>
|
||||
<string name="confirm_mute">أكّد كتم %s</string>
|
||||
<string name="do_mute">اكتم</string>
|
||||
<string name="confirm_unmute_title">ارفع الكتم عن الحساب</string>
|
||||
<string name="confirm_unmute">أكِّد رفع الكتم عن %s</string>
|
||||
<string name="do_unmute">ارفع الكتم</string>
|
||||
<string name="confirm_block_title">احجب الحساب</string>
|
||||
<string name="confirm_block_domain_title">احجب النطاق</string>
|
||||
<string name="confirm_block">أكّد حجب %s</string>
|
||||
<string name="do_block">احجب</string>
|
||||
<string name="confirm_unblock_title">ارفع الحجب عن الحساب</string>
|
||||
<string name="confirm_unblock_domain_title">ارفع الحجب عن النطاق</string>
|
||||
<string name="confirm_unblock">أكّد رفع الحجب عن %s</string>
|
||||
<string name="do_unblock">ارفع الحجب</string>
|
||||
<string name="button_blocked">محجوب</string>
|
||||
<string name="action_vote">صَوّت</string>
|
||||
@@ -202,9 +189,7 @@
|
||||
<string name="report_personal_subtitle">فيما يلي خياراتك للتحكم بما يُعرَض عليك في ماستدون:</string>
|
||||
<string name="back">العودة</string>
|
||||
<string name="search_communities">اسم الخادم أو عنوان URL</string>
|
||||
<string name="instance_rules_title">قواعد الخادم</string>
|
||||
<string name="instance_rules_subtitle">من خلال المتابعة، أنت توافق على اتباع القواعد التالية التي تم تعيينها وإنفاذها من قبل مشرفي %s.</string>
|
||||
<string name="signup_title">إنشاء حساب</string>
|
||||
<string name="display_name">الاسم</string>
|
||||
<string name="username">اسم المستخدم</string>
|
||||
<string name="email">البريد الإلكتروني</string>
|
||||
@@ -212,7 +197,6 @@
|
||||
<string name="confirm_password">تأكيد كلمة المرور</string>
|
||||
<string name="password_note">ضمّن الأحرف الكبيرة والأحرف الخاصة والأرقام لزيادة قوة كلمة المرور.</string>
|
||||
<string name="category_general">عام</string>
|
||||
<string name="confirm_email_title">تحقق من صندوق الوارد الخاص بك</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="confirm_email_subtitle">اضغط على الرابط الذي أرسلناه إليك للتحقق من %s. سننتظر هنا.</string>
|
||||
<string name="confirm_email_didnt_get">ألم تحصل على رابط؟</string>
|
||||
@@ -251,7 +235,6 @@
|
||||
<string name="theme_dark">داكن</string>
|
||||
<string name="settings_behavior">السلوك</string>
|
||||
<string name="settings_gif">تشغيل الصور الرمزية المتحركة والرموز التعبيرية المتحركة</string>
|
||||
<string name="settings_custom_tabs">استخدم المتصفح المضمن</string>
|
||||
<string name="settings_notifications">الإشعارات</string>
|
||||
<string name="settings_contribute">ساهم في ماستدون</string>
|
||||
<string name="settings_tos">شروط الخدمة</string>
|
||||
@@ -280,8 +263,6 @@
|
||||
<string name="followed_user">أنت تتابع الآن %s</string>
|
||||
<string name="following_user_requested">طَلَبَ %s مُتابَعتك</string>
|
||||
<string name="open_in_browser">افتح في المتصفح</string>
|
||||
<string name="hide_boosts_from_user">أخفِ المعاد نشرها مِن %s</string>
|
||||
<string name="show_boosts_from_user">أظهر ما أعاد %s نشرَه</string>
|
||||
<string name="signup_reason">لماذا تريد الانضمام؟</string>
|
||||
<string name="signup_reason_note">هذا سوف يساعدنا في مراجعة تطبيقك.</string>
|
||||
<string name="clear">امسح</string>
|
||||
@@ -304,7 +285,6 @@
|
||||
<string name="recommended_accounts_info_banner">قد تعجبك هذه الحسابات استنادا إلى حسابات أخرى تتابعها.</string>
|
||||
<string name="see_new_posts">المنشورات الجديدة</string>
|
||||
<string name="load_missing_posts">حمّل المَنشورات المَفقودَة</string>
|
||||
<string name="follow_back">رُدّ المتابعة</string>
|
||||
<string name="button_follow_pending">معلق</string>
|
||||
<string name="follows_you">يُتابِعُك</string>
|
||||
<string name="manually_approves_followers">الموافقة اليدوية على طلبات المتابعة</string>
|
||||
@@ -384,28 +364,18 @@
|
||||
<string name="file_size_mb">%.2f ميغابايت</string>
|
||||
<string name="file_size_gb">%.2f جيجابايت</string>
|
||||
<string name="upload_processing">قيد المعالجة…</string>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is file size -->
|
||||
<string name="download_update">جارٍ التنزيل (%s)</string>
|
||||
<string name="install_update">تثبيت</string>
|
||||
<string name="privacy_policy_title">خصوصيتك</string>
|
||||
<string name="privacy_policy_subtitle">على الرغم من أن تطبيق ماستدون لا يجمع أي بيانات، فإن الخادم الذي قمت بالتسجيل من خلاله قد تكون له سياسة مختلفة.\n\nإذا لم توافق على سياسة %s، يمكنك العودة واختيار خادم مختلف.</string>
|
||||
<string name="i_agree">أنا مُوافِق</string>
|
||||
<string name="empty_list">هذه القائمة فارغة</string>
|
||||
<string name="instance_signup_closed">هذا الخادم لا يقبل التسجيلات الجديدة.</string>
|
||||
<string name="text_copied">نُسِخ إلى الحافظة</string>
|
||||
<string name="add_bookmark">إضافة إلى الفواصل المرجعية</string>
|
||||
<string name="remove_bookmark">إزالة الفاصلة المرجعية</string>
|
||||
<string name="bookmarks">الفواصل المرجعية</string>
|
||||
<string name="your_favorites">مفضّلاتك</string>
|
||||
<string name="login_title">مرحبا بك مجددًا</string>
|
||||
<string name="login_subtitle">قم بتسجيل الدخول باستخدام الخادم حيث قمتَ بإنشاء حسابك فيه.</string>
|
||||
<string name="server_url">رابط الخادم</string>
|
||||
<string name="server_filter_any_language">أي لغة</string>
|
||||
<string name="server_filter_instant_signup">تسجيل فوري</string>
|
||||
<string name="server_filter_manual_review">مراجعة يدوية</string>
|
||||
<string name="server_filter_any_signup_speed">أي سرعة تسجيل</string>
|
||||
<string name="server_filter_region_europe">أوروبا</string>
|
||||
<string name="server_filter_region_north_america">أمريكا الشمالية</string>
|
||||
<string name="server_filter_region_south_america">أمريكا الجنوبية</string>
|
||||
@@ -413,7 +383,6 @@
|
||||
<string name="server_filter_region_asia">آسيا</string>
|
||||
<string name="server_filter_region_oceania">أوقيانوسيا</string>
|
||||
<string name="not_accepting_new_members">لا يقبل استقبال أعضاء جدد</string>
|
||||
<string name="category_special_interests">المصالح الخاصة</string>
|
||||
<string name="signup_passwords_dont_match">كلمات المرور غير متطابقة</string>
|
||||
<string name="profile_add_row">إضافة صف</string>
|
||||
<string name="profile_setup">إعداد الملف الشخصي</string>
|
||||
@@ -722,7 +691,6 @@
|
||||
<string name="remove">إزالة</string>
|
||||
<string name="add_list_member">إضافة عضو</string>
|
||||
<string name="search_among_people_you_follow">البحث بين الأشخاص الذين تتابعهم</string>
|
||||
<string name="add_user_to_list">إضافة القائمة…</string>
|
||||
<string name="add_user_to_list_title">إضافة إلى القائمة</string>
|
||||
<!-- %s is a username -->
|
||||
<string name="remove_from_list">إزالة من القائمة</string>
|
||||
@@ -738,4 +706,5 @@
|
||||
<string name="list_no_members">لا أعضاء حتى الآن</string>
|
||||
<string name="list_find_users">البحث عن مستخدمين للإضافة</string>
|
||||
<!-- %s is a time interval ("5 months") -->
|
||||
<!-- Shown on a button that saves a file, after it was successfully saved -->
|
||||
</resources>
|
||||
|
||||
@@ -35,13 +35,10 @@
|
||||
<item quantity="other">падпісаны</item>
|
||||
</plurals>
|
||||
<string name="posts">Допісы</string>
|
||||
<string name="posts_and_replies">Допісы і адказы</string>
|
||||
<string name="media">Медыя</string>
|
||||
<string name="profile_about">Інфармацыя</string>
|
||||
<string name="button_follow">Падпісацца</string>
|
||||
<string name="button_following">Вы падпісаны</string>
|
||||
<string name="edit_profile">Рэдагаваць профіль</string>
|
||||
<string name="share_user">Падзяліцца профілем</string>
|
||||
<string name="mute_user">Ігнараваць %s</string>
|
||||
<string name="unmute_user">Не ігнараваць %s</string>
|
||||
<string name="block_user">Заблакіраваць %s</string>
|
||||
@@ -112,19 +109,9 @@
|
||||
<item quantity="other">%,d голаса</item>
|
||||
</plurals>
|
||||
<string name="poll_closed">Завершана</string>
|
||||
<string name="confirm_mute_title">Ігнараваць уліковы запіс</string>
|
||||
<string name="confirm_mute">Ігнараваць %s?</string>
|
||||
<string name="do_mute">Ігнараваць</string>
|
||||
<string name="confirm_unmute_title">Не ігнараваць уліковы запіс</string>
|
||||
<string name="confirm_unmute">Не ігнараваць %s?</string>
|
||||
<string name="do_unmute">Не ігнараваць</string>
|
||||
<string name="confirm_block_title">Заблакіраваць уліковы запіс</string>
|
||||
<string name="confirm_block_domain_title">Заблакіраваць дамен</string>
|
||||
<string name="confirm_block">Заблакіраваць %s?</string>
|
||||
<string name="do_block">Заблакіраваць</string>
|
||||
<string name="confirm_unblock_title">Разблакіраваць уліковы запіс</string>
|
||||
<string name="confirm_unblock_domain_title">Разблакіраваць дамен</string>
|
||||
<string name="confirm_unblock">Разблакіраваць %s?</string>
|
||||
<string name="do_unblock">Разблакіраваць</string>
|
||||
<string name="button_blocked">Заблакіраваны</string>
|
||||
<string name="action_vote">Прагаласаваць</string>
|
||||
@@ -178,9 +165,7 @@
|
||||
<string name="report_personal_subtitle">Вось вашыя варыянты кантролю над тым, што вы бачыце в Mastodon:</string>
|
||||
<string name="back">Назад</string>
|
||||
<string name="search_communities">Назва сервера або URL</string>
|
||||
<string name="instance_rules_title">Правілы сервера</string>
|
||||
<string name="instance_rules_subtitle">Працягваючы, вы згаджаецеся выконваць правілы, устаноўленыя мадэратарам %s.</string>
|
||||
<string name="signup_title">Стварыць уліковы запіс</string>
|
||||
<string name="display_name">Імя</string>
|
||||
<string name="username">Імя карыстальніка</string>
|
||||
<string name="email">Электронная пошта</string>
|
||||
@@ -188,7 +173,6 @@
|
||||
<string name="confirm_password">Пацвердзіць пароль</string>
|
||||
<string name="password_note">Выкарыстоўвайце вялікія літары, спецыяльныя сімвалы і лічбы, каб павялічыць надзейнасць пароля.</string>
|
||||
<string name="category_general">Асноўныя</string>
|
||||
<string name="confirm_email_title">Праверце паштовую скрыню</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="confirm_email_subtitle">Націсніце на спасылку, якую мы адправілі, каб спраўдзіць %s. Мы вас пачакаем тут.</string>
|
||||
<string name="confirm_email_didnt_get">Не атрымалі спасылку?</string>
|
||||
@@ -225,7 +209,6 @@
|
||||
<string name="theme_dark">Цёмная</string>
|
||||
<string name="settings_behavior">Паводзіны</string>
|
||||
<string name="settings_gif">Прайграваць аніміраваныя аватары і эмодзі</string>
|
||||
<string name="settings_custom_tabs">Выкарыстоўваць убудаваны браўзер</string>
|
||||
<string name="settings_notifications">Апавяшчэнні</string>
|
||||
<string name="settings_contribute">Унесці ўклад у Mastodon</string>
|
||||
<string name="settings_tos">Умовы выкарыстання</string>
|
||||
@@ -254,8 +237,6 @@
|
||||
<string name="followed_user">Цяпер вы падпісаны на %s</string>
|
||||
<string name="following_user_requested">Запытана падпісацца на %s</string>
|
||||
<string name="open_in_browser">Адкрыць у браўзеры</string>
|
||||
<string name="hide_boosts_from_user">Схаваць пашырэнні ад %s</string>
|
||||
<string name="show_boosts_from_user">Паказаць пашырэнні ад @%s</string>
|
||||
<string name="signup_reason">Чаму вы хочаце далучыцца?</string>
|
||||
<string name="signup_reason_note">Гэта дапаможа нам разгледзець вашу заяўку.</string>
|
||||
<string name="clear">Ачысціць</string>
|
||||
@@ -278,7 +259,6 @@
|
||||
<string name="recommended_accounts_info_banner">Вам могуць спадабацца гэтыя ўліковыя запісы на аснове вашых падпісак.</string>
|
||||
<string name="see_new_posts">Новыя допісы</string>
|
||||
<string name="load_missing_posts">Загрузіць адсутныя допісы</string>
|
||||
<string name="follow_back">Падпісацца ў адказ</string>
|
||||
<string name="button_follow_pending">Чакаюць</string>
|
||||
<string name="follows_you">Падпісаны(-а) на вас</string>
|
||||
<string name="manually_approves_followers">Уручную пацвярджае падпісчыкаў</string>
|
||||
@@ -346,28 +326,18 @@
|
||||
<string name="file_size_mb">%.2f МБ</string>
|
||||
<string name="file_size_gb">%.2f ГБ</string>
|
||||
<string name="upload_processing">Апрацоўка…</string>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is file size -->
|
||||
<string name="download_update">Спампаваць (%s)</string>
|
||||
<string name="install_update">Усталяваць</string>
|
||||
<string name="privacy_policy_title">Ваша прыватнасць</string>
|
||||
<string name="privacy_policy_subtitle">Нягледзячы на тое, што праграма Mastodon не збірае даных, сервер, на якім вы рэгіструецеся, можа мець іншую палітыку.\n\nКалі вы адмовіцеся ад палітыкі %s, вы можаце вярнуцца і выбраць іншы сервер.</string>
|
||||
<string name="i_agree">Я згодны</string>
|
||||
<string name="empty_list">Гэты ліст пусты</string>
|
||||
<string name="instance_signup_closed">Гэты сервер не прымае новыя рэгістрацыі.</string>
|
||||
<string name="text_copied">Скапіявана ў буфер абмену</string>
|
||||
<string name="add_bookmark">Закладка</string>
|
||||
<string name="remove_bookmark">Выдаліць закладку</string>
|
||||
<string name="bookmarks">Закладкі</string>
|
||||
<string name="your_favorites">Вашы абраныя</string>
|
||||
<string name="login_title">З вяртаннем</string>
|
||||
<string name="login_subtitle">Увайдзіце з дапамогай сервера, на якім вы стварылі свой уліковы запіс.</string>
|
||||
<string name="server_url">URL-адрас сервера</string>
|
||||
<string name="server_filter_any_language">Любая мова</string>
|
||||
<string name="server_filter_instant_signup">Імгненная рэгістрацыя</string>
|
||||
<string name="server_filter_manual_review">Ручная праверка</string>
|
||||
<string name="server_filter_any_signup_speed">Любая хуткасць рэгістрацыі</string>
|
||||
<string name="server_filter_region_europe">Еўропа</string>
|
||||
<string name="server_filter_region_north_america">Паўночная Амерыка</string>
|
||||
<string name="server_filter_region_south_america">Паўднёвая Амерыка</string>
|
||||
@@ -375,7 +345,6 @@
|
||||
<string name="server_filter_region_asia">Азія</string>
|
||||
<string name="server_filter_region_oceania">Акіянія</string>
|
||||
<string name="not_accepting_new_members">Не прымае новых удзельнікаў</string>
|
||||
<string name="category_special_interests">Асаблівыя інтарэсы</string>
|
||||
<string name="signup_passwords_dont_match">Паролі не супадаюць</string>
|
||||
<string name="profile_add_row">Дадаць радок</string>
|
||||
<string name="profile_setup">Налады профілю</string>
|
||||
@@ -668,7 +637,6 @@
|
||||
<string name="remove">Выдаліць</string>
|
||||
<string name="add_list_member">Дадаць удзельніка</string>
|
||||
<string name="search_among_people_you_follow">Шукайце сярод людзей, на якіх Вы падпісаны</string>
|
||||
<string name="add_user_to_list">Дадаць да спісу…</string>
|
||||
<string name="add_user_to_list_title">Дадаць да спісу</string>
|
||||
<!-- %s is a username -->
|
||||
<string name="manage_user_lists">Кіраваць спісамі з %s</string>
|
||||
@@ -692,14 +660,82 @@
|
||||
<string name="list_no_members">Пакуль няма ўдзельнікаў</string>
|
||||
<string name="list_find_users">Знайдзіце карыстальнікаў для дадання</string>
|
||||
<string name="reply_to_user">Адказаць %s</string>
|
||||
<string name="posted_at">Допіс у %s</string>
|
||||
<string name="non_mutual_sheet_title">Прывітанне, новы кантакт!</string>
|
||||
<string name="non_mutual_sheet_text">Падобна, вы збіраецеся адказаць камусьці, з кім у вас пакуль не было кантакту. Давайце зробім добрае першае ўражанне.</string>
|
||||
<string name="got_it">Добра</string>
|
||||
<string name="dont_remind_again">Больш не нагадваць</string>
|
||||
<!-- %s is a time interval ("5 months") -->
|
||||
<string name="old_post_sheet_title">Гэтаму допісу больш за %s</string>
|
||||
<string name="old_post_sheet_text">Вы ўсё яшчэ можаце адказаць, але гэта можа быць ужо неактуальна.</string>
|
||||
<plurals name="x_months">
|
||||
<item quantity="one">%,d месяц</item>
|
||||
<item quantity="few">%,d месяцы</item>
|
||||
<item quantity="many">%,d месяцаў</item>
|
||||
<item quantity="other">%,d месяцаў</item>
|
||||
</plurals>
|
||||
<string name="more_than_two_years">больш чым за 2 гады</string>
|
||||
<string name="non_mutual_title1">Заставайцеся ўважлівымі & дарэчнымі</string>
|
||||
<string name="non_mutual_text1">Пераканайцеся, што ваш адказ ветлівы і па тэме.</string>
|
||||
<string name="non_mutual_title2">Прыміце дабрыню</string>
|
||||
<string name="non_mutual_text2">Пазітыўны тон заўсёды вітаецца.</string>
|
||||
<string name="non_mutual_title3">Будзьце адкрыты</string>
|
||||
<string name="non_mutual_text3">У кожнага свой стыль зносін. Будзьце да гэтага гатовыя.</string>
|
||||
<string name="make_profile_discoverable">Зрабіць мой профіль бачным</string>
|
||||
<string name="discoverability">Адкрытасць</string>
|
||||
<string name="discoverability_help">Калі вы станеце адчыненыя для прагляду на Mastodon, вашы допісы могуць з\'яўляцца ў выніках пошуку і ў трэндах. \n\nВаш профіль можа быць прапанаваны людзям з падобнымі з вамі інтарэсамі.\n\nАдмовіцеся хаваць свой профіль, калі нехта шукае вас па імені.</string>
|
||||
<string name="app_version_copied">Нумар версіі скапіяваны ў буфер абмену</string>
|
||||
<string name="onboarding_recommendations_intro">Вы самі фармуеце сваю стужку.
|
||||
Чым больш людзей на якіх вы падпісаны, тым больш актыўнай і цікавай яна будзе.</string>
|
||||
<string name="onboarding_recommendations_title">Папулярна на Mastodon</string>
|
||||
<string name="article_by_author">Ад %s</string>
|
||||
<string name="info">Звесткі</string>
|
||||
<string name="button_reblogged">Пашыраны</string>
|
||||
<string name="button_favorited">Упадабанае</string>
|
||||
<string name="bookmarked">У закладках</string>
|
||||
<string name="join_server_x_with_invite">Далучайцеся да %s па запрашэнні</string>
|
||||
<string name="expired_invite_link">Тэрмін дзеяння запрашальнай спасылкі скончыўся</string>
|
||||
<string name="expired_clipboard_invite_link_alert">Спасылка запрашэння для %1$s у буферы абмену састарэла і не можа быць выкарыстана для рэгістрацыі.\n\nВы можаце запытаць новую спасылку ў існуючага карыстальніка. Зарэгіструйцеся праз %2$s, або абярыце іншы сервер для рэгістрацыі.</string>
|
||||
<string name="invalid_invite_link">Несапраўдная спасылка для запрашэння</string>
|
||||
<string name="invalid_clipboard_invite_link_alert">Спасылка запрашэння для %1$s у буферы абмену састарэла і не можа быць выкарыстана для рэгістрацыі.\n\nВы можаце запытаць новую спасылку ў існуючага карыстальніка. Зарэгіструйцеся праз %2$s, або абярыце іншы сервер для рэгістрацыі.</string>
|
||||
<string name="use_invite_link">Выкарыстаць запрашальную спасылку</string>
|
||||
<string name="enter_invite_link">Увядзіце запрашальную спасылку</string>
|
||||
<string name="this_invite_is_invalid">Гэтая запрашальная спасылка несапраўдная.</string>
|
||||
<string name="this_invite_has_expired">Тэрмін дзеяння гэтай запрашальнай спасылкі скончыўся.</string>
|
||||
<string name="invite_link_pasted">Спасылка ўстаўлена з буфера абмену.</string>
|
||||
<string name="need_invite_to_join_server">Каб далучыцца да %s, вам патрэбна запрашальная спасылка ад існуючага карыстальніка.</string>
|
||||
<string name="mute_user_confirm_title">Ігнараваць карыстальніка?</string>
|
||||
<string name="user_wont_know_muted">Яны не будуць ведаць, што іх заглушылі.</string>
|
||||
<string name="user_can_still_see_your_posts">Яны па-ранейшаму будуць бачыць вашыя паведамленні, але вы не будзеце бачыць іх.</string>
|
||||
<string name="you_wont_see_user_mentions">Вы не ўбачыце паведамленняў з іх згадваннем.</string>
|
||||
<string name="user_can_mention_and_follow_you">Яны могуць згадваць вас і сачыць за вамі, але вы гэтага не ўбачыце.</string>
|
||||
<string name="unmuted_user_x">Не ігнараваць %s</string>
|
||||
<string name="block_user_confirm_title">Заблакіраваць карыстальніка?</string>
|
||||
<string name="user_can_see_blocked">Яны ўбачаць, што іх заблакіравалі.</string>
|
||||
<string name="user_cant_see_each_other_posts">Яны не бачаць вашых допісаў, а вы - іх.</string>
|
||||
<string name="user_cant_mention_or_follow_you">Яны не могуць згадваць або сачыць за вамі.</string>
|
||||
<string name="unblocked_user_x">Разблакіраваць %s</string>
|
||||
<string name="block_domain_confirm_title">Заблакіраваць дамен?</string>
|
||||
<string name="do_block_server">Блакіраванне сервера</string>
|
||||
<string name="block_user_x_instead">Замест гэтага заблакіруйце %s</string>
|
||||
<string name="users_cant_see_blocked">Вы не ўбачыце допісаў і апавяшчэнняў ад карыстальнікаў на гэтым серверы.</string>
|
||||
<string name="you_wont_see_server_posts">Вы не ўбачыце ніякіх допісаў ад карыстальнікаў гэтага сервера.</string>
|
||||
<string name="server_followers_will_be_removed">Вашы падпісчыкі з гэтага сервера будуць выдаленыя.</string>
|
||||
<string name="server_cant_mention_or_follow_you">Ніхто з гэтага сервера ня можа сачыць за вамі.</string>
|
||||
<string name="server_can_interact_with_older">Людзі з гэтага сервера могуць узаемадзейнічаць з вашымі старымі допісамі.</string>
|
||||
<string name="unblocked_domain_x">Разблакіраваны дамен %s</string>
|
||||
<string name="handle_help_title">Што такое ідэнтыфікатар карыстальніка?</string>
|
||||
<string name="handle_title">Ідэнтыфікатар карыстальніка</string>
|
||||
<string name="handle_username_explanation">Іх унікальны ідэнтыфікатар на серверы. Можна знайсці карыстальнікаў з аднолькавым імем карыстальніка на розных серверах.</string>
|
||||
<string name="handle_title_own">Ваш ідэнтыфікатар</string>
|
||||
<string name="handle_username_explanation_own">Ваш унікальны ідэнтыфікатар на гэтым серверы. Можна знайсці карыстальнікаў з аднолькавым імем карыстальніка на розных серверах.</string>
|
||||
<string name="server">Сервер</string>
|
||||
<string name="handle_server_explanation">Іх лічбавы дом, дзе захоўваюцца ўсе допісы.</string>
|
||||
<string name="handle_explanation">Паколькі ідэнтыфікатары кажуць аб тым, хто чалавек і дзе ён знаходзіцца, вы можаце ўзаемадзейнічаць з людзьмі ў сацыяльнай сетцы <a> на платформах, якія падтрымліваюцца ActivityPub</a>.</string>
|
||||
<string name="handle_server_explanation_own">Ваш лічбавы дом, дзе захоўваюцца ўсе вашыя пасты. Не падабаецца гэты? Змяняйце сервер у любы час і прыводзьце сваіх падпісчыкаў.</string>
|
||||
<string name="handle_explanation_own">Паколькі ваш лагін кажа аб тым, хто вы і дзе знаходзіцеся, людзі могуць узаемадзейнічаць з вамі ў сацыяльнай сетцы <a> на платформах, якія падтрымліваюцца ActivityPub</a>.</string>
|
||||
<string name="what_is_activitypub_title">Што такое ActivityPub?</string>
|
||||
<string name="what_is_activitypub">ActivityPub - гэта мова, на якой Mastodon размаўляе з іншымі сацыяльнымі сеткамі.\n\nГэта дазваляе вам звязвацца і ўзаемадзейнічаць з людзьмі не толькі ў Mastodon, але і ў розных сацыяльных праграмах.</string>
|
||||
<string name="handle_copied">Ідэнтыфікатар карыстальніка скапіяваны ў буфер абмену.</string>
|
||||
<!-- Shown on a button that saves a file, after it was successfully saved -->
|
||||
</resources>
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
<string name="media">মিডিয়া</string>
|
||||
<string name="button_follow">ফলো করুন</string>
|
||||
<string name="button_following">ফলো করছেন</string>
|
||||
<string name="edit_profile">প্রোফাইল সংশোধন করুন</string>
|
||||
<string name="mute_user">%s -কে মিউট করুন</string>
|
||||
<string name="unmute_user">%s -কে আনমিউট করুন</string>
|
||||
<string name="block_user">%s -কে ব্লক করুন</string>
|
||||
@@ -71,16 +70,9 @@
|
||||
<item quantity="other">%,d ভোট</item>
|
||||
</plurals>
|
||||
<string name="poll_closed">বন্ধ</string>
|
||||
<string name="confirm_mute_title">অ্যাকাউন্টটি মিউট করুন</string>
|
||||
<string name="do_mute">মিউট করুন</string>
|
||||
<string name="confirm_unmute_title">অ্যাকাউন্ট টি আনমিউট করুন</string>
|
||||
<string name="confirm_unmute">%s আনমিউট নিশ্চিত করুন</string>
|
||||
<string name="do_unmute">মিউট</string>
|
||||
<string name="confirm_block_title">অ্যাকাউন্টটি ব্লক করুন</string>
|
||||
<string name="confirm_block_domain_title">ওয়েবসাইটটি ব্লক করুন</string>
|
||||
<string name="do_block">ব্লক করুন</string>
|
||||
<string name="confirm_unblock_title">অ্যাকাউন্ট আনব্লক করুন</string>
|
||||
<string name="confirm_unblock">%s আনব্লক নিশ্চিত করুন</string>
|
||||
<string name="do_unblock">আনব্লক করুন</string>
|
||||
<string name="button_blocked">ব্লক করা আছে</string>
|
||||
<string name="action_vote">ভোট</string>
|
||||
@@ -115,14 +107,11 @@
|
||||
<string name="report_personal_title">আপনি এটি আর দেখতে চান না?</string>
|
||||
<string name="back">ফিরে যান</string>
|
||||
<string name="search_communities">সার্ভারের নাম বা লিঙ্ক</string>
|
||||
<string name="instance_rules_title">সার্ভারের নিয়মাবলী</string>
|
||||
<string name="signup_title">অ্যাকাউন্ট তৈরি করুন</string>
|
||||
<string name="display_name">নাম</string>
|
||||
<string name="username">ইউজারনেম</string>
|
||||
<string name="email">ই-মেইল</string>
|
||||
<string name="password">পাসওয়ার্ড</string>
|
||||
<string name="confirm_password">পাসওয়ার্ড নিশ্চিত করুন</string>
|
||||
<string name="confirm_email_title">আপনার ইনবক্স দেখুন</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="open_email_app">ই-মেইল অ্যাপ খুলুন</string>
|
||||
<string name="resent_email">নিশ্চিতকরনের ই-মেইল পাঠানো হয়েছে</string>
|
||||
@@ -175,8 +164,6 @@
|
||||
<item quantity="one">%d মিনিট আগে</item>
|
||||
<item quantity="other">%d মিনিট আগে</item>
|
||||
</plurals>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is file size -->
|
||||
<!-- %s is server domain -->
|
||||
<!-- Shown in a progress dialog when you tap "follow all" -->
|
||||
@@ -201,4 +188,5 @@
|
||||
<!-- %s is the name of the list -->
|
||||
<!-- %s is a username -->
|
||||
<!-- %s is a time interval ("5 months") -->
|
||||
<!-- Shown on a button that saves a file, after it was successfully saved -->
|
||||
</resources>
|
||||
|
||||
@@ -25,12 +25,10 @@
|
||||
<item quantity="other">prati</item>
|
||||
</plurals>
|
||||
<string name="posts">Objave</string>
|
||||
<string name="posts_and_replies">Objave i Odgovori</string>
|
||||
<string name="media">Slike / Video</string>
|
||||
<string name="profile_about">O</string>
|
||||
<string name="button_follow">Prati</string>
|
||||
<string name="button_following">Prati</string>
|
||||
<string name="edit_profile">Uredi Profil</string>
|
||||
<string name="mute_user">Utišaj %s</string>
|
||||
<string name="unmute_user">Uključi %s</string>
|
||||
<string name="block_user">Blokiraj %s</string>
|
||||
@@ -47,19 +45,9 @@
|
||||
<string name="post_from_user">Objava od %s</string>
|
||||
<string name="poll_option_hint">Opcija %d</string>
|
||||
<string name="poll_closed">Zatvoreno</string>
|
||||
<string name="confirm_mute_title">Ušuti korisnika</string>
|
||||
<string name="confirm_mute">Potvrdi za %s</string>
|
||||
<string name="do_mute">Ušuti</string>
|
||||
<string name="confirm_unmute_title">Iskljuci šutnju</string>
|
||||
<string name="confirm_unmute">Potvrdi za %s</string>
|
||||
<string name="do_unmute">Iskljuci šutnju</string>
|
||||
<string name="confirm_block_title">Blokiraj korisnika</string>
|
||||
<string name="confirm_block_domain_title">Blokiraj domenu</string>
|
||||
<string name="confirm_block">Potvrdi blokadu za %s</string>
|
||||
<string name="do_block">Blokiraj</string>
|
||||
<string name="confirm_unblock_title">Iskljuci blokadu korisnika</string>
|
||||
<string name="confirm_unblock_domain_title">Iskljuci blokadu domene</string>
|
||||
<string name="confirm_unblock">Potvrdi deblokadu %s</string>
|
||||
<string name="do_unblock">Iskljuci blokadu</string>
|
||||
<string name="button_blocked">Blokiran</string>
|
||||
<string name="action_vote">Glasaj</string>
|
||||
@@ -116,7 +104,6 @@
|
||||
<string name="theme_dark">Tamna</string>
|
||||
<string name="settings_behavior">Ponašanje</string>
|
||||
<string name="settings_gif">Play animirane avatare i smajlije</string>
|
||||
<string name="settings_custom_tabs">Koristi preglednik aplikacije</string>
|
||||
<string name="settings_notifications">Obavijesti</string>
|
||||
<string name="settings_contribute">Pomozite Mastodon-u</string>
|
||||
<string name="settings_tos">Uslovi korištenja</string>
|
||||
@@ -153,8 +140,6 @@
|
||||
<string name="downloading">Downloading…</string>
|
||||
<!-- %s is the server domain -->
|
||||
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is file size -->
|
||||
<!-- %s is server domain -->
|
||||
<!-- Shown in a progress dialog when you tap "follow all" -->
|
||||
@@ -176,4 +161,5 @@
|
||||
<!-- %s is the name of the list -->
|
||||
<!-- %s is a username -->
|
||||
<!-- %s is a time interval ("5 months") -->
|
||||
<!-- Shown on a button that saves a file, after it was successfully saved -->
|
||||
</resources>
|
||||
|
||||
@@ -25,12 +25,10 @@
|
||||
<item quantity="other">seguint</item>
|
||||
</plurals>
|
||||
<string name="posts">Publicacions</string>
|
||||
<string name="posts_and_replies">Amb respostes</string>
|
||||
<string name="media">Multimèdia</string>
|
||||
<string name="profile_about">Quant a</string>
|
||||
<string name="button_follow">Segueix</string>
|
||||
<string name="button_following">Seguint</string>
|
||||
<string name="edit_profile">Edita el perfil</string>
|
||||
<string name="mute_user">Silencia %s</string>
|
||||
<string name="unmute_user">Deixa de silenciar %s</string>
|
||||
<string name="block_user">Bloca %s</string>
|
||||
@@ -79,19 +77,9 @@
|
||||
<item quantity="other">%d dies restants</item>
|
||||
</plurals>
|
||||
<string name="poll_closed">Finalitzada</string>
|
||||
<string name="confirm_mute_title">Silencia el compte</string>
|
||||
<string name="confirm_mute">Confirma per a silenciar %s</string>
|
||||
<string name="do_mute">Silencia</string>
|
||||
<string name="confirm_unmute_title">Deixar de silenciar el compte</string>
|
||||
<string name="confirm_unmute">Confirma per deixar de silenciar %s</string>
|
||||
<string name="do_unmute">Deixa de silenciar</string>
|
||||
<string name="confirm_block_title">Bloca el compte</string>
|
||||
<string name="confirm_block_domain_title">Bloca el domini</string>
|
||||
<string name="confirm_block">Confirma per blocar %s</string>
|
||||
<string name="do_block">Bloca</string>
|
||||
<string name="confirm_unblock_title">Desbloca el compte</string>
|
||||
<string name="confirm_unblock_domain_title">Desbloca el domini</string>
|
||||
<string name="confirm_unblock">Confirma per desbloquejar %s</string>
|
||||
<string name="do_unblock">Desbloca</string>
|
||||
<string name="button_blocked">Blocat</string>
|
||||
<string name="action_vote">Vota</string>
|
||||
@@ -130,8 +118,6 @@
|
||||
<string name="unfollow">Deixar de seguir</string>
|
||||
<string name="back">Enrere</string>
|
||||
<string name="search_communities">Nom del servidor o URL</string>
|
||||
<string name="instance_rules_title">Normes del servidor</string>
|
||||
<string name="signup_title">Crear un compte</string>
|
||||
<string name="display_name">Nom</string>
|
||||
<string name="username">Nom d\'usuari</string>
|
||||
<string name="email">Correu electrònic</string>
|
||||
@@ -139,7 +125,6 @@
|
||||
<string name="confirm_password">Confirmar contrasenya</string>
|
||||
<string name="password_note">Inclou lletres majúscules, caràcters especials i números per augmentar la seguretat de la teva contrasenya.</string>
|
||||
<string name="category_general">General</string>
|
||||
<string name="confirm_email_title">Comprova la teva safata d\'entrada</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="resend">Reenvia</string>
|
||||
<string name="open_email_app">Obre l\'aplicació de correu</string>
|
||||
@@ -164,7 +149,6 @@
|
||||
<string name="theme_dark">Fosc</string>
|
||||
<string name="settings_behavior">Comportament</string>
|
||||
<string name="settings_gif">Reprodueix emojis i avatar animats</string>
|
||||
<string name="settings_custom_tabs">Utilitza el navegador intern</string>
|
||||
<string name="settings_notifications">Notificacions</string>
|
||||
<string name="settings_contribute">Contribueix a Mastodon</string>
|
||||
<string name="settings_tos">Condicions de servei</string>
|
||||
@@ -202,7 +186,6 @@
|
||||
<string name="local_timeline">Local</string>
|
||||
<!-- %s is the server domain -->
|
||||
<string name="load_missing_posts">Carrega les publicacions faltants</string>
|
||||
<string name="follow_back">Segueix</string>
|
||||
<string name="button_follow_pending">Pendent</string>
|
||||
<string name="follows_you">Et segueix</string>
|
||||
<string name="manually_approves_followers">Aprova seguidors manualment</string>
|
||||
@@ -254,20 +237,15 @@
|
||||
<string name="file_size_mb">%.2f MB</string>
|
||||
<string name="file_size_gb">%.2f GB</string>
|
||||
<string name="upload_processing">S\'està processant…</string>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is file size -->
|
||||
<string name="download_update">Baixa (%s)</string>
|
||||
<string name="install_update">Instal·la</string>
|
||||
<string name="i_agree">D’acord</string>
|
||||
<string name="empty_list">Aquesta llista està buida</string>
|
||||
<string name="instance_signup_closed">Aquest servidor no accepta nous registres.</string>
|
||||
<string name="text_copied">Copiat al porta-retalls</string>
|
||||
<string name="add_bookmark">Marca</string>
|
||||
<string name="remove_bookmark">Elimina el marcador</string>
|
||||
<string name="bookmarks">Marcadors</string>
|
||||
<string name="your_favorites">Els meus preferits</string>
|
||||
<string name="login_title">Hola de nou</string>
|
||||
<string name="login_subtitle">Inicia sessió amb el servidor on vas crear el compte.</string>
|
||||
<string name="server_url">URL del servidor</string>
|
||||
<string name="server_filter_region_europe">Europa</string>
|
||||
@@ -300,4 +278,5 @@
|
||||
<!-- %s is the name of the list -->
|
||||
<!-- %s is a username -->
|
||||
<!-- %s is a time interval ("5 months") -->
|
||||
<!-- Shown on a button that saves a file, after it was successfully saved -->
|
||||
</resources>
|
||||
|
||||
@@ -35,13 +35,10 @@
|
||||
<item quantity="other">sledovaných</item>
|
||||
</plurals>
|
||||
<string name="posts">Příspěvky</string>
|
||||
<string name="posts_and_replies">Příspěvky a odpovědi</string>
|
||||
<string name="media">Média</string>
|
||||
<string name="profile_about">O uživateli</string>
|
||||
<string name="button_follow">Sledovat</string>
|
||||
<string name="button_following">Sleduji</string>
|
||||
<string name="edit_profile">Upravit profil</string>
|
||||
<string name="share_user">Sdílet profil</string>
|
||||
<string name="mute_user">Skrýt %s</string>
|
||||
<string name="unmute_user">Zrušit skrytí @%s</string>
|
||||
<string name="block_user">Blokovat %s</string>
|
||||
@@ -112,19 +109,9 @@
|
||||
<item quantity="other">%,d hlasů</item>
|
||||
</plurals>
|
||||
<string name="poll_closed">Uzavřeno</string>
|
||||
<string name="confirm_mute_title">Skrýt účet</string>
|
||||
<string name="confirm_mute">Potvrdit skrytí %s</string>
|
||||
<string name="do_mute">Skrýt</string>
|
||||
<string name="confirm_unmute_title">Zrušit skrytí účtu</string>
|
||||
<string name="confirm_unmute">Potvrdit zrušení skrytí %s</string>
|
||||
<string name="do_unmute">Zrušit skrytí</string>
|
||||
<string name="confirm_block_title">Blokovat účet</string>
|
||||
<string name="confirm_block_domain_title">Blokovat doménu</string>
|
||||
<string name="confirm_block">Potvrdit blokování %s</string>
|
||||
<string name="do_block">Blokovat</string>
|
||||
<string name="confirm_unblock_title">Odblokovat účet</string>
|
||||
<string name="confirm_unblock_domain_title">Odblokovat doménu</string>
|
||||
<string name="confirm_unblock">Potvrdit odblokování %s</string>
|
||||
<string name="do_unblock">Odblokovat</string>
|
||||
<string name="button_blocked">Blokovaný</string>
|
||||
<string name="action_vote">Hlasovat</string>
|
||||
@@ -178,9 +165,7 @@
|
||||
<string name="report_personal_subtitle">Tady jsou vaše možnosti pro řízení toho, co na Mastodonu vidíte:</string>
|
||||
<string name="back">Zpět</string>
|
||||
<string name="search_communities">Název nebo URL serveru</string>
|
||||
<string name="instance_rules_title">Pravidla serveru</string>
|
||||
<string name="instance_rules_subtitle">Pokračováním souhlasíte s následujícími pravidly, která jsou nastavena a prosazována moderátory %s.</string>
|
||||
<string name="signup_title">Vytvořit účet</string>
|
||||
<string name="display_name">Jméno</string>
|
||||
<string name="username">Uživatelské jméno</string>
|
||||
<string name="email">E-mail</string>
|
||||
@@ -188,7 +173,6 @@
|
||||
<string name="confirm_password">Potvrdit heslo</string>
|
||||
<string name="password_note">Použijte velká písmena, speciální znaky a čísla, abyste zvýšili sílu hesla.</string>
|
||||
<string name="category_general">Obecné</string>
|
||||
<string name="confirm_email_title">Zkontrolujte si příchozí poštu</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="confirm_email_subtitle">Klepněte na odkaz, který jsme vám poslali, abyste ověřili %s. Budeme tu na vás čekat.</string>
|
||||
<string name="confirm_email_didnt_get">Nedostali jste odkaz?</string>
|
||||
@@ -225,7 +209,6 @@
|
||||
<string name="theme_dark">Tmavý</string>
|
||||
<string name="settings_behavior">Chování</string>
|
||||
<string name="settings_gif">Přehrávat animované avatary a emoji</string>
|
||||
<string name="settings_custom_tabs">Používat prohlížeč v aplikaci</string>
|
||||
<string name="settings_notifications">Oznámení</string>
|
||||
<string name="settings_contribute">Přispějte do Mastodonu</string>
|
||||
<string name="settings_tos">Podmínky užití</string>
|
||||
@@ -254,8 +237,6 @@
|
||||
<string name="followed_user">Nyní sledujete %s</string>
|
||||
<string name="following_user_requested">Požádáno o sledování %s</string>
|
||||
<string name="open_in_browser">Otevřít v prohlížeči</string>
|
||||
<string name="hide_boosts_from_user">Skrýt boosty od %s</string>
|
||||
<string name="show_boosts_from_user">Zobrazit boosty od %s</string>
|
||||
<string name="signup_reason">Proč se chcete připojit?</string>
|
||||
<string name="signup_reason_note">Toto nám pomůže posoudit vaši žádost.</string>
|
||||
<string name="clear">Vyčistit</string>
|
||||
@@ -278,7 +259,6 @@
|
||||
<string name="recommended_accounts_info_banner">Podle toho, koho sledujete, by se vám mohly líbit tyto účty.</string>
|
||||
<string name="see_new_posts">Nové příspěvky</string>
|
||||
<string name="load_missing_posts">Načíst chybějící příspěvky</string>
|
||||
<string name="follow_back">Sledovat nazpět</string>
|
||||
<string name="button_follow_pending">Čekající</string>
|
||||
<string name="follows_you">Sleduje vás</string>
|
||||
<string name="manually_approves_followers">Ručně schvaluje sledující</string>
|
||||
@@ -346,28 +326,18 @@
|
||||
<string name="file_size_mb">%.2f MB</string>
|
||||
<string name="file_size_gb">%.2f GB</string>
|
||||
<string name="upload_processing">Zpracovávání…</string>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is file size -->
|
||||
<string name="download_update">Stáhnout (%s)</string>
|
||||
<string name="install_update">Instalovat</string>
|
||||
<string name="privacy_policy_title">Vaše soukromí</string>
|
||||
<string name="privacy_policy_subtitle">Přestože aplikace Mastodon neshromažďuje žádná data, server, který se přihlásíte, může mít jiné zásady.\n\nPokud nesouhlasíte se zásadami pro %s, můžete se vrátit a vybrat jiný server.</string>
|
||||
<string name="i_agree">Souhlasím</string>
|
||||
<string name="empty_list">Tento seznam je prázdný</string>
|
||||
<string name="instance_signup_closed">Tento server nepřijímá nové registrace.</string>
|
||||
<string name="text_copied">Zkopírováno do schránky</string>
|
||||
<string name="add_bookmark">Přidat do záložek</string>
|
||||
<string name="remove_bookmark">Odstranit ze záložek</string>
|
||||
<string name="bookmarks">Záložky</string>
|
||||
<string name="your_favorites">Vaše oblíbení</string>
|
||||
<string name="login_title">Vítejte zpět</string>
|
||||
<string name="login_subtitle">Přihlaste se pomocí serveru, kde jste vytvořili svůj účet.</string>
|
||||
<string name="server_url">URL serveru</string>
|
||||
<string name="server_filter_any_language">Libovolný jazyk</string>
|
||||
<string name="server_filter_instant_signup">Okamžitá registrace</string>
|
||||
<string name="server_filter_manual_review">Ruční kontrola</string>
|
||||
<string name="server_filter_any_signup_speed">Jakákoliv rychlost registrace</string>
|
||||
<string name="server_filter_region_europe">Evropa</string>
|
||||
<string name="server_filter_region_north_america">Severní Amerika</string>
|
||||
<string name="server_filter_region_south_america">Jižní Amerika</string>
|
||||
@@ -375,7 +345,6 @@
|
||||
<string name="server_filter_region_asia">Asie</string>
|
||||
<string name="server_filter_region_oceania">Oceánie</string>
|
||||
<string name="not_accepting_new_members">Nepřijímá nové členy</string>
|
||||
<string name="category_special_interests">Speciální zájmy</string>
|
||||
<string name="signup_passwords_dont_match">Hesla se neshodují</string>
|
||||
<string name="profile_add_row">Přidat řádek</string>
|
||||
<string name="profile_setup">Nastavení profilu</string>
|
||||
@@ -668,7 +637,6 @@
|
||||
<string name="remove">Odstranit</string>
|
||||
<string name="add_list_member">Přidat člena</string>
|
||||
<string name="search_among_people_you_follow">Hledejte mezi lidmi, které sledujete</string>
|
||||
<string name="add_user_to_list">Přidat do seznamu…</string>
|
||||
<string name="add_user_to_list_title">Přidat do seznamu</string>
|
||||
<!-- %s is a username -->
|
||||
<string name="manage_user_lists">Spravovat seznamy, v kterých je %s</string>
|
||||
@@ -736,4 +704,38 @@
|
||||
<string name="this_invite_has_expired">Platnost tohoto odkazu pozvánky vypršela.</string>
|
||||
<string name="invite_link_pasted">Odkaz vložen ze schránky.</string>
|
||||
<string name="need_invite_to_join_server">Chcete-li se připojit k %s, budete potřebovat odkaz pozvánky od existujícího uživatele.</string>
|
||||
<string name="mute_user_confirm_title">Skrýt uživatele?</string>
|
||||
<string name="user_wont_know_muted">Nebude vědět, že byl skryt.</string>
|
||||
<string name="user_can_still_see_your_posts">Stále bude moct vidět vaše příspěvky, ale vy neuvidíte jeho.</string>
|
||||
<string name="you_wont_see_user_mentions">Neuvidíte příspěvky, které ho zmiňují.</string>
|
||||
<string name="user_can_mention_and_follow_you">Můžou vás zmiňovat a sledovat, ale neuvidíte to.</string>
|
||||
<string name="unmuted_user_x">Zrušit skrytí %s</string>
|
||||
<string name="block_user_confirm_title">Blokovat uživatele?</string>
|
||||
<string name="user_can_see_blocked">Může vidět, že je zablokovaný.</string>
|
||||
<string name="user_cant_see_each_other_posts">Nemůže vidět vaše příspěvky a vy neuvidíte jeho.</string>
|
||||
<string name="user_cant_mention_or_follow_you">Nemůže vás zmiňovat nebo sledovat.</string>
|
||||
<string name="unblocked_user_x">Odblokovat %s</string>
|
||||
<string name="block_domain_confirm_title">Blokovat doménu?</string>
|
||||
<string name="do_block_server">Blokovat server</string>
|
||||
<string name="block_user_x_instead">Zablokovat místo toho %s</string>
|
||||
<string name="users_cant_see_blocked">Neuvidíte příspěvky a upozornění od uživatelů na tomto serveru.</string>
|
||||
<string name="you_wont_see_server_posts">Neuvidíte žádné příspěvky od uživatelů na tomto serveru.</string>
|
||||
<string name="server_followers_will_be_removed">Vaši sledující z tohoto serveru budou odstraněni.</string>
|
||||
<string name="server_cant_mention_or_follow_you">Nikdo z tohoto serveru vás nemůže sledovat.</string>
|
||||
<string name="server_can_interact_with_older">Lidé z tohoto serveru mohou reagovat na vaše staré příspěvky.</string>
|
||||
<string name="unblocked_domain_x">Odblokovat doménu %s</string>
|
||||
<string name="handle_help_title">Co je ve jméně?</string>
|
||||
<string name="handle_title">Jméno</string>
|
||||
<string name="handle_username_explanation">Jedinečný identifikátor na serveru. Je možné najít uživatele se stejným uživatelským jménem na různých serverech.</string>
|
||||
<string name="handle_title_own">Vaše jméno</string>
|
||||
<string name="handle_username_explanation_own">Jedinečný identifikátor na tomto serveru. Je možné najít uživatele se stejným uživatelským jménem na různých serverech.</string>
|
||||
<string name="server">Server</string>
|
||||
<string name="handle_server_explanation">Jejich digitální domov, kde žijí všechny jejich příspěvky.</string>
|
||||
<string name="handle_explanation">Protože jméno říká, kdo je a kde je, můžete komunikovat s lidmi přes sociální síť <a>platforem postavených na ActivityPub</a>.</string>
|
||||
<string name="handle_server_explanation_own">Váš digitální domov, kde všechny vaše příspěvky žijí. Nelíbí se vám tento? Přecházejte mezi servery kdykoliv a vezměte s sebou také své sledující.</string>
|
||||
<string name="handle_explanation_own">Protože vaše jméno říká, kdo jste a kde jste, lidé s vámi mohou komunikovat přes sociální web <a>platforem postavených na ActivityPub</a>.</string>
|
||||
<string name="what_is_activitypub_title">Co je ActivityPub?</string>
|
||||
<string name="what_is_activitypub">ActivityPub je jako jazyk, kterým Mastodon mluví s ostatními sociálními sítěmi.\n\nUmožňuje vám se propojit a komunikovat s lidmi nejen na Mastodonu, ale také napříč různými sociálními aplikacemi.</string>
|
||||
<string name="handle_copied">Jméno zkopírováno do schránky.</string>
|
||||
<!-- Shown on a button that saves a file, after it was successfully saved -->
|
||||
</resources>
|
||||
|
||||
@@ -31,13 +31,10 @@
|
||||
<item quantity="other">følger</item>
|
||||
</plurals>
|
||||
<string name="posts">Indlæg</string>
|
||||
<string name="posts_and_replies">Indlæg og svar</string>
|
||||
<string name="media">Medier</string>
|
||||
<string name="profile_about">Om</string>
|
||||
<string name="button_follow">Følg</string>
|
||||
<string name="button_following">Følger</string>
|
||||
<string name="edit_profile">Redigér profil</string>
|
||||
<string name="share_user">Del profil</string>
|
||||
<string name="mute_user">Gør tavs %s</string>
|
||||
<string name="unmute_user">Vis %s igen</string>
|
||||
<string name="block_user">Blokér %s</string>
|
||||
@@ -90,19 +87,9 @@
|
||||
<item quantity="other">%,d stemmer</item>
|
||||
</plurals>
|
||||
<string name="poll_closed">Lukket</string>
|
||||
<string name="confirm_mute_title">Tavsgør konto</string>
|
||||
<string name="confirm_mute">Bekræft tavsgørelse af %s</string>
|
||||
<string name="do_mute">Tavsgør</string>
|
||||
<string name="confirm_unmute_title">Vis konto igen</string>
|
||||
<string name="confirm_unmute">Bekræft, at %s ikke længere er tavsgjort</string>
|
||||
<string name="do_unmute">Ophæv tavsgørelse</string>
|
||||
<string name="confirm_block_title">Blokér konto</string>
|
||||
<string name="confirm_block_domain_title">Blokér domæne</string>
|
||||
<string name="confirm_block">Bekræft blokering af %s</string>
|
||||
<string name="do_block">Blokér</string>
|
||||
<string name="confirm_unblock_title">Afblokér konto</string>
|
||||
<string name="confirm_unblock_domain_title">Afblokér domæne</string>
|
||||
<string name="confirm_unblock">Bekræft afblokeringen af %s</string>
|
||||
<string name="do_unblock">Afblokér</string>
|
||||
<string name="button_blocked">Blokeret</string>
|
||||
<string name="action_vote">Stem</string>
|
||||
@@ -154,9 +141,7 @@
|
||||
<string name="report_personal_subtitle">Her er mulighederne for at styre, hvad du ser på Mastodon:</string>
|
||||
<string name="back">Retur</string>
|
||||
<string name="search_communities">Servernavn eller -URL</string>
|
||||
<string name="instance_rules_title">Serverregler</string>
|
||||
<string name="instance_rules_subtitle">Ved at fortsætte accepterer du at overholde flg. regler, som angivet og håndhævet af %s moderatorerne.</string>
|
||||
<string name="signup_title">Opret konto</string>
|
||||
<string name="display_name">Navn</string>
|
||||
<string name="username">Brugernavn</string>
|
||||
<string name="email">E-mail</string>
|
||||
@@ -164,7 +149,6 @@
|
||||
<string name="confirm_password">Bekræft adgangskode</string>
|
||||
<string name="password_note">Benyt majuskler, specialtegn og tal for at øge adgangskodens styrke.</string>
|
||||
<string name="category_general">Generelt</string>
|
||||
<string name="confirm_email_title">Tjek din indbakke</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="confirm_email_subtitle">Tryk på det modtage link for at bekræfte %s. Vi venter her så længe.</string>
|
||||
<string name="confirm_email_didnt_get">Modtog intet link?</string>
|
||||
@@ -199,7 +183,6 @@
|
||||
<string name="theme_dark">Mørk</string>
|
||||
<string name="settings_behavior">Adfærd</string>
|
||||
<string name="settings_gif">Afspil animerede avatarer og emojier</string>
|
||||
<string name="settings_custom_tabs">Benyt in-app browser</string>
|
||||
<string name="settings_notifications">Notifikationer</string>
|
||||
<string name="settings_contribute">Bidrag til Mastodon</string>
|
||||
<string name="settings_tos">Tjenestevilkår</string>
|
||||
@@ -228,8 +211,6 @@
|
||||
<string name="followed_user">Du følger nu %s</string>
|
||||
<string name="following_user_requested">Anmodede om at følge %s</string>
|
||||
<string name="open_in_browser">Åbn i browser</string>
|
||||
<string name="hide_boosts_from_user">Skjul fremhævninger fra %s</string>
|
||||
<string name="show_boosts_from_user">Vis fremhævninger fra %s</string>
|
||||
<string name="signup_reason">Hvorfor ønsker du at tilmelde dig?</string>
|
||||
<string name="signup_reason_note">Dette hjælper os med at vurdere din ansøgning.</string>
|
||||
<string name="clear">Ryd</string>
|
||||
@@ -251,7 +232,6 @@
|
||||
<string name="local_timeline_info_banner">Disse er alle indlæg fra alle brugere på din server (%s).</string>
|
||||
<string name="recommended_accounts_info_banner">Baseret på andre, du følger, vil du måske synes om disse konti.</string>
|
||||
<string name="load_missing_posts">Indlæs manglende indlæg</string>
|
||||
<string name="follow_back">Følg Tilbage</string>
|
||||
<string name="button_follow_pending">Afventer</string>
|
||||
<string name="follows_you">Følger dig</string>
|
||||
<string name="manually_approves_followers">Godkender følgere manuelt</string>
|
||||
@@ -307,28 +287,18 @@
|
||||
<string name="file_size_mb">%.2f MB</string>
|
||||
<string name="file_size_gb">%.2f GB</string>
|
||||
<string name="upload_processing">Behandler…</string>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is file size -->
|
||||
<string name="download_update">Download (%s)</string>
|
||||
<string name="install_update">Installér</string>
|
||||
<string name="privacy_policy_title">Din fortrolighed</string>
|
||||
<string name="privacy_policy_subtitle">Selvom Mastodon-appen ikke indsamler data, kan serveren, via hvilken man tilmelder sig, have en anden politik.\n\nEr man uenig i politikken for %s, kan man gå tilbage og vælge en anden server.</string>
|
||||
<string name="i_agree">Jeg accepterer</string>
|
||||
<string name="empty_list">Denne liste er tom</string>
|
||||
<string name="instance_signup_closed">Denne server accepterer ikke nye tilmeldinger.</string>
|
||||
<string name="text_copied">Kopieret til udklipsholderen</string>
|
||||
<string name="add_bookmark">Bogmærk</string>
|
||||
<string name="remove_bookmark">Fjern bogmærke</string>
|
||||
<string name="bookmarks">Bogmærker</string>
|
||||
<string name="your_favorites">Dine Favoritter</string>
|
||||
<string name="login_title">Velkommen tilbage</string>
|
||||
<string name="login_subtitle">Log ind med serveren, på hvilken din konto blev oprettet.</string>
|
||||
<string name="server_url">Server-URL</string>
|
||||
<string name="server_filter_any_language">Hvilket som helst sprog</string>
|
||||
<string name="server_filter_instant_signup">Øjeblikkelig tilmelding</string>
|
||||
<string name="server_filter_manual_review">Manuel evaluering</string>
|
||||
<string name="server_filter_any_signup_speed">Hvilken som helst tilmeldingshastghed</string>
|
||||
<string name="server_filter_region_europe">Europa</string>
|
||||
<string name="server_filter_region_north_america">Nordamerika</string>
|
||||
<string name="server_filter_region_south_america">Sydamerika</string>
|
||||
@@ -336,7 +306,6 @@
|
||||
<string name="server_filter_region_asia">Asien</string>
|
||||
<string name="server_filter_region_oceania">Oceania</string>
|
||||
<string name="not_accepting_new_members">Ikke åben for nye medlemmer</string>
|
||||
<string name="category_special_interests">Særlige Interesser</string>
|
||||
<string name="signup_passwords_dont_match">Adgangskoder matcher ikke</string>
|
||||
<string name="profile_add_row">Tilføj række</string>
|
||||
<string name="profile_setup">Profilopsætning</string>
|
||||
@@ -491,4 +460,5 @@
|
||||
<!-- %s is the name of the list -->
|
||||
<!-- %s is a username -->
|
||||
<!-- %s is a time interval ("5 months") -->
|
||||
<!-- Shown on a button that saves a file, after it was successfully saved -->
|
||||
</resources>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<string name="button_follow">Folgen</string>
|
||||
<string name="button_following">Folge ich</string>
|
||||
<string name="edit_profile">Profil bearbeiten</string>
|
||||
<string name="share_user">Profil teilen</string>
|
||||
<string name="share_user">Profil teilen…</string>
|
||||
<string name="mute_user">%s stummschalten</string>
|
||||
<string name="unmute_user">%s nicht mehr stummschalten</string>
|
||||
<string name="block_user">%s sperren</string>
|
||||
@@ -90,19 +90,9 @@
|
||||
<item quantity="other">%,d Stimmen</item>
|
||||
</plurals>
|
||||
<string name="poll_closed">Beendet</string>
|
||||
<string name="confirm_mute_title">Konto stummschalten</string>
|
||||
<string name="confirm_mute">Bestätigen, um %s stummzuschalten</string>
|
||||
<string name="do_mute">Stummschalten</string>
|
||||
<string name="confirm_unmute_title">Konto nicht mehr stummschalten</string>
|
||||
<string name="confirm_unmute">Bestätigen, um %s nicht mehr stummzuschalten</string>
|
||||
<string name="do_unmute">Nicht mehr stummschalten</string>
|
||||
<string name="confirm_block_title">Konto sperren</string>
|
||||
<string name="confirm_block_domain_title">Domain sperren</string>
|
||||
<string name="confirm_block">Bestätigen, um %s zu blockieren</string>
|
||||
<string name="do_block">Sperren</string>
|
||||
<string name="confirm_unblock_title">Konto nicht mehr sperren</string>
|
||||
<string name="confirm_unblock_domain_title">Domain nicht mehr blockieren</string>
|
||||
<string name="confirm_unblock">Bestätigen, um %s nicht mehr zu blockieren</string>
|
||||
<string name="do_unblock">Nicht mehr blockieren</string>
|
||||
<string name="button_blocked">Blockiert</string>
|
||||
<string name="action_vote">Abstimmen</string>
|
||||
@@ -154,9 +144,9 @@
|
||||
<string name="report_personal_subtitle">Das sind deine Möglichkeiten zu bestimmen, was du auf Mastodon sehen möchtest:</string>
|
||||
<string name="back">Zurück</string>
|
||||
<string name="search_communities">Servername oder -adresse</string>
|
||||
<string name="instance_rules_title">Server-Regeln</string>
|
||||
<string name="instance_rules_title">Serverregeln</string>
|
||||
<string name="instance_rules_subtitle">Solltest du fortfahren, erklärst du dich mit den Serverregeln, die die Moderator*innen von %s aufgestellt haben und durchsetzen werden, einverstanden.</string>
|
||||
<string name="signup_title">Konto erstellen</string>
|
||||
<string name="signup_title">Account erstellen</string>
|
||||
<string name="display_name">Name</string>
|
||||
<string name="username">Profilname</string>
|
||||
<string name="email">E-Mail</string>
|
||||
@@ -164,7 +154,7 @@
|
||||
<string name="confirm_password">Passwort bestätigen</string>
|
||||
<string name="password_note">Verwende Großbuchstaben, Sonderzeichen und Zahlen, um deine Passwortstärke zu erhöhen.</string>
|
||||
<string name="category_general">Allgemein</string>
|
||||
<string name="confirm_email_title">Überprüfe deinen Posteingang</string>
|
||||
<string name="confirm_email_title">Überprüfe dein E-Mail-Postfach</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="confirm_email_subtitle">Bitte klicke auf den Link, den wir an %s geschickt haben, um dich zu verifizieren. Wir warten bis dahin einfach hier.</string>
|
||||
<string name="confirm_email_didnt_get">Kein Link angekommen?</string>
|
||||
@@ -199,7 +189,6 @@
|
||||
<string name="theme_dark">Dunkel</string>
|
||||
<string name="settings_behavior">Verhalten</string>
|
||||
<string name="settings_gif">Animierte GIFs, Avatare und Emojis abspielen</string>
|
||||
<string name="settings_custom_tabs">In-App-Browser verwenden</string>
|
||||
<string name="settings_notifications">Benachrichtigungen</string>
|
||||
<string name="settings_contribute">Zu Mastodon beitragen</string>
|
||||
<string name="settings_tos">Nutzungsbedingungen</string>
|
||||
@@ -228,8 +217,6 @@
|
||||
<string name="followed_user">Du folgst nun %s</string>
|
||||
<string name="following_user_requested">Deine Follower-Anfrage an %s wurde gesendet</string>
|
||||
<string name="open_in_browser">Im Browser öffnen</string>
|
||||
<string name="hide_boosts_from_user">Geteilte Beiträge von %s ausblenden</string>
|
||||
<string name="show_boosts_from_user">Geteilte Beiträge von %s anzeigen</string>
|
||||
<string name="signup_reason">Warum möchtest du beitreten?</string>
|
||||
<string name="signup_reason_note">Das erleichtert uns die Prüfung deiner Anmeldung.</string>
|
||||
<string name="clear">Leeren</string>
|
||||
@@ -252,7 +239,7 @@
|
||||
<string name="recommended_accounts_info_banner">Dir könnten diese Konten gefallen, basierend auf Leuten, denen du folgst.</string>
|
||||
<string name="see_new_posts">Neue Beiträge</string>
|
||||
<string name="load_missing_posts">Weitere Beiträge laden</string>
|
||||
<string name="follow_back">Zurückfolgen</string>
|
||||
<string name="follow_back">Ebenfalls folgen</string>
|
||||
<string name="button_follow_pending">Ausstehend</string>
|
||||
<string name="follows_you">Folgt dir</string>
|
||||
<string name="manually_approves_followers">Manuelles Genehmigen von Followern</string>
|
||||
@@ -308,14 +295,10 @@
|
||||
<string name="file_size_mb">%.2f MB</string>
|
||||
<string name="file_size_gb">%.2f GB</string>
|
||||
<string name="upload_processing">Wird verarbeitet …</string>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is file size -->
|
||||
<string name="download_update">(%s) herunterladen</string>
|
||||
<string name="install_update">Installieren</string>
|
||||
<string name="privacy_policy_title">Deine Privatsphäre</string>
|
||||
<string name="privacy_policy_subtitle">Auch wenn die Mastodon-App keine Daten sammelt, kann der Server, über den du dich anmeldest, eine andere Richtlinie haben.\n\nWenn du mit der Richtlinie für %s nicht einverstanden bist, kannst du zurückkehren und einen anderen Server wählen.</string>
|
||||
<string name="i_agree">Ich stimme zu</string>
|
||||
<string name="empty_list">Diese Liste ist leer</string>
|
||||
<string name="instance_signup_closed">Dieser Server akzeptiert keine neuen Registrierungen.</string>
|
||||
<string name="text_copied">In die Zwischenablage kopiert</string>
|
||||
@@ -324,12 +307,8 @@
|
||||
<string name="bookmarks">Lesezeichen</string>
|
||||
<string name="your_favorites">Deine Favoriten</string>
|
||||
<string name="login_title">Willkommen zurück</string>
|
||||
<string name="login_subtitle">Melde dich mit dem Server an, auf dem du dein Konto erstellt hast.</string>
|
||||
<string name="login_subtitle">Melde dich mit dem Server an, auf dem du dein Account erstellt hast.</string>
|
||||
<string name="server_url">Serveradresse</string>
|
||||
<string name="server_filter_any_language">Alle Sprachen</string>
|
||||
<string name="server_filter_instant_signup">Schnellregistrierung</string>
|
||||
<string name="server_filter_manual_review">Manuelle Überprüfung</string>
|
||||
<string name="server_filter_any_signup_speed">Beliebige Registrierungsdauer</string>
|
||||
<string name="server_filter_region_europe">Europa</string>
|
||||
<string name="server_filter_region_north_america">Nordamerika</string>
|
||||
<string name="server_filter_region_south_america">Südamerika</string>
|
||||
@@ -337,7 +316,6 @@
|
||||
<string name="server_filter_region_asia">Asien</string>
|
||||
<string name="server_filter_region_oceania">Ozeanien</string>
|
||||
<string name="not_accepting_new_members">Keine Aufnahme neuer Mitglieder</string>
|
||||
<string name="category_special_interests">Besondere Interessen</string>
|
||||
<string name="signup_passwords_dont_match">Passwörter stimmen nicht überein</string>
|
||||
<string name="profile_add_row">Zeile hinzufügen</string>
|
||||
<string name="profile_setup">Profil einrichten</string>
|
||||
@@ -359,7 +337,7 @@
|
||||
<string name="profile_featured">Empfohlen</string>
|
||||
<string name="profile_timeline">Timeline</string>
|
||||
<string name="view_all">Alle anzeigen</string>
|
||||
<string name="profile_endorsed_accounts">Konten</string>
|
||||
<string name="profile_endorsed_accounts">Accounts</string>
|
||||
<string name="verified_link">Verifizierter Link</string>
|
||||
<string name="show">Anzeigen</string>
|
||||
<string name="hide">Ausblenden</string>
|
||||
@@ -574,7 +552,12 @@
|
||||
<string name="settings_privacy">Datenschutz und Reichweite</string>
|
||||
<string name="settings_discoverable">Profil und Beiträge in Suchalgorithmen berücksichtigen</string>
|
||||
<string name="settings_indexable">Öffentliche Beiträge in die Suchergebnisse einbeziehen</string>
|
||||
<plurals name="x_posts_today">
|
||||
<item quantity="one">%,d Beitrag heute</item>
|
||||
<item quantity="other">%,d Beiträge heute</item>
|
||||
</plurals>
|
||||
<string name="error_playing_video">Fehler bei Videowiedergabe</string>
|
||||
<string name="timeline_following">Startseite</string>
|
||||
<string name="lists">Listen</string>
|
||||
<string name="followed_hashtags">Gefolgte Hashtags</string>
|
||||
<string name="manage_lists">Listen verwalten</string>
|
||||
@@ -597,7 +580,6 @@
|
||||
<string name="remove">Entfernen</string>
|
||||
<string name="add_list_member">Mitglied hinzufügen</string>
|
||||
<string name="search_among_people_you_follow">Suche nach Leuten, denen du folgst</string>
|
||||
<string name="add_user_to_list">Zur Liste hinzufügen…</string>
|
||||
<string name="add_user_to_list_title">Zur Liste hinzufügen</string>
|
||||
<!-- %s is a username -->
|
||||
<string name="manage_user_lists">Listen mit %s verwalten</string>
|
||||
@@ -608,11 +590,28 @@
|
||||
<string name="no_lists_title">Startseite mit Listen organisieren</string>
|
||||
<string name="no_lists_subtitle">Deine werden hier erscheinen</string>
|
||||
<string name="manage_accounts">Konten hinzufügen oder wechseln</string>
|
||||
<plurals name="x_posts_recently">
|
||||
<item quantity="one">%,d kürzlicher Beitrag</item>
|
||||
<item quantity="other">%,d kürzliche Beiträge</item>
|
||||
</plurals>
|
||||
<string name="create_list">Liste erstellen</string>
|
||||
<string name="step_x_of_y">Schritt %1$d von %2$d</string>
|
||||
<string name="manage_list_members">Listenmitglieder verwalten</string>
|
||||
<string name="list_no_members">Noch keine Mitglieder</string>
|
||||
<string name="posted_at">Veröffentlicht um %s Uhr</string>
|
||||
<string name="dont_remind_again">Nicht erneut erinnern</string>
|
||||
<!-- %s is a time interval ("5 months") -->
|
||||
<string name="old_post_sheet_title">Dieser Beitrag ist %s alt</string>
|
||||
<plurals name="x_months">
|
||||
<item quantity="one">%,d Monat</item>
|
||||
<item quantity="other">%,d Monate</item>
|
||||
</plurals>
|
||||
<string name="more_than_two_years">mehr als 2 Jahre</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="expired_invite_link">Abgelaufener Einladungslink</string>
|
||||
<string name="server">Server</string>
|
||||
<!-- Shown on a button that saves a file, after it was successfully saved -->
|
||||
<string name="saved">Gespeichert</string>
|
||||
<string name="image_saved">Bild gespeichert.</string>
|
||||
<string name="video_saved">Video gespeichert.</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,13 +31,10 @@
|
||||
<item quantity="other">ακολουθεί</item>
|
||||
</plurals>
|
||||
<string name="posts">Αναρτήσεις</string>
|
||||
<string name="posts_and_replies">Αναρτήσεις και Απαντήσεις</string>
|
||||
<string name="media">Πολυμέσα</string>
|
||||
<string name="profile_about">Σχετικά</string>
|
||||
<string name="button_follow">Ακολούθησε</string>
|
||||
<string name="button_following">Ακολουθείς</string>
|
||||
<string name="edit_profile">Επεξεργασία Προφίλ</string>
|
||||
<string name="share_user">Κοινοποίηση προφίλ</string>
|
||||
<string name="mute_user">Σίγαση %s</string>
|
||||
<string name="unmute_user">Κατάργηση σίγασης %s</string>
|
||||
<string name="block_user">Αποκλεισμός %s</string>
|
||||
@@ -90,19 +87,9 @@
|
||||
<item quantity="other">%,d ψήφοι</item>
|
||||
</plurals>
|
||||
<string name="poll_closed">Κλειστό</string>
|
||||
<string name="confirm_mute_title">Σίγαση Λογαριασμού</string>
|
||||
<string name="confirm_mute">Επιβεβαίωσε για σίγαση %s</string>
|
||||
<string name="do_mute">Σίγαση</string>
|
||||
<string name="confirm_unmute_title">Κατάργηση Σίγασης Λογαριασμού</string>
|
||||
<string name="confirm_unmute">Επιβεβαίωσε για κατάργηση σίγασης %s</string>
|
||||
<string name="do_unmute">Κατάργηση σίγασης</string>
|
||||
<string name="confirm_block_title">Αποκλεισμός Λογαριασμού</string>
|
||||
<string name="confirm_block_domain_title">Αποκλεισμός Τομέα</string>
|
||||
<string name="confirm_block">Επιβεβαίωση αποκλεισμού %s</string>
|
||||
<string name="do_block">Αποκλεισμός</string>
|
||||
<string name="confirm_unblock_title">Άρση Αποκλεισμού Λογαριασμού</string>
|
||||
<string name="confirm_unblock_domain_title">Άρση αποκλεισμού Τομέα</string>
|
||||
<string name="confirm_unblock">Επιβεβαίωση άρσης αποκλεισμού %s</string>
|
||||
<string name="do_unblock">Άρση αποκλεισμού</string>
|
||||
<string name="button_blocked">Αποκλείστηκε</string>
|
||||
<string name="action_vote">Ψήφισε</string>
|
||||
@@ -154,9 +141,7 @@
|
||||
<string name="report_personal_subtitle">Αυτές είναι οι επιλογές σου για να ελέγχεις τί βλέπεις στο Mastodon:</string>
|
||||
<string name="back">Πίσω</string>
|
||||
<string name="search_communities">Όνομα διακομιστή ή διεύθυνση URL</string>
|
||||
<string name="instance_rules_title">Κανόνες Διακομιστή</string>
|
||||
<string name="instance_rules_subtitle">Συνεχίζοντας, συμφωνείς να τηρείς τους ακόλουθους κανόνες που έχουν οριστεί και επιβάλλονται από τους %s συντονιστές.</string>
|
||||
<string name="signup_title">Δημιουργία Λογαριασμού</string>
|
||||
<string name="display_name">Όνομα</string>
|
||||
<string name="username">Όνομα χρήστη</string>
|
||||
<string name="email">Email</string>
|
||||
@@ -164,7 +149,6 @@
|
||||
<string name="confirm_password">Επιβεβαίωση κωδικού πρόσβασης</string>
|
||||
<string name="password_note">Να συμπεριλάβεις κεφαλαία γράμματα, ειδικούς χαρακτήρες και αριθμούς για να αυξήσεις την ισχύ του κωδικού πρόσβασης.</string>
|
||||
<string name="category_general">Γενικά</string>
|
||||
<string name="confirm_email_title">Έλεγξε τα Εισερχόμενα σου</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="confirm_email_subtitle">Πάτησε το σύνδεσμο που σού στείλαμε για να επαληθεύσεις το %s. Θα σε περιμένουμε εδώ.</string>
|
||||
<string name="confirm_email_didnt_get">Δεν έλαβες τον σύνδεσμο;</string>
|
||||
@@ -199,7 +183,6 @@
|
||||
<string name="theme_dark">Σκοτεινό</string>
|
||||
<string name="settings_behavior">Συμπεριφορά</string>
|
||||
<string name="settings_gif">Αναπαραγωγή κινούμενων άβαταρ και εμότζι</string>
|
||||
<string name="settings_custom_tabs">Χρήση περιηγητή εντός εφαρμογής</string>
|
||||
<string name="settings_notifications">Ειδοποιήσεις</string>
|
||||
<string name="settings_contribute">Συνείσφερε στο Mastodon</string>
|
||||
<string name="settings_tos">Όροι χρήσης</string>
|
||||
@@ -228,8 +211,6 @@
|
||||
<string name="followed_user">Ακολουθείς πλέον τον/την %s</string>
|
||||
<string name="following_user_requested">Έγινε αίτημα ακολούθησης του/της %s</string>
|
||||
<string name="open_in_browser">Άνοιξε στον περιηγητή</string>
|
||||
<string name="hide_boosts_from_user">Απόκρυψη ενισχύσεων από @%s</string>
|
||||
<string name="show_boosts_from_user">Εμφάνιση ενισχύσεων από @%s</string>
|
||||
<string name="signup_reason">Γιατί θέλεις να συμμετάσχεις;</string>
|
||||
<string name="signup_reason_note">Αυτό θα μας βοηθήσει να εξετάσουμε την αίτησή σου.</string>
|
||||
<string name="clear">Εκκαθάριση</string>
|
||||
@@ -252,7 +233,6 @@
|
||||
<string name="recommended_accounts_info_banner">Μπορεί να σου αρέσουν αυτοί οι λογαριασμοί με βάση άλλους που ακολουθείς.</string>
|
||||
<string name="see_new_posts">Νέες αναρτήσεις</string>
|
||||
<string name="load_missing_posts">Φόρτωση αναρτήσεων που λείπουν</string>
|
||||
<string name="follow_back">Ακολούθησε και εσύ</string>
|
||||
<string name="button_follow_pending">Εκκρεμεί</string>
|
||||
<string name="follows_you">Σε ακολουθεί</string>
|
||||
<string name="manually_approves_followers">Χειροκίνητη έγκριση ακολούθων</string>
|
||||
@@ -308,28 +288,18 @@
|
||||
<string name="file_size_mb">%.2f MB</string>
|
||||
<string name="file_size_gb">%.2f GB</string>
|
||||
<string name="upload_processing">Επεξεργασία…</string>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is file size -->
|
||||
<string name="download_update">Λήψη (%s)</string>
|
||||
<string name="install_update">Εγκατάσταση</string>
|
||||
<string name="privacy_policy_title">Το Απόρρητό σου</string>
|
||||
<string name="privacy_policy_subtitle">Αν και η εφαρμογή Mastodon δεν συλλέγει δεδομένα, ο διακομιστής που εγγράφεσαι μπορεί να έχει διαφορετική πολιτική.\n\nΕάν διαφωνείς με την πολιτική του %s, μπορείς να επιστρέψεις και να επιλέξεις διαφορετικό διακομιστή.</string>
|
||||
<string name="i_agree">Συμφωνώ</string>
|
||||
<string name="empty_list">Αυτή η λίστα είναι κενή</string>
|
||||
<string name="instance_signup_closed">Αυτός ο διακομιστής δεν δέχεται νέες εγγραφές.</string>
|
||||
<string name="text_copied">Αντιγράφηκε στο πρόχειρο</string>
|
||||
<string name="add_bookmark">Σελιδοδείκτης</string>
|
||||
<string name="remove_bookmark">Αφαίρεση σελιδοδείκτη</string>
|
||||
<string name="bookmarks">Σελιδοδείκτες</string>
|
||||
<string name="your_favorites">Τα Αγαπημένα σας</string>
|
||||
<string name="login_title">Καλώς Ήρθες Ξανά</string>
|
||||
<string name="login_subtitle">Συνδέσου με τον διακομιστή όπου δημιούργησες τον λογαριασμό σου.</string>
|
||||
<string name="server_url">URL διακομιστή</string>
|
||||
<string name="server_filter_any_language">Οποιαδήποτε Γλώσσα</string>
|
||||
<string name="server_filter_instant_signup">Άμεση Εγγραφή</string>
|
||||
<string name="server_filter_manual_review">Χειροκίνητη Αξιολόγηση</string>
|
||||
<string name="server_filter_any_signup_speed">Οποιαδήποτε Ταχύτητα Εγγραφής</string>
|
||||
<string name="server_filter_region_europe">Ευρώπη</string>
|
||||
<string name="server_filter_region_north_america">Βόρεια Αμερική</string>
|
||||
<string name="server_filter_region_south_america">Νότια Αμερική</string>
|
||||
@@ -337,7 +307,6 @@
|
||||
<string name="server_filter_region_asia">Ασία</string>
|
||||
<string name="server_filter_region_oceania">Oceania</string>
|
||||
<string name="not_accepting_new_members">Δεν δέχεται νέα μέλη</string>
|
||||
<string name="category_special_interests">Ειδικά Ενδιαφέροντα</string>
|
||||
<string name="signup_passwords_dont_match">Οι κωδικοί πρόσβασης δεν ταιριάζουν</string>
|
||||
<string name="profile_add_row">Προσθήκη γραμμής</string>
|
||||
<string name="profile_setup">Ρύθμιση προφίλ</string>
|
||||
@@ -606,7 +575,6 @@
|
||||
<string name="remove">Αφαίρεση</string>
|
||||
<string name="add_list_member">Προσθήκη μέλους</string>
|
||||
<string name="search_among_people_you_follow">Αναζήτησε μεταξύ των ανθρώπων που ακουλουθείς</string>
|
||||
<string name="add_user_to_list">Προσθήκη στη λίστα…</string>
|
||||
<string name="add_user_to_list_title">Προσθήκη στη λίστα</string>
|
||||
<!-- %s is a username -->
|
||||
<string name="manage_user_lists">Διαχείριση λιστών όπου εμφανίζεται ο/η %s</string>
|
||||
@@ -669,4 +637,19 @@
|
||||
<string name="this_invite_has_expired">Αυτός ο σύνδεσμος πρόσκλησης έχει λήξει.</string>
|
||||
<string name="invite_link_pasted">Ο σύνδεσμος επικολλήθηκε από το πρόχειρο.</string>
|
||||
<string name="need_invite_to_join_server">Για να γίνεις μέλος στο %s, θα χρειαστείς έναν σύνδεσμο πρόσκλησης από έναν υπάρχοντα χρήστη.</string>
|
||||
<string name="mute_user_confirm_title">Σίγαση χρήστη;</string>
|
||||
<string name="user_wont_know_muted">Δε θα ξέρουν ότι είναι σε σίγαση.</string>
|
||||
<string name="user_can_still_see_your_posts">Μπορεί ακόμα να δει τις αναρτήσεις σου, αλλά δε θα βλέπεις τις δικές του.</string>
|
||||
<string name="you_wont_see_user_mentions">Δε θα βλέπεις τις αναρτήσεις που τον αναφέρουν.</string>
|
||||
<string name="user_can_mention_and_follow_you">Μπορούν να σε αναφέρουν και να σε ακολουθήσουν, αλλά δε θα τους βλέπεις.</string>
|
||||
<string name="unmuted_user_x">Κατάργηση σίγασης %s</string>
|
||||
<string name="block_user_confirm_title">Αποκλεισμός χρήστη;</string>
|
||||
<string name="user_can_see_blocked">Μπορούν να δουν ότι είναι αποκλεισμένοι.</string>
|
||||
<string name="user_cant_see_each_other_posts">Δεν μπορεί να δει τις αναρτήσεις σου και δε θα δεις τις δικές του.</string>
|
||||
<string name="user_cant_mention_or_follow_you">Δεν μπορούν να σε επισημάνει ή να σε ακολουθήσει.</string>
|
||||
<string name="unblocked_user_x">Άρση αποκλεισμού %s</string>
|
||||
<string name="block_domain_confirm_title">Αποκλεισμός τομέα;</string>
|
||||
<string name="do_block_server">Αποκλεισμός διακομιστή</string>
|
||||
<string name="block_user_x_instead">Αποκλεισμός %s αντ\' αυτού</string>
|
||||
<!-- Shown on a button that saves a file, after it was successfully saved -->
|
||||
</resources>
|
||||
|
||||
@@ -31,13 +31,10 @@
|
||||
<item quantity="other">siguiendo</item>
|
||||
</plurals>
|
||||
<string name="posts">Publicaciones</string>
|
||||
<string name="posts_and_replies">Publicaciones y respuestas</string>
|
||||
<string name="media">Multimedia</string>
|
||||
<string name="profile_about">Acerca de</string>
|
||||
<string name="button_follow">Seguir</string>
|
||||
<string name="button_following">Siguiendo</string>
|
||||
<string name="edit_profile">Editar perfil</string>
|
||||
<string name="share_user">Compartir perfil</string>
|
||||
<string name="mute_user">Silenciar a %s</string>
|
||||
<string name="unmute_user">Dejar de silenciar a %s</string>
|
||||
<string name="block_user">Bloquear a %s</string>
|
||||
@@ -90,19 +87,9 @@
|
||||
<item quantity="other">%,d votos</item>
|
||||
</plurals>
|
||||
<string name="poll_closed">Cerrado</string>
|
||||
<string name="confirm_mute_title">Silenciar cuenta</string>
|
||||
<string name="confirm_mute">Confirmar para silenciar a %s</string>
|
||||
<string name="do_mute">Silenciar</string>
|
||||
<string name="confirm_unmute_title">Dejar de silenciar cuenta</string>
|
||||
<string name="confirm_unmute">Confirmar para dejar de silenciar a %s</string>
|
||||
<string name="do_unmute">Dejar de silenciar</string>
|
||||
<string name="confirm_block_title">Bloquear cuenta</string>
|
||||
<string name="confirm_block_domain_title">Bloquear dominio</string>
|
||||
<string name="confirm_block">Confirmar para bloquear a %s</string>
|
||||
<string name="do_block">Bloquear</string>
|
||||
<string name="confirm_unblock_title">Desbloquear cuenta</string>
|
||||
<string name="confirm_unblock_domain_title">Desbloquear dominio</string>
|
||||
<string name="confirm_unblock">Confirmar para desbloquear a %s</string>
|
||||
<string name="do_unblock">Desbloquear</string>
|
||||
<string name="button_blocked">Bloqueado</string>
|
||||
<string name="action_vote">Votar</string>
|
||||
@@ -154,9 +141,7 @@
|
||||
<string name="report_personal_subtitle">Aquí están tus opciones para controlar lo que ves en Mastodon:</string>
|
||||
<string name="back">Atrás</string>
|
||||
<string name="search_communities">Nombre del servidor o URL</string>
|
||||
<string name="instance_rules_title">Reglas del servidor</string>
|
||||
<string name="instance_rules_subtitle">Al continuar, aceptas seguir las siguientes reglas establecidas y aplicadas por los %s moderadores.</string>
|
||||
<string name="signup_title">Crear cuenta</string>
|
||||
<string name="display_name">Nombre</string>
|
||||
<string name="username">Nombre de usuario</string>
|
||||
<string name="email">Correo electrónico</string>
|
||||
@@ -164,7 +149,6 @@
|
||||
<string name="confirm_password">Confirmar contraseña</string>
|
||||
<string name="password_note">Incluye letras mayúsculas, caracteres especiales y números para aumentar la fuerza de tu contraseña.</string>
|
||||
<string name="category_general">General</string>
|
||||
<string name="confirm_email_title">Revisa tu bandeja de entrada</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="confirm_email_subtitle">Pulsa el enlace que te hemos enviado para verificar %s. Esperaremos aquí mismo.</string>
|
||||
<string name="confirm_email_didnt_get">¿No recibiste un enlace?</string>
|
||||
@@ -199,7 +183,6 @@
|
||||
<string name="theme_dark">Oscuro</string>
|
||||
<string name="settings_behavior">Comportamiento</string>
|
||||
<string name="settings_gif">Reproducir avatares animados y emoji</string>
|
||||
<string name="settings_custom_tabs">Usar navegador interno</string>
|
||||
<string name="settings_notifications">Notificaciones</string>
|
||||
<string name="settings_contribute">Contribuir a Mastodon</string>
|
||||
<string name="settings_tos">Términos del servicio</string>
|
||||
@@ -228,8 +211,6 @@
|
||||
<string name="followed_user">Ahora estás siguiendo a %s</string>
|
||||
<string name="following_user_requested">%s solicitó seguirte</string>
|
||||
<string name="open_in_browser">Abrir en el navegador</string>
|
||||
<string name="hide_boosts_from_user">Ocultar retoots de %s</string>
|
||||
<string name="show_boosts_from_user">Mostrar retoots de %s</string>
|
||||
<string name="signup_reason">¿Por qué quieres unirte?</string>
|
||||
<string name="signup_reason_note">Esto nos ayudará a revisar su solicitud.</string>
|
||||
<string name="clear">Borrar</string>
|
||||
@@ -252,7 +233,6 @@
|
||||
<string name="recommended_accounts_info_banner">Es posible que te gusten estas cuentas basadas en otras que sigues.</string>
|
||||
<string name="see_new_posts">Nuevas publicaciones</string>
|
||||
<string name="load_missing_posts">Cargar publicaciones faltantes</string>
|
||||
<string name="follow_back">Seguir de vuelta</string>
|
||||
<string name="button_follow_pending">Pendiente</string>
|
||||
<string name="follows_you">Te sigue</string>
|
||||
<string name="manually_approves_followers">Aprueba seguidores manualmente</string>
|
||||
@@ -308,28 +288,18 @@
|
||||
<string name="file_size_mb">%.2f MB</string>
|
||||
<string name="file_size_gb">%.2f GB</string>
|
||||
<string name="upload_processing">Procesando…</string>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is file size -->
|
||||
<string name="download_update">Descargar (%s)</string>
|
||||
<string name="install_update">Instalar</string>
|
||||
<string name="privacy_policy_title">Tu privacidad</string>
|
||||
<string name="privacy_policy_subtitle">Aunque la aplicación Mastodon no recoge ningún dato, el servidor al que se registra puede tener una política diferente.\n\nSi no está de acuerdo con la política para %s, puede volver atrás y elegir un servidor diferente.</string>
|
||||
<string name="i_agree">Acepto</string>
|
||||
<string name="empty_list">Esta lista está vacía</string>
|
||||
<string name="instance_signup_closed">Este servidor no acepta altas nuevas.</string>
|
||||
<string name="text_copied">Copiado en el portapapeles</string>
|
||||
<string name="add_bookmark">Marcadores</string>
|
||||
<string name="remove_bookmark">Eliminar marcador</string>
|
||||
<string name="bookmarks">Marcadores</string>
|
||||
<string name="your_favorites">Sus favoritos</string>
|
||||
<string name="login_title">Qué bueno verle de nuevo</string>
|
||||
<string name="login_subtitle">Inicie sesión con el servidor donde creó su cuenta.</string>
|
||||
<string name="server_url">URL del servidor</string>
|
||||
<string name="server_filter_any_language">Cualquier idioma</string>
|
||||
<string name="server_filter_instant_signup">Registro instantáneo</string>
|
||||
<string name="server_filter_manual_review">Revisión manual</string>
|
||||
<string name="server_filter_any_signup_speed">Cualquier velocidad de registro</string>
|
||||
<string name="server_filter_region_europe">Europa</string>
|
||||
<string name="server_filter_region_north_america">Norteamérica</string>
|
||||
<string name="server_filter_region_south_america">Suramérica</string>
|
||||
@@ -337,7 +307,6 @@
|
||||
<string name="server_filter_region_asia">Asia</string>
|
||||
<string name="server_filter_region_oceania">Oceanía</string>
|
||||
<string name="not_accepting_new_members">No se aceptan nuevos miembros</string>
|
||||
<string name="category_special_interests">Intereses especiales</string>
|
||||
<string name="signup_passwords_dont_match">Las contraseñas no coinciden</string>
|
||||
<string name="profile_add_row">Añadir fila</string>
|
||||
<string name="profile_setup">Configuración del perfil</string>
|
||||
@@ -606,7 +575,6 @@
|
||||
<string name="remove">Eliminar</string>
|
||||
<string name="add_list_member">Añadir miembro</string>
|
||||
<string name="search_among_people_you_follow">Buscar entre las personas a las que sigues</string>
|
||||
<string name="add_user_to_list">Añadir a lista…</string>
|
||||
<string name="add_user_to_list_title">Añadir a lista</string>
|
||||
<!-- %s is a username -->
|
||||
<string name="manage_user_lists">Administrar las listas en las que %s aparece</string>
|
||||
@@ -659,4 +627,33 @@ Mientras más personas sigas, más activo e interesante será.</string>
|
||||
<string name="button_reblogged">Impulsado</string>
|
||||
<string name="button_favorited">Añadido a favoritos</string>
|
||||
<string name="bookmarked">Añadido a marcadores</string>
|
||||
<string name="join_server_x_with_invite">Únete a %s con invitación</string>
|
||||
<string name="expired_invite_link">Enlace de invitación expirado</string>
|
||||
<string name="expired_clipboard_invite_link_alert">El enlace de invitación para %1$s en tu portapapeles ha expirado y no puede ser utilizado para registrarse.\n\nPuedes solicitar un nuevo enlace de un usuario existente y registrarte a través de %2$s, elegir otro servidor para registrarte.</string>
|
||||
<string name="invalid_invite_link">Enlace de invitación inválido</string>
|
||||
<string name="invalid_clipboard_invite_link_alert">El enlace de invitación para %1$s en tu portapapeles no es válido y no puede ser utilizado para registrarse.\n\nPuedes solicitar un nuevo enlace de un usuario existente y registrarte a través de %2$s, elegir otro servidor para registrarte.</string>
|
||||
<string name="use_invite_link">Usar enlace de invitación</string>
|
||||
<string name="enter_invite_link">Ingresar enlace de invitación</string>
|
||||
<string name="this_invite_is_invalid">Este enlace de invitación no es válido.</string>
|
||||
<string name="this_invite_has_expired">Este enlace de invitación ha expirado.</string>
|
||||
<string name="invite_link_pasted">Enlace pegado desde el portapapeles.</string>
|
||||
<string name="need_invite_to_join_server">Para unirte a %s, necesitarás un enlace de invitación de un usuario existente.</string>
|
||||
<string name="mute_user_confirm_title">¿Silenciar usuario?</string>
|
||||
<string name="user_wont_know_muted">No sabrán que han sido silenciados.</string>
|
||||
<string name="user_can_still_see_your_posts">Todavía pueden ver tus publicaciones, pero no verás las suyas.</string>
|
||||
<string name="user_can_mention_and_follow_you">Pueden mencionarte y seguirte, pero no los verás.</string>
|
||||
<string name="unmuted_user_x">%s ya no está silenciado</string>
|
||||
<string name="block_user_confirm_title">¿Bloquear usuario?</string>
|
||||
<string name="user_can_see_blocked">Pueden ver que están bloqueados.</string>
|
||||
<string name="user_cant_see_each_other_posts">No pueden ver tus publicaciones y no verás las suyas.</string>
|
||||
<string name="user_cant_mention_or_follow_you">No pueden mencionarte ni seguirte.</string>
|
||||
<string name="unblocked_user_x">%s ya no está bloqueado</string>
|
||||
<string name="block_domain_confirm_title">¿Bloquear dominio?</string>
|
||||
<string name="do_block_server">Bloquear servidor</string>
|
||||
<string name="block_user_x_instead">Bloquear a %s en su lugar</string>
|
||||
<string name="you_wont_see_server_posts">No verás ninguna publicación de usuarios en este servidor.</string>
|
||||
<string name="server_followers_will_be_removed">Tus seguidores de este servidor serán eliminados.</string>
|
||||
<string name="server_can_interact_with_older">Las personas de este servidor pueden interactuar con tus mensajes antiguos.</string>
|
||||
<string name="unblocked_domain_x">El dominio %s ya no está bloqueado</string>
|
||||
<!-- Shown on a button that saves a file, after it was successfully saved -->
|
||||
</resources>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<string name="button_follow">Jarraitu</string>
|
||||
<string name="button_following">Jarraitzen</string>
|
||||
<string name="edit_profile">Editatu profila</string>
|
||||
<string name="share_user">Partekatu profila</string>
|
||||
<string name="share_user">Partekatu profila honen bidez…</string>
|
||||
<string name="mute_user">Mututu %s</string>
|
||||
<string name="unmute_user">Desmututu %s</string>
|
||||
<string name="block_user">Blokeatu %s</string>
|
||||
@@ -90,19 +90,9 @@
|
||||
<item quantity="other">%,d boto</item>
|
||||
</plurals>
|
||||
<string name="poll_closed">Itxita</string>
|
||||
<string name="confirm_mute_title">Mututu kontua</string>
|
||||
<string name="confirm_mute">Berretsi %s mututzea</string>
|
||||
<string name="do_mute">Mututu</string>
|
||||
<string name="confirm_unmute_title">Desmututu kontua</string>
|
||||
<string name="confirm_unmute">Berretsi %s desmututzea</string>
|
||||
<string name="do_unmute">Desmututu</string>
|
||||
<string name="confirm_block_title">Blokeatu kontua</string>
|
||||
<string name="confirm_block_domain_title">Blokeatu domeinua</string>
|
||||
<string name="confirm_block">Berretsi %s blokeatzea</string>
|
||||
<string name="do_block">Blokeatu</string>
|
||||
<string name="confirm_unblock_title">Desblokeatu kontua</string>
|
||||
<string name="confirm_unblock_domain_title">Desblokeatu domeinua</string>
|
||||
<string name="confirm_unblock">Berretsi %s desblokeatzea</string>
|
||||
<string name="do_unblock">Desblokeatu</string>
|
||||
<string name="button_blocked">Blokeatuta</string>
|
||||
<string name="action_vote">Bozkatu</string>
|
||||
@@ -155,6 +145,7 @@
|
||||
<string name="back">Atzera</string>
|
||||
<string name="search_communities">Zerbitzari izena edo URLa</string>
|
||||
<string name="instance_rules_title">Zerbitzariaren arauak</string>
|
||||
<string name="instance_rules_subtitle">Aurrera egitean, %s-ko moderatzaileek ezarritako eta aplikatutako arau hauek jarraitzea onartzen duzu.</string>
|
||||
<string name="signup_title">Sortu kontua</string>
|
||||
<string name="display_name">Izena</string>
|
||||
<string name="username">Erabiltzaile-izena</string>
|
||||
@@ -163,7 +154,7 @@
|
||||
<string name="confirm_password">Berretsi pasahitza</string>
|
||||
<string name="password_note">Sartu letra larriak, karaktere bereziak eta zenbakiak zure pasahitzaren segurtasuna areagotzeko.</string>
|
||||
<string name="category_general">Orokorra</string>
|
||||
<string name="confirm_email_title">Egiaztatu zure sarrerako ontzia</string>
|
||||
<string name="confirm_email_title">Begiratu zure sarrera-ontzia</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="confirm_email_subtitle">Sakatu bidali dizugun esteka %s helbidea egiaztatzeko. Hementxe itxarongo zaitugu.</string>
|
||||
<string name="confirm_email_didnt_get">Ez duzu estekarik jaso?</string>
|
||||
@@ -198,7 +189,7 @@
|
||||
<string name="theme_dark">Iluna</string>
|
||||
<string name="settings_behavior">Jokabidea</string>
|
||||
<string name="settings_gif">Erreproduzitu animatutako abatarrak eta emojiak</string>
|
||||
<string name="settings_custom_tabs">Erabili aplikazio barneko nabigatzailea</string>
|
||||
<string name="settings_custom_tabs">Ireki estekak hemen</string>
|
||||
<string name="settings_notifications">Jakinarazpenak</string>
|
||||
<string name="settings_contribute">Lagundu Mastodon</string>
|
||||
<string name="settings_tos">Erabilera baldintzak</string>
|
||||
@@ -227,8 +218,8 @@
|
||||
<string name="followed_user">%s jarraitzen ari zara</string>
|
||||
<string name="following_user_requested">%s jarraitzea eskatuta</string>
|
||||
<string name="open_in_browser">Ireki nabigatzailean</string>
|
||||
<string name="hide_boosts_from_user">Ezkutatu %s(r)en bultzadak</string>
|
||||
<string name="show_boosts_from_user">Erakutsi @%s(r)en bultzadak</string>
|
||||
<string name="hide_boosts_from_user">Ezkutatu bultzadak</string>
|
||||
<string name="show_boosts_from_user">Erakutsi bultzadak</string>
|
||||
<string name="signup_reason">Zergatik elkartu nahi duzu?</string>
|
||||
<string name="signup_reason_note">Honek zure eskaera berrikustean lagunduko digu.</string>
|
||||
<string name="clear">Garbitu</string>
|
||||
@@ -245,10 +236,13 @@
|
||||
<string name="no_app_to_handle_action">Ez dago ekintza hau kudeatu dezkeen aplikaziorik</string>
|
||||
<string name="local_timeline">Lokala</string>
|
||||
<string name="trending_posts_info_banner">Hauek dira zure Mastodon txokoan beraien lekua hartzen ari diren argitalpenak.</string>
|
||||
<string name="trending_links_info_banner">Hauek dira Mastodonen aipatzen diren albisteak.</string>
|
||||
<!-- %s is the server domain -->
|
||||
<string name="local_timeline_info_banner">Hauek dira zure zerbitzariko (%s) erabiltzaile guztien mezuak.</string>
|
||||
<string name="recommended_accounts_info_banner">Jarraitzen dituzun beste batzuetan oinarrituta, baliteke kontu hauek gustuko izatea.</string>
|
||||
<string name="see_new_posts">Bidalketa berriak</string>
|
||||
<string name="load_missing_posts">Falta diren bidalketak kargatu</string>
|
||||
<string name="follow_back">Jarraitu</string>
|
||||
<string name="follow_back">Jarraitu bueltan</string>
|
||||
<string name="button_follow_pending">Zain</string>
|
||||
<string name="follows_you">Jarraitzen zaitu</string>
|
||||
<string name="manually_approves_followers">Jarraitzaileak eskuz onartu</string>
|
||||
@@ -304,12 +298,11 @@
|
||||
<string name="file_size_mb">%.2f MB</string>
|
||||
<string name="file_size_gb">%.2f GB</string>
|
||||
<string name="upload_processing">Prozesatzen…</string>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is file size -->
|
||||
<string name="download_update">(%s) deskargatu</string>
|
||||
<string name="install_update">Instalatu</string>
|
||||
<string name="privacy_policy_title">Zure pribatutasuna</string>
|
||||
<string name="privacy_policy_subtitle">Mastodon aplikazioak daturik biltzen ez duen arren, erregistratzen zaren zerbitzariak beste politika bat izan dezake.\n\nEz bazaude %s zerbitzariaren politikarekin ados, atzera egin eta beste zerbitzari bat aukeratu dezakezu.</string>
|
||||
<string name="i_agree">Ados nago</string>
|
||||
<string name="empty_list">Zerrenda hau hutsik dago</string>
|
||||
<string name="instance_signup_closed">Zerbitzari honek ez ditu izen-emate berriak onartzen.</string>
|
||||
@@ -324,6 +317,7 @@
|
||||
<string name="server_filter_any_language">Edozein hizkuntza</string>
|
||||
<string name="server_filter_instant_signup">Berehalako erregistroa</string>
|
||||
<string name="server_filter_manual_review">Eskuzko berrikuspena</string>
|
||||
<string name="server_filter_any_signup_speed">Edozein erregistro abiadura</string>
|
||||
<string name="server_filter_region_europe">Europa</string>
|
||||
<string name="server_filter_region_north_america">Ipar Amerika</string>
|
||||
<string name="server_filter_region_south_america">Hego Amerika</string>
|
||||
@@ -338,12 +332,14 @@
|
||||
<string name="profile_setup_subtitle">Beti osa dezakezu hau geroago Profila fitxan.</string>
|
||||
<string name="follow_all">Jarraitu denak</string>
|
||||
<string name="server_rules_disagree">Ez ados</string>
|
||||
<string name="privacy_policy_explanation">Laburpena: Ez dugu ezer jasotzen ezta prozesatzen ere.</string>
|
||||
<!-- %s is server domain -->
|
||||
<string name="server_policy_disagree">%s rekin desados</string>
|
||||
<string name="profile_bio">Biografia</string>
|
||||
<!-- Shown in a progress dialog when you tap "follow all" -->
|
||||
<string name="sending_follows">Erabiltzaileak jarraitzen…</string>
|
||||
<!-- %1$s is server domain, %2$s is email domain. You can reorder these placeholders to fit your language better. -->
|
||||
<string name="signup_email_domain_blocked">%1$s(e)k ez du erregistrorik onartzen %2$s(e)tik. Probatu beste bat edo <a>aukeratu beste zerbitzari bat<a>.</string>
|
||||
<string name="spoiler_show">Erakutsi hala ere</string>
|
||||
<string name="spoiler_hide">Berrezkutatu</string>
|
||||
<string name="poll_multiple_choice">Aukeratu bat edo gehiago</string>
|
||||
@@ -360,12 +356,15 @@
|
||||
<string name="signup_or_login">edo</string>
|
||||
<string name="learn_more">Ikasi gehiago</string>
|
||||
<string name="welcome_to_mastodon">Ongi etorri Mastodon-era</string>
|
||||
<string name="welcome_paragraph1">Mastodon sare sozial deszentralizatua da, eta horrek esan nahi du ez duela konpainia bakar batek kontrolatzen. Zerbitzari independente askok osatzen dute, denak batera konektatuta.</string>
|
||||
<string name="what_are_servers">Zer dira zerbitzariak?</string>
|
||||
<string name="welcome_paragraph2">Mastodonen kontu bakoitza zerbitzari batean dago — bakoitzak bere balore, arau eta administratzaileekin. Ez du axola zein aukeratzen duzun, edozein zerbitzaritako pertsonak jarraitu eta beraiekin elkarreraginean jardun dezakezu.</string>
|
||||
<string name="opening_link">Lotura irekitzen…</string>
|
||||
<string name="link_not_supported">Lotura hau ezin da aplikazioan ireki</string>
|
||||
<string name="log_out_all_accounts">Irten kontu guztietatik</string>
|
||||
<string name="confirm_log_out_all_accounts">Kontu guztietatik irten?</string>
|
||||
<string name="retry">Berriro saiatu</string>
|
||||
<string name="post_failed">Bidalketak huts egin du</string>
|
||||
<!-- %s is formatted file size ("467 KB image") -->
|
||||
<string name="attachment_description_image">irudi %s</string>
|
||||
<string name="attachment_description_video">bideo %s</string>
|
||||
@@ -376,6 +375,7 @@
|
||||
<string name="attachment_type_audio">Audioa</string>
|
||||
<string name="attachment_type_gif">GIF-a</string>
|
||||
<string name="attachment_type_unknown">Fitxategia</string>
|
||||
<string name="attachment_x_percent_uploaded">%d%% kargatuta</string>
|
||||
<string name="add_poll_option">Gehitu inkesta aukera</string>
|
||||
<string name="poll_length">Inkesta luzera</string>
|
||||
<string name="poll_style">Estiloa</string>
|
||||
@@ -386,9 +386,11 @@
|
||||
<string name="alt_text">Alt testua</string>
|
||||
<string name="help">Laguntza</string>
|
||||
<string name="what_is_alt_text">Zer da alt testua?</string>
|
||||
<string name="alt_text_help">Alt testuak irudiak deskribatzeko aukera ematen du, ikusmen-urritasunak, banda-zabalera txikiko konexioak edo testuinguru gehigarria nahi duten pertsonentzat.\n\nAlt testu argi, zehatz eta objektiboen bidez, guztion irisgarritasuna eta ulermena hobetu ditzakezu.\n\n<ul><li>Hartu errementu garrantzitsuenak</li>\n<li>Irudietako testua laburbildu.</li>\n<li>Esaldien egitura erregularra erabili.</li>\n<li>Baztertu informazio erredundantea.</li>\n<li>Enfokatu joeretan eta funtsezko aurkikuntzetan irudi konplexuetan (diagrametan edo mapetan, adibidez)</li></ul></string>
|
||||
<string name="edit_post">Editatu argitalpena</string>
|
||||
<string name="no_verified_link">Lotura ez egiaztatua</string>
|
||||
<string name="compose_autocomplete_emoji_empty">Esploratu emojiak</string>
|
||||
<string name="compose_autocomplete_users_empty">Aurkitu bilatzen ari zaren pertsona</string>
|
||||
<string name="no_search_results">Ez da emaitzarik aurkitu bilaketa-termino horientzat</string>
|
||||
<string name="language">Hizkuntza</string>
|
||||
<string name="language_default">Lehenetsia</string>
|
||||
@@ -404,19 +406,29 @@
|
||||
<string name="forward_report_to_server">Birbidali hona: %s</string>
|
||||
<!-- Shown on the "stamp" on the screen that appears after you report a post/user. Please keep the translation short, preferably a single word -->
|
||||
<string name="reported">Salatua</string>
|
||||
<string name="report_unfollow_explanation">Zure hasierako denbora-lerroan beraien bidalketa gehiago ez ikusteko, utzi jarraitzeari.</string>
|
||||
<string name="muted_user">Mututu %s</string>
|
||||
<string name="report_sent_already_blocked">Dagoeneko blokeatu duzu erabiltzaile hau, beraz, ez daukazu besterik egiteko zure txostena berrikusten dugun bitartean.</string>
|
||||
<string name="report_personal_already_blocked">Erabiltzaile hau blokeatu duzu dagoeneko, beraz, ez duzu besterik egiteko.\n\nEskerrik asko Mastodon denontzako seguru mantentzen laguntzeagatik!</string>
|
||||
<string name="blocked_user">Blokeatu %s</string>
|
||||
<string name="mark_all_notifications_read">Markatu denak irakurrita bezala</string>
|
||||
<string name="settings_display">Bistaratzea</string>
|
||||
<string name="settings_filters">Iragazkiak</string>
|
||||
<string name="settings_server_explanation">Laburpena, arauak, moderatzaileak</string>
|
||||
<!-- %s is the app name (Mastodon, key app_name). I made it a placeholder so everything Just Works™ with forks -->
|
||||
<string name="about_app">%s(r)i buruz</string>
|
||||
<string name="default_post_language">Lehenetsitako bidalketa hizkuntza</string>
|
||||
<string name="settings_alt_text_reminders">Gehitu alt testu gogorarazleak</string>
|
||||
<string name="settings_confirm_unfollow">Norbaiti jarraitzeari utzi aurretik galdetu</string>
|
||||
<string name="settings_confirm_boost">Galdetu bultzatu aurretik</string>
|
||||
<string name="settings_confirm_delete_post">Galdetu bidalketa ezabatu orduko</string>
|
||||
<string name="pause_all_notifications">Pausatu denak</string>
|
||||
<string name="pause_notifications_off">Itzali</string>
|
||||
<string name="notifications_policy_anyone">Edozein</string>
|
||||
<string name="notifications_policy_followed">Zu jarraitzen zaituzten pertsonak</string>
|
||||
<string name="notifications_policy_follower">Zuk jarraitzen dituzun pertsonak</string>
|
||||
<string name="notifications_policy_no_one">Bat ere ez</string>
|
||||
<string name="settings_notifications_policy">Honen jakinarazpenak jaso</string>
|
||||
<string name="notification_type_mentions_and_replies">Aipamenak eta erantzunak</string>
|
||||
<string name="pause_all_notifications_title">Pausatu jakinarazpen guztiak</string>
|
||||
<plurals name="x_weeks">
|
||||
@@ -429,13 +441,21 @@
|
||||
<string name="yesterday">atzo</string>
|
||||
<string name="tomorrow">bihar</string>
|
||||
<!-- %s is the timestamp ("tomorrow at 12:34") -->
|
||||
<string name="pause_notifications_ends">Amaiera: %s</string>
|
||||
<!-- %s is the timestamp ("tomorrow at 12:34") -->
|
||||
<string name="pause_notifications_banner">%s jakinarazpenak berriro hasiko dira.</string>
|
||||
<string name="resume_notifications_now">Jarraitu orain</string>
|
||||
<string name="open_system_notification_settings">Joan jakinarazpenaren ezarpenetara</string>
|
||||
<string name="about_server">Honi buruz</string>
|
||||
<string name="server_rules">Arauak</string>
|
||||
<string name="server_administrator">Administratzailea</string>
|
||||
<string name="send_email_to_server_admin">Administratzaileari mezua</string>
|
||||
<string name="notifications_disabled_in_system">Piztu jakinarazpenak gailuaren ezarpenetatik, edozein tokitatik eguneratzeak ikusteko.</string>
|
||||
<string name="settings_even_more">Ezarpen gaehiago</string>
|
||||
<string name="settings_show_cws">Erakutsi eduki abisuak</string>
|
||||
<string name="settings_hide_sensitive_media">Ezkutatu esplizitu gisa markatutako edukiak</string>
|
||||
<string name="settings_show_interaction_counts">Bidalketarekiko interakzio-kontagailua</string>
|
||||
<string name="settings_show_emoji_in_names">Emoji pertsonalizatua pantaila-izenetan</string>
|
||||
<plurals name="in_x_seconds">
|
||||
<item quantity="one">segundo %d barru</item>
|
||||
<item quantity="other">%d segundo barru</item>
|
||||
@@ -452,6 +472,15 @@
|
||||
<item quantity="one">Duela ordu %d</item>
|
||||
<item quantity="other">Duela %d ordu</item>
|
||||
</plurals>
|
||||
<string name="alt_text_reminder_title">Multimedien alt testua falta da</string>
|
||||
<plurals name="alt_text_reminder_x_images">
|
||||
<item quantity="one">Zure argazki %sek ez du alt testurik. Nolanahi ere, argitaratu?</item>
|
||||
<item quantity="other">Zure %s argazkik ez dute alt testurik. Nolanahi ere, argitaratu?</item>
|
||||
</plurals>
|
||||
<plurals name="alt_text_reminder_x_attachments">
|
||||
<item quantity="one">Zure multimedia fitxategi erantsi %sek ez du alt testurik. Nolanahi ere, argitaratu?</item>
|
||||
<item quantity="other">Zure multimedia fitxategi erantsi %sk ez dute alt testurik. Nolanahi ere, argitaratu?</item>
|
||||
</plurals>
|
||||
<string name="count_one">Bat</string>
|
||||
<string name="count_two">Bi</string>
|
||||
<string name="count_three">Hiru</string>
|
||||
@@ -465,23 +494,37 @@
|
||||
<string name="settings_edit_filter">Editatu iragazkia</string>
|
||||
<string name="settings_filter_duration">Iraupena</string>
|
||||
<string name="settings_filter_muted_words">Mutututako hitzak</string>
|
||||
<string name="settings_filter_context">Isilarazi</string>
|
||||
<string name="settings_filter_show_cw">Erakutsi eduki abisuarekin</string>
|
||||
<string name="settings_filter_show_cw_explanation">Iragazki horrekin bat datozen sarrerak erakusten jarraitzen du, baina edukien abisuarekin</string>
|
||||
<string name="settings_delete_filter">Ezabatu iragazkia</string>
|
||||
<string name="filter_duration_forever">Betirakoa</string>
|
||||
<!-- %s is the timestamp ("tomorrow at 12:34") -->
|
||||
<string name="settings_filter_ends">Amaiera: %s</string>
|
||||
<plurals name="settings_x_muted_words">
|
||||
<item quantity="one">Hitz edo esaldi %d mutututa</item>
|
||||
<item quantity="other">%d hitz edo esaldi mutututa</item>
|
||||
</plurals>
|
||||
<string name="selection_2_options">%1$s eta %2$s</string>
|
||||
<string name="selection_3_options">%1$s, %2$s, eta %3$s</string>
|
||||
<string name="selection_4_or_more">%1$s, %2$s, eta beste %3$d</string>
|
||||
<string name="filter_context_home_lists">Hasierako denbora-lerroa</string>
|
||||
<string name="filter_context_notifications">Jakinarazpenak</string>
|
||||
<string name="filter_context_public_timelines">Denbora-lerro publikoak</string>
|
||||
<string name="filter_context_threads_replies">Hariak & erantzunak</string>
|
||||
<string name="filter_context_profiles">Profilak</string>
|
||||
<string name="settings_filter_title">Izenburua</string>
|
||||
<string name="settings_delete_filter_title">Ezabatu \"%s\" iragazkia”?</string>
|
||||
<string name="settings_delete_filter_confirmation">Iragazki hau zure kontutik ezabatuko da gailu guztietan.</string>
|
||||
<string name="add_muted_word">Gehitu mutututako hitza</string>
|
||||
<string name="edit_muted_word">Editatu mutututako hitza</string>
|
||||
<string name="add">Gehitu</string>
|
||||
<string name="filter_word_or_phrase">Hitz edo esaldia</string>
|
||||
<string name="filter_add_word_help">Hitzek ez dute maiuskulak eta minuskulak bereizten eta hitz osoak bakarrik bereizten dituzte.\n\n\"Sagar,\" hitza filtratzen baduzu, \"sagar\" edo \"sAgar\" duten bidalketak ezkutatuko dira, baina ez \"sagardo\" dutenak.</string>
|
||||
<string name="settings_delete_filter_word">“%s” hitza ezabatu?</string>
|
||||
<string name="enter_selection_mode">Hautatu</string>
|
||||
<string name="select_all">Hautatu dena</string>
|
||||
<string name="settings_filter_duration_title">Irgazkiaren iraupena</string>
|
||||
<string name="filter_duration_custom">Pertsonalizatua</string>
|
||||
<plurals name="settings_delete_x_filter_words">
|
||||
<item quantity="one">Ezabatu hitz %d?</item>
|
||||
@@ -497,18 +540,25 @@
|
||||
<string name="app_update_version">Bertsioa: %s</string>
|
||||
<string name="downloading_update">Deskargatzen (%d%%)</string>
|
||||
<!-- Shown like a content warning, %s is the name of the filter -->
|
||||
<string name="post_matches_filter_x">“%s” iragazkiarekin bat dator</string>
|
||||
<string name="search_mastodon">Bilatu Mastodonen</string>
|
||||
<string name="clear_all">Garbitu dena</string>
|
||||
<string name="search_open_url">Ireki URLa Mastodonen</string>
|
||||
<string name="posts_matching_hashtag">\"%s\" duten bidalketak</string>
|
||||
<string name="search_go_to_account">Joan hona %s</string>
|
||||
<string name="posts_matching_string">\"%s\" duten bidalketak</string>
|
||||
<string name="accounts_matching_string">\"%s\" duten pertsonak</string>
|
||||
<!-- Shown in the post header. Please keep it short -->
|
||||
<string name="time_seconds_ago_short">Duela %d segundo</string>
|
||||
<string name="time_minutes_ago_short">Duela %d minutu</string>
|
||||
<string name="time_hours_ago_short">Duela %d ordu</string>
|
||||
<string name="time_days_ago_short">Duela %d egun</string>
|
||||
<!-- %s is the name of the post language -->
|
||||
<string name="translate_post">%s-tik itzuli</string>
|
||||
<!-- %1$s is the language, %2$s is the name of the translation service -->
|
||||
<string name="post_translated">%1$s(e)tik %2$s erabiliz itzuli</string>
|
||||
<string name="translation_show_original">Erakutsi jatorrizkoa</string>
|
||||
<string name="translation_failed">Itzulpenak huts egin du. Agian administratzaileak ez du itzulpenik gaitu zerbitzari honetan, edo zerbitzari hau Mastodon bertsio zaharrago bat exekutatzen ari da, non itzulpenak oraindik onartzen ez diren.</string>
|
||||
<string name="settings_privacy">Pribatutasuna eta irismena</string>
|
||||
<string name="settings_discoverable">Ezagutarazi profila eta bidalketak bilaketa algoritmoetan</string>
|
||||
<string name="settings_indexable">Gehitu argitalpen publikoak bilaketa-emaitzetan</string>
|
||||
@@ -516,6 +566,10 @@
|
||||
<item quantity="one">partaide %,d</item>
|
||||
<item quantity="other">%d partaide</item>
|
||||
</plurals>
|
||||
<plurals name="x_posts_today">
|
||||
<item quantity="one">%,d bidalketa gaur</item>
|
||||
<item quantity="other">%,d bidalketa gaur</item>
|
||||
</plurals>
|
||||
<string name="error_playing_video">Errorea bideoa erreproduzitzerakoan</string>
|
||||
<string name="timeline_following">Hasiera</string>
|
||||
<string name="lists">Zerrendak</string>
|
||||
@@ -529,6 +583,8 @@
|
||||
<string name="delete_list">Ezabatu zerrenda</string>
|
||||
<!-- %s is the name of the list -->
|
||||
<string name="delete_list_confirm">Ezabatu “%s”?</string>
|
||||
<string name="list_exclusive">Jarraitzen dituzun erabiltzaileen zerrendako kideak ezkutatu</string>
|
||||
<string name="list_exclusive_subtitle">Norbait zerrenda horretan badago, ezkutatu bere hasierako denbora-lerroan mezuak bitan ez ikusteko.</string>
|
||||
<string name="list_name">Zerrendaren izena</string>
|
||||
<string name="list_show_replies_to">Erakutsi erantzunak</string>
|
||||
<string name="list_replies_no_one">Bat ere ez</string>
|
||||
@@ -538,28 +594,113 @@
|
||||
<string name="remove">Kendu</string>
|
||||
<string name="add_list_member">Gehitu kidea</string>
|
||||
<string name="search_among_people_you_follow">Bilatu jarraitzen dituzun pertsonen artean</string>
|
||||
<string name="add_user_to_list">Gehitu zerrendara…</string>
|
||||
<string name="add_user_to_list">Gehitu edo kendu zerrendetatik…</string>
|
||||
<string name="add_user_to_list_title">Gehitu zerrendara</string>
|
||||
<!-- %s is a username -->
|
||||
<string name="manage_user_lists">%s agertzen den zerrendak kudeatu</string>
|
||||
<string name="remove_from_list">Kendu zerrendatik</string>
|
||||
<string name="confirm_remove_list_member">Kendu kideak?</string>
|
||||
<string name="no_followed_hashtags_title">Mantendu egunean interesatzen zaizuna, traolak jarraituz</string>
|
||||
<string name="no_followed_hashtags_subtitle">Jarraituak hemen agertuko dira</string>
|
||||
<string name="no_lists_title">Antolatu zure hasiera lerroa Zerrendekin</string>
|
||||
<string name="no_lists_subtitle">Zurea hemen agertuko da</string>
|
||||
<string name="manage_accounts">Gehitu edo aldatu kontuz</string>
|
||||
<plurals name="x_posts_recently">
|
||||
<item quantity="one">Bidalketa berri %,d</item>
|
||||
<item quantity="other">%,d bidalketa berri</item>
|
||||
</plurals>
|
||||
<string name="create_list">Sortu zerrenda</string>
|
||||
<string name="step_x_of_y">%1$d pausua %2$d -(e)tik</string>
|
||||
<string name="create">Sortu</string>
|
||||
<string name="manage_list_members">Kudeatu partaide zerrrenda</string>
|
||||
<string name="list_no_members">Oraindik ez dago kiderik</string>
|
||||
<string name="list_find_users">Bilatu gehitzeko erabiltzaileak</string>
|
||||
<string name="reply_to_user">Erantzun honi: %s</string>
|
||||
<string name="posted_at">%s(e)an argitaratua</string>
|
||||
<string name="non_mutual_sheet_title">Kaixo, konexio berria!</string>
|
||||
<string name="non_mutual_sheet_text">Oraindik alde biko loturarik ez duen norbaiti erantzutera zoazela dirudi. Eman dezagun lehen itxura on bat.</string>
|
||||
<string name="got_it">Ulertuta</string>
|
||||
<string name="dont_remind_again">Ez gogorarazi berriro</string>
|
||||
<!-- %s is a time interval ("5 months") -->
|
||||
<string name="old_post_sheet_title">Argitalpen hau duela %s(e)koa da</string>
|
||||
<string name="old_post_sheet_text">Oraindik erantzun dezakezu, baina baliteke jada garrantzitsua ez izatea.</string>
|
||||
<plurals name="x_months">
|
||||
<item quantity="one">hilabete %,d</item>
|
||||
<item quantity="other">%,d hilabete</item>
|
||||
</plurals>
|
||||
<string name="more_than_two_years">2 urte baino gehiago</string>
|
||||
<string name="non_mutual_title1">Izan adeitsua & esanguratsua</string>
|
||||
<string name="non_mutual_text1">Ziurtatu zure erantzuna adeitsua eta gaiari buruzkoa dela.</string>
|
||||
<string name="non_mutual_title2">Besarkatu adeitasuna</string>
|
||||
<string name="non_mutual_text2">Tonu positiboa beti da eskertzekoa.</string>
|
||||
<string name="non_mutual_title3">Irekia izan</string>
|
||||
<string name="non_mutual_text3">Pertsona guztien elkarrizketa estiloa bakarra da. Prest egon egokitzeko.</string>
|
||||
<string name="make_profile_discoverable">Profila aurkitzeko moduan jarri</string>
|
||||
<string name="discoverability">Aurkigarritasuna</string>
|
||||
<string name="discoverability_help">Zure kontua Mastodonen bilaketan agertzea aukeratzen baduzu, zure argitalpenak bilaketa emaitzetan eta joeretan ager daitezke.\n\nZure profila zure antzeko gustuak dituzten pertsonentzat gomenda daiteke.\n\nBilaketan ez agertzea aukeratzeak ez du zure profila ezkutatuko norbaitek zure izena bilatzen badu.</string>
|
||||
<string name="app_version_copied">Bertsio zenbakia arbelera kopiatu da</string>
|
||||
<string name="onboarding_recommendations_intro">Zuk ontzen duzu zure hasiera lerroa.
|
||||
Zenbat eta jende gehiago jarraitu, orduan eta aktiboagoa eta interesgarriagoa izango da.</string>
|
||||
<string name="onboarding_recommendations_title">Pertsonalizatu zure hasiera lerroa</string>
|
||||
<string name="article_by_author">%s(r)en eskutik</string>
|
||||
<string name="info">Informazioa</string>
|
||||
<string name="button_reblogged">Bultzatuta</string>
|
||||
<string name="button_favorited">Gogokoetara gehituta</string>
|
||||
<string name="bookmarked">Laster-marketara gehituta</string>
|
||||
<string name="join_server_x_with_invite">Batu %s -ra gonbidapenarekin</string>
|
||||
<string name="expired_invite_link">Iraungitako gonbidapen esteka</string>
|
||||
<string name="expired_clipboard_invite_link_alert">Zure arbeleko %1$s(e)rako gonbidapen-esteka iraungi egin da eta ezin da erregistratzeko erabili.\n\nLehendik dagoen erabiltzaile bati esteka berri bat eska diezaiokezu %2$s(e)ren bidez, edo erregistratzeko beste zerbitzari bat aukeratu.</string>
|
||||
<string name="invalid_invite_link">Gonbidapen esteka baliogabea</string>
|
||||
<string name="invalid_clipboard_invite_link_alert">Zure arbeleko %1$s(e)rako gonbidapen-esteka baliogabea da eta ezin da erregistratzeko erabili.\n\nLehendik dagoen erabiltzaile bati esteka berri bat eska diezaiokezu %2$s(e)ren bidez, edo erregistratzeko beste zerbitzari bat aukeratu.</string>
|
||||
<string name="use_invite_link">Erabili gonbidapen esteka</string>
|
||||
<string name="enter_invite_link">Sartu gonbidapena esteka</string>
|
||||
<string name="this_invite_is_invalid">Gonbidapen esteka hau ez da baliozkoa.</string>
|
||||
<string name="this_invite_has_expired">Gonbidapen esteka hau iraungita dago.</string>
|
||||
<string name="invite_link_pasted">Zure arbeletik itsatsitako lotura.</string>
|
||||
<string name="need_invite_to_join_server">%s -ra batzeko lehendik dagoen erabiltzaile baten gonbidapen esteka beharko duzu.</string>
|
||||
<string name="mute_user_confirm_title">Mututu erabiltzailea?</string>
|
||||
<string name="user_wont_know_muted">Ez dute jakingo mututuak izan direla.</string>
|
||||
<string name="user_can_still_see_your_posts">Oraindik ikus ditzakete zure bidalketak, baina ez dituzu beraienak ikusiko.</string>
|
||||
<string name="you_wont_see_user_mentions">Ez duzu ikusiko aipatzen dituen argitalpenik.</string>
|
||||
<string name="user_can_mention_and_follow_you">Aipa eta jarrai zaitzakete, baina ez dituzu ikusiko.</string>
|
||||
<string name="unmuted_user_x">%s desmututu da</string>
|
||||
<string name="block_user_confirm_title">Erabiltzailea blokeatu?</string>
|
||||
<string name="user_can_see_blocked">Blokeatuta daudela ikus dezakete.</string>
|
||||
<string name="user_cant_see_each_other_posts">Ezin dituzte zure bidalketak ikusi eta ez dituzu baraienak ikusiko.</string>
|
||||
<string name="user_cant_mention_or_follow_you">Ezin zaituzte aipatu edo jarraitu.</string>
|
||||
<string name="unblocked_user_x">%s desblokeatu da</string>
|
||||
<string name="block_domain_confirm_title">Blokeatu domeinua?</string>
|
||||
<string name="do_block_server">Blokeatu zerbitzaria</string>
|
||||
<string name="block_user_x_instead">Blokeatu %s haren ordez</string>
|
||||
<string name="users_cant_see_blocked">Ez dituzu zerbitzari honetako erabiltzaileen bidalketarik edota jakinarazpenik ikusiko.</string>
|
||||
<string name="you_wont_see_server_posts">Ez dituzu zerbitzari honetako erabiltzaileen bidalketarik ikusiko.</string>
|
||||
<string name="server_followers_will_be_removed">Zerbitzari honetako zure jarraitzaileak ezabatu egingo dira.</string>
|
||||
<string name="server_cant_mention_or_follow_you">Zerbitzari honetako inork ezin zaitu jarraitu.</string>
|
||||
<string name="server_can_interact_with_older">Zerbitzari honetako erabiltzaileek zure bidalketa zaharrekin elkarreragin dezakete.</string>
|
||||
<string name="unblocked_domain_x">%s domeinua desblokeatu da</string>
|
||||
<string name="handle_help_title">Zer dago helbidean?</string>
|
||||
<string name="handle_title">Haien helbidea</string>
|
||||
<string name="handle_username_explanation">Haien helbide bakarra beraien zerbitzarian. Erabiltzaile-izen bera duten erabiltzaileak hainbat zerbitzaritan aurki daitezke.</string>
|
||||
<string name="handle_title_own">Zure helbidea</string>
|
||||
<string name="handle_username_explanation_own">Zure helbide bakarra zerbitzari honetan. Erabiltzaile-izen bera duten erabiltzaileak hainbat zerbitzaritan aurki daitezke.</string>
|
||||
<string name="server">Zerbitzaria</string>
|
||||
<string name="handle_server_explanation">Beraien etxe digitala, beraien bidalketa guztiak bizi diren tokia.</string>
|
||||
<string name="handle_explanation">Helbideek nor diren eta non dauden adierazten duelako, pertsonekin elkarreraginean jardun zaitezke <a>ActivityPub-ek bultzatutako plataformen</a> sare sozialaren bidez.</string>
|
||||
<string name="handle_server_explanation_own">Zure etxe digitala, zure bidalketa guztiak bizi diren tokia. Ez duzu hau gustuko? Transferitu zerbitzariak noiznahi, eta eraman zure jarraitzaileak ere.</string>
|
||||
<string name="handle_explanation_own">Zure helbideak nor zaren eta non zauden adierazten duenez, jendeak zurekin elkarreraginean jardun daiteke, <a>ActivityPub-ek bultzatutako plataformen</a> sare sozialaren bidez.</string>
|
||||
<string name="what_is_activitypub_title">Zer da ActivityPub?</string>
|
||||
<string name="what_is_activitypub">ActivityPub Mastodonek beste sare sozial batzuekin hitz egiten duen hizkuntza da.\n\nMastodonen ez ezik, bestelako aplikazio sozialetan ere konektatzeko eta elkarreraginean aritzeko aukera ematen dizu.</string>
|
||||
<string name="handle_copied">Helbidea arbelera kopiatu da.</string>
|
||||
<string name="qr_code">QR kodea</string>
|
||||
<string name="scan_qr_code">Eskaneatu QR kodea</string>
|
||||
<!-- Shown on a button that saves a file, after it was successfully saved -->
|
||||
<string name="saved">Gordea</string>
|
||||
<string name="image_saved">Irudia gorde da.</string>
|
||||
<string name="video_saved">Bideoa gorde da.</string>
|
||||
<string name="view_file">Ikusi</string>
|
||||
<string name="share_sheet_preview_profile">%s Mastodonen</string>
|
||||
<string name="share_sheet_preview_post">%1$s Mastodonen: “%2$s”</string>
|
||||
<string name="copy_profile_link">Kopiatu profilerako esteka</string>
|
||||
<string name="in_app_browser">Aplikazio barneko nabigatzailea</string>
|
||||
<string name="system_browser">Sistemako nabigatzailea</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,13 +31,10 @@
|
||||
<item quantity="other">پیگرفته</item>
|
||||
</plurals>
|
||||
<string name="posts">فرستهها</string>
|
||||
<string name="posts_and_replies">فرستهها و پاسخها</string>
|
||||
<string name="media">رسانه</string>
|
||||
<string name="profile_about">درباره</string>
|
||||
<string name="button_follow">پیگیری</string>
|
||||
<string name="button_following">پی میگیرید</string>
|
||||
<string name="edit_profile">ویرایش نمایه</string>
|
||||
<string name="share_user">همرسانی نمایهٔ</string>
|
||||
<string name="mute_user">خموشی %s</string>
|
||||
<string name="unmute_user">ناخموشی %s</string>
|
||||
<string name="block_user">مسدود %s</string>
|
||||
@@ -90,19 +87,9 @@
|
||||
<item quantity="other">%,d رأی</item>
|
||||
</plurals>
|
||||
<string name="poll_closed">پایانیافته</string>
|
||||
<string name="confirm_mute_title">خموشی حساب</string>
|
||||
<string name="confirm_mute">خموشی %s را تایید کنید</string>
|
||||
<string name="do_mute">خموش</string>
|
||||
<string name="confirm_unmute_title">لغو خموشی حساب</string>
|
||||
<string name="confirm_unmute">ناخموشی %s را تایید کنید</string>
|
||||
<string name="do_unmute">ناخموشی</string>
|
||||
<string name="confirm_block_title">مسدود کردن حساب</string>
|
||||
<string name="confirm_block_domain_title">مسدود کردن دامنهٔ</string>
|
||||
<string name="confirm_block">مسدود کردن %s را تایید کنید</string>
|
||||
<string name="do_block">مسدود کردن</string>
|
||||
<string name="confirm_unblock_title">رفع مسدودی حساب</string>
|
||||
<string name="confirm_unblock_domain_title">رفع مسدودیت دامنهٔ</string>
|
||||
<string name="confirm_unblock">رفع مسدودیت %s را تایید کنید</string>
|
||||
<string name="do_unblock">رفع مسدودیت</string>
|
||||
<string name="button_blocked">مسدود شده</string>
|
||||
<string name="action_vote">رأی</string>
|
||||
@@ -154,9 +141,7 @@
|
||||
<string name="report_personal_subtitle">در اینجا گزینههایی برای کنترل آنچه در ماستودون میبینید، وجود دارد:</string>
|
||||
<string name="back">بازگشت</string>
|
||||
<string name="search_communities">نام کارساز یا نشانی</string>
|
||||
<string name="instance_rules_title">قوانین کارساز</string>
|
||||
<string name="instance_rules_subtitle">با ادامه دادن، موافقت میکنید که از قوانین زیر پیروی کنید که توسط ناظران %s تنظیم و اجرا شده است.</string>
|
||||
<string name="signup_title">ایجاد حساب</string>
|
||||
<string name="display_name">نام</string>
|
||||
<string name="username">نام کاربری</string>
|
||||
<string name="email">رایانامه</string>
|
||||
@@ -164,7 +149,6 @@
|
||||
<string name="confirm_password">تأیید گذرواژه</string>
|
||||
<string name="password_note">برای افزایش قدرت گذرواژه خود، حروف بزرگ، کاراکترهای خاص و اعداد را اضافه کنید.</string>
|
||||
<string name="category_general">عمومی</string>
|
||||
<string name="confirm_email_title">صندوق ورودیتان را بررسی کنید</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="confirm_email_subtitle">برای تأیید %s، روی پیوندی که برای شما ارسال کردیم ضربه بزنید. همین جا منتظر میمانیم.</string>
|
||||
<string name="confirm_email_didnt_get">پیوندی نگرفتید؟</string>
|
||||
@@ -199,7 +183,6 @@
|
||||
<string name="theme_dark">تاریک</string>
|
||||
<string name="settings_behavior">رفتار</string>
|
||||
<string name="settings_gif">پخش ایموجی و آواتار های متحرک</string>
|
||||
<string name="settings_custom_tabs">استفاده از مرورگر درون برنامهای</string>
|
||||
<string name="settings_notifications">آگاهیها</string>
|
||||
<string name="settings_contribute">کمک به ماستودون</string>
|
||||
<string name="settings_tos">شرایط استفاده از خدمات</string>
|
||||
@@ -228,8 +211,6 @@
|
||||
<string name="followed_user">شما اکنون %s را پی میگیرید</string>
|
||||
<string name="following_user_requested">درخواست پیگیری %s شد</string>
|
||||
<string name="open_in_browser">بازکردن در مرورگر</string>
|
||||
<string name="hide_boosts_from_user">نهفتن تقویتهای %s</string>
|
||||
<string name="show_boosts_from_user">نمایش تقویتهای %s</string>
|
||||
<string name="signup_reason">چرا میخواهید عضو شوید؟</string>
|
||||
<string name="signup_reason_note">این برای بررسی درخواست شما به ما کمک خواهد کرد.</string>
|
||||
<string name="clear">پاککردن</string>
|
||||
@@ -252,7 +233,6 @@
|
||||
<string name="recommended_accounts_info_banner">ممکن است این حسابها را بر اساس حسابهای دیگری که پی میگیرید بپسندید.</string>
|
||||
<string name="see_new_posts">فرستههای جدید</string>
|
||||
<string name="load_missing_posts">بارگذاری فرستههای گم شده</string>
|
||||
<string name="follow_back">پیگیری متقابل</string>
|
||||
<string name="button_follow_pending">منتظر</string>
|
||||
<string name="follows_you">پیگیرتان است</string>
|
||||
<string name="manually_approves_followers">بهصورت دستی پیگیران را تایید میکند</string>
|
||||
@@ -308,28 +288,18 @@
|
||||
<string name="file_size_mb">%.2f مگابایت</string>
|
||||
<string name="file_size_gb">%.2f گیگابایت</string>
|
||||
<string name="upload_processing">در حال پردازش…</string>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is file size -->
|
||||
<string name="download_update">بارگیری (%s)</string>
|
||||
<string name="install_update">نصب</string>
|
||||
<string name="privacy_policy_title">حریم خصوصی شما</string>
|
||||
<string name="privacy_policy_subtitle">اگرچه برنامه ماستودون هیچ داده ای را جمعآوری نمی کند، کارسازی که از طریق آن ثبت نام می کنید ممکن است خط مشی متفاوتی داشته باشد.\n\nاگر با خط مشی %s موافق نیستید، می توانید به عقب برگردید و سرور دیگری را انتخاب کنید.</string>
|
||||
<string name="i_agree">موافقم</string>
|
||||
<string name="empty_list">این سیاهه خالی است</string>
|
||||
<string name="instance_signup_closed">این کارساز ثبت نام های جدید را نمی پذیرد.</string>
|
||||
<string name="text_copied">در تختهگیره رونوشت شد</string>
|
||||
<string name="add_bookmark">نشانک</string>
|
||||
<string name="remove_bookmark">برداشتن نشانک</string>
|
||||
<string name="bookmarks">نشانکها</string>
|
||||
<string name="your_favorites">برگزیده های شما</string>
|
||||
<string name="login_title">خوش برگشتید</string>
|
||||
<string name="login_subtitle">با کارسازی که حساب خود را در آن ایجاد کردید وارد شوید.</string>
|
||||
<string name="server_url">نشانی کارساز</string>
|
||||
<string name="server_filter_any_language">هر زبانی</string>
|
||||
<string name="server_filter_instant_signup">ثبت نام فوری</string>
|
||||
<string name="server_filter_manual_review">بررسی دستی</string>
|
||||
<string name="server_filter_any_signup_speed">هر سرعت ثبت نام</string>
|
||||
<string name="server_filter_region_europe">اروپا</string>
|
||||
<string name="server_filter_region_north_america">آمریکای شمالی</string>
|
||||
<string name="server_filter_region_south_america">آمریکای جنوبی</string>
|
||||
@@ -337,7 +307,6 @@
|
||||
<string name="server_filter_region_asia">آسیا</string>
|
||||
<string name="server_filter_region_oceania">اقیانوسیه</string>
|
||||
<string name="not_accepting_new_members">عدم پذیرش اعضای جدید</string>
|
||||
<string name="category_special_interests">منافع خاص</string>
|
||||
<string name="signup_passwords_dont_match">گذرواژهها مطابقت ندارند</string>
|
||||
<string name="profile_add_row">افزودن سطر</string>
|
||||
<string name="profile_setup">تنظیم نمایه</string>
|
||||
@@ -605,7 +574,6 @@
|
||||
<string name="remove">برداشتن</string>
|
||||
<string name="add_list_member">افزودن عضو</string>
|
||||
<string name="search_among_people_you_follow">جستوجو بین کسانی که پیگرفتهاید</string>
|
||||
<string name="add_user_to_list">افزودن به سیاهه…</string>
|
||||
<string name="add_user_to_list_title">افزودن به سیاهه</string>
|
||||
<!-- %s is a username -->
|
||||
<string name="remove_from_list">برداشتن از سیاهه</string>
|
||||
@@ -626,7 +594,10 @@
|
||||
<string name="list_no_members">هنوز هیچ عضوی نیست</string>
|
||||
<string name="list_find_users">پیدا کردن کاربرانی برای افزودن</string>
|
||||
<string name="reply_to_user">در پاسخ به %s</string>
|
||||
<string name="posted_at">فرستادهشده در %s</string>
|
||||
<string name="non_mutual_sheet_title">سلام، اتصال جدید!</string>
|
||||
<string name="got_it">فهمیدم</string>
|
||||
<string name="dont_remind_again">ديگر يادآوری نكن</string>
|
||||
<!-- %s is a time interval ("5 months") -->
|
||||
<plurals name="x_months">
|
||||
<item quantity="one">%,d ماه</item>
|
||||
@@ -644,4 +615,7 @@
|
||||
<string name="enter_invite_link">واردکردن پیوند دعوت</string>
|
||||
<string name="this_invite_is_invalid">این پیوند دعوت معتبر نیست.</string>
|
||||
<string name="this_invite_has_expired">این پیوند دعوت منقضی شده است.</string>
|
||||
<string name="server">کارساز</string>
|
||||
<string name="what_is_activitypub_title">اکتیویتی پاپ چیست؟</string>
|
||||
<!-- Shown on a button that saves a file, after it was successfully saved -->
|
||||
</resources>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user