Compare commits
154 Commits
feature/ca
...
1.2.3+fork
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1810821983 | ||
|
|
ea650d0154 | ||
|
|
62ac3458b5 | ||
|
|
b9ca0d74cf | ||
|
|
ee9bd3dd4b | ||
|
|
d3e24bd35c | ||
|
|
fa6ee2cba4 | ||
|
|
f67d28c7ec | ||
|
|
80aa73555f | ||
|
|
6812ecbc3e | ||
|
|
b90a85cd6f | ||
|
|
90fef0aaed | ||
|
|
d054d87438 | ||
|
|
03b37ad40f | ||
|
|
75410a22c9 | ||
|
|
064f71ef8a | ||
|
|
1ff34d5b24 | ||
|
|
7710b25ee5 | ||
|
|
dcf96c8a1c | ||
|
|
a501a41cb2 | ||
|
|
5709389322 | ||
|
|
bb4a52f03a | ||
|
|
50360059ce | ||
|
|
08866e0bea | ||
|
|
63bcef990b | ||
|
|
a177d35c4f | ||
|
|
2bbcae9fc6 | ||
|
|
7a46992d88 | ||
|
|
1beba48b66 | ||
|
|
e7ce822f7e | ||
|
|
8094d8c7a3 | ||
|
|
b214974228 | ||
|
|
b27a5a2431 | ||
|
|
7cf01e89b8 | ||
|
|
0c014dcf0f | ||
|
|
cf3d6afffc | ||
|
|
8fcc27d2b3 | ||
|
|
77734e8e0c | ||
|
|
6ecc6c4621 | ||
|
|
94eb6b5775 | ||
|
|
6595a088fb | ||
|
|
b463ef65ce | ||
|
|
b22a25e7af | ||
|
|
f197d6f19a | ||
|
|
45e3cc2d24 | ||
|
|
b226fcc128 | ||
|
|
7b4728cff7 | ||
|
|
9977d7168a | ||
|
|
10fc8c9b03 | ||
|
|
034be8f78f | ||
|
|
77ba8b9c9b | ||
|
|
ad22c30775 | ||
|
|
6b42b7d7f0 | ||
|
|
3520018812 | ||
|
|
b0353920a3 | ||
|
|
e1a262ec5f | ||
|
|
c75b436745 | ||
|
|
b1006b33f0 | ||
|
|
3af7518cf4 | ||
|
|
91b9fdf5ce | ||
|
|
22f5667549 | ||
|
|
8115578e94 | ||
|
|
08dc122b6b | ||
|
|
e3199c009e | ||
|
|
e1f5c9cb63 | ||
|
|
7ca4f2ab2c | ||
|
|
2e09010151 | ||
|
|
3d79e87ec8 | ||
|
|
09ed3c647a | ||
|
|
172515ba0a | ||
|
|
a2f0fc8c87 | ||
|
|
888cee4556 | ||
|
|
c005e9bf18 | ||
|
|
cda565aadb | ||
|
|
64362968fc | ||
|
|
e7654affa7 | ||
|
|
320027ca9b | ||
|
|
ad2678da7c | ||
|
|
c56c7448d0 | ||
|
|
731e67725c | ||
|
|
8178f81c85 | ||
|
|
6fbf00a132 | ||
|
|
a39024dfe1 | ||
|
|
7a0e827e23 | ||
|
|
968cde9e4c | ||
|
|
f2ab2acef7 | ||
|
|
81d1ecc5f8 | ||
|
|
513e6439ff | ||
|
|
1f0108b14e | ||
|
|
1433d0717e | ||
|
|
be91775f4b | ||
|
|
7b2f8d2be3 | ||
|
|
54c386ccec | ||
|
|
9d9e98959f | ||
|
|
a3cd7224bd | ||
|
|
6cf214f127 | ||
|
|
b6976fb519 | ||
|
|
871ada23ab | ||
|
|
040237de2b | ||
|
|
29b2a25840 | ||
|
|
bca157a4af | ||
|
|
1f4cf2b2f0 | ||
|
|
bd2f05be67 | ||
|
|
123fef2cc7 | ||
|
|
636bab51da | ||
|
|
ed075b276f | ||
|
|
dd25f3380a | ||
|
|
1a2d1efa29 | ||
|
|
f3e1fa4b2b | ||
|
|
666f7c4ac0 | ||
|
|
fc307ff43f | ||
|
|
04304b3397 | ||
|
|
23f4b63195 | ||
|
|
edeae13dda | ||
|
|
44558534e9 | ||
|
|
2b760bb215 | ||
|
|
44154a987d | ||
|
|
7260db6668 | ||
|
|
1dadc51ddf | ||
|
|
fe85351869 | ||
|
|
74a83c6ac4 | ||
|
|
acb1369e88 | ||
|
|
8e0d74f9c2 | ||
|
|
df77ba61ad | ||
|
|
ed40f74d59 | ||
|
|
42e26bef68 | ||
|
|
af60adb55f | ||
|
|
b94741feae | ||
|
|
e43d6c35d8 | ||
|
|
4a6f9e80b1 | ||
|
|
ec02680507 | ||
|
|
5fc569a45a | ||
|
|
4bc9c5691d | ||
|
|
19b68855ac | ||
|
|
70fdfb612e | ||
|
|
0a32c217d8 | ||
|
|
5dfa9237ad | ||
|
|
573ff75498 | ||
|
|
87c37df370 | ||
|
|
7fb0944e66 | ||
|
|
35c8a3d121 | ||
|
|
9e58413d1a | ||
|
|
90e60aef84 | ||
|
|
8547ce05ed | ||
|
|
0825faee5c | ||
|
|
d43a697df7 | ||
|
|
3b742c4391 | ||
|
|
a43a396043 | ||
|
|
bcb4fac553 | ||
|
|
c890195567 | ||
|
|
b50a327b17 | ||
|
|
dbe7eb25ff | ||
|
|
45ecec09f5 | ||
|
|
9b4556d293 |
9
img/ic_fluent_animal_cat_24_regular.svg
Normal file
9
img/ic_fluent_animal_cat_24_regular.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
path { fill: black; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path { fill: white; }
|
||||
}
|
||||
</style>
|
||||
<path d="M15.4925 3.50673C14.652 3.58251 13.9933 4.28929 13.9933 5.15V10C13.9933 10.4142 13.6577 10.75 13.2437 10.75C11.8002 10.75 10.7863 11.3378 10.0365 12.238C9.26389 13.1656 8.7607 14.444 8.44554 15.7954C8.13254 17.1376 8.01871 18.4912 7.98453 19.5172C7.97182 19.8987 7.9702 20.2324 7.97313 20.5H14.9928V19.75C14.9928 18.5074 13.986 17.5 12.744 17.5H11.4947C11.0807 17.5 10.7451 17.1642 10.7451 16.75C10.7451 16.3358 11.0807 16 11.4947 16H12.744C14.8139 16 16.4919 17.6789 16.4919 19.75V20.5H17.2415C17.6555 20.5 17.9911 20.1642 17.9911 19.75V9.75C17.9911 9.33579 18.3267 9 18.7407 9H19.2472C20.2264 9 20.8249 7.92404 20.309 7.09132L19.6893 6.09132C19.4615 5.72367 19.0599 5.5 18.6275 5.5H16.2421C15.8281 5.5 15.4925 5.16421 15.4925 4.75V3.50673ZM6.47388 20.5C6.47098 20.2156 6.47293 19.8655 6.4862 19.4672C6.52229 18.3838 6.64271 16.9249 6.98559 15.4546C7.32631 13.9935 7.90065 12.4594 8.88484 11.2777C9.75681 10.2307 10.9399 9.47669 12.4942 9.29318V5.15C12.4942 3.4103 13.9037 2 15.6424 2C16.3876 2 16.9916 2.60442 16.9916 3.35V4H18.6275C19.5787 4 20.4622 4.49207 20.9634 5.30092L21.5831 6.30092C22.6749 8.06291 21.4985 10.32 19.4903 10.4898V19.75C19.4903 20.9926 18.4835 22 17.2415 22H7.24708L7.24537 22H5.79625C3.69964 22 2 20.2994 2 18.2016C2 17.2395 2.36489 16.3133 3.02098 15.6099L4.15612 14.393C4.92005 13.5741 5.17521 12.4027 4.82117 11.3399C4.67114 10.8896 4.41837 10.4804 4.08288 10.1447L2.96914 9.03042C2.67641 8.73753 2.67639 8.26266 2.96912 7.96976C3.26184 7.67686 3.73645 7.67685 4.02919 7.96974L5.14293 9.08405C5.643 9.58438 6.01977 10.1943 6.2434 10.8656C6.77114 12.4497 6.3908 14.1958 5.25209 15.4165L4.11695 16.6334C3.71996 17.059 3.49916 17.6195 3.49916 18.2016C3.49916 19.471 4.52761 20.5 5.79625 20.5H6.47388Z" fill="#212121"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -16,8 +16,8 @@ android {
|
||||
applicationId "org.joinmastodon.android.moshinda"
|
||||
minSdk 23
|
||||
targetSdk 33
|
||||
versionCode 100
|
||||
versionName "1.3.0+fork.100.moshinda"
|
||||
versionCode 101
|
||||
versionName "1.2.3+fork.101.moshinda"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
resourceConfigurations += ['ar-rSA', 'ar-rDZ', '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', 'kab', 'ko-rKR', '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']
|
||||
}
|
||||
|
||||
39
mastodon/proguard-rules.pro
vendored
39
mastodon/proguard-rules.pro
vendored
@@ -30,6 +30,9 @@
|
||||
*;
|
||||
}
|
||||
|
||||
# i don't know how proguard works
|
||||
-keep class org.joinmastodon.android.** { *; }
|
||||
|
||||
# Keep all enums for debugging purposes
|
||||
-keepnames public enum * {
|
||||
*;
|
||||
@@ -53,3 +56,39 @@
|
||||
-keep interface org.parceler.Parcel
|
||||
-keep @org.parceler.Parcel class * { *; }
|
||||
-keep class **$$Parcelable { *; }
|
||||
|
||||
##---------------Begin: proguard configuration for Gson ----------
|
||||
# Gson uses generic type information stored in a class file when working with fields. Proguard
|
||||
# removes such information by default, so configure it to keep all of it.
|
||||
-keepattributes Signature
|
||||
|
||||
# For using GSON @Expose annotation
|
||||
-keepattributes *Annotation*
|
||||
|
||||
# Gson specific classes
|
||||
-dontwarn sun.misc.**
|
||||
#-keep class com.google.gson.stream.** { *; }
|
||||
|
||||
# Application classes that will be serialized/deserialized over Gson
|
||||
-keep class com.google.gson.examples.android.model.** { <fields>; }
|
||||
|
||||
# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
|
||||
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
|
||||
-keep class * extends com.google.gson.TypeAdapter
|
||||
-keep class * implements com.google.gson.TypeAdapterFactory
|
||||
-keep class * implements com.google.gson.JsonSerializer
|
||||
-keep class * implements com.google.gson.JsonDeserializer
|
||||
|
||||
# Prevent R8 from leaving Data object members always null
|
||||
-keepclassmembers,allowobfuscation class * {
|
||||
@com.google.gson.annotations.SerializedName <fields>;
|
||||
}
|
||||
|
||||
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
|
||||
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
|
||||
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
|
||||
|
||||
##---------------End: proguard configuration for Gson ----------
|
||||
|
||||
|
||||
-dontobfuscate
|
||||
|
||||
@@ -93,6 +93,16 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<provider
|
||||
android:name="org.joinmastodon.android.utils.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -69,7 +69,7 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
||||
.ifPresent(req ->
|
||||
req.wrapProgress(this, R.string.loading, true, d -> {
|
||||
UiUtils.transformDialogForLookup(this, accountId, isFediUrl ? text.get() : null, d);
|
||||
d.setOnDismissListener((ev) -> finish());
|
||||
d.setOnDismissListener((x) -> finish());
|
||||
}));
|
||||
} else {
|
||||
openComposeFragment(accountId);
|
||||
|
||||
@@ -43,7 +43,7 @@ public class GlobalUserPreferences{
|
||||
public static boolean showAltIndicator;
|
||||
public static boolean showNoAltIndicator;
|
||||
public static boolean enablePreReleases;
|
||||
public static boolean prefixRepliesWithRe;
|
||||
public static PrefixRepliesMode prefixReplies;
|
||||
public static boolean bottomEncoding;
|
||||
public static boolean collapseLongPosts;
|
||||
public static boolean spectatorMode;
|
||||
@@ -57,14 +57,12 @@ public class GlobalUserPreferences{
|
||||
public static boolean loadRemoteAccountFollowers;
|
||||
public static boolean mentionRebloggerAutomatically;
|
||||
public static boolean allowRemoteLoading;
|
||||
public static boolean forwardReportDefault;
|
||||
public static AutoRevealMode autoRevealEqualSpoilers;
|
||||
public static String publishButtonText;
|
||||
public static ThemePreference theme;
|
||||
public static ColorPreference color;
|
||||
|
||||
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
|
||||
private final static Type pinnedTimelinesType = new TypeToken<Map<String, List<TimelineDefinition>>>() {}.getType();
|
||||
private final static Type accountsDefaultContentTypesType = new TypeToken<Map<String, ContentType>>() {}.getType();
|
||||
public static Map<String, List<String>> recentLanguages;
|
||||
public static Map<String, List<TimelineDefinition>> pinnedTimelines;
|
||||
public static Set<String> accountsWithLocalOnlySupport;
|
||||
@@ -72,6 +70,10 @@ public class GlobalUserPreferences{
|
||||
public static Set<String> accountsWithContentTypesEnabled;
|
||||
public static Map<String, ContentType> accountsDefaultContentTypes;
|
||||
|
||||
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
|
||||
private final static Type pinnedTimelinesType = new TypeToken<Map<String, List<TimelineDefinition>>>() {}.getType();
|
||||
private final static Type accountsDefaultContentTypesType = new TypeToken<Map<String, ContentType>>() {}.getType();
|
||||
|
||||
private final static Type recentEmojisType = new TypeToken<Map<String, Integer>>() {}.getType();
|
||||
public static Map<String, Integer> recentEmojis;
|
||||
|
||||
@@ -126,7 +128,7 @@ public class GlobalUserPreferences{
|
||||
showAltIndicator=prefs.getBoolean("showAltIndicator", true);
|
||||
showNoAltIndicator=prefs.getBoolean("showNoAltIndicator", true);
|
||||
enablePreReleases=prefs.getBoolean("enablePreReleases", false);
|
||||
prefixRepliesWithRe=prefs.getBoolean("prefixRepliesWithRe", false);
|
||||
prefixReplies=PrefixRepliesMode.valueOf(prefs.getString("prefixReplies", PrefixRepliesMode.NEVER.name()));
|
||||
bottomEncoding=prefs.getBoolean("bottomEncoding", false);
|
||||
collapseLongPosts=prefs.getBoolean("collapseLongPosts", true);
|
||||
spectatorMode=prefs.getBoolean("spectatorMode", false);
|
||||
@@ -153,6 +155,16 @@ public class GlobalUserPreferences{
|
||||
accountsDefaultContentTypes=fromJson(prefs.getString("accountsDefaultContentTypes", null), accountsDefaultContentTypesType, new HashMap<>());
|
||||
allowRemoteLoading=prefs.getBoolean("allowRemoteLoading", true);
|
||||
autoRevealEqualSpoilers=AutoRevealMode.valueOf(prefs.getString("autoRevealEqualSpoilers", AutoRevealMode.THREADS.name()));
|
||||
forwardReportDefault=prefs.getBoolean("forwardReportDefault", true);
|
||||
|
||||
if (prefs.contains("prefixRepliesWithRe")) {
|
||||
prefixReplies = prefs.getBoolean("prefixRepliesWithRe", false)
|
||||
? PrefixRepliesMode.TO_OTHERS : PrefixRepliesMode.NEVER;
|
||||
prefs.edit()
|
||||
.putString("prefixReplies", prefixReplies.name())
|
||||
.remove("prefixRepliesWithRe")
|
||||
.apply();
|
||||
}
|
||||
|
||||
try {
|
||||
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
|
||||
@@ -179,6 +191,8 @@ public class GlobalUserPreferences{
|
||||
.putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings)
|
||||
.putBoolean("disableMarquee", disableMarquee)
|
||||
.putBoolean("disableSwipe", disableSwipe)
|
||||
.putBoolean("enableDeleteNotifications", enableDeleteNotifications)
|
||||
.putBoolean("translateButtonOpenedOnly", translateButtonOpenedOnly)
|
||||
.putBoolean("showDividers", showDividers)
|
||||
.putBoolean("relocatePublishButton", relocatePublishButton)
|
||||
.putBoolean("uniformNotificationIcon", uniformNotificationIcon)
|
||||
@@ -189,7 +203,7 @@ public class GlobalUserPreferences{
|
||||
.putBoolean("showAltIndicator", showAltIndicator)
|
||||
.putBoolean("showNoAltIndicator", showNoAltIndicator)
|
||||
.putBoolean("enablePreReleases", enablePreReleases)
|
||||
.putBoolean("prefixRepliesWithRe", prefixRepliesWithRe)
|
||||
.putString("prefixReplies", prefixReplies.name())
|
||||
.putBoolean("collapseLongPosts", collapseLongPosts)
|
||||
.putBoolean("spectatorMode", spectatorMode)
|
||||
.putBoolean("autoHideFab", autoHideFab)
|
||||
@@ -216,6 +230,7 @@ public class GlobalUserPreferences{
|
||||
.putString("accountsDefaultContentTypes", gson.toJson(accountsDefaultContentTypes))
|
||||
.putBoolean("allowRemoteLoading", allowRemoteLoading)
|
||||
.putString("autoRevealEqualSpoilers", autoRevealEqualSpoilers.name())
|
||||
.putBoolean("forwardReportDefault", forwardReportDefault)
|
||||
.apply();
|
||||
}
|
||||
|
||||
@@ -242,4 +257,10 @@ public class GlobalUserPreferences{
|
||||
THREADS,
|
||||
DISCUSSIONS
|
||||
}
|
||||
|
||||
public enum PrefixRepliesMode {
|
||||
NEVER,
|
||||
ALWAYS,
|
||||
TO_OTHERS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import android.widget.Toast;
|
||||
import org.joinmastodon.android.api.ObjectValidationException;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.PictureTakenEvent;
|
||||
import org.joinmastodon.android.events.TakePictureRequestEvent;
|
||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||
import org.joinmastodon.android.fragments.HomeFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
@@ -132,7 +132,9 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
|
||||
Log.w("MainActivity", x);
|
||||
return;
|
||||
}
|
||||
UiUtils.showFragmentForNotification(this, notification, accountID, null);
|
||||
Bundle args = new Bundle();
|
||||
args.putBoolean("noTransition", true);
|
||||
UiUtils.showFragmentForNotification(this, notification, accountID, args);
|
||||
}
|
||||
|
||||
private void showFragmentForExternalShare(Bundle args) {
|
||||
@@ -197,22 +199,19 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data){
|
||||
if(requestCode==CAMERA_PIC_REQUEST_CODE && resultCode== Activity.RESULT_OK){
|
||||
Bitmap image = (Bitmap) data.getExtras().get("data");
|
||||
String path = MediaStore.Images.Media.insertImage(this.getContentResolver(), image, null, null);
|
||||
E.post(new PictureTakenEvent(Uri.parse(path)));
|
||||
}
|
||||
}
|
||||
// @Override
|
||||
// public void onActivityResult(int requestCode, int resultCode, Intent data){
|
||||
// if(requestCode==CAMERA_PIC_REQUEST_CODE && resultCode== Activity.RESULT_OK){
|
||||
// E.post(new TakePictureRequestEvent());
|
||||
// }
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
|
||||
if (requestCode == CAMERA_PERMISSION_CODE && (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
||||
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
startActivityForResult(cameraIntent, CAMERA_PIC_REQUEST_CODE);
|
||||
E.post(new TakePictureRequestEvent());
|
||||
} else {
|
||||
Toast.makeText(this, R.string.permission_required, Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.joinmastodon.android;
|
||||
|
||||
import static org.joinmastodon.android.GlobalUserPreferences.PrefixRepliesMode.ALWAYS;
|
||||
import static org.joinmastodon.android.GlobalUserPreferences.PrefixRepliesMode.TO_OTHERS;
|
||||
import static org.joinmastodon.android.GlobalUserPreferences.getPrefs;
|
||||
|
||||
import android.app.Notification;
|
||||
@@ -320,7 +322,11 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||
req.language = preferences.postingDefaultLanguage;
|
||||
req.visibility = preferences.postingDefaultVisibility;
|
||||
req.inReplyToId = notification.status.id;
|
||||
if(!notification.status.spoilerText.isEmpty() && GlobalUserPreferences.prefixRepliesWithRe && !notification.status.spoilerText.startsWith("re: ")){
|
||||
|
||||
if (!notification.status.spoilerText.isEmpty() &&
|
||||
(GlobalUserPreferences.prefixReplies == ALWAYS
|
||||
|| (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(notification.status.account.id)))
|
||||
&& !notification.status.spoilerText.startsWith("re: ")) {
|
||||
req.spoilerText = "re: " + notification.status.spoilerText;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,16 +4,18 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.ListTimeline;
|
||||
|
||||
public class CreateList extends MastodonAPIRequest<ListTimeline> {
|
||||
public CreateList(String title, ListTimeline.RepliesPolicy repliesPolicy) {
|
||||
public CreateList(String title, boolean exclusive, ListTimeline.RepliesPolicy repliesPolicy) {
|
||||
super(HttpMethod.POST, "/lists", ListTimeline.class);
|
||||
Request req = new Request();
|
||||
req.title = title;
|
||||
req.exclusive = exclusive;
|
||||
req.repliesPolicy = repliesPolicy;
|
||||
setRequestBody(req);
|
||||
}
|
||||
|
||||
public static class Request {
|
||||
public String title;
|
||||
public boolean exclusive;
|
||||
public ListTimeline.RepliesPolicy repliesPolicy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.ListTimeline;
|
||||
|
||||
public class UpdateList extends MastodonAPIRequest<ListTimeline> {
|
||||
public UpdateList(String id, String title, ListTimeline.RepliesPolicy repliesPolicy) {
|
||||
public UpdateList(String id, String title, boolean exclusive, ListTimeline.RepliesPolicy repliesPolicy) {
|
||||
super(HttpMethod.PUT, "/lists/" + id, ListTimeline.class);
|
||||
CreateList.Request req = new CreateList.Request();
|
||||
req.title = title;
|
||||
req.exclusive = exclusive;
|
||||
req.repliesPolicy = repliesPolicy;
|
||||
setRequestBody(req);
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ public class ListUpdatedCreatedEvent {
|
||||
public final String id;
|
||||
public final String title;
|
||||
public final ListTimeline.RepliesPolicy repliesPolicy;
|
||||
public final boolean exclusive;
|
||||
|
||||
public ListUpdatedCreatedEvent(String id, String title, ListTimeline.RepliesPolicy repliesPolicy) {
|
||||
public ListUpdatedCreatedEvent(String id, String title, boolean exclusive, ListTimeline.RepliesPolicy repliesPolicy) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.exclusive = exclusive;
|
||||
this.repliesPolicy = repliesPolicy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package org.joinmastodon.android.events;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import org.joinmastodon.android.model.Poll;
|
||||
|
||||
public class PictureTakenEvent {
|
||||
public Uri uri;
|
||||
|
||||
public PictureTakenEvent(Uri uri){
|
||||
this.uri = uri;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.joinmastodon.android.events;
|
||||
|
||||
public class TakePictureRequestEvent {
|
||||
public TakePictureRequestEvent(){
|
||||
}
|
||||
}
|
||||
@@ -94,12 +94,15 @@ public class AnnouncementsFragment extends BaseStatusListFragment<Announcement>
|
||||
@Override
|
||||
public void onSuccess(List<Announcement> result){
|
||||
if (getActivity() == null) return;
|
||||
List<Announcement> unread = result.stream().filter(a -> !a.read).collect(toList());
|
||||
List<Announcement> read = result.stream().filter(a -> a.read).collect(toList());
|
||||
onDataLoaded(unread, true);
|
||||
onDataLoaded(read, false);
|
||||
if (unread.isEmpty()) setResult(true, null);
|
||||
else unreadIDs = unread.stream().map(a -> a.id).collect(toList());
|
||||
|
||||
// get unread items first
|
||||
List<Announcement> data = result.stream().filter(a -> !a.read).collect(toList());
|
||||
if (data.isEmpty()) setResult(true, null);
|
||||
else unreadIDs = data.stream().map(a -> a.id).collect(toList());
|
||||
|
||||
// append read items at the end
|
||||
data.addAll(result.stream().filter(a -> a.read).collect(toList()));
|
||||
onDataLoaded(data, false);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import static org.joinmastodon.android.GlobalUserPreferences.PrefixRepliesMode.*;
|
||||
import static org.joinmastodon.android.GlobalUserPreferences.recentLanguages;
|
||||
import static org.joinmastodon.android.api.requests.statuses.CreateStatus.DRAFTS_AFTER_INSTANT;
|
||||
import static org.joinmastodon.android.api.requests.statuses.CreateStatus.getDraftInstant;
|
||||
@@ -69,8 +70,6 @@ import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.github.bottomSoftwareFoundation.bottom.Bottom;
|
||||
import com.squareup.otto.Subscribe;
|
||||
import com.twitter.twittertext.TwitterTextEmojiRegex;
|
||||
@@ -89,14 +88,12 @@ import org.joinmastodon.android.api.requests.statuses.GetAttachmentByID;
|
||||
import org.joinmastodon.android.api.requests.statuses.UploadAttachment;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.PictureTakenEvent;
|
||||
import org.joinmastodon.android.events.TakePictureRequestEvent;
|
||||
import org.joinmastodon.android.events.ScheduledStatusCreatedEvent;
|
||||
import org.joinmastodon.android.events.ScheduledStatusDeletedEvent;
|
||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||
import org.joinmastodon.android.fragments.settings.SettingsBaseFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.model.ContentType;
|
||||
@@ -118,7 +115,7 @@ import org.joinmastodon.android.ui.text.ComposeAutocompleteSpan;
|
||||
import org.joinmastodon.android.ui.text.ComposeHashtagOrMentionSpan;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
import org.joinmastodon.android.utils.FileProvider;
|
||||
import org.joinmastodon.android.utils.TransferSpeedTracker;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ComposeEditText;
|
||||
@@ -131,6 +128,9 @@ import org.joinmastodon.android.utils.StatusTextEncoder;
|
||||
import org.parceler.Parcel;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
@@ -244,6 +244,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
private boolean creatingView;
|
||||
private boolean ignoreSelectionChanges=false;
|
||||
private Runnable updateUploadEtaRunnable;
|
||||
private Uri photoUri;
|
||||
|
||||
private String language, encoding;
|
||||
private ContentType contentType;
|
||||
@@ -382,11 +383,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
|
||||
attachPopup.setOnMenuItemClickListener(i -> {
|
||||
if (i.getItemId() == R.id.camera){
|
||||
if (getContext().checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
|
||||
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
startActivityForResult(cameraIntent, CAMERA_PIC_REQUEST_CODE);
|
||||
} else {
|
||||
getActivity().requestPermissions(new String[] { Manifest.permission.CAMERA }, CAMERA_PERMISSION_CODE);
|
||||
try {
|
||||
openCamera();
|
||||
} catch (IOException e){
|
||||
Toast.makeText(getContext(), e.getMessage(), Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -807,9 +807,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
if(!TextUtils.isEmpty(status.spoilerText)){
|
||||
hasSpoiler=true;
|
||||
spoilerEdit.setVisibility(View.VISIBLE);
|
||||
if(GlobalUserPreferences.prefixRepliesWithRe && !status.spoilerText.startsWith("re: ")){
|
||||
if ((GlobalUserPreferences.prefixReplies == ALWAYS
|
||||
|| (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(status.account.id)))
|
||||
&& !status.spoilerText.startsWith("re: ")) {
|
||||
spoilerEdit.setText("re: " + status.spoilerText);
|
||||
}else{
|
||||
} else {
|
||||
spoilerEdit.setText(status.spoilerText);
|
||||
}
|
||||
spoilerBtn.setSelected(true);
|
||||
@@ -1461,15 +1463,37 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}
|
||||
|
||||
if(requestCode==CAMERA_PIC_REQUEST_CODE && resultCode==Activity.RESULT_OK){
|
||||
Bitmap image = (Bitmap) data.getExtras().get("data");
|
||||
String path = MediaStore.Images.Media.insertImage(getContext().getContentResolver(), image, null, null);
|
||||
addMediaAttachment(Uri.parse(path), null);
|
||||
addMediaAttachment(photoUri, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPictureTaken(PictureTakenEvent ev){
|
||||
addMediaAttachment(ev.uri, null);
|
||||
public void onTakePictureRequest(TakePictureRequestEvent ev) {
|
||||
if(isVisible()) {
|
||||
try {
|
||||
openCamera();
|
||||
} catch (IOException e) {
|
||||
Toast.makeText(getContext(), e.getMessage(), Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void openCamera() throws IOException {
|
||||
if (getContext().checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
|
||||
File photoFile = File.createTempFile("img", ".jpg");
|
||||
photoUri = FileProvider.getUriForFile(getContext(), getContext().getPackageName() + ".fileprovider", photoFile);
|
||||
|
||||
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
|
||||
if(getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)){
|
||||
startActivityForResult(cameraIntent, CAMERA_PIC_REQUEST_CODE);
|
||||
} else {
|
||||
Toast.makeText(getContext(), R.string.mo_camera_not_available, Toast.LENGTH_SHORT);
|
||||
}
|
||||
} else {
|
||||
getActivity().requestPermissions(new String[]{Manifest.permission.CAMERA}, CAMERA_PERMISSION_CODE);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean addMediaAttachment(Uri uri, String description){
|
||||
|
||||
@@ -2,21 +2,30 @@ package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
|
||||
import org.joinmastodon.android.DomainManager;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.tags.GetHashtag;
|
||||
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.TimelineDefinition;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class CustomLocalTimelineFragment extends StatusListFragment implements ProvidesAssistContent.ProvidesWebUri {
|
||||
public class CustomLocalTimelineFragment extends PinnableStatusListFragment implements ProvidesAssistContent.ProvidesWebUri{
|
||||
// private String name;
|
||||
private String domain;
|
||||
|
||||
@@ -74,6 +83,13 @@ public class CustomLocalTimelineFragment extends StatusListFragment implements P
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.custom_local_timelines, menu);
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
UiUtils.enableOptionsMenuIcons(getContext(), menu, R.id.pin);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return null;
|
||||
@@ -83,4 +99,9 @@ public class CustomLocalTimelineFragment extends StatusListFragment implements P
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return Uri.parse(domain);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TimelineDefinition makeTimelineDefinition() {
|
||||
return TimelineDefinition.ofCustomLocalTimeline(domain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,6 +255,10 @@ public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsL
|
||||
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
|
||||
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
|
||||
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, item.account.statusesCount)));
|
||||
followersCount.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
||||
followersLabel.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
||||
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
||||
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
||||
relationship=relationships.get(item.account.id);
|
||||
if(relationship == null || !relationship.followedBy){
|
||||
actionWrap.setVisibility(View.GONE);
|
||||
|
||||
@@ -498,6 +498,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
||||
} else if ((list = listItems.get(id)) != null) {
|
||||
args.putString("listID", list.id);
|
||||
args.putString("listTitle", list.title);
|
||||
args.putBoolean("listIsExclusive", list.exclusive);
|
||||
if (list.repliesPolicy != null) args.putInt("repliesPolicy", list.repliesPolicy.ordinal());
|
||||
Nav.go(getActivity(), ListTimelineFragment.class, args);
|
||||
} else if ((hashtag = hashtagsItems.get(id)) != null) {
|
||||
|
||||
@@ -41,11 +41,6 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDomain() {
|
||||
return super.getDomain() + "/home";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
@@ -53,12 +48,13 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||
loadData();
|
||||
}
|
||||
|
||||
private boolean typeFilterPredicate(Status s) {
|
||||
return (GlobalUserPreferences.showReplies || s.inReplyToId == null) &&
|
||||
(GlobalUserPreferences.showBoosts || s.reblog == null);
|
||||
}
|
||||
|
||||
private List<Status> filterPosts(List<Status> items) {
|
||||
// This is the function I must use to solve the filters thing for real
|
||||
return items.stream().filter(i ->
|
||||
(GlobalUserPreferences.showReplies || i.inReplyToId == null) &&
|
||||
(GlobalUserPreferences.showBoosts || i.reblog == null)
|
||||
).collect(Collectors.toList());
|
||||
return items.stream().filter(this::typeFilterPredicate).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -107,24 +103,24 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||
@Override
|
||||
protected void onHidden(){
|
||||
super.onHidden();
|
||||
// if(!data.isEmpty()){
|
||||
// String topPostID=displayItems.get(list.getChildAdapterPosition(list.getChildAt(0))-getMainAdapterOffset()).parentID;
|
||||
// if(!topPostID.equals(lastSavedMarkerID)){
|
||||
// lastSavedMarkerID=topPostID;
|
||||
// new SaveMarkers(topPostID, null)
|
||||
// .setCallback(new Callback<>(){
|
||||
// @Override
|
||||
// public void onSuccess(SaveMarkers.Response result){
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void onError(ErrorResponse error){
|
||||
// lastSavedMarkerID=null;
|
||||
// }
|
||||
// })
|
||||
// .exec(accountID);
|
||||
// }
|
||||
// }
|
||||
if(!data.isEmpty()){
|
||||
String topPostID=displayItems.get(Math.max(0, list.getChildAdapterPosition(list.getChildAt(0))-getMainAdapterOffset())).parentID;
|
||||
if(!topPostID.equals(lastSavedMarkerID)){
|
||||
lastSavedMarkerID=topPostID;
|
||||
new SaveMarkers(topPostID, null)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(SaveMarkers.Response result){
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
lastSavedMarkerID=null;
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onStatusCreated(StatusCreatedEvent ev){
|
||||
@@ -238,7 +234,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||
for(Status s:result){
|
||||
if(idsBelowGap.contains(s.id))
|
||||
break;
|
||||
if(filterPredicate.test(s)){
|
||||
if(typeFilterPredicate(s) && filterPredicate.test(s)){
|
||||
targetList.addAll(buildDisplayItems(s));
|
||||
insertedPosts.add(s);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.res.Configuration;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableStringBuilder;
|
||||
@@ -47,8 +48,11 @@ import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.CoverImageView;
|
||||
import org.joinmastodon.android.ui.views.LinkedTextView;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -65,7 +69,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class InstanceInfoFragment extends LoaderFragment {
|
||||
public class InstanceInfoFragment extends LoaderFragment implements ProvidesAssistContent.ProvidesWebUri {
|
||||
|
||||
private Instance instance;
|
||||
private String extendedDescription;
|
||||
@@ -233,6 +237,7 @@ public class InstanceInfoFragment extends LoaderFragment {
|
||||
toolbarTitleView.setTranslationY(titleTransY);
|
||||
toolbarSubtitleView.setTranslationY(titleTransY);
|
||||
}
|
||||
RecyclerFragment.setRefreshLayoutColors(refreshLayout);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -256,7 +261,7 @@ public class InstanceInfoFragment extends LoaderFragment {
|
||||
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(instance.thumbnail, 1000, 1000));
|
||||
uri.setText(instance.title);
|
||||
setTitle(instance.title);
|
||||
setSubtitle(instance.uri);
|
||||
setSubtitle(targetDomain);
|
||||
|
||||
updateDescription();
|
||||
collapseDescription();
|
||||
@@ -267,7 +272,7 @@ public class InstanceInfoFragment extends LoaderFragment {
|
||||
if (instance.contactAccount != null) {
|
||||
AccountField admin = new AccountField();
|
||||
admin.parsedName=admin.name=getContext().getString(R.string.mo_instance_admin);
|
||||
admin.parsedValue=buildLinkText(instance.contactAccount.url, instance.contactAccount.getDisplayUsername() + "@" + instance.uri);
|
||||
admin.parsedValue=buildLinkText(instance.contactAccount.url, instance.contactAccount.getDisplayUsername() + "@" + targetDomain);
|
||||
fields.add(admin);
|
||||
}
|
||||
|
||||
@@ -361,23 +366,28 @@ public class InstanceInfoFragment extends LoaderFragment {
|
||||
if (instance != null) {
|
||||
inflater.inflate(R.menu.instance_info, menu);
|
||||
UiUtils.enableOptionsMenuIcons(getActivity(), menu);
|
||||
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, instance.uri));
|
||||
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, targetDomain));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean wantsToolbarMenuIconsTinted() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@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, instance.uri);
|
||||
intent.putExtra(Intent.EXTRA_TEXT, targetDomain);
|
||||
startActivity(Intent.createChooser(intent, item.getTitle()));
|
||||
} else if (id==R.id.open_timeline) {
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putString("domain", instance.uri);
|
||||
args.putString("domain", targetDomain);
|
||||
Nav.go(getActivity(), CustomLocalTimelineFragment.class, args);
|
||||
}else if (id==R.id.rules) {
|
||||
Bundle args=new Bundle();
|
||||
@@ -420,6 +430,16 @@ public class InstanceInfoFragment extends LoaderFragment {
|
||||
if (adapter != null) adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccountID() {
|
||||
return accountID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return Uri.parse(targetDomain);
|
||||
}
|
||||
|
||||
private class MetadataAdapter extends UsableRecyclerView.Adapter<BaseViewHolder> {
|
||||
public MetadataAdapter(){
|
||||
super(imgLoader);
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.TimelineDefinition;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ListTimelineEditor;
|
||||
import org.joinmastodon.android.ui.views.ListEditor;
|
||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||
|
||||
import java.util.List;
|
||||
@@ -36,12 +36,12 @@ import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
|
||||
public class ListTimelineFragment extends PinnableStatusListFragment {
|
||||
private String listID;
|
||||
private String listTitle;
|
||||
@Nullable
|
||||
private ListTimeline.RepliesPolicy repliesPolicy;
|
||||
private boolean exclusive;
|
||||
|
||||
@Override
|
||||
protected boolean wantsComposeButton() {
|
||||
@@ -54,6 +54,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
||||
Bundle args = getArguments();
|
||||
listID = args.getString("listID");
|
||||
listTitle = args.getString("listTitle");
|
||||
exclusive = args.getBoolean("listIsExclusive");
|
||||
repliesPolicy = ListTimeline.RepliesPolicy.values()[args.getInt("repliesPolicy", 0)];
|
||||
|
||||
setTitle(listTitle);
|
||||
@@ -88,8 +89,8 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (super.onOptionsItemSelected(item)) return true;
|
||||
if (item.getItemId() == R.id.edit) {
|
||||
ListTimelineEditor editor = new ListTimelineEditor(getContext());
|
||||
editor.applyList(listTitle, repliesPolicy);
|
||||
ListEditor editor = new ListEditor(getContext());
|
||||
editor.applyList(listTitle, exclusive, repliesPolicy);
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.sk_edit_list_title)
|
||||
.setIcon(R.drawable.ic_fluent_people_28_regular)
|
||||
@@ -97,14 +98,15 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
||||
.setPositiveButton(R.string.save, (d, which) -> {
|
||||
String newTitle = editor.getTitle().trim();
|
||||
setTitle(newTitle);
|
||||
new UpdateList(listID, newTitle, editor.getRepliesPolicy()).setCallback(new Callback<>() {
|
||||
new UpdateList(listID, newTitle, editor.isExclusive(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(ListTimeline list) {
|
||||
if (getActivity() == null) return;
|
||||
setTitle(list.title);
|
||||
listTitle = list.title;
|
||||
repliesPolicy = list.repliesPolicy;
|
||||
E.post(new ListUpdatedCreatedEvent(listID, listTitle, repliesPolicy));
|
||||
exclusive = list.exclusive;
|
||||
E.post(new ListUpdatedCreatedEvent(listID, listTitle, exclusive, repliesPolicy));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -127,7 +129,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
||||
|
||||
@Override
|
||||
protected TimelineDefinition makeTimelineDefinition() {
|
||||
return TimelineDefinition.ofList(listID, listTitle);
|
||||
return TimelineDefinition.ofList(listID, listTitle, exclusive);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,7 +27,7 @@ import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
||||
import org.joinmastodon.android.model.ListTimeline;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.views.ListTimelineEditor;
|
||||
import org.joinmastodon.android.ui.views.ListEditor;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -91,18 +91,18 @@ public class ListsFragment extends RecyclerFragment<ListTimeline> implements Scr
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.create) {
|
||||
ListTimelineEditor editor = new ListTimelineEditor(getContext());
|
||||
ListEditor editor = new ListEditor(getContext());
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.sk_create_list_title)
|
||||
.setIcon(R.drawable.ic_fluent_people_add_28_regular)
|
||||
.setView(editor)
|
||||
.setPositiveButton(R.string.sk_create, (d, which) ->
|
||||
new CreateList(editor.getTitle(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
|
||||
new CreateList(editor.getTitle(), editor.isExclusive(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(ListTimeline list) {
|
||||
data.add(0, list);
|
||||
adapter.notifyItemRangeInserted(0, 1);
|
||||
E.post(new ListUpdatedCreatedEvent(list.id, list.title, list.repliesPolicy));
|
||||
E.post(new ListUpdatedCreatedEvent(list.id, list.title, list.exclusive, list.repliesPolicy));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -185,6 +185,7 @@ public class ListsFragment extends RecyclerFragment<ListTimeline> implements Scr
|
||||
if (item.id.equals(event.id)) {
|
||||
item.title = event.title;
|
||||
item.repliesPolicy = event.repliesPolicy;
|
||||
item.exclusive = event.exclusive;
|
||||
adapter.notifyItemChanged(i);
|
||||
break;
|
||||
}
|
||||
@@ -242,7 +243,9 @@ public class ListsFragment extends RecyclerFragment<ListTimeline> implements Scr
|
||||
@Override
|
||||
public void onBind(ListTimeline item) {
|
||||
title.setText(item.title);
|
||||
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(R.drawable.ic_fluent_people_24_regular), null, null, null);
|
||||
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(
|
||||
item.exclusive ? R.drawable.ic_fluent_rss_24_regular : R.drawable.ic_fluent_people_24_regular
|
||||
), null, null, null);
|
||||
if (profileAccountId != null) {
|
||||
Boolean checked = userInList.get(item.id);
|
||||
listToggle.setVisibility(View.VISIBLE);
|
||||
@@ -263,6 +266,7 @@ public class ListsFragment extends RecyclerFragment<ListTimeline> implements Scr
|
||||
args.putString("account", accountID);
|
||||
args.putString("listID", item.id);
|
||||
args.putString("listTitle", item.title);
|
||||
args.putBoolean("listIsExclusive", item.exclusive);
|
||||
if (item.repliesPolicy != null) args.putInt("repliesPolicy", item.repliesPolicy.ordinal());
|
||||
Nav.go(getActivity(), ListTimelineFragment.class, args);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class PinnedPostsListFragment extends StatusListFragment{
|
||||
private Account account;
|
||||
|
||||
public PinnedPostsListFragment() {
|
||||
setListLayoutId(R.layout.recycler_fragment_no_refresh);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
account=Parcels.unwrap(getArguments().getParcelable("profileAccount"));
|
||||
setTitle(R.string.posts);
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
new GetAccountStatuses(account.id, null, null, 100, GetAccountStatuses.Filter.PINNED)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
onDataLoaded(result, false);
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return Filter.FilterContext.ACCOUNT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return Uri.parse(account.url);
|
||||
}
|
||||
}
|
||||
@@ -134,7 +134,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
private ProgressBarButton actionButton, notifyButton;
|
||||
private ViewPager2 pager;
|
||||
private NestedRecyclerScrollView scrollView;
|
||||
private AccountTimelineFragment postsFragment, postsWithRepliesFragment, pinnedPostsFragment, mediaFragment;
|
||||
private AccountTimelineFragment postsFragment, postsWithRepliesFragment, mediaFragment;
|
||||
private PinnedPostsListFragment pinnedPostsFragment;
|
||||
// private ProfileAboutFragment aboutFragment;
|
||||
private TabLayout tabbar;
|
||||
private SwipeRefreshLayout refreshLayout;
|
||||
@@ -515,8 +516,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
if(postsFragment==null){
|
||||
postsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.DEFAULT, true);
|
||||
postsWithRepliesFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.INCLUDE_REPLIES, false);
|
||||
pinnedPostsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.PINNED, false);
|
||||
mediaFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.MEDIA, false);
|
||||
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(account));
|
||||
args.putBoolean("__is_tab", true);
|
||||
pinnedPostsFragment=new PinnedPostsListFragment();
|
||||
pinnedPostsFragment.setArguments(args);
|
||||
// aboutFragment=new ProfileAboutFragment();
|
||||
setFields(fields);
|
||||
}
|
||||
@@ -675,6 +682,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, account.followingCount)));
|
||||
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, account.statusesCount)));
|
||||
|
||||
if (account.followersCount < 0) followersBtn.setVisibility(View.GONE);
|
||||
if (account.followingCount < 0) followingBtn.setVisibility(View.GONE);
|
||||
|
||||
UiUtils.loadCustomEmojiInTextView(name);
|
||||
UiUtils.loadCustomEmojiInTextView(bio);
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.GlobalUserPreferences.AutoRevealMode;
|
||||
import org.joinmastodon.android.GlobalUserPreferences.ColorPreference;
|
||||
import org.joinmastodon.android.GlobalUserPreferences.PrefixRepliesMode;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
@@ -221,14 +222,27 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
|
||||
GlobalUserPreferences.keepOnlyLatestNotification=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_prefix_reply_cw_with_re, R.drawable.ic_fluent_arrow_reply_24_regular, GlobalUserPreferences.prefixRepliesWithRe, i->{
|
||||
GlobalUserPreferences.prefixRepliesWithRe=i.checked;
|
||||
items.add(new ButtonItem(R.string.sk_settings_prefix_reply_cw_with_re, R.drawable.ic_fluent_arrow_reply_24_regular, b->{
|
||||
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
|
||||
popupMenu.inflate(R.menu.settings_prefix_reply_mode);
|
||||
popupMenu.setOnMenuItemClickListener(i -> onPrefixRepliesClick(i, b));
|
||||
b.setOnTouchListener(popupMenu.getDragToOpenListener());
|
||||
b.setOnClickListener(v->popupMenu.show());
|
||||
b.setText(switch(GlobalUserPreferences.prefixReplies){
|
||||
case TO_OTHERS -> R.string.sk_settings_prefix_replies_to_others;
|
||||
case ALWAYS -> R.string.sk_settings_prefix_replies_always;
|
||||
default -> R.string.sk_settings_prefix_replies_never;
|
||||
});
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_confirm_before_reblog, R.drawable.ic_fluent_checkmark_circle_24_regular, GlobalUserPreferences.confirmBeforeReblog, i->{
|
||||
GlobalUserPreferences.confirmBeforeReblog=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_forward_report_default, R.drawable.ic_fluent_arrow_forward_24_regular, GlobalUserPreferences.forwardReportDefault, i->{
|
||||
GlobalUserPreferences.forwardReportDefault=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_allow_remote_loading, R.drawable.ic_fluent_communication_24_regular, GlobalUserPreferences.allowRemoteLoading, i->{
|
||||
GlobalUserPreferences.allowRemoteLoading=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
@@ -541,6 +555,22 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean onPrefixRepliesClick(MenuItem item, Button btn) {
|
||||
int id = item.getItemId();
|
||||
PrefixRepliesMode mode = PrefixRepliesMode.NEVER;
|
||||
if (id == R.id.prefix_replies_always) mode = PrefixRepliesMode.ALWAYS;
|
||||
else if (id == R.id.prefix_replies_to_others) mode = PrefixRepliesMode.TO_OTHERS;
|
||||
GlobalUserPreferences.prefixReplies = mode;
|
||||
|
||||
btn.setText(switch(GlobalUserPreferences.prefixReplies){
|
||||
case TO_OTHERS -> R.string.sk_settings_prefix_replies_to_others;
|
||||
case ALWAYS -> R.string.sk_settings_prefix_replies_always;
|
||||
default -> R.string.sk_settings_prefix_replies_never;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean onAutoRevealSpoilerClick(MenuItem item, Button btn) {
|
||||
int id = item.getItemId();
|
||||
|
||||
@@ -557,12 +587,12 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
|
||||
|
||||
private void onAutoRevealSpoilerChanged(Button b) {
|
||||
if (GlobalUserPreferences.alwaysExpandContentWarnings) {
|
||||
b.setText(R.string.sk_settings_auto_reveal_always);
|
||||
b.setText(R.string.sk_settings_auto_reveal_anyone);
|
||||
} else {
|
||||
b.setText(switch(GlobalUserPreferences.autoRevealEqualSpoilers){
|
||||
case THREADS -> R.string.sk_settings_auto_reveal_threads;
|
||||
case DISCUSSIONS -> R.string.sk_settings_auto_reveal_discussions;
|
||||
default -> R.string.sk_settings_auto_reveal_never;
|
||||
case THREADS -> R.string.sk_settings_auto_reveal_author;
|
||||
case DISCUSSIONS -> R.string.sk_settings_auto_reveal_anyone;
|
||||
default -> R.string.sk_settings_auto_reveal_nobody;
|
||||
});
|
||||
if (alwaysRevealSpoilersItem.checked != GlobalUserPreferences.alwaysExpandContentWarnings) {
|
||||
alwaysRevealSpoilersItem.checked = GlobalUserPreferences.alwaysExpandContentWarnings;
|
||||
|
||||
@@ -178,13 +178,29 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
||||
protected void removeStatus(Status status){
|
||||
data.remove(status);
|
||||
preloadedData.remove(status);
|
||||
int index=-1;
|
||||
int index=-1, ancestorFirstIndex = -1, ancestorLastIndex = -1;
|
||||
for(int i=0;i<displayItems.size();i++){
|
||||
if(status.id.equals(displayItems.get(i).parentID)){
|
||||
StatusDisplayItem item = displayItems.get(i);
|
||||
if(status.id.equals(item.parentID)){
|
||||
index=i;
|
||||
break;
|
||||
}
|
||||
if (item.parentID.equals(status.inReplyToId)) {
|
||||
if (ancestorFirstIndex == -1) ancestorFirstIndex = i;
|
||||
ancestorLastIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
// did we find an ancestor that is also the status' neighbor?
|
||||
if (ancestorFirstIndex >= 0 && ancestorLastIndex == index - 1) {
|
||||
for (int i = ancestorFirstIndex; i <= ancestorLastIndex; i++) {
|
||||
StatusDisplayItem item = displayItems.get(i);
|
||||
// update ancestor to have no descendant anymore
|
||||
if (item.parentID.equals(status.inReplyToId)) item.hasDescendantNeighbor = false;
|
||||
}
|
||||
adapter.notifyItemRangeChanged(ancestorFirstIndex, ancestorLastIndex - ancestorFirstIndex + 1);
|
||||
}
|
||||
|
||||
if(index==-1)
|
||||
return;
|
||||
int lastIndex;
|
||||
|
||||
@@ -52,7 +52,8 @@ import me.grishka.appkit.utils.V;
|
||||
public class ThreadFragment extends StatusListFragment implements ProvidesAssistContent {
|
||||
protected Status mainStatus, updatedStatus;
|
||||
private final HashMap<String, NeighborAncestryInfo> ancestryMap = new HashMap<>();
|
||||
protected boolean contextInitiallyRendered;
|
||||
private StatusContext result;
|
||||
protected boolean contextInitiallyRendered, transitionFinished;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -64,6 +65,7 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||
data.add(mainStatus);
|
||||
onAppendItems(Collections.singletonList(mainStatus));
|
||||
setTitle(HtmlParser.parseCustomEmoji(getString(R.string.post_from_user, mainStatus.account.displayName), mainStatus.account.emojis));
|
||||
transitionFinished = getArguments().getBoolean("noTransition", false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -105,13 +107,20 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||
footer.hideCounts=true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int deleteThisItem : deleteTheseItems) itemsToModify.remove(deleteThisItem);
|
||||
if(s.id.equals(mainStatus.id)) {
|
||||
items.add(new ExtendedFooterStatusDisplayItem(s.id, this, getAccountID(), s.getContentStatus()));
|
||||
items.add(new ExtendedFooterStatusDisplayItem(s.id, this, accountID, s.getContentStatus()));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionFinished() {
|
||||
transitionFinished = true;
|
||||
maybeApplyContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
if (refreshing) loadMainStatus();
|
||||
@@ -119,72 +128,8 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(StatusContext result){
|
||||
if (getContext() == null) return;
|
||||
Map<String, Status> oldData = null;
|
||||
if(refreshing){
|
||||
oldData = new HashMap<>(data.size());
|
||||
for (Status s : data) oldData.put(s.id, s);
|
||||
data.clear();
|
||||
ancestryMap.clear();
|
||||
displayItems.clear();
|
||||
data.add(mainStatus);
|
||||
onAppendItems(Collections.singletonList(mainStatus));
|
||||
}
|
||||
|
||||
// TODO: figure out how this code works
|
||||
if(isInstanceAkkoma()) sortStatusContext(mainStatus, result);
|
||||
|
||||
result.descendants=filterStatuses(result.descendants);
|
||||
result.ancestors=filterStatuses(result.ancestors);
|
||||
|
||||
for (NeighborAncestryInfo i : mapNeighborhoodAncestry(mainStatus, result)) {
|
||||
ancestryMap.put(i.status.id, i);
|
||||
}
|
||||
|
||||
if(footerProgress!=null)
|
||||
footerProgress.setVisibility(View.GONE);
|
||||
data.addAll(result.descendants);
|
||||
int prevCount=displayItems.size();
|
||||
onAppendItems(result.descendants);
|
||||
int count=displayItems.size();
|
||||
if(!refreshing)
|
||||
adapter.notifyItemRangeInserted(prevCount, count-prevCount);
|
||||
int prependedCount = prependItems(result.ancestors, !refreshing);
|
||||
if (prependedCount > 0 && displayItems.get(prependedCount) instanceof ReblogOrReplyLineStatusDisplayItem) {
|
||||
displayItems.remove(prependedCount);
|
||||
adapter.notifyItemRemoved(prependedCount);
|
||||
count--;
|
||||
}
|
||||
|
||||
for (Status s : data) {
|
||||
Status oldStatus = oldData == null ? null : oldData.get(s.id);
|
||||
// restore previous spoiler/filter revealed states when refreshing
|
||||
if (oldStatus != null) {
|
||||
s.spoilerRevealed = oldStatus.spoilerRevealed;
|
||||
s.filterRevealed = oldStatus.filterRevealed;
|
||||
} else if (GlobalUserPreferences.autoRevealEqualSpoilers != AutoRevealMode.NEVER &&
|
||||
s.spoilerText != null &&
|
||||
s.spoilerText.equals(mainStatus.spoilerText) &&
|
||||
mainStatus.spoilerRevealed) {
|
||||
if (GlobalUserPreferences.autoRevealEqualSpoilers == AutoRevealMode.DISCUSSIONS || Objects.equals(mainStatus.account.id, s.account.id)) {
|
||||
s.spoilerRevealed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dataLoaded();
|
||||
if(refreshing){
|
||||
refreshDone();
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
list.scrollToPosition(displayItems.size()-count);
|
||||
|
||||
// no animation is going to happen, so proceeding to apply right now
|
||||
if (data.size() == 1) {
|
||||
contextInitiallyRendered = true;
|
||||
// for the case that the main status has already finished loading
|
||||
maybeApplyMainStatus();
|
||||
}
|
||||
ThreadFragment.this.result = result;
|
||||
maybeApplyContext();
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
@@ -207,6 +152,77 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
protected void maybeApplyContext() {
|
||||
if (!transitionFinished || result == null || getContext() == null) return;
|
||||
Map<String, Status> oldData = null;
|
||||
if(refreshing){
|
||||
oldData = new HashMap<>(data.size());
|
||||
for (Status s : data) oldData.put(s.id, s);
|
||||
data.clear();
|
||||
ancestryMap.clear();
|
||||
displayItems.clear();
|
||||
data.add(mainStatus);
|
||||
onAppendItems(Collections.singletonList(mainStatus));
|
||||
}
|
||||
|
||||
// TODO: figure out how this code works
|
||||
if (isInstanceAkkoma()) sortStatusContext(mainStatus, result);
|
||||
|
||||
result.descendants=filterStatuses(result.descendants);
|
||||
result.ancestors=filterStatuses(result.ancestors);
|
||||
|
||||
for (NeighborAncestryInfo i : mapNeighborhoodAncestry(mainStatus, result)) {
|
||||
ancestryMap.put(i.status.id, i);
|
||||
}
|
||||
|
||||
if(footerProgress!=null)
|
||||
footerProgress.setVisibility(View.GONE);
|
||||
data.addAll(result.descendants);
|
||||
int prevCount=displayItems.size();
|
||||
onAppendItems(result.descendants);
|
||||
int count=displayItems.size();
|
||||
if(!refreshing)
|
||||
adapter.notifyItemRangeInserted(prevCount, count-prevCount);
|
||||
int prependedCount = prependItems(result.ancestors, !refreshing);
|
||||
if (prependedCount > 0 && displayItems.get(prependedCount) instanceof ReblogOrReplyLineStatusDisplayItem) {
|
||||
displayItems.remove(prependedCount);
|
||||
adapter.notifyItemRemoved(prependedCount);
|
||||
count--;
|
||||
}
|
||||
|
||||
for (Status s : data) {
|
||||
Status oldStatus = oldData == null ? null : oldData.get(s.id);
|
||||
// restore previous spoiler/filter revealed states when refreshing
|
||||
if (oldStatus != null) {
|
||||
s.spoilerRevealed = oldStatus.spoilerRevealed;
|
||||
s.filterRevealed = oldStatus.filterRevealed;
|
||||
} else if (GlobalUserPreferences.autoRevealEqualSpoilers != AutoRevealMode.NEVER &&
|
||||
s.spoilerText != null &&
|
||||
s.spoilerText.equals(mainStatus.spoilerText) &&
|
||||
mainStatus.spoilerRevealed) {
|
||||
if (GlobalUserPreferences.autoRevealEqualSpoilers == AutoRevealMode.DISCUSSIONS || Objects.equals(mainStatus.account.id, s.account.id)) {
|
||||
s.spoilerRevealed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dataLoaded();
|
||||
if(refreshing){
|
||||
refreshDone();
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
list.scrollToPosition(displayItems.size()-count);
|
||||
|
||||
// no animation is going to happen, so proceeding to apply right now
|
||||
if (data.size() == 1) {
|
||||
contextInitiallyRendered = true;
|
||||
// for the case that the main status has already finished loading
|
||||
maybeApplyMainStatus();
|
||||
}
|
||||
|
||||
result = null;
|
||||
}
|
||||
|
||||
protected Object maybeApplyMainStatus() {
|
||||
if (updatedStatus == null || !contextInitiallyRendered) return null;
|
||||
|
||||
@@ -337,10 +353,60 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||
}
|
||||
|
||||
protected void onStatusCreated(StatusCreatedEvent ev){
|
||||
if(ev.status.inReplyToId!=null && getStatusByID(ev.status.inReplyToId)!=null){
|
||||
data.add(ev.status);
|
||||
onAppendItems(Collections.singletonList(ev.status));
|
||||
if (ev.status.inReplyToId == null) return;
|
||||
Status repliedToStatus = getStatusByID(ev.status.inReplyToId);
|
||||
if (repliedToStatus == null) return;
|
||||
NeighborAncestryInfo ancestry = ancestryMap.get(repliedToStatus.id);
|
||||
|
||||
int nextDisplayItemsIndex = -1, indexOfPreviousDisplayItem = -1;
|
||||
|
||||
if (ancestry != null) for (int i = 0; i < displayItems.size(); i++) {
|
||||
StatusDisplayItem item = displayItems.get(i);
|
||||
if (repliedToStatus.id.equals(item.parentID)) {
|
||||
// saving the replied-to status' display items index to eventually reach the last one
|
||||
indexOfPreviousDisplayItem = i;
|
||||
item.hasDescendantNeighbor = true;
|
||||
} else if (indexOfPreviousDisplayItem >= 0 && nextDisplayItemsIndex == -1) {
|
||||
// previous display item was the replied-to status' display items
|
||||
nextDisplayItemsIndex = i;
|
||||
// nothing left to do if there's no other reply to that status
|
||||
if (ancestry.descendantNeighbor == null) break;
|
||||
}
|
||||
if (ancestry.descendantNeighbor != null && item.parentID.equals(ancestry.descendantNeighbor.id)) {
|
||||
// existing reply shall no longer have the replied-to status as its neighbor
|
||||
item.hasAncestoringNeighbor = false;
|
||||
}
|
||||
}
|
||||
|
||||
// fall back to inserting the item at the end
|
||||
nextDisplayItemsIndex = nextDisplayItemsIndex >= 0 ? nextDisplayItemsIndex : displayItems.size();
|
||||
int nextDataIndex = data.indexOf(repliedToStatus) + 1;
|
||||
|
||||
// if replied-to status already has another reply...
|
||||
if (ancestry != null && ancestry.descendantNeighbor != null) {
|
||||
// update the reply's ancestry to remove its ancestoring neighbor (as we did above)
|
||||
ancestryMap.get(ancestry.descendantNeighbor.id).ancestoringNeighbor = null;
|
||||
// make sure the existing reply has a reply line
|
||||
if (nextDataIndex < data.size() &&
|
||||
!(displayItems.get(nextDisplayItemsIndex) instanceof ReblogOrReplyLineStatusDisplayItem)) {
|
||||
Status nextStatus = data.get(nextDataIndex);
|
||||
if (!nextStatus.account.id.equals(repliedToStatus.account.id)) {
|
||||
// create reply line manually since we're not building that status' items
|
||||
displayItems.add(nextDisplayItemsIndex, StatusDisplayItem.buildReplyLine(
|
||||
this, nextStatus, accountID, nextStatus, repliedToStatus.account, false
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update replied-to status' ancestry
|
||||
if (ancestry != null) ancestry.descendantNeighbor = ev.status;
|
||||
|
||||
// add ancestry for newly created status before building its display items
|
||||
ancestryMap.put(ev.status.id, new NeighborAncestryInfo(ev.status, null, repliedToStatus));
|
||||
displayItems.addAll(nextDisplayItemsIndex, buildDisplayItems(ev.status));
|
||||
data.add(nextDataIndex, ev.status);
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -244,6 +244,10 @@ public class DiscoverAccountsFragment extends RecyclerFragment<DiscoverAccountsF
|
||||
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
|
||||
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
|
||||
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, item.account.statusesCount)));
|
||||
followersCount.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
||||
followersLabel.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
||||
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
||||
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
||||
relationship=relationships.get(item.account.id);
|
||||
if(relationship==null){
|
||||
actionWrap.setVisibility(View.GONE);
|
||||
|
||||
@@ -15,6 +15,7 @@ import android.widget.TextView;
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.reports.SendReport;
|
||||
import org.joinmastodon.android.events.FinishReportFragmentsEvent;
|
||||
@@ -39,7 +40,7 @@ public class ReportCommentFragment extends MastodonToolbarFragment{
|
||||
private TextView forwardReportText;
|
||||
private Switch forwardReportSwitch;
|
||||
private EditText commentEdit;
|
||||
private boolean forwardReport;
|
||||
private boolean forwardReport = GlobalUserPreferences.forwardReportDefault;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -89,7 +90,7 @@ public class ReportCommentFragment extends MastodonToolbarFragment{
|
||||
} else {
|
||||
forwardReportItem.setOnClickListener(this::onForwardReportClick);
|
||||
forwardReportText.setText(getActivity().getString(R.string.sk_forward_report_to, domain));
|
||||
forwardReportSwitch.setChecked(forwardReport = true);
|
||||
forwardReportSwitch.setChecked(forwardReport);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,15 @@ package org.joinmastodon.android.fragments.settings;
|
||||
|
||||
import android.os.Build;
|
||||
import android.view.Gravity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.PopupMenu;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.ContentType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -21,6 +25,10 @@ public class AppearanceFragment extends SettingsBaseFragment {
|
||||
popupMenu.inflate(R.menu.color_palettes);
|
||||
popupMenu.getMenu().findItem(R.id.m3_color).setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S);
|
||||
popupMenu.setOnMenuItemClickListener(this::onColorPreferenceClick);
|
||||
|
||||
Menu colorTheme = popupMenu.getMenu();
|
||||
colorTheme.findItem(getColorThemeRes(GlobalUserPreferences.color)).setChecked(true);
|
||||
|
||||
b.setOnTouchListener(popupMenu.getDragToOpenListener());
|
||||
b.setOnClickListener(v -> popupMenu.show());
|
||||
b.setText(switch (GlobalUserPreferences.color) {
|
||||
@@ -48,6 +56,20 @@ public class AppearanceFragment extends SettingsBaseFragment {
|
||||
}));
|
||||
}
|
||||
|
||||
public static int getColorThemeRes(@Nullable GlobalUserPreferences.ColorPreference colorPreference) {
|
||||
return colorPreference == null ? R.id.content_type_null : switch(colorPreference) {
|
||||
case MATERIAL3 -> R.id.m3_color;
|
||||
case PINK -> R.id.pink_color;
|
||||
case PURPLE -> R.id.purple_color;
|
||||
case GREEN -> R.id.green_color;
|
||||
case BLUE -> R.id.blue_color;
|
||||
case BROWN -> R.id.brown_color;
|
||||
case RED -> R.id.red_color;
|
||||
case YELLOW -> R.id.yellow_color;
|
||||
case NORD -> R.id.nord_color;
|
||||
};
|
||||
}
|
||||
|
||||
private boolean onColorPreferenceClick(MenuItem item) {
|
||||
GlobalUserPreferences.ColorPreference pref = null;
|
||||
int id = item.getItemId();
|
||||
|
||||
@@ -22,6 +22,8 @@ public class BehaviourFragment extends SettingsBaseFragment{
|
||||
|
||||
SwitchItem alwaysRevealSpoilersItem;
|
||||
ButtonItem autoRevealSpoilersItem;
|
||||
ButtonItem publishButtonTextSetting;
|
||||
SwitchItem relocatePublishButtonSetting;
|
||||
@Override
|
||||
public void addItems(ArrayList<Item> items) {
|
||||
items.add(new HeaderItem(R.string.settings_behavior));
|
||||
@@ -65,9 +67,13 @@ public class BehaviourFragment extends SettingsBaseFragment{
|
||||
GlobalUserPreferences.confirmBeforeReblog=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SettingsBaseFragment.SwitchItem(R.string.sk_settings_forward_report_default, R.drawable.ic_fluent_arrow_forward_24_regular, GlobalUserPreferences.forwardReportDefault, i->{
|
||||
GlobalUserPreferences.forwardReportDefault=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
|
||||
items.add(new HeaderItem(R.string.mo_composer_behavior));
|
||||
items.add(new ButtonItem(R.string.sk_settings_publish_button_text, R.drawable.ic_fluent_send_24_regular, b-> {
|
||||
items.add(publishButtonTextSetting = new ButtonItem(R.string.sk_settings_publish_button_text, R.drawable.ic_fluent_send_24_regular, b-> {
|
||||
updatePublishText(b);
|
||||
b.setOnClickListener(l -> {
|
||||
if(!GlobalUserPreferences.relocatePublishButton) {
|
||||
@@ -99,8 +105,11 @@ public class BehaviourFragment extends SettingsBaseFragment{
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
|
||||
b.setAlpha(relocatePublishButtonSetting.checked ? 0.7f : 1f);
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.mo_relocate_publish_button, R.string.mo_setting_relocate_publish_summary, R.drawable.ic_fluent_arrow_autofit_down_24_regular, GlobalUserPreferences.relocatePublishButton, i->{
|
||||
items.add(relocatePublishButtonSetting = new SwitchItem(R.string.mo_relocate_publish_button, R.string.mo_setting_relocate_publish_summary, R.drawable.ic_fluent_arrow_autofit_down_24_regular, GlobalUserPreferences.relocatePublishButton, i->{
|
||||
if (list.findViewHolderForAdapterPosition(items.indexOf(publishButtonTextSetting)) instanceof SettingsBaseFragment.ButtonViewHolder bvh) bvh.rebind();
|
||||
GlobalUserPreferences.relocatePublishButton=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
@@ -117,12 +126,37 @@ public class BehaviourFragment extends SettingsBaseFragment{
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_prefix_reply_cw_with_re, R.drawable.ic_fluent_arrow_reply_24_regular, GlobalUserPreferences.prefixRepliesWithRe, i->{
|
||||
GlobalUserPreferences.prefixRepliesWithRe=i.checked;
|
||||
items.add(new SettingsBaseFragment.ButtonItem(R.string.sk_settings_prefix_reply_cw_with_re, R.drawable.ic_fluent_arrow_reply_24_regular, b->{
|
||||
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
|
||||
popupMenu.inflate(R.menu.settings_prefix_reply_mode);
|
||||
popupMenu.setOnMenuItemClickListener(i -> onPrefixRepliesClick(i, b));
|
||||
b.setOnTouchListener(popupMenu.getDragToOpenListener());
|
||||
b.setOnClickListener(v->popupMenu.show());
|
||||
b.setText(switch(GlobalUserPreferences.prefixReplies){
|
||||
case TO_OTHERS -> R.string.sk_settings_prefix_replies_to_others;
|
||||
case ALWAYS -> R.string.sk_settings_prefix_replies_always;
|
||||
default -> R.string.sk_settings_prefix_replies_never;
|
||||
});
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
}
|
||||
|
||||
private boolean onPrefixRepliesClick(MenuItem item, Button btn) {
|
||||
int id = item.getItemId();
|
||||
GlobalUserPreferences.PrefixRepliesMode mode = GlobalUserPreferences.PrefixRepliesMode.NEVER;
|
||||
if (id == R.id.prefix_replies_always) mode = GlobalUserPreferences.PrefixRepliesMode.ALWAYS;
|
||||
else if (id == R.id.prefix_replies_to_others) mode = GlobalUserPreferences.PrefixRepliesMode.TO_OTHERS;
|
||||
GlobalUserPreferences.prefixReplies = mode;
|
||||
|
||||
btn.setText(switch(GlobalUserPreferences.prefixReplies){
|
||||
case TO_OTHERS -> R.string.sk_settings_prefix_replies_to_others;
|
||||
case ALWAYS -> R.string.sk_settings_prefix_replies_always;
|
||||
default -> R.string.sk_settings_prefix_replies_never;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean onAutoRevealSpoilerClick(MenuItem item, Button btn) {
|
||||
int id = item.getItemId();
|
||||
|
||||
@@ -139,12 +173,12 @@ public class BehaviourFragment extends SettingsBaseFragment{
|
||||
|
||||
private void onAutoRevealSpoilerChanged(Button b) {
|
||||
if (GlobalUserPreferences.alwaysExpandContentWarnings) {
|
||||
b.setText(R.string.sk_settings_auto_reveal_always);
|
||||
b.setText(R.string.sk_settings_auto_reveal_anyone);
|
||||
} else {
|
||||
b.setText(switch(GlobalUserPreferences.autoRevealEqualSpoilers){
|
||||
case THREADS -> R.string.sk_settings_auto_reveal_threads;
|
||||
case DISCUSSIONS -> R.string.sk_settings_auto_reveal_discussions;
|
||||
default -> R.string.sk_settings_auto_reveal_never;
|
||||
case THREADS -> R.string.sk_settings_auto_reveal_author;
|
||||
case DISCUSSIONS -> R.string.sk_settings_auto_reveal_anyone;
|
||||
default -> R.string.sk_settings_auto_reveal_nobody;
|
||||
});
|
||||
if (alwaysRevealSpoilersItem.checked != GlobalUserPreferences.alwaysExpandContentWarnings) {
|
||||
alwaysRevealSpoilersItem.checked = GlobalUserPreferences.alwaysExpandContentWarnings;
|
||||
|
||||
@@ -15,7 +15,7 @@ public abstract class BaseModel implements Cloneable{
|
||||
/**
|
||||
* indicates the profile has been fetched from a foreign instance.
|
||||
*
|
||||
* @see MastodonAPIRequest#execRemote
|
||||
* @see org.joinmastodon.android.api.MastodonAPIRequest#execRemote
|
||||
*/
|
||||
public transient boolean isRemote;
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ public class Filter extends BaseModel{
|
||||
public FilterAction filterAction;
|
||||
|
||||
@SerializedName("context")
|
||||
private List<FilterContext> _context;
|
||||
protected List<FilterContext> _context;
|
||||
|
||||
private transient Pattern pattern;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ public class ListTimeline extends BaseModel {
|
||||
@RequiredField
|
||||
public String title;
|
||||
public RepliesPolicy repliesPolicy;
|
||||
public boolean exclusive;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
@@ -22,6 +23,7 @@ public class ListTimeline extends BaseModel {
|
||||
"id='" + id + '\'' +
|
||||
", title='" + title + '\'' +
|
||||
", repliesPolicy=" + repliesPolicy +
|
||||
", exclusive=" + exclusive +
|
||||
'}';
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ public class Poll extends BaseModel{
|
||||
@RequiredField
|
||||
public String id;
|
||||
public Instant expiresAt;
|
||||
private boolean expired;
|
||||
protected boolean expired;
|
||||
public boolean multiple;
|
||||
public int votersCount;
|
||||
public int votesCount;
|
||||
|
||||
@@ -35,7 +35,7 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
||||
public Instant createdAt;
|
||||
@RequiredField
|
||||
public Account account;
|
||||
// @RequiredField
|
||||
// @RequiredField
|
||||
public String content;
|
||||
@RequiredField
|
||||
public StatusPrivacy visibility;
|
||||
@@ -178,9 +178,10 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
||||
return strippedText;
|
||||
}
|
||||
|
||||
public boolean canBeBoosted(String accountID){
|
||||
return (visibility==StatusPrivacy.PUBLIC || visibility==StatusPrivacy.UNLISTED || visibility==StatusPrivacy.LOCAL
|
||||
|| (visibility==StatusPrivacy.PRIVATE && account.id.equals(AccountSessionManager.getInstance().getAccount(accountID).self.id)));
|
||||
public boolean isReblogPermitted(String accountID){
|
||||
return visibility.isReblogPermitted(account.id.equals(
|
||||
AccountSessionManager.getInstance().getAccount(accountID).self.id
|
||||
));
|
||||
}
|
||||
|
||||
public static Status ofFake(String id, String text, Instant createdAt) {
|
||||
|
||||
@@ -14,7 +14,7 @@ public enum StatusPrivacy{
|
||||
@SerializedName("local")
|
||||
LOCAL(4); // akkoma
|
||||
|
||||
private int privacy;
|
||||
private final int privacy;
|
||||
|
||||
StatusPrivacy(int privacy) {
|
||||
this.privacy = privacy;
|
||||
@@ -24,6 +24,13 @@ public enum StatusPrivacy{
|
||||
return privacy > other.getPrivacy();
|
||||
}
|
||||
|
||||
public boolean isReblogPermitted(boolean isOwnStatus){
|
||||
return (this == StatusPrivacy.PUBLIC ||
|
||||
this == StatusPrivacy.UNLISTED ||
|
||||
this == StatusPrivacy.LOCAL ||
|
||||
(this == StatusPrivacy.PRIVATE && isOwnStatus));
|
||||
}
|
||||
|
||||
public int getPrivacy() {
|
||||
return privacy;
|
||||
}
|
||||
|
||||
@@ -32,18 +32,21 @@ public class TimelineDefinition {
|
||||
|
||||
private @Nullable String listId;
|
||||
private @Nullable String listTitle;
|
||||
private boolean listIsExclusive;
|
||||
|
||||
private @Nullable String domain;
|
||||
private @Nullable String hashtagName;
|
||||
|
||||
public static TimelineDefinition ofList(String listId, String listTitle) {
|
||||
public static TimelineDefinition ofList(String listId, String listTitle, boolean listIsExclusive) {
|
||||
TimelineDefinition def = new TimelineDefinition(TimelineType.LIST);
|
||||
def.listId = listId;
|
||||
def.listTitle = listTitle;
|
||||
def.listIsExclusive = listIsExclusive;
|
||||
return def;
|
||||
}
|
||||
|
||||
public static TimelineDefinition ofList(ListTimeline list) {
|
||||
return ofList(list.id, list.title);
|
||||
return ofList(list.id, list.title, list.exclusive);
|
||||
}
|
||||
|
||||
public static TimelineDefinition ofHashtag(String hashtag) {
|
||||
@@ -108,7 +111,7 @@ public class TimelineDefinition {
|
||||
case LOCAL -> Icon.LOCAL;
|
||||
case FEDERATED -> Icon.FEDERATED;
|
||||
case POST_NOTIFICATIONS -> Icon.POST_NOTIFICATIONS;
|
||||
case LIST -> Icon.LIST;
|
||||
case LIST -> listIsExclusive ? Icon.EXCLUSIVE_LIST : Icon.LIST;
|
||||
case HASHTAG -> Icon.HASHTAG;
|
||||
case CUSTOM_LOCAL_TIMELINE -> Icon.CUSTOM_LOCAL_TIMELINE;
|
||||
case BUBBLE -> Icon.BUBBLE;
|
||||
@@ -169,6 +172,7 @@ public class TimelineDefinition {
|
||||
def.title = title;
|
||||
def.listId = listId;
|
||||
def.listTitle = listTitle;
|
||||
def.listIsExclusive = listIsExclusive;
|
||||
def.hashtagName = hashtagName;
|
||||
def.domain = domain;
|
||||
def.icon = icon == null ? null : Icon.values()[icon.ordinal()];
|
||||
@@ -179,6 +183,7 @@ public class TimelineDefinition {
|
||||
if (type == TimelineType.LIST) {
|
||||
args.putString("listTitle", title);
|
||||
args.putString("listID", listId);
|
||||
args.putBoolean("listIsExclusive", listIsExclusive);
|
||||
} else if (type == TimelineType.HASHTAG) {
|
||||
args.putString("hashtag", hashtagName);
|
||||
} else if (type == TimelineType.CUSTOM_LOCAL_TIMELINE) {
|
||||
@@ -196,6 +201,7 @@ public class TimelineDefinition {
|
||||
CITY(R.drawable.ic_fluent_city_24_regular, R.string.sk_icon_city),
|
||||
IMAGE(R.drawable.ic_fluent_image_24_regular, R.string.sk_icon_image),
|
||||
NEWS(R.drawable.ic_fluent_news_24_regular, R.string.sk_icon_news),
|
||||
FEED(R.drawable.ic_fluent_rss_24_regular, R.string.sk_icon_feed),
|
||||
COLOR_PALETTE(R.drawable.ic_fluent_color_24_regular, R.string.sk_icon_color_palette),
|
||||
CAT(R.drawable.ic_fluent_animal_cat_24_regular, R.string.sk_icon_cat),
|
||||
DOG(R.drawable.ic_fluent_animal_dog_24_regular, R.string.sk_icon_dog),
|
||||
@@ -250,6 +256,7 @@ public class TimelineDefinition {
|
||||
FEDERATED(R.drawable.ic_fluent_earth_24_regular, R.string.sk_timeline_federated, true),
|
||||
POST_NOTIFICATIONS(R.drawable.ic_fluent_chat_24_regular, R.string.sk_timeline_posts, true),
|
||||
LIST(R.drawable.ic_fluent_people_24_regular, R.string.sk_list, true),
|
||||
EXCLUSIVE_LIST(R.drawable.ic_fluent_rss_24_regular, R.string.sk_exclusive_list, true),
|
||||
HASHTAG(R.drawable.ic_fluent_number_symbol_24_regular, R.string.sk_hashtag, true),
|
||||
CUSTOM_LOCAL_TIMELINE(R.drawable.ic_fluent_people_community_24_regular, R.string.sk_timeline_local, true),
|
||||
BUBBLE(R.drawable.ic_fluent_circle_24_regular, R.string.sk_timeline_bubble, true);
|
||||
|
||||
@@ -10,153 +10,170 @@ import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class PhotoLayoutHelper{
|
||||
public static final int MAX_WIDTH=1000;
|
||||
public static final int MAX_HEIGHT=1910;
|
||||
public static final int MAX_HEIGHT=1700;
|
||||
public static final float GAP=1.5f;
|
||||
|
||||
// 2 * margin + close button height - gap. i don't know if the gap subtraction is correct
|
||||
public static final int MIN_HEIGHT = Math.round(V.dp(2 * 12) + V.dp(40) - GAP);
|
||||
|
||||
@NonNull
|
||||
public static TiledLayoutResult processThumbs(List<Attachment> thumbs){
|
||||
int _maxW=MAX_WIDTH;
|
||||
int _maxH=MAX_HEIGHT;
|
||||
float maxRatio=MAX_WIDTH/(float)MAX_HEIGHT;
|
||||
|
||||
TiledLayoutResult result=new TiledLayoutResult();
|
||||
if(thumbs.size()==1){
|
||||
Attachment att=thumbs.get(0);
|
||||
result.rowSizes=result.columnSizes=new int[]{1};
|
||||
if(att.getWidth()>att.getHeight()){
|
||||
result.width=_maxW;
|
||||
result.height=Math.round(att.getHeight()/(float)att.getWidth()*_maxW);
|
||||
float ratio=att.getWidth()/(float) att.getHeight();
|
||||
if(ratio>maxRatio){
|
||||
result.width=MAX_WIDTH;
|
||||
result.height=Math.max(MIN_HEIGHT, Math.round(att.getHeight()/(float)att.getWidth()*MAX_WIDTH));
|
||||
}else{
|
||||
result.height=_maxH;
|
||||
result.width=Math.round(att.getWidth()/(float)att.getHeight()*_maxH);
|
||||
result.height=MAX_HEIGHT;
|
||||
result.width=MAX_WIDTH;//Math.round(att.getWidth()/(float)att.getHeight()*MAX_HEIGHT);
|
||||
}
|
||||
result.tiles=new TiledLayoutResult.Tile[]{new TiledLayoutResult.Tile(1, 1, result.width, result.height, 0, 0)};
|
||||
result.tiles=new TiledLayoutResult.Tile[]{new TiledLayoutResult.Tile(1, 1, 0, 0)};
|
||||
return result;
|
||||
}else if(thumbs.size()==0){
|
||||
throw new IllegalArgumentException("Empty thumbs array");
|
||||
}
|
||||
|
||||
String orients="";
|
||||
ArrayList<Float> ratios=new ArrayList<Float>();
|
||||
ArrayList<Float> ratios=new ArrayList<>();
|
||||
int cnt=thumbs.size();
|
||||
|
||||
boolean allAreWide=true, allAreSquare=true;
|
||||
|
||||
for(Attachment thumb : thumbs){
|
||||
// float ratio=thumb.isSizeKnown() ? thumb.getWidth()/(float) thumb.getHeight() : 1f;
|
||||
float ratio=thumb.getWidth()/(float) thumb.getHeight();
|
||||
char orient=ratio>1.2 ? 'w' : (ratio<0.8 ? 'n' : 'q');
|
||||
orients+=orient;
|
||||
float ratio=Math.max(0.45f, thumb.getWidth()/(float) thumb.getHeight());
|
||||
if(ratio<=1.2f){
|
||||
allAreWide=false;
|
||||
if(ratio<0.8f)
|
||||
allAreSquare=false;
|
||||
}else{
|
||||
allAreSquare=false;
|
||||
}
|
||||
ratios.add(ratio);
|
||||
}
|
||||
|
||||
float avgRatio=!ratios.isEmpty() ? sum(ratios)/ratios.size() : 1.0f;
|
||||
|
||||
float maxW, maxH, marginW=0, marginH=0;
|
||||
maxW=_maxW;
|
||||
maxH=_maxH;
|
||||
|
||||
float maxRatio=maxW/maxH;
|
||||
|
||||
if(cnt==2){
|
||||
if(orients.equals("ww") && avgRatio>1.4*maxRatio && (ratios.get(1)-ratios.get(0))<0.2){ // two wide photos, one above the other
|
||||
float h=Math.min(maxW/ratios.get(0), Math.min(maxW/ratios.get(1), (maxH-marginH)/2.0f));
|
||||
if(allAreWide && avgRatio>1.4*maxRatio && (ratios.get(1)-ratios.get(0))<0.2){ // two wide photos, one above the other
|
||||
float h=Math.max(Math.min(MAX_WIDTH/ratios.get(0), Math.min(MAX_WIDTH/ratios.get(1), (MAX_HEIGHT-GAP)/2.0f)), MIN_HEIGHT/2f);
|
||||
|
||||
result.width=Math.round(maxW);
|
||||
result.height=Math.round(h*2+marginH);
|
||||
result.width=MAX_WIDTH;
|
||||
result.height=Math.round(h*2+GAP);
|
||||
result.columnSizes=new int[]{result.width};
|
||||
result.rowSizes=new int[]{Math.round(h), Math.round(h)};
|
||||
result.tiles=new TiledLayoutResult.Tile[]{
|
||||
new TiledLayoutResult.Tile(1, 1, maxW, h, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, maxW, h, 0, 1)
|
||||
new TiledLayoutResult.Tile(1, 1, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, 0, 1)
|
||||
};
|
||||
}else if(orients.equals("ww") || orients.equals("qq")){ // next to each other, same ratio
|
||||
float w=((maxW-marginW)/2);
|
||||
float h=Math.min(w/ratios.get(0), Math.min(w/ratios.get(1), maxH));
|
||||
}else if(allAreWide || allAreSquare){ // next to each other, same ratio
|
||||
float w=((MAX_WIDTH-GAP)/2);
|
||||
float h=Math.max(Math.min(w/ratios.get(0), Math.min(w/ratios.get(1), MAX_HEIGHT)), MIN_HEIGHT);
|
||||
|
||||
result.width=Math.round(maxW);
|
||||
result.width=MAX_WIDTH;
|
||||
result.height=Math.round(h);
|
||||
result.columnSizes=new int[]{Math.round(w), _maxW-Math.round(w)};
|
||||
result.columnSizes=new int[]{Math.round(w), MAX_WIDTH-Math.round(w)};
|
||||
result.rowSizes=new int[]{Math.round(h)};
|
||||
result.tiles=new TiledLayoutResult.Tile[]{
|
||||
new TiledLayoutResult.Tile(1, 1, w, h, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, w, h, 1, 0)
|
||||
new TiledLayoutResult.Tile(1, 1, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, 1, 0)
|
||||
};
|
||||
}else{ // next to each other, different ratios
|
||||
float w0=((maxW-marginW)/ratios.get(1)/(1/ratios.get(0)+1/ratios.get(1)));
|
||||
float w1=(maxW-w0-marginW);
|
||||
float h=Math.min(maxH, Math.min(w0/ratios.get(0), w1/ratios.get(1)));
|
||||
float w0=((MAX_WIDTH-GAP)/ratios.get(1)/(1/ratios.get(0)+1/ratios.get(1)));
|
||||
float w1=(MAX_WIDTH-w0-GAP);
|
||||
float h=Math.max(Math.min(MAX_HEIGHT, Math.min(w0/ratios.get(0), w1/ratios.get(1))), MIN_HEIGHT);
|
||||
|
||||
result.columnSizes=new int[]{Math.round(w0), Math.round(w1)};
|
||||
result.rowSizes=new int[]{Math.round(h)};
|
||||
result.width=Math.round(w0+w1+marginW);
|
||||
result.width=Math.round(w0+w1+GAP);
|
||||
result.height=Math.round(h);
|
||||
result.tiles=new TiledLayoutResult.Tile[]{
|
||||
new TiledLayoutResult.Tile(1, 1, w0, h, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, w1, h, 1, 0)
|
||||
new TiledLayoutResult.Tile(1, 1, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, 1, 0)
|
||||
};
|
||||
}
|
||||
}else if(cnt==3){
|
||||
if(/*(ratios.get(0) > 1.2 * maxRatio || avgRatio > 1.5 * maxRatio) &&*/ orients.equals("www") || true){ // 2nd and 3rd photos are on the next line
|
||||
float hCover=Math.min(maxW/ratios.get(0), (maxH-marginH)*0.66f);
|
||||
float w2=((maxW-marginW)/2);
|
||||
float h=Math.min(maxH-hCover-marginH, Math.min(w2/ratios.get(1), w2/ratios.get(2)));
|
||||
result.width=Math.round(maxW);
|
||||
result.height=Math.round(hCover+h+marginH);
|
||||
result.columnSizes=new int[]{Math.round(w2), _maxW-Math.round(w2)};
|
||||
if((ratios.get(0) > 1.2 * maxRatio || avgRatio > 1.5 * maxRatio) || allAreWide){ // 2nd and 3rd photos are on the next line
|
||||
float hCover=Math.min(MAX_WIDTH/ratios.get(0), (MAX_HEIGHT-GAP)*0.66f);
|
||||
float w2=((MAX_WIDTH-GAP)/2);
|
||||
float h=Math.min(MAX_HEIGHT-hCover-GAP, Math.min(w2/ratios.get(1), w2/ratios.get(2)));
|
||||
if(hCover+h<MIN_HEIGHT){
|
||||
float prevTotalHeight=hCover+h;
|
||||
hCover=MIN_HEIGHT*(hCover/prevTotalHeight);
|
||||
h=MIN_HEIGHT*(h/prevTotalHeight);
|
||||
}
|
||||
result.width=MAX_WIDTH;
|
||||
result.height=Math.round(hCover+h+GAP);
|
||||
result.columnSizes=new int[]{Math.round(w2), MAX_WIDTH-Math.round(w2)};
|
||||
result.rowSizes=new int[]{Math.round(hCover), Math.round(h)};
|
||||
result.tiles=new TiledLayoutResult.Tile[]{
|
||||
new TiledLayoutResult.Tile(2, 1, maxW, hCover, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, w2, h, 0, 1),
|
||||
new TiledLayoutResult.Tile(1, 1, w2, h, 1, 1)
|
||||
new TiledLayoutResult.Tile(2, 1, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, 0, 1),
|
||||
new TiledLayoutResult.Tile(1, 1, 1, 1)
|
||||
};
|
||||
}else{ // 2nd and 3rd photos are on the right part
|
||||
float wCover=Math.min(maxH*ratios.get(0), (maxW-marginW)*0.75f);
|
||||
float h1=(ratios.get(1)*(maxH-marginH)/(ratios.get(2)+ratios.get(1)));
|
||||
float h0=(maxH-h1-marginH);
|
||||
float w=Math.min(maxW-wCover-marginW, Math.min(h1*ratios.get(2), h0*ratios.get(1)));
|
||||
result.width=Math.round(wCover+w+marginW);
|
||||
result.height=Math.round(maxH);
|
||||
float height=Math.min(MAX_HEIGHT, MAX_WIDTH*0.66f/avgRatio);
|
||||
float wCover=Math.min(height*ratios.get(0), (MAX_WIDTH-GAP)*0.66f);
|
||||
float h1=(ratios.get(1)*(height-GAP)/(ratios.get(2)+ratios.get(1)));
|
||||
float h0=(height-h1-GAP);
|
||||
float w=Math.min(MAX_WIDTH-wCover-GAP, Math.min(h1*ratios.get(2), h0*ratios.get(1)));
|
||||
result.width=Math.round(wCover+w+GAP);
|
||||
result.height=Math.round(height);
|
||||
result.columnSizes=new int[]{Math.round(wCover), Math.round(w)};
|
||||
result.rowSizes=new int[]{Math.round(h0), Math.round(h1)};
|
||||
result.tiles=new TiledLayoutResult.Tile[]{
|
||||
new TiledLayoutResult.Tile(1, 2, wCover, maxH, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, w, h0, 1, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, w, h1, 1, 1)
|
||||
new TiledLayoutResult.Tile(1, 2, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, 1, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, 1, 1)
|
||||
};
|
||||
}
|
||||
}else if(cnt==4){
|
||||
if(/*(ratios.get(0) > 1.2 * maxRatio || avgRatio > 1.5 * maxRatio) &&*/ orients.equals("wwww") || true /* temporary fix */){ // 2nd, 3rd and 4th photos are on the next line
|
||||
float hCover=Math.min(maxW/ratios.get(0), (maxH-marginH)*0.66f);
|
||||
float h=(maxW-2*marginW)/(ratios.get(1)+ratios.get(2)+ratios.get(3));
|
||||
if((ratios.get(0) > 1.2 * maxRatio || avgRatio > 1.5 * maxRatio) || allAreWide){ // 2nd, 3rd and 4th photos are on the next line
|
||||
float hCover=Math.min(MAX_WIDTH/ratios.get(0), (MAX_HEIGHT-GAP)*0.66f);
|
||||
float h=(MAX_WIDTH-2*GAP)/(ratios.get(1)+ratios.get(2)+ratios.get(3));
|
||||
float w0=h*ratios.get(1);
|
||||
float w1=h*ratios.get(2);
|
||||
float w2=h*ratios.get(3);
|
||||
h=Math.min(maxH-hCover-marginH, h);
|
||||
result.width=Math.round(maxW);
|
||||
result.height=Math.round(hCover+h+marginH);
|
||||
result.columnSizes=new int[]{Math.round(w0), Math.round(w1), _maxW-Math.round(w0)-Math.round(w1)};
|
||||
h=Math.min(MAX_HEIGHT-hCover-GAP, h);
|
||||
if(hCover+h<MIN_HEIGHT){
|
||||
float prevTotalHeight=hCover+h;
|
||||
hCover=MIN_HEIGHT*(hCover/prevTotalHeight);
|
||||
h=MIN_HEIGHT*(h/prevTotalHeight);
|
||||
}
|
||||
result.width=MAX_WIDTH;
|
||||
result.height=Math.round(hCover+h+GAP);
|
||||
result.columnSizes=new int[]{Math.round(w0), Math.round(w1), MAX_WIDTH-Math.round(w0)-Math.round(w1)};
|
||||
result.rowSizes=new int[]{Math.round(hCover), Math.round(h)};
|
||||
result.tiles=new TiledLayoutResult.Tile[]{
|
||||
new TiledLayoutResult.Tile(3, 1, maxW, hCover, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, w0, h, 0, 1),
|
||||
new TiledLayoutResult.Tile(1, 1, w1, h, 1, 1),
|
||||
new TiledLayoutResult.Tile(1, 1, w2, h, 2, 1),
|
||||
new TiledLayoutResult.Tile(3, 1, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, 0, 1),
|
||||
new TiledLayoutResult.Tile(1, 1, 1, 1),
|
||||
new TiledLayoutResult.Tile(1, 1, 2, 1),
|
||||
};
|
||||
}else{ // 2nd, 3rd and 4th photos are on the right part
|
||||
float wCover= Math.min(maxH*ratios.get(0), (maxW-marginW)*0.66f);
|
||||
float w=(maxH-2*marginH)/(1/ratios.get(1)+1/ratios.get(2)+1/ratios.get(3));
|
||||
float height=Math.min(MAX_HEIGHT, MAX_WIDTH*0.66f/avgRatio);
|
||||
float wCover= Math.min(height*ratios.get(0), (MAX_WIDTH-GAP)*0.66f);
|
||||
float w=(height-2*GAP)/(1/ratios.get(1)+1/ratios.get(2)+1/ratios.get(3));
|
||||
float h0=w/ratios.get(1);
|
||||
float h1=w/ratios.get(2);
|
||||
float h2=w/ratios.get(3)+marginH;
|
||||
w=Math.min(maxW-wCover-marginW, w);
|
||||
result.width=Math.round(wCover+marginW+w);
|
||||
result.height=Math.round(maxH);
|
||||
float h2=w/ratios.get(3)+GAP;
|
||||
w=Math.min(MAX_WIDTH-wCover-GAP, w);
|
||||
result.width=Math.round(wCover+GAP+w);
|
||||
result.height=Math.round(height);
|
||||
result.columnSizes=new int[]{Math.round(wCover), Math.round(w)};
|
||||
result.rowSizes=new int[]{Math.round(h0), Math.round(h1), Math.round(h2)};
|
||||
result.tiles=new TiledLayoutResult.Tile[]{
|
||||
new TiledLayoutResult.Tile(1, 3, wCover, maxH, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, w, h0, 1, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, w, h1, 1, 1),
|
||||
new TiledLayoutResult.Tile(1, 1, w, h2, 1, 2),
|
||||
new TiledLayoutResult.Tile(1, 3, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, 1, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, 1, 1),
|
||||
new TiledLayoutResult.Tile(1, 1, 1, 2),
|
||||
};
|
||||
}
|
||||
}else{
|
||||
@@ -174,14 +191,14 @@ public class PhotoLayoutHelper{
|
||||
HashMap<int[], float[]> tries=new HashMap<>();
|
||||
|
||||
// One line
|
||||
int firstLine, secondLine, thirdLine;
|
||||
tries.put(new int[]{firstLine=cnt}, new float[]{calculateMultiThumbsHeight(ratiosCropped, maxW, marginW)});
|
||||
int firstLine, secondLine;
|
||||
tries.put(new int[]{cnt}, new float[]{calculateMultiThumbsHeight(ratiosCropped, MAX_WIDTH, GAP)});
|
||||
|
||||
// Two lines
|
||||
for(firstLine=1; firstLine<=cnt-1; firstLine++){
|
||||
tries.put(new int[]{firstLine, secondLine=cnt-firstLine}, new float[]{
|
||||
calculateMultiThumbsHeight(ratiosCropped.subList(0, firstLine), maxW, marginW),
|
||||
calculateMultiThumbsHeight(ratiosCropped.subList(firstLine, ratiosCropped.size()), maxW, marginW)
|
||||
tries.put(new int[]{firstLine, cnt-firstLine}, new float[]{
|
||||
calculateMultiThumbsHeight(ratiosCropped.subList(0, firstLine), MAX_WIDTH, GAP),
|
||||
calculateMultiThumbsHeight(ratiosCropped.subList(firstLine, ratiosCropped.size()), MAX_WIDTH, GAP)
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -189,23 +206,24 @@ public class PhotoLayoutHelper{
|
||||
// Three lines
|
||||
for(firstLine=1; firstLine<=cnt-2; firstLine++){
|
||||
for(secondLine=1; secondLine<=cnt-firstLine-1; secondLine++){
|
||||
tries.put(new int[]{firstLine, secondLine, thirdLine=cnt-firstLine-secondLine}, new float[]{
|
||||
calculateMultiThumbsHeight(ratiosCropped.subList(0, firstLine), maxW, marginW),
|
||||
calculateMultiThumbsHeight(ratiosCropped.subList(firstLine, firstLine+secondLine), maxW, marginW),
|
||||
calculateMultiThumbsHeight(ratiosCropped.subList(firstLine+secondLine, ratiosCropped.size()), maxW, marginW)
|
||||
tries.put(new int[]{firstLine, secondLine, cnt-firstLine-secondLine}, new float[]{
|
||||
calculateMultiThumbsHeight(ratiosCropped.subList(0, firstLine), MAX_WIDTH, GAP),
|
||||
calculateMultiThumbsHeight(ratiosCropped.subList(firstLine, firstLine+secondLine), MAX_WIDTH, GAP),
|
||||
calculateMultiThumbsHeight(ratiosCropped.subList(firstLine+secondLine, ratiosCropped.size()), MAX_WIDTH, GAP)
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Looking for minimum difference between thumbs block height and maxH (may probably be little over)
|
||||
// Looking for minimum difference between thumbs block height and maxHeight (may probably be little over)
|
||||
final int realMaxHeight=Math.min(MAX_HEIGHT, MAX_WIDTH);
|
||||
int[] optConf=null;
|
||||
float optDiff=0;
|
||||
for(int[] conf : tries.keySet()){
|
||||
float[] heights=tries.get(conf);
|
||||
float confH=marginH*(heights.length-1);
|
||||
float confH=GAP*(heights.length-1);
|
||||
for(float h : heights) confH+=h;
|
||||
float confDiff=Math.abs(confH-maxH);
|
||||
float confDiff=Math.abs(confH-realMaxHeight);
|
||||
if(conf.length>1){
|
||||
if(conf[0]>conf[1] || conf.length>2 && conf[1]>conf[2]){
|
||||
confDiff*=1.1;
|
||||
@@ -222,7 +240,7 @@ public class PhotoLayoutHelper{
|
||||
float[] optHeights=tries.get(optConf);
|
||||
int k=0;
|
||||
|
||||
result.width=Math.round(maxW);
|
||||
result.width=MAX_WIDTH;
|
||||
result.rowSizes=new int[optHeights.length];
|
||||
result.tiles=new TiledLayoutResult.Tile[thumbs.size()];
|
||||
float totalHeight=0f;
|
||||
@@ -240,11 +258,11 @@ public class PhotoLayoutHelper{
|
||||
ArrayList<TiledLayoutResult.Tile> row=new ArrayList<>();
|
||||
for(int j=0; j<lineThumbs.size(); j++){
|
||||
float thumb_ratio=ratiosRemain.remove(0);
|
||||
float w=j==lineThumbs.size()-1 ? (maxW-totalWidth) : (thumb_ratio*lineHeight);
|
||||
float w=j==lineThumbs.size()-1 ? (MAX_WIDTH-totalWidth) : (thumb_ratio*lineHeight);
|
||||
totalWidth+=Math.round(w);
|
||||
if(j<lineThumbs.size()-1 && !gridLineOffsets.contains(totalWidth))
|
||||
gridLineOffsets.add(totalWidth);
|
||||
TiledLayoutResult.Tile tile=new TiledLayoutResult.Tile(1, 1, w, lineHeight, 0, i);
|
||||
TiledLayoutResult.Tile tile=new TiledLayoutResult.Tile(1, 1, 0, i, Math.round(w));
|
||||
result.tiles[k]=tile;
|
||||
row.add(tile);
|
||||
k++;
|
||||
@@ -252,7 +270,7 @@ public class PhotoLayoutHelper{
|
||||
rowTiles.add(row);
|
||||
}
|
||||
Collections.sort(gridLineOffsets);
|
||||
gridLineOffsets.add(Math.round(maxW));
|
||||
gridLineOffsets.add(Math.round(MAX_WIDTH));
|
||||
result.columnSizes=new int[gridLineOffsets.size()];
|
||||
result.columnSizes[0]=gridLineOffsets.get(0);
|
||||
for(int i=gridLineOffsets.size()-1; i>0; i--){
|
||||
@@ -276,7 +294,7 @@ public class PhotoLayoutHelper{
|
||||
columnOffset+=tile.colSpan;
|
||||
}
|
||||
}
|
||||
result.height=Math.round(totalHeight+marginH*(optHeights.length-1));
|
||||
result.height=Math.round(totalHeight+GAP*(optHeights.length-1));
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -310,19 +328,19 @@ public class PhotoLayoutHelper{
|
||||
}
|
||||
|
||||
public static class Tile{
|
||||
public int colSpan, rowSpan, width, height, startCol, startRow;
|
||||
public int colSpan, rowSpan, startCol, startRow;
|
||||
public int width;
|
||||
|
||||
public Tile(int colSpan, int rowSpan, int width, int height, int startCol, int startRow){
|
||||
public Tile(int colSpan, int rowSpan, int startCol, int startRow){
|
||||
this.colSpan=colSpan;
|
||||
this.rowSpan=rowSpan;
|
||||
this.width=width;
|
||||
this.height=height;
|
||||
this.startCol=startCol;
|
||||
this.startRow=startRow;
|
||||
}
|
||||
|
||||
public Tile(int colSpan, int rowSpan, float width, float height, int startCol, int startRow){
|
||||
this(colSpan, rowSpan, Math.round(width), Math.round(height), startCol, startRow);
|
||||
public Tile(int colSpan, int rowSpan, int startCol, int startRow, int width){
|
||||
this(colSpan, rowSpan, startCol, startRow);
|
||||
this.width=width;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -330,8 +348,8 @@ public class PhotoLayoutHelper{
|
||||
return "Tile{"+
|
||||
"colSpan="+colSpan+
|
||||
", rowSpan="+rowSpan+
|
||||
", width="+width+
|
||||
", height="+height+
|
||||
", startCol="+startCol+
|
||||
", startRow="+startRow+
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +133,10 @@ public class AccountCardStatusDisplayItem extends StatusDisplayItem{
|
||||
followersLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
|
||||
followingLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
|
||||
postsLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, item.account.statusesCount)));
|
||||
followersCount.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
||||
followersLabel.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
||||
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
||||
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
||||
relationship=item.parentFragment.getRelationship(item.account.id);
|
||||
if(item.notification.type == Notification.Type.FOLLOW_REQUEST && (relationship == null || !relationship.followedBy)){
|
||||
actionWrap.setVisibility(View.GONE);
|
||||
|
||||
@@ -76,10 +76,8 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
|
||||
public void onBind(ExtendedFooterStatusDisplayItem item){
|
||||
Status s=item.status;
|
||||
favorites.setText(context.getResources().getQuantityString(R.plurals.x_favorites, (int)(s.favouritesCount%1000), s.favouritesCount));
|
||||
|
||||
reblogs.setText(context.getResources().getQuantityString(R.plurals.x_reblogs, (int) (s.reblogsCount % 1000), s.reblogsCount));
|
||||
if (!s.canBeBoosted(item.accountID))
|
||||
reblogs.setVisibility(View.GONE);
|
||||
reblogs.setVisibility(s.isReblogPermitted(item.accountID) ? View.VISIBLE : View.GONE);
|
||||
|
||||
if(s.editedAt!=null){
|
||||
editHistory.setVisibility(View.VISIBLE);
|
||||
@@ -88,7 +86,7 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
|
||||
editHistory.setVisibility(View.GONE);
|
||||
}
|
||||
String timeStr=item.status.createdAt != null ? TIME_FORMATTER.format(item.status.createdAt.atZone(ZoneId.systemDefault())) : null;
|
||||
|
||||
|
||||
if (item.status.application!=null && !TextUtils.isEmpty(item.status.application.name)) {
|
||||
time.setText(timeStr != null ? item.parentFragment.getString(R.string.timestamp_via_app, timeStr, "") : "");
|
||||
applicationName.setText(item.status.application.name);
|
||||
@@ -143,4 +141,4 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
|
||||
Nav.go(item.parentFragment.getActivity(), StatusEditHistoryFragment.class, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,8 +147,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
boost.setSelected(item.status.reblogged);
|
||||
favorite.setSelected(item.status.favourited);
|
||||
bookmark.setSelected(item.status.bookmarked);
|
||||
boost.setEnabled(item.status.visibility==StatusPrivacy.PUBLIC || item.status.visibility==StatusPrivacy.UNLISTED || item.status.visibility==StatusPrivacy.LOCAL
|
||||
|| (item.status.visibility==StatusPrivacy.PRIVATE && item.status.account.id.equals(AccountSessionManager.getInstance().getAccount(item.accountID).self.id)));
|
||||
boost.setEnabled(item.status.isReblogPermitted(item.accountID));
|
||||
|
||||
int nextPos = getAbsoluteAdapterPosition() + 1;
|
||||
boolean nextIsWarning = item.parentFragment.getDisplayItems().size() > nextPos &&
|
||||
@@ -187,9 +186,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
} else if (action == MotionEvent.ACTION_DOWN) {
|
||||
longClickPerformed = false;
|
||||
touchingView = v;
|
||||
// 28dp to center in middle of icon, because:
|
||||
// (icon width = 24dp) / 2 + (paddingStart = 8dp) + (paddingHorizontal = 8dp)
|
||||
v.setPivotX(UiUtils.sp(v.getContext(), 28));
|
||||
v.setPivotX(V.sp(28));
|
||||
v.animate().scaleX(0.85f).scaleY(0.85f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(75).start();
|
||||
if (disabled) return true;
|
||||
v.postDelayed(longClickRunnable, ViewConfiguration.getLongPressTimeout());
|
||||
|
||||
@@ -425,6 +425,9 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
else collapseBtnIcon.animate().scaleY(item.status.textExpanded ? -1 : 1).start();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) collapseBtn.setTooltipText(collapseText);
|
||||
}
|
||||
|
||||
itemView.setPaddingRelative(itemView.getPaddingStart(), itemView.getPaddingTop(),
|
||||
item.inset ? V.dp(10) : V.dp(4), itemView.getPaddingBottom());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.app.Activity;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
@@ -25,7 +26,9 @@ import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
|
||||
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.FrameLayoutThatOnlyMeasuresFirstChild;
|
||||
import org.joinmastodon.android.ui.views.MaxWidthFrameLayout;
|
||||
import org.joinmastodon.android.ui.views.MediaGridLayout;
|
||||
import org.joinmastodon.android.utils.TypedObjectPool;
|
||||
|
||||
@@ -88,6 +91,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
private final View.OnClickListener clickListener=this::onViewClick, altTextClickListener=this::onAltTextClick;
|
||||
private final ArrayList<MediaAttachmentViewController> controllers=new ArrayList<>();
|
||||
|
||||
private final MaxWidthFrameLayout overlays;
|
||||
private final FrameLayout altTextWrapper;
|
||||
private final TextView altTextButton;
|
||||
private final ImageView noAltTextButton;
|
||||
@@ -105,8 +109,13 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
wrapper=(FrameLayout)itemView;
|
||||
layout=new MediaGridLayout(activity);
|
||||
wrapper.addView(layout);
|
||||
wrapper.setClipToPadding(false);
|
||||
|
||||
activity.getLayoutInflater().inflate(R.layout.overlay_image_alt_text, wrapper);
|
||||
overlays=new MaxWidthFrameLayout(activity);
|
||||
overlays.setMaxWidth(UiUtils.MAX_WIDTH);
|
||||
wrapper.addView(overlays, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER_HORIZONTAL));
|
||||
|
||||
activity.getLayoutInflater().inflate(R.layout.overlay_image_alt_text, overlays);
|
||||
altTextWrapper=findViewById(R.id.alt_text_wrapper);
|
||||
altTextButton=findViewById(R.id.alt_button);
|
||||
noAltTextButton=findViewById(R.id.no_alt_button);
|
||||
@@ -215,7 +224,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
int[] loc={0, 0};
|
||||
v.getLocationInWindow(loc);
|
||||
int btnL=loc[0], btnT=loc[1];
|
||||
wrapper.getLocationInWindow(loc);
|
||||
overlays.getLocationInWindow(loc);
|
||||
btnL-=loc[0];
|
||||
btnT-=loc[1];
|
||||
|
||||
@@ -278,7 +287,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
int[] loc={0, 0};
|
||||
btn.getLocationInWindow(loc);
|
||||
int btnL=loc[0], btnT=loc[1];
|
||||
wrapper.getLocationInWindow(loc);
|
||||
overlays.getLocationInWindow(loc);
|
||||
btnL-=loc[0];
|
||||
btnT-=loc[1];
|
||||
|
||||
|
||||
@@ -105,6 +105,21 @@ public abstract class StatusDisplayItem{
|
||||
return buildItems(fragment, status, accountID, parentObject, knownAccounts, inset, addFooter, notification, false, filterContext);
|
||||
}
|
||||
|
||||
public static ReblogOrReplyLineStatusDisplayItem buildReplyLine(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parent, Account account, boolean threadReply) {
|
||||
String parentID = parent.getID();
|
||||
String text = threadReply ? fragment.getString(R.string.sk_show_thread)
|
||||
: account == null ? fragment.getString(R.string.sk_in_reply)
|
||||
: GlobalUserPreferences.compactReblogReplyLine && status.reblog != null ? account.displayName
|
||||
: fragment.getString(R.string.in_reply_to, account.displayName);
|
||||
String fullText = threadReply ? fragment.getString(R.string.sk_show_thread)
|
||||
: account == null ? fragment.getString(R.string.sk_in_reply)
|
||||
: fragment.getString(R.string.in_reply_to, account.displayName);
|
||||
return new ReblogOrReplyLineStatusDisplayItem(
|
||||
parentID, fragment, text, account == null ? List.of() : account.emojis,
|
||||
R.drawable.ic_fluent_arrow_reply_20sp_filled, null, null, fullText
|
||||
);
|
||||
}
|
||||
|
||||
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate, Filter.FilterContext filterContext){
|
||||
String parentID=parentObject.getID();
|
||||
ArrayList<StatusDisplayItem> items=new ArrayList<>();
|
||||
@@ -120,17 +135,7 @@ public abstract class StatusDisplayItem{
|
||||
|
||||
if(statusForContent.inReplyToAccountId!=null && !(threadReply && fragment instanceof ThreadFragment)){
|
||||
Account account = knownAccounts.get(statusForContent.inReplyToAccountId);
|
||||
String text = threadReply ? fragment.getString(R.string.sk_show_thread)
|
||||
: account == null ? fragment.getString(R.string.sk_in_reply)
|
||||
: GlobalUserPreferences.compactReblogReplyLine && status.reblog != null ? account.displayName
|
||||
: fragment.getString(R.string.in_reply_to, account.displayName);
|
||||
String fullText = threadReply ? fragment.getString(R.string.sk_show_thread)
|
||||
: account == null ? fragment.getString(R.string.sk_in_reply)
|
||||
: fragment.getString(R.string.in_reply_to, account.displayName);
|
||||
replyLine = new ReblogOrReplyLineStatusDisplayItem(
|
||||
parentID, fragment, text, account == null ? List.of() : account.emojis,
|
||||
R.drawable.ic_fluent_arrow_reply_20sp_filled, null, null, fullText
|
||||
);
|
||||
replyLine = buildReplyLine(fragment, status, accountID, parentObject, account, threadReply);
|
||||
}
|
||||
|
||||
if(status.reblog!=null){
|
||||
|
||||
@@ -1307,7 +1307,7 @@ public class UiUtils {
|
||||
go.accept(ProfileFragment.class, args);
|
||||
return;
|
||||
}
|
||||
go.accept(null, bundleError(context.getString(R.string.sk_resource_not_found)));
|
||||
go.accept(null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1499,16 +1499,6 @@ public class UiUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale the input value according to the device's scaled display density
|
||||
* @param sp Input value in scale-independent pixels (sp)
|
||||
* @return Scaled value in physical pixels (px)
|
||||
*/
|
||||
public static int sp(Context context, float sp){
|
||||
// TODO: replace with V.sp in next AppKit version
|
||||
return Math.round(sp*context.getApplicationContext().getResources().getDisplayMetrics().scaledDensity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a View.OnClickListener to filter multiple clicks in succession.
|
||||
* Useful for buttons that perform some action that changes their state asynchronously.
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.view.MenuItem;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.Switch;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -16,18 +17,20 @@ import androidx.annotation.Nullable;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.ListTimeline;
|
||||
|
||||
public class ListTimelineEditor extends LinearLayout {
|
||||
public class ListEditor extends LinearLayout {
|
||||
private ListTimeline.RepliesPolicy policy = null;
|
||||
private final TextInputFrameLayout input;
|
||||
private final Button button;
|
||||
private final Switch exclusiveSwitch;
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public ListTimelineEditor(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
public ListEditor(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
LayoutInflater.from(context).inflate(R.layout.list_timeline_editor, this);
|
||||
|
||||
button = findViewById(R.id.button);
|
||||
input = findViewById(R.id.input);
|
||||
exclusiveSwitch = findViewById(R.id.exclusive_checkbox);
|
||||
|
||||
PopupMenu popupMenu = new PopupMenu(context, button, Gravity.CENTER_HORIZONTAL);
|
||||
popupMenu.inflate(R.menu.list_reply_policies);
|
||||
@@ -36,12 +39,15 @@ public class ListTimelineEditor extends LinearLayout {
|
||||
button.setOnTouchListener(popupMenu.getDragToOpenListener());
|
||||
button.setOnClickListener(v->popupMenu.show());
|
||||
input.getEditText().setHint(context.getString(R.string.sk_list_name_hint));
|
||||
findViewById(R.id.exclusive)
|
||||
.setOnClickListener(v -> exclusiveSwitch.setChecked(!exclusiveSwitch.isChecked()));
|
||||
|
||||
setRepliesPolicy(ListTimeline.RepliesPolicy.LIST);
|
||||
}
|
||||
|
||||
public void applyList(String title, @Nullable ListTimeline.RepliesPolicy policy) {
|
||||
public void applyList(String title, boolean exclusive, @Nullable ListTimeline.RepliesPolicy policy) {
|
||||
input.getEditText().setText(title);
|
||||
exclusiveSwitch.setChecked(exclusive);
|
||||
if (policy != null) setRepliesPolicy(policy);
|
||||
}
|
||||
|
||||
@@ -53,6 +59,10 @@ public class ListTimelineEditor extends LinearLayout {
|
||||
return policy;
|
||||
}
|
||||
|
||||
public boolean isExclusive() {
|
||||
return exclusiveSwitch.isChecked();
|
||||
}
|
||||
|
||||
public void setRepliesPolicy(@NonNull ListTimeline.RepliesPolicy policy) {
|
||||
this.policy = policy;
|
||||
switch (policy) {
|
||||
@@ -73,15 +83,15 @@ public class ListTimelineEditor extends LinearLayout {
|
||||
return true;
|
||||
}
|
||||
|
||||
public ListTimelineEditor(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
public ListEditor(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
public ListTimelineEditor(Context context, AttributeSet attrs) {
|
||||
public ListEditor(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public ListTimelineEditor(Context context) {
|
||||
public ListEditor(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import me.grishka.appkit.utils.V;
|
||||
public class MediaGridLayout extends ViewGroup{
|
||||
private static final String TAG="MediaGridLayout";
|
||||
|
||||
private static final int GAP=1; // dp
|
||||
private static final int GAP=2; // dp
|
||||
private PhotoLayoutHelper.TiledLayoutResult tiledLayout;
|
||||
private int[] columnStarts=new int[10], columnEnds=new int[10], rowStarts=new int[10], rowEnds=new int[10];
|
||||
|
||||
@@ -37,6 +37,9 @@ public class MediaGridLayout extends ViewGroup{
|
||||
}
|
||||
int width=Math.min(UiUtils.MAX_WIDTH, MeasureSpec.getSize(widthMeasureSpec));
|
||||
int height=Math.round(width*(tiledLayout.height/(float)PhotoLayoutHelper.MAX_WIDTH));
|
||||
if(tiledLayout.width<PhotoLayoutHelper.MAX_WIDTH){
|
||||
width=Math.round(width*(tiledLayout.width/(float)PhotoLayoutHelper.MAX_WIDTH));
|
||||
}
|
||||
|
||||
int offset=0;
|
||||
for(int i=0;i<tiledLayout.columnSizes.length;i++){
|
||||
@@ -73,9 +76,13 @@ public class MediaGridLayout extends ViewGroup{
|
||||
if(tiledLayout==null)
|
||||
return;
|
||||
|
||||
int maxWidth=UiUtils.MAX_WIDTH;
|
||||
if(tiledLayout.width<PhotoLayoutHelper.MAX_WIDTH){
|
||||
maxWidth=Math.round((r-l)*(tiledLayout.width/(float)PhotoLayoutHelper.MAX_WIDTH));
|
||||
}
|
||||
int xOffset=0;
|
||||
if(r-l>UiUtils.MAX_WIDTH){
|
||||
xOffset=(r-l)/2-UiUtils.MAX_WIDTH/2;
|
||||
if(r-l>maxWidth){
|
||||
xOffset=(r-l)/2-maxWidth/2;
|
||||
}
|
||||
|
||||
for(int i=0;i<getChildCount();i++){
|
||||
|
||||
@@ -0,0 +1,956 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
|
||||
import static org.xmlpull.v1.XmlPullParser.START_TAG;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Service;
|
||||
import android.content.ClipData;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentResolver;
|
||||
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.DoNotInline;
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.annotation.XmlRes;
|
||||
|
||||
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 Activity} is active. For an {@link Intent} going to a
|
||||
* {@link Service}, the permissions are available as long as the
|
||||
* {@link 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>Defining a FileProvider</li>
|
||||
* <li>Specifying Available Files</li>
|
||||
* <li>Generating the Content URI for a File</li>
|
||||
* <li>Granting Temporary Permissions to a URI</li>
|
||||
* <li>Serving a Content URI to Another App</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* <b>Defining a FileProvider</b>
|
||||
* <p>
|
||||
* Extend FileProvider with a default constructor, and call super with an XML resource file that
|
||||
* specifies the available files (see below for the structure of the XML file):
|
||||
* <pre class="prettyprint">
|
||||
* public class MyFileProvider extends FileProvider {
|
||||
* public MyFileProvider() {
|
||||
* super(R.xml.file_paths)
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* 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 the FileProvider you
|
||||
* created. 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="com.sample.MyFileProvider"
|
||||
* android:authorities="com.mydomain.fileprovider"
|
||||
* android:exported="false"
|
||||
* android:grantUriPermissions="true">
|
||||
* ...
|
||||
* </provider>
|
||||
* ...
|
||||
* </application>
|
||||
* </manifest></pre>
|
||||
* <p>
|
||||
* It is possible to use FileProvider directly instead of extending it. However, this is not
|
||||
* reliable and will causes crashes on some devices.
|
||||
* <p>
|
||||
* <b>Specifying Available Files</b>
|
||||
* <p>
|
||||
* A FileProvider can only generate a content URI for files in directories that you specify
|
||||
* beforehand. To specify a directory, specify 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:
|
||||
* <ul>
|
||||
* <li>
|
||||
* <pre class="prettyprint"><files-path name="<i>name</i>" path="<i>path</i>" /></pre>
|
||||
* 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()}.
|
||||
* </li>
|
||||
* <li><pre><cache-path name="<i>name</i>" path="<i>path</i>" /></pre>
|
||||
* 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()}.
|
||||
* <li>
|
||||
* <pre class="prettyprint"><external-path name="<i>name</i>" path="<i>path</i>" /></pre>
|
||||
* 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()}.
|
||||
* <li>
|
||||
* <pre class="prettyprint"><external-files-path name="<i>name</i>" path="<i>path</i>"
|
||||
* /></pre>
|
||||
* 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
|
||||
* {@link Context#getExternalFilesDir(String) Context.getExternalFilesDir(null)}.
|
||||
* </li>
|
||||
* <li>
|
||||
* <pre class="prettyprint"><external-cache-path name="<i>name</i>" path="<i>path</i>"
|
||||
* /></pre>
|
||||
* 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()}.
|
||||
* <li>
|
||||
* <pre class="prettyprint"><external-media-path name="<i>name</i>" path="<i>path</i>"
|
||||
* /></pre>
|
||||
* 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>
|
||||
* </li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* These child elements all use the same attributes:
|
||||
* <ul>
|
||||
* <li>
|
||||
* <code>name="<i>name</i>"</code>
|
||||
* <p>
|
||||
* 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.
|
||||
* </li>
|
||||
* <li>
|
||||
* <code>path="<i>path</i>"</code>
|
||||
* <p>
|
||||
* 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.
|
||||
* </li>
|
||||
* </ul>
|
||||
* <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>.
|
||||
* </pre>
|
||||
* To link this file to the FileProvider, pass it to super() in the constructor for the
|
||||
* FileProvider you defined above, 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="com.sample.MyFileProvider"
|
||||
* 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>
|
||||
* <p>
|
||||
* <b>Generating the Content URI for a File</b>
|
||||
* <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 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(), "my_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>.
|
||||
* <p>
|
||||
* <b>Granting Temporary Permissions to a URI</b>
|
||||
* <p>
|
||||
* To grant an access permission to a content URI returned from
|
||||
* {@link #getUriForFile(Context, String, File) getUriForFile()}, you can either grant the
|
||||
* permission to a specific package or include the permission in an intent, as shown in the
|
||||
* following sections.
|
||||
* <h4>Grant Permission to a Specific Package</h4>
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
* <h4>Include the Permission in an Intent</h4>
|
||||
* <p>
|
||||
* To allow the user to choose which app receives the intent, and the permission to access the
|
||||
* content, do the following:
|
||||
* </p>
|
||||
* <ol>
|
||||
* <li>
|
||||
* Put the content URI in an {@link Intent} by calling {@link Intent#setData(Uri) setData()}.
|
||||
* </li>
|
||||
* <li>
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
* <p>
|
||||
* To support devices that run a version between Android 4.1 (API level 16) and Android 5.1
|
||||
* (API level 22) inclusive, create a {@link ClipData} object from the content
|
||||
* URI, and set the access permissions on the <code>ClipData</code> object:
|
||||
* </p>
|
||||
* <pre class="prettyprint">
|
||||
* shareContentIntent.setClipData(ClipData.newRawUri("", contentUri));
|
||||
* shareContentIntent.addFlags(
|
||||
* Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
* </pre>
|
||||
* </li>
|
||||
* <li>
|
||||
* Send the {@link Intent} to
|
||||
* another app. Most often, you do this by calling
|
||||
* {@link Activity#setResult(int, Intent) setResult()}.
|
||||
* </li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* Permissions granted in an {@link Intent} remain in effect while the stack of the receiving
|
||||
* {@link Activity} is active. When the stack finishes, the permissions are
|
||||
* automatically removed. Permissions granted to one {@link Activity} in a client
|
||||
* app are automatically extended to other components of that app.
|
||||
* <p>
|
||||
* <b>Serving a Content URI to Another App</b>
|
||||
* <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 Activity#startActivityForResult(Intent, int, Bundle) startActivityResult()},
|
||||
* which sends an {@link Intent} to your app to start an {@link 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 Activity#setResult(int, Intent) setResult()}.
|
||||
* </p>
|
||||
* <p>
|
||||
* You can also put the content URI in a {@link 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 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>
|
||||
* <b>More Information</b>
|
||||
* <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 String DISPLAYNAME_FIELD = "displayName";
|
||||
|
||||
private static final File DEVICE_ROOT = new File("/");
|
||||
|
||||
@GuardedBy("sCache")
|
||||
private static final HashMap<String, PathStrategy> sCache = new HashMap<>();
|
||||
|
||||
// Do not use {@code mLocalPathStrategy} directly; access it via {@link #getLocalPathStrategy}.
|
||||
@GuardedBy("this")
|
||||
@Nullable private PathStrategy mLocalPathStrategy;
|
||||
|
||||
private int mResourceId;
|
||||
private String mAuthority;
|
||||
|
||||
public FileProvider() {
|
||||
mResourceId = 0;
|
||||
}
|
||||
|
||||
protected FileProvider(@XmlRes int resourceId) {
|
||||
mResourceId = resourceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@SuppressWarnings("StringSplitter")
|
||||
@Override
|
||||
public void attachInfo(@NonNull Context context, @NonNull ProviderInfo info) {
|
||||
super.attachInfo(context, info);
|
||||
|
||||
// Check our security attributes
|
||||
if (info.exported) {
|
||||
throw new SecurityException("Provider must not be exported");
|
||||
}
|
||||
if (!info.grantUriPermissions) {
|
||||
throw new SecurityException("Provider must grant uri permissions");
|
||||
}
|
||||
|
||||
mAuthority = info.authority.split(";")[0];
|
||||
synchronized (sCache) {
|
||||
sCache.remove(mAuthority);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, 0);
|
||||
return strategy.getUriForFile(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
* @param displayName The filename to be displayed. This can be used if the original filename
|
||||
* is undesirable.
|
||||
* @return A content URI for the file.
|
||||
* @throws IllegalArgumentException When the given {@link File} is outside
|
||||
* the paths supported by the provider.
|
||||
*/
|
||||
@SuppressLint("StreamFiles")
|
||||
@NonNull
|
||||
public static Uri getUriForFile(@NonNull Context context, @NonNull String authority,
|
||||
@NonNull File file, @NonNull String displayName) {
|
||||
Uri uri = getUriForFile(context, authority, file);
|
||||
return uri.buildUpon().appendQueryParameter(DISPLAYNAME_FIELD, displayName).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
@NonNull
|
||||
@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 = getLocalPathStrategy().getFileForUri(uri);
|
||||
String displayName = uri.getQueryParameter(DISPLAYNAME_FIELD);
|
||||
|
||||
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++] = (displayName == null) ? file.getName() : displayName;
|
||||
} 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>.
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public String getType(@NonNull Uri uri) {
|
||||
// ContentProvider has already checked granted permissions
|
||||
final File file = getLocalPathStrategy().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";
|
||||
}
|
||||
|
||||
/**
|
||||
* Unrestricted version of getType
|
||||
* called, when caller does not have corresponding permissions
|
||||
*/
|
||||
//@Override
|
||||
@SuppressWarnings("MissingOverride")
|
||||
@Nullable
|
||||
public String getTypeAnonymous(@NonNull Uri uri) {
|
||||
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, @NonNull 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, @NonNull 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 = getLocalPathStrategy().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 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.
|
||||
*/
|
||||
@SuppressLint("UnknownNullness") // b/171012356
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
|
||||
throws FileNotFoundException {
|
||||
// ContentProvider has already checked granted permissions
|
||||
final File file = getLocalPathStrategy().getFileForUri(uri);
|
||||
final int fileMode = modeToMode(mode);
|
||||
return ParcelFileDescriptor.open(file, fileMode);
|
||||
}
|
||||
|
||||
/** Return the local {@link PathStrategy}, creating it if necessary. */
|
||||
private PathStrategy getLocalPathStrategy() {
|
||||
synchronized (this) {
|
||||
if (mLocalPathStrategy == null) {
|
||||
mLocalPathStrategy = getPathStrategy(getContext(), mAuthority, mResourceId);
|
||||
}
|
||||
|
||||
return mLocalPathStrategy;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@link PathStrategy} for given authority, either by parsing or
|
||||
* returning from cache.
|
||||
*/
|
||||
private static PathStrategy getPathStrategy(Context context, String authority, int resourceId) {
|
||||
PathStrategy strat;
|
||||
synchronized (sCache) {
|
||||
strat = sCache.get(authority);
|
||||
if (strat == null) {
|
||||
try {
|
||||
strat = parsePathStrategy(context, authority, resourceId);
|
||||
} 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;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static XmlResourceParser getFileProviderPathsMetaData(Context context, String authority,
|
||||
@Nullable ProviderInfo info,
|
||||
int resourceId) {
|
||||
if (info == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Couldn't find meta-data for provider with authority " + authority);
|
||||
}
|
||||
|
||||
if (info.metaData == null && resourceId != 0) {
|
||||
info.metaData = new Bundle(1);
|
||||
info.metaData.putInt(META_DATA_FILE_PROVIDER_PATHS, resourceId);
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and return {@link PathStrategy} for given authority as defined in
|
||||
* {@link #META_DATA_FILE_PROVIDER_PATHS} {@code <meta-data>}.
|
||||
*
|
||||
* @see #getPathStrategy(Context, String, int)
|
||||
*/
|
||||
private static PathStrategy parsePathStrategy(Context context, String authority, int resourceId)
|
||||
throws IOException, XmlPullParserException {
|
||||
final SimplePathStrategy strat = new SimplePathStrategy(authority);
|
||||
|
||||
final ProviderInfo info = context.getPackageManager()
|
||||
.resolveContentProvider(authority, PackageManager.GET_META_DATA);
|
||||
final XmlResourceParser in = getFileProviderPathsMetaData(context, authority, info,
|
||||
resourceId);
|
||||
|
||||
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 = Api21Impl.getExternalMediaDirs(context);
|
||||
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 allowed list
|
||||
* 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<>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@RequiresApi(21)
|
||||
static class Api21Impl {
|
||||
private Api21Impl() {
|
||||
// This class is not instantiable.
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
static File[] getExternalMediaDirs(Context context) {
|
||||
// Deprecated, otherwise this would belong on context as a public method.
|
||||
return context.getExternalMediaDirs();
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--~ Copyright (c) 2022. ~ Microsoft Corporation. All rights reserved.-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/ic_fluent_alert_24_filled" android:state_activated="true"/>
|
||||
<item android:drawable="@drawable/ic_fluent_alert_24_filled" android:state_checked="true"/>
|
||||
<item android:drawable="@drawable/ic_fluent_alert_24_filled" android:state_selected="true"/>
|
||||
<item android:drawable="@drawable/ic_fluent_alert_urgent_24_filled" android:state_activated="true"/>
|
||||
<item android:drawable="@drawable/ic_fluent_alert_urgent_24_filled" android:state_checked="true"/>
|
||||
<item android:drawable="@drawable/ic_fluent_alert_urgent_24_filled" android:state_selected="true"/>
|
||||
<item android:drawable="@drawable/ic_fluent_alert_24_regular"/>
|
||||
</selector>
|
||||
|
||||
@@ -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:pathData="M10.429,19.924L13.991,18.412C14.124,19.292 13.653,20.185 12.796,20.549C11.939,20.913 10.969,20.632 10.429,19.924ZM15.861,2.094C17.947,3.233 19.513,4.697 20.543,6.481C21.573,8.265 22.058,10.353 22.001,12.729C21.992,13.143 21.648,13.471 21.234,13.461C20.82,13.451 20.492,13.108 20.502,12.694C20.552,10.582 20.13,8.766 19.244,7.231C18.358,5.696 16.995,4.423 15.142,3.41C14.779,3.212 14.645,2.756 14.844,2.392C15.042,2.029 15.498,1.895 15.861,2.094ZM6.711,6.515C9.573,5.241 12.916,6.446 14.311,9.261L14.409,9.47L15.697,12.362L17.395,13.832C17.488,13.912 17.568,14.006 17.661,14.157L17.72,14.271C17.999,14.902 17.714,15.641 17.083,15.92L6.756,20.49C6.597,20.561 6.424,20.597 6.25,20.597C5.56,20.597 5,20.038 5,19.347L4.999,17.005L3.757,14.213C2.443,11.263 3.766,7.827 6.711,6.515ZM15.624,5.695C16.591,6.222 17.366,6.989 17.94,7.984C18.514,8.979 18.791,10.033 18.764,11.134C18.754,11.549 18.41,11.876 17.996,11.866C17.582,11.856 17.255,11.512 17.265,11.098C17.285,10.276 17.079,9.493 16.641,8.734C16.203,7.975 15.627,7.405 14.906,7.012C14.542,6.813 14.408,6.358 14.606,5.994C14.804,5.63 15.26,5.496 15.624,5.695Z"
|
||||
android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M6.75 7.5C6.345 7.5 6 7.183 6 6.778V6.723C6 6.33 6.305 6.002 6.698 6H6.75C12.963 6 18 11.037 18 17.25v0.052C17.998 17.695 17.67 18 17.277 18h-0.055c-0.405 0-0.722-0.345-0.722-0.75 0-5.385-4.365-9.75-9.75-9.75z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
<path android:pathData="M13.294 18c0.38 0 0.701-0.287 0.705-0.666L14 17.25C14 13.246 10.754 10 6.75 10H6.666C6.287 10.006 6 10.328 6 10.707v0.09C6 11.195 6.351 11.5 6.75 11.5c3.176 0 5.75 2.574 5.75 5.75 0 0.399 0.305 0.75 0.704 0.75h0.09zM9 16.5C9 17.328 8.328 18 7.5 18S6 17.328 6 16.5 6.672 15 7.5 15 9 15.672 9 16.5z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
<path android:pathData="M6.25 3C4.455 3 3 4.455 3 6.25v11.5C3 19.545 4.455 21 6.25 21h11.5c1.795 0 3.25-1.455 3.25-3.25V6.25C21 4.455 19.545 3 17.75 3H6.25zM4.5 6.25c0-0.966 0.784-1.75 1.75-1.75h11.5c0.966 0 1.75 0.784 1.75 1.75v11.5c0 0.966-0.784 1.75-1.75 1.75H6.25c-0.966 0-1.75-0.784-1.75-1.75V6.25z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -6,79 +6,79 @@
|
||||
android:paddingEnd="4dp"
|
||||
android:paddingStart="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/more"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
<LinearLayout
|
||||
android:id="@+id/buttons"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="13dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:contentDescription="@string/more_options"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_fluent_more_vertical_20_filled"
|
||||
android:tint="?android:textColorSecondary" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/delete_notification"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginTop="13dp"
|
||||
android:layout_toStartOf="@id/more"
|
||||
android:visibility="gone"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:contentDescription="@string/sk_delete_notification"
|
||||
android:tooltipText="@string/sk_delete_notification"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_fluent_dismiss_20_filled"
|
||||
android:tint="?android:textColorSecondary" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/visibility"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginTop="13dp"
|
||||
android:layout_toStartOf="@id/delete_notification"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_visibility"
|
||||
android:tint="?android:textColorSecondary" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/collapse_btn"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginTop="13dp"
|
||||
android:layout_toStartOf="@id/visibility"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:visibility="gone"
|
||||
android:importantForAccessibility="noHideDescendants">
|
||||
|
||||
<!-- wrapping this button so the flip animation doesn't flip the background and the tooltip
|
||||
isn't displaced by the -1 scale -->
|
||||
android:layout_alignParentEnd="true">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/collapse_btn_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:importantForAccessibility="no"
|
||||
android:id="@+id/unread_indicator"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:visibility="gone"
|
||||
android:tint="?android:colorAccent"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_fluent_chevron_down_20_filled"
|
||||
android:src="@drawable/ic_fluent_circle_small_20_filled" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/collapse_btn"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:visibility="gone"
|
||||
android:importantForAccessibility="noHideDescendants">
|
||||
|
||||
<!-- wrapping this button so the flip animation doesn't flip the background and the tooltip
|
||||
isn't displaced by the -1 scale -->
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/collapse_btn_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:importantForAccessibility="no"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_fluent_chevron_down_20_filled"
|
||||
android:tint="?android:textColorSecondary" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/visibility"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_visibility"
|
||||
android:tint="?android:textColorSecondary" />
|
||||
|
||||
</FrameLayout>
|
||||
<ImageView
|
||||
android:id="@+id/delete_notification"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:visibility="gone"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:contentDescription="@string/sk_delete_notification"
|
||||
android:tooltipText="@string/sk_delete_notification"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_fluent_dismiss_20_filled"
|
||||
android:tint="?android:textColorSecondary" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/more"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:contentDescription="@string/more_options"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_fluent_more_vertical_20_filled"
|
||||
android:tint="?android:textColorSecondary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/unread_indicator"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginTop="13dp"
|
||||
android:layout_toStartOf="@id/collapse_btn"
|
||||
android:visibility="gone"
|
||||
android:tint="?android:colorAccent"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_fluent_circle_small_20_filled" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/avatar"
|
||||
@@ -94,7 +94,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_toStartOf="@id/unread_indicator"
|
||||
android:layout_toStartOf="@id/buttons"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_above="@+id/username_wrap">
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_toStartOf="@id/unread_indicator"
|
||||
android:layout_toStartOf="@id/buttons"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_alignBottom="@id/avatar"
|
||||
android:layoutDirection="locale"
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
<TextView android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="marquee"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="16sp"
|
||||
|
||||
@@ -39,4 +39,52 @@
|
||||
android:id="@+id/input"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/exclusive"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:minHeight="48dp"
|
||||
android:gravity="center_vertical"
|
||||
android:layoutDirection="locale">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:tint="?android:textColorPrimary"
|
||||
android:src="@drawable/ic_fluent_rss_24_regular"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:paddingVertical="8dp"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:text="@string/sk_list_exclusive_switch" />
|
||||
|
||||
<Switch
|
||||
android:id="@+id/exclusive_checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:duplicateParentState="true"
|
||||
android:focusable="false"
|
||||
android:clickable="false"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="14sp"
|
||||
android:text="@string/sk_list_exclusive_switch_explanation" />
|
||||
</LinearLayout>
|
||||
@@ -43,7 +43,8 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="40dp">
|
||||
android:layout_marginEnd="40dp"
|
||||
android:minWidth="40dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/m3_color" android:title="@string/sk_color_palette_material3"/>
|
||||
<item android:id="@+id/purple_color" android:title="@string/sk_color_palette_purple"/>
|
||||
<item android:id="@+id/pink_color" android:title="@string/sk_color_palette_pink"/>
|
||||
<item android:id="@+id/green_color" android:title="@string/sk_color_palette_green"/>
|
||||
<item android:id="@+id/blue_color" android:title="@string/sk_color_palette_blue"/>
|
||||
<item android:id="@+id/brown_color" android:title="@string/sk_color_palette_brown"/>
|
||||
<item android:id="@+id/yellow_color" android:title="@string/sk_color_palette_yellow"/>
|
||||
<item android:id="@+id/red_color" android:title="@string/sk_color_palette_red"/>
|
||||
<item android:id="@+id/nord_color" android:title="@string/mo_color_palette_nord"/>
|
||||
<group android:id="@+id/color_palettes_group" android:checkableBehavior="single">
|
||||
<item android:id="@+id/m3_color" android:title="@string/sk_color_palette_material3"/>
|
||||
<item android:id="@+id/purple_color" android:title="@string/sk_color_palette_purple"/>
|
||||
<item android:id="@+id/pink_color" android:title="@string/sk_color_palette_pink"/>
|
||||
<item android:id="@+id/green_color" android:title="@string/sk_color_palette_green"/>
|
||||
<item android:id="@+id/blue_color" android:title="@string/sk_color_palette_blue"/>
|
||||
<item android:id="@+id/brown_color" android:title="@string/sk_color_palette_brown"/>
|
||||
<item android:id="@+id/yellow_color" android:title="@string/sk_color_palette_yellow"/>
|
||||
<item android:id="@+id/red_color" android:title="@string/sk_color_palette_red"/>
|
||||
<item android:id="@+id/nord_color" android:title="@string/mo_color_palette_nord"/>
|
||||
</group>
|
||||
</menu>
|
||||
8
mastodon/src/main/res/menu/custom_local_timelines.xml
Normal file
8
mastodon/src/main/res/menu/custom_local_timelines.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/pin"
|
||||
android:title="@string/sk_pin_timeline"
|
||||
android:icon="@drawable/ic_fluent_pin_24_regular"
|
||||
android:showAsAction="always" />
|
||||
</menu>
|
||||
@@ -2,11 +2,11 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/auto_reveal_never"
|
||||
android:title="@string/sk_settings_auto_reveal_never" />
|
||||
android:title="@string/sk_settings_auto_reveal_nobody" />
|
||||
<item
|
||||
android:id="@+id/auto_reveal_threads"
|
||||
android:title="@string/sk_settings_auto_reveal_threads" />
|
||||
android:title="@string/sk_settings_auto_reveal_author" />
|
||||
<item
|
||||
android:id="@+id/auto_reveal_discussions"
|
||||
android:title="@string/sk_settings_auto_reveal_discussions" />
|
||||
android:title="@string/sk_settings_auto_reveal_anyone" />
|
||||
</menu>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/prefix_replies_never" android:title="@string/sk_settings_prefix_replies_never" />
|
||||
<item android:id="@+id/prefix_replies_to_others" android:title="@string/sk_settings_prefix_replies_to_others" />
|
||||
<item android:id="@+id/prefix_replies_always" android:title="@string/sk_settings_prefix_replies_always" />
|
||||
</menu>
|
||||
@@ -1,17 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="sk_confirm_delete_and_redraft">هل أنت متأكد أنك تريد حذف وإعادة صياغة هذا المنشور؟</string>
|
||||
<string name="sk_visibility_unlisted">غير مدرج</string>
|
||||
<string name="sk_list_timelines">القوائم</string>
|
||||
<string name="sk_follow_requests">طلبات المتابعة</string>
|
||||
<string name="sk_pinned_posts">مدبّس</string>
|
||||
<string name="sk_delete_and_redraft">حذف وإعادة الصياغة</string>
|
||||
<string name="sk_confirm_delete_and_redraft_title">حذف وإعادة صياغة الرسالة</string>
|
||||
<string name="sk_pin_post">تدبيس على الصفحة الشخصية</string>
|
||||
<string name="sk_confirm_pin_post_title">تدبيس الرسالة على الصفحة الشخصية</string>
|
||||
<string name="sk_settings_show_federated_timeline">إظهار الخيط الفديرالي</string>
|
||||
<string name="sk_settings_contribute">المساهمة في Megalodon</string>
|
||||
<string name="sk_accept_follow_request">قبول طلب المتابعة</string>
|
||||
<string name="sk_reject_follow_request">رفض طلب المتابعة</string>
|
||||
<string name="sk_lists_with_user">قوائم بها %s</string>
|
||||
</resources>
|
||||
<resources></resources>
|
||||
@@ -1,3 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
</resources>
|
||||
<resources></resources>
|
||||
@@ -2,7 +2,33 @@
|
||||
<resources>
|
||||
<string name="sk_confirm_delete_and_redraft">هل أنت متأكد أنك تريد حذف وإعادة صياغة هذا المنشور؟</string>
|
||||
<string name="sk_pin_post">تثبيت في الملف الشخصي</string>
|
||||
<string name="sk_pinned_posts">مثبت</string>
|
||||
<string name="sk_pinned_posts">المُثَبَّتَة</string>
|
||||
<string name="sk_delete_and_redraft">حذف وإعادة صياغة</string>
|
||||
<string name="sk_confirm_delete_and_redraft_title">حذف وإعادة صياغة المنشور</string>
|
||||
<string name="sk_visibility_unlisted">غير مدرج</string>
|
||||
<string name="sk_list_timelines">القوائم</string>
|
||||
<string name="sk_follow_requests">طلبات المتابعة</string>
|
||||
<string name="sk_confirm_pin_post_title">تدبيس الرسالة على الصفحة الشخصية</string>
|
||||
<string name="sk_settings_show_federated_timeline">إظهار الخيط الفديرالي</string>
|
||||
<string name="sk_settings_contribute">المساهمة في Megalodon</string>
|
||||
<string name="sk_accept_follow_request">قبول طلب المتابعة</string>
|
||||
<string name="sk_reject_follow_request">رفض طلب المتابعة</string>
|
||||
<string name="sk_lists_with_user">قوائم بها %s</string>
|
||||
<string name="sk_confirm_unpin_post">أتريد فك المنشور من ملفك الشخصي؟</string>
|
||||
<string name="sk_confirm_pin_post">أتريد تثبيت هذا المنشور في ملفك الشخصي؟</string>
|
||||
<string name="sk_pinning">يثبت المنشور…</string>
|
||||
<string name="sk_unpin_post">فك من ملفك الشخصي</string>
|
||||
<string name="sk_confirm_unpin_post_title">فك المنشور من ملفك الشخصي</string>
|
||||
<string name="sk_unpinning">يفك المنشور…</string>
|
||||
<string name="sk_image_description">وصف الصورة</string>
|
||||
<string name="sk_settings_show_replies">أظهر الردود</string>
|
||||
<string name="sk_quoting_user">اقتباس %s</string>
|
||||
<string name="sk_settings_reply_visibility_all">كل الردود</string>
|
||||
<string name="sk_settings_reply_visibility_following">ردود على متابِعي</string>
|
||||
<string name="sk_settings_reply_visibility_self">ردود عليَّ</string>
|
||||
<string name="sk_settings_load_new_posts">تحميل المنشورات الجديدة تلقائيًا</string>
|
||||
<string name="sk_user_post_notifications_on">شغِّل إشعارات النشر لـ %s</string>
|
||||
<string name="sk_user_post_notifications_off">عطل إشعارات النشر لـ %s</string>
|
||||
<string name="sk_check_for_update">تحقق من وجود تحديثات</string>
|
||||
<string name="sk_no_update_available">لا يتوفر تحديث</string>
|
||||
</resources>
|
||||
@@ -36,15 +36,43 @@
|
||||
<string name="mo_duration_indefinite">Unbegrenzt</string>
|
||||
<string name="mo_duration_minutes_5">5 Minuten</string>
|
||||
<string name="mo_duration_minutes_30">30 Minuten</string>
|
||||
<string name="mo_change_default_reply_visibility_to_unlisted">Standard Antwortsichtbarkeit auf nicht aufgelistet ändern</string>
|
||||
<string name="mo_change_default_reply_visibility_to_unlisted">Standard-Sichtbarkeit ‚Ungelistet‘ für Antworten</string>
|
||||
<string name="mo_duration_days_7">7 Tage</string>
|
||||
<string name="mo_composer_behavior">Verhalten des Verfassers</string>
|
||||
<string name="mo_miscellaneous_settings">Sonstige Einstellungen</string>
|
||||
<string name="mo_share_open_url">In der App öffnen</string>
|
||||
<string name="mo_disable_double_tap_to_swipe_between_tabs">Deaktiviere doppeltes Tippen, um zwischen Tabs zu wechseln</string>
|
||||
<string name="mo_swap_bookmark_with_reblog">Verwenden der Reblog-Aktion anstelle der Lesezeichen-Aktion bei Benachrichtigungen</string>
|
||||
<string name="mo_swap_bookmark_with_reblog">Teilen statt Lesezeichen-Aktion</string>
|
||||
<string name="mo_download_latest_nightly_release">Neueste Nightly Version herunterladen</string>
|
||||
<string name="mo_load_remote_followers">Follower und Follower des Remote Profils laden</string>
|
||||
<string name="mo_confirm_unfollow">Bestätige %s zu entfolgen</string>
|
||||
<string name="mo_confirm_unfollow_title">Entfolge Account</string>
|
||||
<string name="mo_instance_registration">Registrierung</string>
|
||||
<string name="mo_instance_registration_open">Offen</string>
|
||||
<string name="mo_instance_registration_approval">Genehmigung erforderlich</string>
|
||||
<string name="mo_instance_info_open_timeline">Lokale Timeline</string>
|
||||
<string name="mo_severity_silence">Stummgeschalten</string>
|
||||
<string name="mo_severity_suspend">Gesperrt</string>
|
||||
<string name="mo_instance_admin">Verwaltet von</string>
|
||||
<string name="mo_instance_contact">Kontakt</string>
|
||||
<string name="mo_instance_users">Benutzer*innen</string>
|
||||
<string name="mo_instance_status">Status</string>
|
||||
<string name="mo_instance_info_moderated_servers">Moderierte Server</string>
|
||||
<string name="mo_open_camera">Foto aufnehmen</string>
|
||||
<string name="mo_notification_audience_settings">Publikum für Benachrichtigungen</string>
|
||||
<string name="mo_mention_reblogger_automatically">Beim Antworten die Person erwähnen, die den Beitrag geteilt hat</string>
|
||||
<string name="mo_swap_bookmark_with_reblog_summary">Direkt in der Benachrichtigung Lesezeichen setzen oder Beiträge teilen</string>
|
||||
<string name="mo_setting_remote_follower_summary">Follower*innen von anderen Instanzen anzeigen</string>
|
||||
<string name="mo_setting_relocate_publish_summary">Veröffentlichen-Button in die untere Leiste verschieben</string>
|
||||
<string name="mo_setting_default_reply_privacy_summary">Antworten scheinen dadurch nicht in öffentlichen Timelines und Hashtags auf</string>
|
||||
<string name="mo_setting_interaction_count_summary">Zeigt in der Timeline an, wie viele Personen mit einem Post interagiert haben</string>
|
||||
<string name="mo_setting_disable_swipe_summary">Wischen, um die angezeigte Timeline zu wechseln</string>
|
||||
<string name="mo_enable_dividers">Trennlinien anzeigen</string>
|
||||
<string name="mo_notification_management_settings">Benachrichtigungen verwalten</string>
|
||||
<string name="mo_double_tap_to_swipe_between_tabs">Doppelt tippen, um zwischen Tabs zu wechseln</string>
|
||||
<string name="mo_setting_true_black_summary">Kann bei AMOLED-Bildschirmen Strom sparen</string>
|
||||
<string name="mo_setting_marquee_summary">Deaktiviert das Scrollen von Titeln als Laufschrift</string>
|
||||
<string name="mo_setting_uniform_summary">App-Symbol für alle Benachrichtigungen verwenden</string>
|
||||
<string name="mo_setting_reduced_motion_summary">Animationen für Interaktionen deaktivieren</string>
|
||||
<string name="mo_setting_play_gif_summary">GIFs in Profilbildern und Emojis automatisch abspielen</string>
|
||||
</resources>
|
||||
@@ -38,7 +38,7 @@
|
||||
<string name="sk_disable_marquee">Laufschrift in Titelleisten deaktivieren</string>
|
||||
<string name="sk_settings_contribute">Zu Megalodon beitragen</string>
|
||||
<string name="sk_settings_show_federated_timeline">Föderierte Timeline anzeigen</string>
|
||||
<string name="sk_notify_posts">Beitrags-Benachrichtigungen</string>
|
||||
<string name="sk_notify_posts">Beitrags-Benachrichtigungen</string>
|
||||
<string name="sk_settings_color_palette">Farbschema</string>
|
||||
<string name="sk_color_palette_pink">Pink</string>
|
||||
<string name="sk_color_palette_purple">Violett</string>
|
||||
@@ -101,7 +101,7 @@
|
||||
<string name="sk_already_reblogged">Bereits geteilt</string>
|
||||
<string name="sk_reply_as">Antworten mit anderem Konto</string>
|
||||
<string name="sk_settings_uniform_icon_for_notifications">Einheitliches Icon für alle Benachrichtigungen</string>
|
||||
<string name="sk_forward_report_to">Weiterleiten zu %s</string>
|
||||
<string name="sk_forward_report_to">Weiterleiten an %s</string>
|
||||
<string name="sk_unsent_posts">Nicht gesendete Beiträge</string>
|
||||
<string name="sk_draft">Entwurf</string>
|
||||
<string name="sk_schedule">Planen</string>
|
||||
@@ -250,7 +250,7 @@
|
||||
<string name="sk_settings_see_new_posts_button">“Neue Beiträge anzeigen”-Button</string>
|
||||
<string name="sk_settings_server_version">Server-Version: %s</string>
|
||||
<string name="sk_notify_poll_results">Umfrage-Ergebnisse</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">CWs beim Antworten “re:” voranstellen</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Voranstellen von „re:“ an CWs in Antworten an</string>
|
||||
<string name="sk_filtered">Gefiltert: %s</string>
|
||||
<string name="sk_expand">Erweitern</string>
|
||||
<string name="sk_collapse">Einklappen</string>
|
||||
@@ -276,7 +276,7 @@
|
||||
<string name="sk_reacted_with">hat mit %s reagiert</string>
|
||||
<string name="sk_external_share_title">Mit Konto teilen</string>
|
||||
<string name="sk_external_share_or_open_title">Mit Konto teilen oder öffnen</string>
|
||||
<string name="sk_timeline_bubble">Bubble</string>
|
||||
<string name="sk_timeline_bubble">Blase</string>
|
||||
<string name="sk_settings_default_content_type_explanation">Vorausgewählter Inhaltstyp für neue Beiträge – überschreibt den Wert, der unter „Einstellungen für Beiträge“ gesetzt ist.</string>
|
||||
<string name="sk_open_in_app_failed">Konnte nicht in der App öffnen</string>
|
||||
<string name="sk_content_type_unspecified">Nicht angegeben</string>
|
||||
@@ -296,4 +296,13 @@
|
||||
<string name="sk_no_remote_info_hint">keine Remote-Infos abrufbar</string>
|
||||
<string name="sk_error_loading_profile">Konnte das Profil via %s nicht laden</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Für vollständigere Auflistung von Follower*innen, Likes und Boosts können die Informationen von der Ursprungs-Instanz geladen werden.</string>
|
||||
<string name="sk_settings_auto_reveal_equal_spoilers">Zeigen von gleichen CWs in Antworten von</string>
|
||||
<string name="sk_settings_auto_reveal_nobody">niemandem</string>
|
||||
<string name="sk_settings_auto_reveal_author">Autor*in</string>
|
||||
<string name="sk_settings_auto_reveal_anyone">allen</string>
|
||||
<string name="sk_settings_prefix_replies_to_others">andere</string>
|
||||
<string name="sk_settings_prefix_replies_always">alle</string>
|
||||
<string name="sk_settings_prefix_replies_never">niemanden</string>
|
||||
<string name="sk_settings_forward_report_default">Standardwert „Meldung weiterleiten“-Schalter</string>
|
||||
<string name="sk_settings_show_new_posts_button">\"Neue Beiträge anzeigen\" Schaltfläche</string>
|
||||
</resources>
|
||||
@@ -69,4 +69,5 @@
|
||||
<string name="mo_enable_dividers">Mostrar divisores entre publicaciones</string>
|
||||
<string name="mo_setting_relocate_publish_summary">Mueve el botón de publicación a la barra inferior</string>
|
||||
<string name="mo_swap_bookmark_with_reblog_summary">Añade un marcador o impulsa publicaciones desde la notificación</string>
|
||||
<string name="mo_open_camera">Hacer foto</string>
|
||||
</resources>
|
||||
@@ -256,7 +256,7 @@
|
||||
<string name="sk_settings_collapse_long_posts">Minimizar publicaciones largas</string>
|
||||
<string name="sk_unfinished_attachments">¿Corregir adjuntos\?</string>
|
||||
<string name="sk_unfinished_attachments_message">Algunos adjuntos no han terminado de subirse.</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Añadir \"re:\" a respuestas a Advertencias de Contenido</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Añadir \"re:\" en respuestas a Advertencias de Contenido de</string>
|
||||
<string name="sk_spectator_mode">Modo espectador</string>
|
||||
<string name="sk_settings_hide_interaction">Ocultar los botones de interacción</string>
|
||||
<string name="sk_follow_as">Seguir desde otra cuenta</string>
|
||||
@@ -292,4 +292,22 @@
|
||||
<string name="sk_external_share_or_open_title">Compartir o abrir con una cuenta</string>
|
||||
<string name="sk_open_in_app">Abrir en la app</string>
|
||||
<string name="sk_external_share_title">Compartir con una cuenta</string>
|
||||
<string name="sk_error_loading_profile">Fallo al cargar el perfil con %s</string>
|
||||
<string name="sk_settings_allow_remote_loading">Cargar información de instancias remotas</string>
|
||||
<string name="sk_settings_auto_reveal_equal_spoilers">Mostrar Avisos de Contenidos iguales respuestas de</string>
|
||||
<string name="sk_settings_auto_reveal_never">Nunca</string>
|
||||
<string name="sk_settings_auto_reveal_threads">Mismo autor</string>
|
||||
<string name="sk_settings_auto_reveal_discussions">Conversaciones</string>
|
||||
<string name="sk_settings_auto_reveal_always">Siempre</string>
|
||||
<string name="sk_open_in_app_failed">No se pudo abrir en la app</string>
|
||||
<string name="sk_no_remote_info_hint">información remota no disponible</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Intenta buscar una lista de seguidores, me gusta e impulsos más precisa cargando la información de la instancia de origen.</string>
|
||||
<string name="sk_settings_show_new_posts_button">Botón \"Ver nuevas publicaciones\"</string>
|
||||
<string name="sk_settings_auto_reveal_nobody">nadie</string>
|
||||
<string name="sk_settings_auto_reveal_author">autor</string>
|
||||
<string name="sk_settings_auto_reveal_anyone">todos</string>
|
||||
<string name="sk_settings_prefix_replies_never">nadie</string>
|
||||
<string name="sk_settings_prefix_replies_to_others">otros</string>
|
||||
<string name="sk_settings_forward_report_default">\"Reenviar denuncia\" por defecto</string>
|
||||
<string name="sk_settings_prefix_replies_always">todos</string>
|
||||
</resources>
|
||||
@@ -41,9 +41,9 @@
|
||||
<string name="sk_already_bookmarked">Dagoeneko laster-marka da</string>
|
||||
<string name="sk_favorite_as">Beste kontu baten gogokoa</string>
|
||||
<string name="sk_favorited_as">%s bezela dago gogokoetan</string>
|
||||
<string name="sk_reblog_as">Beste kontu batekin bultzatua</string>
|
||||
<string name="sk_reblog_as">Bultzatu beste kontu batekin</string>
|
||||
<string name="sk_reblogged_as">%s bezela bultzatua</string>
|
||||
<string name="sk_already_reblogged">Dagoeneko bultzatua izan da</string>
|
||||
<string name="sk_already_reblogged">Jada bultzatu da</string>
|
||||
<string name="sk_reply_as">Erantzun beste kontu batekin</string>
|
||||
<string name="sk_translate_post">Itzuli</string>
|
||||
<string name="sk_tabs_disable_swipe">Desgaitu fitxen arteko joan-etorria</string>
|
||||
@@ -75,9 +75,9 @@
|
||||
<string name="sk_clear_all_notifications_confirm_action">Ezabatu denak</string>
|
||||
<string name="sk_clear_all_notifications_confirm">Ziur al zaude jakinarazpen guztiak ezabatu nahi dituzula\?</string>
|
||||
<string name="sk_loading_fediverse_resource_title">Fedibertsoan bilatzen</string>
|
||||
<string name="sk_undo_reblog">Bultzada desegin</string>
|
||||
<string name="sk_undo_reblog">Desegin bultzada</string>
|
||||
<string name="sk_quote_post">Honen inguruan argitaratu</string>
|
||||
<string name="sk_reblog_with_visibility">Bultzada ikusgarria</string>
|
||||
<string name="sk_reblog_with_visibility">Bultzatu ikusgarritasunarekin</string>
|
||||
<string name="sk_hashtags_you_follow">Jarraitzen dituzun Hashtag-ak</string>
|
||||
<string name="sk_copy_link_to_post">Kopiatu link-a argitaratzeko</string>
|
||||
<string name="sk_loading_resource_on_instance_title">%s-n bilatzen</string>
|
||||
@@ -219,7 +219,7 @@
|
||||
<string name="sk_alt_button">ALT</string>
|
||||
<string name="sk_post_edited">Editatua</string>
|
||||
<string name="sk_notification_type_update">Editatutako argitalpenak</string>
|
||||
<string name="sk_notify_update">Bultzatutako bidalketa editatu</string>
|
||||
<string name="sk_notify_update">Bultzatutako bidalketa editatu da</string>
|
||||
<string name="sk_no_results">Emaitzarik ez</string>
|
||||
<string name="sk_save_draft">Zirriborroa gorde\?</string>
|
||||
<string name="sk_no_alt_text">Ez dago testu alternatiborik eskuragarri</string>
|
||||
@@ -232,7 +232,7 @@
|
||||
<string name="sk_alt_text_missing_title">Testu alternatiboa falta da</string>
|
||||
<string name="sk_searching">Bilatzen…</string>
|
||||
<string name="sk_save_draft_message">Zirriborro honetako aldaketak gorde edo argitaratu nahi dituzu\?</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Gehitu \"re:\" hasieran edukiaren abisuen erantzunetan</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Gehitu \"re:\" edukiaren abisuen erantzunen hasieran</string>
|
||||
<string name="sk_filtered">Iragazita: %s</string>
|
||||
<string name="sk_expand">Zabaldu</string>
|
||||
<string name="sk_collapse">Itxi</string>
|
||||
@@ -267,8 +267,23 @@
|
||||
<string name="sk_settings_reply_visibility_following">Jarraitzen ditudanei eginiko erantzunak</string>
|
||||
<string name="sk_reply_line_above_avatar">\"Honi erantzunez\" abatarraren gaineko lerroa</string>
|
||||
<string name="sk_show_thread">Erakutsi haria</string>
|
||||
<string name="sk_compact_reblog_reply_line">Bultzada/erantzun lerro trinkoa</string>
|
||||
<string name="sk_compact_reblog_reply_line">Bultzada-/erantzun-lerro trinkoa</string>
|
||||
<string name="sk_quoting_user">%s aipatzen</string>
|
||||
<string name="sk_settings_reply_visibility_self">Niri eginiko erantzunak</string>
|
||||
<string name="sk_notification_action_replied">%s-(r)i erantzun</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Hauexek dira zure instantziako administratzaileek saretik aukeratutako azken bidalketak.</string>
|
||||
<string name="sk_content_type">Edukiaren mota</string>
|
||||
<string name="sk_content_type_unspecified">Zehaztu gabea</string>
|
||||
<string name="sk_content_type_plain">Testu arrunta</string>
|
||||
<string name="sk_content_type_html">HTML</string>
|
||||
<string name="sk_content_type_markdown">Markdown</string>
|
||||
<string name="sk_content_type_bbcode">BBCode</string>
|
||||
<string name="sk_content_type_mfm">MFM</string>
|
||||
<string name="sk_settings_content_types">Gaitu bidalketaren formateatzea</string>
|
||||
<string name="sk_settings_content_types_explanation">Markdown bezalako eduki-mota ezartzea ahalbidetzen du bidalketa bat sortzerakoan. Gogoan izan instantzia guztiek ez dutela hau baimentzen.</string>
|
||||
<string name="sk_settings_default_content_type">Eduki-mota lehenetsia</string>
|
||||
<string name="sk_settings_confirm_before_reblog">Berretsi bultzatu aurretik</string>
|
||||
<string name="sk_reacted_with">%s bezala erreakzionatuta</string>
|
||||
<string name="sk_reacted">erreakzionatuta</string>
|
||||
<string name="sk_timeline_bubble">Burbuila</string>
|
||||
</resources>
|
||||
@@ -12,51 +12,85 @@
|
||||
<string name="in_reply_to">در پاسخ به %s</string>
|
||||
<string name="notifications">اعلانها</string>
|
||||
<string name="user_followed_you">شما را دنبال میکند</string>
|
||||
<string name="user_sent_follow_request">یک درخواست دنبال کردن برای شما ارسال کرد</string>
|
||||
<string name="user_sent_follow_request">یک درخواست پیگیری برای شما ارسال کرد</string>
|
||||
<string name="user_favorited"> فرستهتان را پسندید</string>
|
||||
<string name="notification_boosted">فرستهٔ شما را تقویت کرد</string>
|
||||
<string name="poll_ended">نظرسنجی به پایان رسید</string>
|
||||
<string name="time_seconds">%dثانیه</string>
|
||||
<string name="time_minutes">%dدقیقه</string>
|
||||
<string name="time_hours">%dساعت</string>
|
||||
<string name="time_days">%dروز</string>
|
||||
<string name="share_toot_title">اشتراکگذاری</string>
|
||||
<string name="share_toot_title">همرسانی</string>
|
||||
<string name="settings">تنظیمات</string>
|
||||
<string name="publish">انتشار</string>
|
||||
<string name="discard_draft">پیشنویس کنار گذاشته شود؟</string>
|
||||
<string name="discard">صرفنظر کردن</string>
|
||||
<string name="cancel">لغو</string>
|
||||
<string name="posts">پستها</string>
|
||||
<string name="posts_and_replies">پستها و پاسخها</string>
|
||||
<plurals name="followers">
|
||||
<item quantity="one">پیگیرنده</item>
|
||||
<item quantity="other">پیگیرندگان</item>
|
||||
</plurals>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">فرسته</item>
|
||||
<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_follow">پیگیری</string>
|
||||
<string name="edit_profile">ویرایش نمایه</string>
|
||||
<string name="share_user">اشتراکگذاری %s</string>
|
||||
<string name="mute_user">بیصدا %s</string>
|
||||
<string name="unmute_user">ناخموشی %s</string>
|
||||
<string name="block_user">مسدود %s</string>
|
||||
<string name="unblock_user">رفع مسدودیت %s</string>
|
||||
<string name="report_user">گزارش کردن %s</string>
|
||||
<string name="profile_joined">عضو شد</string>
|
||||
<string name="done">انجام شد</string>
|
||||
<string name="loading">درحال بارگذاری…</string>
|
||||
<string name="field_label">برچسب</string>
|
||||
<string name="field_content">محتوا</string>
|
||||
<string name="saving">درحال ذخیرهسازی…</string>
|
||||
<string name="poll_closed">پایانیافته</string>
|
||||
<string name="confirm_mute_title">خموشی حساب</string>
|
||||
<string name="do_mute">بیصدا</string>
|
||||
<string name="confirm_unmute_title">لغو خموشی حساب</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_domain_title">رفع مسدودیت دامنهٔ</string>
|
||||
<string name="do_unblock">رفع مسدودیت</string>
|
||||
<string name="button_muted">خموش</string>
|
||||
<string name="button_blocked">مسدود شده</string>
|
||||
<string name="action_vote">رأی</string>
|
||||
<string name="delete">حذف</string>
|
||||
<string name="confirm_delete_title">حذف پست</string>
|
||||
<string name="confirm_delete_title">حذف فرسته</string>
|
||||
<string name="play">پخش</string>
|
||||
<string name="pause">توقف</string>
|
||||
<string name="log_out">خروج</string>
|
||||
<string name="add_account">افزودن حساب</string>
|
||||
<string name="search_hint">جستجو</string>
|
||||
<string name="hashtags">هشتگها</string>
|
||||
<string name="hashtags">برچسبها</string>
|
||||
<string name="news">اخبار</string>
|
||||
<string name="for_you">برای شما</string>
|
||||
<string name="all_notifications">همه</string>
|
||||
<string name="report_title">گزارش کردن %s</string>
|
||||
<string name="report_reason_personal">من این را دوست ندارم</string>
|
||||
<string name="report_reason_spam">این هرزنامه است</string>
|
||||
<string name="sending_report">درحال ارسال گزارش…</string>
|
||||
<string name="unfollow">پینگرفتن</string>
|
||||
<string name="back">بازگشت</string>
|
||||
<string name="instance_rules_title">قوانین کارساز</string>
|
||||
<string name="signup_title">ایجاد حساب</string>
|
||||
<string name="edit_photo">ویرایش</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="category_activism">فعالیت</string>
|
||||
<string name="category_all">همه</string>
|
||||
<string name="category_art">هنر</string>
|
||||
@@ -68,34 +102,118 @@
|
||||
<string name="category_tech">فناوری</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="resend">ارسال دوباره</string>
|
||||
<string name="retry_upload">بارگذاری مجدد</string>
|
||||
<string name="edit_image">ویرایش تصویر</string>
|
||||
<string name="save">ذخیره</string>
|
||||
<string name="visibility_public">عمومی</string>
|
||||
<string name="visibility_followers_only">فقط پیگیرندگان</string>
|
||||
<string name="search_all">همه</string>
|
||||
<string name="search_people">افراد</string>
|
||||
<string name="skip">بعدی</string>
|
||||
<string name="notification_type_favorite">علاقهمندیها</string>
|
||||
<string name="notification_type_reblog">تقویتها</string>
|
||||
<string name="notification_type_poll">نظرسنجیها</string>
|
||||
<string name="choose_account">انتخاب حساب</string>
|
||||
<string name="theme_auto">خودکار</string>
|
||||
<string name="theme_light">روشن</string>
|
||||
<string name="theme_dark">تاریک</string>
|
||||
<string name="new_post">پست جدید</string>
|
||||
<string name="new_post">فرسته جدید</string>
|
||||
<string name="button_reply">پاسخ</string>
|
||||
<string name="button_reblog">تقویت</string>
|
||||
<string name="button_favorite">برگزیده</string>
|
||||
<string name="button_share">اشتراکگذاری</string>
|
||||
<string name="add_media">افزودن رسانه</string>
|
||||
<string name="add_poll">افزودن نظرسنجی</string>
|
||||
<string name="emoji">ایموجی</string>
|
||||
<string name="my_profile">نمایه من</string>
|
||||
<string name="follow_user">پیگیری %s</string>
|
||||
<string name="unfollowed_user">پینگرفتن %s</string>
|
||||
<string name="open_in_browser">بازکردن در مرورگر</string>
|
||||
<string name="clear">پاککردن</string>
|
||||
<string name="download">دانلود</string>
|
||||
<string name="download">بارگیری</string>
|
||||
<string name="open_settings">باز کردن تنظیمات</string>
|
||||
<string name="file_saved">پرونده ذخیره شد</string>
|
||||
<string name="downloading">درحال بارگیری…</string>
|
||||
<string name="local_timeline">اجتماع</string>
|
||||
<string name="follows_you">پیگیرتان است</string>
|
||||
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
||||
<plurals name="x_favorites">
|
||||
<item quantity="one">%,d پسندیده</item>
|
||||
<item quantity="other">%,d پسندیدهها</item>
|
||||
</plurals>
|
||||
<string name="time_now">اکنون</string>
|
||||
<string name="post_info_reblogs">تقویتها</string>
|
||||
<string name="post_info_favorites">علاقهمندیها</string>
|
||||
<string name="last_edit_at_x">آخرین ویرایش %s</string>
|
||||
<string name="time_just_now">همين الان</string>
|
||||
<plurals name="x_seconds_ago">
|
||||
<item quantity="one">%d ثانیه پیش</item>
|
||||
<item quantity="other">%d ثانیه پیش</item>
|
||||
</plurals>
|
||||
<plurals name="x_minutes_ago">
|
||||
<item quantity="one">%d دقیقه پیش</item>
|
||||
<item quantity="other">%d دقیقه پیش</item>
|
||||
</plurals>
|
||||
<string name="edit_original_post">فرستهٔ اصلی</string>
|
||||
<string name="edit_text_edited">متن ویرایش شد</string>
|
||||
<string name="edit_spoiler_added">هشدار محتوا افزوده شد</string>
|
||||
<string name="edit_spoiler_edited">هشدار محتوا ویرایش شد</string>
|
||||
<string name="edit_spoiler_removed">هشدار محتوا برداشته شد</string>
|
||||
<string name="edit_poll_added">نظرسنجی اضافه شد</string>
|
||||
<string name="edit_poll_edited">نظرسنجی ویرایش شد</string>
|
||||
<string name="edit_poll_removed">نظرسنجی برداشته شد</string>
|
||||
<string name="edit_media_added">رسانه اضافه شد</string>
|
||||
<string name="edit_media_removed">رسانه برداشته شد</string>
|
||||
<string name="edit_multiple_changed">فرسته ویرایش شد</string>
|
||||
<string name="edit">ویرایش</string>
|
||||
<string name="upload_failed">بارگذاری ناموفق بود</string>
|
||||
<string name="file_size_bytes">%d بایت</string>
|
||||
<string name="file_size_kb">%.2f کیلوبایت</string>
|
||||
<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 -->
|
||||
<string name="update_available">ماستودون برای اندروید %s آماده بارگیری است.</string>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<string name="update_ready">ماستودون برای اندروید %s بارگیری شده و آماده نصب است.</string>
|
||||
<!-- %s is file size -->
|
||||
<string name="download_update">بارگیری (%s)</string>
|
||||
<string name="install_update">نصب</string>
|
||||
<string name="privacy_policy_title">حریم خصوصی شما</string>
|
||||
<string name="i_agree">موافقم</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="server_filter_any_language">هر زبانی</string>
|
||||
<string name="server_filter_region_europe">اروپا</string>
|
||||
<string name="server_filter_region_north_america">آمریکای شمالی</string>
|
||||
<string name="server_filter_region_south_america">آمریکای جنوبی</string>
|
||||
<string name="server_filter_region_africa">آفریقا</string>
|
||||
<string name="server_filter_region_asia">آسیا</string>
|
||||
<string name="server_filter_region_oceania">اقیانوسیه</string>
|
||||
<string name="profile_add_row">افزودن سطر</string>
|
||||
<string name="profile_setup">تنظیم نمایه</string>
|
||||
<string name="popular_on_mastodon">محبوب در ماستودون</string>
|
||||
<string name="follow_all">پیگیری همه</string>
|
||||
<!-- %s is server domain -->
|
||||
<string name="profile_bio">دربارهٔ شما</string>
|
||||
<!-- Shown in a progress dialog when you tap "follow all" -->
|
||||
<!-- %1$s is server domain, %2$s is email domain. You can reorder these placeholders to fit your language better. -->
|
||||
<string name="spoiler_show">به هرصورت نشان داده شود</string>
|
||||
<string name="spoiler_hide">نهفتن دوباره</string>
|
||||
<string name="poll_multiple_choice">یک یا چند مورد را انتخاب کنید</string>
|
||||
<string name="save_changes">ذخیرهٔ تغییرات</string>
|
||||
<string name="profile_timeline">خط زمانی</string>
|
||||
<string name="view_all">مشاهده همه</string>
|
||||
<string name="profile_endorsed_accounts">حسابها</string>
|
||||
<string name="verified_link">پیوند تأییدشده</string>
|
||||
<string name="show">نمایش</string>
|
||||
<string name="hide">نهفتن</string>
|
||||
<string name="join_default_server">%s پیوست</string>
|
||||
<string name="pick_server">انتخاب کارسازی دیگر</string>
|
||||
<string name="signup_or_login">یا</string>
|
||||
<string name="learn_more">بیشتر بیاموزید</string>
|
||||
<string name="welcome_to_mastodon">به ماستودون خوش آمدید</string>
|
||||
</resources>
|
||||
|
||||
2
mastodon/src/main/res/values-fa/strings_mo.xml
Normal file
2
mastodon/src/main/res/values-fa/strings_mo.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
169
mastodon/src/main/res/values-fa/strings_sk.xml
Normal file
169
mastodon/src/main/res/values-fa/strings_sk.xml
Normal file
@@ -0,0 +1,169 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="sk_pinned_posts">سنجاق شده</string>
|
||||
<string name="sk_confirm_pin_post_title">سنجاق فرسته به نمایه</string>
|
||||
<string name="sk_app_name">مگالودون</string>
|
||||
<string name="sk_pin_post">سنجاق به نمایه</string>
|
||||
<string name="sk_pinning">درحال سنجاق کردن فرسته…</string>
|
||||
<string name="sk_unpin_post">برداشتن سنجاق از نمایه</string>
|
||||
<string name="sk_confirm_unpin_post_title">برداشتن سنجاق فرسته از نمایه</string>
|
||||
<string name="sk_unpinning">درحال برداشتن سنجاق فرسته…</string>
|
||||
<string name="sk_image_description">توضیحات تصویر</string>
|
||||
<string name="sk_visibility_unlisted">فهرست نشده</string>
|
||||
<string name="sk_settings_show_replies">نمایش پاسخها</string>
|
||||
<string name="sk_list_timelines">سیاههها</string>
|
||||
<string name="sk_notification_type_status">فرستهها</string>
|
||||
<string name="sk_no_update_available">بهروزرسانی موجود نیست</string>
|
||||
<string name="sk_settings_reply_visibility_all">همه پاسخها</string>
|
||||
<string name="sk_mark_media_as_sensitive">علامتگذاری رسانه به عنوان حساس</string>
|
||||
<string name="sk_update_available">مگالودون %s آماده بارگیری است.</string>
|
||||
<string name="sk_follow_requests">درخواستهای پیگیری</string>
|
||||
<string name="sk_accept_follow_request">پذیرفتن درخواست پیگیری</string>
|
||||
<string name="sk_reject_follow_request">رد درخواست پیگیری</string>
|
||||
<string name="sk_notify_posts">اعلانهای فرسته</string>
|
||||
<string name="sk_color_palette_material3">سامانه</string>
|
||||
<string name="sk_color_palette_pink">صورتی</string>
|
||||
<string name="sk_color_palette_purple">بنفش</string>
|
||||
<string name="sk_color_palette_green">سبز</string>
|
||||
<string name="sk_color_palette_blue">آبی</string>
|
||||
<string name="sk_color_palette_brown">قهوهای</string>
|
||||
<string name="sk_color_palette_red">قرمز</string>
|
||||
<string name="sk_color_palette_yellow">زرد</string>
|
||||
<string name="sk_translate_post">ترجمه</string>
|
||||
<string name="sk_post_language">زبان: %s</string>
|
||||
<string name="sk_available_languages">زبان های موجود</string>
|
||||
<string name="sk_welcome_title">خوش آمدید!</string>
|
||||
<string name="sk_language_name">%1$s (%2$s)</string>
|
||||
<string name="sk_settings_profile">تنظیم نمایه</string>
|
||||
<string name="sk_settings_filters">پیکربندی پالایه</string>
|
||||
<string name="sk_settings_auth">تنظیمات امنیتی</string>
|
||||
<string name="sk_settings_about">درباره کاره</string>
|
||||
<string name="sk_settings_donate">اعانه</string>
|
||||
<string name="sk_clear_all_notifications">پاکسازی همه اعلانات</string>
|
||||
<string name="sk_clear_all_notifications_confirm_action">حذف همه</string>
|
||||
<string name="sk_settings_publish_button_text">متن دکمه انتشار</string>
|
||||
<string name="sk_settings_publish_button_text_title">سفارشیسازی متن دکمه انتشار</string>
|
||||
<string name="sk_undo_reblog">برگردان تقویت</string>
|
||||
<string name="sk_settings_rules">قوانین</string>
|
||||
<string name="sk_collapse">بستن</string>
|
||||
<string name="sk_expand">باز کردن</string>
|
||||
<string name="sk_notify_poll_results">نتایج نظرسنجی</string>
|
||||
<string name="sk_settings_server_version">نسخه کارساز: %s</string>
|
||||
<string name="sk_settings_glitch_instance">حالت فقط محلی Glitch</string>
|
||||
<string name="sk_inline_local_only">فقط محلی</string>
|
||||
<string name="sk_updater_enable_pre_releases">به کار انداختن پیش انتشار</string>
|
||||
<string name="sk_save_draft">ذخیره پیش نویس؟</string>
|
||||
<string name="sk_no_results">بدون نتیجه</string>
|
||||
<string name="sk_searching">درحال جستجو…</string>
|
||||
<string name="sk_attach_file">پیوست پرونده</string>
|
||||
<string name="sk_post_edited">ویرایش شده</string>
|
||||
<string name="sk_content_type_markdown">مارکدون</string>
|
||||
<string name="sk_content_type_html">HTML</string>
|
||||
<string name="sk_content_type_plain">متن ساده</string>
|
||||
<string name="sk_check_for_update">بررسی برای بهروزرسانی</string>
|
||||
<string name="sk_settings_support_local_only">پشتیبانی کارساز از ارسال فرسته فقط محلی</string>
|
||||
<string name="sk_settings_show_boosts">نمایش تقویتها</string>
|
||||
<string name="sk_notification_type_update">فرستههای ویرایش شده</string>
|
||||
<string name="sk_update_ready">مگالودون %s بارگیری شده و آماده نصب است.</string>
|
||||
<string name="sk_poll_allow_multiple">اجازه انتخاب های متعدد</string>
|
||||
<string name="sk_open_with_account">باز کردن با حساب دیگر</string>
|
||||
<string name="sk_confirm_delete_draft_title">حذف پیش نویس</string>
|
||||
<string name="sk_draft">پیشنویس</string>
|
||||
<string name="sk_draft_saved">پیش نویس ذخیره شد</string>
|
||||
<string name="sk_delete_and_redraft">حذف و بازنویسی</string>
|
||||
<string name="sk_confirm_delete_and_redraft_title">حذف و بازنویسی فرسته</string>
|
||||
<string name="sk_confirm_delete_and_redraft">آیا مطمئنید که میخواهید این فرسته را حذف و بازنویسی کنید؟</string>
|
||||
<string name="sk_confirm_pin_post">آیا میخواهید این فرسته را به نمایه خود سنجاق کنید؟</string>
|
||||
<string name="sk_confirm_unpin_post">آیا میخواهید سنجاق این فرسته را بردارید؟</string>
|
||||
<string name="sk_lists_with_user">سیاهههایی با %s</string>
|
||||
<string name="sk_settings_app_version">مگالودون v%1$s (%2$d)</string>
|
||||
<string name="sk_settings_posting">ترجیحات ارسال فرسته</string>
|
||||
<string name="sk_translated_using">با استفاده از %s ترجمه شده است</string>
|
||||
<string name="sk_translate_show_original">نمایش اصلی</string>
|
||||
<string name="sk_example_domain">example.social</string>
|
||||
<string name="sk_delete_notification">حذف آگاهی</string>
|
||||
<string name="sk_delete_notification_confirm_action">حذف آگاهی</string>
|
||||
<string name="sk_settings_translation_availability_note_available">%s از ترجمه پشتیبانی میکند!</string>
|
||||
<string name="sk_copy_link_to_post">رونوشت پیوند فرسته</string>
|
||||
<string name="sk_already_favorited">قبلا برگزیده بوده است</string>
|
||||
<string name="sk_favorite_as">برگزیدن با حساب دیگر</string>
|
||||
<string name="sk_unsent_posts">فرستههای ارسال نشده</string>
|
||||
<string name="sk_reblog_as">تقویت با حساب دیگر</string>
|
||||
<string name="sk_already_reblogged">قبلا تقویت شده است</string>
|
||||
<string name="sk_reply_as">پاسخ با حساب دیگر</string>
|
||||
<string name="sk_bookmark_as">نشانکگذاری با حساب دیگر</string>
|
||||
<string name="sk_forward_report_to">هدایت به %s</string>
|
||||
<string name="sk_schedule">زمانبندی</string>
|
||||
<string name="sk_compose_scheduled">برنامهریزی شده برای</string>
|
||||
<string name="sk_post_scheduled">فرسته برنامهریزی شد</string>
|
||||
<string name="sk_confirm_save_draft">ذخیره پیش نویس؟</string>
|
||||
<string name="sk_confirm_save_changes">ذخیره تغییرات؟</string>
|
||||
<string name="sk_mark_as_draft">علامتگذاری به عنوان پیش نویس</string>
|
||||
<string name="sk_schedule_post">برنامهریزی فرسته</string>
|
||||
<string name="sk_compose_no_draft">پیشنویس نکن</string>
|
||||
<string name="sk_announcements">اطلاعیهها</string>
|
||||
<string name="sk_settings_about_instance">درباره نمونه</string>
|
||||
<string name="sk_create">ایجاد کردن</string>
|
||||
<string name="sk_create_list_title">ایجاد سیاهه</string>
|
||||
<string name="sk_list_name_hint">اسم سیاهه</string>
|
||||
<string name="sk_delete_list">حذف سیاهه</string>
|
||||
<string name="sk_your_lists">سیاهه شما</string>
|
||||
<string name="sk_timeline_local">محلی</string>
|
||||
<string name="sk_do_remove_follower">حذف کردن</string>
|
||||
<string name="sk_confirm_delete_scheduled_post_title">حذف فرسته برنامهریزی شده</string>
|
||||
<string name="sk_draft_or_schedule">پیش نویس یا برنامهریزی</string>
|
||||
<string name="sk_compose_no_schedule">برنامهریزی نکن</string>
|
||||
<string name="sk_list_replies_policy_none">هیچکس</string>
|
||||
<string name="sk_timeline_home">خانه</string>
|
||||
<string name="sk_timelines">خط زمانیها</string>
|
||||
<string name="sk_timeline_posts">فرستهها</string>
|
||||
<string name="sk_timelines_add">افزودن</string>
|
||||
<string name="sk_timeline">خط زمانی</string>
|
||||
<string name="sk_list">سیاهه</string>
|
||||
<string name="sk_hashtag">برچسب</string>
|
||||
<string name="sk_remove">حذف کردن</string>
|
||||
<string name="sk_timeline_icon">نقشک</string>
|
||||
<string name="sk_icon_city">شهر</string>
|
||||
<string name="sk_icon_cat">گربه</string>
|
||||
<string name="sk_icon_dog">سگ</string>
|
||||
<string name="sk_icon_rabbit">خرگوش</string>
|
||||
<string name="sk_icon_turtle">لاکپشت</string>
|
||||
<string name="sk_icon_image">تصویر</string>
|
||||
<string name="sk_icon_bot">ربات</string>
|
||||
<string name="sk_icon_language">زبان</string>
|
||||
<string name="sk_icon_location">مکان</string>
|
||||
<string name="sk_icon_microphone">میکروفون</string>
|
||||
<string name="sk_icon_coffee">قهوه</string>
|
||||
<string name="sk_icon_news">اخبار</string>
|
||||
<string name="sk_icon_games">بازی</string>
|
||||
<string name="sk_icon_code">کد</string>
|
||||
<string name="sk_icon_train">قطار</string>
|
||||
<string name="sk_icon_music">موسیقی</string>
|
||||
<string name="sk_icon_people">مردم</string>
|
||||
<string name="sk_icon_health">سلامتی</string>
|
||||
<string name="sk_icon_chat">گپ</string>
|
||||
<string name="sk_icon_book">کتاب</string>
|
||||
<string name="sk_icon_bicycle">دوچرخه</string>
|
||||
<string name="sk_icon_map">نقشه</string>
|
||||
<string name="sk_icon_backpack">کوله پشتی</string>
|
||||
<string name="sk_icon_fire">آتش</string>
|
||||
<string name="sk_icon_pizza">پیتزا</string>
|
||||
<string name="sk_icon_headphones">هدفون</string>
|
||||
<string name="sk_icon_human">انسان</string>
|
||||
<string name="sk_icon_pin">سنجاق</string>
|
||||
<string name="sk_edit_timeline">ویرایش خط زمانی</string>
|
||||
<string name="sk_settings_see_new_posts_button">دکمه \"مشاهده فرستههای جدید\"</string>
|
||||
<string name="sk_local_only">فقط نمونه محلی</string>
|
||||
<string name="sk_instance_features">ویژگی های نمونه</string>
|
||||
<string name="sk_new_reports">گزارشهای جدید</string>
|
||||
<string name="sk_reported">گزارش شد</string>
|
||||
<string name="sk_in_reply">در پاسخ</string>
|
||||
<string name="sk_open_in_app">بازکردن در کاره</string>
|
||||
<string name="sk_settings_prefix_replies_never">هیچکس</string>
|
||||
<string name="sk_settings_prefix_replies_to_others">دیگران</string>
|
||||
<string name="sk_schedule_or_draft">برنامهریزی یا پیشنویس</string>
|
||||
<string name="sk_edit_list_title">ویرایش سیاهه</string>
|
||||
<string name="sk_icon_star">ستاره</string>
|
||||
<string name="sk_confirm_delete_draft">آیا مطمئنید که میخواهید این فرسته پیش نویس شده را حذف کنید؟</string>
|
||||
<string name="sk_changelog">گزارش تغییرات</string>
|
||||
</resources>
|
||||
@@ -254,7 +254,7 @@
|
||||
<string name="sk_expand">Développer</string>
|
||||
<string name="sk_collapse">Réduire</string>
|
||||
<string name="sk_settings_collapse_long_posts">Réduire les messages très longs</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Préfixe \"re :\" lors d\'une réponse avec AC</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Préfixe les AC avec \"re :\" sur les réponses à</string>
|
||||
<string name="sk_filtered">Filtré : %s</string>
|
||||
<string name="sk_unfinished_attachments">Corriger les pièces jointes \?</string>
|
||||
<string name="sk_unfinished_attachments_message">Certaines pièces jointes n\'ont pas fini de se télécharger.</string>
|
||||
@@ -294,4 +294,16 @@
|
||||
<string name="sk_timeline_bubble">Bulle</string>
|
||||
<string name="sk_instance_info_unavailable">Informations sur l\'instance temporairement indisponibles</string>
|
||||
<string name="sk_open_in_app_failed">Impossible de l\'ouvrir dans l\'application</string>
|
||||
<string name="sk_settings_allow_remote_loading">Charger des informations à partir d\'instances distantes</string>
|
||||
<string name="sk_no_remote_info_hint">informations distantes indisponibles</string>
|
||||
<string name="sk_error_loading_profile">Échec du chargement du profil via %s</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Essayez de récupérer des listes plus précises pour les abonnés, les likes et les boosts en chargeant les informations à partir de l\'instance d\'origine.</string>
|
||||
<string name="sk_settings_auto_reveal_equal_spoilers">Révéler les AC identiques dans les réponses de</string>
|
||||
<string name="sk_settings_auto_reveal_nobody">personne</string>
|
||||
<string name="sk_settings_auto_reveal_author">auteur</string>
|
||||
<string name="sk_settings_auto_reveal_anyone">tout le monde</string>
|
||||
<string name="sk_settings_prefix_replies_always">tout le monde</string>
|
||||
<string name="sk_settings_prefix_replies_never">personne</string>
|
||||
<string name="sk_settings_prefix_replies_to_others">autres</string>
|
||||
<string name="sk_settings_forward_report_default">\"Transférer le rapport\" par défaut</string>
|
||||
</resources>
|
||||
@@ -181,7 +181,7 @@
|
||||
<string name="sk_settings_local_only_explanation">Vaša početna instanca mora podržavati isključivo-lokalno objavljivanje da bi ovo radilo. Većina modificiranih verzija Mastodona to radi, ali Mastodon ne.</string>
|
||||
<string name="sk_settings_hide_interaction">Sakrij gumbe za interakciju</string>
|
||||
<string name="sk_settings_hide_fab">Automatski sakrij gumb \"Nova objava\"</string>
|
||||
<string name="sk_quoting_user">"Citiranje %"</string>
|
||||
<string name="sk_quoting_user">Citiranje %s</string>
|
||||
<string name="sk_settings_reply_visibility">Vidljivost odgovora</string>
|
||||
<string name="sk_settings_reply_visibility_all">Svi odgovori</string>
|
||||
<string name="sk_settings_reply_visibility_following">Odgovori onima koje pratim</string>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<string name="sk_delete_and_redraft">Törlés és újraszövegezés</string>
|
||||
<string name="sk_pin_post">Pin a profilhoz</string>
|
||||
<string name="sk_confirm_pin_post_title">Hozzászólás rögzítése a profilhoz</string>
|
||||
<string name="sk_pinning">Pinning poszt…</string>
|
||||
<string name="sk_pinning">Bejegyzés kitűzése…</string>
|
||||
<string name="sk_unpin_post">Unpin a profilból</string>
|
||||
<string name="sk_confirm_unpin_post_title">Hozzászólás feloldása a profilból</string>
|
||||
<string name="sk_unpinning">Unpinning poszt…</string>
|
||||
@@ -124,4 +124,7 @@
|
||||
<string name="sk_poll_allow_multiple">Több választási lehetőség engedélyezése</string>
|
||||
<string name="sk_tabs_disable_swipe">Lapok közötti lapozás letiltása</string>
|
||||
<string name="sk_settings_about">Az alkalmazásról</string>
|
||||
<string name="sk_quoting_user">%s említése</string>
|
||||
<string name="sk_settings_reply_visibility">Válasz láthatósága</string>
|
||||
<string name="sk_settings_reply_visibility_all">Összes válasz</string>
|
||||
</resources>
|
||||
@@ -10,7 +10,7 @@
|
||||
<string name="ok">Oke</string>
|
||||
<string name="preparing_auth">Menyiapkan untuk autentikasi…</string>
|
||||
<string name="finishing_auth">Menyelesaikan autentikasi…</string>
|
||||
<string name="user_boosted">%s di-boost-kan</string>
|
||||
<string name="user_boosted">%s membagikan</string>
|
||||
<string name="in_reply_to">Membalas ke %s</string>
|
||||
<string name="notifications">Notifikasi</string>
|
||||
<string name="user_followed_you">mengikuti Anda</string>
|
||||
@@ -417,6 +417,7 @@
|
||||
<string name="show">Tampilkan</string>
|
||||
<string name="hide">Sembunyikan</string>
|
||||
<string name="join_default_server">Gabung %s</string>
|
||||
<string name="pick_server">Cari server lain</string>
|
||||
<string name="signup_or_login">atau</string>
|
||||
<string name="learn_more">Pelajari lebih lanjut</string>
|
||||
<string name="welcome_to_mastodon">Selamat datang di Mastodon</string>
|
||||
|
||||
@@ -289,8 +289,17 @@
|
||||
<string name="sk_settings_content_types_explanation">Memperbolehkan menetapkan jenis konten seperti Markdown ketika membuat kiriman. Perlu diingat bahwa tidak semua server mendukung ini.</string>
|
||||
<string name="sk_open_in_app">Buka dalam aplikasi</string>
|
||||
<string name="sk_external_share_title">Bagikan dengan akun</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Ini adalah kiriman yamg paling terkini oleh orang-orang dalam gelembung server Akkoma Anda.</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Ini adalah kiriman yang paling terkini dari jaringan dikurasikan oleh admin server Anda.</string>
|
||||
<string name="sk_timeline_bubble">Gelembung</string>
|
||||
<string name="sk_instance_info_unavailable">Info server sementara tidak tersedia</string>
|
||||
<string name="sk_external_share_or_open_title">Bagikan atau buka dengan akun</string>
|
||||
<string name="sk_no_remote_info_hint">info jarak jauh tidak tersedia</string>
|
||||
<string name="sk_settings_allow_remote_loading">Muat info dari server jarak jauh</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Coba mendapatkan pendaftaran akurat untuk pengikut cr</string>
|
||||
<string name="sk_error_loading_profile">Gagal memuat profil melalui %s</string>
|
||||
<string name="sk_open_in_app_failed">Tidak dapat buka dalam aplikasi</string>
|
||||
<string name="sk_settings_auto_reveal_equal_spoilers">Tampilkan peringatan konten yang sama dari</string>
|
||||
<string name="sk_settings_auto_reveal_nobody">bukan siapa pun</string>
|
||||
<string name="sk_settings_auto_reveal_author">pembuat</string>
|
||||
<string name="sk_settings_auto_reveal_anyone">semuanya</string>
|
||||
</resources>
|
||||
@@ -153,7 +153,7 @@
|
||||
<string name="sk_timelines">Timeline</string>
|
||||
<string name="sk_timeline_posts">Post</string>
|
||||
<string name="sk_timelines_add">Aggiungi</string>
|
||||
<string name="sk_timeline">Linea temporale</string>
|
||||
<string name="sk_timeline">Timeline</string>
|
||||
<string name="sk_list">Lista</string>
|
||||
<string name="sk_hashtag">Hashtag</string>
|
||||
<string name="sk_pin_timeline">Fissa timeline</string>
|
||||
@@ -274,8 +274,17 @@
|
||||
<string name="sk_compact_reblog_reply_line">Linea boost/risposta compatta</string>
|
||||
<string name="sk_reacted_with">ha reagito con %s</string>
|
||||
<string name="sk_reply_line_above_avatar">Linea \"In risposta a\" sopra l\'avatar</string>
|
||||
<string name="sk_settings_show_new_posts_button">Tasto \"Mostra nuovi post\"</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Questi sono i post più recenti della rete, curati dagli amministratori della tua istanza.</string>
|
||||
<string name="sk_settings_content_types_explanation">Permette di impostare un tipo di contenuto, come Markdown, quando si crea un post. Tieni presente che non tutte le istanze lo supportano.</string>
|
||||
<string name="sk_open_in_app_failed">Impossibile aprire nell\'app</string>
|
||||
<string name="sk_external_share_title">Condividi con l\'account</string>
|
||||
<string name="sk_external_share_or_open_title">Condividi o apri con l\'account</string>
|
||||
<string name="sk_no_remote_info_hint">Informazioni remote non disponibili</string>
|
||||
<string name="sk_error_loading_profile">Impossibile caricare il profilo tramite %s</string>
|
||||
<string name="sk_settings_allow_remote_loading">Carica informazioni da istanze remote</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Cerca di ottenere elenchi più accurati di follower, like e boost caricando le informazioni dall\'istanza di origine.</string>
|
||||
<string name="sk_content_type">Tipo di contenuto</string>
|
||||
<string name="sk_timeline_bubble">Bolla</string>
|
||||
<string name="sk_content_type_unspecified">Non specificato</string>
|
||||
<string name="sk_content_type_plain">Testo semplice</string>
|
||||
<string name="sk_content_type_html">HTML</string>
|
||||
@@ -283,7 +292,8 @@
|
||||
<string name="sk_content_type_bbcode">BBCode</string>
|
||||
<string name="sk_content_type_mfm">MFM</string>
|
||||
<string name="sk_settings_content_types">Abilita la formattazione dei post</string>
|
||||
<string name="sk_settings_content_types_explanation">Permette di impostare un tipo di contenuto, come Markdown, quando si crea un post. Tieni presente che non tutte le istanze lo supportano.</string>
|
||||
<string name="sk_settings_default_content_type">Tipo di contenuto predefinito</string>
|
||||
<string name="sk_settings_default_content_type_explanation">Ti permette di preselezionare un tipo di contenuto quando si creano nuovi post, sovrascrivendo il valore impostato in \"Preferenze di pubblicazione\".</string>
|
||||
<string name="sk_instance_info_unavailable">Informazioni sull\'istanza temporaneamente non disponibili</string>
|
||||
<string name="sk_open_in_app">Apri nell\'app</string>
|
||||
</resources>
|
||||
@@ -17,7 +17,7 @@
|
||||
<string name="mo_clear_recent_emoji">Wis recent gebruikte emoji</string>
|
||||
<string name="mo_no_image_desc">De bijgesloten afbeeldingen hebben geen omschrijving. Overweeg deze alsnog toe te voegen, zodat ook mensen met een visuele handicap kunnen deelnemen.</string>
|
||||
<string name="mo_share_open_url">Open in app</string>
|
||||
<string name="mo_disable_reminder_to_add_alt_text">Uitschakelen herinnering om alt text toe te voegen</string>
|
||||
<string name="mo_disable_reminder_to_add_alt_text">Uitschakelen herinnering om alt tekst toe te voegen</string>
|
||||
<string name="mo_filtered">Filter: %s</string>
|
||||
<string name="mo_mute_label">Duur:</string>
|
||||
<string name="mo_duration_minutes_30">30 minuten</string>
|
||||
@@ -40,7 +40,7 @@
|
||||
<string name="mo_add_custom_server_local_timeline">Toevoegen lokale tijdlijn van een aangepaste server</string>
|
||||
<string name="mo_confirm_unfollow_title">Ontvolg account</string>
|
||||
<string name="mo_notification_management_settings">Beheer meldingen</string>
|
||||
<string name="mo_setting_true_black_summary">Kan batterij beparen bij AMOLED scherm</string>
|
||||
<string name="mo_setting_true_black_summary">Kan batterij besparen bij AMOLED scherm</string>
|
||||
<string name="mo_confirm_unfollow">Bevestig ontvolgen van %s</string>
|
||||
<string name="mo_setting_play_gif_summary">Autoplay GIFs in avatars en emoji</string>
|
||||
<string name="mo_double_tap_to_swipe_between_tabs">Dubbeltikken om te swipen tussen tabs</string>
|
||||
@@ -48,4 +48,24 @@
|
||||
<string name="mo_notification_audience_settings">Doelgroep van meldingen</string>
|
||||
<string name="mo_setting_uniform_summary">Gebruik het icon van de app voor alle meldingen</string>
|
||||
<string name="mo_swap_bookmark_with_reblog_summary">Bookmark of reblog vanuit de melding</string>
|
||||
<string name="mo_instance_registration">Registratie</string>
|
||||
<string name="mo_instance_status">Status</string>
|
||||
<string name="mo_instance_users">Gebruikers</string>
|
||||
<string name="mo_instance_contact">Contact</string>
|
||||
<string name="mo_severity_silence">Genegeerd</string>
|
||||
<string name="mo_instance_info_moderated_servers">Gemodereerde servers</string>
|
||||
<string name="mo_instance_info_open_timeline">Lokale tijdlijn</string>
|
||||
<string name="mo_instance_registration_approval">Toestemming vereist</string>
|
||||
<string name="mo_instance_registration_open">Open</string>
|
||||
<string name="mo_instance_admin">Beheerd door</string>
|
||||
<string name="mo_open_camera">Maak foto</string>
|
||||
<string name="mo_severity_suspend">Geblokkeerd</string>
|
||||
<string name="mo_setting_remote_follower_summary">Toon volgers van andere instances</string>
|
||||
<string name="mo_setting_reduced_motion_summary">Uitschakelen animatie bij interacties</string>
|
||||
<string name="mo_setting_interaction_count_summary">Toon hoeveel mensen interactie hebben met een bericht op de tijdlijn</string>
|
||||
<string name="mo_setting_default_reply_privacy_summary">Reacties worden niet meegenomen in de discovery features</string>
|
||||
<string name="mo_setting_relocate_publish_summary">Verplaats de publiceerknop naar de onderste balk</string>
|
||||
<string name="mo_setting_disable_swipe_summary">Niet swipen om de volgende tijdlijn te zien</string>
|
||||
<string name="mo_setting_marquee_summary">Schakelt de horizontaal scrollende tekst uit</string>
|
||||
<string name="mo_mention_reblogger_automatically">Automatisch in reacties het account erbij noteren, van degene die het bericht boost</string>
|
||||
</resources>
|
||||
@@ -39,14 +39,14 @@
|
||||
<string name="sk_settings_contribute">Bijdragen aan Megalodon</string>
|
||||
<string name="sk_settings_show_federated_timeline">Toon gefedereerde tijdlijn</string>
|
||||
<string name="sk_notification_type_status">Berichten</string>
|
||||
<string name="sk_notify_posts">Bericht meldingen</string>
|
||||
<string name="sk_notify_posts">Een bericht publiceert</string>
|
||||
<string name="sk_timeline_local">Lokaal</string>
|
||||
<string name="sk_timeline_home">Home</string>
|
||||
<string name="sk_timeline_federated">Federatie</string>
|
||||
<string name="sk_translate_post">Vertaal</string>
|
||||
<string name="sk_poll_allow_multiple">Meerdere keuzes toestaan</string>
|
||||
<string name="sk_clear_recent_languages">Wis recent gebruikte talen</string>
|
||||
<string name="sk_welcome_text">De haai groet je! Graag je instance naam (Mastodon server) hieronder invullen om te beginnen.</string>
|
||||
<string name="sk_welcome_text">Hallo! Graag je instance naam (Mastodon server) hieronder invullen om te beginnen.</string>
|
||||
<string name="sk_welcome_title">Welkom!</string>
|
||||
<string name="sk_translated_using">Vertaald met %s</string>
|
||||
<string name="sk_post_language">Taal: %s</string>
|
||||
@@ -65,7 +65,7 @@
|
||||
<string name="sk_settings_rules">Regels</string>
|
||||
<string name="sk_settings_about">Over de app</string>
|
||||
<string name="sk_settings_donate">Doneer</string>
|
||||
<string name="sk_settings_color_palette">Kleurpalet</string>
|
||||
<string name="sk_settings_color_palette">Kleurenpalet</string>
|
||||
<string name="sk_color_palette_material3">Systeem</string>
|
||||
<string name="sk_color_palette_pink">Roze</string>
|
||||
<string name="sk_color_palette_purple">Paars</string>
|
||||
@@ -106,7 +106,7 @@
|
||||
<string name="sk_settings_reduce_motion">Beweging in animaties verminderen</string>
|
||||
<string name="sk_announcements">Aankondigingen</string>
|
||||
<string name="sk_mark_as_read">Markeer als gelezen</string>
|
||||
<string name="sk_settings_about_instance">Over instance</string>
|
||||
<string name="sk_settings_about_instance">Over je instance (server)</string>
|
||||
<string name="sk_settings_single_notification">Toon maar één melding</string>
|
||||
<string name="sk_create">Maken</string>
|
||||
<string name="sk_create_list_title">Maak lijst</string>
|
||||
@@ -133,7 +133,7 @@
|
||||
<string name="sk_confirm_delete_scheduled_post">Weet je zeker dat je dit ingeplande bericht wilt verwijderen\?</string>
|
||||
<string name="sk_confirm_save_draft">Concept opslaan\?</string>
|
||||
<string name="sk_compose_no_schedule">Niet inplannen</string>
|
||||
<string name="sk_already_reblogged">Al geboost</string>
|
||||
<string name="sk_already_reblogged">Al eerder geboost</string>
|
||||
<string name="sk_reblogged_as">Geboost als %s</string>
|
||||
<string name="sk_reblog_with_visibility">Boosten met zichtbaarheid</string>
|
||||
<string name="sk_quote_post">Maak een bericht hierover</string>
|
||||
@@ -146,15 +146,15 @@
|
||||
<string name="sk_recent_searches_placeholder">Type om te beginnen met zoeken</string>
|
||||
<string name="sk_remove_follower_confirm">Verwijder %s als volger door te blokkeren en meteen te de-blokkeren\?</string>
|
||||
<string name="sk_post_edited">bewerkt</string>
|
||||
<string name="sk_notify_update">Bewerkt een geboost bericht</string>
|
||||
<string name="sk_notify_update">Een geboost bericht bewerkt</string>
|
||||
<string name="sk_list">Lijst</string>
|
||||
<string name="sk_icon_star">Ster</string>
|
||||
<string name="sk_timeline_posts">Berichten</string>
|
||||
<string name="sk_timelines_add">Toevoegen</string>
|
||||
<string name="sk_alt_text_missing_title">Ontbrekende alt text</string>
|
||||
<string name="sk_alt_text_missing_title">Ontbrekende alt tekst</string>
|
||||
<string name="sk_alt_text_missing">Minstens één bijlage bevat geen omschrijving.</string>
|
||||
<string name="sk_publish_anyway">Toch publiceren</string>
|
||||
<string name="sk_settings_disable_alt_text_reminder">Alt text herinnering uitschakelen</string>
|
||||
<string name="sk_settings_disable_alt_text_reminder">Alt tekst herinnering uitschakelen</string>
|
||||
<string name="sk_notify_posts_info_banner">Als je meldingen voor berichten van bepaalde mensen inschakelt, dan zie je hier hun nieuwe berichten.</string>
|
||||
<string name="sk_icon_pi">Pi</string>
|
||||
<string name="sk_icon_city">Stad</string>
|
||||
@@ -169,11 +169,11 @@
|
||||
<string name="sk_icon_megaphone">Megafoon</string>
|
||||
<string name="sk_icon_microphone">Microfoon</string>
|
||||
<string name="sk_icon_microscope">Microscoop</string>
|
||||
<string name="sk_icon_keyboard">Keyboard</string>
|
||||
<string name="sk_icon_keyboard">Toetsenbord</string>
|
||||
<string name="sk_icon_coffee">Koffie</string>
|
||||
<string name="sk_icon_laugh">Lach</string>
|
||||
<string name="sk_edit_timeline">Bewerk tijdlijn</string>
|
||||
<string name="sk_icon_color_palette">Kleurpalet</string>
|
||||
<string name="sk_icon_color_palette">Kleurenpalet</string>
|
||||
<string name="sk_icon_tag">Label</string>
|
||||
<string name="sk_icon_stethoscope">Stethoscoop</string>
|
||||
<string name="sk_icon_weather">Weer</string>
|
||||
@@ -234,12 +234,12 @@
|
||||
<string name="sk_sign_ups">Gebruikers die zich aanmelden</string>
|
||||
<string name="sk_settings_see_new_posts_button">\"Zie nieuwe berichten\" knop</string>
|
||||
<string name="sk_inline_local_only">alleen-lokaal</string>
|
||||
<string name="sk_local_only">Alleen lokale instance</string>
|
||||
<string name="sk_local_only">Alleen lokale instance (server)</string>
|
||||
<string name="sk_settings_glitch_instance">Glitch alleen-lokaal modus</string>
|
||||
<string name="sk_instance_features">Instance mogelijkheden</string>
|
||||
<string name="sk_instance_features">Instance (server) mogelijkheden</string>
|
||||
<string name="sk_settings_support_local_only">Server ondersteunt alleen-lokaal berichten plaatsing</string>
|
||||
<string name="sk_settings_server_version">Serverversie: %s</string>
|
||||
<string name="sk_notify_poll_results">Peiling uitslagen</string>
|
||||
<string name="sk_notify_poll_results">Peiling uitslagen publiceert</string>
|
||||
<string name="sk_pinned_timeline">Vastgemaakt aan Home</string>
|
||||
<string name="sk_unpinned_timeline">Losgemaakt van Home</string>
|
||||
<string name="sk_expand">Uitvouwen</string>
|
||||
@@ -254,4 +254,32 @@
|
||||
<string name="sk_quoting_user">Quoting %s</string>
|
||||
<string name="sk_settings_reply_visibility">Zichtbaarheid reactie</string>
|
||||
<string name="sk_settings_reply_visibility_all">Alle reacties</string>
|
||||
<string name="sk_settings_auto_reveal_never">Nooit</string>
|
||||
<string name="sk_settings_auto_reveal_always">Altijd</string>
|
||||
<string name="sk_settings_auto_reveal_equal_spoilers">Toon dezelfde CW\'s in reacties van</string>
|
||||
<string name="sk_settings_auto_reveal_threads">Zelfde auteur</string>
|
||||
<string name="sk_settings_auto_reveal_discussions">Hele gesprek</string>
|
||||
<string name="sk_instance_info_unavailable">Instance (server) informatie tijdelijk niet beschikbaar</string>
|
||||
<string name="sk_settings_allow_remote_loading">Laad informatie van remote instances (servers)</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Probeer meer accurate informatie te krijgen van volgers, favorieten en boosts, door informatie te laden van de originele instance (server).</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Dit zijn de nieuwste berichten op het netwerk, samengesteld door de beheerders van je instance (server).</string>
|
||||
<string name="sk_settings_glitch_mode_explanation">Inschakelen als je home instance (server) draait op Glitch. Niet nodig voor Hometown of Akkoma.</string>
|
||||
<string name="sk_settings_show_new_posts_button">\"Toon nieuwe berichten\" knop</string>
|
||||
<string name="sk_changelog">Changelog</string>
|
||||
<string name="sk_settings_confirm_before_reblog">Bevestig om te boosten</string>
|
||||
<string name="sk_content_type_plain">Plain tekst</string>
|
||||
<string name="sk_content_type_bbcode">BBCode</string>
|
||||
<string name="sk_content_type_mfm">MFM</string>
|
||||
<string name="sk_content_type_unspecified">Niet gespecificeerd</string>
|
||||
<string name="sk_content_type_html">HTML</string>
|
||||
<string name="sk_content_type_markdown">Markdown</string>
|
||||
<string name="sk_settings_content_types">Schakel berichtopmaak in</string>
|
||||
<string name="sk_notification_action_replied">Reactie verzonden naar %s</string>
|
||||
<string name="sk_open_in_app">Open in app</string>
|
||||
<string name="sk_open_in_app_failed">Kan niet openen in app</string>
|
||||
<string name="sk_external_share_title">Deel met account</string>
|
||||
<string name="sk_external_share_or_open_title">Deel of open met account</string>
|
||||
<string name="sk_no_remote_info_hint">remote informatie niet beschikbaar</string>
|
||||
<string name="sk_error_loading_profile">Laden van het profiel via %s mislukt</string>
|
||||
<string name="sk_compact_reblog_reply_line">Compacte boost/reply regel</string>
|
||||
</resources>
|
||||
@@ -33,14 +33,42 @@
|
||||
<string name="mo_duration_indefinite">Indefinido</string>
|
||||
<string name="mo_duration_minutes_5">5 minutos</string>
|
||||
<string name="mo_duration_minutes_30">30 minutos</string>
|
||||
<string name="mo_change_default_reply_visibility_to_unlisted">Mudar visibilidade padrão das respostas para não listado</string>
|
||||
<string name="mo_change_default_reply_visibility_to_unlisted">Responder como \'Não listado\' por padrão</string>
|
||||
<string name="mo_miscellaneous_settings">Configurações Diversas</string>
|
||||
<string name="mo_disable_double_tap_to_swipe_between_tabs">Desativar toque duplo para deslizar entre abas</string>
|
||||
<string name="mo_share_open_url">Abrir no App</string>
|
||||
<string name="mo_swap_bookmark_with_reblog">Usar ação de reblogar ao invés da ação salvar nas notificações</string>
|
||||
<string name="mo_swap_bookmark_with_reblog">Trocar ação \'salvar\' por \'reblogar\'</string>
|
||||
<string name="mo_download_latest_nightly_release">Baixar mais novo lançamento noturno</string>
|
||||
<string name="mo_load_remote_followers">Carregar seguidores e seguindo de perfil remoto</string>
|
||||
<string name="mo_mention_reblogger_automatically">Automaticamente mencionar conta que reblogou a postagem nas respostas</string>
|
||||
<string name="mo_confirm_unfollow_title">Deixar de seguir conta</string>
|
||||
<string name="mo_confirm_unfollow">Confirmar para deixar de seguir %s</string>
|
||||
<string name="mo_instance_admin">Administrado por</string>
|
||||
<string name="mo_instance_contact">Contato</string>
|
||||
<string name="mo_instance_registration">Registro</string>
|
||||
<string name="mo_instance_users">Usuários</string>
|
||||
<string name="mo_instance_status">Publicações</string>
|
||||
<string name="mo_instance_registration_open">Aberto</string>
|
||||
<string name="mo_instance_info_open_timeline">Linha do tempo local</string>
|
||||
<string name="mo_instance_info_moderated_servers">Servidores moderados</string>
|
||||
<string name="mo_severity_silence">Silenciado</string>
|
||||
<string name="mo_severity_suspend">Bloqueado</string>
|
||||
<string name="mo_open_camera">Tirar foto</string>
|
||||
<string name="mo_instance_registration_approval">Requer aprovação</string>
|
||||
<string name="mo_setting_remote_follower_summary">Mostrar seguidores de outras instâncias</string>
|
||||
<string name="mo_setting_relocate_publish_summary">Move o botão de publicar para a barra de baixo</string>
|
||||
<string name="mo_setting_default_reply_privacy_summary">Respostas serão removidas dos recursos de descoberta</string>
|
||||
<string name="mo_setting_interaction_count_summary">Mostrar quantas pessoas interagiram com uma publicação na linha do tempo</string>
|
||||
<string name="mo_setting_disable_swipe_summary">Deslizar para mudar a linha do tempo vista</string>
|
||||
<string name="mo_enable_dividers">Mostrar divisores de publicações</string>
|
||||
<string name="mo_notification_management_settings">Gerenciar notificações</string>
|
||||
<string name="mo_double_tap_to_swipe_between_tabs">Toque duplo para deslizar entre abas</string>
|
||||
<string name="mo_setting_true_black_summary">Talvez poupará energia em telas AMOLED</string>
|
||||
<string name="mo_setting_marquee_summary">Desative rolagem de texto nas barras de título</string>
|
||||
<string name="mo_setting_uniform_summary">Usar o ícone do aplicativo para todas as notificações</string>
|
||||
<string name="mo_setting_reduced_motion_summary">Desativar animações nas interações</string>
|
||||
<string name="mo_setting_play_gif_summary">Reproduzir automaticamente GIFs em fotos de perfil e emojis</string>
|
||||
<string name="mo_swap_bookmark_with_reblog_summary">Salvar ou reblogar publicações pela notificação</string>
|
||||
<string name="mo_notification_audience_settings">Audiência de notificação</string>
|
||||
<string name="mo_camera_not_available">Nenhuma camera disponível!</string>
|
||||
</resources>
|
||||
@@ -78,8 +78,8 @@
|
||||
<string name="sk_clear_all_notifications">Limpar todas as notificações</string>
|
||||
<string name="sk_clear_all_notifications_confirm_action">Excluir tudo</string>
|
||||
<string name="sk_loading_fediverse_resource_title">Procurando no Fediverso</string>
|
||||
<string name="sk_undo_reblog">Desfazer reblog</string>
|
||||
<string name="sk_reblog_with_visibility">Reblogar com visibilidade</string>
|
||||
<string name="sk_undo_reblog">Desfazer impulso</string>
|
||||
<string name="sk_reblog_with_visibility">Impulsionar com visibilidade</string>
|
||||
<string name="sk_hashtags_you_follow">Hashtags que você segue</string>
|
||||
<string name="sk_copy_link_to_post">Copiar link da publicação</string>
|
||||
<string name="sk_loading_resource_on_instance_title">Procurando em %s</string>
|
||||
@@ -94,8 +94,8 @@
|
||||
<string name="sk_already_bookmarked">Já salvo</string>
|
||||
<string name="sk_already_favorited">Já favoritado</string>
|
||||
<string name="sk_reply_as">Responder com outra conta</string>
|
||||
<string name="sk_already_reblogged">Já reblogado</string>
|
||||
<string name="sk_reblog_as">Reblogar com outra conta</string>
|
||||
<string name="sk_already_reblogged">Já impulsionado</string>
|
||||
<string name="sk_reblog_as">Impulsionar com outra conta</string>
|
||||
<string name="sk_schedule">Agendar</string>
|
||||
<string name="sk_draft">Rascunho</string>
|
||||
<string name="sk_unsent_posts">Publicações não enviadas</string>
|
||||
@@ -115,7 +115,7 @@
|
||||
<string name="sk_mark_as_draft">Marcar como rascunho</string>
|
||||
<string name="sk_bookmarked_as">Salvo em %s</string>
|
||||
<string name="sk_favorited_as">Favoritado em %s</string>
|
||||
<string name="sk_reblogged_as">Reblogou como %s</string>
|
||||
<string name="sk_reblogged_as">Impulsionou como %s</string>
|
||||
<string name="sk_settings_uniform_icon_for_notifications">Padronizar ícone para todas as notificações</string>
|
||||
<string name="sk_quote_post">Publique sobre isso</string>
|
||||
<string name="sk_draft_or_schedule">Rascunhar ou agendar</string>
|
||||
@@ -128,13 +128,13 @@
|
||||
<string name="sk_create">Criar</string>
|
||||
<string name="sk_create_list_title">Criar lista</string>
|
||||
<string name="sk_list_replies_policy">Mostrar respostas para</string>
|
||||
<string name="sk_list_replies_policy_list">listar membros</string>
|
||||
<string name="sk_list_replies_policy_list">membros da lista</string>
|
||||
<string name="sk_list_replies_policy_none">ninguém</string>
|
||||
<string name="sk_delete_list">Excluir lista</string>
|
||||
<string name="sk_delete_list_confirm">Tem certeza que deseja excluir a lista “%s”\?</string>
|
||||
<string name="sk_edit_list_title">Editar lista</string>
|
||||
<string name="sk_your_lists">Suas listas</string>
|
||||
<string name="sk_list_name_hint">Lista de nomes</string>
|
||||
<string name="sk_list_name_hint">Nome da lista</string>
|
||||
<string name="sk_settings_reduce_motion">Reduzir movimento nas animações</string>
|
||||
<string name="sk_announcements">Comunicados</string>
|
||||
<string name="sk_settings_single_notification">Mostrar apenas uma notificação</string>
|
||||
@@ -192,7 +192,7 @@
|
||||
<string name="sk_attach_file">Anexar arquivo</string>
|
||||
<string name="sk_post_edited">editado</string>
|
||||
<string name="sk_notification_type_update">Publicações editadas</string>
|
||||
<string name="sk_notify_update">Editarem uma publicação reblogada</string>
|
||||
<string name="sk_notify_update">Editarem uma publicação impulsionada</string>
|
||||
<string name="sk_icon_train">Trem</string>
|
||||
<string name="sk_alt_text_missing_title">Sem texto descritivo</string>
|
||||
<string name="sk_alt_text_missing">Pelo menos um anexo não contém uma descrição.</string>
|
||||
@@ -251,27 +251,62 @@
|
||||
<string name="sk_unfinished_attachments">Corrigir anexos\?</string>
|
||||
<string name="sk_settings_glitch_mode_explanation">Habilite isso se sua instância inicial for executada no Glitch. Não é necessário para Hometown ou Akkoma.</string>
|
||||
<string name="sk_collapse">Mostrar menos</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Adicionar \"re:\" nas respostas com Avisos de Conteúdo</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Adicionar \"re:\" nos Avisos de Conteúdo em resposta a</string>
|
||||
<string name="sk_spectator_mode">Modo espectador</string>
|
||||
<string name="sk_quoting_user">Citando %s</string>
|
||||
<string name="sk_settings_reply_visibility_all">Todas as respostas</string>
|
||||
<string name="sk_settings_reply_visibility">Visibilidade da resposta</string>
|
||||
<string name="sk_settings_reply_visibility_self">Respostas para mim</string>
|
||||
<string name="sk_timeline_bubble">Bolha</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Esses são as publicações mais recentes da rede com curadoria dos administradores da sua instância.</string>
|
||||
<string name="sk_signed_up">se inscreveu</string>
|
||||
<string name="sk_reported">reportado</string>
|
||||
<string name="sk_sign_ups">Usuários se inscrevendo</string>
|
||||
<string name="sk_new_reports">Novas reportagens</string>
|
||||
<string name="sk_settings_hide_interaction">Esconder botões de interação</string>
|
||||
<string name="sk_follow_as">Seguir com outra conta</string>
|
||||
<string name="sk_followed_as">Seguindo por %s</string>
|
||||
<string name="sk_settings_hide_fab">Ocultar compositor ao rolar a tela</string>
|
||||
<string name="sk_in_reply">Em resposta</string>
|
||||
<string name="sk_show_thread">Mostrar fio</string>
|
||||
<string name="sk_quoting_user">Citando %s</string>
|
||||
<string name="sk_settings_reply_visibility">Visibilidade das respostas</string>
|
||||
<string name="sk_settings_reply_visibility_all">Todas as respostas</string>
|
||||
<string name="sk_settings_reply_visibility_following">Respostas para meus seguidores</string>
|
||||
<string name="sk_settings_show_new_posts_button">Botão \"Ver novas publicações\"</string>
|
||||
<string name="sk_settings_collapse_long_posts">Colapsar postagens muito longas</string>
|
||||
<string name="sk_settings_reply_visibility_self">Respostas para mim</string>
|
||||
<string name="sk_reacted_with">reagiu com %s</string>
|
||||
<string name="sk_reacted">reagiu</string>
|
||||
<string name="sk_sign_ups">Usuários se inscrevendo</string>
|
||||
<string name="sk_new_reports">Novas relatórios</string>
|
||||
<string name="sk_reported">reportado</string>
|
||||
<string name="sk_settings_auto_reveal_equal_spoilers">Revelar Avisos de Conteúdo em respostas de</string>
|
||||
<string name="sk_settings_auto_reveal_nobody">ninguém</string>
|
||||
<string name="sk_settings_auto_reveal_author">autor</string>
|
||||
<string name="sk_settings_auto_reveal_anyone">todo mundo</string>
|
||||
<string name="sk_open_in_app_failed">Não foi possível abrir no app</string>
|
||||
<string name="sk_external_share_title">Compartilhar com conta</string>
|
||||
<string name="sk_external_share_or_open_title">Compartilhar ou abrir com conta</string>
|
||||
<string name="sk_no_remote_info_hint">informação remota indisponível</string>
|
||||
<string name="sk_error_loading_profile">Falha em carregar o perfil via %s</string>
|
||||
<string name="sk_settings_allow_remote_loading">Carregar informações de instâncias remotas</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Tentar buscar listagens mais precisas de seguidores, curtidas e impulsos carregando a informação pela instância de origem.</string>
|
||||
<string name="sk_settings_prefix_replies_always">todo mundo</string>
|
||||
<string name="sk_settings_prefix_replies_never">ninguém</string>
|
||||
<string name="sk_settings_prefix_replies_to_others">outros</string>
|
||||
<string name="sk_content_type">Tipo de conteúdo</string>
|
||||
<string name="sk_content_type_unspecified">Não especificado</string>
|
||||
<string name="sk_content_type_plain">Texto simples</string>
|
||||
<string name="sk_content_type_html">HTML</string>
|
||||
<string name="sk_content_type_markdown">Markdown</string>
|
||||
<string name="sk_content_type_bbcode">BBCode</string>
|
||||
<string name="sk_content_type_mfm">MFM</string>
|
||||
<string name="sk_settings_content_types">Habilitar formatação de publicação</string>
|
||||
<string name="sk_settings_content_types_explanation">Permite configurar um tipo de conteúdo como o Markdown na criação de uma publicação. Lembre-se que nem todas as instâncias suportam isso.</string>
|
||||
<string name="sk_settings_default_content_type">Tipo de conteúdo padrão</string>
|
||||
<string name="sk_settings_default_content_type_explanation">Permite pré-selecionar um tipo de conteúdo quando criar novas publicações, substituindo o valor definido em \"Preferências de publicação\".</string>
|
||||
<string name="sk_settings_forward_report_default">\"Encaminhar denúncia\" interruptor padrão</string>
|
||||
<string name="sk_in_reply">Em resposta</string>
|
||||
<string name="sk_settings_hide_interaction">Esconder botões de interação</string>
|
||||
<string name="sk_follow_as">Seguir em outra conta</string>
|
||||
<string name="sk_followed_as">Seguido como %s</string>
|
||||
<string name="sk_notification_action_replied">Resposta enviada para %s</string>
|
||||
<string name="sk_reply_line_above_avatar">Linha \"Em resposta para\" acima do avatar</string>
|
||||
<string name="sk_compact_reblog_reply_line">Linha resposta/reblog compacta</string>
|
||||
<string name="sk_settings_confirm_before_reblog">Confirmar antes de reblogar</string>
|
||||
<string name="sk_reply_line_above_avatar">Em resposta a</string>
|
||||
<string name="sk_compact_reblog_reply_line">Linha compacta resposta/impulsionar</string>
|
||||
<string name="sk_settings_confirm_before_reblog">Confirmar antes de impulsionar</string>
|
||||
<string name="sk_settings_collapse_long_posts">Colapsar publicações muito longas</string>
|
||||
<string name="sk_settings_hide_fab">Esconder automaticamente o botão Escrever</string>
|
||||
<string name="sk_show_thread">Mostrar fio</string>
|
||||
<string name="sk_instance_info_unavailable">Informação de instância temporariamente inacessível</string>
|
||||
<string name="sk_open_in_app">Abrir no app</string>
|
||||
<string name="sk_icon_feed">Feed</string>
|
||||
<string name="sk_exclusive_list">Lista exclusiva</string>
|
||||
<string name="sk_list_exclusive_switch">Tornar lista exclusiva</string>
|
||||
<string name="sk_list_exclusive_switch_explanation">Membros de uma lista exclusiva não irão aparecer na sua linha do tempo inicial - somente se sua instância suportar.</string>
|
||||
</resources>
|
||||
@@ -1,44 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="sk_app_name">Megalodon</string>
|
||||
<string name="sk_pinned_posts">Fixado</string>
|
||||
<string name="sk_delete_and_redraft">Apagar e reescrever</string>
|
||||
<string name="sk_confirm_delete_and_redraft">Tem a certeza que pretende apagar e reescrever esta publicação\?</string>
|
||||
<string name="sk_confirm_unpin_post">Tem a certeza que deseja desafixar esta publicação\?</string>
|
||||
<string name="sk_settings_reply_visibility_all">Todas as respostas</string>
|
||||
<string name="sk_settings_load_new_posts">Carregar novas publicações automaticamente</string>
|
||||
<string name="sk_user_post_notifications_off">Desligar as notificações de publicação para %s</string>
|
||||
<string name="sk_federated_timeline_info_banner">Estas são as publicações mais recentes das pessoas na tua federação.</string>
|
||||
<string name="sk_confirm_delete_and_redraft_title">Apagar e reescrever publicação</string>
|
||||
<string name="sk_pin_post">Fixar no perfil</string>
|
||||
<string name="sk_confirm_pin_post_title">Fixar publicação no perfil</string>
|
||||
<string name="sk_confirm_pin_post">Deseja fixar esta publicação ao seu perfil\?</string>
|
||||
<string name="sk_pinning">A fixar a publicação…</string>
|
||||
<string name="sk_unpin_post">Desafixar do perfil</string>
|
||||
<string name="sk_confirm_unpin_post_title">Desafixar publicação do perfil</string>
|
||||
<string name="sk_unpinning">A desafixar publicação…</string>
|
||||
<string name="sk_image_description">Descrição da imagem</string>
|
||||
<string name="sk_visibility_unlisted">Não listado</string>
|
||||
<string name="sk_settings_show_replies">Mostrar respostas</string>
|
||||
<string name="sk_quoting_user">Citação %s</string>
|
||||
<string name="sk_settings_reply_visibility">Visibilidade da resposta</string>
|
||||
<string name="sk_clear_recent_languages">Apagar idiomas usados recentemente</string>
|
||||
<string name="sk_settings_reply_visibility_following">Respostas aos meus comentários</string>
|
||||
<string name="sk_settings_reply_visibility_self">Respostas a mim</string>
|
||||
<string name="sk_settings_show_boosts">Mostrar impulsionamentos</string>
|
||||
<string name="sk_settings_show_interaction_counts">Mostrar contagem de interações</string>
|
||||
<string name="sk_settings_app_version">Megalodon v%1$s (%2$d)</string>
|
||||
<string name="sk_mark_media_as_sensitive">Marcar conteúdo como sensível</string>
|
||||
<string name="sk_user_post_notifications_on">Ligar as notificações de publicação para %s</string>
|
||||
<string name="sk_federated_timeline">Federação</string>
|
||||
<string name="sk_update_available">Megalodon %s pronto a descarregar.</string>
|
||||
<string name="sk_update_ready">Megalodon %s descarregado e pronto a instalar.</string>
|
||||
<string name="sk_check_for_update">A verificar atualizações</string>
|
||||
<string name="sk_no_update_available">Sem atualizações disponíveis</string>
|
||||
<string name="sk_list_timelines">Listas</string>
|
||||
<string name="sk_follow_requests">Pedidos para seguir</string>
|
||||
<string name="sk_accept_follow_request">Aceitar pedido para seguir</string>
|
||||
<string name="sk_reject_follow_request">Rejeitar pedido para seguir</string>
|
||||
<string name="sk_lists_with_user">Listas com %s</string>
|
||||
<string name="sk_settings_always_reveal_content_warnings">Mostrar sempre avisos de conteúdo</string>
|
||||
</resources>
|
||||
<resources></resources>
|
||||
@@ -63,4 +63,6 @@
|
||||
<string name="mo_enable_dividers">Показать разделители публикаций</string>
|
||||
<string name="mo_double_tap_to_swipe_between_tabs">Двойное нажатие для переключения между вкладками</string>
|
||||
<string name="mo_load_remote_followers">Загружать подписки и подписчиков других инстансов</string>
|
||||
<string name="mo_open_camera">Сделать фото</string>
|
||||
<string name="mo_setting_marquee_summary">Отключает прокрутку заголовка в виде эллипса</string>
|
||||
</resources>
|
||||
@@ -257,7 +257,7 @@
|
||||
<string name="sk_settings_collapse_long_posts">Сворачивать очень длинные посты</string>
|
||||
<string name="sk_unfinished_attachments">Исправить вложения\?</string>
|
||||
<string name="sk_unfinished_attachments_message">Некоторые вложения еще не загрузились.</string>
|
||||
<string name="sk_quoting_user">Цитирование%s</string>
|
||||
<string name="sk_quoting_user">Цитирование %s</string>
|
||||
<string name="sk_settings_reply_visibility">Видимость ответа</string>
|
||||
<string name="sk_settings_hide_interaction">Скрыть кнопки взаимодействия</string>
|
||||
<string name="sk_follow_as">Подписаться с другого аккаунта</string>
|
||||
@@ -286,4 +286,20 @@
|
||||
<string name="sk_settings_default_content_type">Тип контента по умолчанию</string>
|
||||
<string name="sk_settings_default_content_type_explanation">Это позволяет вам предварительно выбирать тип контента при создании новых публикаций, переопределяя значение, установленное в “Настройках публикации”.</string>
|
||||
<string name="sk_settings_show_new_posts_button">Кнопка \"Показать новые посты\"</string>
|
||||
<string name="sk_open_in_app">Открыть в приложении</string>
|
||||
<string name="sk_open_in_app_failed">Не удалось открыть в приложении</string>
|
||||
<string name="sk_external_share_or_open_title">Поделиться или открыть с помощью учетной записи</string>
|
||||
<string name="sk_no_remote_info_hint">дистанционная информация недоступна</string>
|
||||
<string name="sk_settings_allow_remote_loading">Загрузка информации из других инстансов</string>
|
||||
<string name="sk_settings_auto_reveal_equal_spoilers">Выявить одинаковые предупреждения в чатах</string>
|
||||
<string name="sk_settings_auto_reveal_never">Никогда</string>
|
||||
<string name="sk_settings_auto_reveal_threads">Тот же автор</string>
|
||||
<string name="sk_settings_auto_reveal_discussions">Обсуждения</string>
|
||||
<string name="sk_settings_auto_reveal_always">Всегда</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Это самые последние сообщения из сети, созданные администраторами вашего экземпляра.</string>
|
||||
<string name="sk_timeline_bubble">Пузырь</string>
|
||||
<string name="sk_instance_info_unavailable">Информация об инстансе временно недоступна</string>
|
||||
<string name="sk_external_share_title">Поделиться с аккаунтом</string>
|
||||
<string name="sk_error_loading_profile">Не удалось загрузить профиль через %s</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Попробуйте получить более точные данные о подписчиках, лайках и увеличениях, загрузив информацию из инстанса автора.</string>
|
||||
</resources>
|
||||
@@ -269,6 +269,7 @@
|
||||
<string name="hide_content">Dölj innehåll</string>
|
||||
<string name="new_post">Nytt inlägg</string>
|
||||
<string name="button_reply">Svara</string>
|
||||
<string name="button_reblog">Boosta</string>
|
||||
<string name="button_favorite">Favoritmarkera</string>
|
||||
<string name="button_share">Dela</string>
|
||||
<string name="media_no_description">Media utan beskrivning</string>
|
||||
@@ -428,5 +429,7 @@
|
||||
<string name="signup_or_login">eller</string>
|
||||
<string name="learn_more">Läs mer</string>
|
||||
<string name="welcome_to_mastodon">Välkommen till Mastodon</string>
|
||||
<string name="welcome_paragraph1">Mastodon är ett decentraliserat socialt nätverk, vilket innebär att inget enskilt företag kontrollerar det. Det består av många oberoende servrar, alla sammankopplade.</string>
|
||||
<string name="what_are_servers">Vad är servrar?</string>
|
||||
<string name="welcome_paragraph2"><![CDATA[Varje Mastodon-konto finns på en server — var och en med sina värderingar, regler och administratörer. Oavsett vilken du väljer kan du följa och interagera med människor på vilken server som helst.]]></string>
|
||||
</resources>
|
||||
|
||||
@@ -254,7 +254,7 @@
|
||||
<string name="sk_expand">Розгорнути</string>
|
||||
<string name="sk_collapse">Згорнути</string>
|
||||
<string name="sk_unfinished_attachments">Виправити вкладення\?</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Префікс «re:» під час відповіді CW</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Префікс CW під час відповіді</string>
|
||||
<string name="sk_settings_collapse_long_posts">Згортати надто довгі дописи</string>
|
||||
<string name="sk_unfinished_attachments_message">Деякі вкладення не повністю завантажилися.</string>
|
||||
<string name="sk_spectator_mode">Режим глядача</string>
|
||||
@@ -293,4 +293,16 @@
|
||||
<string name="sk_instance_info_unavailable">Сервер тимчасово недоступний</string>
|
||||
<string name="sk_external_share_or_open_title">Поділитися або відкрити за допомогою облікового запису</string>
|
||||
<string name="sk_open_in_app_failed">Не вдалося відкрити в застосунку</string>
|
||||
<string name="sk_no_remote_info_hint">віддалена інформація недоступна</string>
|
||||
<string name="sk_settings_allow_remote_loading">Завантажити інформацію з віддалених серверів</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Спробуйте отримати точніші списки підписників, вподобань і поширень, завантаживши інформацію з джерела.</string>
|
||||
<string name="sk_error_loading_profile">Не вдалося завантажити профіль через %s</string>
|
||||
<string name="sk_settings_auto_reveal_equal_spoilers">Виявити ті самі CW у відповідях від</string>
|
||||
<string name="sk_settings_auto_reveal_nobody">нікого</string>
|
||||
<string name="sk_settings_auto_reveal_author">автора</string>
|
||||
<string name="sk_settings_auto_reveal_anyone">будь-кого</string>
|
||||
<string name="sk_settings_prefix_replies_always">усіх</string>
|
||||
<string name="sk_settings_prefix_replies_never">нікого</string>
|
||||
<string name="sk_settings_prefix_replies_to_others">іншим</string>
|
||||
<string name="sk_settings_forward_report_default">Усталений перемикач “Пересилати звіт”</string>
|
||||
</resources>
|
||||
@@ -31,16 +31,16 @@
|
||||
<string name="mo_duration_indefinite">无限</string>
|
||||
<string name="mo_duration_minutes_30">30分钟</string>
|
||||
<string name="mo_duration_hours_1">1小时</string>
|
||||
<string name="mo_change_default_reply_visibility_to_unlisted">将回复的默认可见性改为“不公开列出”</string>
|
||||
<string name="mo_change_default_reply_visibility_to_unlisted">默认以“不公开可见”发送回复</string>
|
||||
<string name="mo_duration_days_7">7天</string>
|
||||
<string name="mo_composer_behavior">作者的行为</string>
|
||||
<string name="mo_composer_behavior">写作行为</string>
|
||||
<string name="mo_miscellaneous_settings">杂项设置</string>
|
||||
<string name="mo_share_open_url">应用内打开</string>
|
||||
<string name="mo_disable_double_tap_to_swipe_between_tabs">禁用双击以切换标签页</string>
|
||||
<string name="mo_download_latest_nightly_release">下载最新的每日版</string>
|
||||
<string name="mo_load_remote_followers">加载远程账户的关注者和粉丝</string>
|
||||
<string name="mo_mention_reblogger_automatically">自动在回复中提及转发帖子的账户</string>
|
||||
<string name="mo_swap_bookmark_with_reblog">在通知中使用转载操作而不是收藏操作</string>
|
||||
<string name="mo_swap_bookmark_with_reblog">使用转载操作替换收藏操作</string>
|
||||
<string name="mo_confirm_unfollow_title">取消关注</string>
|
||||
<string name="mo_confirm_unfollow">确认取消关注 %s</string>
|
||||
<string name="mo_instance_admin">管理者</string>
|
||||
@@ -51,4 +51,23 @@
|
||||
<string name="mo_instance_registration_open">打开</string>
|
||||
<string name="mo_instance_registration_approval">需要审核</string>
|
||||
<string name="mo_instance_info_open_timeline">本地时间线</string>
|
||||
<string name="mo_instance_info_moderated_servers">被限制的服务器</string>
|
||||
<string name="mo_severity_silence">已隐藏</string>
|
||||
<string name="mo_severity_suspend">已屏蔽</string>
|
||||
<string name="mo_setting_marquee_summary">禁用过长标题的滚动</string>
|
||||
<string name="mo_setting_uniform_summary">全部通知统一使用应用图标</string>
|
||||
<string name="mo_setting_reduced_motion_summary">禁用互动动画</string>
|
||||
<string name="mo_setting_play_gif_summary">自动播放头像与表情中的GIF图片</string>
|
||||
<string name="mo_setting_remote_follower_summary">显示来自其他实例的关注者</string>
|
||||
<string name="mo_setting_relocate_publish_summary">将发布按钮移至底栏</string>
|
||||
<string name="mo_swap_bookmark_with_reblog_summary">在通知中收藏或转发帖文</string>
|
||||
<string name="mo_setting_disable_swipe_summary">滑动以切换浏览的时间线</string>
|
||||
<string name="mo_notification_audience_settings">消息通知受众</string>
|
||||
<string name="mo_open_camera">拍照</string>
|
||||
<string name="mo_setting_default_reply_privacy_summary">回复将不会被显示在发现功能中</string>
|
||||
<string name="mo_setting_interaction_count_summary">在时间线中显示与帖文互动的人数</string>
|
||||
<string name="mo_enable_dividers">显示帖文分割线</string>
|
||||
<string name="mo_notification_management_settings">管理消息通知</string>
|
||||
<string name="mo_setting_true_black_summary">AMOLED屏幕上可能更省电</string>
|
||||
<string name="mo_double_tap_to_swipe_between_tabs">双击以在选项卡之间滑动</string>
|
||||
</resources>
|
||||
@@ -100,7 +100,7 @@
|
||||
<string name="sk_reblogged_as">已用 %s 转嘟</string>
|
||||
<string name="sk_already_reblogged">已转嘟过</string>
|
||||
<string name="sk_reply_as">用其他帐号回复</string>
|
||||
<string name="sk_settings_uniform_icon_for_notifications">所有通知的统一图标</string>
|
||||
<string name="sk_settings_uniform_icon_for_notifications">统一通知图标</string>
|
||||
<string name="sk_unsent_posts">未发送的嘟文</string>
|
||||
<string name="sk_confirm_delete_draft_title">删除草稿</string>
|
||||
<string name="sk_draft">草稿</string>
|
||||
@@ -257,7 +257,7 @@
|
||||
<string name="sk_unfinished_attachments_message">部分附件尚未上传完毕。</string>
|
||||
<string name="sk_filtered">已过滤:%s</string>
|
||||
<string name="sk_settings_collapse_long_posts">折叠很长的嘟文</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">在回复带有内容警告的嘟文前加上 \"re:\"</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">回复时在内容警告前加上 “re:”</string>
|
||||
<string name="sk_spectator_mode">旁观模式</string>
|
||||
<string name="sk_settings_hide_interaction">隐藏互动按钮</string>
|
||||
<string name="sk_settings_reply_visibility_self">对我的回复</string>
|
||||
@@ -275,7 +275,7 @@
|
||||
<string name="sk_show_thread">显示对话</string>
|
||||
<string name="sk_settings_show_new_posts_button">“显示新嘟文”按钮</string>
|
||||
<string name="sk_reacted">已回应</string>
|
||||
<string name="sk_settings_confirm_before_reblog">转读前确认</string>
|
||||
<string name="sk_settings_confirm_before_reblog">转嘟前确认</string>
|
||||
<string name="sk_reacted_with">已以 %s 回应</string>
|
||||
<string name="sk_content_type_plain">纯文本</string>
|
||||
<string name="sk_content_type_html">HTML</string>
|
||||
@@ -288,4 +288,23 @@
|
||||
<string name="sk_content_type_unspecified">未指明</string>
|
||||
<string name="sk_settings_content_types_explanation">创建帖文时允许设置内容类型(如 Markdown)。请注意并非所有实例都支持该特性。</string>
|
||||
<string name="sk_settings_default_content_type_explanation">该选项允许你为创建的帖文预设一种内容类型,将覆盖「发帖偏好」中的设置参数。</string>
|
||||
<string name="sk_timeline_bubble">局域气泡</string>
|
||||
<string name="sk_instance_info_unavailable">暂时无法获取实例信息</string>
|
||||
<string name="sk_open_in_app">在应用中打开</string>
|
||||
<string name="sk_open_in_app_failed">无法在应用中打开</string>
|
||||
<string name="sk_external_share_title">使用指定账户分享</string>
|
||||
<string name="sk_external_share_or_open_title">使用指定账户分享或打开</string>
|
||||
<string name="sk_no_remote_info_hint">无法获取远程信息</string>
|
||||
<string name="sk_error_loading_profile">通过 %s 加载个人资料失败</string>
|
||||
<string name="sk_settings_allow_remote_loading">从远程实例中加载信息</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">通过从原始实例中加载信息,尝试拉取更精确的关注者列表、喜欢以及转发。</string>
|
||||
<string name="sk_settings_auto_reveal_equal_spoilers">一并显示含有相同内容警告的回复</string>
|
||||
<string name="sk_settings_prefix_replies_always">所有人</string>
|
||||
<string name="sk_settings_prefix_replies_to_others">其他人</string>
|
||||
<string name="sk_settings_auto_reveal_nobody">无</string>
|
||||
<string name="sk_settings_prefix_replies_never">无</string>
|
||||
<string name="sk_settings_forward_report_default">默认开启“转发举报”至原始服务器</string>
|
||||
<string name="sk_bubble_timeline_info_banner">这些是您所在实例的管理员所精选的网络上的最新嘟文。</string>
|
||||
<string name="sk_settings_auto_reveal_author">作者</string>
|
||||
<string name="sk_settings_auto_reveal_anyone">所有人</string>
|
||||
</resources>
|
||||
@@ -33,6 +33,7 @@
|
||||
<string name="mo_composer_behavior">Composer\'s Behavior</string>
|
||||
<string name="mo_notification_management_settings">Manage Notifications</string>
|
||||
<string name="mo_open_camera">Take picture</string>
|
||||
<string name="mo_camera_not_available">No camera available!</string>
|
||||
|
||||
<!-- accessibility labels-->
|
||||
<string name="mo_poll_option_add">Add new poll option</string>
|
||||
|
||||
@@ -227,6 +227,7 @@
|
||||
<string name="sk_icon_human">Human</string>
|
||||
<string name="sk_icon_globe">Globe</string>
|
||||
<string name="sk_icon_pin">Pin</string>
|
||||
<string name="sk_icon_feed">Feed</string>
|
||||
<string name="sk_edit_timeline">Edit timeline</string>
|
||||
<string name="sk_edit_timelines">Edit timelines</string>
|
||||
<string name="sk_alt_button">ALT</string>
|
||||
@@ -260,7 +261,7 @@
|
||||
<string name="sk_new_reports">New reports</string>
|
||||
<string name="sk_settings_server_version">Server version: %s</string>
|
||||
<string name="sk_notify_poll_results">Poll results</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Prefix reply CW with “re:”</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Prefix CW with “re:” on replies to</string>
|
||||
<string name="sk_filtered">Filtered: %s</string>
|
||||
<string name="sk_expand">Expand</string>
|
||||
<string name="sk_collapse">Collapse</string>
|
||||
@@ -297,9 +298,15 @@
|
||||
<string name="sk_error_loading_profile">Failed loading the profile via %s</string>
|
||||
<string name="sk_settings_allow_remote_loading">Load info from remote instances</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Try fetching more accurate listings for followers, likes and boosts by loading the information from the instance of origin.</string>
|
||||
<string name="sk_settings_auto_reveal_equal_spoilers">Reveal equal CWs in threads</string>
|
||||
<string name="sk_settings_auto_reveal_never">Never</string>
|
||||
<string name="sk_settings_auto_reveal_threads">Same author</string>
|
||||
<string name="sk_settings_auto_reveal_discussions">Discussions</string>
|
||||
<string name="sk_settings_auto_reveal_always">Always</string>
|
||||
<string name="sk_settings_auto_reveal_equal_spoilers">Reveal same CWs in replies from</string>
|
||||
<string name="sk_settings_auto_reveal_nobody">nobody</string>
|
||||
<string name="sk_settings_auto_reveal_author">author</string>
|
||||
<string name="sk_settings_auto_reveal_anyone">everyone</string>
|
||||
<string name="sk_settings_prefix_replies_always">everyone</string>
|
||||
<string name="sk_settings_prefix_replies_never">nobody</string>
|
||||
<string name="sk_settings_prefix_replies_to_others">others</string>
|
||||
<string name="sk_settings_forward_report_default">“Forward report” switch default</string>
|
||||
<string name="sk_exclusive_list">Exclusive list</string>
|
||||
<string name="sk_list_exclusive_switch">Make list exclusive</string>
|
||||
<string name="sk_list_exclusive_switch_explanation">Members of an exclusive list will not show up on your home timeline – if your instance supports it.</string>
|
||||
</resources>
|
||||
7
mastodon/src/main/res/xml/file_paths.xml
Normal file
7
mastodon/src/main/res/xml/file_paths.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<external-path name="external_files" path="." />
|
||||
<cache-path
|
||||
name="cache"
|
||||
path="."/>
|
||||
</paths>
|
||||
9
metadata/en-US/changelogs/101.txt
Normal file
9
metadata/en-US/changelogs/101.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
- Completely redesigned settings fragment
|
||||
- New camera shortcut added to the Composition fragment
|
||||
- Fixes many crashes on Calckey
|
||||
- Many improvements to Akkoma/Pleroma
|
||||
- Many improvements to filters behavior
|
||||
- New redesigned Account sheet
|
||||
- Add the option to change post content style (Plain text, markdown, etc) on instances that support it
|
||||
- Add the ability to view one's server info by tapping on their username inside of anyone's profile
|
||||
- Many, many small fixes and improvements
|
||||
6
metadata/pt-BR/changelogs/59.txt
Normal file
6
metadata/pt-BR/changelogs/59.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
- Adiciona seletor de idiomas
|
||||
- Adiciona funcionalidade de tradução
|
||||
- Melhora a semântica de votar em enquetes (botões exclusivos e inclusivos)
|
||||
- Adiciona opção para permitir votar em multiplas opções nas enquetes
|
||||
- Nova tela de login
|
||||
- Correção de bugs
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user