Compare commits

...

19 Commits

Author SHA1 Message Date
mr.bumper Gonzalez perez f7e92072b7 Translated using Weblate (Spanish)
Currently translated at 83.8% (52 of 62 strings)

Translation: Husky/Husky translations
Translate-URL: https://l10n.mentality.rip/projects/husky/husky-translations/es/
2020-06-24 11:17:19 +02:00
Alibek Omarov 467bfefde6 ComposeActivity: preview ability for Pleroma, WIP 2020-06-23 18:18:41 +03:00
Konrad Pozniak a38acd34a5 fix MainActivity title when coming from a notification (#1844) 2020-06-23 12:47:22 +03:00
Konrad Pozniak 7084031f59 upgrade gradle wrapper, materialdrawer and AndroidX fragment (#1837) 2020-06-23 12:47:15 +03:00
Konrad Pozniak 426db0ca88 speedup blurhash decoding by using a intermediate int array (#1835) 2020-06-23 12:47:06 +03:00
Konrad Pozniak fe6f34fd9f don't create a CW when sharing text to Tusky (#1836) 2020-06-23 12:46:57 +03:00
Konrad Pozniak 2e40fe0055 add possibility to move the main navigation to the bottom (#1808)
* add possibility to move the main navigation to the bottom

* add top toolbar with drawer toggle, title and search button
2020-06-23 12:46:43 +03:00
Alibek Omarov 4e9c5254ed app: patch to compile 2020-06-16 14:55:55 +03:00
Ivan Kupalov ed646a8b02 Fix settings default values for preferences (#1828)
At some point settings DSL was refactored to first add a preference and
then run the builder. We shouldn't add a preference to the hierarchy
without setting a key for the preference first because preference gets
it's default value in `onAttachedToHierarchy()` and if the key is not
set then no default value will be set either.

This commit changes the order to execute builder (and set the key)
first and and preference to the point later.
2020-06-15 17:08:36 +03:00
Levi Bard bac8f93a26 Fix preview cards for uncollapsible statuses (#1826) 2020-06-15 17:08:24 +03:00
Ivan Kupalov 3663e263d4 Settings refactor (#1615)
* Refactor main preferences to use DSL

* Refactor account preferences to use DSL

* Use DSL in rest of the preference screens

* Preferences cleanup

* Fix preference dependencies
2020-06-15 17:08:12 +03:00
Ivan Kupalov 7df234ceaa Fix loading more than one page of favs/bookmarks, fix #1824 (#1825) 2020-06-15 16:40:00 +03:00
Conny Duck b77a128d79 downgrade dagger to get rid of READ_PHONE_STATE permission
https://github.com/google/dagger/issues/1864
2020-06-15 16:39:49 +03:00
Konrad Pozniak bd68374ed5 cleanup proguard rules (#1819) 2020-06-15 16:39:27 +03:00
Konrad Pozniak 77abeef6e1 upgrade gradle, AGP and dependencies (#1818)
* upgrade gradle, AGP and dependencies

* fix new layout related warnings
2020-06-15 16:39:06 +03:00
Levi Bard e177eb8e70 One more layout fix for length-collapsed posts with preview cards (#1805) 2020-06-15 16:25:16 +03:00
Konrad Pozniak b965cc6766 use AndroidX WorkManager instead of Evernote Android Job (#1783)
* use AndroidX WorkManager instead of Evernote Android Job

* move notification related classes to their own package

* fix missing import
2020-06-15 16:22:35 +03:00
Alibek Omarov c7808b4e0f l10n: upgrade generated translations 2020-06-15 16:14:27 +03:00
Alibek Omarov 85f23411ff l10n: upgrade translations from upstream 2020-06-15 16:09:36 +03:00
89 changed files with 2156 additions and 1234 deletions

View File

@ -112,35 +112,37 @@ project.tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
ext.lifecycleVersion = "2.2.0"
ext.roomVersion = '2.2.5'
ext.retrofitVersion = '2.8.1'
ext.okhttpVersion = '4.4.0'
ext.retrofitVersion = '2.9.0'
ext.okhttpVersion = '4.7.2'
ext.glideVersion = '4.11.0'
ext.daggerVersion = '2.27'
ext.materialdrawerVersion = '8.0.1'
ext.materialdrawerVersion = '8.1.2'
// if libraries are changed here, they should also be changed in LicenseActivity
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "androidx.core:core-ktx:1.2.0"
implementation "androidx.appcompat:appcompat:1.2.0-alpha02"
implementation "androidx.fragment:fragment-ktx:1.2.2"
implementation "androidx.core:core-ktx:1.3.0"
implementation "androidx.appcompat:appcompat:1.2.0-rc01"
implementation "androidx.fragment:fragment-ktx:1.2.5"
implementation "androidx.browser:browser:1.2.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation "androidx.exifinterface:exifinterface:1.1.0"
implementation "androidx.exifinterface:exifinterface:1.2.0"
implementation "androidx.cardview:cardview:1.0.0"
implementation "androidx.preference:preference:1.1.0"
implementation "androidx.sharetarget:sharetarget:1.0.0-rc01"
implementation "androidx.emoji:emoji:1.1.0-beta01"
implementation "androidx.emoji:emoji-appcompat:1.1.0-beta01"
implementation "androidx.emoji:emoji-bundled:1.1.0-beta01"
implementation "androidx.preference:preference:1.1.1"
implementation "androidx.sharetarget:sharetarget:1.0.0"
implementation "androidx.emoji:emoji:1.1.0-rc01"
implementation "androidx.emoji:emoji-appcompat:1.1.0-rc01"
implementation "androidx.emoji:emoji-bundled:1.1.0-rc01"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycleVersion"
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
implementation "androidx.paging:paging-runtime-ktx:2.1.1"
implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation "androidx.work:work-runtime:2.3.4"
implementation "androidx.room:room-runtime:$roomVersion"
implementation "androidx.room:room-rxjava2:$roomVersion"
kapt "androidx.room:room-compiler:$roomVersion"
@ -161,7 +163,7 @@ dependencies {
implementation "com.github.bumptech.glide:glide:$glideVersion"
implementation "com.github.bumptech.glide:okhttp3-integration:$glideVersion"
implementation "io.reactivex.rxjava2:rxjava:2.2.16"
implementation "io.reactivex.rxjava2:rxjava:2.2.19"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation "io.reactivex.rxjava2:rxkotlin:2.4.0"
@ -184,14 +186,12 @@ dependencies {
implementation "com.theartofdev.edmodo:android-image-cropper:2.8.0"
implementation "com.evernote:android-job:1.4.2"
implementation "de.c1710:filemojicompat:1.0.17"
implementation 'com.github.Tunous:MarkdownEdit:1.0.0'
testImplementation "androidx.test.ext:junit:1.1.1"
testImplementation "org.robolectric:robolectric:4.3.1"
testImplementation "org.mockito:mockito-inline:3.2.4"
testImplementation "org.mockito:mockito-inline:3.3.3"
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
androidTestImplementation "androidx.test.espresso:espresso-core:3.2.0"

View File

@ -17,12 +17,12 @@
# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
void set*(***);
*** get*();
}
# We want to keep methods in Activity that could be used in the XML attribute onClick
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
public void *(android.view.View);
}
# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
@ -30,43 +30,19 @@
public static ** valueOf(java.lang.String);
}
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
-keepclassmembers class **.R$* {
public static <fields>;
public static final ** CREATOR;
}
# TUSKY SPECIFIC OPTIONS
## for okhttp
-dontwarn javax.annotation.**
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
-dontwarn org.codehaus.mojo.animal_sniffer.*
-dontwarn okhttp3.internal.platform.ConscryptPlatform
##for keep
-dontwarn android.arch.util.paging.CountedDataSource
-dontwarn android.arch.persistence.room.paging.LimitOffsetDataSource
## for retrofit
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions
-keepattributes *Annotation*
-keepclasseswithmembers class * {
@retrofit2.http.* <methods>;
}
-keep class com.keylesspalace.tusky.entity.** { *; }
# keep members of our model classes, they are used in json de/serialization
-keepclassmembers class com.keylesspalace.tusky.entity.* { *; }
-keep public enum com.keylesspalace.tusky.entity.*$** {
**[] $VALUES;
public *;
}
# preserve line numbers for crash reporting
-keepattributes SourceFile,LineNumberTable
-renamesourcefileattribute SourceFile
@ -87,17 +63,7 @@
static void throwUninitializedPropertyAccessException(java.lang.String);
}
-dontwarn com.google.errorprone.annotations.*
# without this emoji font downloading fails with AbstractMethodError
-keep class * extends android.os.AsyncTask {
public *;
}
# Glide
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}

View File

@ -1,6 +1,5 @@
<!-- These are mostly literal translations; I'm not sure what the usual jargon in Spanish for some terms (like "reacting") is -->
<resources>
<?xml version="1.0" encoding="utf-8"?>
<!-- These are mostly literal translations; I'm not sure what the usual jargon in Spanish for some terms (like "reacting") is --><resources>
<string name="action_reply_to">Responder a</string>
<!-- "Unmute" does not have a proper equivalent in Spanish so I chose "Show". Consequently, I used "Hide" instead of "Mute" in the previous one. -->
<string name="action_emoji_react">Reaccionar</string>
@ -8,24 +7,65 @@
<string name="action_emoji_reacted_by">Quién ha reaccionado</string>
<string name="action_enable_formatting_syntax">Activar %s</string>
<string name="action_disable_formatting_syntax">Desactivar %s</string>
<string name="title_emoji_reacted_by">%s ha reaccionado con</string>
<string name="hint_appname">Nombre de la aplicación</string>
<string name="hint_website">Página web de la aplicación</string>
<string name="admin">Administrador/a</string>
<!-- "Admin" in Spanish is a gendered word. I used "masculine/femenine" since it is the more conservative option and should avoid more complaints than the informal neutral. -->
<string name="moderator">Moderador/a</string>
<string name="error_media_upload_size">El archivo es demasiado grande</string>
<string name="notification_emoji_format">%s ha reaccionado con %s a tu publicación</string>
<string name="notification_emoji_name">Reacciones</string>
<string name="notification_emoji_description">Notificaciones acerca de reacciones nuevas</string>
<string name="pref_title_default_formatting">
Sintaxis de formato por defecto(si la instancia los admite)</string>
<string name="pref_title_notification_filter_emoji">se pueden enviar reacciones a mis publicaciones</string>
<string name="pref_title_hide_muted_users">Ocultar a los usuarios silenciados</string>
</resources>
<string name="error_sticker_fetch">Se produjo un error al buscar la pegatina</string>
<string name="pref_title_enable_experimental_stickers">Habilite los adhesivos experimentales de Pleroma-FE (si están disponibles)</string>
<string name="dialog_delete_toot_warning">eliminar esta publicación\?</string>
<string name="action_hide_reblogs">esconder repeticiones</string>
<string name="reblog_private">Repetir la audiencia original</string>
<string name="dialog_redraft_toot_warning">eliminar y re-hacer esta publicación\?</string>
<string name="error_sender_account_gone">Error al enviar la publicación</string>
<string name="notification_favourite_description">Notificaciones cuando tus publicaciones se marcan como favoritas</string>
<string name="action_sticker">pegatinas</string>
<string name="pref_title_enable_big_emojis">Habilitar emojis personalizados más grandes</string>
<string name="action_toggle_visibility">Visibilidad de publicaciones</string>
<string name="action_schedule_toot">Programar publicación</string>
<string name="action_reblog">repetir</string>
<string name="action_unreblog">remover repeticiónes</string>
<string name="action_show_reblogs">mostrar repeticiones</string>
<string name="action_send">ENVIAR</string>
<string name="action_open_reblogger">Abrir autor repetido</string>
<string name="action_open_reblogged_by">Mostrar repeticiones</string>
<string name="unreblog_private">remover la repetición</string>
<string name="action_open_toot">abrir publicación</string>
<string name="compose_shortcut_long_label">Redactar publicación</string>
<string name="description_status_reblogged">repetido</string>
<string name="action_send_public">ENVIAR!</string>
<string name="notification_reblog_format">%s repitió tu publicación</string>
<string name="notification_favourite_format">%s marcó tu publicación como favorita</string>
<string name="notification_boost_name">repeticiónes</string>
<string name="notification_boost_description">Notificaciones cuando tus publicaciones se repiten</string>
<string name="pref_title_confirm_reblogs">Mostrar diálogo de confirmación antes de repetir</string>
<string name="pref_title_notification_filter_reblogs">mis publicaciones se repiten</string>
<string name="pref_title_show_boosts">Mostrar repeticiones</string>
<string name="pref_title_alway_open_spoiler">Siempre expandir las publicaciones marcadas con advertencias de contenido</string>
<plurals name="reblogs">
<item quantity="one"><b>%s</b> Repetir</item>
<item quantity="other"><b>%s</b> se repite</item>
</plurals>
<string name="send_status_link_to">Compartir URL de publicación para …</string>
<string name="send_status_content_to">Compartir publicación en …</string>
<string name="send_toot_notification_title">enviando publicación…</string>
<string name="send_toot_notification_channel_name">enviando publicaciones</string>
<string name="send_toot_notification_saved_content">Se ha guardado una copia de la publicación en sus borradores</string>
<string name="status_share_content">Compartir contenido de la publicación</string>
<string name="status_share_link">Compartir enlace para publicar</string>
<string name="status_boosted_format">%s repetido</string>
<string name="title_reblogged_by">Repetido por</string>
<string name="title_view_thread">Enviar</string>
<string name="send_toot_notification_error_title">Error al enviar la publicación</string>
<string name="title_scheduled_toot">Publicaciones programadas</string>
</resources>

View File

@ -47,7 +47,7 @@
<string name="dialog_whats_an_instance">Sartu hemen helbidea edo mastodon.eus, mastodon.jalgi.eus, shitposter.club bezalako <a href="https://fediverse.network/pleroma?count=peers">edozein instantzia</a>,
<string name="dialog_whats_an_instance"> Sartu hemen helbidea edo mastodon.eus, mastodon.jalgi.eus, shitposter.club bezalako <a href="https://fediverse.network/pleroma?count=peers">edozein instantzia</a>,
\n
\n Oraindik ez baduzu konturik, instantziaren izena sartu eta bertan kontua sortu dezakezu.
\n

View File

@ -1,8 +1,6 @@
<resources>
<string name="about_tusky_license">Husky یک برنامه آزاد و متن‌باز است که تحت مجوز GNU General Public License Version 3. منتشر شده است.
\n شما می‌توانید مجوز را از اینجا ببینید:
\n https://www.gnu.org/licenses/gpl-3.0.en.html</string>
<string name="about_tusky_license">تاسکی نرم‌افزاری آزاد است که تحت نگارش ۳ از پروانهٔ جامع همگانی گنو منتشر شده است. پروانه را می‌توانید از این‌جا ببینید: https://www.gnu.org/licenses/gpl-3.0.en.html</string>
<string name="about_tusky_account">نمایهٔ تاسکی</string>
@ -38,7 +36,7 @@
<string name="action_login">ورود با ماستودون</string>
<string name="add_account_description">افزودن حساب ماستودون جدید</string>
<string name="add_account_description">افزودن حساب جدید ماستودون</string>
<string name="warning_scheduling_interval">ماستودون، بازهٔ زمان‌بندی‌ای با کمینهٔ ۵ دقیقه دارد.</string>
@ -46,13 +44,13 @@
<string name="dialog_whats_an_instance">آدرس یا دامنه هر نمونه را می‌توانید وارد کنید، مثلا shitposter.club, blob.cat, expired.mentality.rip, و &lt;a href=\"https://fediverse.network/pleroma?count=peers\" &gt;بیشتر!
\n
\n اگر شما هنوز حساب کاربری ندارید، می‌توانید نام نمونه مورد نظر را وارد کنید از اینجا بپیوندید و حساب کاربری ایجاد کنید.
\n
\n نمونه جایی است که حساب کاربری شما میزبان آن است اما شما به راحتی می‌توانید با افراد دیگر در نمونه‌های دیگر ارتباط برقرار کنید و آنها را دنبال کنید شما درست مثل اینکه در یکجا باشید.
\n
\n برای اطلاعات بیشتر به اینجا مراجعه کنید <a href="https://joinmastodon.org">joinmastodon.org</a>. </string>
<string name="dialog_whats_an_instance">نشانی یا دامنهٔ هر نمونه‌ای می‌تواند وارد شود، مثل shitposter.club, blob.cat, expired.mentality.rip, و <a href="https://fediverse.network/pleroma?count=peers">بیش‌تر!</a>.
\n
\n اگر هنوز حسابی ندارید، می‌توانید نام نمونه مورد نظر را وارد کرده و در آن حسابی بسازید.
\n
\n نمونه، جاییست که حسابتان رویش میزبانی می‌شود، ولی به راحتی می‌توانید با دیگر افراد روی نمونه‌های دیگر ارتباط داشته و دنبالشان کنید؛ انگار که روی یک پایگاه باشید.
\n
\nاطّلاعات بیش‌تر می‌تواند در <a href="https://joinmastodon.org">joinmastodon.org</a> پیدا شود. </string>
</resources>

View File

@ -46,9 +46,9 @@
<string name="dialog_whats_an_instance">Het adres of domein van elke Mastodonserver kan hier worden ingevoerd, zoals shitposter.club, mastodon.nl, octodon.social en <a href="https://fediverse.network/pleroma?count=peers">nog veel meer!</a>
\n
\n Wanneer je nog geen account hebt, kun je de naam van de Mastodonserver waar jij je graag wil registeren invoeren, waarna je daar een account kunt aanmaken.
\nWanneer je nog geen account hebt, kun je de naam van de Mastodonserver waar jij je graag wil registeren invoeren, waarna je daar een account kunt aanmaken.
\n
\n Een Mastodonserver (Engels: instance) is een computerserver waar jouw account zich bevindt (vergelijk het met een e-mailserver). Je kan eenvoudig mensen van andere servers volgen en met ze communiceren, alsof jullie met elkaar op dezelfde website zitten.
\nEen Mastodonserver (Engels: instance) is een computerserver waar jouw account zich bevindt (vergelijk het met een e-mailserver). Je kan eenvoudig mensen van andere servers volgen en met ze communiceren, alsof jullie met elkaar op dezelfde website zitten.
\n
\n Meer informatie kun je vinden op <a href="https://joinmastodon.org">joinmastodon.org</a>.</string>

View File

@ -41,7 +41,7 @@
<string name="add_account_description">Adicionar nova conta Pleroma</string>
<string name="warning_scheduling_interval">Pleroma possui um intervalo mínimo de agendamento de 5 minutos.</string>
<string name="warning_scheduling_interval">Pleroma possui um intervalo mínimo de 5 minutos para agendar.</string>

View File

@ -0,0 +1,62 @@
<resources>
<string name="license_description">Husky มีโค้ดและสินทรัพย์จากโครงการโอเพนซอร์สต่อไปนี้:</string>
<string name="restart_emoji">จำเป็นต้องเริ่ม Husky ใหม่ เพื่อใช้การเปลี่ยนแปลงเหล่านี้</string>
<string name="about_tusky_account">บัญชีทางการของ Husky</string>
<string name="about_tusky_license">Husky คือซอฟต์แวร์เสรีและโอเพนซอร์ส &lt;!-- --&gt; ภายใต้สัญญาอนุญาต GNU General Public License Version 3 &lt;!-- --&gt;ดูสัญญาที่ : https://www.gnu.org/licenses/gpl-3.0.ja.html</string>
<string name="about_powered_by_tusky">ขับเคลื่อนด้วย Husky</string>
<string name="about_tusky_version">Husky %s</string>
<string name="about_project_site">เว็บไซต์โปรเจกต์:
\nhttps://husky.fwgs.ru</string>
<string name="about_bug_feature_request_site">รายงานช่องโหว่ และ ขอฟีเจอร์ (ภาษาอังกฤษ):
\nhttps://git.mentality.rip/FWGS/Husky/issues</string>
<string name="add_account_description">เพิ่มบัญชี Pleroma ใหม่</string>
<string name="action_login">เข้าสู่ระบบด้วย Pleroma</string>
<string name="warning_scheduling_interval">Pleroma กำหนดเวลาขั้นต่ำ 5 นาที</string>
<string name="dialog_whats_an_instance">"ใส่ที่อยู่หรือโดเมนของ Instance ได้ที่นี่ เช่น shitposter.club blob.cat expired.mentality.rip และ &lt;a href=\"https://fediverse.network/pleroma?count=peers\"&gt;อีกมากมาย!&lt;/a&gt;
\n
\nถ้ายังไม่มีบัญชี สามารถใส่ชื่อ Instance ที่ต้องการจะร่วมแล้วสร้างบัญชีที่นั่น
\n
\nInstance คือที่ที่หนึ่งไว้โฮสต์บัญชีคุณ แต่คุณยังสามารถสื่อสาร ติดตามบุคคลบน Instance อื่นได้เหมือนอยู่บนไซต์เดียวกัน
\n
\nพบข้อมูลเพิ่มเติมได้ที่ &lt;a href=\"https://joinmastodon.org\"&gt;joinmastodon.org&lt;/a&gt; "<a href="https://fediverse.network/pleroma?count=peers">more!</a>
\n\nIf you don\'t yet have an account, you can enter the name of the instance you\'d like to
join and create an account there.\n\nAn instance is a single place where your account is
hosted, but you can easily communicate with and follow folks on other instances as though
you were on the same site.
\n\nMore info can be found at <a href="https://joinmastodon.org">joinmastodon.org</a>.
</string>
</resources>

View File

@ -174,6 +174,13 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<!-- disable automatic WorkManager initialization -->
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
android:exported="false"
tools:node="remove"/>
</application>
</manifest>

View File

@ -43,6 +43,18 @@ public class EmojiPreference extends Preference {
private boolean updated, currentNeedsUpdate;
public EmojiPreference(Context context) {
super(context);
// Find out which font is currently active
this.selected = EmojiCompatFont.byId(PreferenceManager
.getDefaultSharedPreferences(context)
.getInt(FONT_PREFERENCE, 0));
// We'll use this later to determine if anything has changed
this.original = this.selected;
setSummary(selected.getDisplay(context));
}
public EmojiPreference(Context context, AttributeSet attrs) {
super(context, attrs);

View File

@ -31,6 +31,7 @@ import android.view.View
import android.widget.ImageView
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AlertDialog
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.emoji.text.EmojiCompat
@ -48,6 +49,7 @@ import com.keylesspalace.tusky.appstore.*
import com.keylesspalace.tusky.components.compose.ComposeActivity
import com.keylesspalace.tusky.components.compose.ComposeActivity.Companion.canHandleMimeType
import com.keylesspalace.tusky.components.conversation.ConversationsRepository
import com.keylesspalace.tusky.components.notifications.NotificationHelper
import com.keylesspalace.tusky.components.scheduled.ScheduledTootActivity
import com.keylesspalace.tusky.components.search.SearchActivity
import com.keylesspalace.tusky.db.AccountEntity
@ -58,7 +60,10 @@ import com.keylesspalace.tusky.interfaces.ActionButtonActivity
import com.keylesspalace.tusky.interfaces.ReselectableFragment
import com.keylesspalace.tusky.pager.MainPagerAdapter
import com.keylesspalace.tusky.util.*
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import com.mikepenz.materialdrawer.iconics.iconicsIcon
import com.mikepenz.materialdrawer.model.*
import com.mikepenz.materialdrawer.model.interfaces.*
@ -88,8 +93,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
private lateinit var drawerToggle: ActionBarDrawerToggle
private var notificationTabPosition = 0
private var adapter: MainPagerAdapter? = null
private var onTabSelectedListener: OnTabSelectedListener? = null
private val emojiInitCallback = object : InitCallback() {
override fun onInitialized() {
@ -159,6 +163,19 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
val composeIntent = Intent(applicationContext, ComposeActivity::class.java)
startActivity(composeIntent)
}
mainToolbar.menu.add(R.string.action_search).apply {
setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
icon = IconicsDrawable(this@MainActivity, GoogleMaterial.Icon.gmd_search).apply {
sizeDp = 20
colorInt = ThemeUtils.getColor(this@MainActivity, android.R.attr.textColorPrimary)
}
setOnMenuItemClickListener {
startActivity(SearchActivity.getIntent(this@MainActivity))
true
}
}
setupDrawer(savedInstanceState)
/* Fetch user info while we're doing other things. This has to be done after setting up the
@ -167,35 +184,11 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
setupTabs(showNotificationTab)
val pageMargin = resources.getDimensionPixelSize(R.dimen.tab_page_margin)
viewPager.setPageTransformer(MarginPageTransformer(pageMargin))
val uswSwipeForTabs = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean("enableSwipeForTabs", true)
viewPager.isUserInputEnabled = uswSwipeForTabs
tabLayout.addOnTabSelectedListener(object : OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
if (tab.position == notificationTabPosition) {
NotificationHelper.clearNotificationsForActiveAccount(this@MainActivity, accountManager)
}
}
override fun onTabUnselected(tab: TabLayout.Tab) {}
override fun onTabReselected(tab: TabLayout.Tab) {
val fragment = adapter?.getFragment(tab.position)
if (fragment is ReselectableFragment) {
(fragment as ReselectableFragment).onReselect()
}
}
})
// Setup push notifications
if (NotificationHelper.areNotificationsEnabled(this, accountManager)) {
NotificationHelper.enablePullNotifications()
NotificationHelper.enablePullNotifications(this)
} else {
NotificationHelper.disablePullNotifications()
NotificationHelper.disablePullNotifications(this)
}
eventHub.events
.observeOn(AndroidSchedulers.mainThread())
@ -376,13 +369,6 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
startActivityWithSlideInAnimation(ListsActivity.newIntent(context))
}
},
primaryDrawerItem {
nameRes = R.string.action_search
iconicsIcon = GoogleMaterial.Icon.gmd_search
onClick = {
startActivityWithSlideInAnimation(SearchActivity.getIntent(context))
}
},
primaryDrawerItem {
nameRes = R.string.action_access_saved_toot
iconRes = R.drawable.ic_notebook
@ -461,20 +447,37 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
}
private fun setupTabs(selectNotificationTab: Boolean) {
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
val activeTabLayout = if(preferences.getString("mainNavPosition", "top") == "bottom") {
val actionBarSize = ThemeUtils.getDimension(this, R.attr.actionBarSize)
val fabMargin = resources.getDimensionPixelSize(R.dimen.fabMargin)
(composeButton.layoutParams as CoordinatorLayout.LayoutParams).bottomMargin = actionBarSize + fabMargin
tabLayout.hide()
bottomTabLayout
} else {
bottomNav.hide()
(viewPager.layoutParams as CoordinatorLayout.LayoutParams).bottomMargin = 0
(composeButton.layoutParams as CoordinatorLayout.LayoutParams).anchorId = R.id.viewPager
tabLayout
}
val tabs = accountManager.activeAccount!!.tabPreferences
adapter = MainPagerAdapter(tabs, this)
val adapter = MainPagerAdapter(tabs, this)
viewPager.adapter = adapter
TabLayoutMediator(tabLayout, viewPager, TabConfigurationStrategy { _: TabLayout.Tab?, _: Int -> }).attach()
tabLayout.removeAllTabs()
TabLayoutMediator(activeTabLayout, viewPager, TabConfigurationStrategy { _: TabLayout.Tab?, _: Int -> }).attach()
activeTabLayout.removeAllTabs()
for (i in tabs.indices) {
val tab = tabLayout.newTab()
val tab = activeTabLayout.newTab()
.setIcon(tabs[i].icon)
if (tabs[i].id == LIST) {
tab.contentDescription = tabs[i].arguments[1]
} else {
tab.setContentDescription(tabs[i].text)
}
tabLayout.addTab(tab)
activeTabLayout.addTab(tab)
if (tabs[i].id == NOTIFICATIONS) {
notificationTabPosition = i
if (selectNotificationTab) {
@ -482,6 +485,41 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
}
}
}
val pageMargin = resources.getDimensionPixelSize(R.dimen.tab_page_margin)
viewPager.setPageTransformer(MarginPageTransformer(pageMargin))
val enableSwipeForTabs = preferences.getBoolean("enableSwipeForTabs", true)
viewPager.isUserInputEnabled = enableSwipeForTabs
onTabSelectedListener?.let {
activeTabLayout.removeOnTabSelectedListener(it)
}
onTabSelectedListener = object : OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
if (tab.position == notificationTabPosition) {
NotificationHelper.clearNotificationsForActiveAccount(this@MainActivity, accountManager)
}
mainToolbar.title = tabs[tab.position].title(this@MainActivity)
}
override fun onTabUnselected(tab: TabLayout.Tab) {}
override fun onTabReselected(tab: TabLayout.Tab) {
val fragment = adapter.getFragment(tab.position)
if (fragment is ReselectableFragment) {
(fragment as ReselectableFragment).onReselect()
}
}
}.also {
activeTabLayout.addOnTabSelectedListener(it)
}
val activeTabPosition = if (selectNotificationTab) notificationTabPosition else 0
mainToolbar.title = tabs[activeTabPosition].title(this@MainActivity)
}
private fun handleProfileClick(profile: IProfile, current: Boolean): Boolean {
@ -525,19 +563,18 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
.setTitle(R.string.action_logout)
.setMessage(getString(R.string.action_logout_confirm, activeAccount.fullName))
.setPositiveButton(android.R.string.yes) { _: DialogInterface?, _: Int ->
NotificationHelper.deleteNotificationChannelsForAccount(activeAccount, this@MainActivity)
NotificationHelper.deleteNotificationChannelsForAccount(activeAccount, this)
cacheUpdater.clearForUser(activeAccount.id)
conversationRepository.deleteCacheForAccount(activeAccount.id)
removeShortcut(this, activeAccount)
val newAccount = accountManager.logActiveAccountOut()
if (!NotificationHelper.areNotificationsEnabled(this@MainActivity, accountManager)) {
NotificationHelper.disablePullNotifications()
if (!NotificationHelper.areNotificationsEnabled(this, accountManager)) {
NotificationHelper.disablePullNotifications(this)
}
val intent: Intent
intent = if (newAccount == null) {
LoginActivity.getIntent(this@MainActivity, false)
val intent = if (newAccount == null) {
LoginActivity.getIntent(this, false)
} else {
Intent(this@MainActivity, MainActivity::class.java)
Intent(this, MainActivity::class.java)
}
startActivity(intent)
finishWithoutSlideOutAnimation()

View File

@ -130,7 +130,7 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference
}
"statusTextSize", "absoluteTimeView", "showBotOverlay", "animateGifAvatars",
"useBlurhash", "showCardsInTimelines", "confirmReblogs", "hideMutedUsers",
"enableSwipeForTabs", "bigEmojis" -> {
"enableSwipeForTabs", "bigEmojis", "mainNavPosition" -> {
restartActivitiesOnExit = true
}
"language" -> {

View File

@ -21,7 +21,7 @@ import androidx.appcompat.app.AppCompatActivity
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.util.NotificationHelper
import com.keylesspalace.tusky.components.notifications.NotificationHelper
import javax.inject.Inject
class SplashActivity : AppCompatActivity(), Injectable {

View File

@ -15,6 +15,7 @@
package com.keylesspalace.tusky
import android.content.Context
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.fragment.app.Fragment
@ -36,17 +37,58 @@ data class TabData(val id: String,
@StringRes val text: Int,
@DrawableRes val icon: Int,
val fragment: (List<String>) -> Fragment,
val arguments: List<String> = emptyList())
val arguments: List<String> = emptyList(),
val title: (Context) -> String = { context -> context.getString(text)}
)
fun createTabDataFromId(id: String, arguments: List<String> = emptyList()): TabData {
return when (id) {
HOME -> TabData(HOME, R.string.title_home, R.drawable.ic_home_24dp, { TimelineFragment.newInstance(TimelineFragment.Kind.HOME) })
NOTIFICATIONS -> TabData(NOTIFICATIONS, R.string.title_notifications, R.drawable.ic_notifications_24dp, { NotificationsFragment.newInstance() })
LOCAL -> TabData(LOCAL, R.string.title_public_local, R.drawable.ic_local_24dp, { TimelineFragment.newInstance(TimelineFragment.Kind.PUBLIC_LOCAL) })
FEDERATED -> TabData(FEDERATED, R.string.title_public_federated, R.drawable.ic_public_24dp, { TimelineFragment.newInstance(TimelineFragment.Kind.PUBLIC_FEDERATED) })
DIRECT -> TabData(DIRECT, R.string.title_direct_messages, R.drawable.ic_reblog_direct_24dp, { ConversationsFragment.newInstance() })
HASHTAG -> TabData(HASHTAG, R.string.hashtags, R.drawable.ic_hashtag, { args -> TimelineFragment.newHashtagInstance(args) }, arguments)
LIST -> TabData(LIST, R.string.list, R.drawable.ic_list, { args -> TimelineFragment.newInstance(TimelineFragment.Kind.LIST, args.getOrNull(0).orEmpty()) }, arguments)
HOME -> TabData(
HOME,
R.string.title_home,
R.drawable.ic_home_24dp,
{ TimelineFragment.newInstance(TimelineFragment.Kind.HOME) }
)
NOTIFICATIONS -> TabData(
NOTIFICATIONS,
R.string.title_notifications,
R.drawable.ic_notifications_24dp,
{ NotificationsFragment.newInstance() }
)
LOCAL -> TabData(
LOCAL,
R.string.title_public_local,
R.drawable.ic_local_24dp,
{ TimelineFragment.newInstance(TimelineFragment.Kind.PUBLIC_LOCAL) }
)
FEDERATED -> TabData(
FEDERATED,
R.string.title_public_federated,
R.drawable.ic_public_24dp,
{ TimelineFragment.newInstance(TimelineFragment.Kind.PUBLIC_FEDERATED) }
)
DIRECT -> TabData(
DIRECT,
R.string.title_direct_messages,
R.drawable.ic_reblog_direct_24dp,
{ ConversationsFragment.newInstance() }
)
HASHTAG -> TabData(
HASHTAG,
R.string.hashtags,
R.drawable.ic_hashtag,
{ args -> TimelineFragment.newHashtagInstance(args) },
arguments,
{ context -> arguments.joinToString(separator = " ") { context.getString(R.string.title_tag, it) }}
)
LIST -> TabData(
LIST,
R.string.list,
R.drawable.ic_list,
{ args -> TimelineFragment.newInstance(TimelineFragment.Kind.LIST, args.getOrNull(0).orEmpty()) },
arguments,
{ arguments.getOrNull(1).orEmpty() }
)
else -> throw IllegalArgumentException("unknown tab type")
}
}

View File

@ -21,12 +21,10 @@ import android.content.res.Configuration
import android.util.Log
import androidx.emoji.text.EmojiCompat
import androidx.preference.PreferenceManager
import com.evernote.android.job.JobManager
import androidx.work.WorkManager
import com.keylesspalace.tusky.components.notifications.NotificationWorkerFactory
import com.keylesspalace.tusky.di.AppInjector
import com.keylesspalace.tusky.util.EmojiCompatFont
import com.keylesspalace.tusky.util.LocaleManager
import com.keylesspalace.tusky.util.NotificationPullJobCreator
import com.keylesspalace.tusky.util.ThemeUtils
import com.keylesspalace.tusky.util.*
import com.uber.autodispose.AutoDisposePlugins
import dagger.android.DispatchingAndroidInjector
import dagger.android.HasAndroidInjector
@ -40,7 +38,7 @@ class TuskyApplication : Application(), HasAndroidInjector {
@Inject
lateinit var androidInjector: DispatchingAndroidInjector<Any>
@Inject
lateinit var notificationPullJobCreator: NotificationPullJobCreator
lateinit var notificationWorkerFactory: NotificationWorkerFactory
override fun onCreate() {
@ -65,7 +63,12 @@ class TuskyApplication : Application(), HasAndroidInjector {
val theme = preferences.getString("appTheme", ThemeUtils.APP_THEME_DEFAULT)
ThemeUtils.setAppNightMode(theme)
JobManager.create(this).addJobCreator(notificationPullJobCreator)
WorkManager.initialize(
this,
androidx.work.Configuration.Builder()
.setWorkerFactory(notificationWorkerFactory)
.build()
)
RxJavaPlugins.setErrorHandler {
Log.w("RxJava", "undeliverable exception", it)

View File

@ -1023,7 +1023,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
status.getAttachments().size() == 0 &&
status.getCard() != null &&
!TextUtils.isEmpty(status.getCard().getUrl()) &&
!status.isCollapsed()) {
(!status.isCollapsible() || !status.isCollapsed())) {
final Card card = status.getCard();
cardView.setVisibility(View.VISIBLE);
cardTitle.setText(card.getTitle());

View File

@ -14,6 +14,7 @@ data class UnfollowEvent(val accountId: String) : Dispatchable
data class BlockEvent(val accountId: String) : Dispatchable
data class MuteEvent(val accountId: String) : Dispatchable
data class StatusDeletedEvent(val statusId: String) : Dispatchable
data class StatusPreviewEvent(val status: Status) : Dispatchable
data class StatusComposedEvent(val status: Status) : Dispatchable
data class StatusScheduledEvent(val status: Status) : Dispatchable
data class ProfileEditedEvent(val newProfileData: Account) : Dispatchable

View File

@ -70,6 +70,7 @@ import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.adapter.ComposeAutoCompleteAdapter
import com.keylesspalace.tusky.adapter.EmojiAdapter
import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener
import com.keylesspalace.tusky.appstore.*
import com.keylesspalace.tusky.components.compose.dialog.makeCaptionDialog
import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog
import com.keylesspalace.tusky.components.compose.view.ComposeOptionsListener
@ -108,12 +109,16 @@ class ComposeActivity : BaseActivity(),
@Inject
lateinit var viewModelFactory: ViewModelFactory
@Inject
lateinit var eventHub: EventHub
private lateinit var composeOptionsBehavior: BottomSheetBehavior<*>
private lateinit var addMediaBehavior: BottomSheetBehavior<*>
private lateinit var emojiBehavior: BottomSheetBehavior<*>
private lateinit var scheduleBehavior: BottomSheetBehavior<*>
private lateinit var stickerBehavior: BottomSheetBehavior<*>
private lateinit var previewBehavior: BottomSheetBehavior<*>
// this only exists when a status is trying to be sent, but uploads are still occurring
private var finishingUploadDialog: ProgressDialog? = null
@ -192,6 +197,12 @@ class ComposeActivity : BaseActivity(),
viewModel.setupComplete.value = true
stickerKeyboard.isSticky = true
eventHub.events.subscribe { event ->
when(event) {
is StatusPreviewEvent -> onStatusPreviewReady(event.status)
}
}
}
private fun uriToFilename(uri: Uri): String {
@ -256,25 +267,22 @@ class ComposeActivity : BaseActivity(),
for (uri in uriList) {
pickMedia(uri)
}
} else if (type == "text/plain") {
val action = intent.action
if (action != null && action == Intent.ACTION_SEND) {
val subject = intent.getStringExtra(Intent.EXTRA_SUBJECT)
val text = intent.getStringExtra(Intent.EXTRA_TEXT)
val shareBody = text ?: subject
} else if (type == "text/plain" && intent.action == Intent.ACTION_SEND) {
if (shareBody != null) {
if (!subject.isNullOrBlank() && subject !in shareBody) {
composeContentWarningField.setText(subject)
viewModel.showContentWarning.value = true
}
val subject = intent.getStringExtra(Intent.EXTRA_SUBJECT)
val text = intent.getStringExtra(Intent.EXTRA_TEXT).orEmpty()
val shareBody = if (!subject.isNullOrBlank() && subject !in text) {
subject + '\n' + text
} else {
text
}
val start = composeEditField.selectionStart.coerceAtLeast(0)
val end = composeEditField.selectionEnd.coerceAtLeast(0)
val left = min(start, end)
val right = max(start, end)
composeEditField.text.replace(left, right, shareBody, 0, shareBody.length)
}
if (shareBody.isNotBlank()) {
val start = composeEditField.selectionStart.coerceAtLeast(0)
val end = composeEditField.selectionEnd.coerceAtLeast(0)
val left = min(start, end)
val right = max(start, end)
composeEditField.text.replace(left, right, shareBody, 0, shareBody.length)
}
}
}
@ -386,6 +394,7 @@ class ComposeActivity : BaseActivity(),
}
if(instanceData.software.equals("pleroma")) {
composePreviewButton.visibility = View.VISIBLE
reenableAttachments()
}
}
@ -458,13 +467,15 @@ class ComposeActivity : BaseActivity(),
scheduleBehavior = BottomSheetBehavior.from(composeScheduleView)
emojiBehavior = BottomSheetBehavior.from(emojiView)
stickerBehavior = BottomSheetBehavior.from(stickerKeyboard)
previewBehavior = BottomSheetBehavior.from(previewScroll)
emojiView.layoutManager = GridLayoutManager(this, 3, GridLayoutManager.HORIZONTAL, false)
enableButton(composeEmojiButton, clickable = false, colorActive = false)
enableButton(composeStickerButton, false, false)
// Setup the interface buttons.
composeTootButton.setOnClickListener { onSendClicked() }
composeTootButton.setOnClickListener { onSendClicked(false) }
composePreviewButton.setOnClickListener { onSendClicked(true) }
composeAddMediaButton.setOnClickListener { openPickDialog() }
composeToggleVisibilityButton.setOnClickListener { showComposeOptions() }
composeContentWarningButton.setOnClickListener { onContentWarningChanged() }
@ -774,6 +785,7 @@ class ComposeActivity : BaseActivity(),
composeScheduleButton.isClickable = enable
composeFormattingSyntax.isClickable = enable
composeTootButton.isEnabled = enable
composePreviewButton.isEnabled = enable
composeStickerButton.isEnabled = enable
}
@ -799,6 +811,7 @@ class ComposeActivity : BaseActivity(),
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
stickerBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
} else {
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
}
@ -819,6 +832,7 @@ class ComposeActivity : BaseActivity(),
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
stickerBehavior.state = BottomSheetBehavior.STATE_HIDDEN
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
} else {
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
}
@ -836,6 +850,7 @@ class ComposeActivity : BaseActivity(),
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
} else {
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
}
@ -850,6 +865,7 @@ class ComposeActivity : BaseActivity(),
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
stickerBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
} else {
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
}
@ -950,13 +966,31 @@ class ComposeActivity : BaseActivity(),
return composeScheduleView.verifyScheduledTime(composeScheduleView.getDateTime(viewModel.scheduledAt.value))
}
private fun onSendClicked() {
private fun onSendClicked(preview: Boolean) {
if(preview && previewBehavior.state == BottomSheetBehavior.STATE_EXPANDED) {
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
return
}
if (verifyScheduledTime()) {
sendStatus()
sendStatus(preview)
} else {
showScheduleView()
}
}
private fun onStatusPreviewReady(status: Status) {
enableButtons(true)
previewView.setupWithStatus(status)
previewBehavior.state = BottomSheetBehavior.STATE_EXPANDED
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
stickerBehavior.state = BottomSheetBehavior.STATE_HIDDEN
// Log.d("ComposeActivityPreview", "Preview: " + status.content)
}
/** This is for the fancy keyboards which can insert images and stuff. */
override fun onCommitContent(inputContentInfo: InputContentInfoCompat, flags: Int, opts: Bundle?): Boolean {
@ -980,7 +1014,7 @@ class ComposeActivity : BaseActivity(),
return false
}
private fun sendStatus() {
private fun sendStatus(preview: Boolean) {
enableButtons(false)
val contentText = composeEditField.text.toString()
var spoilerText = ""
@ -996,9 +1030,10 @@ class ComposeActivity : BaseActivity(),
this, getString(R.string.dialog_title_finishing_media_upload),
getString(R.string.dialog_message_uploading_media), true, true)
viewModel.sendStatus(contentText, spoilerText).observe(this, Observer {
viewModel.sendStatus(contentText, spoilerText, preview).observe(this, Observer {
finishingUploadDialog?.dismiss()
deleteDraftAndFinish()
if(!preview)
deleteDraftAndFinish()
})
} else {
@ -1159,6 +1194,7 @@ class ComposeActivity : BaseActivity(),
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
stickerBehavior.state = BottomSheetBehavior.STATE_HIDDEN
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
return
}
@ -1171,7 +1207,7 @@ class ComposeActivity : BaseActivity(),
if (event.isCtrlPressed) {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
// send toot by pressing CTRL + ENTER
this.onSendClicked()
this.onSendClicked(false)
return true
}
}
@ -1232,6 +1268,7 @@ class ComposeActivity : BaseActivity(),
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
} else {
stickerBehavior.state = BottomSheetBehavior.STATE_HIDDEN
}

View File

@ -302,7 +302,8 @@ class ComposeViewModel
*/
fun sendStatus(
content: String,
spoilerText: String
spoilerText: String,
preview: Boolean
): LiveData<Unit> {
return media
.filter { items -> items.all { it.uploadPercent == -1 } }
@ -330,6 +331,7 @@ class ComposeViewModel
replyingStatusContent = null,
replyingStatusAuthorUsername = null,
formattingSyntax = formattingSyntax,
preview = preview,
savedJsonUrls = null,
accountId = accountManager.activeAccount!!.id,
savedTootUid = 0,

View File

@ -14,7 +14,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.util;
package com.keylesspalace.tusky.components.notifications;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
@ -38,12 +38,15 @@ import androidx.core.app.RemoteInput;
import androidx.core.app.TaskStackBuilder;
import androidx.core.content.ContextCompat;
import androidx.core.text.BidiFormatter;
import androidx.work.Constraints;
import androidx.work.NetworkType;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import androidx.work.WorkRequest;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.FutureTarget;
import com.evernote.android.job.JobManager;
import com.evernote.android.job.JobRequest;
import com.keylesspalace.tusky.BuildConfig;
import com.keylesspalace.tusky.MainActivity;
import com.keylesspalace.tusky.R;
@ -65,6 +68,7 @@ import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers;
@ -119,11 +123,11 @@ public class NotificationHelper {
public static final String CHANNEL_POLL = "CHANNEL_POLL";
public static final String CHANNEL_EMOJI_REACTION = "CHANNEL_EMOJI_REACTION";
/**
* time in minutes between notification checks
* note that this cannot be less than 15 minutes due to Android battery saving constraints
* WorkManager Tag
*/
private static final int NOTIFICATION_CHECK_INTERVAL_MINUTES = 15;
private static final String NOTIFICATION_PULL_TAG = "pullNotifications";
/**
* by setting this as false, it's possible to test legacy notification channels on newer devices
@ -131,7 +135,6 @@ public class NotificationHelper {
//public static final boolean NOTIFICATION_USE_CHANNELS = false;
public static final boolean NOTIFICATION_USE_CHANNELS = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
/**
* Takes a given Mastodon notification and either creates a new Android notification or updates
* the state of the existing notification to reflect the new interaction.
@ -146,12 +149,12 @@ public class NotificationHelper {
if (!filterNotification(account, body, context)) {
return;
}
// Pleroma extension: don't notify about seen notifications
if (body.getPleroma() != null && body.getPleroma().getSeen() == true) {
return;
}
if (body.getStatus() != null &&
(body.getStatus().isUserMuted() == true ||
body.getStatus().isThreadMuted() == true)) {
@ -476,23 +479,27 @@ public class NotificationHelper {
}
public static void enablePullNotifications() {
long checkInterval = 1000 * 60 * NOTIFICATION_CHECK_INTERVAL_MINUTES;
public static void enablePullNotifications(Context context) {
WorkManager workManager = WorkManager.getInstance(context);
workManager.cancelAllWorkByTag(NOTIFICATION_PULL_TAG);
new JobRequest.Builder(NotificationPullJobCreator.NOTIFICATIONS_JOB_TAG)
.setPeriodic(checkInterval)
.setUpdateCurrent(true)
.setRequiredNetworkType(JobRequest.NetworkType.CONNECTED)
.build()
.scheduleAsync();
WorkRequest workRequest = new PeriodicWorkRequest.Builder(
NotificationWorker.class,
PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, TimeUnit.MILLISECONDS,
PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS, TimeUnit.MILLISECONDS
)
.addTag(NOTIFICATION_PULL_TAG)
.setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
.build();
Log.d(TAG, "enabled notification checks with "+ NOTIFICATION_CHECK_INTERVAL_MINUTES + "min interval");
workManager.enqueue(workRequest);
Log.d(TAG, "enabled notification checks with "+ PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS + "ms interval");
}
public static void disablePullNotifications() {
JobManager.instance().cancelAllForTag(NotificationPullJobCreator.NOTIFICATIONS_JOB_TAG);
public static void disablePullNotifications(Context context) {
WorkManager.getInstance(context).cancelAllWorkByTag(NOTIFICATION_PULL_TAG);
Log.d(TAG, "disabled notification checks");
}
public static void clearNotificationsForActiveAccount(@NonNull Context context, @NonNull AccountManager accountManager) {
@ -507,8 +514,8 @@ public class NotificationHelper {
notificationManager.cancel((int) account.getId());
return true;
})
.subscribeOn(Schedulers.io())
.subscribe();
.subscribeOn(Schedulers.io())
.subscribe();
}
}
@ -548,7 +555,8 @@ public class NotificationHelper {
}
}
private static @Nullable String getChannelId(AccountEntity account, Notification notification) {
@Nullable
private static String getChannelId(AccountEntity account, Notification notification) {
switch (notification.getType()) {
case MENTION:
return CHANNEL_MENTION + account.getIdentifier();

View File

@ -0,0 +1,99 @@
/* Copyright 2020 Tusky Contributors
*
* This file is part of Tusky.
*
* Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with Tusky. If
* not, see <http://www.gnu.org/licenses/>. */
package com.keylesspalace.tusky.components.notifications
import android.content.Context
import android.util.Log
import androidx.work.ListenableWorker
import androidx.work.Worker
import androidx.work.WorkerFactory
import androidx.work.WorkerParameters
import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.entity.Notification
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.isLessThan
import java.io.IOException
import javax.inject.Inject
class NotificationWorker(
private val context: Context,
params: WorkerParameters,
private val mastodonApi: MastodonApi,
private val accountManager: AccountManager
) : Worker(context, params) {
override fun doWork(): Result {
val accountList = accountManager.getAllAccountsOrderedByActive()
for (account in accountList) {
if (account.notificationsEnabled) {
try {
Log.d(TAG, "getting Notifications for " + account.fullName)
// don't care about withMuted because they are always silently ignored
val notificationsResponse = mastodonApi.notificationsWithAuth(
String.format("Bearer %s", account.accessToken),
account.domain, true
).execute()
val notifications = notificationsResponse.body()
if (notificationsResponse.isSuccessful && notifications != null) {
onNotificationsReceived(account, notifications)
} else {
Log.w(TAG, "error receiving notifications")
}
} catch (e: IOException) {
Log.w(TAG, "error receiving notifications", e)
}
}
}
return Result.success()
}
private fun onNotificationsReceived(account: AccountEntity, notificationList: List<Notification>) {
val newId = account.lastNotificationId
var newestId = ""
var isFirstOfBatch = true
notificationList.reversed().forEach { notification ->
val currentId = notification.id
if (newestId.isLessThan(currentId)) {
newestId = currentId
}
if (newId.isLessThan(currentId)) {
NotificationHelper.make(context, notification, account, isFirstOfBatch)
isFirstOfBatch = false
}
}
account.lastNotificationId = newestId
accountManager.saveAccount(account)
}
companion object {
private const val TAG = "NotificationWorker"
}
}
class NotificationWorkerFactory @Inject constructor(
val api: MastodonApi,
val accountManager: AccountManager
): WorkerFactory() {
override fun createWorker(appContext: Context, workerClassName: String, workerParameters: WorkerParameters): ListenableWorker? {
if(workerClassName == NotificationWorker::class.java.name) {
return NotificationWorker(appContext, workerParameters, api, accountManager)
}
return null
}
}

View File

@ -30,7 +30,8 @@ data class Instance (
@SerializedName("contact_account") val contactAccount: Account,
@SerializedName("max_toot_chars") val maxTootChars: Int?,
@SerializedName("max_bio_chars") val maxBioChars: Int?,
@SerializedName("poll_limits") val pollLimits: PollLimits?
@SerializedName("poll_limits") val pollLimits: PollLimits?,
val pleroma: InstancePleroma
) {
override fun hashCode(): Int {
return uri.hashCode()
@ -45,6 +46,14 @@ data class Instance (
}
}
data class InstancePleroma (
val metadata: InstancePleromaMetadata
)
data class InstancePleromaMetadata (
val features: List<String>
)
data class PollLimits (
@SerializedName("max_options") val maxOptions: Int?,
@SerializedName("max_option_chars") val maxOptionChars: Int?

View File

@ -28,7 +28,8 @@ data class NewStatus(
@SerializedName("media_ids") val mediaIds: List<String>?,
@SerializedName("scheduled_at") val scheduledAt: String?,
val poll: NewPoll?,
var content_type: String?
var content_type: String?,
val preview: Boolean?
)
@Parcelize

View File

@ -18,6 +18,7 @@ package com.keylesspalace.tusky.fragment;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
@ -62,6 +63,7 @@ import com.keylesspalace.tusky.repository.TimelineRepository;
import com.keylesspalace.tusky.repository.TimelineRequestMode;
import com.keylesspalace.tusky.util.CardViewMode;
import com.keylesspalace.tusky.util.Either;
import com.keylesspalace.tusky.util.HttpHeaderLink;
import com.keylesspalace.tusky.util.LinkHelper;
import com.keylesspalace.tusky.util.ListStatusAccessibilityDelegate;
import com.keylesspalace.tusky.util.ListUtils;
@ -151,6 +153,10 @@ public class TimelineFragment extends SFragment implements
private Kind kind;
private String id;
private List<String> tags;
/**
* For some timeline kinds we must use LINK headers and not just status ids.
*/
private String nextId;
private LinearLayoutManager layoutManager;
private EndlessOnScrollListener scrollListener;
private boolean filterRemoveReplies;
@ -220,7 +226,7 @@ public class TimelineFragment extends SFragment implements
|| kind == Kind.LIST) {
id = arguments.getString(ID_ARG);
}
if(kind == Kind.TAG) {
if (kind == Kind.TAG) {
tags = arguments.getStringArrayList(HASHTAGS_ARG);
}
@ -974,13 +980,17 @@ public class TimelineFragment extends SFragment implements
updateAdapter();
String bottomId = null;
final ListIterator<Either<Placeholder, Status>> iterator =
this.statuses.listIterator(this.statuses.size());
while (iterator.hasPrevious()) {
Either<Placeholder, Status> previous = iterator.previous();
if (previous.isRight()) {
bottomId = previous.asRight().getId();
break;
if (kind == Kind.FAVOURITES || kind == Kind.BOOKMARKS) {
bottomId = this.nextId;
} else {
final ListIterator<Either<Placeholder, Status>> iterator =
this.statuses.listIterator(this.statuses.size());
while (iterator.hasPrevious()) {
Either<Placeholder, Status> previous = iterator.previous();
if (previous.isRight()) {
bottomId = previous.asRight().getId();
break;
}
}
}
sendFetchTimelineRequest(bottomId, null, null, FetchEnd.BOTTOM, -1);
@ -1063,6 +1073,14 @@ public class TimelineFragment extends SFragment implements
@Override
public void onResponse(@NonNull Call<List<Status>> call, @NonNull Response<List<Status>> response) {
if (response.isSuccessful()) {
@Nullable
String newNextId = extractNextId(response);
if (newNextId != null) {
// when we reach the bottom of the list, we won't have a new link. If
// we blindly write `null` here we will start loading from the top
// again.
nextId = newNextId;
}
onFetchTimelineSuccess(liftStatusList(response.body()), fetchEnd, pos);
} else {
onFetchTimelineFailure(new Exception(response.message()), fetchEnd, pos);
@ -1081,6 +1099,24 @@ public class TimelineFragment extends SFragment implements
}
}
@Nullable
private String extractNextId(Response<?> response) {
String linkHeader = response.headers().get("Link");
if (linkHeader == null) {
return null;
}
List<HttpHeaderLink> links = HttpHeaderLink.parse(linkHeader);
HttpHeaderLink nextHeader = HttpHeaderLink.findByRelationType(links, "next");
if (nextHeader == null) {
return null;
}
Uri nextLink = nextHeader.uri;
if (nextLink == null) {
return null;
}
return nextLink.getQueryParameter("max_id");
}
private void onFetchTimelineSuccess(List<Either<Placeholder, Status>> statuses,
FetchEnd fetchEnd, int pos) {

View File

@ -19,23 +19,23 @@ import android.content.Intent
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Bundle
import com.google.android.material.snackbar.Snackbar
import android.util.Log
import android.view.View
import androidx.preference.*
import androidx.preference.PreferenceFragmentCompat
import com.google.android.material.snackbar.Snackbar
import com.keylesspalace.tusky.*
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
import com.keylesspalace.tusky.components.notifications.NotificationHelper
import com.keylesspalace.tusky.components.instancemute.InstanceListActivity
import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.entity.Account
import com.keylesspalace.tusky.entity.Filter
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.settings.*
import com.keylesspalace.tusky.util.ThemeUtils
import com.keylesspalace.tusky.util.NotificationHelper
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt
@ -45,11 +45,7 @@ import retrofit2.Callback
import retrofit2.Response
import javax.inject.Inject
class AccountPreferencesFragment : PreferenceFragmentCompat(),
Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener,
Injectable {
class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
@Inject
lateinit var accountManager: AccountManager
@ -59,206 +55,247 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(),
@Inject
lateinit var eventHub: EventHub
private lateinit var notificationPreference: Preference
private lateinit var tabPreference: Preference
private lateinit var mutedUsersPreference: Preference
private lateinit var blockedUsersPreference: Preference
private lateinit var mutedDomainsPreference: Preference
private lateinit var defaultPostPrivacyPreference: ListPreference
private lateinit var defaultMediaSensitivityPreference: SwitchPreferenceCompat
private lateinit var defaultFormattingSyntaxPreference: ListPreference
private lateinit var alwaysShowSensitiveMediaPreference: SwitchPreferenceCompat
private lateinit var alwaysOpenSpoilerPreference: SwitchPreferenceCompat
private lateinit var mediaPreviewEnabledPreference: SwitchPreferenceCompat
private lateinit var homeFiltersPreference: Preference
private lateinit var notificationFiltersPreference: Preference
private lateinit var publicFiltersPreference: Preference
private lateinit var threadFiltersPreference: Preference
private lateinit var accountFiltersPreference: Preference
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.account_preferences)
notificationPreference = requirePreference("notificationPreference")
tabPreference = requirePreference("tabPreference")
mutedUsersPreference = requirePreference("mutedUsersPreference")
blockedUsersPreference = requirePreference("blockedUsersPreference")
mutedDomainsPreference = requirePreference("mutedDomainsPreference")
defaultPostPrivacyPreference = requirePreference("defaultPostPrivacy") as ListPreference
defaultMediaSensitivityPreference = requirePreference("defaultMediaSensitivity") as SwitchPreferenceCompat
defaultFormattingSyntaxPreference = requirePreference("defaultFormattingSyntax") as ListPreference
mediaPreviewEnabledPreference = requirePreference("mediaPreviewEnabled") as SwitchPreferenceCompat
alwaysShowSensitiveMediaPreference = requirePreference("alwaysShowSensitiveMedia") as SwitchPreferenceCompat
alwaysOpenSpoilerPreference = requirePreference("alwaysOpenSpoiler") as SwitchPreferenceCompat
homeFiltersPreference = requirePreference("homeFilters")
notificationFiltersPreference = requirePreference("notificationFilters")
publicFiltersPreference = requirePreference("publicFilters")
threadFiltersPreference = requirePreference("threadFilters")
accountFiltersPreference = requirePreference("accountFilters")
notificationPreference.icon = IconicsDrawable(notificationPreference.context, GoogleMaterial.Icon.gmd_notifications).apply { sizeRes = R.dimen.preference_icon_size; colorInt = ThemeUtils.getColor(notificationPreference.context, R.attr.iconColor) }
mutedUsersPreference.icon = getTintedIcon(R.drawable.ic_mute_24dp)
blockedUsersPreference.icon = IconicsDrawable(blockedUsersPreference.context, GoogleMaterial.Icon.gmd_block).apply { sizeRes = R.dimen.preference_icon_size; colorInt = ThemeUtils.getColor(blockedUsersPreference.context, R.attr.iconColor) }
mutedDomainsPreference.icon = getTintedIcon(R.drawable.ic_mute_24dp)
notificationPreference.onPreferenceClickListener = this
tabPreference.onPreferenceClickListener = this
mutedUsersPreference.onPreferenceClickListener = this
blockedUsersPreference.onPreferenceClickListener = this
mutedDomainsPreference.onPreferenceClickListener = this
homeFiltersPreference.onPreferenceClickListener = this
notificationFiltersPreference.onPreferenceClickListener = this
publicFiltersPreference.onPreferenceClickListener = this
threadFiltersPreference.onPreferenceClickListener = this
accountFiltersPreference.onPreferenceClickListener = this
defaultPostPrivacyPreference.onPreferenceChangeListener = this
defaultMediaSensitivityPreference.onPreferenceChangeListener = this
defaultFormattingSyntaxPreference.onPreferenceChangeListener = this
mediaPreviewEnabledPreference.onPreferenceChangeListener = this
alwaysShowSensitiveMediaPreference.onPreferenceChangeListener = this
alwaysOpenSpoilerPreference.onPreferenceChangeListener = this
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
accountManager.activeAccount?.let {
defaultPostPrivacyPreference.value = it.defaultPostPrivacy.serverString()
defaultPostPrivacyPreference.icon = getIconForVisibility(it.defaultPostPrivacy)
defaultFormattingSyntaxPreference.value = when(it.defaultFormattingSyntax) {
"text/markdown" -> "Markdown"
"text/bbcode" -> "BBCode"
"text/html" -> "HTML"
else -> "Plaintext"
}
defaultFormattingSyntaxPreference.icon = getIconForSyntax(it.defaultFormattingSyntax)
defaultMediaSensitivityPreference.isChecked = it.defaultMediaSensitivity
defaultMediaSensitivityPreference.icon = getIconForSensitivity(it.defaultMediaSensitivity)
mediaPreviewEnabledPreference.isChecked = it.mediaPreviewEnabled
alwaysShowSensitiveMediaPreference.isChecked = it.alwaysShowSensitiveMedia
alwaysOpenSpoilerPreference.isChecked = it.alwaysOpenSpoiler
}
}
override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean {
when (preference) {
defaultPostPrivacyPreference -> {
preference.icon = getIconForVisibility(Status.Visibility.byString(newValue as String))
syncWithServer(visibility = newValue)
}
defaultMediaSensitivityPreference -> {
preference.icon = getIconForSensitivity(newValue as Boolean)
syncWithServer(sensitive = newValue)
}
defaultFormattingSyntaxPreference -> {
val syntax = when(newValue) {
"Markdown" -> "text/markdown"
"BBCode" -> "text/bbcode"
"HTML" -> "text/html"
else -> ""
val context = requireContext()
makePreferenceScreen {
preference {
setTitle(R.string.pref_title_edit_notification_settings)
icon = IconicsDrawable(context, GoogleMaterial.Icon.gmd_notifications).apply {
sizeRes = R.dimen.preference_icon_size
colorInt = ThemeUtils.getColor(context, R.attr.iconColor)
}
preference.icon = getIconForSyntax(syntax)
accountManager.activeAccount?.let {
it.defaultFormattingSyntax = syntax
accountManager.saveAccount(it)
setOnPreferenceClickListener {
openNotificationPrefs()
true
}
}
mediaPreviewEnabledPreference -> {
accountManager.activeAccount?.let {
it.mediaPreviewEnabled = newValue as Boolean
accountManager.saveAccount(it)
preference {
setTitle(R.string.title_tab_preferences)
setOnPreferenceClickListener {
val intent = Intent(context, TabPreferenceActivity::class.java)
activity?.startActivity(intent)
activity?.overridePendingTransition(R.anim.slide_from_right,
R.anim.slide_to_left)
true
}
}
alwaysShowSensitiveMediaPreference -> {
accountManager.activeAccount?.let {
it.alwaysShowSensitiveMedia = newValue as Boolean
accountManager.saveAccount(it)
preference {
setTitle(R.string.action_view_mutes)
icon = getTintedIcon(R.drawable.ic_mute_24dp)
setOnPreferenceClickListener {
val intent = Intent(context, AccountListActivity::class.java)
intent.putExtra("type", AccountListActivity.Type.MUTES)
activity?.startActivity(intent)
activity?.overridePendingTransition(R.anim.slide_from_right,
R.anim.slide_to_left)
true
}
}
alwaysOpenSpoilerPreference -> {
accountManager.activeAccount?.let {
it.alwaysOpenSpoiler = newValue as Boolean
accountManager.saveAccount(it)
preference {
setTitle(R.string.action_view_blocks)
icon = IconicsDrawable(context, GoogleMaterial.Icon.gmd_block).apply {
sizeRes = R.dimen.preference_icon_size
colorInt = ThemeUtils.getColor(context, R.attr.iconColor)
}
setOnPreferenceClickListener {
val intent = Intent(context, AccountListActivity::class.java)
intent.putExtra("type", AccountListActivity.Type.BLOCKS)
activity?.startActivity(intent)
activity?.overridePendingTransition(R.anim.slide_from_right,
R.anim.slide_to_left)
true
}
}
}
eventHub.dispatch(PreferenceChangedEvent(preference.key))
preference {
setTitle(R.string.title_domain_mutes)
icon = getTintedIcon(R.drawable.ic_mute_24dp)
setOnPreferenceClickListener {
val intent = Intent(context, InstanceListActivity::class.java)
activity?.startActivity(intent)
activity?.overridePendingTransition(R.anim.slide_from_right,
R.anim.slide_to_left)
true
}
}
return true
}
override fun onPreferenceClick(preference: Preference): Boolean {
return when (preference) {
notificationPreference -> {
if (NotificationHelper.NOTIFICATION_USE_CHANNELS) {
val intent = Intent()
intent.action = "android.settings.APP_NOTIFICATION_SETTINGS"
intent.putExtra("android.provider.extra.APP_PACKAGE", BuildConfig.APPLICATION_ID)
startActivity(intent)
} else {
activity?.let {
val intent = PreferencesActivity.newIntent(it, PreferencesActivity.NOTIFICATION_PREFERENCES)
it.startActivity(intent)
it.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
preferenceCategory(R.string.pref_publishing) {
listPreference {
setTitle(R.string.pref_default_post_privacy)
setEntries(R.array.post_privacy_names)
setEntryValues(R.array.post_privacy_values)
key = PrefKeys.DEFAULT_POST_PRIVACY
setSummaryProvider { entry }
val visibility = accountManager.activeAccount?.defaultPostPrivacy
?: Status.Visibility.PUBLIC
value = visibility.serverString()
icon = getIconForVisibility(visibility)
setOnPreferenceChangeListener { _, newValue ->
icon = getIconForVisibility(
Status.Visibility.byString(newValue as String)
)
syncWithServer(visibility = newValue)
eventHub.dispatch(PreferenceChangedEvent(key))
true
}
}
listPreference {
setTitle(R.string.pref_title_default_formatting)
setEntries(R.array.formatting_syntax_values)
setEntryValues(R.array.formatting_syntax_values)
key = PrefKeys.DEFAULT_FORMATTING_SYNTAX
setSummaryProvider { entry }
val syntax = accountManager.activeAccount?.defaultFormattingSyntax
?: ""
value = when(syntax) {
"text/markdown" -> "Markdown"
"text/bbcode" -> "BBCode"
"text/html" -> "HTML"
else -> "Plaintext"
}
icon = getIconForSyntax(value)
setOnPreferenceChangeListener { _, newValue ->
val syntax = when(newValue) {
"Markdown" -> "text/markdown"
"BBCode" -> "text/bbcode"
"HTML" -> "text/html"
else -> ""
}
icon = getIconForSyntax(syntax)
updateAccount { it.defaultFormattingSyntax = syntax }
eventHub.dispatch(PreferenceChangedEvent(key))
true
}
}
switchPreference {
setTitle(R.string.pref_default_media_sensitivity)
setIcon(R.drawable.ic_eye_24dp)
key = PrefKeys.DEFAULT_MEDIA_SENSITIVITY
isSingleLineTitle = false
val sensitivity = accountManager.activeAccount?.defaultMediaSensitivity
?: false
setDefaultValue(sensitivity)
icon = getIconForSensitivity(sensitivity)
setOnPreferenceChangeListener { _, newValue ->
icon = getIconForSensitivity(newValue as Boolean)
syncWithServer(sensitive = newValue)
eventHub.dispatch(PreferenceChangedEvent(key))
true
}
}
true
}
tabPreference -> {
val intent = Intent(context, TabPreferenceActivity::class.java)
activity?.startActivity(intent)
activity?.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
true
}
mutedUsersPreference -> {
val intent = Intent(context, AccountListActivity::class.java)
intent.putExtra("type", AccountListActivity.Type.MUTES)
activity?.startActivity(intent)
activity?.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
true
}
blockedUsersPreference -> {
val intent = Intent(context, AccountListActivity::class.java)
intent.putExtra("type", AccountListActivity.Type.BLOCKS)
activity?.startActivity(intent)
activity?.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
true
}
mutedDomainsPreference -> {
val intent = Intent(context, InstanceListActivity::class.java)
activity?.startActivity(intent)
activity?.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
true
}
homeFiltersPreference -> {
launchFilterActivity(Filter.HOME, R.string.title_home)
}
notificationFiltersPreference -> {
launchFilterActivity(Filter.NOTIFICATIONS, R.string.title_notifications)
}
publicFiltersPreference -> {
launchFilterActivity(Filter.PUBLIC, R.string.pref_title_public_filter_keywords)
}
threadFiltersPreference -> {
launchFilterActivity(Filter.THREAD, R.string.pref_title_thread_filter_keywords)
}
accountFiltersPreference -> {
launchFilterActivity(Filter.ACCOUNT, R.string.title_accounts)
}
else -> false
preferenceCategory(R.string.pref_title_timelines) {
switchPreference {
key = PrefKeys.MEDIA_PREVIEW_ENABLED
setTitle(R.string.pref_title_show_media_preview)
isSingleLineTitle = false
isChecked = accountManager.activeAccount?.mediaPreviewEnabled ?: true
setOnPreferenceChangeListener { _, newValue ->
updateAccount { it.mediaPreviewEnabled = newValue as Boolean }
eventHub.dispatch(PreferenceChangedEvent(key))
true
}
}
switchPreference {
key = PrefKeys.ALWAYS_SHOW_SENSITIVE_MEDIA
setTitle(R.string.pref_title_alway_show_sensitive_media)
isSingleLineTitle = false
isChecked = accountManager.activeAccount?.alwaysShowSensitiveMedia ?: false
setOnPreferenceChangeListener { _, newValue ->
updateAccount { it.alwaysShowSensitiveMedia = newValue as Boolean }
eventHub.dispatch(PreferenceChangedEvent(key))
true
}
}
switchPreference {
key = PrefKeys.ALWAYS_OPEN_SPOILER
setTitle(R.string.pref_title_alway_open_spoiler)
isSingleLineTitle = false
isChecked = accountManager.activeAccount?.alwaysOpenSpoiler ?: false
setOnPreferenceChangeListener { _, newValue ->
updateAccount { it.alwaysOpenSpoiler = newValue as Boolean }
eventHub.dispatch(PreferenceChangedEvent(key))
true
}
}
}
preferenceCategory(R.string.pref_title_timeline_filters) {
preference {
setTitle(R.string.pref_title_public_filter_keywords)
setOnPreferenceClickListener {
launchFilterActivity(Filter.THREAD,
R.string.pref_title_thread_filter_keywords)
true
}
}
preference {
setTitle(R.string.title_notifications)
setOnPreferenceClickListener {
launchFilterActivity(Filter.NOTIFICATIONS, R.string.title_notifications)
true
}
}
preference {
setTitle(R.string.title_home)
setOnPreferenceClickListener {
launchFilterActivity(Filter.HOME, R.string.title_home)
true
}
}
preference {
setTitle(R.string.pref_title_thread_filter_keywords)
setOnPreferenceClickListener {
launchFilterActivity(Filter.THREAD,
R.string.pref_title_thread_filter_keywords)
true
}
}
preference {
setTitle(R.string.title_accounts)
setOnPreferenceClickListener {
launchFilterActivity(Filter.ACCOUNT, R.string.title_accounts)
true
}
}
}
}
}
private fun openNotificationPrefs() {
if (NotificationHelper.NOTIFICATION_USE_CHANNELS) {
val intent = Intent()
intent.action = "android.settings.APP_NOTIFICATION_SETTINGS"
intent.putExtra("android.provider.extra.APP_PACKAGE", BuildConfig.APPLICATION_ID)
startActivity(intent)
} else {
activity?.let {
val intent = PreferencesActivity.newIntent(it, PreferencesActivity.NOTIFICATION_PREFERENCES)
it.startActivity(intent)
it.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
}
}
}
private inline fun updateAccount(changer: (AccountEntity) -> Unit) {
accountManager.activeAccount?.let { account ->
changer(account)
accountManager.saveAccount(account)
}
}
private fun syncWithServer(visibility: String? = null, sensitive: Boolean? = null) {
@ -334,17 +371,15 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(),
return ThemeUtils.getTintedDrawable(requireContext(), iconId, R.attr.iconColor)
}
private fun launchFilterActivity(filterContext: String, titleResource: Int): Boolean {
private fun launchFilterActivity(filterContext: String, titleResource: Int) {
val intent = Intent(context, FiltersActivity::class.java)
intent.putExtra(FiltersActivity.FILTERS_CONTEXT, filterContext)
intent.putExtra(FiltersActivity.FILTERS_TITLE, getString(titleResource))
activity?.startActivity(intent)
activity?.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
return true
}
companion object {
fun newInstance() = AccountPreferencesFragment()
}
}

View File

@ -16,82 +16,159 @@
package com.keylesspalace.tusky.fragment.preference
import android.os.Bundle
import android.view.View
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.notifications.NotificationHelper
import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.util.NotificationHelper
import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.settings.makePreferenceScreen
import com.keylesspalace.tusky.settings.preferenceCategory
import com.keylesspalace.tusky.settings.switchPreference
import javax.inject.Inject
class NotificationPreferencesFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener, Injectable {
class NotificationPreferencesFragment : PreferenceFragmentCompat(), Injectable {
@Inject
lateinit var accountManager: AccountManager
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.notification_preferences)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val activeAccount = accountManager.activeAccount
if (activeAccount != null) {
for (pair in mapOf(
"notificationsEnabled" to activeAccount.notificationsEnabled,
"notificationFilterMentions" to activeAccount.notificationsMentioned,
"notificationFilterFollows" to activeAccount.notificationsFollowed,
"notificationFilterFollowRequests" to activeAccount.notificationsFollowRequested,
"notificationFilterReblogs" to activeAccount.notificationsReblogged,
"notificationFilterFavourites" to activeAccount.notificationsFavorited,
"notificationFilterPolls" to activeAccount.notificationsPolls,
"notificationFilterEmojis" to activeAccount.notificationsEmojiReactions,
"notificationAlertSound" to activeAccount.notificationSound,
"notificationAlertVibrate" to activeAccount.notificationVibration,
"notificationAlertLight" to activeAccount.notificationLight
)) {
(requirePreference(pair.key) as SwitchPreferenceCompat).apply {
isChecked = pair.value
onPreferenceChangeListener = this@NotificationPreferencesFragment
val activeAccount = accountManager.activeAccount ?: return
val context = requireContext()
makePreferenceScreen {
switchPreference {
setTitle(R.string.pref_title_notifications_enabled)
key = PrefKeys.NOTIFICATIONS_ENABLED
isIconSpaceReserved = false
isChecked = activeAccount.notificationsEnabled
setOnPreferenceChangeListener { _, newValue ->
updateAccount { it.notificationsEnabled = newValue as Boolean }
if (NotificationHelper.areNotificationsEnabled(context, accountManager)) {
NotificationHelper.enablePullNotifications(context)
} else {
NotificationHelper.disablePullNotifications(context)
}
true
}
}
}
}
override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean {
preferenceCategory(R.string.pref_title_notification_filters) { category ->
category.dependency = PrefKeys.NOTIFICATIONS_ENABLED
category.isIconSpaceReserved = false
val activeAccount = accountManager.activeAccount
if (activeAccount != null) {
when (preference.key) {
"notificationsEnabled" -> {
activeAccount.notificationsEnabled = newValue as Boolean
if (NotificationHelper.areNotificationsEnabled(preference.context, accountManager)) {
NotificationHelper.enablePullNotifications()
} else {
NotificationHelper.disablePullNotifications()
switchPreference {
setTitle(R.string.pref_title_notification_filter_follows)
key = PrefKeys.NOTIFICATIONS_FILTER_FOLLOWS
isIconSpaceReserved = false
isChecked = activeAccount.notificationsFollowed
setOnPreferenceChangeListener { _, newValue ->
updateAccount { it.notificationsFollowed = newValue as Boolean }
true
}
}
"notificationFilterMentions" -> activeAccount.notificationsMentioned = newValue as Boolean
"notificationFilterFollows" -> activeAccount.notificationsFollowed = newValue as Boolean
"notificationFilterFollowRequests" -> activeAccount.notificationsFollowRequested = newValue as Boolean
"notificationFilterReblogs" -> activeAccount.notificationsReblogged = newValue as Boolean
"notificationFilterFavourites" -> activeAccount.notificationsFavorited = newValue as Boolean
"notificationFilterPolls" -> activeAccount.notificationsPolls = newValue as Boolean
"notificationFilterEmojis" -> activeAccount.notificationsEmojiReactions = newValue as Boolean
"notificationAlertSound" -> activeAccount.notificationSound = newValue as Boolean
"notificationAlertVibrate" -> activeAccount.notificationVibration = newValue as Boolean
"notificationAlertLight" -> activeAccount.notificationLight = newValue as Boolean
}
accountManager.saveAccount(activeAccount)
return true
}
return false
switchPreference {
setTitle(R.string.pref_title_notification_filter_follow_requests)
key = PrefKeys.NOTIFICATION_FILTER_FOLLOW_REQUESTS
isIconSpaceReserved = false
isChecked = activeAccount.notificationsFollowRequested
setOnPreferenceChangeListener { _, newValue ->
updateAccount { it.notificationsFollowRequested = newValue as Boolean }
true
}
}
switchPreference {
setTitle(R.string.pref_title_notification_filter_reblogs)
key = PrefKeys.NOTIFICATION_FILTER_REBLOGS
isIconSpaceReserved = false
isChecked = activeAccount.notificationsReblogged
setOnPreferenceChangeListener { _, newValue ->
updateAccount { it.notificationsReblogged = newValue as Boolean }
true
}
}
switchPreference {
setTitle(R.string.pref_title_notification_filter_favourites)
key = PrefKeys.NOTIFICATION_FILTER_FAVS
isIconSpaceReserved = false
isChecked = activeAccount.notificationsFavorited
setOnPreferenceChangeListener { _, newValue ->
updateAccount { it.notificationsFavorited = newValue as Boolean }
true
}
}
switchPreference {
setTitle(R.string.pref_title_notification_filter_emoji)
key = PrefKeys.NOTIFICATION_FILTER_EMOJI_REACTIONS
isIconSpaceReserved = false
isChecked = activeAccount.notificationsEmojiReactions
setOnPreferenceChangeListener { _, newValue ->
updateAccount { it.notificationsEmojiReactions = newValue as Boolean }
true
}
}
switchPreference {
setTitle(R.string.pref_title_notification_filter_poll)
key = PrefKeys.NOTIFICATION_FILTER_POLLS
isIconSpaceReserved = false
isChecked = activeAccount.notificationsPolls
setOnPreferenceChangeListener { _, newValue ->
updateAccount { it.notificationsPolls = newValue as Boolean }
true
}
}
}
preferenceCategory(R.string.pref_title_notification_alerts) { category ->
category.dependency = PrefKeys.NOTIFICATIONS_ENABLED
category.isIconSpaceReserved = false
switchPreference {
setTitle(R.string.pref_title_notification_alert_sound)
key = PrefKeys.NOTIFICATION_ALERT_SOUND
isIconSpaceReserved = false
isChecked = activeAccount.notificationSound
setOnPreferenceChangeListener { _, newValue ->
updateAccount { it.notificationSound = newValue as Boolean }
true
}
}
switchPreference {
setTitle(R.string.pref_title_notification_alert_vibrate)
key = PrefKeys.NOTIFICATION_ALERT_VIBRATE
isIconSpaceReserved = false
isChecked = activeAccount.notificationVibration
setOnPreferenceChangeListener { _, newValue ->
updateAccount { it.notificationVibration = newValue as Boolean }
true
}
}
switchPreference {
setTitle(R.string.pref_title_notification_alert_light)
key = PrefKeys.NOTIFICATION_ALERT_LIGHT
isIconSpaceReserved = false
isChecked = activeAccount.notificationLight
setOnPreferenceChangeListener { _, newValue ->
updateAccount { it.notificationLight = newValue as Boolean }
true
}
}
}
}
}
private inline fun updateAccount(changer: (AccountEntity) -> Unit) {
accountManager.activeAccount?.let { account ->
changer(account)
accountManager.saveAccount(account)
}
}
companion object {

View File

@ -20,58 +20,201 @@ import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.keylesspalace.tusky.PreferencesActivity
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.settings.*
import com.keylesspalace.tusky.util.ThemeUtils
import com.keylesspalace.tusky.util.getNonNullString
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeRes
fun PreferenceFragmentCompat.requirePreference(key: String): Preference {
return findPreference(key)!!
}
import com.mikepenz.iconics.utils.sizePx
class PreferencesFragment : PreferenceFragmentCompat() {
private val iconSize by lazy { resources.getDimensionPixelSize(R.dimen.preference_icon_size) }
private var httpProxyPref: Preference? = null
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
val context = requireContext()
makePreferenceScreen {
preferenceCategory(R.string.pref_title_appearance_settings) {
listPreference {
setDefaultValue(AppTheme.NIGHT.value)
setEntries(R.array.app_theme_names)
entryValues = AppTheme.stringValues()
key = PrefKeys.APP_THEME
setSummaryProvider { entry }
setTitle(R.string.pref_title_app_theme)
icon = makeIcon(GoogleMaterial.Icon.gmd_palette)
}
addPreferencesFromResource(R.xml.preferences)
emojiPreference {
setDefaultValue("system_default")
setIcon(R.drawable.ic_emoji_24dp)
key = PrefKeys.EMOJI
setSummary(R.string.system_default)
setTitle(R.string.emoji_style)
icon = makeIcon(GoogleMaterial.Icon.gmd_sentiment_satisfied)
}
val themePreference: Preference = requirePreference("appTheme")
themePreference.icon = IconicsDrawable(themePreference.context, GoogleMaterial.Icon.gmd_palette).apply { sizeRes = R.dimen.preference_icon_size; colorInt = ThemeUtils.getColor(themePreference.context, R.attr.iconColor) }
listPreference {
setDefaultValue("default")
setEntries(R.array.language_entries)
setEntryValues(R.array.language_values)
key = PrefKeys.LANGUAGE
setSummaryProvider { entry }
setTitle(R.string.pref_title_language)
icon = makeIcon(GoogleMaterial.Icon.gmd_translate)
}
val emojiPreference: Preference = requirePreference("emojiCompat")
emojiPreference.icon = IconicsDrawable(emojiPreference.context, GoogleMaterial.Icon.gmd_sentiment_satisfied).apply { sizeRes = R.dimen.preference_icon_size; colorInt = ThemeUtils.getColor(themePreference.context, R.attr.iconColor) }
listPreference {
setDefaultValue("medium")
setEntries(R.array.status_text_size_names)
setEntryValues(R.array.status_text_size_values)
key = PrefKeys.STATUS_TEXT_SIZE
setSummaryProvider { entry }
setTitle(R.string.pref_status_text_size)
icon = makeIcon(GoogleMaterial.Icon.gmd_format_size)
}
val textSizePreference: Preference = requirePreference("statusTextSize")
textSizePreference.icon = IconicsDrawable(textSizePreference.context, GoogleMaterial.Icon.gmd_format_size).apply { sizeRes = R.dimen.preference_icon_size; colorInt = ThemeUtils.getColor(themePreference.context, R.attr.iconColor) }
listPreference {
setDefaultValue("top")
setEntries(R.array.pref_main_nav_position_options)
setEntryValues(R.array.pref_main_nav_position_values)
key = PrefKeys.MAIN_NAV_POSITION
setSummaryProvider { entry }
setTitle(R.string.pref_main_nav_position)
}
val timelineFilterPreferences: Preference = requirePreference("timelineFilterPreferences")
timelineFilterPreferences.setOnPreferenceClickListener {
activity?.let { activity ->
val intent = PreferencesActivity.newIntent(activity, PreferencesActivity.TAB_FILTER_PREFERENCES)
activity.startActivity(intent)
activity.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
switchPreference {
setDefaultValue(false)
key = PrefKeys.FAB_HIDE
setTitle(R.string.pref_title_hide_follow_button)
isSingleLineTitle = false
}
switchPreference {
setDefaultValue(false)
key = PrefKeys.ABSOLUTE_TIME_VIEW
setTitle(R.string.pref_title_absolute_time)
isSingleLineTitle = false
}
switchPreference {
setDefaultValue(true)
key = PrefKeys.SHOW_BOT_OVERLAY
setTitle(R.string.pref_title_bot_overlay)
isSingleLineTitle = false
icon = ThemeUtils.getTintedDrawable(
context,
R.drawable.ic_bot_24dp,
R.attr.iconColor
)
}
switchPreference {
setDefaultValue(false)
key = PrefKeys.ANIMATE_GIF_AVATARS
setTitle(R.string.pref_title_animate_gif_avatars)
isSingleLineTitle = false
}
switchPreference {
setDefaultValue(true)
key = PrefKeys.USE_BLURHASH
setTitle(R.string.pref_title_gradient_for_media)
isSingleLineTitle = false
}
switchPreference {
setDefaultValue(true)
key = PrefKeys.SHOW_NOTIFICATIONS_FILTER
setTitle(R.string.pref_title_show_notifications_filter)
isSingleLineTitle = false
setOnPreferenceClickListener {
activity?.let { activity ->
val intent = PreferencesActivity.newIntent(activity, PreferencesActivity.TAB_FILTER_PREFERENCES)
activity.startActivity(intent)
activity.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
}
true
}
}
switchPreference {
setDefaultValue(false)
key = PrefKeys.SHOW_CARDS_IN_TIMELINES
setTitle(R.string.pref_title_confirm_reblogs)
isSingleLineTitle = false
}
switchPreference {
setDefaultValue(true)
key = PrefKeys.ENABLE_SWIPE_FOR_TABS
setTitle(R.string.pref_title_enable_swipe_for_tabs)
isSingleLineTitle = false
}
switchPreference {
setDefaultValue(true)
key = PrefKeys.BIG_EMOJIS
setTitle(R.string.pref_title_enable_big_emojis)
isSingleLineTitle = false
}
switchPreference {
setDefaultValue(false)
key = PrefKeys.STICKERS
setTitle(R.string.pref_title_enable_experimental_stickers)
isSingleLineTitle = false
}
}
true
preferenceCategory(R.string.pref_title_browser_settings) {
switchPreference {
setDefaultValue(false)
key = PrefKeys.CUSTOM_TABS
setTitle(R.string.pref_title_custom_tabs)
isSingleLineTitle = false
}
}
preferenceCategory(R.string.pref_title_timeline_filters) {
preference {
setTitle(R.string.pref_title_status_tabs)
setOnPreferenceClickListener {
activity?.let { activity ->
val intent = PreferencesActivity.newIntent(activity, PreferencesActivity.TAB_FILTER_PREFERENCES)
activity.startActivity(intent)
activity.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
}
true
}
}
}
preferenceCategory(R.string.pref_title_proxy_settings) {
httpProxyPref = preference {
setTitle(R.string.pref_title_http_proxy_settings)
setOnPreferenceClickListener {
activity?.let { activity ->
val intent = PreferencesActivity.newIntent(activity, PreferencesActivity.PROXY_PREFERENCES)
activity.startActivity(intent)
activity.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
}
true
}
}
}
}
}
private fun makeIcon(icon: GoogleMaterial.Icon): IconicsDrawable {
val context = requireContext()
return IconicsDrawable(context, icon).apply {
sizePx = iconSize
colorInt = ThemeUtils.getColor(context, R.attr.iconColor)
}
val httpProxyPreferences: Preference = requirePreference("httpProxyPreferences")
httpProxyPreferences.setOnPreferenceClickListener {
activity?.let { activity ->
val intent = PreferencesActivity.newIntent(activity, PreferencesActivity.PROXY_PREFERENCES)
activity.startActivity(intent)
activity.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
}
true
}
val languagePreference: Preference = requirePreference("language")
languagePreference.icon = IconicsDrawable(languagePreference.context, GoogleMaterial.Icon.gmd_translate).apply { sizeRes = R.dimen.preference_icon_size; colorInt = ThemeUtils.getColor(themePreference.context, R.attr.iconColor) }
val botIndicatorPreference = requirePreference("showBotOverlay")
botIndicatorPreference.icon = ThemeUtils.getTintedDrawable(requireContext(), R.drawable.ic_bot_24dp, R.attr.iconColor)
}
override fun onResume() {
@ -80,28 +223,23 @@ class PreferencesFragment : PreferenceFragmentCompat() {
}
private fun updateHttpProxySummary() {
val httpProxyPref: Preference = requirePreference("httpProxyPreferences")
val sharedPreferences = preferenceManager.sharedPreferences
val httpProxyEnabled = sharedPreferences.getBoolean("httpProxyEnabled", false)
val httpServer = sharedPreferences.getNonNullString("httpProxyServer", "")
val httpProxyEnabled = sharedPreferences.getBoolean(PrefKeys.HTTP_PROXY_ENABLED, false)
val httpServer = sharedPreferences.getNonNullString(PrefKeys.HTTP_PROXY_SERVER, "")
try {
val httpPort = sharedPreferences.getNonNullString("httpProxyPort", "-1").toInt()
val httpPort = sharedPreferences.getNonNullString(PrefKeys.HTTP_PROXY_PORT, "-1")
.toInt()
if (httpProxyEnabled && httpServer.isNotBlank() && httpPort > 0 && httpPort < 65535) {
httpProxyPref.summary = "$httpServer:$httpPort"
httpProxyPref?.summary = "$httpServer:$httpPort"
return
}
} catch (e: NumberFormatException) {
// user has entered wrong port, fall back to empty summary
}
httpProxyPref.summary = ""
httpProxyPref?.summary = ""
}
companion object {

View File

@ -15,61 +15,53 @@
package com.keylesspalace.tusky.fragment.preference
import android.content.SharedPreferences
import android.os.Bundle
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceFragmentCompat
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.settings.editTextPreference
import com.keylesspalace.tusky.settings.makePreferenceScreen
import com.keylesspalace.tusky.settings.switchPreference
import kotlin.system.exitProcess
class ProxyPreferencesFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener {
class ProxyPreferencesFragment : PreferenceFragmentCompat() {
private var pendingRestart = false
private lateinit var sharedPreferences: SharedPreferences
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.http_proxy_preferences)
makePreferenceScreen {
switchPreference {
setTitle(R.string.pref_title_http_proxy_enable)
isIconSpaceReserved = false
key = PrefKeys.HTTP_PROXY_ENABLED
setDefaultValue(false)
}
sharedPreferences = preferenceManager.sharedPreferences
editTextPreference {
setTitle(R.string.pref_title_http_proxy_server)
key = PrefKeys.HTTP_PROXY_SERVER
isIconSpaceReserved = false
setSummaryProvider { text }
}
}
editTextPreference {
setTitle(R.string.pref_title_http_proxy_port)
key = PrefKeys.HTTP_PROXY_PORT
isIconSpaceReserved = false
setSummaryProvider { text }
}
}
override fun onResume() {
super.onResume()
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
updateSummary("httpProxyServer")
updateSummary("httpProxyPort")
}
override fun onPause() {
super.onPause()
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
if (pendingRestart) {
pendingRestart = false
exitProcess(0)
}
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
updateSummary (key)
}
private fun updateSummary(key: String) {
when (key) {
"httpProxyServer", "httpProxyPort" -> {
val editTextPreference = requirePreference(key) as EditTextPreference
editTextPreference.summary = editTextPreference.text
}
}
}
companion object {
fun newInstance(): ProxyPreferencesFragment {
return ProxyPreferencesFragment()
}

View File

@ -15,22 +15,38 @@
package com.keylesspalace.tusky.fragment.preference
import android.content.SharedPreferences
import android.os.Bundle
import androidx.preference.PreferenceFragmentCompat
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.settings.checkBoxPreference
import com.keylesspalace.tusky.settings.makePreferenceScreen
import com.keylesspalace.tusky.settings.preferenceCategory
class TabFilterPreferencesFragment : PreferenceFragmentCompat() {
private lateinit var sharedPreferences: SharedPreferences
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.timeline_filter_preferences)
sharedPreferences = preferenceManager.sharedPreferences
makePreferenceScreen {
preferenceCategory(R.string.title_home) { category ->
category.isIconSpaceReserved = false
checkBoxPreference {
setTitle(R.string.pref_title_show_boosts)
key = PrefKeys.TAB_FILTER_HOME_BOOSTS
setDefaultValue(true)
isIconSpaceReserved = false
}
checkBoxPreference {
setTitle(R.string.pref_title_show_replies)
key = PrefKeys.TAB_FILTER_HOME_REPLIES
setDefaultValue(false)
isIconSpaceReserved = false
}
}
}
}
companion object {
fun newInstance(): TabFilterPreferencesFragment {
return TabFilterPreferencesFragment()
}

View File

@ -20,7 +20,7 @@ import android.content.Context
import android.content.Intent
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.util.NotificationHelper
import com.keylesspalace.tusky.components.notifications.NotificationHelper
import dagger.android.AndroidInjection
import javax.inject.Inject

View File

@ -30,7 +30,7 @@ import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.service.SendTootService
import com.keylesspalace.tusky.service.TootToSend
import com.keylesspalace.tusky.util.NotificationHelper
import com.keylesspalace.tusky.components.notifications.NotificationHelper
import com.keylesspalace.tusky.util.randomAlphanumericString
import dagger.android.AndroidInjection
import javax.inject.Inject
@ -103,6 +103,7 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() {
null,
null,
"",
false,
account.id,
0,
randomAlphanumericString(16),

View File

@ -16,8 +16,8 @@ import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.StatusComposedEvent
import com.keylesspalace.tusky.appstore.StatusScheduledEvent
import com.keylesspalace.tusky.appstore.*
import com.keylesspalace.tusky.components.notifications.NotificationHelper
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.AppDatabase
import com.keylesspalace.tusky.di.Injectable
@ -26,7 +26,6 @@ import com.keylesspalace.tusky.entity.NewStatus
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.SaveTootHelper
import com.keylesspalace.tusky.util.NotificationHelper
import dagger.android.AndroidInjection
import kotlinx.android.parcel.Parcelize
import retrofit2.Call
@ -132,6 +131,7 @@ class SendTootService : Service(), Injectable {
tootToSend.retries++
val contentType : String? = if(tootToSend.formattingSyntax.length == 0) null else tootToSend.formattingSyntax
val preview : Boolean? = if(tootToSend.preview) tootToSend.preview else null
val newStatus = NewStatus(
tootToSend.text,
@ -142,7 +142,8 @@ class SendTootService : Service(), Injectable {
tootToSend.mediaIds,
tootToSend.scheduledAt,
tootToSend.poll,
contentType
contentType,
preview
)
val sendCall = mastodonApi.createStatus(
@ -166,7 +167,9 @@ class SendTootService : Service(), Injectable {
saveTootHelper.deleteDraft(tootToSend.savedTootUid)
}
if (scheduled) {
if (tootToSend.preview) {
response.body()?.let(::StatusPreviewEvent)?.let(eventHub::dispatch)
} else if (scheduled) {
response.body()?.let(::StatusScheduledEvent)?.let(eventHub::dispatch)
} else {
response.body()?.let(::StatusComposedEvent)?.let(eventHub::dispatch)
@ -328,6 +331,7 @@ data class TootToSend(
val replyingStatusAuthorUsername: String?,
val savedJsonUrls: List<String>?,
val formattingSyntax: String,
val preview: Boolean,
val accountId: Long,
val savedTootUid: Int,
val idempotencyKey: String,

View File

@ -0,0 +1,61 @@
package com.keylesspalace.tusky.settings
enum class AppTheme(val value: String) {
NIGHT("night"),
DAY("day"),
BLACK("black"),
AUTO("auto"),
AUTO_SYSTEM("auto_system");
companion object {
fun stringValues() = values().map { it.value }.toTypedArray()
}
}
object PrefKeys {
// Note: not all of these keys are actually used as SharedPreferences keys but we must give
// each preference a key for it to work.
const val APP_THEME = "appTheme"
const val EMOJI = "emojiCompat"
const val FAB_HIDE = "fabHide"
const val LANGUAGE = "language"
const val STATUS_TEXT_SIZE = "statusTextSize"
const val MAIN_NAV_POSITION = "mainNavPosition"
const val ABSOLUTE_TIME_VIEW = "absoluteTimeView"
const val SHOW_BOT_OVERLAY = "showBotOverlay"
const val ANIMATE_GIF_AVATARS = "animateGifAvatars"
const val USE_BLURHASH = "useBlurhash"
const val SHOW_NOTIFICATIONS_FILTER = "showNotificationsFilter"
const val SHOW_CARDS_IN_TIMELINES = "showCardsInTimelines"
const val ENABLE_SWIPE_FOR_TABS = "enableSwipeForTabs"
const val BIG_EMOJIS = "bigEmojis"
const val STICKERS = "stickers"
const val CUSTOM_TABS = "customTabs"
const val HTTP_PROXY_ENABLED = "httpProxyEnabled"
const val HTTP_PROXY_SERVER = "httpProxyServer"
const val HTTP_PROXY_PORT = "httpProxyPort"
const val DEFAULT_POST_PRIVACY = "defaultPostPrivacy"
const val DEFAULT_MEDIA_SENSITIVITY = "defaultMediaSensitivity"
const val DEFAULT_FORMATTING_SYNTAX = "defaultFormattingSyntax"
const val MEDIA_PREVIEW_ENABLED = "mediaPreviewEnabled"
const val ALWAYS_SHOW_SENSITIVE_MEDIA = "alwaysShowSensitiveMedia"
const val ALWAYS_OPEN_SPOILER = "alwaysOpenSpoiler"
const val NOTIFICATIONS_ENABLED = "notificationsEnabled"
const val NOTIFICATION_ALERT_LIGHT = "notificationAlertLight"
const val NOTIFICATION_ALERT_VIBRATE = "notificationAlertVibrate"
const val NOTIFICATION_ALERT_SOUND = "notificationAlertSound"
const val NOTIFICATION_FILTER_POLLS = "notificationFilterPolls"
const val NOTIFICATION_FILTER_FAVS = "notificationFilterFavourites"
const val NOTIFICATION_FILTER_REBLOGS = "notificationFilterReblogs"
const val NOTIFICATION_FILTER_FOLLOW_REQUESTS = "notificationFilterFollowRequests"
const val NOTIFICATION_FILTER_EMOJI_REACTIONS = "notificationFilterEmojis"
const val NOTIFICATIONS_FILTER_FOLLOWS = "notificationFilterFollows"
const val TAB_FILTER_HOME_REPLIES = "tabFilterHomeBoosts"
const val TAB_FILTER_HOME_BOOSTS = "tabFilterHomeReplies"
}

View File

@ -0,0 +1,83 @@
package com.keylesspalace.tusky.settings
import android.content.Context
import androidx.annotation.StringRes
import androidx.preference.*
import com.keylesspalace.tusky.EmojiPreference
class PreferenceParent(
val context: Context,
val addPref: (pref: Preference) -> Unit
)
inline fun PreferenceParent.preference(builder: Preference.() -> Unit): Preference {
val pref = Preference(context)
builder(pref)
addPref(pref)
return pref
}
inline fun PreferenceParent.listPreference(builder: ListPreference.() -> Unit): ListPreference {
val pref = ListPreference(context)
builder(pref)
addPref(pref)
return pref
}
inline fun PreferenceParent.emojiPreference(builder: EmojiPreference.() -> Unit): EmojiPreference {
val pref = EmojiPreference(context)
builder(pref)
addPref(pref)
return pref
}
inline fun PreferenceParent.switchPreference(
builder: SwitchPreference.() -> Unit
): SwitchPreference {
val pref = SwitchPreference(context)
builder(pref)
addPref(pref)
return pref
}
inline fun PreferenceParent.editTextPreference(
builder: EditTextPreference.() -> Unit
): EditTextPreference {
val pref = EditTextPreference(context)
builder(pref)
addPref(pref)
return pref
}
inline fun PreferenceParent.checkBoxPreference(
builder: CheckBoxPreference.() -> Unit
): CheckBoxPreference {
val pref = CheckBoxPreference(context)
builder(pref)
addPref(pref)
return pref
}
inline fun PreferenceParent.preferenceCategory(
@StringRes title: Int,
builder: PreferenceParent.(PreferenceCategory) -> Unit
) {
val category = PreferenceCategory(context)
addPref(category)
category.setTitle(title)
val newParent = PreferenceParent(context) { category.addPreference(it) }
builder(newParent, category)
}
inline fun PreferenceFragmentCompat.makePreferenceScreen(
builder: PreferenceParent.() -> Unit
): PreferenceScreen {
val context = requireContext()
val screen = preferenceManager.createPreferenceScreen(context)
val parent = PreferenceParent(context) { screen.addPreference(it) }
// For some functions (like dependencies) it's much easier for us if we attach screen first
// and change it later
preferenceScreen = screen
builder(parent)
return screen
}

View File

@ -87,7 +87,7 @@ object BlurHashDecoder {
numCompX: Int, numCompY: Int,
colors: Array<FloatArray>
): Bitmap {
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val imageArray = IntArray(width * height)
for (y in 0 until height) {
for (x in 0 until width) {
var r = 0f
@ -102,10 +102,10 @@ object BlurHashDecoder {
b += color[2] * basis
}
}
bitmap.setPixel(x, y, Color.rgb(linearToSrgb(r), linearToSrgb(g), linearToSrgb(b)))
imageArray[x + width * y] = Color.rgb(linearToSrgb(r), linearToSrgb(g), linearToSrgb(b))
}
}
return bitmap
return Bitmap.createBitmap(imageArray, width, height, Bitmap.Config.ARGB_8888)
}
private fun linearToSrgb(value: Float): Int {

View File

@ -1,137 +0,0 @@
/* Copyright 2017 Andrew Dawson
*
* This file is part of Tusky.
*
* Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with Tusky. If
* not, see <http://www.gnu.org/licenses/>. */
package com.keylesspalace.tusky.util;
import android.content.Context;
import android.util.Log;
import com.evernote.android.job.Job;
import com.evernote.android.job.JobCreator;
import com.keylesspalace.tusky.db.AccountEntity;
import com.keylesspalace.tusky.db.AccountManager;
import com.keylesspalace.tusky.entity.Notification;
import com.keylesspalace.tusky.network.MastodonApi;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import retrofit2.Response;
import static com.keylesspalace.tusky.util.StringUtils.isLessThan;
/**
* Created by charlag on 31/10/17.
*/
public final class NotificationPullJobCreator implements JobCreator {
private static final String TAG = "NotificationPJC";
static final String NOTIFICATIONS_JOB_TAG = "notifications_job_tag";
private final MastodonApi api;
private final Context context;
private final AccountManager accountManager;
@Inject NotificationPullJobCreator(MastodonApi api, Context context,
AccountManager accountManager) {
this.api = api;
this.context = context;
this.accountManager = accountManager;
}
@Nullable
@Override
public Job create(@NonNull String tag) {
if (tag.equals(NOTIFICATIONS_JOB_TAG)) {
return new NotificationPullJob(context, accountManager, api);
}
return null;
}
private final static class NotificationPullJob extends Job {
private final Context context;
private final AccountManager accountManager;
private final MastodonApi mastodonApi;
NotificationPullJob(Context context, AccountManager accountManager,
MastodonApi mastodonApi) {
this.context = context;
this.accountManager = accountManager;
this.mastodonApi = mastodonApi;
}
@NonNull
@Override
protected Result onRunJob(@NonNull Params params) {
List<AccountEntity> accountList = new ArrayList<>(accountManager.getAllAccountsOrderedByActive());
boolean withMuted = true; // TODO: configurable
for (AccountEntity account : accountList) {
if (account.getNotificationsEnabled()) {
try {
Log.d(TAG, "getting Notifications for " + account.getFullName());
Response<List<Notification>> notifications =
mastodonApi.notificationsWithAuth(
String.format("Bearer %s", account.getAccessToken()),
account.getDomain(),
withMuted
)
.execute();
if (notifications.isSuccessful()) {
onNotificationsReceived(account, notifications.body());
} else {
Log.w(TAG, "error receiving notifications");
}
} catch (IOException e) {
Log.w(TAG, "error receiving notifications", e);
}
}
}
return Result.SUCCESS;
}
private void onNotificationsReceived(AccountEntity account, List<Notification> notificationList) {
Collections.reverse(notificationList);
String newId = account.getLastNotificationId();
String newestId = "";
boolean isFirstOfBatch = true;
for (Notification notification : notificationList) {
String currentId = notification.getId();
if (isLessThan(newestId, currentId)) {
newestId = currentId;
}
if (isLessThan(newId, currentId)) {
NotificationHelper.make(context, notification, account, isFirstOfBatch);
isFirstOfBatch = false;
}
}
account.setLastNotificationId(newestId);
accountManager.saveAccount(account);
}
}
}

View File

@ -29,6 +29,7 @@ import androidx.core.graphics.drawable.IconCompat
import com.bumptech.glide.Glide
import com.keylesspalace.tusky.MainActivity
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.notifications.NotificationHelper
import com.keylesspalace.tusky.db.AccountEntity
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers

View File

@ -16,6 +16,7 @@
package com.keylesspalace.tusky.util;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
@ -51,6 +52,13 @@ public class ThemeUtils {
}
}
public static int getDimension(@NonNull Context context, @AttrRes int attribute) {
TypedArray array = context.obtainStyledAttributes(new int[] { attribute });
int dimen = array.getDimensionPixelSize(0, -1);
array.recycle();
return dimen;
}
/** this can be replaced with drawableTint in xml once minSdkVersion >= 23 */
@Nullable
public static Drawable getTintedDrawable(@NonNull Context context, @DrawableRes int drawableId, @AttrRes int colorAttr) {

View File

@ -0,0 +1,71 @@
package com.keylesspalace.tusky.view
import android.view.*
import android.content.*
import android.util.*
import android.widget.*
import android.app.*
import android.text.*
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.preference.PreferenceManager
import com.keylesspalace.tusky.adapter.StatusViewHolder
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.interfaces.StatusActionListener
import com.keylesspalace.tusky.util.CardViewMode
import com.keylesspalace.tusky.util.ViewDataUtils
import com.keylesspalace.tusky.util.StatusDisplayOptions
import com.keylesspalace.tusky.R
import java.util.*;
class StatusView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0)
: ConstraintLayout(context, attrs, defStyleAttr) {
private var viewHolder : StatusViewHolder
private var statusDisplayOptions : StatusDisplayOptions
init {
View.inflate(context, R.layout.item_status, this)
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
statusDisplayOptions = StatusDisplayOptions(
animateAvatars = preferences.getBoolean("animateGifAvatars", false),
mediaPreviewEnabled = true,
useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false),
showBotOverlay = false,
useBlurhash = preferences.getBoolean("useBlurhash", true),
cardViewMode = CardViewMode.NONE,
confirmReblogs = preferences.getBoolean("confirmReblogs", true)
)
viewHolder = StatusViewHolder(this)
}
fun setupWithStatus(status: Status) {
val concrete = ViewDataUtils.statusToViewData(status, false, false)
viewHolder.setupWithStatus(concrete, DummyStatusActionListener(), statusDisplayOptions)
}
class DummyStatusActionListener: StatusActionListener {
override fun onReply(position: Int) { }
override fun onReblog(reblog: Boolean, position: Int) { }
override fun onFavourite(favourite: Boolean, position: Int) { }
override fun onBookmark(bookmark: Boolean, position: Int) { }
override fun onMore(view: View, position: Int) { }
override fun onViewMedia(position: Int, attachmentIndex: Int, view: View?) { }
override fun onViewThread(position: Int) { }
override fun onOpenReblog(position: Int) { }
override fun onExpandedChange(expanded: Boolean, position: Int) { }
override fun onContentHiddenChange(isShowing: Boolean, position: Int) { }
override fun onLoadMore(position: Int) { }
override fun onContentCollapsedChange(isCollapsed: Boolean, position: Int) { }
override fun onVoteInPoll(position: Int, choices: MutableList<Int>) { }
override fun onViewAccount(id: String) { }
override fun onViewTag(id: String) { }
override fun onViewUrl(id: String) { }
}
}

View File

@ -145,12 +145,12 @@
android:layout_height="16sp"
android:layout_marginStart="4dp"
android:contentDescription="@string/description_account_locked"
android:tint="?android:textColorSecondary"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/accountUsernameTextView"
app:layout_constraintStart_toEndOf="@+id/accountUsernameTextView"
app:layout_constraintTop_toTopOf="@+id/accountUsernameTextView"
app:srcCompat="@drawable/ic_reblog_private_24dp"
app:tint="?android:textColorSecondary"
tools:visibility="visible" />
<TextView

View File

@ -301,10 +301,29 @@
android:layout_height="300dp"
android:background="?attr/colorSurface"
android:elevation="12dp"
android:layout_marginBottom="56dp"
android:paddingBottom="60dp"
app:behavior_hideable="true"
app:behavior_peekHeight="0dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" />
<ScrollView
android:id="@+id/previewScroll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurface"
android:elevation="12dp"
android:paddingBottom="60dp"
app:behavior_hideable="true"
app:behavior_peekHeight="0dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
>
<com.keylesspalace.tusky.view.StatusView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurface"
/>
</ScrollView>
<RelativeLayout
android:layout_width="match_parent"
@ -429,16 +448,29 @@
android:layout_height="wrap_content"
android:textColor="?android:textColorTertiary"
android:textSize="?attr/status_text_medium"
android:layout_toLeftOf="@+id/composeTootButton"
android:layout_toLeftOf="@+id/composePreviewButton"
android:layout_centerVertical="true"
tools:text="500" />
<com.google.android.material.button.MaterialButton
android:id="@+id/composePreviewButton"
style="@style/TuskyButton"
android:padding="4dp"
android:layout_width="32dp"
android:layout_height="wrap_content"
android:gravity="center"
app:icon="@drawable/ic_eye_24dp"
android:layout_marginStart="10dp"
android:visibility="gone"
android:layout_toLeftOf="@+id/composeTootButton"
android:layout_centerVertical="true"/>
<com.keylesspalace.tusky.components.compose.view.TootButton
android:id="@+id/composeTootButton"
style="@style/TuskyButton"
android:layout_width="@dimen/toot_button_width"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginStart="4dp"
android:textSize="?attr/status_text_medium"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"/>

View File

@ -22,19 +22,17 @@
android:id="@+id/mainToolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:contentInsetStartWithNavigation="0dp">
app:contentInsetStartWithNavigation="0dp"
app:layout_scrollFlags="scroll|enterAlways" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
style="@style/TuskyTabAppearance"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:tabGravity="fill"
app:tabMaxWidth="0dp"
app:tabMode="fixed"
app:tabUnboundedRipple="false" />
</androidx.appcompat.widget.Toolbar>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
style="@style/TuskyTabAppearance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabGravity="fill"
app:tabMaxWidth="0dp"
app:tabMode="fixed" />
</com.google.android.material.appbar.AppBarLayout>
@ -42,15 +40,32 @@
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/tabLayout"
android:layout_marginBottom="?attr/actionBarSize"
android:background="?attr/windowBackgroundColor"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottomNav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:contentInsetStart="0dp"
app:fabAlignmentMode="end">
<com.google.android.material.tabs.TabLayout
android:id="@+id/bottomTabLayout"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:tabIndicator="@null"
app:tabMode="fixed" />
</com.google.android.material.bottomappbar.BottomAppBar>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/composeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_margin="@dimen/fabMargin"
android:contentDescription="@string/action_compose"
app:layout_anchor="@id/viewPager"
app:layout_anchorGravity="bottom|end"

View File

@ -29,7 +29,7 @@
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/account_avatar"
app:layout_constraintEnd_toEndOf="@id/account_avatar"
tools:src="@color/accent"
tools:src="#000"
tools:visibility="visible" />
<androidx.emoji.widget.EmojiTextView

View File

@ -76,7 +76,7 @@
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/status_avatar"
app:layout_constraintEnd_toEndOf="@id/status_avatar"
tools:src="@color/accent"
tools:src="#000"
tools:visibility="visible" />
<androidx.emoji.widget.EmojiTextView

View File

@ -2,7 +2,7 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<com.keylesspalace.tusky.view.MediaPreviewImageView
android:id="@+id/status_media_preview_0"
@ -25,13 +25,11 @@
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<com.keylesspalace.tusky.view.MediaPreviewImageView
android:id="@+id/status_media_preview_2"
android:layout_width="0dp"
android:layout_height="@dimen/status_media_preview_height"
android:layout_marginTop="4dp"
android:background="@drawable/media_preview_outline"
android:scaleType="centerCrop"
app:layout_constraintEnd_toStartOf="@+id/status_media_preview_3"
app:layout_constraintStart_toStartOf="parent"
@ -44,6 +42,7 @@
android:layout_height="@dimen/status_media_preview_height"
android:layout_marginStart="4dp"
android:layout_marginTop="4dp"
android:background="@drawable/media_preview_outline"
android:scaleType="centerCrop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/status_media_preview_2"
@ -105,19 +104,18 @@
android:alpha="0.7"
android:contentDescription="@null"
android:padding="@dimen/status_sensitive_media_button_padding"
android:tint="@color/white"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="@+id/status_media_preview_container"
app:layout_constraintTop_toTopOf="@+id/status_media_preview_container"
app:srcCompat="@drawable/ic_eye_24dp" />
app:srcCompat="@drawable/ic_eye_24dp"
app:tint="@color/white" />
<TextView
android:id="@+id/status_sensitive_media_warning"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/media_warning_bg"
android:gravity="center"
android:importantForAccessibility="no"
android:lineSpacingMultiplier="1.2"
android:orientation="vertical"
android:paddingLeft="12dp"
@ -185,5 +183,4 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/status_media_label_2" />
</merge>

View File

@ -194,12 +194,12 @@
android:alpha="0.7"
android:contentDescription="@null"
android:padding="@dimen/status_sensitive_media_button_padding"
android:tint="@color/white"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="@+id/status_media_preview_container"
app:layout_constraintTop_toTopOf="@+id/status_media_preview_container"
app:srcCompat="@drawable/ic_eye_24dp"
tools:visibility="visible" />
tools:visibility="visible"
app:tint="@color/white" />
<TextView
android:id="@+id/status_sensitive_media_warning"

View File

@ -52,7 +52,7 @@
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/status_avatar"
app:layout_constraintEnd_toEndOf="@id/status_avatar"
tools:src="@color/accent"
tools:src="#000"
tools:visibility="visible" />
<androidx.emoji.widget.EmojiTextView
@ -269,7 +269,7 @@
android:importantForAccessibility="noHideDescendants"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/button_toggle_content"
app:layout_constraintTop_toBottomOf="@id/status_card_view"
tools:visibility="visible">
<include layout="@layout/item_media_preview" />

View File

@ -33,7 +33,7 @@
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/status_avatar"
app:layout_constraintEnd_toEndOf="@id/status_avatar"
tools:src="@color/accent"
tools:src="#000"
tools:visibility="visible" />
<androidx.emoji.widget.EmojiTextView

View File

@ -211,7 +211,7 @@
<string name="notification_favourite_name">المفضلة</string>
<string name="notification_favourite_description">الإشعار عندما يقوم أحدهم بإضافة تبويقاتك إلى مفضلاته</string>
<string name="notification_mention_format">%s أشار إليك</string>
<string name="notification_summary_large">%1$s, %2$s, %3$s و %4$d أخرى</string>
<string name="notification_summary_large">%1$s, %2$s, %3$s و %4$d آخرون</string>
<string name="notification_summary_medium">%1$s, %2$s, و %3$s</string>
<string name="notification_summary_small">%1$s و %2$s</string>
<string name="notification_title_summary">%d تفاعلات جديدة</string>
@ -492,4 +492,6 @@
<string name="action_unmute_conversation">ألغِ كتم المحادثة</string>
<string name="action_mute_conversation">اكتم المحادثة</string>
<string name="notification_follow_request_format">%s طلبَ متابعتك</string>
<string name="hashtags">الوسوم</string>
<string name="add_hashtag_title">إضافة وسم</string>
</resources>

View File

@ -357,7 +357,6 @@
<string name="description_visiblity_direct">সরাসরি</string>
<string name="description_poll">পছন্দগুলি সহ নর্বাচন: %1$s, %2$s, %3$s, %4$s; %5$s</string>
<string name="hint_list_name">নামের তালিকা</string>
<string name="edit_hashtag_hint"># ছাড়া হ্যাশট্যাগ</string>
<string name="notifications_clear">পরিষ্কার</string>
<string name="notifications_apply_filter">ফিল্টার</string>
@ -367,33 +366,14 @@
<string name="notification_clear_text">আপনি কি আপনার সমস্ত বিজ্ঞপ্তি স্থায়ীভাবে মুছে ফেলতে চান\?</string>
<string name="compose_preview_image_description">ছবি %s এর জন্য ক্রিয়া</string>
<string name="poll_info_format"> <!-- ১৫ ভোট • ১ ঘন্টা বাকি --> %1$s • %2$s</string>
<plurals name="poll_info_votes">
<item quantity="one"></item>
<item quantity="other"></item>
</plurals>
<string name="poll_info_time_relative">%s বাকি</string>
<string name="poll_info_time_absolute">%s এ শেষ হবে</string>
<string name="poll_info_closed">বন্ধ</string>
<string name="poll_vote">ভোট</string>
<string name="poll_ended_voted">আপনি ভোট দিয়েছেন যে নির্বাচন এ সেটি শেষ হয়েছে</string>
<string name="poll_ended_created">আপনি তৈরি একটি নির্বাচন শেষ হয়েছে</string>
<!--These are for timestamps on polls -->
<plurals name="poll_timespan_days">
<item quantity="one">%d দিন</item>
<item quantity="other">%d দিন</item>
</plurals>
<plurals name="poll_timespan_hours">
<item quantity="one"></item>
<item quantity="other"></item>
</plurals>
<plurals name="poll_timespan_minutes">
<item quantity="one"></item>
<item quantity="other"></item>
</plurals>
<plurals name="poll_timespan_seconds">
<item quantity="one"></item>
<item quantity="other"></item>
</plurals>
<string name="button_continue">চালিয়ে যান</string>
<string name="button_back">পিছনে যান</string>
<string name="button_done">সম্পন্ন</string>

View File

@ -4,16 +4,16 @@
<string name="error_empty">Això no pot estar buit.</string>
<string name="error_invalid_domain">El domini introduït no és vàlid</string>
<string name="error_failed_app_registration">L\'autenticació en aquesta instància ha fallat.</string>
<string name="error_no_web_browser_found">No s\'ha trobat cap navegador web per a usar.</string>
<string name="error_no_web_browser_found">No s\'ha trobat cap navegador web per a utilitzar.</string>
<string name="error_authorization_unknown">S\'ha produït un error d\'autorització no identificat.</string>
<string name="error_authorization_denied">S\'ha denegat l\'autorització.</string>
<string name="error_retrieving_oauth_token">L\'obtenció del testimoni d\'inici de sessió ha fallat.</string>
<string name="error_retrieving_oauth_token">L\'obtenció del token d\'inici de sessió ha fallat.</string>
<string name="error_compose_character_limit">L\'estat és massa llarg!</string>
<string name="error_image_upload_size">El fitxer ha de ser inferior a 8MB.</string>
<string name="error_media_upload_type">Aquest tipus de fitxer no es pot pujar.</string>
<string name="error_media_upload_opening">Aquest tipus de fitxer no es pot obrir.</string>
<string name="error_media_upload_permission">Cal permís d\'accés al emmagatzematge.</string>
<string name="error_media_download_permission">Cal permís d\'escriptura en el mitjà.</string>
<string name="error_media_download_permission">Cal permís d\'escriptura en el dispositiu.</string>
<string name="error_media_upload_image_or_video">No es poden adjuntar imatges i vídeos en el mateix estat.</string>
<string name="error_media_upload_sending">La pujada ha fallat.</string>
<string name="title_home">Inici</string>
@ -202,15 +202,15 @@
<string name="title_direct_messages">Missatges directes</string>
<string name="message_empty">No hi ha res aquí.</string>
<string name="action_unreblog">Elimina l\'impuls</string>
<string name="error_network">S\'ha produït un error de connexió! Comprova la connexió!</string>
<string name="error_video_upload_size">Els fitxers de vídeo han de tenir menys de 40 MB.</string>
<string name="error_network">S\'ha produït un error de connexió! Comprova la connexió i torna-ho a provar!</string>
<string name="error_video_upload_size">Els fitxers de vídeo han de pesar menys de 40 MB.</string>
<string name="status_media_hidden_title">Multimèdia amagada</string>
<string name="status_content_show_less">Amaga</string>
<string name="action_logout_confirm">Estàs segur de tancar la sessió de %1$s\?</string>
<string name="action_hide_reblogs">Amaga els impulsos</string>
<string name="action_show_reblogs">Mostra els impulsos</string>
<string name="action_delete_and_redraft">Elimina i reecririu</string>
<string name="action_open_drawer">Open drawer</string>
<string name="action_open_drawer">Calaix obert</string>
<string name="action_toggle_visibility">Visibilitat del toot</string>
<string name="action_content_warning">Contingut sensible</string>
<string name="action_add_tab">Afegeix una pestanya</string>
@ -226,7 +226,7 @@
<string name="send_media_to">Compartir la imatge a …</string>
<string name="status_sent">Enviat!</string>
<string name="state_follow_requested">Petició de seguiment enviada</string>
<string name="title_statuses_with_replies">Amb resposta</string>
<string name="title_statuses_with_replies">Amb respostes</string>
<string name="action_emoji_keyboard">Teclat d\'emojis</string>
<string name="action_open_media_n">Obrir el media #%d</string>
<string name="action_open_as">Obrir com %s</string>
@ -351,7 +351,6 @@
<string name="description_visiblity_private">Seguidors</string>
<string name="description_visiblity_direct">Directe</string>
<string name="hint_list_name">Nom de la llista</string>
<string name="edit_hashtag_hint">Hashtag sense #</string>
<string name="notifications_clear">Netejar</string>
<string name="notifications_apply_filter">Filtrar</string>
@ -445,8 +444,24 @@
<string name="post_lookup_error_format">S\'ha produït un error en cercar la publicació %s</string>
<string name="gradient_for_media">Mostra degradats de colors per a contingut multimèdia ocult</string>
<string name="no_scheduled_status">No tens cap estat planificat.</string>
<string name="error_audio_upload_size">Els fitxers d\'àudio han de ser més petits de 40MB.</string>
<string name="error_audio_upload_size">Els fitxers d\'àudio han de ser més petits que 40MB.</string>
<string name="no_saved_status">No tens cap esborrany.</string>
<string name="warning_scheduling_interval">L\'interval mínim de planificació a Mastodon és de 5 minuts.</string>
<string name="notification_follow_request_name">Peticions de seguiment</string>
<string name="pref_title_confirm_reblogs">Mostra el diàleg de confirmació abans de promoure</string>
<string name="pref_title_show_cards_in_timelines">Mostra les previsualitzacions dels enllaços en els fils</string>
<string name="pref_title_enable_swipe_for_tabs">Habilita el gest de desplaçament per despleçar-te entre pestanyes</string>
<plurals name="poll_info_people">
<item quantity="one">%s persona</item>
<item quantity="other">%s persones</item>
</plurals>
<string name="hashtags">Hashtags</string>
<string name="add_hashtag_title">Afegir hashtag</string>
<string name="notification_follow_request_description">Notificacions sobre sol·licituds de seguiment</string>
<string name="pref_title_notification_filter_follow_requests">sol·licitació de seguiment</string>
<string name="dialog_mute_warning">Silenciar @%s\?</string>
<string name="dialog_block_warning">Bloquejar @%s\?</string>
<string name="action_unmute_conversation">No silenciar la conversació</string>
<string name="action_mute_conversation">Conversació muda</string>
<string name="notification_follow_request_format">%s ha sol·licitat seguir-te</string>
</resources>

View File

@ -362,9 +362,7 @@
</string>
<string name="hint_list_name">Název seznamu</string>
<string name="edit_hashtag_hint">Hashtag bez #</string>
<string name="compose_shortcut_long_label">Napsat toot</string>
<string name="compose_shortcut_short_label">Napsat</string>
<string name="notifications_clear">Vymazat</string>
<string name="notifications_apply_filter">Filtrovat</string>
@ -426,7 +424,7 @@
<string name="report_remote_instance">Přeposlat na %s</string>
<string name="failed_report">Nahlášení selhalo</string>
<string name="failed_fetch_statuses">Stahování tootů neuspělo</string>
<string name="report_description_1">Nahlášení bude zasláno moderátorům vašeho serveru. Níže můžete uvést, proč tento účet nahlašujete:</string>
<string name="report_description_1">Nahlášení bude zasláno moderátorovi vašeho serveru. Níže můžete uvést, proč tento účet nahlašujete:</string>
<string name="report_description_remote_instance">Tento účet je z jiného serveru. Chcete na něj také poslat anonymizovanou kopii\?</string>
<string name="pref_title_show_notifications_filter">Zobrazit filtr oznámení</string>
<string name="create_poll_title">Anketa</string>

View File

@ -290,4 +290,5 @@
<string name="action_links">Cysylltiadau</string>
<string name="title_links_dialog">Cysylltiadau</string>
<string name="action_open_reblogged_by">Dangos hybiadau</string>
<string name="notification_follow_request_name">Dilyn ceisiadau</string>
</resources>

View File

@ -330,9 +330,7 @@
<string name="hint_search_people_list">Suche nach Leuten denen du folgst</string>
<string name="action_remove_from_list">Von der Liste entfernen</string>
<string name="edit_hashtag_hint">Hashtag ohne #</string>
<string name="action_open_reblogger">Öffne Autor des geteilten Beitrages</string>
<string name="pref_title_public_filter_keywords">Öffentliche Zeitleisten</string>
<plurals name="favs">
<item quantity="one">&lt;b&gt;%1$s&lt;/b&gt; Favorit</item>
@ -432,10 +430,7 @@
<string name="action_view_bookmarks">Lesezeichen</string>
<string name="gradient_for_media">Bunten Farbverlauf für versteckte Medien anzeigen</string>
<string name="about_powered_by_tusky">Powered by Tusky</string>
<plurals name="reblogs">
<item quantity="one"></item>
<item quantity="other"></item>
</plurals>
<string name="description_status_bookmarked">Als Lesezeichen gespeichert</string>
<string name="select_list_title">Liste auswählen</string>
<string name="list">Liste</string>
@ -451,4 +446,6 @@
<string name="action_unmute_conversation">Stummschaltung der Konversation aufheben</string>
<string name="action_mute_conversation">Konversation stummschalten</string>
<string name="notification_follow_request_format">"%s möchte dir folgen"</string>
<string name="hashtags">Hashtags</string>
<string name="add_hashtag_title">Hashtag hinzufügen</string>
</resources>

View File

@ -158,7 +158,7 @@
<string name="pref_title_notification_filter_follows">me siguen</string>
<string name="pref_title_notification_filter_reblogs">mis posts son impulsados</string>
<string name="pref_title_notification_filter_favourites">me dan favorito</string>
<string name="pref_title_appearance_settings">Interfaz</string>
<string name="pref_title_appearance_settings">Apariencia</string>
<string name="pref_title_app_theme">Tema</string>
<string name="pref_title_timelines">Cronologia</string>
<string name="app_them_dark">Oscuro</string>
@ -402,7 +402,7 @@
<string name="mute_domain_warning_dialog_ok">Ocultar dominio completo</string>
<string name="pref_title_animate_gif_avatars">Animar avatares GIF</string>
<string name="filter_dialog_whole_word">Toda palabra</string>
<string name="filter_dialog_whole_word_description">Cuando la palabra o frase sea solo alfanumérica, solo se aplicará si coincide con toda la palabra.</string>
<string name="filter_dialog_whole_word_description">Cuando la palabra o frase sea solo alfanumérica, solo se aplicará si coincide con toda la palabra</string>
<string name="caption_notoemoji">Set de emojis actual de Google</string>
<string name="description_poll">Encuesta con opciones: %1$s, %2$s, %3$s, %4$s; %5$s</string>
<string name="button_continue">Continuar</string>
@ -451,4 +451,20 @@
<string name="no_scheduled_status">No tienes ningún estado programado.</string>
<string name="warning_scheduling_interval">Mastodon tiene un intervalo de programación mínimo de 5 minutos.</string>
<string name="notification_follow_request_name">Solicitudes</string>
<string name="dialog_block_warning">Bloquear @%s\?</string>
<string name="dialog_mute_warning">Silenciar @%s\?</string>
<string name="action_mute_conversation">Silenciar conversación</string>
<string name="action_unmute_conversation">Dejar de silenciar conversación</string>
<string name="notification_follow_request_description">Notificaciones de solicitudes</string>
<string name="pref_title_notification_filter_follow_requests">solicitud de seguimiento</string>
<string name="pref_title_confirm_reblogs">Mostrar diálogo de confirmación antes de impulsar</string>
<string name="pref_title_show_cards_in_timelines">Mostrar previsualización de enlaces en la cronología</string>
<string name="pref_title_enable_swipe_for_tabs">Habilitar gesto de deslizar para alternar entre pestañas</string>
<plurals name="poll_info_people">
<item quantity="one">%s persona</item>
<item quantity="other">%s personas</item>
</plurals>
<string name="hashtags">Etiquetas</string>
<string name="add_hashtag_title">Añadir etiqueta</string>
<string name="notification_follow_request_format">%s solicita seguirte</string>
</resources>

View File

@ -119,7 +119,7 @@
<string name="label_header">Goiburua</string>
<string name="link_whats_an_instance">Zer da instantzia?</string>
<string name="login_connection">Konektatzen…</string>
<string name="dialog_whats_an_instance">Sartu hemen helbidea edo mastodon.eus, mastodon.jalgi.eus, mastodon.social bezalako <a href="https://instances.social">edozein instantzia</a>,
<string name="dialog_whats_an_instance"> Sartu hemen helbidea edo mastodon.eus, mastodon.jalgi.eus, mastodon.social bezalako <a href="https://instances.social">edozein instantzia</a>,
\n
\n Oraindik ez baduzu konturik, instantziaren izena sartu eta bertan kontua sortu dezakezu.
\n
@ -388,7 +388,7 @@
</plurals>
<string name="poll_info_time_absolute">%s amaitzen da</string>
<string name="poll_info_closed">itxita</string>
<string name="poll_vote">Botatu</string>
<string name="poll_vote">Bozkatu</string>
<string name="poll_ended_voted">Botoa eman duzun galdeketa amaitu da</string>
<string name="poll_ended_created">Sortu duzun galdeketa amaitu da</string>
<plurals name="poll_timespan_days">
@ -448,4 +448,9 @@
<string name="no_scheduled_status">Ez duzu tut programaturik.</string>
<string name="warning_scheduling_interval">Mastodonek gutxienez 5 minutuko programazio-tartea du.</string>
<string name="notification_follow_request_name">Eskariak</string>
<string name="notification_follow_request_description">Jarraitzeko eskaereri buruzko jakinarazpenak</string>
<string name="dialog_mute_warning">\@%s isildu\?</string>
<string name="dialog_block_warning">\@%s blokeatu\?</string>
<string name="action_mute_conversation">Elkarrizketa isildu</string>
<string name="notification_follow_request_format">%s -k zu jarraitzeko eskatu dizu</string>
</resources>

View File

@ -26,7 +26,7 @@
<string name="title_statuses">پست‌ها</string>
<string name="title_statuses_with_replies">با پاسخ‌</string>
<string name="title_follows">دنبال شونده‌ها</string>
<string name="title_followers">پی‌گیر</string>
<string name="title_followers">پیروان</string>
<string name="title_favourites">علاقه‌مندی‌ها</string>
<string name="title_mutes">کاربرهای بی‌صدا</string>
<string name="title_blocks">کاربرهای مسدود شده</string>
@ -119,13 +119,13 @@
<string name="label_header">سرایند</string>
<string name="link_whats_an_instance">یک نمونه چیست؟</string>
<string name="login_connection">در حال اتصال …</string>
<string name="dialog_whats_an_instance">آدرس یا دامنه هر نمونه را می‌توانید وارد کنید، مثلا mastodon.social, icosahedron.website, social.tchncs.de, و &lt;a href=\"https://instances.social\" &gt;بیشتر!
\n
\n اگر شما هنوز حساب کاربری ندارید، می‌توانید نام نمونه مورد نظر را وارد کنید از اینجا بپیوندید و حساب کاربری ایجاد کنید.
\n
\n نمونه جایی است که حساب کاربری شما میزبان آن است اما شما به راحتی می‌توانید با افراد دیگر در نمونه‌های دیگر ارتباط برقرار کنید و آنها را دنبال کنید شما درست مثل اینکه در یکجا باشید.
\n
\n برای اطلاعات بیشتر به اینجا مراجعه کنید <a href="https://joinmastodon.org">joinmastodon.org</a>. </string>
<string name="dialog_whats_an_instance">نشانی یا دامنهٔ هر نمونه‌ای می‌تواند وارد شود، مثل mastodon.social, icosahedron.website, social.tchncs.de, و <a href="https://instances.social">بیش‌تر!</a>.
\n
\n اگر هنوز حسابی ندارید، می‌توانید نام نمونه مورد نظر را وارد کرده و در آن حسابی بسازید.
\n
\n نمونه، جاییست که حسابتان رویش میزبانی می‌شود، ولی به راحتی می‌توانید با دیگر افراد روی نمونه‌های دیگر ارتباط داشته و دنبالشان کنید؛ انگار که روی یک پایگاه باشید.
\n
\nاطّلاعات بیش‌تر می‌تواند در <a href="https://joinmastodon.org">joinmastodon.org</a> پیدا شود. </string>
<string name="dialog_title_finishing_media_upload">پایان بارگذاری رسانه</string>
<string name="dialog_message_uploading_media">در حال بارگذاری…</string>
<string name="dialog_download_image">بارگیری</string>
@ -195,9 +195,7 @@
<string name="notification_title_summary">%d برهم‌کنش جدید</string>
<string name="description_account_locked">حساب قفل‌شده</string>
<string name="about_title_activity">درباره</string>
<string name="about_tusky_license">Tusky یک برنامه آزاد و متن‌باز است که تحت مجوز GNU General Public License Version 3. منتشر شده است.
\n شما می‌توانید مجوز را از اینجا ببینید:
\n https://www.gnu.org/licenses/gpl-3.0.en.html</string>
<string name="about_tusky_license">تاسکی نرم‌افزاری آزاد است که تحت نگارش ۳ از پروانهٔ جامع همگانی گنو منتشر شده است. پروانه را می‌توانید از این‌جا ببینید: https://www.gnu.org/licenses/gpl-3.0.en.html</string>
<!-- note to translators:
* you should think of “free” as in “free speech,” not as in “free beer”.
We sometimes call it “libre software,” borrowing the French or Spanish word for “free” as in freedom,
@ -226,7 +224,7 @@
<string name="replying_to">در حال پاسخ به @%s</string>
<string name="load_more_placeholder_text">بارگیری بیش‌تر</string>
<string name="add_account_name">افزودن حساب</string>
<string name="add_account_description">افزودن حساب ماستودون جدید</string>
<string name="add_account_description">افزودن حساب جدید ماستودون</string>
<string name="action_lists">لیست‌ها</string>
<string name="title_lists">فهرست‌ها</string>
<string name="title_list_timeline">خط زمانی فهرست</string>
@ -237,7 +235,7 @@
<string name="action_remove">برداشتن</string>
<string name="lock_account_label">قفل حساب</string>
<string name="lock_account_label_description">نیاز دارد پی‌گیرانتان را به صورت دستی تأیید کنید</string>
<string name="compose_save_draft">ذخیره به عنوان پیش‌نویس</string>
<string name="compose_save_draft">ذخیرهٔ پیش‌نویس؟</string>
<string name="send_toot_notification_title">در حال فرستادن بوق…</string>
<string name="send_toot_notification_error_title">خطای فرستادن بوق</string>
<string name="send_toot_notification_channel_name">در حال فرستادن بوق‌ها</string>
@ -431,7 +429,7 @@
<string name="filter_dialog_whole_word_description">هنگامی که کلیدواژه یا عبارت، فقط حروف‌عددی باشد، فقط اگر با تمام واژه مطابق باشد، اعمال خواهد شد</string>
<string name="filter_add_description">عبارت پالایش</string>
<string name="poll_info_format"> <!-- 15 votes • 1 hour left --> %1$s • %2$s</string>
<string name="report_description_1">گزارش به ناظم‌های کارسازتان ارسال خواهد شد. می‌توانید توضیحی در باب چرایی گزارش این حساب در زیر بنویسید:</string>
<string name="report_description_1">گزارش به ناظر کارسازتان ارسال خواهد شد. می‌توانید توضیحی در باب چرایی گزارش این حساب در زیر بنویسید:</string>
<string name="report_description_remote_instance">این حساب از کارسازی دیگر است. رونوشتی ناشناس از گزارش، به آن‌جا نیز ارسال شود؟</string>
<string name="post_lookup_error_format">خطا در یافتن فرستهٔ %s</string>
<string name="about_powered_by_tusky">قدرت‌گرفته از تاسکی</string>
@ -446,4 +444,21 @@
<string name="no_saved_status">هیچ پیش‌نویسی ندارید.</string>
<string name="no_scheduled_status">هیچ وضعیت زمان‌بسته‌ای ندارید.</string>
<string name="warning_scheduling_interval">ماستودون، بازهٔ زمان‌بندی‌ای با کمینهٔ ۵ دقیقه دارد.</string>
<string name="pref_title_confirm_reblogs">نمایش گفت‌وگوی تأیید پیش از تقویت</string>
<string name="pref_title_show_cards_in_timelines">پیش‌نمایش پیوندها در خط‌زمانی‌ها</string>
<string name="pref_title_enable_swipe_for_tabs">به کار انداختن اشارهٔ کشیدنی برای تعویض بین زبانه‌ها</string>
<plurals name="poll_info_people">
<item quantity="one">%s نفر</item>
<item quantity="other">%s نفر</item>
</plurals>
<string name="hashtags">هشتگ‌ها</string>
<string name="add_hashtag_title">افزودن هشتگ</string>
<string name="notification_follow_request_description">آگاهی‌ها در مورد درخواست‌های پی‌گیری</string>
<string name="notification_follow_request_name">درخواست‌های پی‌گیری</string>
<string name="pref_title_notification_filter_follow_requests">درخواست پی‌گیری</string>
<string name="dialog_mute_warning">خموشی @%s؟</string>
<string name="dialog_block_warning">انسداد @%s؟</string>
<string name="action_unmute_conversation">ناخموشی گفت‌وگو</string>
<string name="action_mute_conversation">خموشی گفت‌وگو</string>
<string name="notification_follow_request_format">%s می‌خواهد دنبالتان کند</string>
</resources>

View File

@ -69,7 +69,7 @@
<string name="action_unfollow">Ne plus suivre</string>
<string name="action_block">Bloquer</string>
<string name="action_unblock">Débloquer</string>
<string name="action_hide_reblogs">Cacher les boosts</string>
<string name="action_hide_reblogs">Cacher les partages</string>
<string name="action_show_reblogs">Montrer les boosts</string>
<string name="action_report">Signaler</string>
<string name="action_delete">Supprimer</string>
@ -369,7 +369,7 @@
<string name="compose_shortcut_long_label">Rédiger un pouet</string>
<string name="compose_shortcut_short_label">Écrire</string>
<string name="pref_title_bot_overlay">Afficher l\'indicateur de robots</string>
<string name="notification_clear_text">"Nettoyer toutes les notifications de façon permanente\? "</string>
<string name="notification_clear_text">Désirez-vous nettoyer toutes vos notifications de façon permanente \?</string>
<string name="action_delete_and_redraft">Effacer et ré-écrire</string>
<string name="dialog_redraft_toot_warning">Effacer et ré-écrire ce pouet \?</string>
<plurals name="poll_info_votes">
@ -449,7 +449,7 @@
<string name="post_lookup_error_format">Erreur lors de la récupération du message %s</string>
<string name="about_powered_by_tusky">Propulsé par Tusky</string>
<string name="title_bookmarks">Signets</string>
<string name="action_bookmark">Marquer comme signet</string>
<string name="action_bookmark">Ajouter aux marque-pages</string>
<string name="action_view_bookmarks">Marque-pages</string>
<string name="description_status_bookmarked">Ajouté aux marque-pages</string>
<string name="select_list_title">Sélectionner la liste</string>
@ -474,4 +474,6 @@
<string name="action_unmute_conversation">Enlever la sourdine à la conversation</string>
<string name="action_mute_conversation">Silencer la conversation</string>
<string name="pref_title_enable_swipe_for_tabs">Activer les gestes de glissement pour passer dun onglet à lautre</string>
<string name="hashtags">Hashtags</string>
<string name="add_hashtag_title">Ajouter hashtag</string>
</resources>

View File

@ -345,14 +345,7 @@
<string name="label_remote_account">Ekki er víst að upplýsingarnar hér að neðan endurspegli notandasniðið að fullu. Opnaðu fullt notandasnið í vafra.</string>
<string name="unpin_action">Losa</string>
<string name="pin_action">Festa</string>
<plurals name="favs">
<item quantity="one"></item>
<item quantity="other"></item>
</plurals>
<plurals name="reblogs">
<item quantity="one"></item>
<item quantity="other"></item>
</plurals>
<string name="title_reblogged_by">Endurbirt af</string>
<string name="title_favourited_by">Sett í eftirlæti af</string>
<string name="conversation_1_recipients">%1$s</string>

View File

@ -9,8 +9,8 @@
<string name="action_edit_profile">Ẓreg amaɣnu</string>
<string name="action_search">Nadi</string>
<string name="about_title_activity">Ɣef</string>
<string name="action_lists">Umuɣen</string>
<string name="title_lists">Umuɣen</string>
<string name="action_lists">Tabdart</string>
<string name="title_lists">Tabdarin</string>
<string name="error_compose_character_limit">Tijewwiqt-ik aṭas i ɣuzzifet!</string>
<string name="title_home">Agejdan</string>
<string name="title_tab_preferences">Iccaren</string>
@ -123,16 +123,16 @@
<string name="status_share_link">Bḍu aseɣwen ɣer tijewwiqt</string>
<string name="filter_addition_dialog_title">Rnu amsizdeg</string>
<string name="filter_edit_dialog_title">Ẓreg amsizdeg</string>
<string name="action_create_list">Snulfu-d umuɣ</string>
<string name="action_rename_list">Snifel isem n wumuɣ</string>
<string name="action_delete_list">Kkes umuɣ-a</string>
<string name="action_edit_list">Ẓreg umuɣ-a</string>
<string name="action_add_to_list">Rnu yiwen umiḍan ɣer wummuɣ</string>
<string name="action_remove_from_list">Kkes amiḍan seg wumuɣ</string>
<string name="action_create_list">Snulfu-d tabdart</string>
<string name="action_rename_list">Snifel isem n tabdart</string>
<string name="action_delete_list">Kkes tabdart-a</string>
<string name="action_edit_list">Ẓreg tabdart-a</string>
<string name="action_add_to_list">Rnu yiwen umiḍan ɣer tabdart</string>
<string name="action_remove_from_list">Kkes amiḍan seg tabdart</string>
<string name="profile_metadata_add">Rnu isefka</string>
<string name="hint_list_name">Isem n wumuɣ</string>
<string name="hint_list_name">Isem n tebdart</string>
<string name="select_list_title">Fren tabdart</string>
<string name="list">Umuɣ</string>
<string name="list">Tabdart</string>
<string name="notifications_apply_filter">Sizdeg</string>
<string name="title_accounts">Imiḍanen</string>
<string name="add_poll_choice">Rnu yiwen wefran</string>
@ -207,7 +207,7 @@
<string name="poll_new_choice_hint">Tafrant %d</string>
<string name="title_follows">Ig ṭafaṛ</string>
<string name="title_followers">Imeḍfaṛen</string>
<string name="hint_search_people_list">Nadi ɣef medden i teṭafareḍ</string>
<string name="hint_search_people_list">Nadi ɣef medden ar at ḍfereḍ</string>
<string name="description_visiblity_private">Imeḍfaṛen</string>
<string name="action_links">Iseɣwan</string>
<string name="action_mentions">Tibdarin</string>
@ -263,4 +263,8 @@
<string name="action_block">Cekkel</string>
<string name="action_unblock">Kkes tacekkalt</string>
<string name="confirmation_unblocked">Tettwakkes tacekkalt ɣef umiḍan-nni</string>
<plurals name="poll_info_people">
<item quantity="one">%s n wemdan</item>
<item quantity="other">%s n yemdanen</item>
</plurals>
</resources>

View File

@ -114,7 +114,7 @@
<string name="action_mentions">멘션</string>
<string name="action_hashtags">해시태그</string>
<string name="action_open_reblogger">부스트한 유저의 프로필로 이동</string>
<string name="action_open_reblogged_by">부스트 보이기</string>
<string name="action_open_reblogged_by">이 유저의 부스트 보이기</string>
<string name="action_open_faved_by">즐겨찾기한 유저 보이기</string>
<string name="title_hashtags_dialog">해시태그</string>
<string name="title_mentions_dialog">멘션</string>
@ -132,7 +132,7 @@
<string name="confirmation_unblocked">차단이 해제됨</string>
<string name="confirmation_unmuted">뮤트가 해제됨</string>
<string name="confirmation_domain_unmuted">%s 숨김 해제됨</string>
<string name="status_sent">보냈습니다!</string>
<string name="status_sent">신고를 보냈습니다!</string>
<string name="status_sent_long">답장을 보냈습니다.</string>
<string name="hint_domain">인스턴스 주소</string>
<string name="hint_compose">지금 무엇을 하고 있나요\?</string>
@ -240,7 +240,7 @@
<string name="notification_mention_format">%s님이 당신을 멘션했습니다</string>
<string name="notification_summary_large">%1$s님, %2$s님, %3$s님 외 %4$d명</string>
<string name="notification_summary_medium">%1$s님, %2$s님, %3$s님</string>
<string name="notification_summary_small">%1$s, %2$s</string>
<string name="notification_summary_small">%1$s, %2$s</string>
<string name="notification_title_summary">%d개의 새로운 알림이 있습니다</string>
<string name="description_account_locked">계정 잠김</string>
<string name="about_title_activity">이 앱에 관하여</string>
@ -371,7 +371,7 @@
<string name="description_visiblity_unlisted">타임라인에 비표시</string>
<string name="description_visiblity_private">비공개</string>
<string name="description_visiblity_direct">다이렉트</string>
<string name="description_poll">투표: %1$s, %2$s, %3$s, %4$s; %5$s</string>
<string name="description_poll">투표 선택지: %1$s, %2$s, %3$s, %4$s, %5$s</string>
<string name="hint_list_name">리스트 이름</string>
<string name="edit_hashtag_hint">#를 제외한 해시태그</string>
<string name="notifications_clear">알림 지우기</string>

View File

@ -58,4 +58,5 @@
<string name="action_retry">വീണ്ടും ശ്രമിക്കുക</string>
<string name="action_view_follow_requests">പിന്‍തുടരുവാനുള്ള അഭ്യര്‍ത്ഥനകള്‍</string>
<string name="title_domain_mutes">മറയ്ക്കപ്പെട്ട ഡൊമൈനുകൾ</string>
<string name="description_visiblity_private">പിന്തുടരുന്നവർ</string>
</resources>

View File

@ -53,7 +53,7 @@
<string name="notification_favourite_format">%s markeerde jouw toot als favoriet</string>
<string name="notification_follow_format">%s volgt jou</string>
<string name="report_username_format">Rapporteer @%s</string>
<string name="report_comment_hint">Extra opmerkingen</string>
<string name="report_comment_hint">Extra opmerkingen\?</string>
<string name="action_quick_reply">Snelle reactie</string>
<string name="action_reply">Reageren</string>
<string name="action_reblog">Boosten</string>
@ -120,8 +120,8 @@
<string name="action_copy_link">Link kopiëren</string>
<string name="action_open_as">Als %s openen</string>
<string name="action_share_as">Delen als …</string>
<string name="send_status_link_to">Link van de toot delen</string>
<string name="send_status_content_to">Inhoud van de toot delen</string>
<string name="send_status_link_to">Link van de toot delen met…</string>
<string name="send_status_content_to">Inhoud van de toot delen met…</string>
<string name="send_media_to">Media delen met …</string>
<string name="confirmation_reported">Verzonden!</string>
<string name="confirmation_unblocked">Gebruiker is gedeblokkeerd</string>
@ -133,7 +133,7 @@
<string name="hint_content_warning">Waarschuwingstekst</string>
<string name="hint_display_name">Weergavenaam</string>
<string name="hint_note">Bio</string>
<string name="hint_search">Zoeken</string>
<string name="hint_search">Zoeken</string>
<string name="search_no_results">Geen resultaten</string>
<string name="label_quick_reply">Reageren…</string>
<string name="label_avatar">Avatar</string>
@ -142,9 +142,9 @@
<string name="login_connection">Aan het verbinden</string>
<string name="dialog_whats_an_instance">Het adres of domein van elke Mastodonserver kan hier worden ingevoerd, zoals mastodon.social, mastodon.nl, octodon.social en <a href="https://instances.social">nog veel meer!</a>
\n
\n Wanneer je nog geen account hebt, kun je de naam van de Mastodonserver waar jij je graag wil registeren invoeren, waarna je daar een account kunt aanmaken.
\nWanneer je nog geen account hebt, kun je de naam van de Mastodonserver waar jij je graag wil registeren invoeren, waarna je daar een account kunt aanmaken.
\n
\n Een Mastodonserver (Engels: instance) is een computerserver waar jouw account zich bevindt (vergelijk het met een e-mailserver). Je kan eenvoudig mensen van andere servers volgen en met ze communiceren, alsof jullie met elkaar op dezelfde website zitten.
\nEen Mastodonserver (Engels: instance) is een computerserver waar jouw account zich bevindt (vergelijk het met een e-mailserver). Je kan eenvoudig mensen van andere servers volgen en met ze communiceren, alsof jullie met elkaar op dezelfde website zitten.
\n
\n Meer informatie kun je vinden op <a href="https://joinmastodon.org">joinmastodon.org</a>.</string>
<string name="dialog_title_finishing_media_upload">Uploaden media bijna voltooid</string>
@ -368,8 +368,8 @@
<string name="notification_clear_text">Weet je zeker dat je alle meldingen permanent wilt verwijderen\?</string>
<string name="poll_info_format"> <!-- 15 votes • 1 hour left --> %1$s • %2$s</string>
<plurals name="poll_info_votes">
<item quantity="one"></item>
<item quantity="other"></item>
<item quantity="one">%s stem</item>
<item quantity="other">%s stemmen</item>
</plurals>
<string name="poll_info_time_relative">%s over</string>
<string name="poll_info_time_absolute">eindigt op %s</string>
@ -391,16 +391,16 @@
<string name="description_poll">Poll met keuzes: %1$s, %2$s, %3$s, %4$s; %5$s</string>
<string name="compose_preview_image_description">Acties voor afbeelding %s</string>
<plurals name="poll_timespan_days">
<item quantity="one"></item>
<item quantity="other"></item>
<item quantity="one">%d dag</item>
<item quantity="other">%d dagen</item>
</plurals>
<plurals name="poll_timespan_hours">
<item quantity="one"></item>
<item quantity="other"></item>
<item quantity="one">%d uur</item>
<item quantity="other">%d uur</item>
</plurals>
<plurals name="poll_timespan_minutes">
<item quantity="one"></item>
<item quantity="other"></item>
<item quantity="one">%d minuut</item>
<item quantity="other">%d minuten</item>
</plurals>
<plurals name="poll_timespan_seconds">
<item quantity="one">"%d seconde"</item>

View File

@ -340,8 +340,8 @@
<string name="dialog_redraft_toot_warning">Vil du slette dette tottet og skrive det på nytt\?</string>
<string name="poll_info_format"> <!-- 15 votes • 1 hour left --> %1$s • %2$s</string>
<plurals name="poll_info_votes">
<item quantity="one"></item>
<item quantity="other"></item>
<item quantity="one">%s stemme</item>
<item quantity="other">%s stemmer</item>
</plurals>
<string name="poll_info_time_relative">%s igjen</string>
<string name="poll_info_time_absolute">avsluttes %s</string>
@ -452,5 +452,6 @@
<string name="dialog_block_warning">Blokkere @%s\?</string>
<string name="action_unmute_conversation">Fjern demping av samtale</string>
<string name="action_mute_conversation">Demp samtale</string>
<string name="notification_follow_request_format">% ba om å få følge deg</string>
<string name="hashtags">Stikkord</string>
<string name="add_hashtag_title">Legg til stikkord</string>
</resources>

View File

@ -164,7 +164,7 @@
<string name="pref_title_http_proxy_port">Pòrt del servidor proxy HTTP</string>
<string name="pref_default_post_privacy">Privacitat predeterminada dels tuts</string>
<string name="pref_publishing">Publicacion</string>
<string name="post_privacy_public">Publica</string>
<string name="post_privacy_public">Public</string>
<string name="post_privacy_unlisted">Pas listada</string>
<string name="post_privacy_followers_only">Seguidors solament</string>
<string name="pref_status_text_size">Talha de text de l\'estatut</string>
@ -332,8 +332,8 @@
<item quantity="other"><b>%1$s</b> Favorits</item>
</plurals>
<plurals name="reblogs">
<item quantity="one"></item>
<item quantity="other"></item>
<item quantity="one"/>
<item quantity="other"/>
</plurals>
<string name="title_reblogged_by">Partejat per</string>
<string name="title_favourited_by">Aimat per</string>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="error_generic">Wystąpił błąd.</string>
<string name="error_empty">To nie może pozostać puste.</string>
<string name="error_empty">To pole nie może być puste.</string>
<string name="error_invalid_domain">Wprowadzono nieprawidłową domenę</string>
<string name="error_failed_app_registration">Nie udało się uwierzytelnić z tą instancją.</string>
<string name="error_no_web_browser_found">Nie znaleziono przeglądarki internetowej.</string>
@ -293,7 +293,7 @@
<string name="action_mute_domain">Wycisz %s</string>
<string name="action_add_tab">Dodaj zakładkę</string>
<string name="action_open_reblogger">Otwórz konto osoby podbijającej</string>
<string name="action_open_reblogged_by">Pokazuj podbicia</string>
<string name="action_open_reblogged_by">Pokaż podbicia</string>
<string name="action_open_media_n">Otwórz media #%d</string>
<string name="download_media">Pobierz media</string>
<string name="downloading_media">Pobieranie mediów</string>
@ -368,7 +368,7 @@
<string name="description_status_reblogged">Podbity</string>
<string name="description_status_favourited">Polubiony</string>
<string name="description_visiblity_public">Publiczny</string>
<string name="description_visiblity_unlisted">Niewidoczny</string>
<string name="description_visiblity_unlisted">Niewidoczne</string>
<string name="description_visiblity_private">Śledzący</string>
<string name="description_visiblity_direct">Bezpośrednio</string>
<string name="description_poll">Głosowanie z opcjami: %1$s, %2$s, %3$s, %4$s; %5$s</string>
@ -457,4 +457,10 @@
<string name="no_scheduled_status">Nie masz żadnych zaplanowanych wpisów.</string>
<string name="warning_scheduling_interval">Mastodon umożliwia wysłanie minimalnie 5 minut od zaplanowania</string>
<string name="notification_follow_request_name">Prośby o możliwość śledzenia</string>
<string name="pref_title_confirm_reblogs">Pytaj o potwierdzenie przed podbiciem</string>
<string name="add_hashtag_title">Dodaj hashtag</string>
<string name="dialog_mute_warning">Wyciszyć @%s\?</string>
<string name="dialog_block_warning">Zablokować @%s\?</string>
<string name="action_unmute_conversation">Cofnij wyciszenie rozmowy</string>
<string name="action_mute_conversation">Wycisz rozmowę</string>
</resources>

View File

@ -32,25 +32,25 @@
<string name="title_favourites">Favoritos</string>
<string name="title_mutes">Usuários silenciados</string>
<string name="title_blocks">Usuários bloqueados</string>
<string name="title_follow_requests">Solicitações de seguidor</string>
<string name="title_follow_requests">Seguidores pendentes</string>
<string name="title_edit_profile">Editar seu perfil</string>
<string name="title_saved_toot">Rascunhos</string>
<string name="title_licenses">Licenças</string>
<string name="status_boosted_format">%s compartilhou</string>
<string name="status_sensitive_media_title">Conteúdo sensível</string>
<string name="status_boosted_format">%s deu boost</string>
<string name="status_sensitive_media_title">Mídia sensível</string>
<string name="status_media_hidden_title">Mídia oculta</string>
<string name="status_sensitive_media_directions">Clique para exibir</string>
<string name="status_sensitive_media_directions">Toque para ver</string>
<string name="status_content_warning_show_more">Mostrar mais</string>
<string name="status_content_warning_show_less">Mostrar menos</string>
<string name="footer_empty">Nada aqui. Arraste para atualizar!</string>
<string name="notification_reblog_format">%s compartilhou o seu toot</string>
<string name="notification_favourite_format">%s curtiu o seu toot</string>
<string name="notification_follow_format">%s seguiu você</string>
<string name="notification_favourite_format">%s favoritou seu toot</string>
<string name="notification_follow_format">%s te seguiu</string>
<string name="report_username_format">Denunciar @%s</string>
<string name="report_comment_hint">Comentários adicionais?</string>
<string name="action_quick_reply">Resposta rápida</string>
<string name="action_reply">Responder</string>
<string name="action_reblog">Compartilhar</string>
<string name="action_reblog">Boost</string>
<string name="action_unreblog">Desfazer boost</string>
<string name="action_favourite">Favoritar</string>
<string name="action_unfavourite">Desfavoritar</string>
@ -76,7 +76,7 @@
<string name="action_view_favourites">Favoritos</string>
<string name="action_view_mutes">Usuários silenciados</string>
<string name="action_view_blocks">Usuários bloqueados</string>
<string name="action_view_follow_requests">Solicitações de seguidor</string>
<string name="action_view_follow_requests">Seguidores pendentes</string>
<string name="action_view_media">Mídia</string>
<string name="action_open_in_web">Abrir no navegador</string>
<string name="action_add_media">Adicionar mídia</string>
@ -170,7 +170,7 @@
<string name="pref_title_hide_follow_button">Esconder compositor ao rolar a página</string>
<string name="pref_title_status_filter">Filtro da linha do tempo</string>
<string name="pref_title_status_tabs">Abas</string>
<string name="pref_title_show_boosts">Mostrar compartilhamentos</string>
<string name="pref_title_show_boosts">Mostrar boosts</string>
<string name="pref_title_show_replies">Mostrar respostas</string>
<string name="pref_title_show_media_preview">Mostrar prévias de mídia</string>
<string name="pref_title_proxy_settings">Proxy</string>
@ -178,7 +178,7 @@
<string name="pref_title_http_proxy_enable">Ativar proxy HTTP</string>
<string name="pref_title_http_proxy_server">Servidor do proxy HTTP</string>
<string name="pref_title_http_proxy_port">Porta do proxy HTTP</string>
<string name="pref_default_post_privacy">Privacidade padrão dos posts</string>
<string name="pref_default_post_privacy">Privacidade padrão dos toots</string>
<string name="pref_default_media_sensitivity">Sempre marcar mídia como sensível</string>
<string name="pref_publishing">Publicação</string>
<string name="pref_failed_to_sync">Falha ao sincronizar configurações</string>
@ -190,15 +190,15 @@
<string name="status_text_size_medium">Médio</string>
<string name="status_text_size_large">Grande</string>
<string name="status_text_size_largest">Maior</string>
<string name="notification_mention_name">Novas Menções</string>
<string name="notification_mention_descriptions">Notificações sobre novas menções</string>
<string name="notification_follow_name">Novos Seguidores</string>
<string name="notification_follow_description">Notificações sobre novos seguidores</string>
<string name="notification_mention_name">Menções</string>
<string name="notification_mention_descriptions">Notificar sobre novas menções</string>
<string name="notification_follow_name">Seguidores</string>
<string name="notification_follow_description">Notificar sobre novos seguidores</string>
<string name="notification_boost_name">Boosts</string>
<string name="notification_boost_description">Notificar quando derem boost nos seus toots</string>
<string name="notification_favourite_name">Favoritos</string>
<string name="notification_favourite_description">Notificar quando favoritarem seus toots</string>
<string name="notification_mention_format">%s mencionou você</string>
<string name="notification_mention_format">%s te mencionou</string>
<string name="notification_summary_large">%1$s, %2$s, %3$s e %4$d outros</string>
<string name="notification_summary_medium">%1$s, %2$s, e %3$s</string>
<string name="notification_summary_small">%1$s e %2$s</string>
@ -237,7 +237,7 @@
<string name="abbreviated_minutes_ago">%dm</string>
<string name="abbreviated_seconds_ago">%ds</string>
<string name="follows_you">Segue você</string>
<string name="pref_title_alway_show_sensitive_media">Sempre mostrar conteúdo sensível</string>
<string name="pref_title_alway_show_sensitive_media">Sempre mostrar mídia sensível</string>
<string name="title_media">Mídia</string>
<string name="replying_to">Respondendo a @%s</string>
<string name="load_more_placeholder_text">carregar mais</string>
@ -330,7 +330,7 @@
<string name="action_add_to_list">Adicionar conta à lista</string>
<string name="action_remove_from_list">Remover conta da lista</string>
<string name="hint_describe_for_visually_impaired">Descrever para deficientes visuais
\n(limite de %d caracteres)</string>
\n(até %d caracteres)</string>
<string name="license_cc_by_4">CC-BY 4.0</string>
<string name="license_cc_by_sa_4">CC-BY-SA 4.0</string>
<string name="label_remote_account">As informações abaixo podem refletir incompletamente o perfil do usuário. Toque aqui para abrir o perfil completo no navegador.</string>
@ -403,7 +403,7 @@
<string name="report_remote_instance">Encaminhar para %s</string>
<string name="failed_report">Falha na denúncia</string>
<string name="failed_fetch_statuses">Falha ao carregar toots</string>
<string name="report_description_1">A denúncia será enviada para o seu administrador da instância. Você pode explicar por que você denunciou a conta:</string>
<string name="report_description_1">A denúncia será enviada aos moderadores da instância. Você pode explicar por que você denunciou a conta:</string>
<string name="report_description_remote_instance">A conta está em outra instância. Enviar uma cópia anônima da denúncia para lá\?</string>
<string name="title_domain_mutes">Instâncias bloqueadas</string>
<string name="action_view_domain_mutes">Instâncias bloqueadas</string>
@ -430,14 +430,14 @@
<string name="poll_allow_multiple_choices">Múltiplas opções</string>
<string name="poll_new_choice_hint">Opção %d</string>
<string name="edit_poll">Editar</string>
<string name="title_scheduled_toot">Toots agendados</string>
<string name="title_scheduled_toot">Agendados</string>
<string name="action_edit">Editar</string>
<string name="action_access_scheduled_toot">Toots agendados</string>
<string name="action_access_scheduled_toot">Agendados</string>
<string name="action_schedule_toot">Agendar toot</string>
<string name="action_reset_schedule">Cancelar</string>
<string name="post_lookup_error_format">Erro ao pesquisar %s</string>
<string name="title_bookmarks">Salvos</string>
<string name="action_bookmark">Salvo</string>
<string name="action_bookmark">Salvar</string>
<string name="action_view_bookmarks">Salvos</string>
<string name="about_powered_by_tusky">Desenvolvido por Tusky</string>
<string name="description_status_bookmarked">Salvo</string>
@ -447,6 +447,22 @@
<string name="no_scheduled_status">Sem toots agendados.</string>
<string name="error_audio_upload_size">Áudios devem ser menores que 40MB.</string>
<string name="no_saved_status">Sem rascunhos.</string>
<string name="warning_scheduling_interval">Mastodon possui um intervalo mínimo de agendamento de 5 minutos.</string>
<string name="notification_follow_request_name">Solicitações de seguidor</string>
<string name="warning_scheduling_interval">Mastodon possui um intervalo mínimo de 5 minutos para agendar.</string>
<string name="notification_follow_request_name">Seguidores pendentes</string>
<string name="notification_follow_request_format">%s quer te seguir</string>
<string name="action_mute_conversation">Silenciar conversa</string>
<string name="action_unmute_conversation">Desfazer silêncio</string>
<string name="dialog_block_warning">Bloquear @%s\?</string>
<string name="dialog_mute_warning">Silenciar @%s\?</string>
<string name="pref_title_notification_filter_follow_requests">pedirem para me seguir</string>
<string name="notification_follow_request_description">Notificar sobre seguidores pendentes</string>
<plurals name="poll_info_people">
<item quantity="one">%s pessoa</item>
<item quantity="other">%s pessoas</item>
</plurals>
<string name="pref_title_enable_swipe_for_tabs">Ativar deslizar para alternar entre abas</string>
<string name="pref_title_show_cards_in_timelines">Mostrar prévias de links nas linhas</string>
<string name="pref_title_confirm_reblogs">Solicitar confirmação antes de dar boost</string>
<string name="hashtags">Hashtags</string>
<string name="add_hashtag_title">Adicionar hashtag</string>
</resources>

View File

@ -474,7 +474,7 @@
<string name="gradient_for_media">Показывать цветные градиенты для скрытых медиа</string>
<string name="post_lookup_error_format">Ошибка поиска поста %s</string>
<string name="no_saved_status">У вас нет черновиков.</string>
<string name="no_scheduled_status">У вас нет запланированный постов.</string>
<string name="no_scheduled_status">У вас нет запланированных постов.</string>
<string name="warning_scheduling_interval">Минимальный интервал планирования в Mastodon составляет 5 минут.</string>
<string name="pref_title_confirm_reblogs">Показвать диалог подтверждения перед продвижением</string>
<string name="pref_title_show_cards_in_timelines">Показывать предосмотр ссылок в лентах</string>

View File

@ -37,7 +37,6 @@
<string name="title_saved_toot">Osnutki</string>
<string name="title_licenses">Licence</string>
<string name="status_username_format">\@%s</string>
<string name="status_boosted_format">% spodbudil</string>
<string name="status_sensitive_media_title">Občutljiva vsebina</string>
<string name="status_media_hidden_title">Medij je skrit</string>
<string name="status_sensitive_media_directions">Kliknite za ogled</string>
@ -284,7 +283,7 @@
<string name="restart_emoji">Če želite uveljaviti te spremembe, morate znova zagnati Tusky</string>
<string name="later">Kasneje</string>
<string name="restart">Znova zaženi</string>
<string name="caption_systememoji">Privzeti komplet emotikonov vaše naprave</string>
<string name="caption_systememoji">"Privzeti komplet emotikonov vaše naprave "</string>
<string name="caption_blobmoji">Blob emotikoni so znani od Android 4.4-7.1</string>
<string name="caption_twemoji">Mastodonov privzeti komplet emotikonov</string>
<string name="download_failed">Prenos ni uspel</string>
@ -304,12 +303,7 @@
<string name="label_remote_account">Spodnje informacije lahko nepopolno odražajo profil uporabnika. Pritisnite, da odprete polni profil v brskalniku.</string>
<string name="unpin_action">Odpni</string>
<string name="pin_action">Pripni</string>
<plurals name="favs">
<item quantity="one"></item>
<item quantity="two"></item>
<item quantity="few"></item>
<item quantity="other"></item>
</plurals>
<plurals name="reblogs">
<item quantity="one">&lt;b&gt;%s&lt;/b&gt; Spodbuda</item>
<item quantity="two">&lt;b&gt;%s&lt;/b&gt; Spodbudi</item>

View File

@ -470,4 +470,6 @@
<string name="dialog_block_warning">Blockera @%s\?</string>
<string name="action_unmute_conversation">Aktivera ljud på konversation</string>
<string name="notification_follow_request_format">%s vill följa dig</string>
<string name="add_hashtag_title">Lägg till hashtag</string>
<string name="hashtags">Hashtaggar</string>
</resources>

View File

@ -123,7 +123,7 @@
<string name="visibility_unlisted">அனைவருக்கும் காட்டாதே</string>
<string name="visibility_private">பின்பற்றுபவர்களுக்கு மட்டும் காண்பி</string>
<string name="visibility_direct">குறிபிடபட்டுள்ள பயனர்களுக்கு மட்டும் காண்பி</string>
<string name="pref_title_edit_notification_settings">அறிவிப்புகளை திருத</string>
<string name="pref_title_edit_notification_settings">அறிவிப்புகள்</string>
<string name="pref_title_notifications_enabled">அறிவிப்புகள்</string>
<string name="pref_title_notification_alerts">எச்சரிக்கைகள்</string>
<string name="pref_title_notification_alert_sound">ஒலி மூலம் தெரிவிக்கவும்</string>
@ -259,7 +259,7 @@
<string name="unpin_action">விடுவி</string>
<string name="pin_action">பொருத்து</string>
<string name="action_view_account_preferences">கணக்கரின் முன்னுரிமைகள்</string>
<string name="error_network">பிணைய பிழை ஏற்பட்டது! உங்கள் இணைப்பைச் சரிபார்த்து மீண்டும் முயற்சிக்கவும்!</string>
<string name="error_network">"பிணைய பிழை ஏற்பட்டது! உங்கள் இணைப்பைச் சரிபார்த்து மீண்டும் முயற்சிக்கவும்!"</string>
<string name="error_video_upload_size">காணொளி 40MB க்கும் குறைவாக இருக்க வேண்டும்.</string>
<string name="error_sender_account_gone">டூத் அனுப்ப இயலவில்லை</string>
<string name="title_direct_messages">நேரடி தகவல்</string>

View File

@ -0,0 +1,457 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="add_poll_choice">เพิ่มตัวเลือก</string>
<string name="poll_duration_7_days">7 วัน</string>
<string name="poll_duration_3_days">3 วัน</string>
<string name="poll_duration_1_day">1 วัน</string>
<string name="poll_duration_6_hours">6 ชั่วโมง</string>
<string name="poll_duration_1_hour">1 ชั่วโมง</string>
<string name="poll_duration_30_min">30 นาที</string>
<string name="poll_duration_5_min">5 นาที</string>
<string name="create_poll_title">โพล</string>
<string name="pref_title_enable_swipe_for_tabs">เปิดใช้งานการเลื่อนนิ้วเพื่อสลับระหว่างแท็บ</string>
<string name="pref_title_show_notifications_filter">แสดงตัวกรองการแจ้งเตือน</string>
<string name="failed_search">ค้นหาล้มเหลว</string>
<string name="title_accounts">บัญชี</string>
<string name="report_description_remote_instance">บัญชีนี้มาจากเซิร์ฟเวอร์อื่น ส่งสำเนารายงานที่ไม่ระบุชื่อไปที่นั่นด้วยหรือไม่\?</string>
<string name="report_description_1">รายงานจะถูกส่งไปยังผู้ดูแลเซิร์ฟเวอร์ของคุณ สามารถให้คำอธิบายว่าทำไมจึงรายงานบัญชีนี้ด้านล่าง:</string>
<string name="failed_fetch_statuses">ดึงข้อมูลสถานะล้มเหลว</string>
<string name="failed_report">รายงานล้มเหลว</string>
<string name="report_remote_instance">ส่งต่อไปยัง %s</string>
<string name="hint_additional_info">ความคิดเห็นเพิ่มเติม</string>
<string name="report_sent_success">รายงาน @%s เรียบร้อยแล้ว</string>
<string name="button_done">ทำ</string>
<string name="button_back">ย้อนกลับ</string>
<string name="button_continue">ต่อไป</string>
<plurals name="poll_timespan_seconds">
<item quantity="other">%d วินาที</item>
</plurals>
<plurals name="poll_timespan_minutes">
<item quantity="other">%d นาที</item>
</plurals>
<plurals name="poll_timespan_hours">
<item quantity="other">%d ชั่วโมง</item>
</plurals>
<plurals name="poll_timespan_days">
<item quantity="other">%d วัน</item>
</plurals>
<string name="poll_ended_created">โพลที่คุณสร้างสิ้นสุดลงแล้ว</string>
<string name="poll_ended_voted">โพลที่คุณโหวตสิ้นสุดลงแล้ว</string>
<string name="poll_vote">โหวต</string>
<string name="poll_info_closed">สิ้นสุดแล้ว</string>
<string name="poll_info_time_absolute">จบที่ %s</string>
<string name="poll_info_time_relative">เหลืออีก %s</string>
<plurals name="poll_info_people">
<item quantity="other">%s คน</item>
</plurals>
<plurals name="poll_info_votes">
<item quantity="other">%s โหวต</item>
</plurals>
<string name="poll_info_format">" &lt;!-- 15 votes • 1 hour left --&gt; %1$s • %2$s"<!-- 15 votes • 1 hour left -->
%1$s • %2$s</string>
<string name="compose_preview_image_description">การกระทำสำหรับภาพ %s</string>
<string name="notification_clear_text">ต้องการลบการแจ้งเตือนทั้งหมดอย่างสมบูรณ์\?</string>
<string name="compose_shortcut_short_label">เขียน</string>
<string name="compose_shortcut_long_label">เขียน Toot</string>
<string name="filter_apply">ใช้งาน</string>
<string name="notifications_apply_filter">คัดกรอง</string>
<string name="notifications_clear">ล้างการแจ้งเตือน</string>
<string name="list">รายการ</string>
<string name="select_list_title">เลือกรายการ</string>
<string name="hashtags">แฮชแท็ก</string>
<string name="edit_hashtag_hint">แฮชแท็กโดยไม่มี #</string>
<string name="add_hashtag_title">เพิ่มแฮชแท็ก</string>
<string name="hint_list_name">ชื่อรายการ</string>
<string name="description_poll">โพลกับตัวเลือก: %1$s, %2$s, %3$s, %4$s; %5$s</string>
<string name="description_visiblity_direct">ไดเร็กต์</string>
<string name="description_visiblity_private">ผู้ติดตาม</string>
<string name="description_visiblity_unlisted">ไม่อยู่ในรายการ</string>
<string name="description_visiblity_public">สาธารณะ</string>
<string name="description_status_bookmarked">คั่นหน้า</string>
<string name="description_status_favourited">ชื่นชอบ</string>
<string name="description_status_reblogged">ได้ถูกเขียนใหม่</string>
<string name="description_status_media_no_description_placeholder">ไม่มีคำอธิบาย</string>
<string name="description_status_cw">เตือนเนื้อหา : %s</string>
<string name="description_status_media">สื่อ: %s</string>
<string name="max_tab_number_reached">ถึงจำนวนแท็บสูงสุดคือ %1$d แล้ว</string>
<string name="conversation_more_recipients">%1$s, %2$s และอีก %3$d</string>
<string name="conversation_2_recipients">%1$s และ %2$s</string>
<string name="conversation_1_recipients">%1$s</string>
<string name="title_favourited_by">ชื่นชอบโดย</string>
<string name="title_reblogged_by">บูสต์โดย</string>
<plurals name="reblogs">
<item quantity="other">&lt;b&gt;%1$s&lt;/b&gt; บูสต์</item>
</plurals>
<plurals name="favs">
<item quantity="other">&lt;b&gt;%1$s&lt;/b&gt; ชื่นชอบ</item>
</plurals>
<string name="pin_action">ปักหมุด</string>
<string name="unpin_action">เลิกปักหมุด</string>
<string name="label_remote_account">ข้อมูลต่อไปนี้อาจไม่ถูกต้อง แตะเพื่อเปิดโปรไฟล์ในเบราว์เซอร์</string>
<string name="pref_title_absolute_time">แสดงเวลาแบบเที่ยงตรง</string>
<string name="profile_metadata_content_label">เนื้อหา</string>
<string name="profile_metadata_label_label">ป้าย</string>
<string name="profile_metadata_add">เพิ่มข้อมูล</string>
<string name="profile_metadata_label">ข้อมูลอภิพันธุ์</string>
<string name="license_cc_by_sa_4">CC-BY-SA 4.0</string>
<string name="license_cc_by_4">CC-BY 4.0</string>
<string name="license_apache_2">ภายใต้สัญญาอนุญาต Apache License (คัดลอกด้านล่าง)</string>
<string name="license_description">Tusky มีโค้ดและสินทรัพย์จากโครงการโอเพนซอร์สต่อไปนี้:</string>
<string name="unreblog_private">ยกเลิกบูสต์</string>
<string name="reblog_private">บูสต์โพสต์ต้นฉบับ</string>
<string name="account_moved_description">%1$s ได้ย้ายไปที่ :</string>
<string name="profile_badge_bot_text">บอต</string>
<string name="download_failed">ดาวน์โหลดล้มเหลว</string>
<string name="caption_notoemoji">ชุดเอโมจิปัจจุบันจากกูเกิล</string>
<string name="caption_twemoji">ชุดเอโมจิจาก Mastodon</string>
<string name="caption_blobmoji">ที่รู้จักจาก Android 4.4 ถึง 7.1 ชุดเอโมจิ Blob</string>
<string name="caption_systememoji">ชุดเริ่มต้นในอุปกรณ์คุณ</string>
<string name="restart">เริ่มใหม่</string>
<string name="later">ภายหลัง</string>
<string name="restart_emoji">จำเป็นต้องเริ่ม Tusky ใหม่ เพื่อใช้การเปลี่ยนแปลงเหล่านี้</string>
<string name="restart_required">จำเป็นต้องเริ่มแอปใหม่</string>
<string name="action_open_toot">เปิด Toot</string>
<string name="expand_collapse_all_statuses">ขยาย/ย่อทั้งหมด</string>
<string name="performing_lookup_title">กำลังค้นหา…</string>
<string name="download_fonts">ต้องดาวน์โหลดชุดเอโมจิเหล่านี้ก่อน</string>
<string name="system_default">ค่าปริยายของระบบ</string>
<string name="emoji_style">รูปแบบเอโมจิ</string>
<string name="copy_to_clipboard_success">คัดลอกไปยังคลิบบอร์ดแล้ว</string>
<string name="error_no_custom_emojis">Instance %s ไม่มีเอโมจิแบบกำหนดเอง</string>
<string name="action_compose_shortcut">เขียน</string>
<string name="send_toot_notification_saved_content">สำเนา Toot บันทึกเป็นฉบับร่างแล้ว</string>
<string name="send_toot_notification_cancel_title">การส่งถูกยกเลิก</string>
<string name="send_toot_notification_channel_name">ส่ง Toot</string>
<string name="send_toot_notification_error_title">การส่ง Toot เกิดข้อผิดผลาด</string>
<string name="send_toot_notification_title">กำลังส่ง Toot…</string>
<string name="compose_save_draft">บันทึกฉบับร่าง\?</string>
<string name="lock_account_label_description">ต้องอนุมัติผู้ติดตามด้วยตัวเอง</string>
<string name="lock_account_label">ล็อกบัญชี</string>
<string name="action_remove">ลบ</string>
<string name="error_failed_set_caption">ตั้งคำอธิบายล้มเหลว</string>
<string name="action_set_caption">ตั้งคำอธิบาย</string>
<string name="hint_describe_for_visually_impaired">อธิบายเพื่อผู้บกพร่องทางสายตา
\n(จำกัด %d ตัวอักขระ)</string>
<string name="compose_active_account_description">โพสต์ด้วยบัญชี %1$s</string>
<string name="action_remove_from_list">ลบบัญชีออกจากรายการ</string>
<string name="action_add_to_list">เพิ่มบัญชีไปใส่รายการ</string>
<string name="hint_search_people_list">ค้นหาผู้ติดตาม</string>
<string name="action_edit_list">แก้ไขรายการ</string>
<string name="action_delete_list">ลบรายการ</string>
<string name="action_rename_list">เปลี่ยนชื่อรายการ</string>
<string name="action_create_list">สร้างรายการ</string>
<string name="error_delete_list">ไม่สามารถลบรายการได้</string>
<string name="error_rename_list">ไม่สามารถเปลี่ยนชื่อรายการได้</string>
<string name="error_create_list">ไม่สามารถสร้างรายการได้</string>
<string name="title_list_timeline">ไทม์ไลน์ในรายการ</string>
<string name="add_account_description">เพิ่มบัญชี Mastodon ใหม่</string>
<string name="add_account_name">เพิ่มบัญชี</string>
<string name="filter_add_description">วลีที่ต้องการกรอง</string>
<string name="filter_dialog_whole_word_description">ถ้าคำหลักหรือวลีเป็นอักษรผสมตัวเลข จะใช้ได้ผลเมื่อตรงทั้งคำเท่านั้น</string>
<string name="filter_dialog_whole_word">ทั้งคำ</string>
<string name="filter_dialog_update_button">อัปเดต</string>
<string name="filter_dialog_remove_button">ลบ</string>
<string name="filter_edit_dialog_title">แก้ไขตัวคัดกรอง</string>
<string name="filter_addition_dialog_title">เพิ่มตัวคัดกรอง</string>
<string name="pref_title_thread_filter_keywords">การสนทนา</string>
<string name="pref_title_public_filter_keywords">ไทม์ไลน์สาธารณะ</string>
<string name="load_more_placeholder_text">โหลดเพิ่ม</string>
<string name="replying_to">ตอบกลับไป @%s</string>
<string name="title_media">สื่อ</string>
<string name="pref_title_alway_open_spoiler">ขยาย Toot ที่มีเครื่องหมายเนื้อหาอ่อนไหวเสมอ</string>
<string name="pref_title_alway_show_sensitive_media">แสดงเนื้อหาอ่อนไหวเสมอ</string>
<string name="follows_you">กำลังติดตามคุณ</string>
<string name="abbreviated_seconds_ago">%d วินาทีที่แล้ว</string>
<string name="abbreviated_minutes_ago">%d นาทีที่แล้ว</string>
<string name="abbreviated_hours_ago">%d ชั่วโมงที่แล้ว</string>
<string name="abbreviated_days_ago">%d วันที่แล้ว</string>
<string name="abbreviated_years_ago">%d ปีที่แล้ว</string>
<string name="abbreviated_in_seconds">ใน %d วินาที</string>
<string name="abbreviated_in_minutes">ใน %d นาที</string>
<string name="abbreviated_in_hours">ใน %d ชั่วโมง</string>
<string name="abbreviated_in_days">ใน %d วัน</string>
<string name="abbreviated_in_years">ใน %d ปี</string>
<string name="state_follow_requested">กำลังขอติดตาม</string>
<string name="status_media_video">วิดีทัศน์</string>
<string name="status_media_images">ภาพ</string>
<string name="status_share_link">แบ่งปันลิงก์ Toot</string>
<string name="status_share_content">แบ่งปันเนื้อหา Toot</string>
<string name="about_tusky_account">บัญชีทางการของ Tusky</string>
<string name="about_bug_feature_request_site">รายงานช่องโหว่ และ ขอฟีเจอร์ (ภาษาอังกฤษ):
\nhttps://github.com/tuskyapp/Tusky/issues</string>
<string name="about_project_site">เว็บไซต์โปรเจกต์:
\nhttps://tusky.app</string>
<string name="about_tusky_license">Tusky คือซอฟต์แวร์เสรีและโอเพนซอร์ส &lt;!-- --&gt; ภายใต้สัญญาอนุญาต GNU General Public License Version 3 &lt;!-- --&gt;ดูสัญญาที่ : https://www.gnu.org/licenses/gpl-3.0.ja.html</string>
<string name="about_powered_by_tusky">ขับเคลื่อนด้วย Tusky</string>
<string name="about_tusky_version">Tusky %s</string>
<string name="description_account_locked">บัญชีไม่สาธารณะ</string>
<string name="notification_title_summary">การโต้ตอบใหม่จำนวน %d</string>
<string name="notification_summary_small">%1$s และ %2$s</string>
<string name="notification_summary_medium">%1$s, %2$s, และ %3$s</string>
<string name="notification_summary_large">%1$s, %2$s, %3$s และอีก %4$d คน</string>
<string name="notification_mention_format">%s ตอบกลับคุณ</string>
<string name="notification_poll_description">การแจ้งเตือนเมื่อโพลได้สิ้นสุดลงแล้ว</string>
<string name="notification_poll_name">โพล</string>
<string name="notification_favourite_description">การแจ้งเตือนเมื่อ Toot คุณถูกชื่นชอบ</string>
<string name="notification_favourite_name">ชื่นชอบ</string>
<string name="notification_boost_description">การแจ้งเตือนเมื่อ Toot คุณถูกบูสต์</string>
<string name="notification_boost_name">บูสต์</string>
<string name="notification_follow_request_description">การแจ้งเตือนคำขอติดตามใหม่</string>
<string name="notification_follow_request_name">คำขอติดตาม</string>
<string name="notification_follow_description">การแจ้งเตือนเกี่ยวกับผู้ติดตามใหม่</string>
<string name="notification_follow_name">ผู้ติดตามใหม่</string>
<string name="notification_mention_name">การกล่าวถึงใหม่</string>
<string name="notification_mention_descriptions">การแจ้งเตือนเกี่ยวกับการกล่าวถึงใหม่</string>
<string name="status_text_size_largest">ใหญ่มาก</string>
<string name="status_text_size_large">ใหญ่</string>
<string name="status_text_size_medium">กลาง</string>
<string name="status_text_size_small">เล็ก</string>
<string name="status_text_size_smallest">เล็กสุด</string>
<string name="pref_status_text_size">ขนาดอักษร Toot</string>
<string name="post_privacy_followers_only">เฉพาะผู้ติดตาม</string>
<string name="post_privacy_unlisted">ไม่อยู่ในรายการ</string>
<string name="post_privacy_public">สาธารณะ</string>
<string name="pref_failed_to_sync">ซิงค์การตั้งค่าล้มเหลว</string>
<string name="pref_publishing">กำลังเผยแพร่ (synced with server)</string>
<string name="pref_default_media_sensitivity">ใส่เครื่องหมายว่าเป็นสื่ออ่อนไหวเสมอ</string>
<string name="pref_default_post_privacy">ความเป็นส่วนตัวโพสต์ปริยาย</string>
<string name="pref_title_http_proxy_port">พอร์ตพร็อกซี่ HTTP</string>
<string name="pref_title_http_proxy_server">เซิร์ฟเวอร์พร็อกซี่ HTTP</string>
<string name="pref_title_http_proxy_enable">เปิดใช้งานพร็อกซี่ HTTP</string>
<string name="pref_title_http_proxy_settings">พร็อกซี่ HTTP</string>
<string name="pref_title_proxy_settings">พร็อกซี่</string>
<string name="pref_title_show_media_preview">ดาวน์โหลดตัวอย่างสื่อ</string>
<string name="pref_title_show_replies">แสดงการตอบกลับ</string>
<string name="pref_title_show_boosts">แสดงบูสต์</string>
<string name="pref_title_status_tabs">แท็บ</string>
<string name="pref_title_status_filter">คัดกรองไทม์ไลน์</string>
<string name="gradient_for_media">แสดงสื่อที่ซ่อนไว้แบบไล่หลากสี</string>
<string name="pref_title_animate_gif_avatars">อวตาร GIF เคลื่อนไหวได้</string>
<string name="pref_title_bot_overlay">แสดงสัญลักษณ์ว่าเป็นบอต</string>
<string name="pref_title_language">ภาษา</string>
<string name="pref_title_hide_follow_button">ซ่อนปุ่มเขียนเมื่อกำลังเลื่อนจอ</string>
<string name="pref_title_custom_tabs">ใช้ Chrome Custom Tabs</string>
<string name="pref_title_browser_settings">เบราว์เซอร์</string>
<string name="app_theme_system">ใช้ตามแบบระบบ</string>
<string name="app_theme_auto">ปรับตามเวลา</string>
<string name="app_theme_black">ดำ</string>
<string name="app_theme_light">สว่าง</string>
<string name="app_them_dark">มืด</string>
<string name="pref_title_timeline_filters">คัดกรอง</string>
<string name="pref_title_timelines">ไทม์ไลน์</string>
<string name="pref_title_app_theme">ธีมแอป</string>
<string name="pref_title_appearance_settings">ลักษณะ</string>
<string name="pref_title_notification_filter_poll">โพลสิ้นสุดแล้ว</string>
<string name="pref_title_notification_filter_favourites">โพสต์ถูกชื่นชอบ</string>
<string name="pref_title_notification_filter_reblogs">โพสต์ถูกบูสต์</string>
<string name="pref_title_notification_filter_follow_requests">คำขอติดตาม</string>
<string name="pref_title_notification_filter_follows">ติดตาม</string>
<string name="pref_title_notification_filter_mentions">กล่าวถึง</string>
<string name="pref_title_notification_filters">แจ้งฉันเมื่อ</string>
<string name="pref_title_notification_alert_sound">แจ้งด้วยเสียง</string>
<string name="pref_title_notification_alert_vibrate">แจ้งด้วยการสั่น</string>
<string name="pref_title_notification_alert_light">แจ้งด้วยแสง</string>
<string name="pref_title_notification_alerts">เตือน</string>
<string name="pref_title_notifications_enabled">การแจ้งเตือนแบบ Push</string>
<string name="pref_title_edit_notification_settings">ตั้งค่าการแจ้งเตือน</string>
<string name="visibility_direct">ไดเร็กต์:โพสต์ให้เฉพาะผู้ที่ถูกกล่าวถึงเห็น</string>
<string name="visibility_private">เฉพาะผู้ติดตาม:โพสต์ให้เฉพาะผู้ติดตามเห็น</string>
<string name="visibility_unlisted">ไม่อยู่ในรายการ:ไม่แสดงในไทม์ไลน์สาธารณะ</string>
<string name="visibility_public">สาธารณะ:โพสต์ในไทม์ไลน์สาธารณะ</string>
<string name="dialog_mute_warning">ปิดเสียง @%s\?</string>
<string name="dialog_block_warning">บล็อก @%s\?</string>
<string name="mute_domain_warning_dialog_ok">ซ่อนทั้งโดเมน</string>
<string name="mute_domain_warning">ต้องการบล็อกทุกอย่างจาก %s \? คุณจะไม่เห็นเนื้อหาจากโดเมนนั้นในไทม์ไลน์สาธารณะหรือในการแจ้งเตือน ผู้ติดตามของคุณจากโดเมนนั้นจะถูกลบออก</string>
<string name="dialog_redraft_toot_warning">ลบ แล้ว ร่าง Toot นี้ใหม่\?</string>
<string name="dialog_delete_toot_warning">ลบ Toot นี้\?</string>
<string name="dialog_unfollow_warning">เลิกติดตามผู้ใช้นี้\?</string>
<string name="dialog_message_cancel_follow_request">ยกเลิกคำขอติดตาม\?</string>
<string name="dialog_download_image">ดาวน์โหลด</string>
<string name="dialog_message_uploading_media">กำลังอัปโหลด…</string>
<string name="dialog_title_finishing_media_upload">กำลังอัปโหลดสื่อใกล้เสร็จ</string>
<string name="dialog_whats_an_instance">"ใส่ที่อยู่หรือโดเมนของ Instance ได้ที่นี่ เช่น mastodon.social icosahedron.website social.tchncs.de และ &lt;a href=\"https://instances.social\"&gt;อีกมากมาย!&lt;/a&gt;
\n
\nถ้ายังไม่มีบัญชี สามารถใส่ชื่อ Instance ที่ต้องการจะร่วมแล้วสร้างบัญชีที่นั่น
\n
\nInstance คือที่ที่หนึ่งไว้โฮสต์บัญชีคุณ แต่คุณยังสามารถสื่อสาร ติดตามบุคคลบน Instance อื่นได้เหมือนอยู่บนไซต์เดียวกัน
\n
\nพบข้อมูลเพิ่มเติมได้ที่ &lt;a href=\"https://joinmastodon.org\"&gt;joinmastodon.org&lt;/a&gt; "<a href="https://instances.social">more!</a>
\n\nIf you don\'t yet have an account, you can enter the name of the instance you\'d like to
join and create an account there.\n\nAn instance is a single place where your account is
hosted, but you can easily communicate with and follow folks on other instances as though
you were on the same site.
\n\nMore info can be found at <a href="https://joinmastodon.org">joinmastodon.org</a>.
</string>
<string name="label_header">ภาพหัวบน</string>
<string name="login_connection">กำลังเชื่อมต่อ…</string>
<string name="label_avatar">อวตาร</string>
<string name="label_quick_reply">ตอบกลับ…</string>
<string name="search_no_results">ไม่มีผลลัพธ์</string>
<string name="hint_search">ค้นหา…</string>
<string name="hint_note">ข้อมูลส่วนตัว</string>
<string name="hint_display_name">ชื่อที่ใช้แสดง</string>
<string name="hint_content_warning">คำเตือนเนื้อหา</string>
<string name="hint_compose">เกิดอะไรขึ้นเอย\?</string>
<string name="hint_domain">Instance ไหน\?</string>
<string name="status_sent_long">ตอบกลับสำเร็จ</string>
<string name="status_sent">ส่งแล้ว!</string>
<string name="confirmation_domain_unmuted">เลิกซ่อน %s แล้ว</string>
<string name="confirmation_unblocked">เลิกบล็อกผู้ใช้แล้ว</string>
<string name="confirmation_unmuted">เลิกปิดเสียงผู้ใช้นี้แล้ว</string>
<string name="confirmation_reported">ส่งแล้ว!</string>
<string name="send_media_to">แบ่งปันสื่อไป…</string>
<string name="send_status_content_to">แบ่งปัน Toot ไป…</string>
<string name="send_status_link_to">แชร์ URL Toot ไป…</string>
<string name="downloading_media">กำลังดาวน์โหลดสื่อ</string>
<string name="download_media">ดาวน์โหลดสื่อ</string>
<string name="action_share_as">แบ่งปันโดย…</string>
<string name="action_open_as">เปิดเป็น %s</string>
<string name="action_copy_link">คัดลอกลิงก์</string>
<string name="download_image">กำลังดาวน์โหลด %1$s</string>
<string name="action_open_media_n">เปิดสื่อ #%d</string>
<string name="title_links_dialog">ลิงก์</string>
<string name="title_mentions_dialog">โต้ตอบ</string>
<string name="title_hashtags_dialog">แฮชแท็ก</string>
<string name="action_open_faved_by">ดูชื่นชอบ</string>
<string name="action_open_reblogged_by">ดูบสต์</string>
<string name="action_open_reblogger">ดูต้นตอบูสต์</string>
<string name="action_hashtags">แฮชแท็ก</string>
<string name="action_mentions">โต้ตอบ</string>
<string name="action_links">ลิงก์</string>
<string name="action_add_tab">เพิ่มแท็บ</string>
<string name="action_schedule_toot">Toot แบบตั้งเวลา</string>
<string name="action_emoji_keyboard">คีย์บอร์ดเอโมจิ</string>
<string name="action_content_warning">เตือนเนื้อหา</string>
<string name="action_toggle_visibility">การมองเห็น Toot</string>
<string name="action_access_scheduled_toot">Toot แบบตั้งเวลา</string>
<string name="action_access_saved_toot">ฉบับร่าง</string>
<string name="action_reject">ปฏิเสธ</string>
<string name="action_accept">ยอมรับ</string>
<string name="action_undo">ย้อนการกระทำ</string>
<string name="action_edit_own_profile">แก้ไข</string>
<string name="action_save">บันทึก</string>
<string name="action_open_drawer">เปิดเมนู</string>
<string name="action_hide_media">ซ่อนสื่อ</string>
<string name="action_mention">กล่าวถึง</string>
<string name="action_unmute_conversation">เลิกปิดเสียงการสนทนา</string>
<string name="action_mute_conversation">ปิดเสียงการสนทนานี้</string>
<string name="action_mute_domain">ปิดเสียง %s</string>
<string name="action_unmute">เลิกปิดเสียง</string>
<string name="action_mute">ปิดเสียง</string>
<string name="action_share">แบ่งปัน</string>
<string name="action_photo_take">ถ่ายภาพ</string>
<string name="action_add_poll">เพิ่มโพล</string>
<string name="action_add_media">เพิ่มสื่อ</string>
<string name="action_open_in_web">เปิดในเบราว์เซอร์</string>
<string name="action_view_media">สื่อ</string>
<string name="action_view_follow_requests">คำขอติดตาม</string>
<string name="action_view_domain_mutes">โดเมนที่ซ่อนไว้</string>
<string name="action_view_blocks">ผู้ใช้ที่ถูกบล็อกไว้</string>
<string name="action_view_mutes">ผู้ใช้ที่ปิดเสียงไว้</string>
<string name="action_view_bookmarks">คั่นหน้า</string>
<string name="action_view_favourites">ชื่นชอบ</string>
<string name="action_view_profile">โปรไฟล์</string>
<string name="action_close">ปิด</string>
<string name="action_retry">ลองอีกครั้ง</string>
<string name="action_send_public">TOOT!</string>
<string name="action_send">TOOT</string>
<string name="action_delete_and_redraft">ลบ แล้ว ร่างใหม่</string>
<string name="action_delete">ลบ</string>
<string name="action_edit">แก้ไข</string>
<string name="action_report">รายงาน</string>
<string name="action_show_reblogs">แสดงบูสต์</string>
<string name="action_hide_reblogs">ซ่อนบูสต์</string>
<string name="action_unblock">เลิกบล็อก</string>
<string name="action_block">บล็อก</string>
<string name="action_unfollow">เลิกติดตาม</string>
<string name="action_follow">ติดตาม</string>
<string name="action_logout_confirm">ต้องการออกจากระบบของบัญชี %1$s \?</string>
<string name="action_compose">เขียนโพสต์ใหม่</string>
<string name="action_more">อื่น ๆ</string>
<string name="action_unfavourite">เลิกชื่นชอบ</string>
<string name="action_bookmark">คั่นหน้า</string>
<string name="action_favourite">ชื่นชอบ</string>
<string name="action_unreblog">ลบบูสต์</string>
<string name="action_reblog">บูสต์</string>
<string name="action_reply">ตอบกลับ</string>
<string name="action_quick_reply">ตอบกลับด่วน</string>
<string name="report_comment_hint">ความคิดเห็นเพิ่มเติม\?</string>
<string name="report_username_format">รายงาน @%s</string>
<string name="notification_follow_request_format">%s ต้องการติดตามคุณ</string>
<string name="notification_follow_format">%s ได้ติดตามคุณ</string>
<string name="notification_favourite_format">%s ได้ชื่นชอบ Toot คุณ</string>
<string name="notification_reblog_format">%s ได้บูสต์ Toot คุณ</string>
<string name="footer_empty">ไม่อะไรเลย ลากลงเพื่อรีเฟรช!</string>
<string name="message_empty">ไม่มีอะไร</string>
<string name="status_content_show_less">ย่อ</string>
<string name="status_content_show_more">ขยาย</string>
<string name="status_content_warning_show_less">แสดงน้อยลง</string>
<string name="status_content_warning_show_more">แสดงเพิ่มเติม</string>
<string name="status_sensitive_media_directions">แตะเพื่อดู</string>
<string name="status_media_hidden_title">สื่อที่ซ่อนไว้</string>
<string name="status_sensitive_media_title">เนื้อหาอ่อนไหว</string>
<string name="status_boosted_format">%s ได้บูสต์</string>
<string name="status_username_format">\@%s</string>
<string name="title_licenses">สัญญาอนุญาต</string>
<string name="title_scheduled_toot">Toot แบบกำหนดเวลา</string>
<string name="title_edit_profile">แก้ไขโปรไฟล์</string>
<string name="title_follow_requests">คำขอติดตาม</string>
<string name="title_domain_mutes">โดเมนที่ซ่อนไว้</string>
<string name="title_blocks">ผู้ใช้ที่ถูกบล็อก</string>
<string name="title_mutes">ผู้ใช้ที่ทำให้เป็นใบ้</string>
<string name="title_bookmarks">คั่นหน้า</string>
<string name="title_followers">ผู้ติดตาม</string>
<string name="title_follows">ติดตาม</string>
<string name="title_statuses_pinned">ปักหมุด</string>
<string name="title_statuses_with_replies">โพสต์และตอบกลับ</string>
<string name="title_statuses">โพสต์</string>
<string name="title_view_thread">เธรด</string>
<string name="title_tab_preferences">แท็บ</string>
<string name="title_direct_messages">ข้อความแบบไดเร็กต์</string>
<string name="title_public_federated">สหพันธ์</string>
<string name="title_public_local">ท้องถิ่น</string>
<string name="title_notifications">แจ้งเตือน</string>
<string name="title_home">หน้าหลัก</string>
<string name="error_sender_account_gone">การส่ง Toot เกิดความผิดพลาด</string>
<string name="error_media_upload_sending">อัปโหลดล้มเหลว</string>
<string name="error_media_upload_image_or_video">ไม่สามารถแนบรูปภาพและวิดีทัศน์ในโพสต์เดียวกันได้</string>
<string name="error_media_download_permission">ต้องมีสิทธิ์เขียนบนสื่อ</string>
<string name="error_media_upload_permission">ต้องมีสิทธิ์อ่านสื่อ</string>
<string name="error_media_upload_opening">ไม่สามารถเปิดไฟล์ได้</string>
<string name="error_media_upload_type">ไม่สามารถอัปโหลดไฟล์ประเภทนี้ได้</string>
<string name="error_audio_upload_size">ไฟล์เสียงต้องมีขนาดน้อยกว่า 40MB</string>
<string name="error_video_upload_size">ไฟล์วีดิทัศน์ต้องมีขนาดน้อยกว่า 40MB</string>
<string name="error_image_upload_size">ไฟล์ต้องมีขนาดน้อยกว่า 8MB</string>
<string name="error_compose_character_limit">ข้อความสถานะยาวเกินไป!</string>
<string name="error_retrieving_oauth_token">ไม่สามารถรับโทเค็นการเข้าสู่ระบบ</string>
<string name="error_authorization_denied">การขออนุญาตสิทธิถูกปฏิเสธ</string>
<string name="error_authorization_unknown">เกิดข้อผิดพลาดในการขออนุญาตสิทธิโดยไม่ทราบสาเหตุ</string>
<string name="error_no_web_browser_found">ไม่พบเว็บเบราว์เซอร์</string>
<string name="error_invalid_domain">โดเมนที่ป้อนไม่ถูกต้อง</string>
<string name="error_empty">ไม่สามารถโพสต์โดยไร้ข้อความได้</string>
<string name="error_network">เครือข่ายมีข้อผิดพลาดเกิดขึ้น! กรุณาตรวจสอบการเชื่อมต่อและลองอีกครั้ง!</string>
<string name="error_generic">เกิดข้อผิดพลาด</string>
<string name="title_lists">รายการ</string>
<string name="action_lists">รายการ</string>
<string name="about_title_activity">เกี่ยวกับแอปนี้</string>
<string name="action_reset_schedule">ล้างค่า</string>
<string name="action_search">ค้นหา</string>
<string name="action_edit_profile">แก้ไขโปรไฟล์</string>
<string name="action_view_account_preferences">ตั้งค่าบัญชี</string>
<string name="action_view_preferences">ตั้งค่า</string>
<string name="action_logout">ออกจากระบบ</string>
<string name="title_saved_toot">ฉบับร่าง</string>
<string name="title_favourites">ชื่นชอบ</string>
<string name="error_failed_app_registration">การยืนยันตัวตนทางอิเล็กทรอนิกส์กับ Instance นั้นล้มเหลว</string>
<string name="link_whats_an_instance">Instance คือ\?</string>
<string name="action_login">เข้าสู่ระบบด้วย Mastodon</string>
<string name="poll_allow_multiple_choices">เลือกได้หลายตัวเลือก</string>
<string name="pref_title_confirm_reblogs">แสดงข้อความยืนยันก่อนที่จะบูสต์</string>
<string name="pref_title_show_cards_in_timelines">แสดงตัวอย่างลิงก์ในไทม์ไลน์</string>
<string name="warning_scheduling_interval">Mastodon กำหนดเวลาขั้นต่ำ 5 นาที</string>
<string name="no_scheduled_status">ไม่มีสถานะแบบตั้งเวลาใด ๆ</string>
<string name="no_saved_status">ไม่มีฉบับร่างใด ๆ</string>
<string name="post_lookup_error_format">การค้นหาโพสต์ %s เกิดข้อผิดผลาด</string>
<string name="edit_poll">แก้ไข</string>
<string name="poll_new_choice_hint">ตัวเลือกที่ %d</string>
</resources>

View File

@ -184,7 +184,7 @@
<string name="post_privacy_unlisted">Liste dışı</string>
<string name="post_privacy_followers_only">Sadece takipçiler</string>
<string name="pref_status_text_size">Durum metin boyutu</string>
<string name="status_text_size_smallest">En küçük</string>
<string name="status_text_size_smallest">Çok küçük</string>
<string name="status_text_size_small">Küçük</string>
<string name="status_text_size_medium">Orta</string>
<string name="status_text_size_large">Büyük</string>
@ -392,7 +392,7 @@
<string name="hint_additional_info">Ek Yorumlar</string>
<string name="report_remote_instance">%s adresine ilet</string>
<string name="failed_fetch_statuses">Durumlar getirilemedi</string>
<string name="report_description_1">"Bildirim sunucu yöneticinize gönderilecektir. Bu hesabı neden bildirdiğinle ilgili açıklama yapabilirsin:"</string>
<string name="report_description_1">"Bildirim sunucu yöneticinize gönderilecektir. Bu hesabı neden bildirdiğinizle ilgili açıklama yapabilirsiniz:"</string>
<string name="report_description_remote_instance">Hesap başka bir sunucudan. Raporun anonim bir kopyasını da oraya gönderilsin mi\?</string>
<string name="pref_title_show_notifications_filter">Bildirim filtresini göster</string>
<string name="action_mentions">Bahsedenler</string>

View File

@ -108,4 +108,6 @@
<string name="hint_content_warning">Попередження про контент</string>
<string name="edit_poll">Змінити</string>
<string name="compose_shortcut_short_label">Написати</string>
<string name="action_unmute_conversation">Скасувати приглушення бесіди</string>
<string name="action_mute_conversation">Приглушити бесіду</string>
</resources>

View File

@ -413,11 +413,9 @@
<string name="action_view_bookmarks">书签</string>
<string name="action_view_domain_mutes">隐藏域名</string>
<string name="action_add_poll">新增意见调查</string>
<string name="action_mute_domain">隐藏</string>
<string name="action_access_scheduled_toot">定时嘟文</string>
<string name="action_schedule_toot">定时嘟文</string>
<string name="confirmation_domain_unmuted">%s 已解除静音</string>
<string name="mute_domain_warning">您确定要封锁 s 域名吗?您将不会在任何联邦时间轴或通知中看到该域名中的内容,并且来自该域名的关注者将被删除。</string>
<string name="mute_domain_warning_dialog_ok">隐藏整个域</string>
<string name="pref_title_animate_gif_avatars">动画GIF头像</string>
<string name="gradient_for_media">显示隐藏媒体的彩色渐变</string>
@ -430,11 +428,10 @@
<string name="description_poll">使用以下选项创建投票:%1$s, %2$s, %3$s, %4$s; %5$s</string>
<string name="select_list_title">选择清单</string>
<string name="list">清单</string>
<string name="compose_preview_image_description">图片 s 的动作</string>
<string name="button_continue">继续</string>
<string name="button_back">返回</string>
<string name="button_done">完成</string>
<string name="report_sent_success">成功回报 @%s</string>
<string name="report_sent_success">"成功回报 @%s"</string>
<string name="hint_additional_info">附加留言</string>
<string name="report_remote_instance">转发到 %s</string>
<string name="failed_report">回报失败</string>

View File

@ -20,7 +20,7 @@
<string name="error_media_upload_sending">媒體檔案上傳失敗</string>
<string name="error_sender_account_gone">嘟文發送時出錯</string>
<string name="title_home">主頁</string>
<string name="title_notifications">通知</string>
<string name="title_notifications">通知設定</string>
<string name="title_public_local">本站時間軸</string>
<string name="title_public_federated">跨站公開時間軸</string>
<string name="title_direct_messages">私信</string>
@ -30,7 +30,7 @@
<string name="title_statuses_with_replies">嘟文和回覆</string>
<string name="title_statuses_pinned">已置頂</string>
<string name="title_follows">正在關注</string>
<string name="title_followers">關注者</string>
<string name="title_followers">關注者</string>
<string name="title_favourites">我的收藏</string>
<string name="title_mutes">被靜音的使用者</string>
<string name="title_blocks">被封鎖的使用者</string>
@ -104,7 +104,7 @@
<string name="action_search">搜尋</string>
<string name="action_access_saved_toot">草稿</string>
<string name="action_toggle_visibility">設定嘟文可見範圍</string>
<string name="action_content_warning">設定敏感內容警告</string>
<string name="action_content_warning">敏感內容警告</string>
<string name="action_emoji_keyboard">插入表情符號</string>
<string name="action_add_tab">新增標籤頁</string>
<string name="action_links">連結</string>

View File

@ -7,7 +7,6 @@
<dimen name="compose_media_preview_margin">8dp</dimen>
<dimen name="compose_media_preview_margin_bottom">0dp</dimen>
<dimen name="compose_media_preview_size">120dp</dimen>
<dimen name="compose_options_margin">8dp</dimen>
<dimen name="account_avatar_margin">14dp</dimen>
<dimen name="tab_page_margin">16dp</dimen>
<dimen name="status_line_margin_start">36dp</dimen>
@ -49,4 +48,6 @@
<dimen name="adaptive_bitmap_inner_size">72dp</dimen>
<dimen name="adaptive_bitmap_outer_size">108dp</dimen>
<dimen name="fabMargin">16dp</dimen>
</resources>

View File

@ -110,6 +110,15 @@
<item>ja</item>
</string-array>
<string-array name="pref_main_nav_position_options">
<item>@string/pref_main_nav_position_option_top</item>
<item>@string/pref_main_nav_position_option_bottom</item>
</string-array>
<string-array name="pref_main_nav_position_values">
<item>top</item>
<item>bottom</item>
</string-array>
<string name="description_status" translatable="false">
<!-- Display name, cw?, content?, poll? relative date, reposted by?, reposted?, favorited?, bookmarked?, username, media?; visibility, fav number?, reblog number?-->

View File

@ -234,7 +234,7 @@
<string name="pref_title_language">Language</string>
<string name="pref_title_bot_overlay">Show indicator for bots</string>
<string name="pref_title_animate_gif_avatars">Animate GIF avatars</string>
<string name="gradient_for_media">Show colorful gradients for hidden media</string>
<string name="pref_title_gradient_for_media">Show colorful gradients for hidden media</string>
<string name="pref_title_status_filter">Timeline filtering</string>
<string name="pref_title_status_tabs">Tabs</string>
@ -252,6 +252,11 @@
<string name="pref_publishing">Publishing (synced with server)</string>
<string name="pref_failed_to_sync">Failed to sync settings</string>
<string name="pref_main_nav_position">Main navigation position</string>
<string name="pref_main_nav_position_option_top">Top</string>
<string name="pref_main_nav_position_option_bottom">Bottom</string>
<string name="post_privacy_public">Public</string>
<string name="post_privacy_unlisted">Unlisted</string>
<string name="post_privacy_followers_only">Followers-only</string>

View File

@ -1,89 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:key="@string/preferences_file_key">
<Preference
android:key="notificationPreference"
android:title="@string/pref_title_edit_notification_settings" />
<Preference
android:key="tabPreference"
android:title="@string/title_tab_preferences" />
<Preference
android:key="mutedUsersPreference"
android:title="@string/action_view_mutes" />
<Preference
android:key="blockedUsersPreference"
android:title="@string/action_view_blocks" />
<Preference
android:key="mutedDomainsPreference"
android:title="@string/action_view_domain_mutes" />
<PreferenceCategory android:title="@string/pref_publishing">
<ListPreference
android:defaultValue="public"
android:entries="@array/post_privacy_names"
android:entryValues="@array/post_privacy_values"
android:key="defaultPostPrivacy"
android:summary="%s"
android:title="@string/pref_default_post_privacy" />
<ListPreference
android:defaultValue="Plaintext"
android:entries="@array/formatting_syntax_values"
android:entryValues="@array/formatting_syntax_values"
android:key="defaultFormattingSyntax"
android:summary="%s"
android:title="@string/pref_title_default_formatting" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:icon="@drawable/ic_eye_24dp"
android:key="defaultMediaSensitivity"
android:title="@string/pref_default_media_sensitivity"
app:singleLineTitle="false" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_title_timelines">
<SwitchPreferenceCompat
android:key="mediaPreviewEnabled"
android:title="@string/pref_title_show_media_preview"
app:singleLineTitle="false" />
<SwitchPreferenceCompat
android:key="alwaysShowSensitiveMedia"
android:title="@string/pref_title_alway_show_sensitive_media"
app:singleLineTitle="false" />
<SwitchPreferenceCompat
android:key="alwaysOpenSpoiler"
android:title="@string/pref_title_alway_open_spoiler"
app:singleLineTitle="false" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_title_timeline_filters">
<Preference
android:key="publicFilters"
android:title="@string/pref_title_public_filter_keywords" />
<Preference
android:key="notificationFilters"
android:title="@string/title_notifications" />
<Preference
android:key="homeFilters"
android:title="@string/title_home" />
<Preference
android:key="threadFilters"
android:title="@string/pref_title_thread_filter_keywords" />
<Preference
android:key="accountFilters"
android:title="@string/title_accounts" />
</PreferenceCategory>
</androidx.preference.PreferenceScreen>

View File

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/pref_title_http_proxy_settings">
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="httpProxyEnabled"
android:title="@string/pref_title_http_proxy_enable"
app:iconSpaceReserved="false" />
<EditTextPreference
android:key="httpProxyServer"
android:summary="%s"
android:title="@string/pref_title_http_proxy_server"
app:iconSpaceReserved="false" />
<EditTextPreference
android:key="httpProxyPort"
android:summary="%s"
android:title="@string/pref_title_http_proxy_port"
app:iconSpaceReserved="false" />
</androidx.preference.PreferenceScreen>

View File

@ -1,84 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:key="notificationSettings"
android:title="@string/pref_title_edit_notification_settings">
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="notificationsEnabled"
android:title="@string/pref_title_notifications_enabled"
app:iconSpaceReserved="false" />
<PreferenceCategory
android:dependency="notificationsEnabled"
android:title="@string/pref_title_notification_filters"
app:iconSpaceReserved="false">
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="notificationFilterMentions"
android:title="@string/pref_title_notification_filter_mentions"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="notificationFilterFollows"
android:title="@string/pref_title_notification_filter_follows"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="notificationFilterFollowRequests"
android:title="@string/pref_title_notification_filter_follow_requests"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="notificationFilterReblogs"
android:title="@string/pref_title_notification_filter_reblogs"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="notificationFilterFavourites"
android:title="@string/pref_title_notification_filter_favourites"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="notificationFilterEmojis"
android:title="@string/pref_title_notification_filter_emoji"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="notificationFilterPolls"
android:title="@string/pref_title_notification_filter_poll"
app:iconSpaceReserved="false" />
</PreferenceCategory>
<PreferenceCategory
android:dependency="notificationsEnabled"
android:title="@string/pref_title_notification_alerts"
app:iconSpaceReserved="false">
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="notificationAlertSound"
android:title="@string/pref_title_notification_alert_sound"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="notificationAlertVibrate"
android:title="@string/pref_title_notification_alert_vibrate"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="notificationAlertLight"
android:title="@string/pref_title_notification_alert_light"
app:iconSpaceReserved="false" />
</PreferenceCategory>
</androidx.preference.PreferenceScreen>

View File

@ -1,136 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:key="@string/preferences_file_key">
<PreferenceCategory android:title="@string/pref_title_appearance_settings">
<ListPreference
android:defaultValue="night"
android:entries="@array/app_theme_names"
android:entryValues="@array/app_theme_values"
android:key="appTheme"
android:summary="%s"
android:title="@string/pref_title_app_theme" />
<com.keylesspalace.tusky.EmojiPreference
android:defaultValue="@string/system_default"
android:icon="@drawable/ic_emoji_24dp"
android:key="emojiCompat"
android:summary="@string/system_default"
android:title="@string/emoji_style" />
<ListPreference
android:defaultValue="default"
android:entries="@array/language_entries"
android:entryValues="@array/language_values"
android:key="language"
android:summary="%s"
android:title="@string/pref_title_language" />
<ListPreference
android:defaultValue="medium"
android:entries="@array/status_text_size_names"
android:entryValues="@array/status_text_size_values"
android:key="statusTextSize"
android:summary="%s"
android:title="@string/pref_status_text_size" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="fabHide"
android:title="@string/pref_title_hide_follow_button"
app:singleLineTitle="false" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="absoluteTimeView"
android:title="@string/pref_title_absolute_time"
app:singleLineTitle="false" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="showBotOverlay"
android:title="@string/pref_title_bot_overlay"
app:singleLineTitle="false" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="animateGifAvatars"
android:title="@string/pref_title_animate_gif_avatars"
app:singleLineTitle="false" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="useBlurhash"
android:title="@string/gradient_for_media"
app:singleLineTitle="false" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="showNotificationsFilter"
android:title="@string/pref_title_show_notifications_filter"
app:singleLineTitle="false" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="showCardsInTimelines"
android:title="@string/pref_title_show_cards_in_timelines"
app:singleLineTitle="false" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="confirmReblogs"
android:title="@string/pref_title_confirm_reblogs"
app:singleLineTitle="false" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="hideMutedUsers"
android:title="@string/pref_title_hide_muted_users"
app:singleLineTitle="false" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="enableSwipeForTabs"
android:title="@string/pref_title_enable_swipe_for_tabs"
app:singleLineTitle="false" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="bigEmojis"
android:title="@string/pref_title_enable_big_emojis"
app:singleLineTitle="false" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="stickers"
android:title="@string/pref_title_enable_experimental_stickers"
app:singleLineTitle="false" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_title_browser_settings">
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="customTabs"
android:title="@string/pref_title_custom_tabs"
app:singleLineTitle="false" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_title_status_filter">
<Preference
android:key="timelineFilterPreferences"
android:title="@string/pref_title_status_tabs" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_title_proxy_settings">
<Preference
android:key="httpProxyPreferences"
android:summary="%s"
android:title="@string/pref_title_http_proxy_settings" />
</PreferenceCategory>
</androidx.preference.PreferenceScreen>

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/pref_title_status_tabs">
<PreferenceCategory
android:title="@string/title_home"
app:iconSpaceReserved="false">
<CheckBoxPreference
android:defaultValue="true"
android:key="tabFilterHomeBoosts"
android:title="@string/pref_title_show_boosts"
app:iconSpaceReserved="false" />
<CheckBoxPreference
android:defaultValue="true"
android:key="tabFilterHomeReplies"
android:title="@string/pref_title_show_replies"
app:iconSpaceReserved="false" />
</PreferenceCategory>
</androidx.preference.PreferenceScreen>

View File

@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.3'
classpath 'com.android.tools.build:gradle:4.0.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

Binary file not shown.

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

2
gradlew vendored
View File

@ -82,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@ -129,6 +130,7 @@ fi
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath

1
gradlew.bat vendored
View File

@ -81,6 +81,7 @@ set CMD_LINE_ARGS=%*
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%