From 4029dd0d514cd8893cc799bb597d61e2dd7e4755 Mon Sep 17 00:00:00 2001 From: qezwan Date: Wed, 13 Jan 2021 08:10:26 +0000 Subject: [PATCH 01/75] Translated using Weblate (Sorani) Currently translated at 36.3% (4 of 11 strings) Translation: Tusky/Tusky description Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/ckb/ --- fastlane/metadata/android/ckb/changelogs/77.txt | 10 ++++++++++ fastlane/metadata/android/ckb/full_description.txt | 12 ++++++++++++ fastlane/metadata/android/ckb/short_description.txt | 1 + fastlane/metadata/android/ckb/title.txt | 1 + 4 files changed, 24 insertions(+) create mode 100644 fastlane/metadata/android/ckb/changelogs/77.txt create mode 100644 fastlane/metadata/android/ckb/full_description.txt create mode 100644 fastlane/metadata/android/ckb/short_description.txt create mode 100644 fastlane/metadata/android/ckb/title.txt diff --git a/fastlane/metadata/android/ckb/changelogs/77.txt b/fastlane/metadata/android/ckb/changelogs/77.txt new file mode 100644 index 000000000..ee35b258f --- /dev/null +++ b/fastlane/metadata/android/ckb/changelogs/77.txt @@ -0,0 +1,10 @@ +تاسکی وشانی ١٣.٠ + +- پشتگیری بۆ تێبینیەکانی پرۆفایل (تایبەتمەندی ماستۆدۆن ٣.٢.٠) +- پشتگیری لە راگەیاندنی بەڕێوەبەر (تایبەتمەندی ماستۆدۆن ٣.١.٠) + +- ئێستا ئەژمێری هەڵبژێردراوی هەژمارەکەت لە شریتی ئامڕازی سەرەکی دا پیشان دەدرێت +- کرتە کردن لەسەر ناوی پیشاندان لە هێڵی کات ئێستا لاپەڕەی پرۆفایلی ئەو بەکارهێنەرە هەڵدەدات + +- زۆر چاککردنەوەی هەڵەکان و چاککردنەوەی بچووک +- وەرگێڕانە باشەکان diff --git a/fastlane/metadata/android/ckb/full_description.txt b/fastlane/metadata/android/ckb/full_description.txt new file mode 100644 index 000000000..ab90d927a --- /dev/null +++ b/fastlane/metadata/android/ckb/full_description.txt @@ -0,0 +1,12 @@ +توسکی ئەپێکی سووکەڵە بۆ ماستۆدۆنە، خزمەتکاری تۆڕی کۆمەڵایەتی ئازاد و کراوە + +• دیزاینی ماتریالی +• زۆربەی ماستۆدۆن API جێبەجێ دەکا +• پشتیوانی هەژمارەی هەمەجۆر +• ڕووکاری تاریک و رووناک لەگەڵ ئەگەری گۆڕینی خۆکار لەسەر بنەمای کاتی رۆژ +• ڕەشنووسەکان - دروستکردنی دووتەکان و هەڵگرتنیان بۆ دواتر +• هەڵبژێرە لەنێوان شێوازە ئیمۆجییە جیاوازەکان +• باشترکراوە بۆ هەموو قەبارەی شاشە +• بەتەواوی کراوەی سەرچاوە - هیچ پشت پێبەستنێکی نائازاد وەک خزمەتگوزاریەکانی گووگڵ + +بۆ زیاتر فێربوون لەبارەی مەستوورن ، سەردانی https://joinmastodon.org/ diff --git a/fastlane/metadata/android/ckb/short_description.txt b/fastlane/metadata/android/ckb/short_description.txt new file mode 100644 index 000000000..df2f8d34f --- /dev/null +++ b/fastlane/metadata/android/ckb/short_description.txt @@ -0,0 +1 @@ +کڕیارێکی هەژماری هەمەجۆر بۆ تۆڕی کۆمەڵایەتی ماستۆدۆن diff --git a/fastlane/metadata/android/ckb/title.txt b/fastlane/metadata/android/ckb/title.txt new file mode 100644 index 000000000..57a4e8902 --- /dev/null +++ b/fastlane/metadata/android/ckb/title.txt @@ -0,0 +1 @@ +تاسکی From 270e49a708fe1b588e28bee4abf538efb4d53a01 Mon Sep 17 00:00:00 2001 From: Grandasse Date: Wed, 13 Jan 2021 08:10:27 +0000 Subject: [PATCH 02/75] Translated using Weblate (French) Currently translated at 100.0% (11 of 11 strings) Translation: Tusky/Tusky description Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/fr/ --- fastlane/metadata/android/fr/changelogs/58.txt | 5 ++++- fastlane/metadata/android/fr/changelogs/70.txt | 2 +- fastlane/metadata/android/fr/changelogs/72.txt | 6 +++--- fastlane/metadata/android/fr/changelogs/74.txt | 8 ++++++++ fastlane/metadata/android/fr/changelogs/77.txt | 10 ++++++++++ 5 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 fastlane/metadata/android/fr/changelogs/74.txt create mode 100644 fastlane/metadata/android/fr/changelogs/77.txt diff --git a/fastlane/metadata/android/fr/changelogs/58.txt b/fastlane/metadata/android/fr/changelogs/58.txt index fb6993c13..22c4090eb 100644 --- a/fastlane/metadata/android/fr/changelogs/58.txt +++ b/fastlane/metadata/android/fr/changelogs/58.txt @@ -1,3 +1,6 @@ Tusky v6.0 -- +- Les filtres de la timeline ont été déplacés dans les Préférences de compte et se synchronisent avec le serveur +- Vous pouvez personnaliser un onglet avec un hashtag dans la fenêtre principale +- Les listes peuvent être éditées +- Sécurité : suppression du support pour TLS 1.0 et TLS 1.1, et ajout du support pour TLS 1.3 sur Android 6+ diff --git a/fastlane/metadata/android/fr/changelogs/70.txt b/fastlane/metadata/android/fr/changelogs/70.txt index 832aec90b..5fb322c62 100644 --- a/fastlane/metadata/android/fr/changelogs/70.txt +++ b/fastlane/metadata/android/fr/changelogs/70.txt @@ -1,7 +1,7 @@ Tusky v10.0 - Vous pouvez maintenant marquer les statuts et lister vos signets dans Tusky. -- Vous pouvez maintenant programmer des toots avec Tusky. Notez que l'heure que vous sélectionnez doit être d'au moins 5 minutes dans le futur. +- Vous pouvez maintenant programmer des pouets avec Tusky. Notez que l'heure que vous sélectionnez doit être d'au moins 5 minutes dans le futur. - Vous pouvez maintenant ajouter des listes à l'écran principal. - Vous pouvez désormais publier des pièces jointes audio avec Tusky. diff --git a/fastlane/metadata/android/fr/changelogs/72.txt b/fastlane/metadata/android/fr/changelogs/72.txt index ad75c990a..fb48ccbca 100644 --- a/fastlane/metadata/android/fr/changelogs/72.txt +++ b/fastlane/metadata/android/fr/changelogs/72.txt @@ -2,8 +2,8 @@ Tusky v11.0 - Notifications à propos des nouvelles demandes d’abonnement lorsque votre compte est verrouillé - Nouvelles fonctionalités activables via l’écran Préférences : -- désactiver le pivotement entre onglets -- afficher une boîte de dialogue de confirmation avant de booster un pouet -- afficher les aperçus des liens dans les fils + - désactiver le pivotement entre onglets + - afficher une boîte de dialogue de confirmation avant de booster un pouet + - afficher les aperçus des liens dans les fils - Possibilité de mettre en sourdine les conversations - Les résultats des sondages seront désormais calculés en fonction du nombre des sond·é·s diff --git a/fastlane/metadata/android/fr/changelogs/74.txt b/fastlane/metadata/android/fr/changelogs/74.txt new file mode 100644 index 000000000..f61f152a7 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/74.txt @@ -0,0 +1,8 @@ +Tusky v12.0 + +- Amélioration de l'interface principale - vous pouvez maintenant déplacer les onglets vers le bas +- Lorsque vous mettez un utilisateur en sourdine, vous pouvez désormais décider de désactiver ses notifications +- Vous pouvez maintenant suivre autant de hashtags que vous le souhaitez dans un seul onglet hashtag +- La description des médias s'affiche correctement quelque soit sa taille + +Historique complet : https://github.com/tuskyapp/Tusky/releases diff --git a/fastlane/metadata/android/fr/changelogs/77.txt b/fastlane/metadata/android/fr/changelogs/77.txt new file mode 100644 index 000000000..1558ac223 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/77.txt @@ -0,0 +1,10 @@ +Tusky v13.0 + +- prise en charge des notes de profil (fonctionnalité de Mastodon 3.2.0) +- le support des annonces de l'administration (fonctionnalité de Mastodon 3.1.0) + +- l'avatar de votre compte sélectionné apparaîtra désormais dans la barre d'outils principale +- en cliquant sur le nom affiché dans une timeline, la page de profil de cet utilisateur s'ouvrira + +- plein corrections de bugs et de petites améliorations +- l'amélioration des traductions From c119c0ce6d82fb355957c0f7762c9e39a9524fb3 Mon Sep 17 00:00:00 2001 From: Prashant Date: Thu, 14 Jan 2021 04:10:28 +0000 Subject: [PATCH 03/75] Translated using Weblate (Hindi) Currently translated at 85.8% (381 of 444 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/hi/ --- app/src/main/res/values-hi/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 6c652ead9..466019f61 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -403,4 +403,6 @@ %d सेकेंड शेष %d सेकेंड शेष + पोस्ट बहुत लंबा है! + उस सर्वर से प्रमाणित करने में विफल। \ No newline at end of file From b4d0158cb24abc0b3246c3e8cb97184a4916de67 Mon Sep 17 00:00:00 2001 From: x Date: Thu, 14 Jan 2021 04:10:28 +0000 Subject: [PATCH 04/75] Translated using Weblate (Italian) Currently translated at 99.7% (444 of 445 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/it/ Translated using Weblate (Italian) Currently translated at 99.7% (443 of 444 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/it/ --- app/src/main/res/values-it/strings.xml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index a704c110f..01dbcb690 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -468,7 +468,7 @@ La tua nota privata su questo account Nascondi il titolo della barra degli strumenti in alto Mostra la finestra di dialogo di conferma prima del boosting - Mostra le anteprime dei collegamenti nelle sequenze temporali + Mostra le anteprime dei collegamenti nelle timelines Mastodon ha un intervallo minimo di programmazione di 5 minuti. Non ci sono annunci. Non hai stati pianificati. @@ -483,4 +483,14 @@ Riattiva le notifiche da %s Annunci Richieste di seguirti + Nascondi statistiche quantitative sui profili + Nascondi le statistiche quantitative sui post + Limita le notifiche della timeline + Revisiona le notifiche + Benessere + Notifiche di quando qualcuno a cui sei iscritto ha pubblicato un nuovo toot + Nuovi toots + qualcuno a cui sono iscritto ha pubblicato un nuovo toot + %s appena pubblicato + Non puoi caricare più di %1$d allegati multimediali. \ No newline at end of file From d8a3476942e4bc62f6f24657e33b35c601628e07 Mon Sep 17 00:00:00 2001 From: Ho Nhat Duy Date: Thu, 14 Jan 2021 04:10:28 +0000 Subject: [PATCH 05/75] Translated using Weblate (Vietnamese) Currently translated at 100.0% (445 of 445 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ Translated using Weblate (Vietnamese) Currently translated at 100.0% (444 of 444 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ Translated using Weblate (Vietnamese) Currently translated at 100.0% (444 of 444 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ --- app/src/main/res/values-vi/strings.xml | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index e6a93e82c..295c438b2 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -124,8 +124,8 @@ Mở ngăn kéo Làm mờ hình ảnh Nhắc tới - Bỏ ẩn cuộc trò chuyện - Ẩn cuộc trò chuyện + Mở lại thông báo + Tắt thông báo Ẩn %s Bỏ ẩn Ẩn @@ -171,7 +171,7 @@ Thu gọn Xem thêm Thu gọn - Mở rộng + Xem thêm Hiển thị Nội dung bị ẩn Nhạy cảm @@ -459,4 +459,21 @@ Ghi chú của bạn Chưa có thông báo. Tin tức + Ẩn số liệu trên trang cá nhân + Ẩn tương tác trên tút + Hạn chế thông báo trên bảng tin + Chọn loại thông báo + Các thông tin ảnh hưởng tới tâm lý hành vi của bạn sẽ bị ẩn. Bao gồm: +\n +\n - Thông báo Lượt thích/Chia sẻ/Theo dõi +\n - Số Lượt thích/Chia sẻ của tút +\n - Số Người theo dõi/Tút trên trang cá nhân +\n +\nThông báo đẩy sẽ không ảnh hưởng, bạn có thể tự thiết lập trong phần cài đặt điện thoại của bạn. + Cai nghiện + Thông báo khi người bạn đăng ký theo dõi đăng tút mới + Tút mới + người tôi đăng ký theo dõi đăng tút mới + %s vừa đăng tút + Bạn không thể đính kèm quá %1$d tệp. \ No newline at end of file From a4f9acc70e432e24123aca64efcb5c8bee275707 Mon Sep 17 00:00:00 2001 From: Connyduck Date: Thu, 14 Jan 2021 04:10:29 +0000 Subject: [PATCH 06/75] Added translation using Weblate (Telugu) Added translation using Weblate (Punjabi) --- app/src/main/res/values-pa/strings.xml | 2 ++ app/src/main/res/values-te/strings.xml | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 app/src/main/res/values-pa/strings.xml create mode 100644 app/src/main/res/values-te/strings.xml diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml new file mode 100644 index 000000000..a6b3daec9 --- /dev/null +++ b/app/src/main/res/values-pa/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml new file mode 100644 index 000000000..a6b3daec9 --- /dev/null +++ b/app/src/main/res/values-te/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 282ed03a1a057e3cd2a48ea67e70af3e7b82c7a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isak=20Holmstr=C3=B6m?= Date: Thu, 14 Jan 2021 04:10:29 +0000 Subject: [PATCH 07/75] Translated using Weblate (Swedish) Currently translated at 100.0% (444 of 444 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/sv/ Translated using Weblate (Swedish) Currently translated at 98.6% (438 of 444 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/sv/ --- app/src/main/res/values-sv/strings.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 8e3d5fb7d..6b5296a30 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -484,4 +484,20 @@ Din privata notering om detta kontot Det finns inga meddelanden. Meddelanden + Aviseringar när någon du följer skrivit en ny toot + Nya toots + någon som jag följer har skrivit en ny toot + %s skrev precis + Dölj kvantitativ information på profiler + Dölj kvantitativ information på inlägg + Begränsa tidslinje aviseringar + Ändra aviseringar + Information som kan påverka ditt välmående kommer att döljas. Detta inkluderar: +\n +\n- Favorisering/Knuff/Följaraviseringar +\n- Favorisering/Antal knuffar +\n- Följare/Inlägg på profiler +\n +\nPush-aviseringar påverkas inte, men du ändra dina aviseringinställningar manuellt. + Välmående \ No newline at end of file From 54236fa9ba5eb213fc3215c25f1ce1ae666ad289 Mon Sep 17 00:00:00 2001 From: Vegard Skjefstad Date: Thu, 14 Jan 2021 04:10:29 +0000 Subject: [PATCH 08/75] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (445 of 445 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/nb_NO/ Translated using Weblate (Norwegian Bokmål) Currently translated at 100.0% (444 of 444 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/nb_NO/ --- app/src/main/res/values-no-rNB/strings.xml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/src/main/res/values-no-rNB/strings.xml b/app/src/main/res/values-no-rNB/strings.xml index d259e5e03..9201edb79 100644 --- a/app/src/main/res/values-no-rNB/strings.xml +++ b/app/src/main/res/values-no-rNB/strings.xml @@ -467,4 +467,21 @@ Ditt private notat om denne kontoen Det er ingen kunngjøringer. Kunngjøringer + Skjul kvantitativ informasjon på profiler + Skjul kvantitativ informasjon på toots + Begrens tidslinjevarsler + Se over varsler + Informasjon som kan påvirke ditt mentale velvære vil bli skjult. Dette inkluderer: +\n +\n - Varsler om favorisering, boosts og følgere +\n - Antall favoriseringer og boots på toots +\n - Antall følgere og toots på profiler +\n +\n Push-varsler vil ikke påvirkes, men du kan se over dine varselinnstillinger manuelt. + Velvære + Varsler når noen jeg følger publiserer en ny toot + Nye toots + noen jeg følger publiserer en ny toot + %s tootet akkurat + Du kan ikke laste opp flere enn %1$d mediavedlegg. \ No newline at end of file From 4fe17b0485e73bb33e74a7be176549ac62756dde Mon Sep 17 00:00:00 2001 From: nailyk Date: Thu, 14 Jan 2021 04:10:29 +0000 Subject: [PATCH 09/75] Added translation using Weblate (Sorani) --- app/src/main/res/values-ckb/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/src/main/res/values-ckb/strings.xml diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml new file mode 100644 index 000000000..a6b3daec9 --- /dev/null +++ b/app/src/main/res/values-ckb/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 871fddc79cf762b86226f484500c3a9947664904 Mon Sep 17 00:00:00 2001 From: qezwan Date: Thu, 14 Jan 2021 04:10:29 +0000 Subject: [PATCH 10/75] Translated using Weblate (Sorani) Currently translated at 100.0% (445 of 445 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ckb/ Translated using Weblate (Sorani) Currently translated at 50.3% (224 of 445 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ckb/ Translated using Weblate (Sorani) Currently translated at 34.8% (155 of 445 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ckb/ --- app/src/main/res/values-ckb/strings.xml | 487 +++++++++++++++++++++++- 1 file changed, 486 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml index a6b3daec9..d07ed2275 100644 --- a/app/src/main/res/values-ckb/strings.xml +++ b/app/src/main/res/values-ckb/strings.xml @@ -1,2 +1,487 @@ - \ No newline at end of file + + چی خەریکه ڕوودەدات؟ + کام نموونە؟ + وەڵام دانەوە کە بە سەرکەوتوویی نێردرا. + ناردن! + %s نەشاراوە + بەکارهێنەر نەگۆڕاو + بەکارهێنەر بەربەست نەکراوە + ناردن! + هاوبەشکردنی میدیا بۆ… + هاوبەشی کردن بە توت بۆ… + هاوبەشکردنی توتی URL بۆ… + داگرتنی میدیا + داگرتنی میدیا + هاوبەش کردن وەک … + کردنەوە وەک %s + بەستەرەکە ڕوونوس بکە + داگرتنی %1$s + کردنەوەی میدیا #%d + بەستەرەکان + ئاماژەکان + هاشتاگی + پیشاندانی دڵخوازەکان + پیشاندانی بەهێزکردنەکان + کردنەوەی بەهێزکردنی نووسەر + هاشتاگ + ئاماژەکان + بەستەرەکان + زیادکردنی سەرخشت + ڕیسێت کردن + خشتەی توت + تەختەکلیلی ئیمۆجی + ئاگاداری ناوەڕۆک + بینینی توت + توتی خشتەکراو + ڕەشنووسەکان + گەڕان + ڕەتکردنەوە + ڕازیبون + گەڕانەوە + بژارکردن + دەستکاری پرۆفایل بکە + بپارێزە + کردنەوەی وێنەکێش + شاردنەوەی میدیا + ئاماژە + گفتوگۆی لاببە + نابێدەنگ کردن %s + بێدەنگکردن %s + بێدەنگکردنی ئاگانامەکان لە %s + ئاگانامەکانی لاببە لە %s + نابێدەنگ %s + بێدەنگی لابردن + بێدەنگ + هاوبەش کردن + وێنە بگرە + زیادکردنی ڕاپرسی + زیادکردنی میدیا + کردنەوە لە وێبگەڕ + میدیا + بەدواداچونی داواکاریەکان بکە + دۆمەینە شاراوەکان + بەکارهێنەرە بلۆککراوەکان + بەکارهێنەرە گۆڕاوەکان + نیشانەکان + دڵخوازەکان + پەسەندکراوەکانی ئەژمێر + پەسەندەکان + پرۆفایل + دابخە + دووبارە هەوڵ بدە + توت! + توت + سڕینەوە و دووبارە-ڕەشنووس + سڕینەوە + دەستکاری + گوزارشەکان + پیشاندانی بەهێزکردنەکان + شاردنەوەی بەهێزکردنەکان + بەربەست کردن لاببە + بلۆک + بەدوادانەچو + بەدواداکەوتن + ئایا دڵنیایت لەوەی دەتەوێت بچیتەدەرەوە لە هەژماری %1$s؟ + چوونەدەرەوە + چوونەژوورەوە لەگەڵ ماستۆدۆن + دروستکردن + زیاتر + لابردنی دڵخوازەکان + نیشانه + دڵخواز + لابردنی بەهێزکردن + بەهێزکردن + وەڵام + وەڵامدانەوەی خێرا + سەرنجەکانی تر؟ + گوزارشت @%s + %s تەنها بڵاوکرایەوە + %s داواکراوە کە شوێنت بکەوێت + %s بەدواتا کەوت + %s خۆشترین توتەکەت + %s توتەکەتی بەرزکردەوە + هیچ شتێک لێرە نیە ڕاکە خوارەوە بۆ نوێکردنەوە! + هیچ شتێک لێرە نیە. + نوشتانەوە + فراوانکردن + کەمتر نیشان بدە + زیاتر پیشان بدە + کرتە بکە بۆ بینین + میدیا شاراوە + ناوەڕۆکی هەستیار + %s بەرزکرا + \@%s + مۆڵەتەکان + ڕاگه یه نراوەکان + توتی خشتەکراو + ڕەشنووسەکان + دەستکاری پرۆفایلەکەت بکە + بەدواداچونی داواکاریەکان بکە + دۆمەینە شاراوەکان + بەکارهێنەرە بلۆککراوەکان + بەکارهێنەرە بێدەنگ + نیشانەکان + دڵخوازەکان + شوێنکەوتوان + بەدوادا + چەسپا + لەگەڵ وەڵامەکان + بابەتەکان + توت + سەرخشتەکان + نامە ڕاستەوخۆکان + گشتی + ناوخۆیی + ئاگادارییەکان + سەرەتا + هەڵە لە ناردنی توت. + بارکردن سەرکەوتوو نەبوو. + وێنە و ڤیدیۆکان ناتوانرێت هەردووک هاوپێچ بکرێت لەگەڵ یەک دۆخ. + مۆڵەت بۆ پاشکەوتکردنی میدیا پێویستە. + مۆڵەت بۆ خوێندنەوەی میدیا پێویستە. + ئەم فایلە ناتوانرێت بکرێتەوە. + ناتوانرێت ئەو جۆرە فایلە باربکرێت. + فایلەکانی دەنگ دەبێت کەمتر بێت لە ٤٠MB. + پێویستە فایلەکانی ڤیدیۆ کەمتر لە 40 مێگابایت بن. + فایلەکە دەبێت کەمتر بێت لە 8 مێگابایت. + ڕەستە زۆر درێژە! + سەرکەوتوو نەبوو لە بەدەستهێنانی نیشانەی چوونەژوورەوە. + ڕێپێدان ڕەتکرایەوە. + هەڵەیەک بۆ مۆڵەتدانی نەناسراو ڕووی دا. + نەیتوانی وێبگەڕبدۆزێتەوە بۆ بەکارهێنان. + سەرکەوتوو نەبوو، ڕاستکردنەوە لەگەڵ ئەم نمونەیە. + دۆمەینی نادروست تێنووسکرا + ئەمە ناتوانێت بەتاڵ بێت. + هەڵەیەک لە تۆڕ ڕوویدا! تکایە پەیوەندیت بپشکنە و دوبارە هەوڵ بدە! + هەڵەیەک ڕوویدا. + تایبەتمەندی بابەت گریمانەیی + دەرگای پرۆکسی HTTP + ڕاژەکاری پرۆکسی HTTP + چالاککردنی پرۆکسی HTTP + HTTP proxy + پرۆکسی + داگرتنی پێشبینینی میدیا + وەڵامدانەوەکان پیشان بدە + پیشاندانی بەهێزکردنەکان + سەرخشتەکان + فلتەرکردنی تایملاین + نمرەی لاری ڕەنگاوڕەنگ نیشان بدە بۆ میدیای شاراوە + وێنۆجکەی ئەنیمەی GIF + نیشاندەر نیشاندەر بۆ بۆتەکان نیشان بدە + زمان + دوگمەی ئاوازدانان بشارەوە لەکاتی خشاندن + بەکارهێنانی خشتەبەندەکانی دڵخواز + وێبگەڕ + دیزاینی سیستەم بەکاربهێنە + خۆکار لە کاتی خۆرئاوابووندا + ڕەش + ڕووناکی + تاریک + فلتەرەکان + ڕووکاری ئەپ + تایملاین + دەرکەوتن + کەسێک کە من بەشدارم لە بڵاو کردنەوەی توتێکی نوێیرکری + ڕاپرسی کۆتایی هاتووە + بابەتەکانی من پەسەندن + پۆستەکانم بەرزدەکرانەوه + بەدواداچوونەوەی داواکراو + بەدوادا + ناوبراو + ئاگادارم بکەوە کاتێک + ئاگاداربکەوە بە ڕووناکی + ئاگادارکردنەوەی لەلەرینە + ئاگادارکردنەوەی بە دەنگێک + ئاگادارکردنەوەکان + ئاگانامەکان + ئاگانامەکان + ڕاستەوخۆ: تەنها بۆ بەکارهێنەرانی ناوبراو پۆست بکە + تەنها شوێنکەوتوانی: تەنها پۆست بۆ شوێنکەوتوانی + لیستی نەکراو: لە هێڵی کاتی گشتی دا پیشان مەدە + گشتی: پۆست بکە بۆ هێڵی کاتی گشتی + شاردنەوەی ئاگانامەکان + بێدەنگکردن @%s؟ + بلۆککردنی @%s؟ + شاردنەوەی هەموو دۆمەینەکە + ئایا دڵنیایت لەوەی دەتەوێت هەموو %s بلۆک بکەیت؟ تۆ ناوەڕۆکێک نابینیت لە دۆمەینەکە لە هیچ هێڵی کاتی گشتی یان لە ئاگانامەکانت. شوێنکەوتوانی تۆ لەو دۆمەینەوە لادەبرێن. + ئەم دووانە بسڕەوە و دووبارە ڕەشنووس یان دەکەیتەوە؟ + ئەم توتە بسڕەوە؟ + شوێن نەکەوتنی ئەم هەژمارە؟ + داواکاری بەدوادا چوەکان هەڵوەشانەوە؟ + داگرتن + بارکردن… + تەواوکردنی بارکردنی میدیا + ناونیشان یان دۆمەینی هەر نمونەیەک دەکرێت لێرە تێبنووسرێت، وەک فرەتر! +\n +\nئەگەر هێشتا ئەژمێرێکت نیە، دەتوانیت ناوی ئەو نمونەیە داخڵ بکەیت کە دەتەوێت بیبەستیت و ئەژمێرێک دروست بکەیت لەوێ. +\n +\nنموونەیەک تاکە شوێنە کە ئەژمێرەکەت میوانداری کراوە، بەڵام دەتوانیت بە ئاسانی پەیوەندی لەگەڵ بکەیت و دوای ئەو خەڵکانە بکەویت لە نمونەکانی تر وەک ئەوەی تۆ لە هەمان سایت دابیت. +\n +\nزانیاری زیاتر دەتوانرێت بدۆزرێتەوە لە joinmastodon.org. + گرێدان… + نموونەیەک چییە؟ + سەرپەڕە + وێنۆچکە + وەڵام… + هیچ ئەنجامێک نیە + گەڕان… + دەربارە + ناوی پیشاندان + ئاگاداری ناوەڕۆک + گفتوگۆی بێدەنگ + + %d کاژێرماوە + %d کاژێرماوە + + کاتێک وشەکە یان دەستەواژەکە تەنها ئەبجەدییە، تەنها ئەگەر لەگەڵ هەموو وشەکە یەکبێت کاری پێدەکرێت + ناتوانیت زیاتر لە %1$d هاوپێچی میدیا باربکەیت. + شاردنەوەی زانیاری چەندێتی لەسەر پرۆفایلەکان + شاردنەوەی زانیاری چەندێتی لە بابەتەکان + سنووردارکردنی ئاگانامەکانی تایم لاین + پێداچوونەوەی ئاگانامەکان + هەندێک زانیاری کە لەوانەیە کاریگەری لەسەر باشبوونی دەروونیت دروست بکات دەشاردرێنەوە. ئەمە پێکدێت لە: +\n +\n- ئاگانامەکانی پەسەند/بەهێزکردن/بەدوادا +\n - پەسەندترین/بەرزکردنەوە لەسەر توت +\n - بەدواداچوون/زانیاری بابەت لەسەر پرۆفایلەکان +\n +\nکارتێکردنی ئاگانامەکانی پاڵپێوەنان، بەڵام دەتوانیت بە پەسەندکردنە ئاگانامەکانت دا بخشێنیەوە بە دەستی. + ڕزگارکرا + تێبینی تایبەتی تۆ دەربارەی ئەم ئەژمێرە + Wellbeing + شاردنەوەی ناونیشانی شریتی ئامڕازی سەرەوە + پیشاندانی دیالۆگی دووپاتکردنەوە پێش بەهێزکردن + نیشاندانی پێشاندانی بەستەر لە هێڵی کات + ماستۆدۆن کەمترین ماوەی خشتەی هەیە لە ٥ خولەک. + هیچ ڕاگه یه نراوێک له بەرده رنه کەون. + هیچ بارێکی خشتەکراوت نیە. + هیچ ڕەشنووسێکت نییە. + هەڵە لە گەڕان بەدوای بابەت %s + دەستکاریکردن + هەڵبژاردنی %d + چەند هەڵبژاردنێک + زیادکردنی هەڵبژاردن + ٧ ڕۆژ + ٣ ڕۆژ + ١ ڕۆژ + ٦ کاتژمێر + ١ کاتژمێر + ٣٠ خولەک + ٥ خولەک + ڕاپرسی + چالاککردنی ئاماژەکردنی لێدانی چالاک بۆ گۆڕین لە نێوان خشتەبەندەکان + تاسکی کۆد و سەرمایەکانی تێدایە لەم پڕۆژە کراوەی سەرچاوە: + فلتەری ئاگانامەکان نیشان بدە + گەڕانەکە سەرکەوتوو نەبوو + ئەژمێرەکان + هەژمارەلە ڕاژەیەکی دیکەیە ترە. کۆپیەکی بێ سەروبەر بنێرە بۆ ڕاپۆرتەکە لەوێ؟ + ڕاپۆرتەکە دەنێردرێت بۆ بەڕێوەبەری ڕاژەکەت. دەتوانیت ڕوونکردنەوەیەک پێشکەش بکەیت کە بۆچی ئەم ئەژمێرە لە خوارەوە ڕاپۆرت دەکەیت: + سەرکەوتوو نەبوو لە هێنانی بارەکان + ڕاپۆرتکردن سەرکەوتوو نەبوو + ناردنەوە بۆ %s + سەرنجەکانی زیاتر + سەرکەوتووانە ڕاپۆرتکرا @%s + تەواوبوو + دواوە + بەردەوام بە + + %d چرکەی ماوەو + %d دووەم چەپ + + + %d خولەک ماوە + %d خولەک ماوە + + + %d ڕۆژ ماوە + %d ڕۆژ ماوە + + ڕاپرسییەک کە دروستت کردووە کۆتایی هات + ڕاپرسییەک کە دەنگی پێداویت کۆتایی هات + دەنگ + داخراوە + کۆتایی دێت لە %s + + %s کەس + %s کەس + + + %s دەنگ + %s دەنگ + + %1$s • %2$s + کارەکان بۆ وێنە %s + ئایا دڵنیایت لەوەی دەتەوێت بە هەمیشەیی هەموو ئاگانامەکانت بسڕیتەوە؟ + دروستکردن + دروستکردنی توت + جێبەجێ کردن + فلتەر + سڕینەوە + لیست + دیاریکردنی لیست + هاشتاگی + هاشتاگی بێ # + هاشتاگی زیاد بکە + ناوی لیست + ڕاپرسی لەگەڵ هەڵبژاردنەکان: %1$s, %2$s, %3$s, %4$s; %5$s + ڕاستەوخۆ + شوێنکەوتوانی + لە لیست نەکراو + گشتی + نیشانکراوە + پەسەندکراو + دووبارە بڵاگ کرا + هیچ وەسفێک + ئاگاداری ناوەڕۆک: %s + میدیا: %s + بەرزترین رێژەی خشتەبەندەکانی %1$d گەیشت + %1$s, %2$s و %3$d زیاتر + %1$s و %2$s + %1$s + پەسەندکراوە لەلایەن + بەرزکراوە لەلایەن + + %s بەهێزکردن + %s بەهێزکردن + + + %1$s دڵخواز + %1$s دڵخواز + + Pin + لابردن + ڕەنگە زانیاری خوارەوە ڕەنگدانەوەی پرۆفایلی بەکارهێنەر بە ناتەواوی بێت. فشار بکە بۆ کردنەوەی پرۆفایلی تەواو لە وێبگەڕەکە. + کاتی ڕەها بەکاربهێنە + ناوەڕۆک + ناونیشان + داتا زیاد بکە + مێتاداتای پرۆفایل + CC-BY-SA 4.0 + CC-BY 4.0 + مۆڵەتدراوە لەژێر مۆڵەتی ئەپاچی (لەبەرگیراوە لە خوارەوە) + بێ هێزکردن + بەرزکردنەوە بۆ جەماوەری ڕەسەن + %1$s گواسترایەوە بۆ: + بۆت + داگرتن سەرکەوتوو نەبوو + کۆمەڵە ئیمۆجیەکەی ئێستای گووگڵ + سێتی ئیمۆجی پێوانەیی ماتۆدۆن + ئیمۆجی Blob لە ئەندرۆید ەوە ناسراوە 4.4–7.1 + سێتی ئیمۆجی بنەڕەتی ئامێرەکەت + دەستپێکردنەوە + دواتر + تۆ پێویستە توسکی دەستپێبکەیتەوە بۆ ئەوەی ئەم گۆڕانکاریانە جێبەجێ بکەیت + دەسپێکردنەوەی کاربەرنامە پێویستە + کردنەوە توت + فراوانکردن/نوشتانەوەی هەموو بارەکان + ئەنجامدانی گەڕان… + تۆ پێویستە سەرەتا ئەم سێتە ئیمۆجییانە دابگریت + سیستەمی بنەڕەت + شێوازی ئیمۆجی + ڕوونووسکراوە بۆ کلیپ بۆرد + نموونەکەت %s هیچ ئیمۆجییەکی ئاسایی نییە + دروستکردن + کۆپیەکی دەستنووسەکە خەزن کراوە بۆ ڕەشنووسەکانت + ناردنی هەڵوەشاوە + ناردنی توتس + هەڵە لە ناردنی توت + (توت) دەنێرم… + ڕەشنووس پاشەکەوت بکەیت؟ + داوات لێدەکات کە بە دەستی شوێنکەوتوانی پەسەند بکە + داخستنی ئەژمێر + لابردن + دانانی سەردێڕ + وەسف بکە بۆ بینایی داڕماو +\n(%d سنوری کاراکتەر) + دانانی سەردێڕ شکستی هێنا + بڵاوکردنەوە بە هەژماری %1$s + لابردنی ئەژمێر لە لیستەکە + زیادکردنی ئەژمێر بۆ لیستەکە + گەڕان بەدوای ئەو کەسانەی کە پەیڕەوی ان دەکەیت + دەستکاریکردنی لیستەکە + سڕینەوەی لیستەکە + ناونانەوەی لیستەکە + دروستکردنی لیستێک + نەیتوانی لیستەکە بسڕێتەوە + نەیتوانی ناوی لیست بنووسرێ + نەیتوانی لیست دروست بکات + لیستی تایم لاین + لیستەکان + لیستەکان + زیادکردنی ئەژمێری ماتۆدۆنی نوێ + زیادکردنی ئەژمێر + دەستەواژە بۆ فلتەر + هەموو وشەکە + نوێکردنەوە + لابردن + دەستکاریکردنی فلتەر + زیادکردنی فلتەر + گفتوگۆکان + هێڵی کاتی گشتی + بارکردنی زیاتر + وەڵام دانەوە بۆ @%s + میدیا + هەمیشە ئەو توتانەی کە بە ئاگادارکردنەوەکانی ناوەڕۆکەوە نیشانەکراون فراوان بکە + هەمیشە ناوەڕۆکی هەستیار نیشان بدە + دوای تۆ دەکەوێت + %ds + %dm + %dh + %dd + %dy + لە %ds + لە %dm + لە %dh + لە %dd + لە %dy + بەدواداچوونەوەی داواکراو + ڤیدیۆ + وێنەکان + هاوبەشکردنی لینک بۆ توت + هاوبەشکردنی ناوەڕۆکی دووت + پرۆفایلی تاسکی + ڕاپۆرتەکانی هەڵەکان و داواکاریەکانی تایبەتمەندی: +\nhttps://github.com/tuskyapp/Tusky/issues + وێبسایتی پڕۆژە: +\nhttps://tusky.app + توسکی سۆفتوێری ئازاد و سەرچاوەی کراوەیە مۆڵەتدراوە بە پێ نامەی گشتی GNU Public Version 3. دەتوانیت لێرە مۆڵەتەکە نیشان بدەی: https://www.gnu.org/licenses/gpl-3.0.en.html + لەلایەن تاسکیەوە دەست کراوە بە + توسکی %s + سەبارەت + هەژماری داخراو + %d چالاکی نوێ + %1$s و %2$s + %1$s و %2$s و %3$s + %1$s, %2$s, %3$s و %4$d ئەوانی تر + %s ئاماژەی بە تۆ کرد + ئاگانامەکان کاتێک کەسێک کە تۆ بەشداریت کردووە لە بڵاوکردنەوەی توتێکی نوێ + توتی نوێ + ئاگادارییەکان دەربارەی ڕاپرسییەکان کە کۆتایی هاتووە + ڕاپرسییەکان + ئاگانامەکان کاتێک کەتوتەکان نیشانە کراون وەک دڵخواز + دڵخوازەکان + ئاگانامەکان کاتێک کە دووتەکەت بەرز دەکرێتەوە + بەهێزکردن + ئاگانامەکان دەربارەی داواکاریەکانی بەدوادا + بەدواداچونی داواکاریەکان بکە + ئاگانامەکان دەربارەی شوێنکەوتوانی نوێ + شوێنکەوتوانی نوێ + ئاگانامەکان دەربارەی ئاماژە نوێیەکان + ئاماژە نوێیەکان + گەورەترین + گەورە + مامناوەندی + بچووک + بچووکترین + قەبارەی دەقی بار + شوێنکەوتوانی تەنها + لە لیست نەکراو + گشتی + خوارەوە + سەرەوە + شوێنی سەرەکی ڕێنیشاندەر + سەرکەوتوو نەبوو لە هاودەمکردنی ڕێکبەندەکان + بڵاوکردنەوە (هاوکاتکراوە لەگەڵ سێرڤەر) + هەمیشە میدیا وەک هەستیار نیشان بکە + \ No newline at end of file From 914e9a78f43776a439caf88061982a19137f1798 Mon Sep 17 00:00:00 2001 From: Gisele Galaburri Date: Thu, 14 Jan 2021 04:10:29 +0000 Subject: [PATCH 11/75] Translated using Weblate (Spanish) Currently translated at 99.7% (444 of 445 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/es/ --- app/src/main/res/values-es/strings.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index e81f543e5..4072ae0c8 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -479,4 +479,20 @@ Tu nota privada acerca de esta cuenta No hay anuncios. Anuncios + % recién publicado + No puedes cargar más de %1$d archivos adjuntos multimedia. + Esconder las estadísticas cuantitativas de los perfiles + Esconder las estadísticas cuantitativas de las publicaciones + Revisar Notificaciones + Bienestar + Notificaciones cuando alguien al que estoy suscrito publicó un nuevo toot + Nuevos toots + alguien al que estoy suscrito publicó un nuevo toot + Alguna información que podría afectar tu bienestar mental va a ser oculta. Esto incluye: +\n +\n- Favoritos/impulsar/Notificaciones de Follow +\n- Favoritos/Conteo de Impulsos en toots +\n- Seguidor/Publicar estadísticas en perfiles +\n +\nLas notificaciones Push no serán afectadas, pero puedes revisar manualmente tus referencias de notificaciones. \ No newline at end of file From 4ab33f0ad5e27fbe926df7048386081b02f4d8c6 Mon Sep 17 00:00:00 2001 From: Grandasse Date: Thu, 14 Jan 2021 04:10:29 +0000 Subject: [PATCH 12/75] Translated using Weblate (French) Currently translated at 100.0% (445 of 445 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/fr/ Translated using Weblate (French) Currently translated at 99.3% (442 of 445 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/fr/ --- app/src/main/res/values-fr/strings.xml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 732f65a25..c0ad6bfd2 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -94,7 +94,7 @@ Mentionner Cacher les médias Ouvrir le menu - Sauvegarder + Enregistrer Modifier le profil Modifier Annuler @@ -488,4 +488,21 @@ Votre note privée sur ce compte Il n’y a pas d’annonce. Annonces + Certaines informations susceptibles d\'affecter votre bien-être mental seront cachées. Il s\'agit : +\n +\n - des notifications de favoris, de partage et de suivi +\n - du compte des favoris/partages sur les pouets +\n - des statistiques sur les profils +\n +\n Les notifications \"push\" ne seront pas affectées, mais vous pouvez revoir vos préférences de notification manuellement. + une personne à laquelle je suis abonné a publié un nouveau pouet + %s vient de publier + Examiner les notifications + Nouveau pouets + Vous ne pouvez pas téléverser plus de %1$d pièces jointes. + Bien-être + Notifications quand quelqu\'un que vous suivez publie un nouveau pouet + Limiter les notifications de la timeline + Cacher les statistiques quantitatives sur les profils + Cacher les statistiques quantitatives sur les publications \ No newline at end of file From be60155de5f17fd5078df1cc95bf8fa06e4b9a37 Mon Sep 17 00:00:00 2001 From: Levi Bard Date: Fri, 15 Jan 2021 21:05:36 +0100 Subject: [PATCH 13/75] Implement timed mutes. (#2035) Fixes #2033 --- .../keylesspalace/tusky/AccountActivity.kt | 4 +-- .../components/search/SearchViewModel.kt | 4 +-- .../fragments/SearchStatusesFragment.kt | 4 +-- .../tusky/fragment/SFragment.java | 4 +-- .../tusky/network/MastodonApi.kt | 3 +- .../tusky/network/TimelineCases.kt | 6 ++-- .../tusky/view/MuteAccountDialog.kt | 9 +++-- .../tusky/viewmodel/AccountViewModel.kt | 8 ++--- .../main/res/layout/dialog_mute_account.xml | 15 +++++++- app/src/main/res/values-ar/strings.xml | 14 ++++---- app/src/main/res/values-bn-rBD/strings.xml | 14 ++++---- app/src/main/res/values-bn-rIN/strings.xml | 14 ++++---- app/src/main/res/values-ca/strings.xml | 14 ++++---- app/src/main/res/values-cs/strings.xml | 14 ++++---- app/src/main/res/values-de/strings.xml | 14 ++++---- app/src/main/res/values-eo/strings.xml | 14 ++++---- app/src/main/res/values-es/strings.xml | 14 ++++---- app/src/main/res/values-eu/strings.xml | 14 ++++---- app/src/main/res/values-fa/strings.xml | 14 ++++---- app/src/main/res/values-fr/strings.xml | 14 ++++---- app/src/main/res/values-ga/strings.xml | 14 ++++---- app/src/main/res/values-hi/strings.xml | 14 ++++---- app/src/main/res/values-hu/strings.xml | 14 ++++---- app/src/main/res/values-is/strings.xml | 14 ++++---- app/src/main/res/values-it/strings.xml | 14 ++++---- app/src/main/res/values-ja/strings.xml | 14 ++++---- app/src/main/res/values-kab/strings.xml | 14 ++++---- app/src/main/res/values-ko/strings.xml | 14 ++++---- app/src/main/res/values-nl/strings.xml | 14 ++++---- app/src/main/res/values-no-rNB/strings.xml | 14 ++++---- app/src/main/res/values-oc/strings.xml | 14 ++++---- app/src/main/res/values-pl/strings.xml | 14 ++++---- app/src/main/res/values-pt-rBR/strings.xml | 14 ++++---- app/src/main/res/values-ru/strings.xml | 14 ++++---- app/src/main/res/values-sa/strings.xml | 14 ++++---- app/src/main/res/values-sl/strings.xml | 14 ++++---- app/src/main/res/values-sv/strings.xml | 14 ++++---- app/src/main/res/values-ta/strings.xml | 6 ++-- app/src/main/res/values-th/strings.xml | 14 ++++---- app/src/main/res/values-tr/strings.xml | 14 ++++---- app/src/main/res/values-vi/strings.xml | 14 ++++---- app/src/main/res/values-zh-rCN/strings.xml | 14 ++++---- app/src/main/res/values/donottranslate.xml | 36 +++++++++++++++---- app/src/main/res/values/strings.xml | 16 +++++---- 44 files changed, 303 insertions(+), 260 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt index c16584eb5..c89623d70 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt @@ -766,8 +766,8 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI showMuteAccountDialog( this, it.username - ) { notifications -> - viewModel.muteAccount(notifications) + ) { notifications, duration -> + viewModel.muteAccount(notifications, duration) } } } else { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchViewModel.kt index da2c3fb10..08afe44de 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchViewModel.kt @@ -193,8 +193,8 @@ class SearchViewModel @Inject constructor( return accountManager.getAllAccountsOrderedByActive() } - fun muteAccount(accountId: String, notifications: Boolean) { - timelineCases.mute(accountId, notifications) + fun muteAccount(accountId: String, notifications: Boolean, duration: Int) { + timelineCases.mute(accountId, notifications, duration) } fun pinAccount(status: Status, isPin: Boolean) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt index ffd698995..5fbbc7155 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt @@ -377,8 +377,8 @@ class SearchStatusesFragment : SearchFragment - viewModel.muteAccount(accountId, notifications) + ) { notifications, duration -> + viewModel.muteAccount(accountId, notifications, duration) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java index 8fe141c6c..e1d504373 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java @@ -340,8 +340,8 @@ public abstract class SFragment extends BaseFragment implements Injectable { MuteAccountDialog.showMuteAccountDialog( this.getActivity(), accountUsername, - (notifications) -> { - timelineCases.mute(accountId, notifications); + (notifications, duration) -> { + timelineCases.mute(accountId, notifications, duration); return Unit.INSTANCE; } ); diff --git a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt index 08a5483db..7b79e1b54 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt +++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt @@ -330,7 +330,8 @@ interface MastodonApi { @POST("api/v1/accounts/{id}/mute") fun muteAccount( @Path("id") accountId: String, - @Field("notifications") notifications: Boolean? = null + @Field("notifications") notifications: Boolean? = null, + @Field("duration") duration: Int? = null ): Single @POST("api/v1/accounts/{id}/unmute") diff --git a/app/src/main/java/com/keylesspalace/tusky/network/TimelineCases.kt b/app/src/main/java/com/keylesspalace/tusky/network/TimelineCases.kt index efdb410b3..8cf2b688d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/TimelineCases.kt +++ b/app/src/main/java/com/keylesspalace/tusky/network/TimelineCases.kt @@ -33,7 +33,7 @@ interface TimelineCases { fun reblog(status: Status, reblog: Boolean): Single fun favourite(status: Status, favourite: Boolean): Single fun bookmark(status: Status, bookmark: Boolean): Single - fun mute(id: String, notifications: Boolean) + fun mute(id: String, notifications: Boolean, duration: Int) fun block(id: String) fun delete(id: String): Single fun pin(status: Status, pin: Boolean) @@ -104,8 +104,8 @@ class TimelineCasesImpl( } } - override fun mute(id: String, notifications: Boolean) { - mastodonApi.muteAccount(id, notifications) + override fun mute(id: String, notifications: Boolean, duration: Int) { + mastodonApi.muteAccount(id, notifications, duration) .subscribe({ eventHub.dispatch(MuteEvent(id)) }, { t -> diff --git a/app/src/main/java/com/keylesspalace/tusky/view/MuteAccountDialog.kt b/app/src/main/java/com/keylesspalace/tusky/view/MuteAccountDialog.kt index 44a70267e..435e24501 100644 --- a/app/src/main/java/com/keylesspalace/tusky/view/MuteAccountDialog.kt +++ b/app/src/main/java/com/keylesspalace/tusky/view/MuteAccountDialog.kt @@ -4,6 +4,7 @@ package com.keylesspalace.tusky.view import android.app.Activity import android.widget.CheckBox +import android.widget.Spinner import android.widget.TextView import androidx.appcompat.app.AlertDialog import com.keylesspalace.tusky.R @@ -11,7 +12,7 @@ import com.keylesspalace.tusky.R fun showMuteAccountDialog( activity: Activity, accountUsername: String, - onOk: (notifications: Boolean) -> Unit + onOk: (notifications: Boolean, duration: Int) -> Unit ) { val view = activity.layoutInflater.inflate(R.layout.dialog_mute_account, null) (view.findViewById(R.id.warning) as TextView).text = @@ -21,7 +22,11 @@ fun showMuteAccountDialog( AlertDialog.Builder(activity) .setView(view) - .setPositiveButton(android.R.string.ok) { _, _ -> onOk(checkbox.isChecked) } + .setPositiveButton(android.R.string.ok) { _, _ -> + val spinner: Spinner = view.findViewById(R.id.duration) + val durationValues = activity.resources.getIntArray(R.array.mute_duration_values) + onOk(checkbox.isChecked, durationValues[spinner.selectedItemPosition]) + } .setNegativeButton(android.R.string.cancel, null) .show() } \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/viewmodel/AccountViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/viewmodel/AccountViewModel.kt index b2568f34e..a0f0ed680 100644 --- a/app/src/main/java/com/keylesspalace/tusky/viewmodel/AccountViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/viewmodel/AccountViewModel.kt @@ -119,8 +119,8 @@ class AccountViewModel @Inject constructor( } } - fun muteAccount(notifications: Boolean) { - changeRelationship(RelationShipAction.MUTE, notifications) + fun muteAccount(notifications: Boolean, duration: Int) { + changeRelationship(RelationShipAction.MUTE, notifications, duration) } fun unmuteAccount() { @@ -187,7 +187,7 @@ class AccountViewModel @Inject constructor( /** * @param parameter showReblogs if RelationShipAction.FOLLOW, notifications if MUTE */ - private fun changeRelationship(relationshipAction: RelationShipAction, parameter: Boolean? = null) { + private fun changeRelationship(relationshipAction: RelationShipAction, parameter: Boolean? = null, duration: Int? = null) { val relation = relationshipData.value?.data val account = accountData.value?.data val isMastodon = relationshipData.value?.data?.notifying != null @@ -227,7 +227,7 @@ class AccountViewModel @Inject constructor( RelationShipAction.UNFOLLOW -> mastodonApi.unfollowAccount(accountId) RelationShipAction.BLOCK -> mastodonApi.blockAccount(accountId) RelationShipAction.UNBLOCK -> mastodonApi.unblockAccount(accountId) - RelationShipAction.MUTE -> mastodonApi.muteAccount(accountId, parameter ?: true) + RelationShipAction.MUTE -> mastodonApi.muteAccount(accountId, parameter ?: true, duration) RelationShipAction.UNMUTE -> mastodonApi.unmuteAccount(accountId) RelationShipAction.SUBSCRIBE -> { if(isMastodon) diff --git a/app/src/main/res/layout/dialog_mute_account.xml b/app/src/main/res/layout/dialog_mute_account.xml index 673fc9e44..b58a277cb 100644 --- a/app/src/main/res/layout/dialog_mute_account.xml +++ b/app/src/main/res/layout/dialog_mute_account.xml @@ -22,4 +22,17 @@ app:buttonTint="@color/compound_button_color" android:text="@string/dialog_mute_hide_notifications"/> - \ No newline at end of file + + + + + diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 52d36870f..835a32f8e 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -441,13 +441,13 @@ إضافة استطلاع رأي افتح دائما التبويقات التي تحتوي على محتوى حساس استطلاع رأي - 5 دقائق - 30 دقيقة - ساعة واحدة - 6 ساعات - يوم واحد - 3 أيام - 7 أيام + 5 دقائق + 30 دقيقة + ساعة واحدة + 6 ساعات + يوم واحد + 3 أيام + 7 أيام ضف خيارا خيارات متعددة الخيار %d diff --git a/app/src/main/res/values-bn-rBD/strings.xml b/app/src/main/res/values-bn-rBD/strings.xml index 2fb78dd8f..2413319b5 100644 --- a/app/src/main/res/values-bn-rBD/strings.xml +++ b/app/src/main/res/values-bn-rBD/strings.xml @@ -345,13 +345,13 @@ পছন্দ %d একাধিক পছন্দ পছন্দ যুক্ত করুন - ৭ দিন - ৩ দিন - ১ দিন - ৬ ঘন্টা - ১ ঘন্টা - ৩০ মিনিট - ৫ মিনিট + ৭ দিন + ৩ দিন + ১ দিন + ৬ ঘন্টা + ১ ঘন্টা + ৩০ মিনিট + ৫ মিনিট ভোটগ্রহণ সরান পোল যুক্ত করুন diff --git a/app/src/main/res/values-bn-rIN/strings.xml b/app/src/main/res/values-bn-rIN/strings.xml index d2e7a0aa2..e578655ad 100644 --- a/app/src/main/res/values-bn-rIN/strings.xml +++ b/app/src/main/res/values-bn-rIN/strings.xml @@ -395,13 +395,13 @@ অনুসন্ধান করতে ব্যর্থ পোল যুক্ত করুন ভোটগ্রহণ - ৫ মিনিট - ৩০ মিনিট - ১ ঘন্টা - ৬ ঘন্টা - ১ দিন - ৩ দিন - ৭ দিন + ৫ মিনিট + ৩০ মিনিট + ১ ঘন্টা + ৬ ঘন্টা + ১ দিন + ৩ দিন + ৭ দিন পছন্দ যুক্ত করুন একাধিক পছন্দ পছন্দ %d diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 882a3f386..a769dbe80 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -399,16 +399,16 @@ \@%s reportat satisfactoriament El compte és d\'un altre servidor. Enviar, igualment, una copia anònima del report\? Cerca fallida - 1 hora - 6 hores + 1 hora + 6 hores Edita Afegeix una enquesta Enquesta - 5 minuts - 30 minuts - 1 dia - 3 dies - 7 dies + 5 minuts + 30 minuts + 1 dia + 3 dies + 7 dies Afegeix una tria Múltiples tries Tria %d diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 42c5ce261..867b3ec2c 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -422,13 +422,13 @@ Tento účet je z jiného serveru. Chcete na něj také poslat anonymizovanou kopii\? Zobrazit filtr oznámení Anketa - 5 minut - 30 minut - 1 hodinu - 6 hodin - 1 den - 3 dny - 7 dní + 5 minut + 30 minut + 1 hodinu + 6 hodin + 1 den + 3 dny + 7 dní Přidat možnost Lze zvolit více možností Možnost %d diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index edf0e7f64..6218bbbfe 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -395,13 +395,13 @@ Dieses Konto ist von einem anderen Server. Soll eine anonymisierte Kopie des Berichts auch dorthin geschickt werden\? Benachrichtigungsfilter anzeigen Umfrage - 5 Minuten - 30 Minuten - 1 Stunde - 6 Stunden - 1 Tag - 3 Tage - 7 Tage + 5 Minuten + 30 Minuten + 1 Stunde + 6 Stunden + 1 Tag + 3 Tage + 7 Tage Editieren test %s Umfrage hinzufügen diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 829888980..e2e011087 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -407,13 +407,13 @@ Aldoni baloton Ĉiam pligrandigi tootoj markiĝita per enhavaj avertoj Baloto - 5 minutoj - 30 minutoj - 1 horo - 6 horoj - 1 tago - 3 tagoj - 7 tagoj + 5 minutoj + 30 minutoj + 1 horo + 6 horoj + 1 tago + 3 tagoj + 7 tagoj Aldoni elekton Multaj elektoj Elekton %d diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 4072ae0c8..089552800 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -420,13 +420,13 @@ Error al buscar Añadir encuesta Encuesta - 5 minutos - 30 minutos - 1 hora - 6 horas - 1 día - 3 días - 7 días + 5 minutos + 30 minutos + 1 hora + 6 horas + 1 día + 3 días + 7 días Añadir opción Opciones múltiples Opción %d diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 6dabede98..f4eb9e71c 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -417,13 +417,13 @@ Bilaketa huts egin du Erakutsi jakinarazpenen iragazkia Inkesta - 5 minutu - 30 minutu - Ordu 1 - 6 ordu - Egun 1 - 3 egun - 7 egun + 5 minutu + 30 minutu + Ordu 1 + 6 ordu + Egun 1 + 3 egun + 7 egun Gehitu aukera Aukera anitzak %d. aukera diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index a42844067..2f20b2109 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -400,13 +400,13 @@ شکست در جست‌وجو نمایش پالایهٔ آگاهی‌ها نظرسنجی - ۵ دقیقه - ۳۰ دقیقه - ۱ ساعت - ۶ ساعت - ۱ روز - ۳ روز - ۷ روز + ۵ دقیقه + ۳۰ دقیقه + ۱ ساعت + ۶ ساعت + ۱ روز + ۳ روز + ۷ روز افزودن گزینه گزینه‌های چندگانه گزینهٔ %d diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index c0ad6bfd2..6240d8134 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -425,13 +425,13 @@ Toujours ouvrir les pouets avec un contenu sensible Ajouter un sondage Sondage - 5 minutes - 30 minutes - 1 heure - 6 heures - 1 jour - 3 jours - 7 jours + 5 minutes + 30 minutes + 1 heure + 6 heures + 1 jour + 3 jours + 7 jours Ajouter un choix Choix multiples Choix %d diff --git a/app/src/main/res/values-ga/strings.xml b/app/src/main/res/values-ga/strings.xml index fa8b85627..aad2a612e 100644 --- a/app/src/main/res/values-ga/strings.xml +++ b/app/src/main/res/values-ga/strings.xml @@ -445,13 +445,13 @@ Taispeáin scagaire Fógraí Cumasaigh gotha swipe aistriú idir cluaisíní Vótaíocht - 5 nóiméad - 30 nóiméad - 1 uair an chloig - 6 uair an chloig - 1 lá - 3 lá - 7 lá + 5 nóiméad + 30 nóiméad + 1 uair an chloig + 6 uair an chloig + 1 lá + 3 lá + 7 lá Cuir rogha leis Ilroghanna Rogha %d diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 466019f61..a4c1b373b 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -200,13 +200,13 @@ विकल्प जोड़ें विकल्प %d कई विकल्प - 7 दिन - 3 दिन - 1 दिन - 6 घंटे - 1 घंटा - 30 मिनिट - 5 मिनट + 7 दिन + 3 दिन + 1 दिन + 6 घंटे + 1 घंटा + 30 मिनिट + 5 मिनट %d घंटा शेष %d घंटे शेष diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 4c1f5cd7c..9215ed09c 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -417,13 +417,13 @@ Sikertelen keresés Szavazás hozzáadása Szavazás - 5 perc - 30 perc - 1 óra - 6 óra - 1 nap - 3 nap - 7 nap + 5 perc + 30 perc + 1 óra + 6 óra + 1 nap + 3 nap + 7 nap Válasz hozzáadása Több lehetőség Válasz %d diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml index 4e27ac529..e52cda276 100644 --- a/app/src/main/res/values-is/strings.xml +++ b/app/src/main/res/values-is/strings.xml @@ -396,13 +396,13 @@ Tókst ekki að leita Birta tilkynningasíu Athuga - 5 mínútur - 30 mínútur - 1 klukkustund - 6 klukkustundir - 1 dagur - 3 dagar - 7 dagar + 5 mínútur + 30 mínútur + 1 klukkustund + 6 klukkustundir + 1 dagur + 3 dagar + 7 dagar Bæta við valkosti Margir valkostir Valkostur %d diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 01dbcb690..3b716a1f9 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -431,13 +431,13 @@ Errore durante la ricerca Mostra il filtro delle notifiche Sondaggio - 5 minuti - 30 minuti - 1 ora - 6 ore - 1 giorno - 3 giorni - 7 giorni + 5 minuti + 30 minuti + 1 ora + 6 ore + 1 giorno + 3 giorni + 7 giorni Aggiungi scelta Scelte multiple Scelta %d diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 37ecd1923..df4ec9319 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -370,13 +370,13 @@ 参加した投票の結果がでました 作成した投票の結果がでました 投票 - 5分 - 30分 - 1時間 - 6時間 - 1日 - 3日 - 7日 + 5分 + 30分 + 1時間 + 6時間 + 1日 + 3日 + 7日 選択肢を追加 複数選択可 編集 diff --git a/app/src/main/res/values-kab/strings.xml b/app/src/main/res/values-kab/strings.xml index 3c10f80b4..8f51de5e5 100644 --- a/app/src/main/res/values-kab/strings.xml +++ b/app/src/main/res/values-kab/strings.xml @@ -196,13 +196,13 @@ Tella-d tuccḍa deg cetki Tucḍa n unadi Assenqed - 5 n tisdidin - 30 n tisdidin - 1 n usrag - 6 n isragen - 1 n wass - 3 n wussan - 7 n wussan + 5 n tisdidin + 30 n tisdidin + 1 n usrag + 6 n isragen + 1 n wass + 3 n wussan + 7 n wussan Tafrant %d Ig ṭṭafar Imeḍfaṛen diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 4689c5afa..cf11c797f 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -409,13 +409,13 @@ 열람주의로 설정된 툿을 항상 펼치기 투표 추가 투표 - 5분 - 30분 - 1시간 - 6시간 - 1일 - 3일 - 7일 + 5분 + 30분 + 1시간 + 6시간 + 1일 + 3일 + 7일 항목 추가 여러 항목 선택 가능 %d번 항목 diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index d24e48c58..b8d2d09da 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -418,13 +418,13 @@ Meldingenfilter tonen Heel woord Wanneer het trefwoord of zinsdeel alfanumeriek is, wordt het alleen gefilterd wanneer het hele woord overeenkomt - 5 minuten - 30 minuten - 1 uur - 6 uur - 1 dag - 3 dagen - 7 dagen + 5 minuten + 30 minuten + 1 uur + 6 uur + 1 dag + 3 dagen + 7 dagen Voeg keuze toe Meerdere keuzes Keuze %d diff --git a/app/src/main/res/values-no-rNB/strings.xml b/app/src/main/res/values-no-rNB/strings.xml index 9201edb79..cd633dd0e 100644 --- a/app/src/main/res/values-no-rNB/strings.xml +++ b/app/src/main/res/values-no-rNB/strings.xml @@ -408,13 +408,13 @@ Ekspander alltid toots markert med innholdsadvarsel Legg til avstemming Avstemming - 5 minutter - 30 minutter - 1 time - 6 timer - 1 dag - 3 dager - 7 dager + 5 minutter + 30 minutter + 1 time + 6 timer + 1 dag + 3 dager + 7 dager Legg til valg Flere valg Valg %d diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml index 2ffd91ed5..c22afc29d 100644 --- a/app/src/main/res/values-oc/strings.xml +++ b/app/src/main/res/values-oc/strings.xml @@ -419,13 +419,13 @@ Fracàs de la recèrca Ajustar un sondatge Sondatge - 5 minutas - 30 minutas - 1 ora - 6 oras - 1 jorn - 3 jorns - 7 jorns + 5 minutas + 30 minutas + 1 ora + 6 oras + 1 jorn + 3 jorns + 7 jorns Ajustar d’opcions Opcions multiplas Opcion %d diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 5a6ec2955..9082f6347 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -430,13 +430,13 @@ Wyszukiwanie nie powidło się Pokaż filtr powiadomień Głosowanie - 5 minut - 30 minut - 1 godzina - 6 godzin - 1 dzień - 3 dni - 7 dni + 5 minut + 30 minut + 1 godzina + 6 godzin + 1 dzień + 3 dni + 7 dni Dodaj wybór Kilka wyborów Opcja %d diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index c5e9a7aa4..95bf8a29e 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -417,13 +417,13 @@ Contas Erro ao pesquisar Enquete - 5 minutos - 30 minutos - 1 hora - 6 horas - 1 dia - 3 dias - 7 dias + 5 minutos + 30 minutos + 1 hora + 6 horas + 1 dia + 3 dias + 7 dias Adicionar opção Múltiplas opções Opção %d diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a13d58ac0..6adb5768f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -444,13 +444,13 @@ Аккаунты Поиск завершился ошибкой Опрос - 5 минут - 30 минут - 1 час - 6 часов - 1 день - 3 дня - 7 дней + 5 минут + 30 минут + 1 час + 6 часов + 1 день + 3 дня + 7 дней Добавить Множественный выбор Вариант %d diff --git a/app/src/main/res/values-sa/strings.xml b/app/src/main/res/values-sa/strings.xml index a322838e8..2fb217ade 100644 --- a/app/src/main/res/values-sa/strings.xml +++ b/app/src/main/res/values-sa/strings.xml @@ -373,13 +373,13 @@ मतम् %d बहूनि मतानि अपरं मतं युज्यताम् - ७ दिनानि - ३ दिनानि - १ दिनम् - ६ घण्टाः - १ घण्टा - ३० निमेषाः - ५ निमेषाः + ७ दिनानि + ३ दिनानि + १ दिनम् + ६ घण्टाः + १ घण्टा + ३० निमेषाः + ५ निमेषाः मतपेटिका सारणहावभावस्य संयुतनं पीठिकापरिवर्तनार्थं कार्यम् सूचनाशोधकं दृश्यताम् diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index 5f4ca650a..70bb80beb 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -415,13 +415,13 @@ Vedno razširite tute, označene z opozorilom o vsebini Dodaj anketo Anketa - 5 minut - 30 minut - 1 ura - 6 ur - 1 dan - 3 dni - 7 dni + 5 minut + 30 minut + 1 ura + 6 ur + 1 dan + 3 dni + 7 dni Dodaj izbiro Več izbir Izbira %d diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 6b5296a30..ff8a5a133 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -425,13 +425,13 @@ Sökning misslyckades Skapa en omröstning Omröstning - 5 minuter - 30 minuter - 1 timme - 6 timmar - 1 dag - 3 dagar - 7 dagar + 5 minuter + 30 minuter + 1 timme + 6 timmar + 1 dag + 3 dagar + 7 dagar Lägg till alternativ Flerval Val %d diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index 2a5a035ea..2affaa29b 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -265,9 +265,9 @@ நேரடி தகவல் பட்டைகள் பொருத்தப்பட்டது - 1 நாள் - 3 நாட்கள் - 7 நாட்கள் + 1 நாள் + 3 நாட்கள் + 7 நாட்கள் விருப்பத்தைச் சேர் பின்பற்ற கோரிக்கை நீக்கு diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index ea831d516..5c994db6a 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -1,13 +1,13 @@ เพิ่มตัวเลือก - 7 วัน - 3 วัน - 1 วัน - 6 ชั่วโมง - 1 ชั่วโมง - 30 นาที - 5 นาที + 7 วัน + 3 วัน + 1 วัน + 6 ชั่วโมง + 1 ชั่วโมง + 30 นาที + 5 นาที โพล เปิดใช้งานการเลื่อนนิ้วเพื่อสลับระหว่างแท็บ แสดงตัวกรองการแจ้งเตือน diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index fdf123a16..596bafa41 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -418,13 +418,13 @@ Hesaplar Arama başarısız Anket - 5 dakika - 30 dakika - 1 saat - 6 saat - 1 gün - 3 gün - 7 gün + 5 dakika + 30 dakika + 1 saat + 6 saat + 1 gün + 3 gün + 7 gün Seçenek ekle Çoklu seçim Düzenle diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 295c438b2..460ebb2d8 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -318,13 +318,13 @@ Lựa chọn %d Cho phép chọn nhiều lựa chọn Thêm lựa chọn - 7 ngày - 3 ngày - 1 ngày - 6 giờ - 1 giờ - 30 phút - 5 phút + 7 ngày + 3 ngày + 1 ngày + 6 giờ + 1 giờ + 30 phút + 5 phút Bình chọn Vuốt qua lại giữa các tab Hiện bộ lọc thông báo diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index ccd20b0b8..03c70771a 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -436,13 +436,13 @@ 搜索失败 显示通知过滤器 投票 - 5 分钟 - 30 分钟 - 1 小时 - 6 小时 - 1 天 - 3 天 - 7 天 + 5 分钟 + 30 分钟 + 1 小时 + 6 小时 + 1 天 + 3 天 + 7 天 添加选择 多项选择 选择 %d diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 027247a07..794682c6f 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -145,13 +145,13 @@ - @string/poll_duration_5_min - @string/poll_duration_30_min - @string/poll_duration_1_hour - @string/poll_duration_6_hours - @string/poll_duration_1_day - @string/poll_duration_3_days - @string/poll_duration_7_days + @string/duration_5_min + @string/duration_30_min + @string/duration_1_hour + @string/duration_6_hours + @string/duration_1_day + @string/duration_3_days + @string/duration_7_days @@ -164,5 +164,27 @@ 604800 + + @string/duration_indefinite + @string/duration_5_min + @string/duration_30_min + @string/duration_1_hour + @string/duration_6_hours + @string/duration_1_day + @string/duration_3_days + @string/duration_7_days + + + + 0 + 300 + 1800 + 3600 + 21600 + 86400 + 259200 + 604800 + + <b>%1$d%%</b> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 87d63df5b..d0bc048dd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -558,13 +558,15 @@ Poll - 5 minutes - 30 minutes - 1 hour - 6 hours - 1 day - 3 days - 7 days + Duration + Indefinite + 5 minutes + 30 minutes + 1 hour + 6 hours + 1 day + 3 days + 7 days Add choice Multiple choices Choice %d From 4c7d09b1e39465255f1a54c83182e3d938e03951 Mon Sep 17 00:00:00 2001 From: Garrit Franke <32395585+garritfra@users.noreply.github.com> Date: Fri, 15 Jan 2021 21:16:32 +0100 Subject: [PATCH 14/75] Show display names for reposts (#2041) * WIP: Show display names for reposts * Display emojis on reposts * Move unicode wrapping Co-authored-by: Garrit Franke --- .../tusky/adapter/StatusViewHolder.java | 12 +++++++---- .../tusky/util/ViewDataUtils.java | 3 ++- .../tusky/viewdata/StatusViewData.java | 20 ++++++++++++++++--- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java index 56cef6ee1..4a0f66790 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java @@ -27,8 +27,10 @@ import androidx.recyclerview.widget.RecyclerView; import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.interfaces.StatusActionListener; +import com.keylesspalace.tusky.util.CustomEmojiHelper; import com.keylesspalace.tusky.util.SmartLengthInputFilter; import com.keylesspalace.tusky.util.StatusDisplayOptions; +import com.keylesspalace.tusky.util.StringUtils; import com.keylesspalace.tusky.viewdata.StatusViewData; import at.connyduck.sparkbutton.helpers.Utils; @@ -64,7 +66,7 @@ public class StatusViewHolder extends StatusBaseViewHolder { if (rebloggedByDisplayName == null) { hideStatusInfo(); } else { - setRebloggedByDisplayName(rebloggedByDisplayName); + setRebloggedByDisplayName(rebloggedByDisplayName, status); statusInfo.setOnClickListener(v -> listener.onOpenReblog(getAdapterPosition())); } @@ -73,10 +75,12 @@ public class StatusViewHolder extends StatusBaseViewHolder { } - private void setRebloggedByDisplayName(final String name) { + private void setRebloggedByDisplayName(final CharSequence name, final StatusViewData.Concrete status) { Context context = statusInfo.getContext(); - String boostedText = context.getString(R.string.status_boosted_format, name); - statusInfo.setText(boostedText); + CharSequence wrappedName = StringUtils.unicodeWrap(name); + CharSequence boostedText = context.getString(R.string.status_boosted_format, wrappedName); + CharSequence emojifiedText = CustomEmojiHelper.emojify(boostedText, status.getRebloggedByAccountEmojis(), statusInfo); + statusInfo.setText(emojifiedText); statusInfo.setVisibility(View.VISIBLE); } diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.java index 69faa6fd9..ffe64a14b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.java @@ -52,7 +52,7 @@ public final class ViewDataUtils { .setSensitive(visibleStatus.getSensitive()) .setIsShowingSensitiveContent(alwaysShowSensitiveMedia || !visibleStatus.getSensitive()) .setSpoilerText(visibleStatus.getSpoilerText()) - .setRebloggedByUsername(status.getReblog() == null ? null : status.getAccount().getUsername()) + .setRebloggedByUsername(status.getReblog() == null ? null : status.getAccount().getDisplayName()) .setUserFullName(visibleStatus.getAccount().getName()) .setVisibility(visibleStatus.getVisibility()) .setSenderId(visibleStatus.getAccount().getId()) @@ -60,6 +60,7 @@ public final class ViewDataUtils { .setApplication(visibleStatus.getApplication()) .setStatusEmojis(visibleStatus.getEmojis()) .setAccountEmojis(visibleStatus.getAccount().getEmojis()) + .setRebloggedByEmojis(status.getReblog() == null ? null : status.getAccount().getEmojis()) .setCollapsible(SmartLengthInputFilterKt.shouldTrimStatus(visibleStatus.getContent())) .setCollapsed(true) .setPoll(visibleStatus.getPoll()) diff --git a/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java b/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java index 81e101007..c0ceeb818 100644 --- a/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java +++ b/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java @@ -16,10 +16,11 @@ package com.keylesspalace.tusky.viewdata; import android.os.Build; -import androidx.annotation.Nullable; import android.text.SpannableStringBuilder; import android.text.Spanned; +import androidx.annotation.Nullable; + import com.keylesspalace.tusky.entity.Attachment; import com.keylesspalace.tusky.entity.Card; import com.keylesspalace.tusky.entity.Emoji; @@ -85,6 +86,7 @@ public abstract class StatusViewData { private final Status.Application application; private final List statusEmojis; private final List accountEmojis; + private final List rebloggedByAccountEmojis; @Nullable private final Card card; private final boolean isCollapsible; /** Whether the status meets the requirement to be collapse */ @@ -99,7 +101,7 @@ public abstract class StatusViewData { boolean isShowingContent, String userFullName, String nickname, String avatar, Date createdAt, int reblogsCount, int favouritesCount, @Nullable String inReplyToId, @Nullable Status.Mention[] mentions, String senderId, boolean rebloggingEnabled, - Status.Application application, List statusEmojis, List accountEmojis, @Nullable Card card, + Status.Application application, List statusEmojis, List accountEmojis, List rebloggedByAccountEmojis, @Nullable Card card, boolean isCollapsible, boolean isCollapsed, @Nullable PollViewData poll, boolean isBot) { this.id = id; @@ -136,6 +138,7 @@ public abstract class StatusViewData { this.application = application; this.statusEmojis = statusEmojis; this.accountEmojis = accountEmojis; + this.rebloggedByAccountEmojis = rebloggedByAccountEmojis; this.card = card; this.isCollapsible = isCollapsible; this.isCollapsed = isCollapsed; @@ -258,6 +261,10 @@ public abstract class StatusViewData { return accountEmojis; } + public List getRebloggedByAccountEmojis() { + return rebloggedByAccountEmojis; + } + @Nullable public Card getCard() { return card; @@ -324,6 +331,7 @@ public abstract class StatusViewData { Objects.equals(application, concrete.application) && Objects.equals(statusEmojis, concrete.statusEmojis) && Objects.equals(accountEmojis, concrete.accountEmojis) && + Objects.equals(rebloggedByAccountEmojis, concrete.rebloggedByAccountEmojis) && Objects.equals(card, concrete.card) && Objects.equals(poll, concrete.poll) && isCollapsed == concrete.isCollapsed; @@ -429,6 +437,7 @@ public abstract class StatusViewData { private Status.Application application; private List statusEmojis; private List accountEmojis; + private List rebloggedByAccountEmojis; private Card card; private boolean isCollapsible; /** Whether the status meets the requirement to be collapsed */ private boolean isCollapsed; /** Whether the status is shown partially or fully */ @@ -613,6 +622,11 @@ public abstract class StatusViewData { return this; } + public Builder setRebloggedByEmojis(List emojis) { + this.rebloggedByAccountEmojis = emojis; + return this; + } + public Builder setCard(Card card) { this.card = card; return this; @@ -656,7 +670,7 @@ public abstract class StatusViewData { visibility, attachments, rebloggedByUsername, rebloggedAvatar, isSensitive, isExpanded, isShowingContent, userFullName, nickname, avatar, createdAt, reblogsCount, favouritesCount, inReplyToId, mentions, senderId, rebloggingEnabled, application, - statusEmojis, accountEmojis, card, isCollapsible, isCollapsed, poll, isBot); + statusEmojis, accountEmojis, rebloggedByAccountEmojis, card, isCollapsible, isCollapsed, poll, isBot); } } } From 5fbd459aed1fc9ac59109c795b4c83e3448aaa80 Mon Sep 17 00:00:00 2001 From: Levi Bard Date: Fri, 15 Jan 2021 21:23:02 +0100 Subject: [PATCH 15/75] Make tapping the main toolbar scroll to the top of the timeline. #1899 (#2045) --- app/src/main/java/com/keylesspalace/tusky/MainActivity.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index 31a4e80c9..bb7b5a5a5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -554,6 +554,9 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje val activeTabPosition = if (selectNotificationTab) notificationTabPosition else 0 mainToolbar.title = tabs[activeTabPosition].title(this@MainActivity) + mainToolbar.setOnClickListener { + (adapter.getFragment(activeTabLayout.selectedTabPosition) as? ReselectableFragment)?.onReselect() + } } From cb2296f2482bca1ad97483ce7079d460561ca99e Mon Sep 17 00:00:00 2001 From: Levi Bard Date: Mon, 18 Jan 2021 11:40:13 +0100 Subject: [PATCH 16/75] Prompt before deleting lists (#2043) * Prompt before deleting lists. #1998 * Address pull request feedback --- .../com/keylesspalace/tusky/ListsActivity.kt | 16 ++++++++++++---- app/src/main/res/values/strings.xml | 1 + 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt index 3894f652d..994069f7a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt @@ -130,9 +130,7 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector { else R.string.action_rename_list) { _, _ -> onPickedDialogName(editText.text, list?.id) } - .setNegativeButton(android.R.string.cancel) { d, _ -> - d.dismiss() - } + .setNegativeButton(android.R.string.cancel, null) .show() val positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE) @@ -143,6 +141,16 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector { editText.text?.let { editText.setSelection(it.length) } } + private fun showListDeleteDialog(list: MastoList) { + AlertDialog.Builder(this) + .setMessage(getString(R.string.dialog_delete_list_warning, list.title)) + .setPositiveButton(R.string.action_delete){ _, _ -> + viewModel.deleteList(list.id) + } + .setNegativeButton(android.R.string.cancel, null) + .show() + } + private fun update(state: ListsViewModel.State) { adapter.submitList(state.lists) @@ -199,7 +207,7 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector { when (item.itemId) { R.id.list_edit -> openListSettings(list) R.id.list_rename -> renameListDialog(list) - R.id.list_delete -> viewModel.deleteList(list.id) + R.id.list_delete -> showListDeleteDialog(list) else -> return@setOnMenuItemClickListener false } true diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d0bc048dd..7f1ec78fc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -594,5 +594,6 @@ Hide quantitative stats on posts Hide quantitative stats on profiles You cannot upload more than %1$d media attachments. + Do you really want to delete the list %s? From baa915a0a3d3a5b9204e527dcb526559731a0fc8 Mon Sep 17 00:00:00 2001 From: Levi Bard Date: Mon, 18 Jan 2021 13:53:13 +0100 Subject: [PATCH 17/75] Support opening unknown attachment types via `openLink` (#2044) * Support opening unknown attachment types via openLink. #1970 * Fix label text for unknown attachment types --- .../tusky/adapter/StatusBaseViewHolder.java | 13 +++++++------ .../conversation/ConversationViewHolder.java | 2 +- .../search/fragments/SearchStatusesFragment.kt | 2 ++ .../tusky/fragment/AccountMediaFragment.kt | 7 +++---- .../com/keylesspalace/tusky/fragment/SFragment.java | 6 +++--- .../keylesspalace/tusky/util/StatusViewHelper.kt | 6 ++++-- app/src/main/res/values/strings.xml | 2 ++ 7 files changed, 22 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java index 046ab9cf3..5bce06246 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java @@ -533,7 +533,6 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { @DrawableRes private static int getLabelIcon(Attachment.Type type) { switch (type) { - default: case IMAGE: return R.drawable.ic_photo_24dp; case GIFV: @@ -541,6 +540,8 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { return R.drawable.ic_videocam_24dp; case AUDIO: return R.drawable.ic_music_box_24dp; + default: + return R.drawable.ic_attach_file_24dp; } } @@ -718,7 +719,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { setBookmarked(status.isBookmarked()); List attachments = status.getAttachments(); boolean sensitive = status.isSensitive(); - if (statusDisplayOptions.mediaPreviewEnabled() && !hasAudioAttachment(attachments)) { + if (statusDisplayOptions.mediaPreviewEnabled() && hasPreviewableAttachment(attachments)) { setMediaPreviews(attachments, sensitive, listener, status.isShowingContent(), statusDisplayOptions.useBlurhash()); if (attachments.size() == 0) { @@ -767,13 +768,13 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { } } - protected static boolean hasAudioAttachment(List attachments) { + protected static boolean hasPreviewableAttachment(List attachments) { for (Attachment attachment : attachments) { - if (attachment.getType() == Attachment.Type.AUDIO) { - return true; + if (attachment.getType() == Attachment.Type.AUDIO || attachment.getType() == Attachment.Type.UNKNOWN) { + return false; } } - return false; + return true; } private void setDescriptionForStatus(@NonNull StatusViewData.Concrete status, diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java index 6727156d0..19ef749e9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java @@ -83,7 +83,7 @@ public class ConversationViewHolder extends StatusBaseViewHolder { setBookmarked(status.getBookmarked()); List attachments = status.getAttachments(); boolean sensitive = status.getSensitive(); - if (statusDisplayOptions.mediaPreviewEnabled() && !hasAudioAttachment(attachments)) { + if (statusDisplayOptions.mediaPreviewEnabled() && hasPreviewableAttachment(attachments)) { setMediaPreviews(attachments, sensitive, listener, status.getShowingHiddenContent(), statusDisplayOptions.useBlurhash()); diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt index 5fbbc7155..bc7ac2ba1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt @@ -54,6 +54,7 @@ import com.keylesspalace.tusky.interfaces.AccountSelectionListener import com.keylesspalace.tusky.interfaces.StatusActionListener import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.CardViewMode +import com.keylesspalace.tusky.util.LinkHelper import com.keylesspalace.tusky.util.NetworkState import com.keylesspalace.tusky.util.StatusDisplayOptions import com.keylesspalace.tusky.view.showMuteAccountDialog @@ -143,6 +144,7 @@ class SearchStatusesFragment : SearchFragment { + LinkHelper.openLink(actionable.attachments[attachmentIndex].url, context) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt index ed1fbad8f..0ae207587 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt @@ -34,6 +34,7 @@ import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.interfaces.RefreshableFragment import com.keylesspalace.tusky.network.MastodonApi +import com.keylesspalace.tusky.util.LinkHelper import com.keylesspalace.tusky.util.ThemeUtils import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.show @@ -260,10 +261,8 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { } } Attachment.Type.UNKNOWN -> { - }/* Intentionally do nothing. This case is here is to handle when new attachment - * types are added to the API before code is added here to handle them. So, the - * best fallback is to just show the preview and ignore requests to view them. */ - + LinkHelper.openLink(items[currentIndex].attachment.url, context) + } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java index e1d504373..4c50740b7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java @@ -63,6 +63,7 @@ import com.keylesspalace.tusky.entity.PollOption; import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.network.MastodonApi; import com.keylesspalace.tusky.network.TimelineCases; +import com.keylesspalace.tusky.util.LinkHelper; import com.keylesspalace.tusky.view.MuteAccountDialog; import com.keylesspalace.tusky.viewdata.AttachmentViewData; @@ -395,10 +396,9 @@ public abstract class SFragment extends BaseFragment implements Injectable { } break; } + default: case UNKNOWN: { - /* Intentionally do nothing. This case is here is to handle when new attachment - * types are added to the API before code is added here to handle them. So, the - * best fallback is to just show the preview and ignore requests to view them. */ + LinkHelper.openLink(active.getUrl(), getContext()); break; } } diff --git a/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt index 2fb9ad428..db0441bd8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt @@ -228,7 +228,8 @@ class StatusViewHelper(private val itemView: View) { return when (type) { Attachment.Type.IMAGE -> context.getString(R.string.status_media_images) Attachment.Type.GIFV, Attachment.Type.VIDEO -> context.getString(R.string.status_media_video) - else -> context.getString(R.string.status_media_images) + Attachment.Type.AUDIO -> context.getString(R.string.status_media_audio) + else -> context.getString(R.string.status_media_attachments) } } @@ -237,7 +238,8 @@ class StatusViewHelper(private val itemView: View) { return when (type) { Attachment.Type.IMAGE -> R.drawable.ic_photo_24dp Attachment.Type.GIFV, Attachment.Type.VIDEO -> R.drawable.ic_videocam_24dp - else -> R.drawable.ic_photo_24dp + Attachment.Type.AUDIO -> R.drawable.ic_music_box_24dp + else -> R.drawable.ic_attach_file_24dp } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7f1ec78fc..6b08043e5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -326,6 +326,8 @@ Share link to toot Images Video + Audio + Attachments Follow requested From 940d6d395a98518467662f5f7275851d59e0aa9c Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Thu, 21 Jan 2021 18:57:09 +0100 Subject: [PATCH 18/75] Drafts v2 (#2032) * cleanup warnings, reorganize some code * move ComposeAutoCompleteAdapter to compose package * composeOptions doesn't need to be a class member * add DraftsActivity and DraftsViewModel * drafts * remove unnecessary Unit in ComposeViewModel * add schema/25.json * fix db migration * drafts * cleanup code * fix compose activity rotation bug * fix media descriptions getting lost when restoring a draft * improve deleting drafts * fix ComposeActivityTest * improve draft layout for almost empty drafts * reformat code * show toast when opening reply to deleted toot * improve item_draft layout --- app/build.gradle | 3 + .../25.json | 821 ++++++++++++++++++ app/src/main/AndroidManifest.xml | 1 + .../com/keylesspalace/tusky/MainActivity.kt | 32 +- .../tusky/SavedTootActivity.java | 4 +- .../components/compose/ComposeActivity.kt | 86 +- .../compose}/ComposeAutoCompleteAdapter.java | 2 +- .../components/compose/ComposeViewModel.kt | 159 ++-- .../tusky/components/compose/MediaUploader.kt | 8 +- .../tusky/components/drafts/DraftHelper.kt | 159 ++++ .../components/drafts/DraftMediaAdapter.kt | 81 ++ .../tusky/components/drafts/DraftsActivity.kt | 197 +++++ .../tusky/components/drafts/DraftsAdapter.kt | 92 ++ .../components/drafts/DraftsViewModel.kt | 69 ++ .../scheduled/ScheduledTootActivity.kt | 2 +- .../keylesspalace/tusky/db/AppDatabase.java | 24 +- .../com/keylesspalace/tusky/db/Converters.kt | 26 +- .../com/keylesspalace/tusky/db/DraftDao.kt | 40 + .../com/keylesspalace/tusky/db/DraftEntity.kt | 55 ++ .../tusky/db/TimelineStatusEntity.kt | 2 +- .../com/keylesspalace/tusky/db/TootDao.java | 11 +- .../tusky/di/ActivitiesModule.kt | 4 + .../com/keylesspalace/tusky/di/AppModule.kt | 2 +- .../tusky/di/ViewModelFactory.kt | 6 + .../tusky/network/MastodonApi.kt | 8 +- .../receiver/SendStatusBroadcastReceiver.kt | 34 +- .../tusky/service/SendTootService.kt | 34 +- .../tusky/util/BindingViewHolder.kt | 8 + .../tusky/util/RxAwareViewModel.kt | 2 + .../tusky/util/SaveTootHelper.java | 150 +--- app/src/main/res/drawable/ic_alert_circle.xml | 8 + app/src/main/res/drawable/ic_notebook.xml | 2 +- .../layout-sw640dp/fragment_view_thread.xml | 4 +- app/src/main/res/layout/activity_compose.xml | 3 + app/src/main/res/layout/activity_drafts.xml | 34 + .../main/res/layout/fragment_view_thread.xml | 4 +- app/src/main/res/layout/item_draft.xml | 95 ++ app/src/main/res/layout/toolbar_basic.xml | 27 +- app/src/main/res/menu/drafts.xml | 10 + app/src/main/res/values-ar/strings.xml | 2 +- app/src/main/res/values-ber/strings.xml | 2 +- app/src/main/res/values-bn-rBD/strings.xml | 2 +- app/src/main/res/values-bn-rIN/strings.xml | 2 +- app/src/main/res/values-ca/strings.xml | 2 +- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-cy/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-eo/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-eu/strings.xml | 2 +- app/src/main/res/values-fa/strings.xml | 2 +- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-ga/strings.xml | 2 +- app/src/main/res/values-gd/strings.xml | 2 +- app/src/main/res/values-hi/strings.xml | 2 +- app/src/main/res/values-hu/strings.xml | 2 +- app/src/main/res/values-is/strings.xml | 2 +- app/src/main/res/values-it/strings.xml | 2 +- app/src/main/res/values-ja/strings.xml | 2 +- app/src/main/res/values-kab/strings.xml | 2 +- app/src/main/res/values-ko/strings.xml | 2 +- app/src/main/res/values-ml/strings.xml | 2 +- app/src/main/res/values-nl/strings.xml | 2 +- app/src/main/res/values-no-rNB/strings.xml | 2 +- app/src/main/res/values-oc/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-sa/strings.xml | 2 +- app/src/main/res/values-sk/strings.xml | 2 +- app/src/main/res/values-sl/strings.xml | 2 +- app/src/main/res/values-sv/strings.xml | 2 +- app/src/main/res/values-ta/strings.xml | 2 +- app/src/main/res/values-th/strings.xml | 2 +- app/src/main/res/values-tr/strings.xml | 2 +- app/src/main/res/values-uk/strings.xml | 2 +- app/src/main/res/values-vi/strings.xml | 2 +- app/src/main/res/values-zh-rCN/strings.xml | 2 +- app/src/main/res/values-zh-rHK/strings.xml | 2 +- app/src/main/res/values-zh-rMO/strings.xml | 2 +- app/src/main/res/values-zh-rSG/strings.xml | 2 +- app/src/main/res/values-zh-rTW/strings.xml | 2 +- app/src/main/res/values/dimens.xml | 1 + app/src/main/res/values/strings.xml | 14 +- .../tusky/ComposeActivityTest.kt | 3 +- 85 files changed, 2032 insertions(+), 381 deletions(-) create mode 100644 app/schemas/com.keylesspalace.tusky.db.AppDatabase/25.json rename app/src/main/java/com/keylesspalace/tusky/{adapter => components/compose}/ComposeAutoCompleteAdapter.java (99%) create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftMediaAdapter.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsAdapter.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsViewModel.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/db/DraftEntity.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/util/BindingViewHolder.kt create mode 100644 app/src/main/res/drawable/ic_alert_circle.xml create mode 100644 app/src/main/res/layout/activity_drafts.xml create mode 100644 app/src/main/res/layout/item_draft.xml create mode 100644 app/src/main/res/menu/drafts.xml diff --git a/app/build.gradle b/app/build.gradle index a7f566ac6..fa6fc401d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -67,6 +67,9 @@ android { androidExtensions { experimental = true } + buildFeatures { + viewBinding true + } testOptions { unitTests { returnDefaultValues = true diff --git a/app/schemas/com.keylesspalace.tusky.db.AppDatabase/25.json b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/25.json new file mode 100644 index 000000000..01a491b4a --- /dev/null +++ b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/25.json @@ -0,0 +1,821 @@ +{ + "formatVersion": 1, + "database": { + "version": 25, + "identityHash": "e2cb844862443c2c5cc884c11f120d43", + "entities": [ + { + "tableName": "TootEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `text` TEXT, `urls` TEXT, `descriptions` TEXT, `contentWarning` TEXT, `inReplyToId` TEXT, `inReplyToText` TEXT, `inReplyToUsername` TEXT, `visibility` INTEGER, `poll` TEXT)", + "fields": [ + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "urls", + "columnName": "urls", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "descriptions", + "columnName": "descriptions", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "contentWarning", + "columnName": "contentWarning", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToId", + "columnName": "inReplyToId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToText", + "columnName": "inReplyToText", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToUsername", + "columnName": "inReplyToUsername", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "visibility", + "columnName": "visibility", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "poll", + "columnName": "poll", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "uid" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "DraftEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `accountId` INTEGER NOT NULL, `inReplyToId` TEXT, `content` TEXT, `contentWarning` TEXT, `sensitive` INTEGER NOT NULL, `visibility` INTEGER NOT NULL, `attachments` TEXT NOT NULL, `poll` TEXT, `failedToSend` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "inReplyToId", + "columnName": "inReplyToId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "contentWarning", + "columnName": "contentWarning", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sensitive", + "columnName": "sensitive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "visibility", + "columnName": "visibility", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "poll", + "columnName": "poll", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "failedToSend", + "columnName": "failedToSend", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AccountEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `domain` TEXT NOT NULL, `accessToken` TEXT NOT NULL, `isActive` INTEGER NOT NULL, `accountId` TEXT NOT NULL, `username` TEXT NOT NULL, `displayName` TEXT NOT NULL, `profilePictureUrl` TEXT NOT NULL, `notificationsEnabled` INTEGER NOT NULL, `notificationsMentioned` INTEGER NOT NULL, `notificationsFollowed` INTEGER NOT NULL, `notificationsFollowRequested` INTEGER NOT NULL, `notificationsReblogged` INTEGER NOT NULL, `notificationsFavorited` INTEGER NOT NULL, `notificationsPolls` INTEGER NOT NULL, `notificationsSubscriptions` INTEGER NOT NULL, `notificationSound` INTEGER NOT NULL, `notificationVibration` INTEGER NOT NULL, `notificationLight` INTEGER NOT NULL, `defaultPostPrivacy` INTEGER NOT NULL, `defaultMediaSensitivity` INTEGER NOT NULL, `alwaysShowSensitiveMedia` INTEGER NOT NULL, `alwaysOpenSpoiler` INTEGER NOT NULL, `mediaPreviewEnabled` INTEGER NOT NULL, `lastNotificationId` TEXT NOT NULL, `activeNotifications` TEXT NOT NULL, `emojis` TEXT NOT NULL, `tabPreferences` TEXT NOT NULL, `notificationsFilter` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "domain", + "columnName": "domain", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accessToken", + "columnName": "accessToken", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isActive", + "columnName": "isActive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "profilePictureUrl", + "columnName": "profilePictureUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "notificationsEnabled", + "columnName": "notificationsEnabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsMentioned", + "columnName": "notificationsMentioned", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsFollowed", + "columnName": "notificationsFollowed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsFollowRequested", + "columnName": "notificationsFollowRequested", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsReblogged", + "columnName": "notificationsReblogged", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsFavorited", + "columnName": "notificationsFavorited", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsPolls", + "columnName": "notificationsPolls", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsSubscriptions", + "columnName": "notificationsSubscriptions", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationSound", + "columnName": "notificationSound", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationVibration", + "columnName": "notificationVibration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationLight", + "columnName": "notificationLight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "defaultPostPrivacy", + "columnName": "defaultPostPrivacy", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "defaultMediaSensitivity", + "columnName": "defaultMediaSensitivity", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "alwaysShowSensitiveMedia", + "columnName": "alwaysShowSensitiveMedia", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "alwaysOpenSpoiler", + "columnName": "alwaysOpenSpoiler", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mediaPreviewEnabled", + "columnName": "mediaPreviewEnabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastNotificationId", + "columnName": "lastNotificationId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "activeNotifications", + "columnName": "activeNotifications", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "emojis", + "columnName": "emojis", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tabPreferences", + "columnName": "tabPreferences", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "notificationsFilter", + "columnName": "notificationsFilter", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_AccountEntity_domain_accountId", + "unique": true, + "columnNames": [ + "domain", + "accountId" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_AccountEntity_domain_accountId` ON `${TABLE_NAME}` (`domain`, `accountId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "InstanceEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`instance` TEXT NOT NULL, `emojiList` TEXT, `maximumTootCharacters` INTEGER, `maxPollOptions` INTEGER, `maxPollOptionLength` INTEGER, `version` TEXT, PRIMARY KEY(`instance`))", + "fields": [ + { + "fieldPath": "instance", + "columnName": "instance", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "emojiList", + "columnName": "emojiList", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "maximumTootCharacters", + "columnName": "maximumTootCharacters", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxPollOptions", + "columnName": "maxPollOptions", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxPollOptionLength", + "columnName": "maxPollOptionLength", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "instance" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimelineStatusEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` TEXT NOT NULL, `url` TEXT, `timelineUserId` INTEGER NOT NULL, `authorServerId` TEXT, `inReplyToId` TEXT, `inReplyToAccountId` TEXT, `content` TEXT, `createdAt` INTEGER NOT NULL, `emojis` TEXT, `reblogsCount` INTEGER NOT NULL, `favouritesCount` INTEGER NOT NULL, `reblogged` INTEGER NOT NULL, `bookmarked` INTEGER NOT NULL, `favourited` INTEGER NOT NULL, `sensitive` INTEGER NOT NULL, `spoilerText` TEXT, `visibility` INTEGER, `attachments` TEXT, `mentions` TEXT, `application` TEXT, `reblogServerId` TEXT, `reblogAccountId` TEXT, `poll` TEXT, `muted` INTEGER, PRIMARY KEY(`serverId`, `timelineUserId`), FOREIGN KEY(`authorServerId`, `timelineUserId`) REFERENCES `TimelineAccountEntity`(`serverId`, `timelineUserId`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "timelineUserId", + "columnName": "timelineUserId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "authorServerId", + "columnName": "authorServerId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToId", + "columnName": "inReplyToId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToAccountId", + "columnName": "inReplyToAccountId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "emojis", + "columnName": "emojis", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reblogsCount", + "columnName": "reblogsCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "favouritesCount", + "columnName": "favouritesCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reblogged", + "columnName": "reblogged", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "bookmarked", + "columnName": "bookmarked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "favourited", + "columnName": "favourited", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sensitive", + "columnName": "sensitive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "spoilerText", + "columnName": "spoilerText", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "visibility", + "columnName": "visibility", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "mentions", + "columnName": "mentions", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "application", + "columnName": "application", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reblogServerId", + "columnName": "reblogServerId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reblogAccountId", + "columnName": "reblogAccountId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "poll", + "columnName": "poll", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "muted", + "columnName": "muted", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "serverId", + "timelineUserId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_TimelineStatusEntity_authorServerId_timelineUserId", + "unique": false, + "columnNames": [ + "authorServerId", + "timelineUserId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_TimelineStatusEntity_authorServerId_timelineUserId` ON `${TABLE_NAME}` (`authorServerId`, `timelineUserId`)" + } + ], + "foreignKeys": [ + { + "table": "TimelineAccountEntity", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "authorServerId", + "timelineUserId" + ], + "referencedColumns": [ + "serverId", + "timelineUserId" + ] + } + ] + }, + { + "tableName": "TimelineAccountEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` TEXT NOT NULL, `timelineUserId` INTEGER NOT NULL, `localUsername` TEXT NOT NULL, `username` TEXT NOT NULL, `displayName` TEXT NOT NULL, `url` TEXT NOT NULL, `avatar` TEXT NOT NULL, `emojis` TEXT NOT NULL, `bot` INTEGER NOT NULL, PRIMARY KEY(`serverId`, `timelineUserId`))", + "fields": [ + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timelineUserId", + "columnName": "timelineUserId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localUsername", + "columnName": "localUsername", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatar", + "columnName": "avatar", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "emojis", + "columnName": "emojis", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "bot", + "columnName": "bot", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "serverId", + "timelineUserId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ConversationEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountId` INTEGER NOT NULL, `id` TEXT NOT NULL, `accounts` TEXT NOT NULL, `unread` INTEGER NOT NULL, `s_id` TEXT NOT NULL, `s_url` TEXT, `s_inReplyToId` TEXT, `s_inReplyToAccountId` TEXT, `s_account` TEXT NOT NULL, `s_content` TEXT NOT NULL, `s_createdAt` INTEGER NOT NULL, `s_emojis` TEXT NOT NULL, `s_favouritesCount` INTEGER NOT NULL, `s_favourited` INTEGER NOT NULL, `s_bookmarked` INTEGER NOT NULL, `s_sensitive` INTEGER NOT NULL, `s_spoilerText` TEXT NOT NULL, `s_attachments` TEXT NOT NULL, `s_mentions` TEXT NOT NULL, `s_showingHiddenContent` INTEGER NOT NULL, `s_expanded` INTEGER NOT NULL, `s_collapsible` INTEGER NOT NULL, `s_collapsed` INTEGER NOT NULL, `s_poll` TEXT, PRIMARY KEY(`id`, `accountId`))", + "fields": [ + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accounts", + "columnName": "accounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.id", + "columnName": "s_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.url", + "columnName": "s_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.inReplyToId", + "columnName": "s_inReplyToId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.inReplyToAccountId", + "columnName": "s_inReplyToAccountId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.account", + "columnName": "s_account", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.content", + "columnName": "s_content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.createdAt", + "columnName": "s_createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.emojis", + "columnName": "s_emojis", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.favouritesCount", + "columnName": "s_favouritesCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.favourited", + "columnName": "s_favourited", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.bookmarked", + "columnName": "s_bookmarked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.sensitive", + "columnName": "s_sensitive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.spoilerText", + "columnName": "s_spoilerText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.attachments", + "columnName": "s_attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.mentions", + "columnName": "s_mentions", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.showingHiddenContent", + "columnName": "s_showingHiddenContent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.expanded", + "columnName": "s_expanded", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.collapsible", + "columnName": "s_collapsible", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.collapsed", + "columnName": "s_collapsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.poll", + "columnName": "s_poll", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id", + "accountId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e2cb844862443c2c5cc884c11f120d43')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 585ff833c..770d45af6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -146,6 +146,7 @@ + + val showDraftWarning = preferences.getBoolean(sharedPrefsKey, true) + if (draftCount > 0 && showDraftWarning) { + AlertDialog.Builder(this) + .setMessage(R.string.new_drafts_warning) + .setNegativeButton("Don't show again") { _, _ -> + preferences.edit(commit = true) { + putBoolean(sharedPrefsKey, false) + } + } + .setPositiveButton(android.R.string.ok, null) + .show() + } + } + + } + override fun getActionButton(): FloatingActionButton? = composeButton override fun androidInjector() = androidInjector diff --git a/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java b/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java index 9a1639898..8e2b5acb2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java @@ -89,7 +89,7 @@ public final class SavedTootActivity extends BaseActivity implements SavedTootAd setSupportActionBar(toolbar); ActionBar bar = getSupportActionBar(); if (bar != null) { - bar.setTitle(getString(R.string.title_saved_toot)); + bar.setTitle(getString(R.string.title_drafts)); bar.setDisplayHomeAsUpEnabled(true); bar.setDisplayShowHomeEnabled(true); } @@ -166,6 +166,7 @@ public final class SavedTootActivity extends BaseActivity implements SavedTootAd ComposeOptions composeOptions = new ComposeOptions( /*scheduledTootUid*/null, item.getUid(), + /*drafId*/null, item.getText(), jsonUrls, descriptions, @@ -177,6 +178,7 @@ public final class SavedTootActivity extends BaseActivity implements SavedTootAd item.getInReplyToUsername(), item.getInReplyToText(), /*mediaAttachments*/null, + /*draftAttachments*/null, /*scheduledAt*/null, /*sensitive*/null, /*poll*/null, diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt index 5306e57ae..69a090880 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt @@ -30,7 +30,6 @@ import android.os.Build import android.os.Bundle import android.os.Parcelable import android.provider.MediaStore -import android.text.TextUtils import android.util.Log import android.view.KeyEvent import android.view.MenuItem @@ -57,13 +56,13 @@ import com.google.android.material.snackbar.Snackbar import com.keylesspalace.tusky.BaseActivity import com.keylesspalace.tusky.BuildConfig 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.components.compose.dialog.makeCaptionDialog import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog import com.keylesspalace.tusky.components.compose.view.ComposeOptionsListener import com.keylesspalace.tusky.db.AccountEntity +import com.keylesspalace.tusky.db.DraftAttachment import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.entity.Attachment @@ -81,7 +80,6 @@ import java.io.File import java.io.IOException import java.util.* import javax.inject.Inject -import kotlin.collections.ArrayList import kotlin.math.max import kotlin.math.min @@ -104,10 +102,10 @@ class ComposeActivity : BaseActivity(), // this only exists when a status is trying to be sent, but uploads are still occurring private var finishingUploadDialog: ProgressDialog? = null private var photoUploadUri: Uri? = null + @VisibleForTesting var maximumTootCharacters = DEFAULT_CHARACTER_LIMIT - private var composeOptions: ComposeOptions? = null private val viewModel: ComposeViewModel by viewModels { viewModelFactory } private val maxUploadMediaNumber = 4 @@ -148,17 +146,17 @@ class ComposeActivity : BaseActivity(), /* If the composer is started up as a reply to another post, override the "starting" state * based on what the intent from the reply request passes. */ - if (intent != null) { - this.composeOptions = intent.getParcelableExtra(COMPOSE_OPTIONS_EXTRA) - viewModel.setup(composeOptions) - setupReplyViews(composeOptions?.replyingStatusAuthor) - val tootText = composeOptions?.tootText - if (!tootText.isNullOrEmpty()) { - composeEditField.setText(tootText) - } + + val composeOptions: ComposeOptions? = intent.getParcelableExtra(COMPOSE_OPTIONS_EXTRA) + + viewModel.setup(composeOptions) + setupReplyViews(composeOptions?.replyingStatusAuthor, composeOptions?.replyingStatusContent) + val tootText = composeOptions?.tootText + if (!tootText.isNullOrEmpty()) { + composeEditField.setText(tootText) } - if (!TextUtils.isEmpty(composeOptions?.scheduledAt)) { + if (!composeOptions?.scheduledAt.isNullOrEmpty()) { composeScheduleView.setDateTime(composeOptions?.scheduledAt) } @@ -169,38 +167,24 @@ class ComposeActivity : BaseActivity(), viewModel.setupComplete.value = true } - private fun applyShareIntent(intent: Intent?, savedInstanceState: Bundle?) { - if (intent != null && savedInstanceState == null) { + private fun applyShareIntent(intent: Intent, savedInstanceState: Bundle?) { + if (savedInstanceState == null) { /* Get incoming images being sent through a share action from another app. Only do this * when savedInstanceState is null, otherwise both the images from the intent and the * instance state will be re-queued. */ - val type = intent.type - if (type != null) { + intent.type?.also { type -> if (type.startsWith("image/") || type.startsWith("video/") || type.startsWith("audio/")) { - val uriList = ArrayList() - if (intent.action != null) { - when (intent.action) { - Intent.ACTION_SEND -> { - val uri = intent.getParcelableExtra(Intent.EXTRA_STREAM) - if (uri != null) { - uriList.add(uri) - } - } - Intent.ACTION_SEND_MULTIPLE -> { - val list = intent.getParcelableArrayListExtra( - Intent.EXTRA_STREAM) - if (list != null) { - for (uri in list) { - if (uri != null) { - uriList.add(uri) - } - } - } + when (intent.action) { + Intent.ACTION_SEND -> { + intent.getParcelableExtra(Intent.EXTRA_STREAM)?.let { uri -> + pickMedia(uri) + } + } + Intent.ACTION_SEND_MULTIPLE -> { + intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)?.forEach { uri -> + pickMedia(uri) } } - } - for (uri in uriList) { - pickMedia(uri) } } else if (type == "text/plain" && intent.action == Intent.ACTION_SEND) { @@ -224,7 +208,7 @@ class ComposeActivity : BaseActivity(), } } - private fun setupReplyViews(replyingStatusAuthor: String?) { + private fun setupReplyViews(replyingStatusAuthor: String?, replyingStatusContent: String?) { if (replyingStatusAuthor != null) { composeReplyView.show() composeReplyView.text = getString(R.string.replying_to, replyingStatusAuthor) @@ -248,7 +232,7 @@ class ComposeActivity : BaseActivity(), } } } - composeOptions?.replyingStatusContent?.let { composeReplyContentView.text = it } + replyingStatusContent?.let { composeReplyContentView.text = it } } private fun setupContentWarningField(startingContentWarning: String?) { @@ -651,7 +635,6 @@ class ComposeActivity : BaseActivity(), } } - private fun removePoll() { viewModel.poll.value = null pollPreview.hide() @@ -835,22 +818,22 @@ class ComposeActivity : BaseActivity(), override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { super.onActivityResult(requestCode, resultCode, intent) if (resultCode == Activity.RESULT_OK && requestCode == MEDIA_PICK_RESULT && intent != null) { - if(intent.data != null){ + if (intent.data != null) { // Single media, upload it and done. pickMedia(intent.data!!) - }else if(intent.clipData != null){ + } else if (intent.clipData != null) { val clipData = intent.clipData!! val count = clipData.itemCount - if(mediaCount + count > maxUploadMediaNumber){ + if (mediaCount + count > maxUploadMediaNumber) { // check if exist media + upcoming media > 4, then prob error message. Toast.makeText(this, getString(R.string.error_upload_max_media_reached, maxUploadMediaNumber), Toast.LENGTH_SHORT).show() - }else{ + } else { // if not grater then 4, upload all multiple media. for (i in 0 until count) { - val imageUri = clipData.getItemAt(i).getUri() - pickMedia(imageUri) - } + val imageUri = clipData.getItemAt(i).getUri() + pickMedia(imageUri) } + } } } else if (resultCode == Activity.RESULT_OK && requestCode == MEDIA_TAKE_PHOTO_RESULT) { pickMedia(photoUploadUri!!) @@ -1018,8 +1001,9 @@ class ComposeActivity : BaseActivity(), @Parcelize data class ComposeOptions( // Let's keep fields var until all consumers are Kotlin - var scheduledTootUid: String? = null, + var scheduledTootId: String? = null, var savedTootUid: Int? = null, + var draftId: Int? = null, var tootText: String? = null, var mediaUrls: List? = null, var mediaDescriptions: List? = null, @@ -1031,6 +1015,7 @@ class ComposeActivity : BaseActivity(), var replyingStatusAuthor: String? = null, var replyingStatusContent: String? = null, var mediaAttachments: List? = null, + var draftAttachments: List? = null, var scheduledAt: String? = null, var sensitive: Boolean? = null, var poll: NewPoll? = null, @@ -1057,7 +1042,6 @@ class ComposeActivity : BaseActivity(), } } - @JvmStatic fun canHandleMimeType(mimeType: String?): Boolean { return mimeType != null && (mimeType.startsWith("image/") || mimeType.startsWith("video/") || mimeType.startsWith("audio/") || mimeType == "text/plain") } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/ComposeAutoCompleteAdapter.java b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java similarity index 99% rename from app/src/main/java/com/keylesspalace/tusky/adapter/ComposeAutoCompleteAdapter.java rename to app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java index ebc292ab4..df9ae8a81 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/ComposeAutoCompleteAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java @@ -13,7 +13,7 @@ * You should have received a copy of the GNU General Public License along with Tusky; if not, * see . */ -package com.keylesspalace.tusky.adapter; +package com.keylesspalace.tusky.components.compose; import android.content.Context; import android.preference.PreferenceManager; diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt index 2c015899b..a1a5e7e63 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt @@ -21,8 +21,8 @@ import androidx.core.net.toUri import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer -import com.keylesspalace.tusky.adapter.ComposeAutoCompleteAdapter import com.keylesspalace.tusky.components.compose.ComposeActivity.QueuedMedia +import com.keylesspalace.tusky.components.drafts.DraftHelper import com.keylesspalace.tusky.components.search.SearchType import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.db.AppDatabase @@ -39,18 +39,12 @@ import io.reactivex.rxkotlin.Singles import java.util.* import javax.inject.Inject -/** - * Throw when trying to add an image when video is already present or the other way around - */ -class VideoOrImageException : Exception() - - -class ComposeViewModel -@Inject constructor( +class ComposeViewModel @Inject constructor( private val api: MastodonApi, private val accountManager: AccountManager, private val mediaUploader: MediaUploader, private val serviceClient: ServiceClient, + private val draftHelper: DraftHelper, private val saveTootHelper: SaveTootHelper, private val db: AppDatabase ) : RxAwareViewModel() { @@ -59,7 +53,8 @@ class ComposeViewModel private var replyingStatusContent: String? = null internal var startingText: String? = null private var savedTootUid: Int = 0 - private var scheduledTootUid: String? = null + private var draftId: Int = 0 + private var scheduledTootId: String? = null private var startingContentWarning: String = "" private var inReplyToId: String? = null private var startingVisibility: Status.Visibility = Status.Visibility.UNKNOWN @@ -81,10 +76,6 @@ class ComposeViewModel val markMediaAsSensitive = mutableLiveData(accountManager.activeAccount?.defaultMediaSensitivity ?: false) - fun toggleMarkSensitive() { - this.markMediaAsSensitive.value = !this.markMediaAsSensitive.value!! - } - val statusVisibility = mutableLiveData(Status.Visibility.UNKNOWN) val showContentWarning = mutableLiveData(false) val setupComplete = mutableLiveData(false) @@ -96,7 +87,7 @@ class ComposeViewModel private val mediaToDisposable = mutableMapOf() - private val isEditingScheduledToot get() = !scheduledTootUid.isNullOrEmpty() + private val isEditingScheduledToot get() = !scheduledTootId.isNullOrEmpty() init { @@ -116,7 +107,7 @@ class ComposeViewModel .onErrorResumeNext( db.instanceDao().loadMetadataForInstance(accountManager.activeAccount?.domain!!) ) - .subscribe ({ instanceEntity -> + .subscribe({ instanceEntity -> emoji.postValue(instanceEntity.emojiList) instance.postValue(instanceEntity) }, { throwable -> @@ -126,7 +117,7 @@ class ComposeViewModel .autoDispose() } - fun pickMedia(uri: Uri): LiveData> { + fun pickMedia(uri: Uri, description: String? = null): LiveData> { // We are not calling .toLiveData() here because we don't want to stop the process when // the Activity goes away temporarily (like on screen rotation). val liveData = MutableLiveData>() @@ -138,7 +129,7 @@ class ComposeViewModel && mediaItems[0].type == QueuedMedia.Type.IMAGE) { throw VideoOrImageException() } else { - addMediaToQueue(type, uri, size) + addMediaToQueue(type, uri, size, description) } } .subscribe({ queuedMedia -> @@ -150,12 +141,23 @@ class ComposeViewModel return liveData } - private fun addMediaToQueue(type: QueuedMedia.Type, uri: Uri, mediaSize: Long): QueuedMedia { - val mediaItem = QueuedMedia(System.currentTimeMillis(), uri, type, mediaSize) + private fun addMediaToQueue( + type: QueuedMedia.Type, + uri: Uri, + mediaSize: Long, + description: String? = null + ): QueuedMedia { + val mediaItem = QueuedMedia( + localId = System.currentTimeMillis(), + uri = uri, + type = type, + mediaSize = mediaSize, + description = description + ) media.value = media.value!! + mediaItem mediaToDisposable[mediaItem.localId] = mediaUploader .uploadMedia(mediaItem) - .subscribe ({ event -> + .subscribe({ event -> val item = media.value?.find { it.localId == mediaItem.localId } ?: return@subscribe val newMediaItem = when (event) { @@ -190,6 +192,10 @@ class ComposeViewModel media.value = media.value!!.withoutFirstWhich { it.localId == item.localId } } + fun toggleMarkSensitive() { + this.markMediaAsSensitive.value = this.markMediaAsSensitive.value != true + } + fun didChange(content: String?, contentWarning: String?): Boolean { val textChanged = !(content.isNullOrEmpty() @@ -210,29 +216,37 @@ class ComposeViewModel } fun deleteDraft() { - saveTootHelper.deleteDraft(this.savedTootUid) + if (savedTootUid != 0) { + saveTootHelper.deleteDraft(savedTootUid) + } + if (draftId != 0) { + draftHelper.deleteDraftAndAttachments(draftId) + .subscribe() + } } fun saveDraft(content: String, contentWarning: String) { - val mediaUris = mutableListOf() - val mediaDescriptions = mutableListOf() - for (item in media.value!!) { + + val mediaUris: MutableList = mutableListOf() + val mediaDescriptions: MutableList = mutableListOf() + media.value?.forEach { item -> mediaUris.add(item.uri.toString()) mediaDescriptions.add(item.description) } - saveTootHelper.saveToot( - content, - contentWarning, - null, - mediaUris, - mediaDescriptions, - savedTootUid, - inReplyToId, - replyingStatusContent, - replyingStatusAuthor, - statusVisibility.value!!, - poll.value - ) + + draftHelper.saveDraft( + draftId = draftId, + accountId = accountManager.activeAccount?.id!!, + inReplyToId = inReplyToId, + content = content, + contentWarning = contentWarning, + sensitive = markMediaAsSensitive.value!!, + visibility = statusVisibility.value!!, + mediaUris = mediaUris, + mediaDescriptions = mediaDescriptions, + poll = poll.value, + failedToSend = false + ).subscribe() } /** @@ -246,7 +260,7 @@ class ComposeViewModel ): LiveData { val deletionObservable = if (isEditingScheduledToot) { - api.deleteScheduledStatus(scheduledTootUid.toString()).toObservable().map { Unit } + api.deleteScheduledStatus(scheduledTootId.toString()).toObservable().map { } } else { just(Unit) }.toLiveData() @@ -257,28 +271,30 @@ class ComposeViewModel val mediaIds = ArrayList() val mediaUris = ArrayList() val mediaDescriptions = ArrayList() + val mediaTypes = ArrayList() for (item in media.value!!) { mediaIds.add(item.id!!) mediaUris.add(item.uri) mediaDescriptions.add(item.description ?: "") + mediaTypes.add(item.type) } val tootToSend = TootToSend( - content, - spoilerText, - statusVisibility.value!!.serverString(), - mediaUris.isNotEmpty() && (markMediaAsSensitive.value!! || showContentWarning.value!!), - mediaIds, - mediaUris.map { it.toString() }, - mediaDescriptions, + text = content, + warningText = spoilerText, + visibility = statusVisibility.value!!.serverString(), + sensitive = mediaUris.isNotEmpty() && (markMediaAsSensitive.value!! || showContentWarning.value!!), + mediaIds = mediaIds, + mediaUris = mediaUris.map { it.toString() }, + mediaDescriptions = mediaDescriptions, scheduledAt = scheduledAt.value, inReplyToId = inReplyToId, poll = poll.value, replyingStatusContent = null, replyingStatusAuthorUsername = null, - savedJsonUrls = null, accountId = accountManager.activeAccount!!.id, - savedTootUid = 0, + savedTootUid = savedTootUid, + draftId = draftId, idempotencyKey = randomAlphanumericString(16), retries = 0 ) @@ -286,9 +302,7 @@ class ComposeViewModel serviceClient.sendToot(tootToSend) } - return combineLiveData(deletionObservable, sendObservable) { _, _ -> Unit } - - + return combineLiveData(deletionObservable, sendObservable) { _, _ -> } } fun updateDescription(localId: Long, description: String): LiveData { @@ -319,7 +333,6 @@ class ComposeViewModel return completedCaptioningLiveData } - fun searchAutocompleteSuggestions(token: String): List { when (token[0]) { '@' -> { @@ -370,14 +383,12 @@ class ComposeViewModel } } - override fun onCleared() { - for (uploadDisposable in mediaToDisposable.values) { - uploadDisposable.dispose() - } - super.onCleared() - } - fun setup(composeOptions: ComposeActivity.ComposeOptions?) { + + if (setupComplete.value == true) { + return + } + val preferredVisibility = accountManager.activeAccount!!.defaultPostPrivacy val replyVisibility = composeOptions?.replyVisibility ?: Status.Visibility.UNKNOWN @@ -385,6 +396,7 @@ class ComposeViewModel preferredVisibility.num.coerceAtLeast(replyVisibility.num)) inReplyToId = composeOptions?.inReplyToId + modifiedInitialState = composeOptions?.modifiedInitialState == true val contentWarning = composeOptions?.contentWarning @@ -396,10 +408,11 @@ class ComposeViewModel } // recreate media list - // when coming from SavedTootActivity val loadedDraftMediaUris = composeOptions?.mediaUrls val loadedDraftMediaDescriptions: List? = composeOptions?.mediaDescriptions + val draftAttachments = composeOptions?.draftAttachments if (loadedDraftMediaUris != null && loadedDraftMediaDescriptions != null) { + // when coming from SavedTootActivity loadedDraftMediaUris.zip(loadedDraftMediaDescriptions) .forEach { (uri, description) -> pickMedia(uri.toUri()).observeForever { errorOrItem -> @@ -408,23 +421,24 @@ class ComposeViewModel } } } + } else if (draftAttachments != null) { + // when coming from DraftActivity + draftAttachments.forEach { attachment -> pickMedia(attachment.uri, attachment.description) } } else composeOptions?.mediaAttachments?.forEach { a -> - // when coming from redraft + // when coming from redraft or ScheduledTootActivity val mediaType = when (a.type) { Attachment.Type.VIDEO, Attachment.Type.GIFV -> QueuedMedia.Type.VIDEO Attachment.Type.UNKNOWN, Attachment.Type.IMAGE -> QueuedMedia.Type.IMAGE Attachment.Type.AUDIO -> QueuedMedia.Type.AUDIO - else -> QueuedMedia.Type.IMAGE } addUploadedMedia(a.id, mediaType, a.url.toUri(), a.description) } - savedTootUid = composeOptions?.savedTootUid ?: 0 - scheduledTootUid = composeOptions?.scheduledTootUid + draftId = composeOptions?.draftId ?: 0 + scheduledTootId = composeOptions?.scheduledTootId startingText = composeOptions?.tootText - val tootVisibility = composeOptions?.visibility ?: Status.Visibility.UNKNOWN if (tootVisibility.num != Status.Visibility.UNKNOWN.num) { startingVisibility = tootVisibility @@ -441,7 +455,6 @@ class ComposeViewModel startingText = builder.toString() } - scheduledAt.value = composeOptions?.scheduledAt composeOptions?.sensitive?.let { markMediaAsSensitive.value = it } @@ -462,6 +475,13 @@ class ComposeViewModel scheduledAt.value = newScheduledAt } + override fun onCleared() { + for (uploadDisposable in mediaToDisposable.values) { + uploadDisposable.dispose() + } + super.onCleared() + } + private companion object { const val TAG = "ComposeViewModel" } @@ -479,4 +499,9 @@ data class ComposeInstanceParams( val pollMaxOptions: Int, val pollMaxLength: Int, val supportsScheduled: Boolean -) \ No newline at end of file +) + +/** + * Thrown when trying to add an image when video is already present or the other way around + */ +class VideoOrImageException : Exception() \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt index 1fa03d54b..8ff7dcf32 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt @@ -173,7 +173,13 @@ class MediaUploaderImpl( val body = MultipartBody.Part.createFormData("file", filename, fileBody) - val uploadDisposable = mastodonApi.uploadMedia(body) + val description = if (media.description != null) { + MultipartBody.Part.createFormData("description", media.description) + } else { + null + } + + val uploadDisposable = mastodonApi.uploadMedia(body, description) .subscribe({ attachment -> emitter.onNext(UploadEvent.FinishedEvent(attachment)) emitter.onComplete() diff --git a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt new file mode 100644 index 000000000..6f5f9005a --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt @@ -0,0 +1,159 @@ +/* Copyright 2021 Tusky Contributors + * + * This file is a part of Tusky. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU 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 General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tusky; if not, + * see . */ + +package com.keylesspalace.tusky.components.drafts + +import android.content.Context +import android.net.Uri +import android.util.Log +import android.webkit.MimeTypeMap +import androidx.core.content.FileProvider +import androidx.core.net.toUri +import com.keylesspalace.tusky.BuildConfig +import com.keylesspalace.tusky.db.AppDatabase +import com.keylesspalace.tusky.db.DraftAttachment +import com.keylesspalace.tusky.db.DraftEntity +import com.keylesspalace.tusky.entity.NewPoll +import com.keylesspalace.tusky.entity.Status +import com.keylesspalace.tusky.util.IOUtils +import io.reactivex.Completable +import io.reactivex.Single +import io.reactivex.schedulers.Schedulers +import java.io.File +import java.text.SimpleDateFormat +import java.util.* +import javax.inject.Inject + +class DraftHelper @Inject constructor( + val context: Context, + db: AppDatabase +) { + + private val draftDao = db.draftDao() + + fun saveDraft( + draftId: Int, + accountId: Long, + inReplyToId: String?, + content: String?, + contentWarning: String?, + sensitive: Boolean, + visibility: Status.Visibility, + mediaUris: List, + mediaDescriptions: List, + poll: NewPoll?, + failedToSend: Boolean + ): Completable { + return Single.fromCallable { + + val draftDirectory = context.getExternalFilesDir("Tusky") + + if (draftDirectory == null || !(draftDirectory.exists())) { + Log.e("DraftHelper", "Error obtaining directory to save media.") + throw Exception() + } + + val uris = mediaUris.map { uriString -> + uriString.toUri() + }.map { uri -> + if (uri.isNotInFolder(draftDirectory)) { + uri.copyToFolder(draftDirectory) + } else { + uri + } + } + + val types = uris.map { uri -> + val mimeType = context.contentResolver.getType(uri) + when (mimeType?.substring(0, mimeType.indexOf('/'))) { + "video" -> DraftAttachment.Type.VIDEO + "image" -> DraftAttachment.Type.IMAGE + "audio" -> DraftAttachment.Type.AUDIO + else -> throw IllegalStateException("unknown media type") + } + } + + val attachments: MutableList = mutableListOf() + for (i in mediaUris.indices) { + attachments.add( + DraftAttachment( + uriString = uris[i].toString(), + description = mediaDescriptions[i], + type = types[i] + ) + ) + } + + DraftEntity( + id = draftId, + accountId = accountId, + inReplyToId = inReplyToId, + content = content, + contentWarning = contentWarning, + sensitive = sensitive, + visibility = visibility, + attachments = attachments, + poll = poll, + failedToSend = failedToSend + ) + + }.flatMapCompletable { draft -> + draftDao.insertOrReplace(draft) + }.subscribeOn(Schedulers.io()) + } + + fun deleteDraftAndAttachments(draftId: Int): Completable { + return draftDao.find(draftId) + .flatMapCompletable { draft -> + deleteDraftAndAttachments(draft) + } + } + + fun deleteDraftAndAttachments(draft: DraftEntity): Completable { + return deleteAttachments(draft) + .andThen(draftDao.delete(draft.id)) + } + + fun deleteAttachments(draft: DraftEntity): Completable { + return Completable.fromCallable { + draft.attachments.forEach { attachment -> + if (context.contentResolver.delete(attachment.uri, null, null) == 0) { + Log.e("DraftHelper", "Did not delete file ${attachment.uriString}") + } + } + }.subscribeOn(Schedulers.io()) + } + + private fun Uri.isNotInFolder(folder: File): Boolean { + val filePath = path ?: return true + return File(filePath).parentFile == folder + } + + private fun Uri.copyToFolder(folder: File): Uri { + val contentResolver = context.contentResolver + + val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date()) + + val mimeType = contentResolver.getType(this) + val map = MimeTypeMap.getSingleton() + val fileExtension = map.getExtensionFromMimeType(mimeType) + + val filename = String.format("Tusky_Draft_Media_%s.%s", timeStamp, fileExtension) + val file = File(folder, filename) + IOUtils.copyToFile(contentResolver, this, file) + return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider", file) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftMediaAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftMediaAdapter.kt new file mode 100644 index 000000000..69403fdb5 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftMediaAdapter.kt @@ -0,0 +1,81 @@ +/* Copyright 2020 Tusky Contributors + * + * This file is a part of Tusky. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU 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 General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tusky; if not, + * see . */ + +package com.keylesspalace.tusky.components.drafts + +import android.view.ViewGroup +import android.widget.ImageView +import androidx.appcompat.widget.AppCompatImageView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.keylesspalace.tusky.R +import com.keylesspalace.tusky.db.DraftAttachment + +class DraftMediaAdapter( + private val attachmentClick: () -> Unit +) : ListAdapter( + object: DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: DraftAttachment, newItem: DraftAttachment): Boolean { + return oldItem == newItem + } + + override fun areContentsTheSame(oldItem: DraftAttachment, newItem: DraftAttachment): Boolean { + return oldItem == newItem + } + + } +) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DraftMediaViewHolder { + return DraftMediaViewHolder(AppCompatImageView(parent.context)) + } + + override fun onBindViewHolder(holder: DraftMediaViewHolder, position: Int) { + getItem(position)?.let { attachment -> + if (attachment.type == DraftAttachment.Type.AUDIO) { + holder.imageView.setImageResource(R.drawable.ic_music_box_preview_24dp) + } else { + Glide.with(holder.itemView.context) + .load(attachment.uri) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .dontAnimate() + .into(holder.imageView) + } + } + } + + inner class DraftMediaViewHolder(val imageView: ImageView) + : RecyclerView.ViewHolder(imageView) { + init { + val thumbnailViewSize = + imageView.context.resources.getDimensionPixelSize(R.dimen.compose_media_preview_size) + val layoutParams = ConstraintLayout.LayoutParams(thumbnailViewSize, thumbnailViewSize) + val margin = itemView.context.resources + .getDimensionPixelSize(R.dimen.compose_media_preview_margin) + val marginBottom = itemView.context.resources + .getDimensionPixelSize(R.dimen.compose_media_preview_margin_bottom) + layoutParams.setMargins(margin, 0, margin, marginBottom) + imageView.layoutParams = layoutParams + imageView.scaleType = ImageView.ScaleType.CENTER_CROP + imageView.setOnClickListener { + attachmentClick() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt new file mode 100644 index 000000000..ddf8a8385 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt @@ -0,0 +1,197 @@ +/* Copyright 2020 Tusky Contributors + * + * This file is a part of Tusky. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU 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 General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tusky; if not, + * see . */ + +package com.keylesspalace.tusky.components.drafts + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.widget.LinearLayout +import android.widget.Toast +import androidx.activity.viewModels +import androidx.lifecycle.Lifecycle +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.snackbar.Snackbar +import com.keylesspalace.tusky.BaseActivity +import com.keylesspalace.tusky.R +import com.keylesspalace.tusky.SavedTootActivity +import com.keylesspalace.tusky.components.compose.ComposeActivity +import com.keylesspalace.tusky.databinding.ActivityDraftsBinding +import com.keylesspalace.tusky.db.DraftEntity +import com.keylesspalace.tusky.di.ViewModelFactory +import com.keylesspalace.tusky.util.hide +import com.keylesspalace.tusky.util.show +import com.uber.autodispose.android.lifecycle.autoDispose +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import retrofit2.HttpException +import javax.inject.Inject + +class DraftsActivity : BaseActivity(), DraftActionListener { + + @Inject + lateinit var viewModelFactory: ViewModelFactory + + private val viewModel: DraftsViewModel by viewModels { viewModelFactory } + + private lateinit var binding: ActivityDraftsBinding + private lateinit var bottomSheet: BottomSheetBehavior + + private var oldDraftsButton: MenuItem? = null + + override fun onCreate(savedInstanceState: Bundle?) { + + super.onCreate(savedInstanceState) + + binding = ActivityDraftsBinding.inflate(layoutInflater) + setContentView(binding.root) + + setSupportActionBar(binding.includedToolbar.toolbar) + supportActionBar?.apply { + title = getString(R.string.title_drafts) + setDisplayHomeAsUpEnabled(true) + setDisplayShowHomeEnabled(true) + } + + binding.draftsErrorMessageView.setup(R.drawable.elephant_friend_empty, R.string.no_saved_status) + + val adapter = DraftsAdapter(this) + + binding.draftsRecyclerView.adapter = adapter + binding.draftsRecyclerView.layoutManager = LinearLayoutManager(this) + binding.draftsRecyclerView.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL)) + + bottomSheet = BottomSheetBehavior.from(binding.bottomSheet.root) + + viewModel.drafts.observe(this) { draftList -> + if (draftList.isEmpty()) { + binding.draftsRecyclerView.hide() + binding.draftsErrorMessageView.show() + } else { + binding.draftsRecyclerView.show() + binding.draftsErrorMessageView.hide() + adapter.submitList(draftList) + } + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.drafts, menu) + oldDraftsButton = menu.findItem(R.id.action_old_drafts) + viewModel.showOldDraftsButton() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(this, Lifecycle.Event.ON_DESTROY) + .subscribe { showOldDraftsButton -> + oldDraftsButton?.isVisible = showOldDraftsButton + } + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + android.R.id.home -> { + onBackPressed() + return true + } + R.id.action_old_drafts -> { + val intent = Intent(this, SavedTootActivity::class.java) + startActivityWithSlideInAnimation(intent) + return true + } + } + return super.onOptionsItemSelected(item) + } + + override fun onOpenDraft(draft: DraftEntity) { + + if (draft.inReplyToId != null) { + bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED + viewModel.getToot(draft.inReplyToId) + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(this) + .subscribe({ status -> + val composeOptions = ComposeActivity.ComposeOptions( + draftId = draft.id, + tootText = draft.content, + contentWarning = draft.contentWarning, + inReplyToId = draft.inReplyToId, + replyingStatusContent = status.content.toString(), + replyingStatusAuthor = status.account.localUsername, + draftAttachments = draft.attachments, + poll = draft.poll, + sensitive = draft.sensitive, + visibility = draft.visibility + ) + + bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN + + startActivity(ComposeActivity.startIntent(this, composeOptions)) + + }, { throwable -> + + bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN + + Log.w(TAG, "failed loading reply information", throwable) + + if (throwable is HttpException && throwable.code() == 404) { + // the original status to which a reply was drafted has been deleted + // let's open the ComposeActivity without reply information + Toast.makeText(this, getString(R.string.drafts_toot_reply_removed), Toast.LENGTH_LONG).show() + openDraftWithoutReply(draft) + } else { + Snackbar.make(binding.root, getString(R.string.drafts_failed_loading_reply), Snackbar.LENGTH_SHORT) + .show() + } + }) + } else { + openDraftWithoutReply(draft) + } + } + + private fun openDraftWithoutReply(draft: DraftEntity) { + val composeOptions = ComposeActivity.ComposeOptions( + draftId = draft.id, + tootText = draft.content, + contentWarning = draft.contentWarning, + draftAttachments = draft.attachments, + poll = draft.poll, + sensitive = draft.sensitive, + visibility = draft.visibility + ) + + startActivity(ComposeActivity.startIntent(this, composeOptions)) + } + + override fun onDeleteDraft(draft: DraftEntity) { + viewModel.deleteDraft(draft) + Snackbar.make(binding.root, getString(R.string.draft_deleted), Snackbar.LENGTH_LONG) + .setAction(R.string.action_undo) { + viewModel.restoreDraft(draft) + } + .show() + } + + companion object { + const val TAG = "DraftsActivity" + + fun newIntent(context: Context) = Intent(context, DraftsActivity::class.java) + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsAdapter.kt new file mode 100644 index 000000000..5dfbceac8 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsAdapter.kt @@ -0,0 +1,92 @@ +/* Copyright 2021 Tusky Contributors + * + * This file is a part of Tusky. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU 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 General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tusky; if not, + * see . */ + +package com.keylesspalace.tusky.components.drafts + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.paging.PagedListAdapter +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.keylesspalace.tusky.databinding.ItemDraftBinding +import com.keylesspalace.tusky.db.DraftEntity +import com.keylesspalace.tusky.util.BindingViewHolder +import com.keylesspalace.tusky.util.hide +import com.keylesspalace.tusky.util.show +import com.keylesspalace.tusky.util.visible + +interface DraftActionListener { + fun onOpenDraft(draft: DraftEntity) + fun onDeleteDraft(draft: DraftEntity) +} + +class DraftsAdapter( + private val listener: DraftActionListener +) : PagedListAdapter>( + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: DraftEntity, newItem: DraftEntity): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: DraftEntity, newItem: DraftEntity): Boolean { + return oldItem == newItem + } + } +) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder { + + val binding = ItemDraftBinding.inflate(LayoutInflater.from(parent.context), parent, false) + + val viewHolder = BindingViewHolder(binding) + + binding.draftMediaPreview.layoutManager = LinearLayoutManager(binding.root.context, RecyclerView.HORIZONTAL, false) + binding.draftMediaPreview.adapter = DraftMediaAdapter { + getItem(viewHolder.adapterPosition)?.let { draft -> + listener.onOpenDraft(draft) + } + } + + return viewHolder + } + + override fun onBindViewHolder(holder: BindingViewHolder, position: Int) { + getItem(position)?.let { draft -> + holder.binding.root.setOnClickListener { + listener.onOpenDraft(draft) + } + holder.binding.deleteButton.setOnClickListener { + listener.onDeleteDraft(draft) + } + holder.binding.draftSendingInfo.visible(draft.failedToSend) + + holder.binding.contentWarning.visible(!draft.contentWarning.isNullOrEmpty()) + holder.binding.contentWarning.text = draft.contentWarning + holder.binding.content.text = draft.content + + holder.binding.draftMediaPreview.visible(draft.attachments.isNotEmpty()) + (holder.binding.draftMediaPreview.adapter as DraftMediaAdapter).submitList(draft.attachments) + + if (draft.poll != null) { + holder.binding.draftPoll.show() + holder.binding.draftPoll.setPoll(draft.poll) + } else { + holder.binding.draftPoll.hide() + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsViewModel.kt new file mode 100644 index 000000000..9eca963aa --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsViewModel.kt @@ -0,0 +1,69 @@ +/* Copyright 2020 Tusky Contributors + * + * This file is a part of Tusky. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU 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 General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tusky; if not, + * see . */ + +package com.keylesspalace.tusky.components.drafts + +import androidx.lifecycle.ViewModel +import androidx.paging.toLiveData +import com.keylesspalace.tusky.db.AccountManager +import com.keylesspalace.tusky.db.AppDatabase +import com.keylesspalace.tusky.db.DraftEntity +import com.keylesspalace.tusky.entity.Status +import com.keylesspalace.tusky.network.MastodonApi +import io.reactivex.Observable +import io.reactivex.Single +import javax.inject.Inject + +class DraftsViewModel @Inject constructor( + val database: AppDatabase, + val accountManager: AccountManager, + val api: MastodonApi, + val draftHelper: DraftHelper +) : ViewModel() { + + val drafts = database.draftDao().loadDrafts(accountManager.activeAccount?.id!!).toLiveData(pageSize = 20) + + private val deletedDrafts: MutableList = mutableListOf() + + fun showOldDraftsButton(): Observable { + return database.tootDao().savedTootCount() + .map { count -> count > 0 } + } + + fun deleteDraft(draft: DraftEntity) { + // this does not immediately delete media files to avoid unnecessary file operations + // in case the user decides to restore the draft + database.draftDao().delete(draft.id) + .subscribe() + deletedDrafts.add(draft) + } + + fun restoreDraft(draft: DraftEntity) { + database.draftDao().insertOrReplace(draft) + .subscribe() + deletedDrafts.remove(draft) + } + + fun getToot(tootId: String): Single { + return api.statusSingle(tootId) + } + + override fun onCleared() { + deletedDrafts.forEach { + draftHelper.deleteAttachments(it).subscribe() + } + } + +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt index 18b04df52..f0944a347 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt @@ -120,7 +120,7 @@ class ScheduledTootActivity : BaseActivity(), ScheduledTootActionListener, Injec override fun edit(item: ScheduledStatus) { val intent = ComposeActivity.startIntent(this, ComposeActivity.ComposeOptions( - scheduledTootUid = item.id, + scheduledTootId = item.id, tootText = item.params.text, contentWarning = item.params.spoilerText, mediaAttachments = item.mediaAttachments, diff --git a/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java b/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java index 5c5b7cb62..d35fd3891 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java +++ b/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java @@ -28,9 +28,9 @@ import com.keylesspalace.tusky.components.conversation.ConversationEntity; * DB version & declare DAO */ -@Database(entities = {TootEntity.class, AccountEntity.class, InstanceEntity.class, TimelineStatusEntity.class, +@Database(entities = { TootEntity.class, DraftEntity.class, AccountEntity.class, InstanceEntity.class, TimelineStatusEntity.class, TimelineAccountEntity.class, ConversationEntity.class - }, version = 24) + }, version = 25) public abstract class AppDatabase extends RoomDatabase { public abstract TootDao tootDao(); @@ -38,6 +38,7 @@ public abstract class AppDatabase extends RoomDatabase { public abstract InstanceDao instanceDao(); public abstract ConversationsDao conversationDao(); public abstract TimelineDao timelineDao(); + public abstract DraftDao draftDao(); public static final Migration MIGRATION_2_3 = new Migration(2, 3) { @Override @@ -46,7 +47,6 @@ public abstract class AppDatabase extends RoomDatabase { database.execSQL("INSERT INTO TootEntity2 SELECT * FROM TootEntity;"); database.execSQL("DROP TABLE TootEntity;"); database.execSQL("ALTER TABLE TootEntity2 RENAME TO TootEntity;"); - } }; @@ -347,4 +347,22 @@ public abstract class AppDatabase extends RoomDatabase { } }; + public static final Migration MIGRATION_24_25 = new Migration(24, 25) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase database) { + database.execSQL( + "CREATE TABLE IF NOT EXISTS `DraftEntity` (" + + "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " + + "`accountId` INTEGER NOT NULL, " + + "`inReplyToId` TEXT," + + "`content` TEXT," + + "`contentWarning` TEXT," + + "`sensitive` INTEGER NOT NULL," + + "`visibility` INTEGER NOT NULL," + + "`attachments` TEXT NOT NULL," + + "`poll` TEXT," + + "`failedToSend` INTEGER NOT NULL)" + ); + } + }; } diff --git a/app/src/main/java/com/keylesspalace/tusky/db/Converters.kt b/app/src/main/java/com/keylesspalace/tusky/db/Converters.kt index 3492deda8..1b1f94f3d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/Converters.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/Converters.kt @@ -24,10 +24,7 @@ import com.google.gson.reflect.TypeToken import com.keylesspalace.tusky.TabData import com.keylesspalace.tusky.components.conversation.ConversationAccountEntity import com.keylesspalace.tusky.createTabDataFromId -import com.keylesspalace.tusky.entity.Attachment -import com.keylesspalace.tusky.entity.Emoji -import com.keylesspalace.tusky.entity.Poll -import com.keylesspalace.tusky.entity.Status +import com.keylesspalace.tusky.entity.* import com.keylesspalace.tusky.json.SpannedTypeAdapter import com.keylesspalace.tusky.util.trimTrailingWhitespace import java.net.URLDecoder @@ -151,4 +148,23 @@ class Converters { return gson.fromJson(pollJson, Poll::class.java) } -} \ No newline at end of file + @TypeConverter + fun newPollToJson(newPoll: NewPoll?): String? { + return gson.toJson(newPoll) + } + + @TypeConverter + fun jsonToNewPoll(newPollJson: String?): NewPoll? { + return gson.fromJson(newPollJson, NewPoll::class.java) + } + + @TypeConverter + fun draftAttachmentListToJson(draftAttachments: List?): String? { + return gson.toJson(draftAttachments) + } + + @TypeConverter + fun jsonToDraftAttachmentList(draftAttachmentListJson: String?): List? { + return gson.fromJson(draftAttachmentListJson, object : TypeToken>() {}.type) + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt b/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt new file mode 100644 index 000000000..105fd7c5a --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt @@ -0,0 +1,40 @@ +/* Copyright 2020 Tusky Contributors + * + * This file is a part of Tusky. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU 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 General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tusky; if not, + * see . */ + +package com.keylesspalace.tusky.db + +import androidx.paging.DataSource +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import io.reactivex.Completable +import io.reactivex.Single + +@Dao +interface DraftDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertOrReplace(draft: DraftEntity): Completable + + @Query("SELECT * FROM DraftEntity WHERE accountId = :accountId ORDER BY id ASC") + fun loadDrafts(accountId: Long): DataSource.Factory + + @Query("DELETE FROM DraftEntity WHERE id = :id") + fun delete(id: Int): Completable + + @Query("SELECT * FROM DraftEntity WHERE id = :id") + fun find(id: Int): Single +} diff --git a/app/src/main/java/com/keylesspalace/tusky/db/DraftEntity.kt b/app/src/main/java/com/keylesspalace/tusky/db/DraftEntity.kt new file mode 100644 index 000000000..be1eca589 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/db/DraftEntity.kt @@ -0,0 +1,55 @@ +/* Copyright 2020 Tusky Contributors + * + * This file is a part of Tusky. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU 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 General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tusky; if not, + * see . */ + +package com.keylesspalace.tusky.db + +import android.net.Uri +import android.os.Parcelable +import androidx.core.net.toUri +import androidx.room.Entity +import androidx.room.PrimaryKey +import androidx.room.TypeConverters +import com.keylesspalace.tusky.entity.NewPoll +import com.keylesspalace.tusky.entity.Status +import kotlinx.android.parcel.Parcelize + +@Entity +@TypeConverters(Converters::class) +data class DraftEntity( + @PrimaryKey(autoGenerate = true) val id: Int = 0, + val accountId: Long, + val inReplyToId: String?, + val content: String?, + val contentWarning: String?, + val sensitive: Boolean, + val visibility: Status.Visibility, + val attachments: List, + val poll: NewPoll?, + val failedToSend: Boolean +) + +@Parcelize +data class DraftAttachment( + val uriString: String, + val description: String?, + val type: Type +): Parcelable { + val uri: Uri + get() = uriString.toUri() + + enum class Type { + IMAGE, VIDEO, AUDIO; + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/db/TimelineStatusEntity.kt b/app/src/main/java/com/keylesspalace/tusky/db/TimelineStatusEntity.kt index da98cb4b3..296111d30 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/TimelineStatusEntity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/TimelineStatusEntity.kt @@ -26,7 +26,7 @@ import com.keylesspalace.tusky.entity.Status // Avoiding rescanning status table when accounts table changes. Recommended by Room(c). indices = [Index("authorServerId", "timelineUserId")] ) -@TypeConverters(TootEntity.Converters::class) +@TypeConverters(Converters::class) data class TimelineStatusEntity( val serverId: String, // id never flips: we need it for sorting so it's a real id val url: String?, diff --git a/app/src/main/java/com/keylesspalace/tusky/db/TootDao.java b/app/src/main/java/com/keylesspalace/tusky/db/TootDao.java index c121e1705..f46c2753a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/TootDao.java +++ b/app/src/main/java/com/keylesspalace/tusky/db/TootDao.java @@ -16,12 +16,12 @@ package com.keylesspalace.tusky.db; import androidx.room.Dao; -import androidx.room.Insert; -import androidx.room.OnConflictStrategy; import androidx.room.Query; import java.util.List; +import io.reactivex.Observable; + /** * Created by cto3543 on 28/06/2017. * @@ -30,8 +30,6 @@ import java.util.List; @Dao public interface TootDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - void insertOrReplace(TootEntity users); @Query("SELECT * FROM TootEntity ORDER BY uid DESC") List loadAll(); @@ -41,4 +39,7 @@ public interface TootDao { @Query("SELECT * FROM TootEntity WHERE uid = :uid") TootEntity find(int uid); -} + + @Query("SELECT COUNT(*) FROM TootEntity") + Observable savedTootCount(); +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt index 0257c28f6..2e82d6407 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt @@ -18,6 +18,7 @@ package com.keylesspalace.tusky.di import com.keylesspalace.tusky.* import com.keylesspalace.tusky.components.announcements.AnnouncementsActivity import com.keylesspalace.tusky.components.compose.ComposeActivity +import com.keylesspalace.tusky.components.drafts.DraftsActivity import com.keylesspalace.tusky.components.instancemute.InstanceListActivity import com.keylesspalace.tusky.components.preference.PreferencesActivity import com.keylesspalace.tusky.components.report.ReportActivity @@ -107,4 +108,7 @@ abstract class ActivitiesModule { @ContributesAndroidInjector abstract fun contributesAnnouncementsActivity(): AnnouncementsActivity + + @ContributesAndroidInjector + abstract fun contributesDraftActivity(): DraftsActivity } diff --git a/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt index 7e86bbac5..1137b12b0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt @@ -80,7 +80,7 @@ class AppModule { AppDatabase.MIGRATION_13_14, AppDatabase.MIGRATION_14_15, AppDatabase.MIGRATION_15_16, AppDatabase.MIGRATION_16_17, AppDatabase.MIGRATION_17_18, AppDatabase.MIGRATION_18_19, AppDatabase.MIGRATION_19_20, AppDatabase.MIGRATION_20_21, AppDatabase.MIGRATION_21_22, - AppDatabase.MIGRATION_22_23, AppDatabase.MIGRATION_23_24) + AppDatabase.MIGRATION_22_23, AppDatabase.MIGRATION_23_24, AppDatabase.MIGRATION_24_25) .build() } diff --git a/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt b/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt index c461012db..ce83deda8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.ViewModelProvider import com.keylesspalace.tusky.components.announcements.AnnouncementsViewModel import com.keylesspalace.tusky.components.compose.ComposeViewModel import com.keylesspalace.tusky.components.conversation.ConversationsViewModel +import com.keylesspalace.tusky.components.drafts.DraftsViewModel import com.keylesspalace.tusky.components.report.ReportViewModel import com.keylesspalace.tusky.components.scheduled.ScheduledTootViewModel import com.keylesspalace.tusky.components.search.SearchViewModel @@ -91,5 +92,10 @@ abstract class ViewModelModule { @ViewModelKey(AnnouncementsViewModel::class) internal abstract fun announcementsViewModel(viewModel: AnnouncementsViewModel): ViewModel + @Binds + @IntoMap + @ViewModelKey(DraftsViewModel::class) + internal abstract fun draftsViewModel(viewModel: DraftsViewModel): ViewModel + //Add more ViewModels here } diff --git a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt index 7b79e1b54..58caec85e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt +++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt @@ -124,7 +124,8 @@ interface MastodonApi { @Multipart @POST("api/v1/media") fun uploadMedia( - @Part file: MultipartBody.Part + @Part file: MultipartBody.Part, + @Part description: MultipartBody.Part? = null ): Single @FormUrlEncoded @@ -147,6 +148,11 @@ interface MastodonApi { @Path("id") statusId: String ): Call + @GET("api/v1/statuses/{id}") + fun statusSingle( + @Path("id") statusId: String + ): Single + @GET("api/v1/statuses/{id}/context") fun statusContext( @Path("id") statusId: String diff --git a/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt b/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt index f8bf81b30..911f58c1b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt +++ b/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt @@ -60,7 +60,6 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() { val notificationManager = NotificationManagerCompat.from(context) - if (intent.action == NotificationHelper.REPLY_ACTION) { val message = getReplyMessage(intent) @@ -89,22 +88,23 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() { val sendIntent = SendTootService.sendTootIntent( context, TootToSend( - text, - spoiler, - visibility.serverString(), - false, - emptyList(), - emptyList(), - emptyList(), - null, - citedStatusId, - null, - null, - null, - null, account.id, - 0, - randomAlphanumericString(16), - 0 + text = text, + warningText = spoiler, + visibility = visibility.serverString(), + sensitive = false, + mediaIds = emptyList(), + mediaUris = emptyList(), + mediaDescriptions = emptyList(), + scheduledAt = null, + inReplyToId = citedStatusId, + poll = null, + replyingStatusContent = null, + replyingStatusAuthorUsername = null, + accountId = account.id, + savedTootUid = -1, + draftId = -1, + idempotencyKey = randomAlphanumericString(16), + retries = 0 ) ) diff --git a/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt b/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt index 328265b5e..b54a941b1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt +++ b/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt @@ -18,6 +18,7 @@ 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.components.drafts.DraftHelper import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.db.AppDatabase import com.keylesspalace.tusky.di.Injectable @@ -46,7 +47,8 @@ class SendTootService : Service(), Injectable { lateinit var eventHub: EventHub @Inject lateinit var database: AppDatabase - + @Inject + lateinit var draftHelper: DraftHelper @Inject lateinit var saveTootHelper: SaveTootHelper @@ -163,6 +165,10 @@ class SendTootService : Service(), Injectable { if (tootToSend.savedTootUid != 0) { saveTootHelper.deleteDraft(tootToSend.savedTootUid) } + if (tootToSend.draftId != 0) { + draftHelper.deleteDraftAndAttachments(tootToSend.draftId) + .subscribe() + } if (scheduled) { response.body()?.let(::StatusScheduledEvent)?.let(eventHub::dispatch) @@ -245,17 +251,19 @@ class SendTootService : Service(), Injectable { private fun saveTootToDrafts(toot: TootToSend) { - saveTootHelper.saveToot(toot.text, - toot.warningText, - toot.savedJsonUrls, - toot.mediaUris, - toot.mediaDescriptions, - toot.savedTootUid, - toot.inReplyToId, - toot.replyingStatusContent, - toot.replyingStatusAuthorUsername, - Status.Visibility.byString(toot.visibility), - toot.poll) + draftHelper.saveDraft( + draftId = toot.draftId, + accountId = toot.accountId, + inReplyToId = toot.inReplyToId, + content = toot.text, + contentWarning = toot.warningText, + sensitive = toot.sensitive, + visibility = Status.Visibility.byString(toot.visibility), + mediaUris = toot.mediaUris, + mediaDescriptions = toot.mediaDescriptions, + poll = toot.poll, + failedToSend = true + ).subscribe() } private fun cancelSendingIntent(tootId: Int): PendingIntent { @@ -323,9 +331,9 @@ data class TootToSend( val poll: NewPoll?, val replyingStatusContent: String?, val replyingStatusAuthorUsername: String?, - val savedJsonUrls: List?, val accountId: Long, val savedTootUid: Int, + val draftId: Int, val idempotencyKey: String, var retries: Int ) : Parcelable diff --git a/app/src/main/java/com/keylesspalace/tusky/util/BindingViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/util/BindingViewHolder.kt new file mode 100644 index 000000000..14aee81b5 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/util/BindingViewHolder.kt @@ -0,0 +1,8 @@ +package com.keylesspalace.tusky.util + +import androidx.recyclerview.widget.RecyclerView +import androidx.viewbinding.ViewBinding + +class BindingViewHolder( + val binding: T +) : RecyclerView.ViewHolder(binding.root) diff --git a/app/src/main/java/com/keylesspalace/tusky/util/RxAwareViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/util/RxAwareViewModel.kt index 2ad4b825d..c78b0f787 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/RxAwareViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/RxAwareViewModel.kt @@ -1,5 +1,6 @@ package com.keylesspalace.tusky.util +import androidx.annotation.CallSuper import androidx.lifecycle.ViewModel import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.Disposable @@ -9,6 +10,7 @@ open class RxAwareViewModel : ViewModel() { fun Disposable.autoDispose() = disposables.add(this) + @CallSuper override fun onCleared() { super.onCleared() disposables.clear() diff --git a/app/src/main/java/com/keylesspalace/tusky/util/SaveTootHelper.java b/app/src/main/java/com/keylesspalace/tusky/util/SaveTootHelper.java index 690098309..29693550d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/SaveTootHelper.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/SaveTootHelper.java @@ -1,33 +1,18 @@ package com.keylesspalace.tusky.util; -import android.annotation.SuppressLint; -import android.content.ContentResolver; import android.content.Context; import android.net.Uri; -import android.os.AsyncTask; -import android.text.TextUtils; import android.util.Log; -import android.webkit.MimeTypeMap; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.FileProvider; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; -import com.keylesspalace.tusky.BuildConfig; import com.keylesspalace.tusky.db.AppDatabase; import com.keylesspalace.tusky.db.TootDao; import com.keylesspalace.tusky.db.TootEntity; -import com.keylesspalace.tusky.entity.NewPoll; -import com.keylesspalace.tusky.entity.Status; -import java.io.File; -import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Locale; import javax.inject.Inject; @@ -45,61 +30,6 @@ public final class SaveTootHelper { this.context = context; } - @SuppressLint("StaticFieldLeak") - public boolean saveToot(@NonNull String content, - @NonNull String contentWarning, - @Nullable List savedJsonUrls, - @NonNull List mediaUris, - @NonNull List mediaDescriptions, - int savedTootUid, - @Nullable String inReplyToId, - @Nullable String replyingStatusContent, - @Nullable String replyingStatusAuthorUsername, - @NonNull Status.Visibility statusVisibility, - @Nullable NewPoll poll) { - - if (TextUtils.isEmpty(content) && mediaUris.isEmpty() && poll == null) { - return false; - } - - // Get any existing file's URIs. - - String mediaUrlsSerialized = null; - String mediaDescriptionsSerialized = null; - - if (!ListUtils.isEmpty(mediaUris)) { - List savedList = saveMedia(mediaUris, savedJsonUrls); - if (!ListUtils.isEmpty(savedList)) { - mediaUrlsSerialized = gson.toJson(savedList); - if (!ListUtils.isEmpty(savedJsonUrls)) { - deleteMedia(setDifference(savedJsonUrls, savedList)); - } - } else { - return false; - } - mediaDescriptionsSerialized = gson.toJson(mediaDescriptions); - } else if (!ListUtils.isEmpty(savedJsonUrls)) { - /* If there were URIs in the previous draft, but they've now been removed, those files - * can be deleted. */ - deleteMedia(savedJsonUrls); - } - final TootEntity toot = new TootEntity(savedTootUid, content, mediaUrlsSerialized, mediaDescriptionsSerialized, contentWarning, - inReplyToId, - replyingStatusContent, - replyingStatusAuthorUsername, - statusVisibility, - poll); - - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - tootDao.insertOrReplace(toot); - return null; - } - }.execute(); - return true; - } - public void deleteDraft(int tootId) { TootEntity item = tootDao.find(tootId); if (item != null) { @@ -124,82 +54,4 @@ public final class SaveTootHelper { tootDao.delete(item.getUid()); } - @Nullable - private List saveMedia(@NonNull List mediaUris, - @Nullable List existingUris) { - - File directory = context.getExternalFilesDir("Tusky"); - - if (directory == null || !(directory.exists())) { - Log.e(TAG, "Error obtaining directory to save media."); - return null; - } - - ContentResolver contentResolver = context.getContentResolver(); - ArrayList filesSoFar = new ArrayList<>(); - ArrayList results = new ArrayList<>(); - for (String mediaUri : mediaUris) { - /* If the media was already saved in a previous draft, there's no need to save another - * copy, just add the existing URI to the results. */ - if (existingUris != null) { - int index = existingUris.indexOf(mediaUri); - if (index != -1) { - results.add(mediaUri); - continue; - } - } - // Otherwise, save the media. - - Uri uri = Uri.parse(mediaUri); - - String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date()); - - String mimeType = contentResolver.getType(uri); - MimeTypeMap map = MimeTypeMap.getSingleton(); - String fileExtension = map.getExtensionFromMimeType(mimeType); - String filename = String.format("Tusky_Draft_Media_%s.%s", timeStamp, fileExtension); - File file = new File(directory, filename); - filesSoFar.add(file); - boolean copied = IOUtils.copyToFile(contentResolver, uri, file); - if (!copied) { - /* If any media files were created in prior iterations, delete those before - * returning. */ - for (File earlierFile : filesSoFar) { - boolean deleted = earlierFile.delete(); - if (!deleted) { - Log.i(TAG, "Could not delete the file " + earlierFile.toString()); - } - } - return null; - } - Uri resultUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider", file); - results.add(resultUri.toString()); - } - return results; - } - - private void deleteMedia(List mediaUris) { - for (String uriString : mediaUris) { - Uri uri = Uri.parse(uriString); - if (context.getContentResolver().delete(uri, null, null) == 0) { - Log.e(TAG, String.format("Did not delete file %s.", uriString)); - } - } - } - - /** - * A∖B={x∈A|x∉B} - * - * @return all elements of set A that are not in set B. - */ - private static List setDifference(List a, List b) { - List c = new ArrayList<>(); - for (String s : a) { - if (!b.contains(s)) { - c.add(s); - } - } - return c; - } - -} +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_alert_circle.xml b/app/src/main/res/drawable/ic_alert_circle.xml new file mode 100644 index 000000000..4c894f0dc --- /dev/null +++ b/app/src/main/res/drawable/ic_alert_circle.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_notebook.xml b/app/src/main/res/drawable/ic_notebook.xml index 2395cd141..93ff78919 100644 --- a/app/src/main/res/drawable/ic_notebook.xml +++ b/app/src/main/res/drawable/ic_notebook.xml @@ -4,5 +4,5 @@ android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + \ No newline at end of file diff --git a/app/src/main/res/layout-sw640dp/fragment_view_thread.xml b/app/src/main/res/layout-sw640dp/fragment_view_thread.xml index 52de2b95a..150f0860c 100644 --- a/app/src/main/res/layout-sw640dp/fragment_view_thread.xml +++ b/app/src/main/res/layout-sw640dp/fragment_view_thread.xml @@ -1,8 +1,10 @@ + android:background="?attr/windowBackgroundColor" + tools:viewBindingIgnore="true"> + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_view_thread.xml b/app/src/main/res/layout/fragment_view_thread.xml index 433f1ed0f..806d420a4 100644 --- a/app/src/main/res/layout/fragment_view_thread.xml +++ b/app/src/main/res/layout/fragment_view_thread.xml @@ -1,9 +1,11 @@ + android:layout_gravity="top" + tools:viewBindingIgnore="true"> + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/toolbar_basic.xml b/app/src/main/res/layout/toolbar_basic.xml index 71039105f..47bd2d90f 100644 --- a/app/src/main/res/layout/toolbar_basic.xml +++ b/app/src/main/res/layout/toolbar_basic.xml @@ -1,19 +1,16 @@ - + - + android:layout_height="?attr/actionBarSize" /> - - - - - \ No newline at end of file + diff --git a/app/src/main/res/menu/drafts.xml b/app/src/main/res/menu/drafts.xml new file mode 100644 index 000000000..bbc9202f4 --- /dev/null +++ b/app/src/main/res/menu/drafts.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 835a32f8e..6f4b1cee7 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -36,7 +36,7 @@ الحسابات المحظورة طلبات المتابعة عدل ملفك التعريفي - المسودات + المسودات الرّخص \@%s شارَكَه %s diff --git a/app/src/main/res/values-ber/strings.xml b/app/src/main/res/values-ber/strings.xml index 2e0412ca4..9726d4ff0 100644 --- a/app/src/main/res/values-ber/strings.xml +++ b/app/src/main/res/values-ber/strings.xml @@ -16,7 +16,7 @@ ⵏⴰⴸⵉ ⵣⵔⴻⴳ ⴰⵎⴰⵖⵏⵓ ⴼⴼⴻⵖ - ⵉⵔⴻⵡⵡⴰⵢⴻⵏ + ⵉⵔⴻⵡⵡⴰⵢⴻⵏ ⵉⵙⵎⴻⵏⵢⵉⴼⴻⵏ ⵓⵖⴰⵍ ⴽⴻⵎⵎⴻⵍ diff --git a/app/src/main/res/values-bn-rBD/strings.xml b/app/src/main/res/values-bn-rBD/strings.xml index 2413319b5..634f0d8f3 100644 --- a/app/src/main/res/values-bn-rBD/strings.xml +++ b/app/src/main/res/values-bn-rBD/strings.xml @@ -292,7 +292,7 @@ মিডিয়া লুকানো সংবেদনশীল কন্টেন্ট লাইসেন্সগুলি - খসড়াগুলো + খসড়াগুলো আপনার প্রোফাইল সম্পাদনা করুন অনুরোধ অনুসরণ করুন অবরুদ্ধ ব্যবহারকারী diff --git a/app/src/main/res/values-bn-rIN/strings.xml b/app/src/main/res/values-bn-rIN/strings.xml index e578655ad..ae3ae2f21 100644 --- a/app/src/main/res/values-bn-rIN/strings.xml +++ b/app/src/main/res/values-bn-rIN/strings.xml @@ -36,7 +36,7 @@ অবরুদ্ধ ব্যবহারকারী অনুরোধ অনুসরণ করুন আপনার প্রোফাইল সম্পাদনা করুন - খসড়াগুলো + খসড়াগুলো লাইসেন্সগুলি \@%s %s সমর্থন দিয়েছে diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index a769dbe80..9748a4819 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -29,7 +29,7 @@ Usuaris blocats Peticions de seguiment Edita el perfil - Esborranys + Esborranys \@%s %s tootejat Contingut sensible diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 867b3ec2c..bac6fe1a6 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -36,7 +36,7 @@ Blokovaní uživatelé Žádosti o sledování Upravit váš profil - Koncepty + Koncepty Licence \@%s %s boostnul/a diff --git a/app/src/main/res/values-cy/strings.xml b/app/src/main/res/values-cy/strings.xml index a322a5df6..b41bbd6f9 100644 --- a/app/src/main/res/values-cy/strings.xml +++ b/app/src/main/res/values-cy/strings.xml @@ -32,7 +32,7 @@ Defnyddwyr wedi\'u blocio Dilyn ceisiadau Golygu\'ch Proffil - Drafftiau + Drafftiau Trwyddedau %s wedi\'u hybu Cynnwys sensitif diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 6218bbbfe..decfff34a 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -36,7 +36,7 @@ Blockierte Profile Folgeanfragen Dein Profil bearbeiten - Entwürfe + Entwürfe Lizenzen \@%s %s teilte diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index e2e011087..34b5a5bc3 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -36,7 +36,7 @@ Blokitaj uzantoj Petoj de sekvado Redakti vian profilon - Malnetoj + Malnetoj Permesiloj \@%s %s diskonigis diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 089552800..2b2633a62 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -36,7 +36,7 @@ Bloqueados Solicitudes Editar tu perfil - Borradores + Borradores Licencias \@%s %s compartió diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index f4eb9e71c..0c6169582 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -32,7 +32,7 @@ Blokeatuak Eskakizunak Profila editatu - Zirriborroak + Zirriborroak Lizentziak %s-(e)k bultzatu du Kontuz edukiarekin diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 2f20b2109..2d5f570e4 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -32,7 +32,7 @@ کاربران مسدود درخواست‌های پی‌گیری ویرایش نمایه‌تان - پیش‌نویس‌ها + پیش‌نویس‌ها پروانه‌ها %s تقویت کرد محتوای حسّاس diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 6240d8134..74f5e1055 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -36,7 +36,7 @@ Comptes bloqués Demandes d’abonnement Modifier votre profil - Brouillons + Brouillons Licences \@%s %s a partagé diff --git a/app/src/main/res/values-ga/strings.xml b/app/src/main/res/values-ga/strings.xml index aad2a612e..130f4abef 100644 --- a/app/src/main/res/values-ga/strings.xml +++ b/app/src/main/res/values-ga/strings.xml @@ -177,7 +177,7 @@ Roghanna Cuntais Sainroghanna Logáil Amach - Dréachtaí + Dréachtaí Roghaí Theip ar fhíordheimhniú leis an gcás sin. Cad is sampla ann\? diff --git a/app/src/main/res/values-gd/strings.xml b/app/src/main/res/values-gd/strings.xml index ed594dc7e..83bfce331 100644 --- a/app/src/main/res/values-gd/strings.xml +++ b/app/src/main/res/values-gd/strings.xml @@ -8,7 +8,7 @@ Roighainnean cunntais Roighainnean Clàraich a-mach - Dreachd + Dreachd Prìomhaich Dè a th ’ann an àite\? Deasaich diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index a4c1b373b..20b6e5d0b 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -2,7 +2,7 @@ हिंदी पसंदीदा - प्रारूप + प्रारूप लॉग आउट पसंद खाता प्राथमिकताएं diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 9215ed09c..0086e2991 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -36,7 +36,7 @@ Letiltott felhasználók Követési kérelmek Profilod szerkesztése - Piszkozatok + Piszkozatok Licenszek \@%s %s megtolta diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml index e52cda276..7f911d96d 100644 --- a/app/src/main/res/values-is/strings.xml +++ b/app/src/main/res/values-is/strings.xml @@ -3,7 +3,7 @@ Skrá inn með Mastodon Hvað er tilvik\? Eftirlæti - Drög + Drög Skrá út Kjörstillingar Eiginleikar tengingar diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 3b716a1f9..c43e60f4d 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -36,7 +36,7 @@ Utenti bloccati Richieste di seguirti Modifica il tuo profilo - Bozze + Bozze Licenze \@%s %s ha boostato diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index df4ec9319..a716f733b 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -35,7 +35,7 @@ ブロックしたユーザー フォローリクエスト プロフィールを編集 - 下書き + 下書き ライセンス %sさんがブーストしました 閲覧注意 diff --git a/app/src/main/res/values-kab/strings.xml b/app/src/main/res/values-kab/strings.xml index 8f51de5e5..ad5a99d92 100644 --- a/app/src/main/res/values-kab/strings.xml +++ b/app/src/main/res/values-kab/strings.xml @@ -2,7 +2,7 @@ Qqen ɣer Maṣṭudun Ismenyifen - Irewwayen + Irewwayen Ffeɣ Iɣewwaṛen Iɣewwaṛen n umiḍan diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index cf11c797f..893b4acb1 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -37,7 +37,7 @@ 숨긴 도메인 팔로우 요청 프로필 편집 - 임시 저장 + 임시 저장 라이선스 \@%s %s님이 부스트 했습니다 diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index 72119eef6..de97414b7 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -3,7 +3,7 @@ മസ്റ്റഡോൺ വഴി പ്രവേശിക്കുക എന്താണ് ഒരു ഇൻസ്റ്റൻസ്\? പ്രിയപ്പെട്ടവ - കരടുകൾ + കരടുകൾ പുറത്തിറങ്ങുക മുൻഗണനകൾ അക്കൗണ്ട് മുൻഗണനകൾ diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index b8d2d09da..50d94b3d7 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -36,7 +36,7 @@ Geblokkeerde gebruikers Volgverzoeken Profiel bewerken - Concepten + Concepten Licenties \@%s %s boostte diff --git a/app/src/main/res/values-no-rNB/strings.xml b/app/src/main/res/values-no-rNB/strings.xml index cd633dd0e..3adcf1eb8 100644 --- a/app/src/main/res/values-no-rNB/strings.xml +++ b/app/src/main/res/values-no-rNB/strings.xml @@ -36,7 +36,7 @@ Blokkerte brukere Forespørsler om følgen Endre profilen din - Kladder + Kladder Lisenser \@%s %s boosted diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml index c22afc29d..a404fef52 100644 --- a/app/src/main/res/values-oc/strings.xml +++ b/app/src/main/res/values-oc/strings.xml @@ -31,7 +31,7 @@ Utilizaires blocats Demandas d’abonament Modificar lo perfil - Borrolhons + Borrolhons Licéncias %s partejat Contengut sensible diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 9082f6347..03ca93804 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -31,7 +31,7 @@ Zablokowani użytkownicy Prośby o możliwość śledzenia Edytuj profil - Szkice + Szkice Licencje %s podbił Wrażliwe treści diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 95bf8a29e..d36363808 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -34,7 +34,7 @@ Usuários bloqueados Seguidores pendentes Editar perfil - Rascunhos + Rascunhos Licenças %s deu boost Mídia sensível diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 6adb5768f..9e2cf800f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -36,7 +36,7 @@ Список блокировки Запросы на подписку Редактировать профиль - Черновики + Черновики Лицензии \@%s %s продвинул(а) diff --git a/app/src/main/res/values-sa/strings.xml b/app/src/main/res/values-sa/strings.xml index 2fb217ade..18bb76015 100644 --- a/app/src/main/res/values-sa/strings.xml +++ b/app/src/main/res/values-sa/strings.xml @@ -36,7 +36,7 @@ \@%s अनुज्ञापत्राणि कालबद्धदौत्यानि - लेखविकर्षाः + लेखविकर्षाः स्वीयव्यक्तिविवरणं सम्पाद्यताम् अनुसरणार्थमनुरोधाः प्रच्छन्नप्रदेशाः diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 68c278706..14af6584d 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -1,7 +1,7 @@ Prihlásiť sa účtom Mastodon - Koncepty + Koncepty Odhlásiť sa Nastavenia Nastavenia účtu diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index 70bb80beb..35b3f08ea 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -34,7 +34,7 @@ Blokirani uporabniki Zahteve za Sledenje Uredi svoj profil - Osnutki + Osnutki Licence \@%s Občutljiva vsebina diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index ff8a5a133..34eb3e8c1 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -36,7 +36,7 @@ Blockerade användare Följarförfrågningar Ändra din profil - Utkast + Utkast Licenser \@%s %s knuffade diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index 2affaa29b..d726ba321 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -29,7 +29,7 @@ தடைசெய்யபட்ட பயனர்கள் பின்பற்ற கோரிக்கை சுயவிவரத்தை திருத்த - வரைவுகள் + வரைவுகள் %s மேலேற்றப்பட்டது உணர்ச்சிகரமான உள்ளடக்கம் ஊடகம் மறைக்கப்பட்டது diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 5c994db6a..dc5487a81 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -431,7 +431,7 @@ ตั้งค่าบัญชี ตั้งค่า ออกจากระบบ - ฉบับร่าง + ฉบับร่าง ชื่นชอบ การยืนยันตัวตนกับเซิร์ฟเวอร์นั้นล้มเหลว Instance คือ\? diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 596bafa41..684ecc21e 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -36,7 +36,7 @@ Engellenmiş kullanıcılar Takip Etme İstekleri Profili düzeltme - Taslaklar + Taslaklar Lisanslar \@%s %s yineledi diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index cb7869add..5c49c828c 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -37,7 +37,7 @@ Налаштування акаунта Налаштування Вийти - Чернетки + Чернетки Вподобане Увійти Зʼєднання… diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 460ebb2d8..d785a1f1a 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -197,7 +197,7 @@ Cộng đồng Thông báo Bảng tin - Nháp + Nháp Lượt thích Máy chủ là gì\? Tải xem trước hình ảnh diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 03c70771a..e123fd9e5 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -36,7 +36,7 @@ 被屏蔽的用户 关注请求 编辑个人资料 - 草稿 + 草稿 开源协议 \@%s %s 转嘟了 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 91bed1d4c..5c50933d4 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -36,7 +36,7 @@ 被封鎖的使用者 關注請求 編輯個人資料 - 草稿 + 草稿 開源授權 \@%s %s 轉嘟了 diff --git a/app/src/main/res/values-zh-rMO/strings.xml b/app/src/main/res/values-zh-rMO/strings.xml index 8333ff691..538284c60 100644 --- a/app/src/main/res/values-zh-rMO/strings.xml +++ b/app/src/main/res/values-zh-rMO/strings.xml @@ -36,7 +36,7 @@ 被封鎖的使用者 關注請求 編輯個人資料 - 草稿 + 草稿 開源授權 \@%s %s 轉嘟了 diff --git a/app/src/main/res/values-zh-rSG/strings.xml b/app/src/main/res/values-zh-rSG/strings.xml index 7fe476a6d..2f45c629d 100644 --- a/app/src/main/res/values-zh-rSG/strings.xml +++ b/app/src/main/res/values-zh-rSG/strings.xml @@ -36,7 +36,7 @@ 被屏蔽的用户 关注请求 编辑个人资料 - 草稿 + 草稿 开源协议 \@%s %s 转嘟了 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index f4e93299d..6d690ace9 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -36,7 +36,7 @@ 被封鎖的使用者 關注請求 編輯個人資料 - 草稿 + 草稿 開源授權 \@%s %s 轉嘟了 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index dd4f599ca..5ec69307a 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -45,6 +45,7 @@ 5dp 12dp + 120dp 72dp 108dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6b08043e5..7439808b8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -40,7 +40,7 @@ Hidden domains Follow Requests Edit your profile - Drafts + Drafts Scheduled toots Announcements Licenses @@ -585,6 +585,7 @@ Wellbeing Your private note about this account Saved! + Some information that might affect your mental wellbeing will be hidden. This includes:\n\n - Favorite/Boost/Follow notifications\n - Favorite/Boost count on toots\n @@ -598,4 +599,15 @@ You cannot upload more than %1$d media attachments. Do you really want to delete the list %s? + This toot failed to send! + + + The draft feature in Tusky has been completely redesigned to be faster, more user friendly and less buggy.\n + You can still access your old drafts via a button on the new drafts screen, + but they will be removed in a future update! + + Old Drafts + Failed loading Reply information + Draft deleted + The Toot you drafted a reply to has been removed diff --git a/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt b/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt index 7f33a9769..bd9be3b21 100644 --- a/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt @@ -13,7 +13,6 @@ * You should have received a copy of the GNU General Public License along with Tusky; if not, * see . */ - package com.keylesspalace.tusky import android.content.Intent @@ -25,6 +24,7 @@ import com.keylesspalace.tusky.components.compose.ComposeActivity import com.keylesspalace.tusky.components.compose.ComposeViewModel import com.keylesspalace.tusky.components.compose.DEFAULT_CHARACTER_LIMIT import com.keylesspalace.tusky.components.compose.MediaUploader +import com.keylesspalace.tusky.components.drafts.DraftHelper import com.keylesspalace.tusky.db.* import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.entity.Account @@ -115,6 +115,7 @@ class ComposeActivityTest { accountManagerMock, mock(MediaUploader::class.java), mock(ServiceClient::class.java), + mock(DraftHelper::class.java), mock(SaveTootHelper::class.java), dbMock ) From 378d2355086221f40da64c319a6636dfa8fd813e Mon Sep 17 00:00:00 2001 From: nailyk-weblate Date: Sun, 24 Jan 2021 21:40:32 +0000 Subject: [PATCH 19/75] Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ --- app/src/main/res/values-ckb/strings.xml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml index d07ed2275..56f4f5e95 100644 --- a/app/src/main/res/values-ckb/strings.xml +++ b/app/src/main/res/values-ckb/strings.xml @@ -115,7 +115,6 @@ مۆڵەتەکان ڕاگه یه نراوەکان توتی خشتەکراو - ڕەشنووسەکان دەستکاری پرۆفایلەکەت بکە بەدواداچونی داواکاریەکان بکە دۆمەینە شاراوەکان @@ -262,13 +261,6 @@ هەڵبژاردنی %d چەند هەڵبژاردنێک زیادکردنی هەڵبژاردن - ٧ ڕۆژ - ٣ ڕۆژ - ١ ڕۆژ - ٦ کاتژمێر - ١ کاتژمێر - ٣٠ خولەک - ٥ خولەک ڕاپرسی چالاککردنی ئاماژەکردنی لێدانی چالاک بۆ گۆڕین لە نێوان خشتەبەندەکان تاسکی کۆد و سەرمایەکانی تێدایە لەم پڕۆژە کراوەی سەرچاوە: From cd70f3d7d25cb6171c8202b7f4f1f7fa98c96c5a Mon Sep 17 00:00:00 2001 From: Vegard Skjefstad Date: Sun, 24 Jan 2021 21:40:32 +0000 Subject: [PATCH 20/75] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/nb_NO/ Translated using Weblate (Norwegian Bokmål) Currently translated at 100.0% (450 of 450 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/nb_NO/ Translated using Weblate (Norwegian Bokmål) Currently translated at 100.0% (447 of 447 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/nb_NO/ --- app/src/main/res/values-no-rNB/strings.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/main/res/values-no-rNB/strings.xml b/app/src/main/res/values-no-rNB/strings.xml index 3adcf1eb8..36c7dc31a 100644 --- a/app/src/main/res/values-no-rNB/strings.xml +++ b/app/src/main/res/values-no-rNB/strings.xml @@ -484,4 +484,16 @@ noen jeg følger publiserer en ny toot %s tootet akkurat Du kan ikke laste opp flere enn %1$d mediavedlegg. + Uendelig + Varighet + Er du sikker på at du vil slette listen %s\? + Vedlegg + Lyd + Tootet du kladdet et svar til har blitt fjernet + Kladd slettet + Lasting av svarinformasjon feilet + Gamle kladder + KladdfunksjonaLiteten i Tusky er skrevet om og er nå kjappere, mer brukervennlig, og med færre feil. +\nGamle kladder er fortsatt tilgjengelige via en knapp på den nye kladdskjermen, men de vil bli fjernet i en fremtidig oppdatering! + Sending av toot feilet! \ No newline at end of file From 295f5d5a8c7e3f996ea4de4752a8e94f0a3ea7ea Mon Sep 17 00:00:00 2001 From: Ho Nhat Duy Date: Sun, 24 Jan 2021 21:40:32 +0000 Subject: [PATCH 21/75] Translated using Weblate (Vietnamese) Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ Translated using Weblate (Vietnamese) Currently translated at 100.0% (450 of 450 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ Translated using Weblate (Vietnamese) Currently translated at 100.0% (450 of 450 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ Translated using Weblate (Vietnamese) Currently translated at 100.0% (450 of 450 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ Translated using Weblate (Vietnamese) Currently translated at 100.0% (447 of 447 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ --- app/src/main/res/values-vi/strings.xml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index d785a1f1a..47b8007fe 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -188,7 +188,7 @@ Người theo dõi Theo dõi Ghim - Tương tác + Rép Tút Tút Xếp tab @@ -456,7 +456,7 @@ Bỏ ẩn %s Ẩn tiêu đề tab Đã lưu! - Ghi chú của bạn + Thêm ghi chú Chưa có thông báo. Tin tức Ẩn số liệu trên trang cá nhân @@ -476,4 +476,16 @@ người tôi đăng ký theo dõi đăng tút mới %s vừa đăng tút Bạn không thể đính kèm quá %1$d tệp. + Không giới hạn + Độ dài + Bạn thật sự muốn xóa danh sách %s\? + Đính kèm + Âm thanh + Tút bạn lên lịch đã bị hủy bỏ + Tút lên lịch cũ + Tút lên lịch đã xóa + Chưa tải được bình luận + Tính năng lên lịch đăng tút của Tusky sẽ được thiết kế lại hoàn toàn để nhanh hơn, thân thiện và ít lỗi hơn. +\nBạn vẫn có thể xem lại bản nháp cũ nhưng chúng sẽ bị xóa bỏ trong bản cập nhật tương lai! + Đăng tút không thành công! \ No newline at end of file From a45c8613addd728e28cd619ff3627f0b840916e9 Mon Sep 17 00:00:00 2001 From: Juanjo Salvador Date: Sun, 24 Jan 2021 21:40:32 +0000 Subject: [PATCH 22/75] Translated using Weblate (Spanish) Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/es/ --- app/src/main/res/values-es/strings.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 2b2633a62..3bd9e38ae 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -495,4 +495,17 @@ \n- Seguidor/Publicar estadísticas en perfiles \n \nLas notificaciones Push no serán afectadas, pero puedes revisar manualmente tus referencias de notificaciones. + El toot al que redactaste una respuesta ha sido eliminado + Borrador eliminado + Error al cargar la información de respuesta + Borradores antiguos + La función de borrador en Tusky se ha rediseñado por completo para que sea más rápida, más fácil de usar y con menos errores. +\nAún puede acceder a sus borradores antiguos a través de un botón en la pantalla de borradores nuevos, ¡pero se eliminarán en una actualización futura! + ¡Este toot no se pudo enviar! + ¿Realmente quieres eliminar la lista %s\? + Indefinido + Duración + Adjuntos + Audio + Limitar notificaciones de cronología \ No newline at end of file From 46468b8a782b954cbc5a1bb76cd5c2244b4ce39b Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Mon, 25 Jan 2021 16:23:32 +0100 Subject: [PATCH 23/75] use CustomTabColorSchemeParams instead of deprecated methods (#2049) --- .../com/keylesspalace/tusky/LoginActivity.kt | 19 ++++++++++-------- .../keylesspalace/tusky/util/LinkHelper.java | 20 ++++++++++--------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt index f68b2337c..136997231 100644 --- a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt @@ -20,7 +20,6 @@ import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.net.Uri -import android.os.Build import android.os.Bundle import android.text.method.LinkMovementMethod import android.util.Log @@ -28,6 +27,7 @@ import android.view.MenuItem import android.view.View import android.widget.TextView import androidx.appcompat.app.AlertDialog +import androidx.browser.customtabs.CustomTabColorSchemeParams import androidx.browser.customtabs.CustomTabsIntent import com.bumptech.glide.Glide import com.keylesspalace.tusky.di.Injectable @@ -346,16 +346,19 @@ class LoginActivity : BaseActivity(), Injectable { private fun openInCustomTab(uri: Uri, context: Context): Boolean { val toolbarColor = ThemeUtils.getColor(context, R.attr.colorSurface) - val customTabsIntentBuilder = CustomTabsIntent.Builder() + val navigationbarColor = ThemeUtils.getColor(context, android.R.attr.navigationBarColor) + val navigationbarDividerColor = ThemeUtils.getColor(context, R.attr.dividerColor) + + val colorSchemeParams = CustomTabColorSchemeParams.Builder() .setToolbarColor(toolbarColor) + .setNavigationBarColor(navigationbarColor) + .setNavigationBarDividerColor(navigationbarDividerColor) + .build() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { - customTabsIntentBuilder.setNavigationBarColor( - ThemeUtils.getColor(context, android.R.attr.navigationBarColor) - ) - } + val customTabsIntent = CustomTabsIntent.Builder() + .setDefaultColorSchemeParams(colorSchemeParams) + .build() - val customTabsIntent = customTabsIntentBuilder.build() try { customTabsIntent.launchUrl(context, uri) } catch (e: ActivityNotFoundException) { diff --git a/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.java b/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.java index 64e329c80..ec0c8a3e2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.java @@ -19,7 +19,6 @@ import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.os.Build; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.method.LinkMovementMethod; @@ -31,6 +30,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.browser.customtabs.CustomTabColorSchemeParams; import androidx.browser.customtabs.CustomTabsIntent; import androidx.preference.PreferenceManager; @@ -229,18 +229,20 @@ public class LinkHelper { */ public static void openLinkInCustomTab(Uri uri, Context context) { int toolbarColor = ThemeUtils.getColor(context, R.attr.colorSurface); + int navigationbarColor = ThemeUtils.getColor(context, android.R.attr.navigationBarColor); + int navigationbarDividerColor = ThemeUtils.getColor(context, R.attr.dividerColor); - CustomTabsIntent.Builder customTabsIntentBuilder = new CustomTabsIntent.Builder() + CustomTabColorSchemeParams colorSchemeParams = new CustomTabColorSchemeParams.Builder() .setToolbarColor(toolbarColor) - .setShowTitle(true); + .setNavigationBarColor(navigationbarColor) + .setNavigationBarDividerColor(navigationbarDividerColor) + .build(); - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { - customTabsIntentBuilder.setNavigationBarColor( - ThemeUtils.getColor(context, android.R.attr.navigationBarColor) - ); - } + CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder() + .setDefaultColorSchemeParams(colorSchemeParams) + .setShowTitle(true) + .build(); - CustomTabsIntent customTabsIntent = customTabsIntentBuilder.build(); try { customTabsIntent.launchUrl(context, uri); } catch (ActivityNotFoundException e) { From 968c4ed3e0e09b8c9bc2a4cd91c5800000a87170 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Mon, 25 Jan 2021 16:23:43 +0100 Subject: [PATCH 24/75] add proguard rule to keep DraftAttachment.Type (#2054) --- app/proguard-rules.pro | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 78b4be883..a05994a10 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -43,6 +43,10 @@ public *; } +-keep enum com.keylesspalace.tusky.db.DraftAttachment$Type { + public *; +} + # preserve line numbers for crash reporting -keepattributes SourceFile,LineNumberTable -renamesourcefileattribute SourceFile From 0275504a059163b697acd740ff0c43af664c5f1f Mon Sep 17 00:00:00 2001 From: lenchan139 Date: Tue, 26 Jan 2021 23:38:03 +0800 Subject: [PATCH 25/75] Move compose content's EditText cursor to first when shared content parsed in ComposeActivity (#2030) * multiple media upload support * multiple media upload support * multiple media upload support * remove typing * Update app/src/main/res/values/strings.xml Co-authored-by: Konrad Pozniak * remove magic number on string.xml and add to activity. * move edittext cursor to first when shareBody parsed in ComposeActivity Co-authored-by: Konrad Pozniak --- .../tusky/components/compose/ComposeActivity.kt | 5 ++++- app/src/main/res/values/strings.xml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt index 69a090880..883294517 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt @@ -30,7 +30,7 @@ import android.os.Build import android.os.Bundle import android.os.Parcelable import android.provider.MediaStore -import android.util.Log + import android.util.Log import android.view.KeyEvent import android.view.MenuItem import android.view.View @@ -202,6 +202,9 @@ class ComposeActivity : BaseActivity(), val left = min(start, end) val right = max(start, end) composeEditField.text.replace(left, right, shareBody, 0, shareBody.length) + // move edittext cursor to first when shareBody parsed + composeEditField.text.insert(0, "\n") + composeEditField.setSelection(0) } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7439808b8..c1615e040 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -598,9 +598,9 @@ Hide quantitative stats on profiles You cannot upload more than %1$d media attachments. Do you really want to delete the list %s? - This toot failed to send! + The draft feature in Tusky has been completely redesigned to be faster, more user friendly and less buggy.\n You can still access your old drafts via a button on the new drafts screen, From b00aa9b461a27e4f1e7fcfe1e0d70eb88645f628 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 31 Jan 2021 20:57:48 +0300 Subject: [PATCH 26/75] SpanUtils: highlight nicknames with dashes (#2061) While it's not allowed to create such accounts on Mastodon, it federates fine with servers that allow dashes. --- app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt b/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt index 484669965..307fbeae7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt @@ -18,7 +18,7 @@ private const val TAG_REGEX = "(?:^|[^/)A-Za-z0-9_])#([\\w_]*[\\p{Alpha}_][\\w_] * @see * Account#MENTION_RE */ -private const val MENTION_REGEX = "(?:^|[^/[:word:]])@([a-z0-9_]+(?:@[a-z0-9\\.\\-]+[a-z0-9]+)?)" +private const val MENTION_REGEX = "(?:^|[^/[:word:]])@([a-z0-9_-]+(?:@[a-z0-9\\.\\-]+[a-z0-9]+)?)" private const val HTTP_URL_REGEX = "(?:(^|\\b)http://[^\\s]+)" private const val HTTPS_URL_REGEX = "(?:(^|\\b)https://[^\\s]+)" From 455942505c82ee3cd7b97a9a17cb6e227e9e2419 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 31 Jan 2021 20:58:45 +0300 Subject: [PATCH 27/75] ComposeViewModel: remove unused variable (#2060) --- .../tusky/components/compose/ComposeViewModel.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt index a1a5e7e63..71293511d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt @@ -271,12 +271,10 @@ class ComposeViewModel @Inject constructor( val mediaIds = ArrayList() val mediaUris = ArrayList() val mediaDescriptions = ArrayList() - val mediaTypes = ArrayList() for (item in media.value!!) { mediaIds.add(item.id!!) mediaUris.add(item.uri) mediaDescriptions.add(item.description ?: "") - mediaTypes.add(item.type) } val tootToSend = TootToSend( @@ -504,4 +502,4 @@ data class ComposeInstanceParams( /** * Thrown when trying to add an image when video is already present or the other way around */ -class VideoOrImageException : Exception() \ No newline at end of file +class VideoOrImageException : Exception() From 2d2b79aa478f99395a9479e09513723560f6b356 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 31 Jan 2021 21:22:06 +0300 Subject: [PATCH 28/75] layout: move media preview to standalone xml and include it in containers (#2063) --- app/src/main/res/layout/item_conversation.xml | 183 +---------------- .../main/res/layout/item_media_preview.xml | 190 ++++++++++++++++++ app/src/main/res/layout/item_status.xml | 188 +---------------- .../main/res/layout/item_status_detailed.xml | 186 +---------------- 4 files changed, 195 insertions(+), 552 deletions(-) create mode 100644 app/src/main/res/layout/item_media_preview.xml diff --git a/app/src/main/res/layout/item_conversation.xml b/app/src/main/res/layout/item_conversation.xml index d746f99ec..1d6eab67c 100644 --- a/app/src/main/res/layout/item_conversation.xml +++ b/app/src/main/res/layout/item_conversation.xml @@ -203,188 +203,7 @@ app:layout_constraintTop_toBottomOf="@id/button_toggle_content" tools:visibility="gone"> - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/app/src/main/res/layout/item_media_preview.xml b/app/src/main/res/layout/item_media_preview.xml new file mode 100644 index 000000000..27b58e7a1 --- /dev/null +++ b/app/src/main/res/layout/item_media_preview.xml @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_status.xml b/app/src/main/res/layout/item_status.xml index b6c597254..8edca2e1a 100644 --- a/app/src/main/res/layout/item_status.xml +++ b/app/src/main/res/layout/item_status.xml @@ -257,192 +257,8 @@ app:layout_constraintStart_toStartOf="@id/status_display_name" app:layout_constraintTop_toBottomOf="@id/status_card_view" tools:visibility="visible"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/app/src/main/res/layout/item_status_detailed.xml b/app/src/main/res/layout/item_status_detailed.xml index f3ad6ed52..a075503e5 100644 --- a/app/src/main/res/layout/item_status_detailed.xml +++ b/app/src/main/res/layout/item_status_detailed.xml @@ -206,189 +206,7 @@ android:importantForAccessibility="noHideDescendants" app:layout_constraintTop_toBottomOf="@id/status_card_view"> - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -588,4 +406,4 @@ app:layout_constraintTop_toTopOf="@id/status_reply" app:srcCompat="@drawable/ic_more_horiz_24dp" /> - \ No newline at end of file + From 886ff2f06b69e67ca459e02d0633f4d69c5b21da Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Sun, 31 Jan 2021 19:34:33 +0100 Subject: [PATCH 29/75] get rid of BaseFragment by using RxJava instead of Retrofit Calls (#2055) * get rid of BaseFragment by using RxJava instead of Retrofit Calls * fix tests --- .../components/drafts/DraftsViewModel.kt | 2 +- .../fragment/InstanceListFragment.kt | 8 +- .../tusky/fragment/AccountListFragment.kt | 52 ++++------- .../tusky/fragment/AccountMediaFragment.kt | 54 +++++------ .../tusky/fragment/BaseFragment.java | 43 --------- .../tusky/fragment/NotificationsFragment.java | 89 ++++++++----------- .../tusky/fragment/SFragment.java | 14 ++- .../tusky/fragment/TimelineFragment.java | 63 ++++++------- .../tusky/fragment/ViewMediaFragment.kt | 3 +- .../tusky/fragment/ViewThreadFragment.java | 65 +++++--------- .../tusky/network/MastodonApi.kt | 49 +++------- .../tusky/repository/TimelineRepository.kt | 8 +- .../tusky/fragment/TimelineRepositoryTest.kt | 29 +++--- 13 files changed, 170 insertions(+), 309 deletions(-) delete mode 100644 app/src/main/java/com/keylesspalace/tusky/fragment/BaseFragment.java diff --git a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsViewModel.kt index 9eca963aa..f928b6d03 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsViewModel.kt @@ -57,7 +57,7 @@ class DraftsViewModel @Inject constructor( } fun getToot(tootId: String): Single { - return api.statusSingle(tootId) + return api.status(tootId) } override fun onCleared() { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt index bb850bdcf..093fc42d3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt @@ -5,6 +5,7 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager @@ -14,7 +15,6 @@ import com.keylesspalace.tusky.R import com.keylesspalace.tusky.components.instancemute.adapter.DomainMutesAdapter import com.keylesspalace.tusky.components.instancemute.interfaces.InstanceActionListener import com.keylesspalace.tusky.di.Injectable -import com.keylesspalace.tusky.fragment.BaseFragment import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.util.HttpHeaderLink import com.keylesspalace.tusky.util.hide @@ -30,7 +30,7 @@ import retrofit2.Response import java.io.IOException import javax.inject.Inject -class InstanceListFragment: BaseFragment(), Injectable, InstanceActionListener { +class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectable, InstanceActionListener { @Inject lateinit var api: MastodonApi @@ -39,10 +39,6 @@ class InstanceListFragment: BaseFragment(), Injectable, InstanceActionListener { private var adapter = DomainMutesAdapter(this) private lateinit var scrollListener: EndlessOnScrollListener - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - return inflater.inflate(R.layout.fragment_instance_list, container, false) - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt index d5785ae36..93a6162b7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt @@ -17,9 +17,8 @@ package com.keylesspalace.tusky.fragment import android.os.Bundle import android.util.Log -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup +import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager @@ -45,14 +44,12 @@ import com.uber.autodispose.autoDispose import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.synthetic.main.fragment_account_list.* -import retrofit2.Call -import retrofit2.Callback import retrofit2.Response import java.io.IOException import java.util.* import javax.inject.Inject -class AccountListFragment : BaseFragment(), AccountActionListener, Injectable { +class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountActionListener, Injectable { @Inject lateinit var api: MastodonApi @@ -71,10 +68,6 @@ class AccountListFragment : BaseFragment(), AccountActionListener, Injectable { id = arguments?.getString(ARG_ID) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - return inflater.inflate(R.layout.fragment_account_list, container, false) - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -202,27 +195,23 @@ class AccountListFragment : BaseFragment(), AccountActionListener, Injectable { override fun onRespondToFollowRequest(accept: Boolean, accountId: String, position: Int) { - val callback = object : Callback { - override fun onResponse(call: Call, response: Response) { - if (response.isSuccessful) { - onRespondToFollowRequestSuccess(position) - } else { - onRespondToFollowRequestFailure(accept, accountId) - } - } - - override fun onFailure(call: Call, t: Throwable) { - onRespondToFollowRequestFailure(accept, accountId) - } - } - - val call = if (accept) { + if (accept) { api.authorizeFollowRequest(accountId) } else { api.rejectFollowRequest(accountId) - } - callList.add(call) - call.enqueue(callback) + }.observeOn(AndroidSchedulers.mainThread()) + .autoDispose(from(this, Lifecycle.Event.ON_DESTROY)) + .subscribe({ + onRespondToFollowRequestSuccess(position) + }, { throwable -> + val verb = if (accept) { + "accept" + } else { + "reject" + } + Log.e(TAG, "Failed to $verb account id $accountId.", throwable) + }) + } private fun onRespondToFollowRequestSuccess(position: Int) { @@ -230,15 +219,6 @@ class AccountListFragment : BaseFragment(), AccountActionListener, Injectable { followRequestsAdapter.removeItem(position) } - private fun onRespondToFollowRequestFailure(accept: Boolean, accountId: String) { - val verb = if (accept) { - "accept" - } else { - "reject" - } - Log.e(TAG, "Failed to $verb account id $accountId.") - } - private fun getFetchCallByListType(fromId: String?): Single>> { return when (type) { Type.FOLLOWS -> { diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt index 0ae207587..b85a87f31 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt @@ -18,12 +18,13 @@ package com.keylesspalace.tusky.fragment import android.graphics.Color import android.os.Bundle import android.util.Log -import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import androidx.core.app.ActivityOptionsCompat import androidx.core.view.ViewCompat +import androidx.fragment.app.Fragment +import androidx.lifecycle.Lifecycle import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide @@ -40,9 +41,11 @@ import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.view.SquareImageView import com.keylesspalace.tusky.viewdata.AttachmentViewData +import com.uber.autodispose.android.lifecycle.autoDispose +import io.reactivex.SingleObserver +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable import kotlinx.android.synthetic.main.fragment_timeline.* -import retrofit2.Call -import retrofit2.Callback import retrofit2.Response import java.io.IOException import java.util.* @@ -54,7 +57,7 @@ import javax.inject.Inject * Fragment with multiple columns of media previews for the specified account. */ -class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { +class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFragment, Injectable { companion object { @JvmStatic fun newInstance(accountId: String, enableSwipeToRefresh:Boolean=true): AccountMediaFragment { @@ -78,14 +81,13 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { lateinit var api: MastodonApi private val adapter = MediaGridAdapter() - private var currentCall: Call>? = null private val statuses = mutableListOf() private var fetchingStatus = FetchingStatus.NOT_FETCHING private lateinit var accountId: String - private val callback = object : Callback> { - override fun onFailure(call: Call>?, t: Throwable?) { + private val callback = object : SingleObserver>> { + override fun onError(t: Throwable) { fetchingStatus = FetchingStatus.NOT_FETCHING if (isAdded) { @@ -107,7 +109,7 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { Log.d(TAG, "Failed to fetch account media", t) } - override fun onResponse(call: Call>, response: Response>) { + override fun onSuccess(response: Response>) { fetchingStatus = FetchingStatus.NOT_FETCHING if (isAdded) { swipeRefreshLayout.isRefreshing = false @@ -128,22 +130,23 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { if (statuses.isEmpty()) { statusView.show() - statusView.setup(R.drawable.elephant_friend_empty, R.string.message_empty, - null) + statusView.setup(R.drawable.elephant_friend_empty, R.string.message_empty) } } } } + + override fun onSubscribe(d: Disposable) {} } - private val bottomCallback = object : Callback> { - override fun onFailure(call: Call>?, t: Throwable?) { + private val bottomCallback = object : SingleObserver>> { + override fun onError(t: Throwable) { fetchingStatus = FetchingStatus.NOT_FETCHING Log.d(TAG, "Failed to fetch account media", t) } - override fun onResponse(call: Call>, response: Response>) { + override fun onSuccess(response: Response>) { fetchingStatus = FetchingStatus.NOT_FETCHING val body = response.body() body?.let { fetched -> @@ -160,6 +163,7 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { } } + override fun onSubscribe(d: Disposable) { } } override fun onCreate(savedInstanceState: Bundle?) { @@ -167,10 +171,6 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { isSwipeToRefreshEnabled = arguments?.getBoolean(ARG_ENABLE_SWIPE_TO_REFRESH,true) == true accountId = arguments?.getString(ACCOUNT_ID_ARG)!! } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_timeline, container, false) - } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -202,8 +202,10 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { statuses.lastOrNull()?.let { (id) -> Log.d(TAG, "Requesting statuses with max_id: ${id}, (bottom)") fetchingStatus = FetchingStatus.FETCHING_BOTTOM - currentCall = api.accountStatuses(accountId, id, null, null, null, true, null) - currentCall?.enqueue(bottomCallback) + api.accountStatuses(accountId, id, null, null, null, true, null) + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(this@AccountMediaFragment, Lifecycle.Event.ON_DESTROY) + .subscribe(bottomCallback) } } } @@ -216,14 +218,15 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { private fun refresh() { statusView.hide() if (fetchingStatus != FetchingStatus.NOT_FETCHING) return - currentCall = if (statuses.isEmpty()) { + if (statuses.isEmpty()) { fetchingStatus = FetchingStatus.INITIAL_FETCHING api.accountStatuses(accountId, null, null, null, null, true, null) } else { fetchingStatus = FetchingStatus.REFRESHING api.accountStatuses(accountId, null, statuses[0].id, null, null, true, null) - } - currentCall?.enqueue(callback) + }.observeOn(AndroidSchedulers.mainThread()) + .autoDispose(this, Lifecycle.Event.ON_DESTROY) + .subscribe(callback) if (!isSwipeToRefreshEnabled) topProgressBar?.show() @@ -235,8 +238,10 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { } if (fetchingStatus == FetchingStatus.NOT_FETCHING && statuses.isEmpty()) { fetchingStatus = FetchingStatus.INITIAL_FETCHING - currentCall = api.accountStatuses(accountId, null, null, null, null, true, null) - currentCall?.enqueue(callback) + api.accountStatuses(accountId, null, null, null, null, true, null) + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(this@AccountMediaFragment, Lifecycle.Event.ON_DESTROY) + .subscribe(callback) } else if (needToRefresh) refresh() @@ -339,5 +344,4 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { needToRefresh = true } - } \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/BaseFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/BaseFragment.java deleted file mode 100644 index b674b8ba5..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/BaseFragment.java +++ /dev/null @@ -1,43 +0,0 @@ -/* Copyright 2017 Andrew Dawson - * - * This file is a part of Tusky. - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU 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 General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Tusky; if not, - * see . */ - -package com.keylesspalace.tusky.fragment; - -import android.os.Bundle; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; - -import java.util.ArrayList; -import java.util.List; - -import retrofit2.Call; - -public class BaseFragment extends Fragment { - protected List callList; - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - callList = new ArrayList<>(); - } - - @Override - public void onDestroy() { - for (Call call : callList) { - call.cancel(); - } - super.onDestroy(); - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java index fe0c75f55..216ee2286 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java @@ -102,13 +102,11 @@ import at.connyduck.sparkbutton.helpers.Utils; import io.reactivex.Observable; import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.disposables.Disposable; import kotlin.Unit; import kotlin.collections.CollectionsKt; import kotlin.jvm.functions.Function1; -import okhttp3.ResponseBody; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; import static com.keylesspalace.tusky.util.StringUtils.isLessThan; import static com.uber.autodispose.AutoDispose.autoDisposable; @@ -125,8 +123,9 @@ public class NotificationsFragment extends SFragment implements private static final int LOAD_AT_ONCE = 30; private int maxPlaceholderId = 0; + private final Set notificationFilter = new HashSet<>(); - private Set notificationFilter = new HashSet<>(); + private final CompositeDisposable disposables = new CompositeDisposable(); private enum FetchEnd { TOP, @@ -685,32 +684,21 @@ public class NotificationsFragment extends SFragment implements updateAdapter(); //Execute clear notifications request - Call call = mastodonApi.clearNotifications(); - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - if (isAdded()) { - if (!response.isSuccessful()) { - //Reload notifications on failure - fullyRefreshWithProgressBar(true); - } - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - //Reload notifications on failure - fullyRefreshWithProgressBar(true); - } - }); - callList.add(call); + mastodonApi.clearNotifications() + .observeOn(AndroidSchedulers.mainThread()) + .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) + .subscribe( + response -> { + // nothing to do + }, + throwable -> { + //Reload notifications on failure + fullyRefreshWithProgressBar(true); + }); } private void resetNotificationsLoad() { - for (Call callItem : callList) { - callItem.cancel(); - } - callList.clear(); + disposables.clear(); bottomLoading = false; topLoading = false; @@ -840,8 +828,8 @@ public class NotificationsFragment extends SFragment implements @Override public void onRespondToFollowRequest(boolean accept, String id, int position) { Single request = accept ? - mastodonApi.authorizeFollowRequestObservable(id) : - mastodonApi.rejectFollowRequestObservable(id); + mastodonApi.authorizeFollowRequest(id) : + mastodonApi.rejectFollowRequest(id); request.observeOn(AndroidSchedulers.mainThread()) .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) .subscribe( @@ -959,27 +947,20 @@ public class NotificationsFragment extends SFragment implements bottomLoading = true; } - Call> call = mastodonApi.notifications(fromId, uptoId, LOAD_AT_ONCE, showNotificationsFilter ? notificationFilter : null); - - call.enqueue(new Callback>() { - @Override - public void onResponse(@NonNull Call> call, - @NonNull Response> response) { - if (response.isSuccessful()) { - String linkHeader = response.headers().get("Link"); - onFetchNotificationsSuccess(response.body(), linkHeader, fetchEnd, pos); - } else { - onFetchNotificationsFailure(new Exception(response.message()), fetchEnd, pos); - } - } - - @Override - public void onFailure(@NonNull Call> call, @NonNull Throwable t) { - if (!call.isCanceled()) - onFetchNotificationsFailure((Exception) t, fetchEnd, pos); - } - }); - callList.add(call); + Disposable notificationCall = mastodonApi.notifications(fromId, uptoId, LOAD_AT_ONCE, showNotificationsFilter ? notificationFilter : null) + .observeOn(AndroidSchedulers.mainThread()) + .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) + .subscribe( + response -> { + if (response.isSuccessful()) { + String linkHeader = response.headers().get("Link"); + onFetchNotificationsSuccess(response.body(), linkHeader, fetchEnd, pos); + } else { + onFetchNotificationsFailure(new Exception(response.message()), fetchEnd, pos); + } + }, + throwable -> onFetchNotificationsFailure(throwable, fetchEnd, pos)); + disposables.add(notificationCall); } private void onFetchNotificationsSuccess(List notifications, String linkHeader, @@ -1038,7 +1019,7 @@ public class NotificationsFragment extends SFragment implements progressBar.setVisibility(View.GONE); } - private void onFetchNotificationsFailure(Exception exception, FetchEnd fetchEnd, int position) { + private void onFetchNotificationsFailure(Throwable throwable, FetchEnd fetchEnd, int position) { swipeRefreshLayout.setRefreshing(false); if (fetchEnd == FetchEnd.MIDDLE && !notifications.get(position).isRight()) { Placeholder placeholder = notifications.get(position).asLeft(); @@ -1050,7 +1031,7 @@ public class NotificationsFragment extends SFragment implements this.statusView.setVisibility(View.VISIBLE); swipeRefreshLayout.setEnabled(false); this.showingError = true; - if (exception instanceof IOException) { + if (throwable instanceof IOException) { this.statusView.setup(R.drawable.elephant_offline, R.string.error_network, __ -> { this.progressBar.setVisibility(View.VISIBLE); this.onRefresh(); @@ -1065,7 +1046,7 @@ public class NotificationsFragment extends SFragment implements } updateFilterVisibility(); } - Log.e(TAG, "Fetch failure: " + exception.getMessage()); + Log.e(TAG, "Fetch failure: " + throwable.getMessage()); if (fetchEnd == FetchEnd.TOP) { topLoading = false; diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java index 4c50740b7..9d4b45b3e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java @@ -20,7 +20,6 @@ import android.app.DownloadManager; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; @@ -30,8 +29,6 @@ import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.widget.CheckBox; -import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; @@ -41,14 +38,14 @@ import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.PopupMenu; import androidx.core.app.ActivityOptionsCompat; import androidx.core.view.ViewCompat; +import androidx.fragment.app.Fragment; import androidx.lifecycle.Lifecycle; -import androidx.preference.PreferenceManager; import com.keylesspalace.tusky.BaseActivity; import com.keylesspalace.tusky.BottomSheetActivity; import com.keylesspalace.tusky.MainActivity; -import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.PostLookupFallbackBehavior; +import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.ViewMediaActivity; import com.keylesspalace.tusky.ViewTagActivity; import com.keylesspalace.tusky.components.compose.ComposeActivity; @@ -76,9 +73,8 @@ import java.util.regex.Pattern; import javax.inject.Inject; -import kotlin.Unit; - import io.reactivex.android.schedulers.AndroidSchedulers; +import kotlin.Unit; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; @@ -92,7 +88,7 @@ import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvid * adapters. I feel like the profile pages and thread viewer, which I haven't made yet, will also * overlap functionality. So, I'm momentarily leaving it and hopefully working on those will clear * up what needs to be where. */ -public abstract class SFragment extends BaseFragment implements Injectable { +public abstract class SFragment extends Fragment implements Injectable { protected abstract void removeItem(int position); @@ -103,7 +99,7 @@ public abstract class SFragment extends BaseFragment implements Injectable { private static List filters; private boolean filterRemoveRegex; private Matcher filterRemoveRegexMatcher; - private static Matcher alphanumeric = Pattern.compile("^\\w+$").matcher(""); + private static final Matcher alphanumeric = Pattern.compile("^\\w+$").matcher(""); @Inject public MastodonApi mastodonApi; diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java index d6910a4c4..06ca56f32 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java @@ -101,12 +101,11 @@ import javax.inject.Inject; import at.connyduck.sparkbutton.helpers.Utils; import io.reactivex.Observable; +import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; import kotlin.Unit; import kotlin.collections.CollectionsKt; import kotlin.jvm.functions.Function1; -import retrofit2.Call; -import retrofit2.Callback; import retrofit2.Response; import static com.uber.autodispose.AutoDispose.autoDisposable; @@ -1004,7 +1003,7 @@ public class TimelineFragment extends SFragment implements } } - private Call> getFetchCallByTimelineType(String fromId, String uptoId) { + private Single>> getFetchCallByTimelineType(String fromId, String uptoId) { MastodonApi api = mastodonApi; switch (kind) { default: @@ -1051,37 +1050,31 @@ public class TimelineFragment extends SFragment implements .observeOn(AndroidSchedulers.mainThread()) .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) .subscribe( - (result) -> onFetchTimelineSuccess(result, fetchEnd, pos), - (err) -> onFetchTimelineFailure(new Exception(err), fetchEnd, pos) + result -> onFetchTimelineSuccess(result, fetchEnd, pos), + err -> onFetchTimelineFailure(err, fetchEnd, pos) ); } else { - Callback> callback = new Callback>() { - @Override - public void onResponse(@NonNull Call> call, @NonNull Response> 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); - } - } - - @Override - public void onFailure(@NonNull Call> call, @NonNull Throwable t) { - onFetchTimelineFailure((Exception) t, fetchEnd, pos); - } - }; - - Call> listCall = getFetchCallByTimelineType(maxId, sinceId); - callList.add(listCall); - listCall.enqueue(callback); + getFetchCallByTimelineType(maxId, sinceId) + .observeOn(AndroidSchedulers.mainThread()) + .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) + .subscribe( + 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); + } + }, + err -> onFetchTimelineFailure(err, fetchEnd, pos) + ); } } @@ -1158,7 +1151,7 @@ public class TimelineFragment extends SFragment implements } } - private void onFetchTimelineFailure(Exception exception, FetchEnd fetchEnd, int position) { + private void onFetchTimelineFailure(Throwable throwable, FetchEnd fetchEnd, int position) { if (isAdded()) { swipeRefreshLayout.setRefreshing(false); topProgressBar.hide(); @@ -1177,7 +1170,7 @@ public class TimelineFragment extends SFragment implements } else if (this.statuses.isEmpty()) { swipeRefreshLayout.setEnabled(false); this.statusView.setVisibility(View.VISIBLE); - if (exception instanceof IOException) { + if (throwable instanceof IOException) { this.statusView.setup(R.drawable.elephant_offline, R.string.error_network, __ -> { this.progressBar.setVisibility(View.VISIBLE); this.onRefresh(); @@ -1192,7 +1185,7 @@ public class TimelineFragment extends SFragment implements } } - Log.e(TAG, "Fetch Failure: " + exception.getMessage()); + Log.e(TAG, "Fetch Failure: " + throwable.getMessage()); updateBottomLoadingState(fetchEnd); progressBar.setVisibility(View.GONE); } diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt index 86b3d09be..b25fec26f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt @@ -17,10 +17,11 @@ package com.keylesspalace.tusky.fragment import android.os.Bundle import android.text.TextUtils +import androidx.fragment.app.Fragment import com.keylesspalace.tusky.ViewMediaActivity import com.keylesspalace.tusky.entity.Attachment -abstract class ViewMediaFragment : BaseFragment() { +abstract class ViewMediaFragment : Fragment() { private var toolbarVisibiltyDisposable: Function0? = null abstract fun setupMediaView( diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java index bf28301ff..5ce66eee2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java @@ -55,7 +55,6 @@ import com.keylesspalace.tusky.di.Injectable; import com.keylesspalace.tusky.entity.Filter; import com.keylesspalace.tusky.entity.Poll; import com.keylesspalace.tusky.entity.Status; -import com.keylesspalace.tusky.entity.StatusContext; import com.keylesspalace.tusky.interfaces.StatusActionListener; import com.keylesspalace.tusky.network.MastodonApi; import com.keylesspalace.tusky.settings.PrefKeys; @@ -75,9 +74,6 @@ import java.util.Locale; import javax.inject.Inject; import io.reactivex.android.schedulers.AndroidSchedulers; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; import static com.uber.autodispose.AutoDispose.autoDisposable; import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from; @@ -463,49 +459,32 @@ public final class ViewThreadFragment extends SFragment implements } private void sendStatusRequest(final String id) { - Call call = mastodonApi.status(id); - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - if (response.isSuccessful()) { - int position = setStatus(response.body()); - recyclerView.scrollToPosition(position); - } else { - onThreadRequestFailure(id); - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - onThreadRequestFailure(id); - } - }); - callList.add(call); + mastodonApi.status(id) + .observeOn(AndroidSchedulers.mainThread()) + .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) + .subscribe( + status -> { + int position = setStatus(status); + recyclerView.scrollToPosition(position); + }, + throwable -> onThreadRequestFailure(id, throwable) + ); } private void sendThreadRequest(final String id) { - Call call = mastodonApi.statusContext(id); - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - StatusContext context = response.body(); - if (response.isSuccessful() && context != null) { - swipeRefreshLayout.setRefreshing(false); - setContext(context.getAncestors(), context.getDescendants()); - } else { - onThreadRequestFailure(id); - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - onThreadRequestFailure(id); - } - }); - callList.add(call); + mastodonApi.statusContext(id) + .observeOn(AndroidSchedulers.mainThread()) + .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) + .subscribe( + context -> { + swipeRefreshLayout.setRefreshing(false); + setContext(context.getAncestors(), context.getDescendants()); + }, + throwable -> onThreadRequestFailure(id, throwable) + ); } - private void onThreadRequestFailure(final String id) { + private void onThreadRequestFailure(final String id, final Throwable throwable) { View view = getView(); swipeRefreshLayout.setRefreshing(false); if (view != null) { @@ -516,7 +495,7 @@ public final class ViewThreadFragment extends SFragment implements }) .show(); } else { - Log.e(TAG, "Couldn't display thread fetch error message"); + Log.e(TAG, "Network request failed", throwable); } } diff --git a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt index 58caec85e..28ac77b6a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt +++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt @@ -56,14 +56,7 @@ interface MastodonApi { @Query("max_id") maxId: String?, @Query("since_id") sinceId: String?, @Query("limit") limit: Int? - ): Call> - - @GET("api/v1/timelines/home") - fun homeTimelineSingle( - @Query("max_id") maxId: String?, - @Query("since_id") sinceId: String?, - @Query("limit") limit: Int? - ): Single> + ): Single>> @GET("api/v1/timelines/public") fun publicTimeline( @@ -71,7 +64,7 @@ interface MastodonApi { @Query("max_id") maxId: String?, @Query("since_id") sinceId: String?, @Query("limit") limit: Int? - ): Call> + ): Single>> @GET("api/v1/timelines/tag/{hashtag}") fun hashtagTimeline( @@ -81,7 +74,7 @@ interface MastodonApi { @Query("max_id") maxId: String?, @Query("since_id") sinceId: String?, @Query("limit") limit: Int? - ): Call> + ): Single>> @GET("api/v1/timelines/list/{listId}") fun listTimeline( @@ -89,7 +82,7 @@ interface MastodonApi { @Query("max_id") maxId: String?, @Query("since_id") sinceId: String?, @Query("limit") limit: Int? - ): Call> + ): Single>> @GET("api/v1/notifications") fun notifications( @@ -97,7 +90,7 @@ interface MastodonApi { @Query("since_id") sinceId: String?, @Query("limit") limit: Int?, @Query("exclude_types[]") excludes: Set? - ): Call> + ): Single>> @GET("api/v1/markers") fun markersWithAuth( @@ -114,12 +107,7 @@ interface MastodonApi { ): Single> @POST("api/v1/notifications/clear") - fun clearNotifications(): Call - - @GET("api/v1/notifications/{id}") - fun notification( - @Path("id") notificationId: String - ): Call + fun clearNotifications(): Single @Multipart @POST("api/v1/media") @@ -146,17 +134,12 @@ interface MastodonApi { @GET("api/v1/statuses/{id}") fun status( @Path("id") statusId: String - ): Call - - @GET("api/v1/statuses/{id}") - fun statusSingle( - @Path("id") statusId: String ): Single @GET("api/v1/statuses/{id}/context") fun statusContext( @Path("id") statusId: String - ): Call + ): Single @GET("api/v1/statuses/{id}/reblogged_by") fun statusRebloggedBy( @@ -295,7 +278,7 @@ interface MastodonApi { @Query("exclude_replies") excludeReplies: Boolean?, @Query("only_media") onlyMedia: Boolean?, @Query("pinned") pinned: Boolean? - ): Call> + ): Single>> @GET("api/v1/accounts/{id}/followers") fun accountFollowers( @@ -398,14 +381,14 @@ interface MastodonApi { @Query("max_id") maxId: String?, @Query("since_id") sinceId: String?, @Query("limit") limit: Int? - ): Call> + ): Single>> @GET("api/v1/bookmarks") fun bookmarks( @Query("max_id") maxId: String?, @Query("since_id") sinceId: String?, @Query("limit") limit: Int? - ): Call> + ): Single>> @GET("api/v1/follow_requests") fun followRequests( @@ -415,20 +398,10 @@ interface MastodonApi { @POST("api/v1/follow_requests/{id}/authorize") fun authorizeFollowRequest( @Path("id") accountId: String - ): Call - - @POST("api/v1/follow_requests/{id}/reject") - fun rejectFollowRequest( - @Path("id") accountId: String - ): Call - - @POST("api/v1/follow_requests/{id}/authorize") - fun authorizeFollowRequestObservable( - @Path("id") accountId: String ): Single @POST("api/v1/follow_requests/{id}/reject") - fun rejectFollowRequestObservable( + fun rejectFollowRequest( @Path("id") accountId: String ): Single diff --git a/app/src/main/java/com/keylesspalace/tusky/repository/TimelineRepository.kt b/app/src/main/java/com/keylesspalace/tusky/repository/TimelineRepository.kt index 8aa3eb769..945c55d33 100644 --- a/app/src/main/java/com/keylesspalace/tusky/repository/TimelineRepository.kt +++ b/app/src/main/java/com/keylesspalace/tusky/repository/TimelineRepository.kt @@ -66,9 +66,9 @@ class TimelineRepositoryImpl( sinceIdMinusOne: String?, limit: Int, accountId: Long, requestMode: TimelineRequestMode ): Single> { - return mastodonApi.homeTimelineSingle(maxId, sinceIdMinusOne, limit + 1) - .map { statuses -> - this.saveStatusesToDb(accountId, statuses, maxId, sinceId) + return mastodonApi.homeTimeline(maxId, sinceIdMinusOne, limit + 1) + .map { response -> + this.saveStatusesToDb(accountId, response.body().orEmpty(), maxId, sinceId) } .flatMap { statuses -> this.addFromDbIfNeeded(accountId, statuses, maxId, sinceId, limit, requestMode) @@ -85,7 +85,7 @@ class TimelineRepositoryImpl( private fun addFromDbIfNeeded(accountId: Long, statuses: List>, maxId: String?, sinceId: String?, limit: Int, requestMode: TimelineRequestMode - ): Single>? { + ): Single> { return if (requestMode != NETWORK && statuses.size < 2) { val newMaxID = if (statuses.isEmpty()) { maxId diff --git a/app/src/test/java/com/keylesspalace/tusky/fragment/TimelineRepositoryTest.kt b/app/src/test/java/com/keylesspalace/tusky/fragment/TimelineRepositoryTest.kt index 16d0e2717..7a7c3f7d2 100644 --- a/app/src/test/java/com/keylesspalace/tusky/fragment/TimelineRepositoryTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/fragment/TimelineRepositoryTest.kt @@ -28,6 +28,7 @@ import org.mockito.ArgumentMatchers.* import org.mockito.Mock import org.mockito.MockitoAnnotations import org.robolectric.annotation.Config +import retrofit2.Response import java.util.* import java.util.concurrent.TimeUnit import kotlin.collections.ArrayList @@ -76,8 +77,8 @@ class TimelineRepositoryTest { makeStatus("3"), makeStatus("2") ) - whenever(mastodonApi.homeTimelineSingle(isNull(), isNull(), anyInt())) - .thenReturn(Single.just(statuses)) + whenever(mastodonApi.homeTimeline(isNull(), isNull(), anyInt())) + .thenReturn(Single.just(Response.success(statuses))) val result = subject.getStatuses(null, null, null, limit, TimelineRequestMode.NETWORK) .blockingGet() @@ -107,8 +108,8 @@ class TimelineRepositoryTest { ) val sinceId = "2" val sinceIdMinusOne = "1" - whenever(mastodonApi.homeTimelineSingle(null, sinceIdMinusOne, limit + 1)) - .thenReturn(Single.just(response)) + whenever(mastodonApi.homeTimeline(null, sinceIdMinusOne, limit + 1)) + .thenReturn(Single.just(Response.success(response))) val result = subject.getStatuses(null, sinceId, sinceIdMinusOne, limit, TimelineRequestMode.NETWORK) .blockingGet() @@ -141,8 +142,8 @@ class TimelineRepositoryTest { ) val sinceId = "2" val sinceIdMinusOne = "1" - whenever(mastodonApi.homeTimelineSingle(null, sinceIdMinusOne, limit + 1)) - .thenReturn(Single.just(response)) + whenever(mastodonApi.homeTimeline(null, sinceIdMinusOne, limit + 1)) + .thenReturn(Single.just(Response.success(response))) val result = subject.getStatuses(null, sinceId, sinceIdMinusOne, limit, TimelineRequestMode.NETWORK) .blockingGet() @@ -181,8 +182,8 @@ class TimelineRepositoryTest { val sinceId = "2" val sinceIdMinusOne = "1" val maxId = "3" - whenever(mastodonApi.homeTimelineSingle(maxId, sinceIdMinusOne, limit + 1)) - .thenReturn(Single.just(response)) + whenever(mastodonApi.homeTimeline(maxId, sinceIdMinusOne, limit + 1)) + .thenReturn(Single.just(Response.success(response))) val result = subject.getStatuses(maxId, sinceId, sinceIdMinusOne, limit, TimelineRequestMode.NETWORK) .blockingGet() @@ -224,8 +225,8 @@ class TimelineRepositoryTest { val sinceId = "2" val sinceIdMinusOne = "1" val maxId = "4" - whenever(mastodonApi.homeTimelineSingle(maxId, sinceIdMinusOne, limit + 1)) - .thenReturn(Single.just(response)) + whenever(mastodonApi.homeTimeline(maxId, sinceIdMinusOne, limit + 1)) + .thenReturn(Single.just(Response.success(response))) val result = subject.getStatuses(maxId, sinceId, sinceIdMinusOne, limit, TimelineRequestMode.NETWORK) .blockingGet() @@ -263,8 +264,8 @@ class TimelineRepositoryTest { dbResult.status = dbStatus.toEntity(account.id, gson) dbResult.account = status.account.toEntity(account.id, gson) - whenever(mastodonApi.homeTimelineSingle(any(), any(), any())) - .thenReturn(Single.just(listOf(status))) + whenever(mastodonApi.homeTimeline(any(), any(), any())) + .thenReturn(Single.just(Response.success((listOf(status))))) whenever(timelineDao.getStatusesForAccount(account.id, status.id, null, 30)) .thenReturn(Single.just(listOf(dbResult))) val result = subject.getStatuses(null, null, null, limit, TimelineRequestMode.ANY) @@ -281,8 +282,8 @@ class TimelineRepositoryTest { val dbResult2 = TimelineStatusWithAccount() dbResult2.status = Placeholder("1").toEntity(account.id) - whenever(mastodonApi.homeTimelineSingle(any(), any(), any())) - .thenReturn(Single.just(listOf(status))) + whenever(mastodonApi.homeTimeline(any(), any(), any())) + .thenReturn(Single.just(Response.success(listOf(status)))) whenever(timelineDao.getStatusesForAccount(account.id, status.id, null, 30)) .thenReturn(Single.just(listOf(dbResult, dbResult2))) val result = subject.getStatuses(null, null, null, limit, TimelineRequestMode.ANY) From dc5f0c87a230b7be948191917617fd9b6a2938a9 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Sun, 31 Jan 2021 19:35:39 +0100 Subject: [PATCH 30/75] remove let's encrypt root cert again (#2056) * remove let's encrypt root cert again * fix user-agent example --- .../keylesspalace/tusky/di/NetworkModule.kt | 41 ++++++- .../keylesspalace/tusky/util/OkHttpUtils.kt | 115 ------------------ 2 files changed, 38 insertions(+), 118 deletions(-) delete mode 100644 app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.kt diff --git a/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt index ca60bac8e..fb232a64c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt @@ -16,6 +16,8 @@ package com.keylesspalace.tusky.di import android.content.Context +import android.content.SharedPreferences +import android.os.Build import android.text.Spanned import com.google.gson.Gson import com.google.gson.GsonBuilder @@ -24,15 +26,20 @@ import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.json.SpannedTypeAdapter import com.keylesspalace.tusky.network.InstanceSwitchAuthInterceptor import com.keylesspalace.tusky.network.MastodonApi -import com.keylesspalace.tusky.util.okhttpClient +import com.keylesspalace.tusky.util.getNonNullString import dagger.Module import dagger.Provides +import okhttp3.Cache +import okhttp3.OkHttp import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.converter.gson.GsonConverterFactory import retrofit2.create +import java.net.InetSocketAddress +import java.net.Proxy +import java.util.concurrent.TimeUnit import javax.inject.Singleton /** @@ -54,9 +61,37 @@ class NetworkModule { @Singleton fun providesHttpClient( accountManager: AccountManager, - context: Context + context: Context, + preferences: SharedPreferences ): OkHttpClient { - return okhttpClient(context) + val httpProxyEnabled = preferences.getBoolean("httpProxyEnabled", false) + val httpServer = preferences.getNonNullString("httpProxyServer", "") + val httpPort = preferences.getNonNullString("httpProxyPort", "-1").toIntOrNull() ?: -1 + val cacheSize = 25 * 1024 * 1024L // 25 MiB + val builder = OkHttpClient.Builder() + .addInterceptor { chain -> + /** + * Add a custom User-Agent that contains Tusky, Android and OkHttp Version to all requests + * Example: + * User-Agent: Tusky/1.1.2 Android/5.0.2 OkHttp/4.9.0 + * */ + val requestWithUserAgent = chain.request().newBuilder() + .header( + "User-Agent", + "Tusky/${BuildConfig.VERSION_NAME} Android/${Build.VERSION.RELEASE} OkHttp/${OkHttp.VERSION}" + ) + .build() + chain.proceed(requestWithUserAgent) + } + .readTimeout(30, TimeUnit.SECONDS) + .writeTimeout(30, TimeUnit.SECONDS) + .cache(Cache(context.cacheDir, cacheSize)) + + if (httpProxyEnabled && httpServer.isNotEmpty() && httpPort > 0 && httpPort < 65535) { + val address = InetSocketAddress.createUnresolved(httpServer, httpPort) + builder.proxy(Proxy(Proxy.Type.HTTP, address)) + } + return builder .apply { addInterceptor(InstanceSwitchAuthInterceptor(accountManager)) if (BuildConfig.DEBUG) { diff --git a/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.kt b/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.kt deleted file mode 100644 index 3e1b89c6a..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* 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 . */ - -package com.keylesspalace.tusky.util - -import android.content.Context -import android.os.Build -import androidx.preference.PreferenceManager -import com.keylesspalace.tusky.BuildConfig -import okhttp3.Cache -import okhttp3.OkHttp -import okhttp3.OkHttpClient -import okhttp3.tls.HandshakeCertificates -import java.io.ByteArrayInputStream -import java.net.InetSocketAddress -import java.net.Proxy -import java.security.cert.CertificateFactory -import java.security.cert.X509Certificate -import java.util.concurrent.TimeUnit - -fun okhttpClient(context: Context): OkHttpClient.Builder { - val preferences = PreferenceManager.getDefaultSharedPreferences(context) - - val httpProxyEnabled = preferences.getBoolean("httpProxyEnabled", false) - val httpServer = preferences.getNonNullString("httpProxyServer", "") - val httpPort = preferences.getNonNullString("httpProxyPort", "-1").toIntOrNull() ?: -1 - - val cacheSize = 25 * 1024 * 1024 // 25 MiB - val builder = OkHttpClient.Builder() - .addInterceptor { chain -> - /** - * Add a custom User-Agent that contains Tusky, Android and Okhttp Version to all requests - * Example: - * User-Agent: Tusky/1.1.2 Android/5.0.2 - * */ - val requestWithUserAgent = chain.request().newBuilder() - .header( - "User-Agent", - "Tusky/${BuildConfig.VERSION_NAME} Android/${Build.VERSION.RELEASE} OkHttp/${OkHttp.VERSION}" - ) - .build() - chain.proceed(requestWithUserAgent) - } - .readTimeout(30, TimeUnit.SECONDS) - .writeTimeout(30, TimeUnit.SECONDS) - .cache(Cache(context.cacheDir, cacheSize.toLong())) - - if (httpProxyEnabled && httpServer.isNotEmpty() && httpPort > 0 && httpPort < 65535) { - val address = InetSocketAddress.createUnresolved(httpServer, httpPort) - builder.proxy(Proxy(Proxy.Type.HTTP, address)) - } - - // trust the new Let's Encrypt root certificate that is not available on Android < 7.1.1 - // new cert https://letsencrypt.org/certs/isrgrootx1.pem - // see https://letsencrypt.org/2020/11/06/own-two-feet.html - // see https://stackoverflow.com/questions/64844311/certpathvalidatorexception-connecting-to-a-lets-encrypt-host-on-android-m-or-ea - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - val isgCert = """ - -----BEGIN CERTIFICATE----- - MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw - TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh - cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 - WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu - ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY - MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc - h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ - 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U - A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW - T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH - B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC - B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv - KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn - OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn - jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw - qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI - rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV - HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq - hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL - ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ - 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK - NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 - ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur - TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC - jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc - oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq - 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA - mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d - emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= - -----END CERTIFICATE----- - """.trimIndent() - val cf = CertificateFactory.getInstance("X.509") - val isgCertificate = cf.generateCertificate(ByteArrayInputStream(isgCert.toByteArray(charset("UTF-8")))) - val certificates = HandshakeCertificates.Builder() - .addTrustedCertificate(isgCertificate as X509Certificate) - .addPlatformTrustedCertificates() - .build() - builder.sslSocketFactory( - certificates.sslSocketFactory(), - certificates.trustManager - ) - } - return builder -} From 3e135b04805f43993a624358fbb4a209e80d7f45 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Sun, 31 Jan 2021 19:35:53 +0100 Subject: [PATCH 31/75] upgrade gradle to 6.8.1 (#2057) --- gradle.properties | 2 ++ gradle/wrapper/gradle-wrapper.jar | Bin 58910 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 +- gradlew.bat | 21 +++------------------ 5 files changed, 7 insertions(+), 20 deletions(-) diff --git a/gradle.properties b/gradle.properties index 8144ece09..bada7909d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,6 +14,8 @@ org.gradle.jvmargs=-Xmx4096m # use parallel execution org.gradle.parallel=true +# enable file system watching +org.gradle.vfs.watch=true android.enableR8.fullMode=true android.useAndroidX=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 62d4c053550b91381bbd28b1afc82d634bf73a8a..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644 GIT binary patch delta 6656 zcmY+Ibx_pN*Z*PZ4(U#j1qtbvrOTyO8fghZ8kYJfEe%U|$dV!@ASKczEZq$fg48M@ z;LnHO_j#Uq?%bL4dY^md%$$4Y+&@nKC|1uHR&59YNhubGh72|a#ylPdh9V+akp|I; zPk^W-a00GrFMkz_NSADdv2G2-i6rb=cB_@WnG(**4ZO$=96R=t|NZ@|0_z&q3GwO^ ziUFcuj$a9QaZ3j?xt`5#q`sT-ufrtBP0nt3IA&dr*+VCsBzBVW?vZ6eZr0oD%t33z zm~-5IVsjy(F>;S~Pm@bxX85>Z*@(QL6i3JQc?1ryQFcC@X^2^mZWhFv|v? z49>l|nA&XNQ6#OvccUTyBMB*WO#NA;FW5|eE_K6dtVYP2G?uUZ09!`Iq1IF2gA(aS zLu@G^cQJmh=x?-YsYa@E6QnE5+1@ds&0f#OQRDl^GnIT_m84G5XY%W z;Ck6bk^Oeu*Ma-XmxI5GjqzWNbJMsQF4)WfMZEA{oxW0E32e)*JfG}3otPishIQBw zkBe6N#4pKPN>q1R6G1@5&(u#5yPEToMBB6_oEK|q z@(i5j!?;NNCv~=HvW%zF&1yWBq(nJa_#``G&SRmQvE|jePUPs{J!$TacM|e}Fsceb zx+76|mDp6@w>)^DIl{8?)6XYNRU|2plG8Jy&7(^9SdOWNKKJK&>0!z6XiN4J*Jkao z=E1y5x-XDC==Ub+8fLb#OW&{2ww{h^xlJFYAMOUd)}Xg@j?ak{7Kno6?9S~F?|6Df zHo|ijXX~`Sp;Vf!nR;m%vUhq>zvlRXsL0u*Tt?F#yR}3tF0#of{(UjitqST|!{aBA zicWh+URU}Jnc*sg9iMkf0pggpd?3TI*C-q$2QOdCC7rV+CHBmjS3O%a3VeZ$ZSs5ubJuJp%e%$LHgrj0niYjX;4kt z&2~j%@q3MO)-QGCA{>o%eZu){ou^MgC6~Z8Y=tc!qF=|TOlG3wJXbaLYr-;$Ch=2J z_UcE59Xzq&h0LsjLrcZrQSa}#=0~Lk|4?e4M z6d;v->NCC1oMti)RRc`Ys0?JXQjsZ@VdCy%Z)TptCrI>0Tte$pR!@yJesoU2dtyuW z7iFsE8)CkbiJP+OP28;(%?!9WddQZcAid@R@`*e%3W65$g9ee`zvwb(VPO+uVBq6p z{QDR%CR(2z@?&9Obm3xPi2lzvfip`7q`_7UDD|lRS}4=bsl3xQIOi0@GSvMuDQX}* z4B^(DI<${qUhcLqO`itJU;e<%%iS+R3I^_xIV1O%sp*x~;-dn` zt$8>RnSUh#rU3{-47067W^WNwTdq-t$-U>Hj%r!GD!gLa;kV zW5g6pCqV+!q8LgrI49(}fIc5K_`FLV4_E#XZ6{<>w8wzc%V9k!!Byg5-0WY+J?1*z%9~Aj4WQr1Jsn2(G!U8fFpi(wsy@JLg^d+IB0kl89 z0@Ssqf!L9JjYKK$J=978+NO*5^C)GPH2a%4hm$HROjM|N3g9ch9kDLh*nlwqy{mVM z`P(l#>3NnK%#O8tSb(VmZrG+`dRD#=Cc1P%(y5S?*Hj5E{vg&Eiw!YV>S#7_WRDVoFxT5m=gFi4)}y5V%KT8!xbsH_rmR& zsmM?%J}K$1l8d?2+m(}2c}-G`x>CY%Y&QBJRC$sKM}zN<9{IlF@yJEG<^0={$+`Hc zDodJ)gCADJ_bD#am(c2ojXKb|j+ENJ#58PAA&pZXufrFzBwnuuo+khfMgd!DMlU#v z9|JelQO~E2;d^w!RZJbt%IANIudpKSP)cssoWhq)>({nvcfCr0=9=FAIMuZm8Eo=} z|DND}8_PB5HqG(QwDvaM@orYBZ9kCkHV*rxKTy>q7n~0emErUwLbhq;VN<2nKT&*a2Ajz z;lKBzU2i8KLV`d)Y&ae)!HcGk$dO}Or%8KF@kE@jU1h@zwpw{6p4ME|uC$Za-ERR2 ztQvL&uOZLe(k{w_+J^ng+l}~N8MP>F1Z$fLu}D-WWaeu#XduP@#8JpmH(X>rIL)k3 zyXNyTIB1(IH%S&pQ{rWaTVfB$~-;RnlY z^(y7mR>@=brI>!TrA)BQsQ={b*6$=1Eqbuu6IdhJ&$YD$08AwtNr9*J?%-WT<;O1< zPl1<@yeqfZ>@s4azqTf<=I4(kU^+^Qkstm%WM-0_VLm({jFc8`5Df2Q1Y9zMZu0^! zsO_yh2Sz9K>Jq6fkYbBZocEJ6C!SdEzYDkiEtNJs{?!tA#e|oiN+VaaAobwKef_kUup&4scD?1+}Q8)DaekkMYn-FOS{J%NY za^mmJ^n`t*1p@hF*gl#L+5wr40*(ub4J#L|@oCl~@|4UvCjHBYDQv&S zhyGMAkRO^tF_dyi&XM)4mQ;k>kj?RgRo@-?==oD+ns*>bf@&fPXF|4U0&ib2 zo~1ZdmCPWf!W9#sGP@9X$;Rc`tjbz^&JY}z{}j9bl?;VC{x)TfQH$D^WowKL&4Zx@ zdSn+QV7H(e0xRfN6aBfH)Q=@weoD?dvu6^ZS)zqb>GwMmIuS8zJfaMUQx9>%k~w34 z3}_B2Jj~u=SnJ~vZPj*)UoDi_FtT=UAb#J^b4B%R6z3H%cj-1OCjU5F$ky>By1zsg z>2A0ccp29(Y<;my|J_g-r{1I@+*O$>!R3`_sFNP4e}LD1e1mM&SA`;;TR0I`_hESV zh4U*9ecK$0=lYk`{SR_cm$}iS*?yQR(}T-5ub?Wn^#RTe*^1~ya%`!xWq-F*WH@%nnZTNREA z3eUX2uM9b_w!Zo$nVTotEtzuL(88N)H~v_G=89|(@IFz~Wq6ME);z(!2^PkR2B&kE zxR)xV8PE|Hszyjp#jNf=ZIQ7JR~4Ls#Vd@mPF(7R5VO$akUq8JM+sn>ZVg(lJZ)5qjqdw(*7tuwjY#0tx+|!sTz9yV~%HOdrb#!5w9>*0LrCS z%wF$Yc6~hqVQZzoC^D<(-h0aOtk}kn<<*xF61HQr<5}efY{zXXA+PaJG7vT&{Oz(@Uu!V#Fp9%Ht!~@;6AcD z$lvlPu&yd(YnAHfpN51*)JN0aYw9gGk{NE7!Oqu4rBp}F30669;{zcH-a7w9KSpDQPIE_f9T zit? zJSjTKWbe{f{9BmSDAFO1(K0oqB4578tU0(oRBE^28X>xDA!1C&VJEiYak4_ZTM*7M`hv_ zw3;2ndv3X$zT!wa7TrId{gNE`Vxf}j5wsyX+;Kn<^$EJT`NzznjyYx=pYMkZjizEU zb;Gg8Pl_pqxg)9P)C)Hxh_-mQ;u-I_Ol>d^>q08zFF!>Z3j1-HmuME_TGZ*Ev;O0O z%e(edJfV<6t3&FKwtInnj9EeQhq9;o5oLJoiKwWF5bP2~Feh#P4oN()JT0pdq!9x* ze3D-1%AV#{G=Op$6q?*Z>s{qFn}cl@9#m@DK_Bs@fdwSN`Qe18_WnveRB583mdMG- z?<3pJC!YljOnO8=M=|Cg)jw;4>4sna`uI>Kh&F20jNOk9HX&}Ry|mHJ+?emHnbYLJ zwfkx@slh31+3nq-9G5FVDQBHWWY}&hJ-fpDf!lQdmw8dlTt#=)20X74S>c&kR(?PT zBg)Y%)q&|hW1K;`nJPAGF*c3{3`FvrhD9=Ld{3M*K&5$jRhXNsq$0CLXINax1AmXX ziF39vkNtcK6i^+G^AEY!WalGazOQ$_#tx?BQ{YY$&V&42sICVl8@AI6yv;sGnT;@f zL=}rZcJqNwrEEA=GDdEe8Z=f9>^?($oS8xGdFf1eUWTYtZF<3tu2V%noPBnd=thZ+ zO&xoc?jvXG7Xt!RTw#5VN50UjgqSntw9Y35*~pxz=8OzkXg{@S2J%+{l3Q>B_qbnl z20Deb7JM&ZSp`%X>xWpb>FF8q7Nq&4#a1}A-(-!aMDmVbz05D!NpUzVe{~72h%cOh zwQFNai2a$K|hFgDk(oPF_tuf{BV!=m0*xqSzGAJ(~XUh8rk#{YOg0ReK>4eJl z;-~u5v$}DM)#vER>F)-}y(X6rGkp<{AkiPM7rFgAV^)FUX8XmCKKaWlS4;MSEagj$ z#pvH`vLX1q{&eOm>htnk4hmv=_)ao!MCp}9ql5yfre&Py!~hBAGNBa}PH&J8K=~<% z&?!J-QaH|0bq_uo6rt*r-M>d7jm1cbW^T>s)S?L{n8v`^?VIPA+qi^6e@cM|5boqEO!p1e|_{7U3Yl6K?0xMN1bbjf0@$TE-T))w> zFe?E?g$PUT-)AJ(PS^By^D^Ed!K5iv$*_eW~VA(I3~UMy*ZcgVu0$XZC*_0PgDmUL)qTCn927LD~p$yXR_GCJ&iQ; z4*`%l-dC5pALH!y*nmhdHRh02QjW1vZL4ySucz*w3f|#`=u@@YvMV1?i!&DIa2+S< z8z!gvN3FV4I;%fl;ruFeV{jKjI~?GlgkmGBuJ<7vY|l3xMOc?S@Q#C(zo*m&JLrjT2rU9PYOniB8O~yO5<1CCcQz# z17B2m1Z{R!Y)UO#CU-Y&mOlv4*Gz%rC_YkRcO)jTUEWHDvv!GWmEihE>OKPx1J?Av z8J{-#7NsT>>R#*7**=QL)1@IR77G9JGZZiVt!=jD+i(oRV;I`JkiTSZkAXuHm-VG1 z+2-LD!!2dNEk@1@Rp|C$MD9mH^)H*G*wI(i*Rc6Vvdik+BDycYQ*=0JA3dxxha|Zg zCIW1Ye-DdpMGTEwbA^6hVC<(@0FL4dkDOYcxxC5c%MJQ^)zpA%>>~Q|Y=@)XW!px; z_Fx+xOo7>sz4QX|Ef~igE+uFnzFWP<-#||*V0`0p7E*+n5+awuOWmvR{-M*chIXgo zYiZvQMond#{F8+4Zh_;>MsaZUuhp=onH@P!7W>sq|CWv|u}Wg0vo&f4UtmLzhCwwu zJaR=IO;sQxS}h(K>9VZjnED+>9rGgB3ks+AwTy_EYH{oc)mo`451n&YH%A1@WC{;1 z=fB6n zIYp46_&u`COM&Di?$P}pPAlAF*Ss<)2Xc?=@_2|EMO?(A1u!Vc=-%bDAP#zDiYQvJ z0}+}3GaLxsMIlh6?f=iRs0K=RyvMOcWl*xqe-IBLv?K{S^hP)@K|$I+h_)pdD9r~! zxhw2u66+F(E`&6hY}B_qe>wil|#*0R0B;<@E?L zVrhXKfwRg0l8r>LuNs1QqW&39ME0sOXe8zycivGVqUOjEWpU)h|9fwp@d(8=M-WxY zeazSz6x5e`k821fgylLIbdqx~Kdh^Oj`Q!4vc*Km)^Tr-qRxPHozdvvU^#xNsKVr6aw8={70&S4y*5xeoF@Q^y596*09`XF56-N z1=Rm5?-An178o?$ix}y7gizQ9gEmGHF5AW+92DYaOcwEHnjAr~!vI>CK%h`E_tO8L Yte!%o?r4GTrVtxD61Ym!|5fq-1K$0e!T1w z1SC8j)_dObefzK9b=~*c&wBRW>;B{VGKiBofK!FMN5oJBE0V;;!kWUz!jc1W?5KdY zyZ3mCBHprpchz-9{ASiJJh&&h1|4rdw6wxD2+9= z#6#}Uq8&^1F3wgvGFoNDo?bIeEQXpcuAR0-+w$JWoK-@yUal1M&~W_O)r+Rx;{@hWH5n^oQWR36GMYBDDZyPK4L@WVjRrF+XlSzi4X4!_!U%Uujl6LHQ#|l(sUU%{ zefYd8jnVYP91K}Qn-OmmSLYFK1h~_}RPS~>+Xdz%dpvpJ{ll!IKX=JN99qowqslbO zV3DmqPZ}6>KB!9>jEObpi$u5oGPfO3O5!o3N2Mn`ozpje<}1I1H)m2rJDcB7AwXc6 z6j)tnPiql7#)r+b+p9?MVahp&=qJ^$oG+a^C*);FoJ!+V*^W+|2Olx5{*&$bXth)U zejc7mU6cBp?^Rj|dd{GL-0eHRTBi6_yJ&GLP5kIncv^z{?=0AVy^5{S8_n=rtua!J zFGY=A(yV^ZhB}1J_y(F`3QTu+zkHlw;1GiFeP&pw0N1k%NShHlO(4W+(!wy5phcg4 zA-|}(lE_1@@e6y`veg;v7m;q%(PFG&K3#}eRhJioXUU0jg_8{kn$;KVwf;zpL2X_( zC*_R#5*PaBaY73(x*oZ}oE#HPLJQRQ7brNK=v!lsu==lSG1(&q>F)`adBT~d*lMS| z%!%7(p~<7kWNmpZ5-N31*e=8`kih|g5lVrI%2wnLF-2D+G4k6@FrYsJ_80AJ}KMRi>) z-kIeHp{maorNWkF81v0FKgB==_6blyaF$5GaW)B!i4v*jNk6r)vU6?G$0pV8(Y+UK z5lgRVt%;N_gWp)^osv=h+^07UY6+$4^#t=M3>0i0`{`aEkFLL#a)93uXhYO+aKTtu zckg2T9S&GKNtZmdAS^8PzvDva-%-K&g9eqPXQ4$dM^inr@6Zl z{!Cq&C_+V;g*{>!0cZP}?ogDb$#ZS=n@NHE{>k@84lOkl&$Bt2NF)W%GClViJq14_ zQIfa^q+0aq){}CO8j%g%R9|;G0uJuND*HO$2i&U_uW_a5xJ33~(Vy?;%6_(2_Cuq1 zLhThN@xH7-BaNtkKTn^taQHrs$<<)euc6z(dhps>SM;^Wx=7;O&IfNVJq3wk4<1VS z-`*7W4DR_i^W4=dRh>AXi~J$K>`UqP>CKVVH&+T(ODhRJZO7DScU$F7D)di-%^8?O z6)Ux`zdrVOe1GNkPo0FgrrxSu1AGQkJe@pqu}8LkBDm+V!N_1l}`tjLW8${rgDLv3m@E*#zappt-Mm zSC<$o+6UO~w0C=(0$&*y**@nKe_Q{|eAuD!(0YL0_a{z%+sdfSyP={Nyd$re6Rzbp zvsgTY7~VflX0^Vf7qqomYZ_$ryrFVV2$sFyzw2r%Q8*uYDA+)iQdfKms_5(>!s#!( z!P5S(N0i9CKQKaqg(U%Gk#V3*?)lO6dLv`8KB~F<-%VhbtL8Rl>mEz+PN=qx&t*|= zQHV=qG)YKlPk4iCyWIUGjC?kpeA>hIBK*A?B0)rB=RqAal#D%1C9yVQwBcz${#Jb5 zR{TRmMrOrJsLc&6x9qDo@FJ^=do_Y?3oU0G^nV5_EU&+DS+VA7Tp{^TAF>yZbyM3c zf*1CqHY9T|aL_lyY7c)i!_MtGPA!sdy3|mrsKVj1mi&>dms@-ozSa}OZ?2I*tAndg z@S7er$t^d^-;!wLQbG60nWd@1pQVD7tw-G_B#OscoYyremiZ_hj8*sXqQdchuD^!R zpXGuSj5psk+jR>3rWu3^`17>j&*^9^rWbszP=Mf@5KIEj%b=z98v=Ymp%$FYt>%Ld zm8})EDbNOJu9n)gwhz_RS``#Ag)fr)3<*?(!9O~mTQWeh;8c;0@o=iBLQNqx3d_2#W7S9#FXzr6VXfs>4 z;QXw}-STvK9_-7H=uqgal2{GkbjVLN+=D5ddd)4^WvX;(NYA*X*(JxTdiUzqVJopd zQg#~psX4o<)cF>r=rxP`(Xsf<+HG-pf&7aFPL8z|-&B*P?Vmsu5d>Nlg^2$WRY!S@#`g2{81;(1w#o5HsvN}5pFZi});>|VK^kL{Zkx~wgn ztlZp;HW`H8(GdRfIwc~?#N6}o#h158ohI*GIsK%56I_9sf2k_K@4vD!l{(dX9E7PJ;w>$|Y;-VBJSO4@){07bo-89^LZ9g<<%;dOl zyIq{s8`8Ltp*GDwu(l_Z$6sA2nam$BM$Q~6TpZg)w2TtW?G5whV(lRwaf$6EU86is zBP9Rs&vS_~sk?Nn_b}^HkM8LiO@>J}=g(T4hLmvH@5Jj#2aHa~K)lD9VB0k>$V2BP zgh;(=y9Op(KQ=H5vj+%qs>?s4tYN~-Q|fyQePA)s?HrF~;l!+@t8VMzqUpqMLudFT z)=o~s!MM4XkgbetIsODwtQ=FF$IcIp&!pjh6Q6{tL+l*7GQ%8Wsg(tC#qU3oW$~n) zL=>XIxI}Hi7HS0F_mmi+(c%1HDuKiWm>|6Xa}nW7ei55ggru9)xjBvC#JcEIN*#cp zv*ACvr=HTC?dX9NNo9Yhulu_gX5Z~}QQ2&QZ&C77{(>Y3_ z6j5Z1Uc5FtPEpS_31HsgmSLHZijGb_p$WlRJ1p^_1!ZLP8kr6OtCEK7Qh267o$H>e zf<4cNGQRk{g5h$XfvTFQ@`qm@iju83-~}ebAYpZryARHVR$AEt3229U{y@Fp4 z-8FBBtGG&(hTyUdx5ZOfiz`c=<0F%+w|Fl=rWk{K7>70k04SN?RU(^mrKSeKDqA!K^Hsv8C?#ioj4@WUL zC*?{hTai6q0%_oBTqDHygp_Kl;({sAScYQIwMDM1U>{x0ww zve?_}E;DG?+|zsUrsph5X_G7l#Y~vqkq3@NNDabbw7|`eJBmn`Qrlr%?`va=mm$Mc{+FBbQbogAZ6{MuzT|P%QZZotd21eb1hfj|;GYAX&>bx#D5EB+=XMj2XJkpnyMUykaVo) zj3ZLqEl1&)Rturc8m@+uUuD^vaNaSxGwP4dq0-OSb~62lPv8E_K4usLvG{Qg zdR%z8dd2H!{JaT|X_bfm{##*W$YM;_J8Y8&Z)*ImOAf4+| zEyi)qK%Ld1bHuqD+}-WiCnjszDeC-%8g+8JRpG1bOc!xUGB?@?6f~FTrI%U#5R~YF z%t5(S2Q>?0`(XNHa8xKdTEZ~Z4SJOheit#ldfdg63}#W6j8kO;SjQD`vftxS+#x1B zYu|5szEvkyz|}|B3x|DNlyi$;+n+cW$Hu+?)=X1!sa%{H-^;oBO9XACZJ}wkQ!sTa zQ#J3h|HX{{&WwIG3h7d6aWktuJaO)ie6&=KJBoX@w(rBWfin`*a6OmCC5M0HzL(gv zY<*e4hmW>SWVhxk-`UGOAbD%Hk+uu<^7zJ_ytVXamfqCd0$g+W08>?QAB}Cv{b}eM z@X}ILg+uT%>-6`A25p@uhS3%;u>ccSq}8|H_^o&`nBT5S0y z;2H0I^(4MO*S+(4l$gULc4KSeKvidto5Nl0P|%9CqQ*ikY!w_GUlo}sb9HYB=L^oFpJ zfTQskXW!LFVnUo4(OHPDaZSf3zB|3{RGu1>ueE$(+dr?tT zp!SGlqDU8vu{5xLWSvj+j$arHglg54#Lx&TvuO3LIIU>hF9Uoj&=-b*Q?uYr`#V?xz?2 zhirZrv^eA{k%{hFh%9LYVXEYWd5#PuUd1QqaqB*J!CMXEM>fEB$@#1>mtB`Bfil}t zhhTIObqh5HRvT+4q_Do$Q*Jika?qV=Np-DtPkU z(KoXyWLfPwr@UY1)hBAvR3nCBZgd|CevTG?H~HqDF}dzy%2sd2`f{^CBbTk*^K~RO zN~O0+2EjAJlywF%SjgYz810l&G5AqzI<=Ber{912^PpSPRJl3dm8W@dKHL}7_@k3)Y!SXYkyxQy>Q4I2o zr`ev7fLF$1t96h|sH<-#*YzGD-b^3$_!#wsh(Yw;)b@udLz9mm`mFYh z1Zz24KIQJ(*_-E0(3&1InqG;U?wF)GYd>DFo(em`#|UaaYmkA9;GTX7b?0@C@QkTVpGD#mf$dQoRNV=n{^Zi_W*ps;3?^$s`0;ER7;==~OmQ~9 zS5P=FjxE5%|;xq6h4@!_h?@|aK&FYI2IT(OHXv2%1 zWEo-v!L7x^YT(xLVHlpJttcwaF@1Y;-S*q3CRa!g7xdzl|Jan>2#dI0`LKl!T1GMk zRKe4|bQO&ET}Z^Aiym*HII>cSxIzl|F~JEUGxz;+DB=8fxXhnBI4R12q6ews$lA`Jfi}r@A@-)6TOAUMNYFYJ zZ-Zd?lxFTyjN3mXnL!%#>Z%$0gJ4*9g;e;@zSmQ{eGGDaRRNM3s@6!;hYuVc=c+3B z=qzNNS~n^EsJU4aOGE|mdy={C^lPKEfPL-IJAsTpQsDgZ@~s+eHZYmp9yb=YW_4r?lqQaYZQ`nau){W`LY#P)>i zq^wHEuOYs#FlPZeMuT@Etb@~A6feCebq`miJE3w+gAL%bVF_s*5e*@)?xmKSo%I3? zLELHVdWia$}~s6 zr!^LfxSSB4Td&9iTXrzQpl5ZDo#SdmNr;23QsPHQ!x!UT9xtb!Ycz^JF8x)%cFOXK z^EXw%dRz_VD}7?RU^4{)1+xFO=z!EI8IUa3U*rag=1BpHX$Xi<__kSbS{y_xa*MJv z_`thq0Z^sPzjAk48ssDQj}!$N8Q$XC84(bU$t_Bm69Jf+C!h_}ep zwzpQj9sRA94<{x3{~z&ix-DwX;RAzka)4-#6ZHJqKh|SVuO|>Yrv+m30+!|sK<-|E z=)5E->#y<_1V|T1f%Af!ZYqXg}`O zI$qKOWdnclF`%_Z`WGOe{`A`l-#a?s=Q1a#@BOWmExH2;Wl`OB!B-%lq3nO{4=WO& z#k_x|N&(qzm*6S{G*|GCegF2N2ulC+(58z2DG~yUs}i8zvRf&$CJCaexJ6Xu!`qz( z)*v8*kAE#D0KCo*s{8^Rbg=`*E2MzeIt0|x55%n-gO&yX#$l=3W7-_~&(G8j1E(XB hw}tl`5K!1C(72%nnjQrp<7@!WCh47rWB+@R{{wClNUHz< diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 33682bbbf..1c4bcc29e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index fbd7c5158..4f906e0c8 100755 --- a/gradlew +++ b/gradlew @@ -130,7 +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 diff --git a/gradlew.bat b/gradlew.bat index a9f778a7a..ac1b06f93 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +64,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,7 +71,7 @@ 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% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell From 352cb863bc04915ac47efa8605dec675b5e70da0 Mon Sep 17 00:00:00 2001 From: Ho Nhat Duy Date: Sun, 31 Jan 2021 17:57:54 +0000 Subject: [PATCH 32/75] Translated using Weblate (Vietnamese) Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ Translated using Weblate (Vietnamese) Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ --- app/src/main/res/values-vi/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 47b8007fe..d0891bb7e 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -420,7 +420,7 @@ Để sau Bạn cần khởi động lại Tusky để áp dụng các thiết lập Yêu cầu khởi động lại ứng dụng - Mở tút + Xem tút Mở rộng/Thu gọn toàn bộ tút Đang tìm kiếm… Bạn cần tải về bộ emoji này trước @@ -458,7 +458,7 @@ Đã lưu! Thêm ghi chú Chưa có thông báo. - Tin tức + Có gì mới\? Ẩn số liệu trên trang cá nhân Ẩn tương tác trên tút Hạn chế thông báo trên bảng tin From 7b1baf0e993ac0f212cea86c5a35b8a5355a0bf8 Mon Sep 17 00:00:00 2001 From: Daniele Lira Mereb Date: Sun, 31 Jan 2021 17:57:54 +0000 Subject: [PATCH 33/75] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/pt_BR/ Translated using Weblate (Spanish) Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/es/ --- app/src/main/res/values-es/strings.xml | 16 +++++----- app/src/main/res/values-pt-rBR/strings.xml | 35 +++++++++++++++++++++- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 3bd9e38ae..c96229a4a 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -479,7 +479,7 @@ Tu nota privada acerca de esta cuenta No hay anuncios. Anuncios - % recién publicado + %s recién publicado No puedes cargar más de %1$d archivos adjuntos multimedia. Esconder las estadísticas cuantitativas de los perfiles Esconder las estadísticas cuantitativas de las publicaciones @@ -488,13 +488,13 @@ Notificaciones cuando alguien al que estoy suscrito publicó un nuevo toot Nuevos toots alguien al que estoy suscrito publicó un nuevo toot - Alguna información que podría afectar tu bienestar mental va a ser oculta. Esto incluye: -\n -\n- Favoritos/impulsar/Notificaciones de Follow -\n- Favoritos/Conteo de Impulsos en toots -\n- Seguidor/Publicar estadísticas en perfiles -\n -\nLas notificaciones Push no serán afectadas, pero puedes revisar manualmente tus referencias de notificaciones. + Algunas informaciones que podríam afectar tu bienestar van a ser ocultas. Esto incluye: +\n +\n- Notificaciones de favoritos, impulsos e seguidores +\n- Conteo de favoritos e impulsos en toots +\n- Estadísticas de seguidores e toots en perfiles +\n +\nLas notificaciones Push no serán afectadas, pero puedes revisar manualmente tus preferencias. El toot al que redactaste una respuesta ha sido eliminado Borrador eliminado Error al cargar la información de respuesta diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index d36363808..cd0c219a4 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -236,7 +236,7 @@ %dh %dm %ds - Te segue + te segue Sempre mostrar mídia sensível Mídia Respondendo @%s @@ -472,4 +472,37 @@ Silencie notificações de %s Dessilencie notificações de %s Ocultar o título da barra superior de tarefas + Notificar sobre toots de quem me interessa + quem me interessa tootar + Erro ao carregar toot para responder + Erro ao enviar o toot! + O toot em que se rascunhou uma resposta foi excluído + Rascunho excluído + A função de rascunhos no Tusky foi totalmente redesenhada para ser mais rápida, mais fácil e com menos erros. +\nÉ possível acessar rascunhos antigos através de um botão na tela de novos rascunhos, mas serão removidos numa futura atualização! + Rascunhos antigos + Deseja excluir esta lista\? + Não é possível anexar mais de %1$d arquivos de mídia. + Ocultar status dos perfis + Ocultar status dos toots + Limitar notificações da linha do tempo + Revisar notificações + Algumas informações que podem afetar seu bem-estar serão ocultadas. Isso inclui: +\n +\n- Notificações de favoritos, boosts e seguidores +\n- Número de favoritos e boosts nos toots +\n- Status de toots e seguidores nos perfis +\n +\nNotificações push não serão afetadas, mas é possível revisar sua preferência manualmente. + Salvo! + Nota pessoal sobre esta conta aqui + Bem-estar + Sem comunicados. + Indefinido + Duração + Anexos + Áudio + Novos toots + %s recém tootou + Comunicados \ No newline at end of file From db13cac681f2ecbb812f0db760be2836c842e1a4 Mon Sep 17 00:00:00 2001 From: Bifo Ho Date: Sun, 31 Jan 2021 17:57:54 +0000 Subject: [PATCH 34/75] Translated using Weblate (Bengali (Bangladesh)) Currently translated at 91.2% (416 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/bn_BD/ --- app/src/main/res/values-bn-rBD/strings.xml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-bn-rBD/strings.xml b/app/src/main/res/values-bn-rBD/strings.xml index 634f0d8f3..72fb77e40 100644 --- a/app/src/main/res/values-bn-rBD/strings.xml +++ b/app/src/main/res/values-bn-rBD/strings.xml @@ -357,7 +357,7 @@ পোল যুক্ত করুন সর্বদা সামগ্রী সতর্কতা সহ চিহ্নিত টুটগুলি প্রসারিত করুন অনুসন্ধান করতে ব্যর্থ - অক্কোউন্টগুলি + অ্যাকাউন্টগুলো যখন শব্দ বা বাক্যাংশটি শুধুমাত্র আলফানিউমেরিক হয় তখন এটি শুধুমাত্র তখনই প্রয়োগ করা হবে যদি এটি সম্পূর্ণ শব্দটির সাথে মেলে সম্পূর্ণ শব্দ বিজ্ঞপ্তি ফিল্টার দেখান @@ -424,4 +424,15 @@ এই জায়গা খালি হতে পারে না। একটি নেটওয়ার্ক ত্রুটি ঘটেছে! আপনার সংযোগ পরীক্ষা করে আবার চেষ্টা করুন! একটি ত্রুটি ঘটেছে। + + %1$sটি পছন্দ + %1$sটি পছন্দ + + %s দৃশ্যমান + %s পোস্ট করেছে + %s তোমাকে ফলো করতে চায় + %s তোমাকে ফলো করেছে + %s তোমার টুট বুস্ট করেছে + %s তোমার টুট বুস্ট করেছে + ঘোষণা \ No newline at end of file From 483beaa957d1e213f2461d0d4d8a5ae4c395e780 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Sun, 31 Jan 2021 20:27:02 +0100 Subject: [PATCH 35/75] move onOptionsItemSelected to BaseActivity (#2059) * move onOptionsItemSelected to BaseActivity * revert change in ComposeActivity --- .../java/com/keylesspalace/tusky/AboutActivity.kt | 11 ----------- .../java/com/keylesspalace/tusky/AccountActivity.kt | 4 ---- .../com/keylesspalace/tusky/AccountListActivity.kt | 11 ----------- .../java/com/keylesspalace/tusky/BaseActivity.java | 10 ++++++++++ .../com/keylesspalace/tusky/EditProfileActivity.kt | 4 ---- .../java/com/keylesspalace/tusky/FiltersActivity.kt | 11 ----------- .../java/com/keylesspalace/tusky/LicenseActivity.kt | 11 ----------- .../java/com/keylesspalace/tusky/ListsActivity.kt | 11 +---------- .../java/com/keylesspalace/tusky/LoginActivity.kt | 9 --------- .../keylesspalace/tusky/ModalTimelineActivity.kt | 9 --------- .../com/keylesspalace/tusky/SavedTootActivity.java | 12 ------------ .../com/keylesspalace/tusky/StatusListActivity.kt | 9 --------- .../keylesspalace/tusky/TabPreferenceActivity.kt | 9 --------- .../com/keylesspalace/tusky/ViewTagActivity.java | 12 ------------ .../com/keylesspalace/tusky/ViewThreadActivity.java | 4 ---- .../announcements/AnnouncementsActivity.kt | 11 ----------- .../tusky/components/compose/ComposeActivity.kt | 2 +- .../components/instancemute/InstanceListActivity.kt | 11 ----------- .../components/preference/PreferencesActivity.kt | 11 ----------- .../tusky/components/report/ReportActivity.kt | 12 ------------ .../components/scheduled/ScheduledTootActivity.kt | 11 ----------- .../tusky/components/search/SearchActivity.kt | 13 +------------ 22 files changed, 13 insertions(+), 195 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt index ce7d7294a..480954251 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt @@ -8,7 +8,6 @@ import android.text.SpannableStringBuilder import android.text.method.LinkMovementMethod import android.text.style.URLSpan import android.text.util.Linkify -import android.view.MenuItem import android.widget.TextView import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.util.CustomURLSpan @@ -50,16 +49,6 @@ class AboutActivity : BottomSheetActivity(), Injectable { } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - } - return super.onOptionsItemSelected(item) - } - } private fun TextView.setClickableTextWithoutUnderlines(@StringRes textId: Int) { diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt index c89623d70..bbcfad806 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt @@ -801,10 +801,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } R.id.action_mention -> { mention() return true diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.kt index 6cf3367eb..d592f0531 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.kt @@ -18,7 +18,6 @@ package com.keylesspalace.tusky import android.content.Context import android.content.Intent import android.os.Bundle -import android.view.MenuItem import com.keylesspalace.tusky.fragment.AccountListFragment import dagger.android.DispatchingAndroidInjector import dagger.android.HasAndroidInjector @@ -68,16 +67,6 @@ class AccountListActivity : BaseActivity(), HasAndroidInjector { .commit() } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - } - return super.onOptionsItemSelected(item) - } - override fun androidInjector() = dispatchingAndroidInjector companion object { diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index 363872684..92994f165 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -24,6 +24,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.util.Log; +import android.view.MenuItem; import android.view.View; import androidx.annotation.NonNull; @@ -127,6 +128,15 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left); } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } + @Override public void finish() { super.finish(); diff --git a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt index 3d86b6e12..64d952b99 100644 --- a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt @@ -296,10 +296,6 @@ class EditProfileActivity : BaseActivity(), Injectable { override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } R.id.action_save -> { save() return true diff --git a/app/src/main/java/com/keylesspalace/tusky/FiltersActivity.kt b/app/src/main/java/com/keylesspalace/tusky/FiltersActivity.kt index 5adce8edb..0726b26e6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/FiltersActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/FiltersActivity.kt @@ -1,7 +1,6 @@ package com.keylesspalace.tusky import android.os.Bundle -import android.view.MenuItem import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.Toast @@ -205,14 +204,4 @@ class FiltersActivity: BaseActivity() { } } - // Activate back arrow in toolbar - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - } - return super.onOptionsItemSelected(item) - } } \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/LicenseActivity.kt b/app/src/main/java/com/keylesspalace/tusky/LicenseActivity.kt index 915baf963..d6cc7bcaa 100644 --- a/app/src/main/java/com/keylesspalace/tusky/LicenseActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/LicenseActivity.kt @@ -18,7 +18,6 @@ package com.keylesspalace.tusky import android.os.Bundle import androidx.annotation.RawRes import android.util.Log -import android.view.MenuItem import android.widget.TextView import com.keylesspalace.tusky.util.IOUtils import kotlinx.android.extensions.CacheImplementation @@ -48,16 +47,6 @@ class LicenseActivity : BaseActivity() { } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - } - return super.onOptionsItemSelected(item) - } - private fun loadFileIntoTextView(@RawRes fileId: Int, textView: TextView) { val sb = StringBuilder() diff --git a/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt index 994069f7a..fa3c92c3d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt @@ -21,7 +21,6 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.LayoutInflater -import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.* @@ -135,7 +134,7 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector { val positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE) editText.onTextChanged { s, _, _, _ -> - positiveButton.isEnabled = !s.isBlank() + positiveButton.isEnabled = s.isNotBlank() } editText.setText(list?.title) editText.text?.let { editText.setSelection(it.length) } @@ -218,14 +217,6 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector { override fun androidInjector() = dispatchingAndroidInjector - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == android.R.id.home) { - onBackPressed() - return true - } - return false - } - private object ListsDiffer : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: MastoList, newItem: MastoList): Boolean { return oldItem.id == newItem.id diff --git a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt index 136997231..1eebcf69a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt @@ -23,7 +23,6 @@ import android.net.Uri import android.os.Bundle import android.text.method.LinkMovementMethod import android.util.Log -import android.view.MenuItem import android.view.View import android.widget.TextView import androidx.appcompat.app.AlertDialog @@ -111,14 +110,6 @@ class LoginActivity : BaseActivity(), Injectable { } } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == android.R.id.home) { - onBackPressed() - return true - } - return super.onOptionsItemSelected(item) - } - /** * Obtain the oauth client credentials for this app. This is only necessary the first time the * app is run on a given server instance. So, after the first authentication, they are diff --git a/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt index e4655a5eb..c3017b0c3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt @@ -3,7 +3,6 @@ package com.keylesspalace.tusky import android.content.Context import android.content.Intent import android.os.Bundle -import android.view.MenuItem import com.google.android.material.floatingactionbutton.FloatingActionButton import com.keylesspalace.tusky.fragment.TimelineFragment import com.keylesspalace.tusky.interfaces.ActionButtonActivity @@ -56,14 +55,6 @@ class ModalTimelineActivity : BottomSheetActivity(), ActionButtonActivity, HasAn override fun getActionButton(): FloatingActionButton? = null - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == android.R.id.home) { - onBackPressed() - return true - } - return false - } - override fun androidInjector() = dispatchingAndroidInjector } diff --git a/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java b/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java index 8e2b5acb2..63a32b174 100644 --- a/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java @@ -18,7 +18,6 @@ package com.keylesspalace.tusky; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; -import android.view.MenuItem; import android.view.View; import androidx.annotation.Nullable; @@ -118,17 +117,6 @@ public final class SavedTootActivity extends BaseActivity implements SavedTootAd if (asyncTask != null) asyncTask.cancel(true); } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: { - onBackPressed(); - return true; - } - } - return super.onOptionsItemSelected(item); - } - private void fetchToots() { asyncTask = new FetchPojosTask(this, database.tootDao()) .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); diff --git a/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt b/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt index 56ea4d2fb..9eba5bbe1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt @@ -18,7 +18,6 @@ package com.keylesspalace.tusky import android.content.Context import android.content.Intent import android.os.Bundle -import android.view.MenuItem import androidx.fragment.app.commit import com.keylesspalace.tusky.fragment.TimelineFragment @@ -66,14 +65,6 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector { } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == android.R.id.home){ - onBackPressed() - return true - } - return super.onOptionsItemSelected(item) - } - override fun androidInjector() = dispatchingAndroidInjector companion object { diff --git a/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt b/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt index 6b1aaef9e..2b61f141a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt @@ -18,7 +18,6 @@ package com.keylesspalace.tusky import android.graphics.Color import android.os.Bundle import android.util.Log -import android.view.MenuItem import android.view.View import android.widget.FrameLayout import androidx.appcompat.app.AlertDialog @@ -345,14 +344,6 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene } } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == android.R.id.home) { - onBackPressed() - return true - } - return false - } - override fun onPause() { super.onPause() if (tabsChanged) { diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java b/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java index a49dcc889..0ff6ff565 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java @@ -18,7 +18,6 @@ package com.keylesspalace.tusky; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.view.MenuItem; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; @@ -72,17 +71,6 @@ public class ViewTagActivity extends BottomSheetActivity implements HasAndroidIn fragmentTransaction.commit(); } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: { - onBackPressed(); - return true; - } - } - return super.onOptionsItemSelected(item); - } - @Override public AndroidInjector androidInjector() { return dispatchingAndroidInjector; diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java b/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java index e2ae63d17..88fb88cc7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java @@ -110,10 +110,6 @@ public class ViewThreadActivity extends BottomSheetActivity implements HasAndroi @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case android.R.id.home: { - onBackPressed(); - return true; - } case R.id.action_open_in_web: { LinkHelper.openLink(getIntent().getStringExtra(URL_EXTRA), this); return true; diff --git a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt index 0b96b430f..865574a53 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt @@ -19,7 +19,6 @@ import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.os.Bundle -import android.view.MenuItem import android.view.View import android.widget.PopupWindow import androidx.activity.viewModels @@ -123,16 +122,6 @@ class AnnouncementsActivity : BottomSheetActivity(), AnnouncementActionListener, progressBar.show() } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - } - return super.onOptionsItemSelected(item) - } - private fun refreshAnnouncements() { viewModel.load() swipeRefreshLayout.isRefreshing = true diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt index 883294517..75b142397 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt @@ -30,7 +30,7 @@ import android.os.Build import android.os.Bundle import android.os.Parcelable import android.provider.MediaStore - import android.util.Log +import android.util.Log import android.view.KeyEvent import android.view.MenuItem import android.view.View diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt index f4505ad69..ca04f9c70 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt @@ -1,7 +1,6 @@ package com.keylesspalace.tusky.components.instancemute import android.os.Bundle -import android.view.MenuItem import com.keylesspalace.tusky.BaseActivity import com.keylesspalace.tusky.R import com.keylesspalace.tusky.components.instancemute.fragment.InstanceListFragment @@ -32,16 +31,6 @@ class InstanceListActivity: BaseActivity(), HasAndroidInjector { .commit() } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - } - return super.onOptionsItemSelected(item) - } - override fun androidInjector() = androidInjector } \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt index 4fe0abd8a..f21ec4607 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt @@ -20,7 +20,6 @@ import android.content.Intent import android.content.SharedPreferences import android.os.Bundle import android.util.Log -import android.view.MenuItem import androidx.fragment.app.Fragment import androidx.preference.PreferenceManager import com.keylesspalace.tusky.BaseActivity @@ -101,16 +100,6 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this) } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - } - return super.onOptionsItemSelected(item) - } - private fun saveInstanceState(outState: Bundle) { outState.putBoolean("restart", restartActivitiesOnExit) } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/ReportActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/ReportActivity.kt index 3ecadd589..2c7f2d464 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/ReportActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/ReportActivity.kt @@ -18,7 +18,6 @@ package com.keylesspalace.tusky.components.report import android.content.Context import android.content.Intent import android.os.Bundle -import android.view.MenuItem import androidx.activity.viewModels import com.keylesspalace.tusky.BottomSheetActivity import com.keylesspalace.tusky.R @@ -30,7 +29,6 @@ import kotlinx.android.synthetic.main.activity_report.* import kotlinx.android.synthetic.main.toolbar_basic.* import javax.inject.Inject - class ReportActivity : BottomSheetActivity(), HasAndroidInjector { @Inject @@ -120,16 +118,6 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector { wizard.currentItem = 0 } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - closeScreen() - return true - } - } - return super.onOptionsItemSelected(item) - } - companion object { private const val ACCOUNT_ID = "account_id" private const val ACCOUNT_USERNAME = "account_username" diff --git a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt index f0944a347..40a67a4eb 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt @@ -18,7 +18,6 @@ package com.keylesspalace.tusky.components.scheduled import android.content.Context import android.content.Intent import android.os.Bundle -import android.view.MenuItem import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager @@ -104,16 +103,6 @@ class ScheduledTootActivity : BaseActivity(), ScheduledTootActionListener, Injec } } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - } - return super.onOptionsItemSelected(item) - } - private fun refreshStatuses() { viewModel.reload() } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt index 8b8d1ef45..be705637d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt @@ -20,7 +20,6 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.Menu -import android.view.MenuItem import androidx.activity.viewModels import androidx.appcompat.widget.SearchView import com.google.android.material.tabs.TabLayoutMediator @@ -82,17 +81,7 @@ class SearchActivity : BottomSheetActivity(), HasAndroidInjector { return true } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - } - return super.onOptionsItemSelected(item) - } - - private fun getPageTitle(position: Int): CharSequence? { + private fun getPageTitle(position: Int): CharSequence { return when (position) { 0 -> getString(R.string.title_statuses) 1 -> getString(R.string.title_accounts) From c685192d49b03e6ad7134c63d9fc16403d0ff0e8 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 4 Feb 2021 21:16:58 +0300 Subject: [PATCH 36/75] Don't stop autocompletion after second @ (#2062) * ComposeTokenizer: do not search stop after second @ * ComposeTokenizer: try to fix test * ComposeTokenizer: try to fix test 2 * ComposeTokenizer: try to fix test 3 * ComposeTokenizer: fix autocomplete for accounts with 1 character nickname * ComposeTokenizer: better document tokenizer * ComposeTokenizer: add tests * ComposeTokenizer: remove unused logging --- .../tusky/util/ComposeTokenizer.kt | 38 +++++++++++++++++-- .../tusky/ComposeTokenizerTest.kt | 23 +++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ComposeTokenizer.kt b/app/src/main/java/com/keylesspalace/tusky/util/ComposeTokenizer.kt index a12943330..c0da4275e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ComposeTokenizer.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/ComposeTokenizer.kt @@ -21,20 +21,52 @@ import android.text.TextUtils import android.widget.MultiAutoCompleteTextView class ComposeTokenizer : MultiAutoCompleteTextView.Tokenizer { + + private fun isMentionOrHashtagAllowedCharacter(character: Char) : Boolean { + return Character.isLetterOrDigit(character) || character == '_' // simple usernames + || character == '-' // extended usernames + || character == '.' // domain dot + } + override fun findTokenStart(text: CharSequence, cursor: Int): Int { if (cursor == 0) { return cursor } var i = cursor var character = text[i - 1] - while (i > 0 && character != '@' && character != '#' && character != ':') { - // See SpanUtils.MENTION_REGEX - if (!Character.isLetterOrDigit(character) && character != '_') { + + // go up to first illegal character or character we're looking for (@, # or :) + while(i > 0 && !(character == '@' || character == '#' || character == ':')) { + if(!isMentionOrHashtagAllowedCharacter(character)) { return cursor } + i-- character = if (i == 0) ' ' else text[i - 1] } + + // maybe caught domain name? try search username + if(i > 2 && character == '@') { + var j = i - 1 + var character2 = text[i - 2] + + // again go up to first illegal character or tag "@" + while(j > 0 && character2 != '@') { + if(!isMentionOrHashtagAllowedCharacter(character2)) { + break + } + + j-- + character2 = if (j == 0) ' ' else text[j - 1] + } + + // found mention symbol, override cursor + if(character2 == '@') { + i = j + character = character2 + } + } + if (i < 1 || (character != '@' && character != '#' && character != ':') || i > 1 && !Character.isWhitespace(text[i - 2])) { diff --git a/app/src/test/java/com/keylesspalace/tusky/ComposeTokenizerTest.kt b/app/src/test/java/com/keylesspalace/tusky/ComposeTokenizerTest.kt index 73a00670c..b603a4a7c 100644 --- a/app/src/test/java/com/keylesspalace/tusky/ComposeTokenizerTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/ComposeTokenizerTest.kt @@ -44,6 +44,29 @@ class ComposeTokenizerTest(private val text: CharSequence, arrayOf(" @ment10n_ @ment20n_", 11, 20), arrayOf(" @ment10n_ @ment20n_n", 11, 21), arrayOf(" @ment10n_ @ment20n_9", 11, 21), + arrayOf(" @ment10n-", 1, 10), + arrayOf(" @ment10n- @", 11, 12), + arrayOf(" @ment10n- @ment20n", 11, 19), + arrayOf(" @ment10n- @ment20n-", 11, 20), + arrayOf(" @ment10n- @ment20n-n", 11, 21), + arrayOf(" @ment10n- @ment20n-9", 11, 21), + arrayOf("@ment10n@l0calhost", 0, 18), + arrayOf(" @ment10n@l0calhost", 1, 19), + arrayOf(" @ment10n_@l0calhost", 1, 20), + arrayOf(" @ment10n-@l0calhost", 1, 20), + arrayOf(" @ment10n_@l0calhost @ment20n@husky", 21, 35), + arrayOf(" @ment10n_@l0calhost @ment20n_@husky", 21, 36), + arrayOf(" @ment10n-@l0calhost @ment20n-@husky", 21, 36), + arrayOf(" @m@localhost", 1, 13), + arrayOf(" @m@localhost @a@localhost", 14, 26), + arrayOf("@m@", 0, 3), + arrayOf(" @m@ @a@asdf", 5, 12), + arrayOf(" @m@ @a@", 5, 8), + arrayOf(" @m@ @a@a", 5, 9), + arrayOf(" @m@a @a@m", 6, 10), + arrayOf("@m@m@", 5, 5), + arrayOf("#tusky@husky", 12, 12), + arrayOf(":tusky@husky", 12, 12), arrayOf("mention", 7, 7), arrayOf("ment10n", 7, 7), arrayOf("mentio_", 7, 7), From 958087044591b9322165e6addf7c8f2b167c4acf Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 6 Feb 2021 10:14:51 +0300 Subject: [PATCH 37/75] Animated emoji support (#2064) * Animated emoji support * Try to query preference only once * Revert to using SpannableStringBuilder --- .../keylesspalace/tusky/AccountActivity.kt | 11 ++++-- .../tusky/AccountsInListFragment.kt | 9 +++-- .../com/keylesspalace/tusky/MainActivity.kt | 3 +- .../tusky/adapter/AccountAdapter.java | 6 ++- .../tusky/adapter/AccountFieldAdapter.kt | 6 +-- .../tusky/adapter/AccountSelectionAdapter.kt | 9 +++-- .../tusky/adapter/AccountViewHolder.java | 6 +-- .../tusky/adapter/BlocksAdapter.java | 13 +++---- .../tusky/adapter/FollowAdapter.java | 6 +-- .../tusky/adapter/FollowRequestViewHolder.kt | 17 ++++----- .../tusky/adapter/FollowRequestsAdapter.java | 6 +-- .../tusky/adapter/MutesAdapter.java | 13 +++---- .../tusky/adapter/NotificationsAdapter.java | 31 ++++++++++----- .../tusky/adapter/PollAdapter.kt | 11 ++++-- .../tusky/adapter/StatusBaseViewHolder.java | 34 +++++++++++++---- .../tusky/adapter/StatusViewHolder.java | 10 +++-- .../tusky/adapter/TimelineAdapter.java | 3 +- .../announcements/AnnouncementAdapter.kt | 6 ++- .../announcements/AnnouncementsActivity.kt | 3 +- .../components/compose/ComposeActivity.kt | 12 ++++-- .../compose/ComposeAutoCompleteAdapter.java | 11 +++--- .../conversation/ConversationViewHolder.java | 2 +- .../conversation/ConversationsFragment.kt | 4 +- .../preference/PreferencesFragment.kt | 7 ++++ .../report/adapter/StatusViewHolder.kt | 6 +-- .../fragments/ReportStatusesFragment.kt | 3 +- .../search/adapter/SearchAccountsAdapter.kt | 4 +- .../fragments/SearchAccountsFragment.kt | 13 ++++++- .../fragments/SearchStatusesFragment.kt | 3 +- .../tusky/fragment/AccountListFragment.kt | 14 +++++-- .../tusky/fragment/NotificationsFragment.java | 3 +- .../tusky/fragment/TimelineFragment.java | 3 +- .../tusky/fragment/ViewThreadFragment.java | 3 +- .../tusky/settings/SettingsConstants.kt | 1 + .../tusky/util/CustomEmojiHelper.kt | 38 ++++++++++++++----- .../tusky/util/StatusDisplayOptions.kt | 4 +- .../tusky/util/StatusViewHelper.kt | 10 ++--- app/src/main/res/values/strings.xml | 1 + 38 files changed, 225 insertions(+), 120 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt index bbcfad806..951017080 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt @@ -78,7 +78,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI private val viewModel: AccountViewModel by viewModels { viewModelFactory } - private val accountFieldAdapter = AccountFieldAdapter(this) + private lateinit var accountFieldAdapter : AccountFieldAdapter private var followState: FollowState = FollowState.NOT_FOLLOWING private var blocking: Boolean = false @@ -89,6 +89,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI private var loadedAccount: Account? = null private var animateAvatar: Boolean = false + private var animateEmojis: Boolean = false // fields for scroll animation private var hideFab: Boolean = false @@ -124,6 +125,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this) animateAvatar = sharedPrefs.getBoolean("animateGifAvatars", false) + animateEmojis = sharedPrefs.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) hideFab = sharedPrefs.getBoolean("fabHide", false) setupToolbar() @@ -162,6 +164,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI accountFollowsYouTextView.hide() // setup the RecyclerView for the account fields + accountFieldAdapter = AccountFieldAdapter(this, animateEmojis) accountFieldList.isNestedScrollingEnabled = false accountFieldList.layoutManager = LinearLayoutManager(this) accountFieldList.adapter = accountFieldAdapter @@ -375,9 +378,9 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI val usernameFormatted = getString(R.string.status_username_format, account.username) accountUsernameTextView.text = usernameFormatted - accountDisplayNameTextView.text = account.name.emojify(account.emojis, accountDisplayNameTextView) + accountDisplayNameTextView.text = account.name.emojify(account.emojis, accountDisplayNameTextView, animateEmojis) - val emojifiedNote = account.note.emojify(account.emojis, accountNoteTextView) + val emojifiedNote = account.note.emojify(account.emojis, accountNoteTextView, animateEmojis) LinkHelper.setClickableText(accountNoteTextView, emojifiedNote, null, this) // accountFieldAdapter.fields = account.fields ?: emptyList() @@ -437,7 +440,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI private fun updateToolbar() { loadedAccount?.let { account -> - val emojifiedName = account.name.emojify(account.emojis, accountToolbar) + val emojifiedName = account.name.emojify(account.emojis, accountToolbar, animateEmojis) try { supportActionBar?.title = EmojiCompat.get().process(emojifiedName) diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt b/app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt index 2933d6893..f1c3d54dd 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt @@ -31,6 +31,7 @@ import androidx.recyclerview.widget.RecyclerView import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.entity.Account +import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.* import com.keylesspalace.tusky.viewmodel.AccountsInListViewModel import com.keylesspalace.tusky.viewmodel.State @@ -71,7 +72,9 @@ class AccountsInListFragment : DialogFragment(), Injectable { private val searchAdapter = SearchAdapter() private val radius by lazy { resources.getDimensionPixelSize(R.dimen.avatar_radius_48dp) } - private val animateAvatar by lazy { PreferenceManager.getDefaultSharedPreferences(requireContext()).getBoolean("animateGifAvatars", false) } + private val pm by lazy { PreferenceManager.getDefaultSharedPreferences(requireContext()) } + private val animateAvatar by lazy { pm.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false) } + private val animateEmojis by lazy { pm.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -209,7 +212,7 @@ class AccountsInListFragment : DialogFragment(), Injectable { } fun bind(account: Account) { - displayNameTextView.text = account.name.emojify(account.emojis, displayNameTextView) + displayNameTextView.text = account.name.emojify(account.emojis, displayNameTextView, animateEmojis) usernameTextView.text = account.username loadAvatar(account.avatar, avatar, radius, animateAvatar) } @@ -252,7 +255,7 @@ class AccountsInListFragment : DialogFragment(), Injectable { override val containerView = itemView fun bind(account: Account, inAList: Boolean) { - displayNameTextView.text = account.name.emojify(account.emojis, displayNameTextView) + displayNameTextView.text = account.name.emojify(account.emojis, displayNameTextView, animateEmojis) usernameTextView.text = account.username loadAvatar(account.avatar, avatar, radius, animateAvatar) diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index 231c6f43c..3f41ca00b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -723,8 +723,9 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje } private fun updateProfiles() { + val animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) val profiles: MutableList = accountManager.getAllAccountsOrderedByActive().map { acc -> - val emojifiedName = EmojiCompat.get().process(acc.displayName.emojify(acc.emojis, header)) + val emojifiedName = EmojiCompat.get().process(acc.displayName.emojify(acc.emojis, header, animateEmojis)) ProfileDrawerItem().apply { isSelected = acc.isActive diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java index 5c52e39ee..24430dcec 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java @@ -33,10 +33,14 @@ public abstract class AccountAdapter extends RecyclerView.Adapter { List accountList; AccountActionListener accountActionListener; private boolean bottomLoading; + protected final boolean animateEmojis; + protected final boolean animateAvatar; - AccountAdapter(AccountActionListener accountActionListener) { + AccountAdapter(AccountActionListener accountActionListener, boolean animateAvatar, boolean animateEmojis) { this.accountList = new ArrayList<>(); this.accountActionListener = accountActionListener; + this.animateAvatar = animateAvatar; + this.animateEmojis = animateEmojis; bottomLoading = false; } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldAdapter.kt index e80129c62..e395a7e66 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldAdapter.kt @@ -29,7 +29,7 @@ import com.keylesspalace.tusky.interfaces.LinkListener import com.keylesspalace.tusky.util.* import kotlinx.android.synthetic.main.item_account_field.view.* -class AccountFieldAdapter(private val linkListener: LinkListener) : RecyclerView.Adapter() { +class AccountFieldAdapter(private val linkListener: LinkListener, private val animateEmojis: Boolean) : RecyclerView.Adapter() { var emojis: List = emptyList() var fields: List> = emptyList() @@ -55,10 +55,10 @@ class AccountFieldAdapter(private val linkListener: LinkListener) : RecyclerView viewHolder.valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0) } else { val field = proofOrField.asRight() - val emojifiedName = field.name.emojify(emojis, viewHolder.nameTextView) + val emojifiedName = field.name.emojify(emojis, viewHolder.nameTextView, animateEmojis) viewHolder.nameTextView.text = emojifiedName - val emojifiedValue = field.value.emojify(emojis, viewHolder.valueTextView) + val emojifiedValue = field.value.emojify(emojis, viewHolder.valueTextView, animateEmojis) LinkHelper.setClickableText(viewHolder.valueTextView, emojifiedValue, null, linkListener) if(field.verifiedAt != null) { diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountSelectionAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountSelectionAdapter.kt index dae0db4b6..c8df79f9f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountSelectionAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountSelectionAdapter.kt @@ -23,6 +23,7 @@ import android.widget.ArrayAdapter import androidx.preference.PreferenceManager import com.keylesspalace.tusky.R import com.keylesspalace.tusky.db.AccountEntity +import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.* import kotlinx.android.synthetic.main.item_autocomplete_account.view.* @@ -41,12 +42,14 @@ class AccountSelectionAdapter(context: Context) : ArrayAdapter(co val username = view.username val displayName = view.display_name val avatar = view.avatar + val pm = PreferenceManager.getDefaultSharedPreferences(avatar.context) + val animateEmojis = pm.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) + username.text = account.fullName - displayName.text = account.displayName.emojify(account.emojis, displayName) + displayName.text = account.displayName.emojify(account.emojis, displayName, animateEmojis) val avatarRadius = avatar.context.resources.getDimensionPixelSize(R.dimen.avatar_radius_42dp) - val animateAvatar = PreferenceManager.getDefaultSharedPreferences(avatar.context) - .getBoolean("animateGifAvatars", false) + val animateAvatar = pm.getBoolean("animateGifAvatars", false) loadAvatar(account.profilePictureUrl, avatar, avatarRadius, animateAvatar) diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java index 7b07d5bd9..559426e38 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java @@ -22,7 +22,6 @@ public class AccountViewHolder extends RecyclerView.ViewHolder { private ImageView avatarInset; private String accountId; private boolean showBotOverlay; - private boolean animateAvatar; public AccountViewHolder(View itemView) { super(itemView); @@ -32,15 +31,14 @@ public class AccountViewHolder extends RecyclerView.ViewHolder { avatarInset = itemView.findViewById(R.id.account_avatar_inset); SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(itemView.getContext()); showBotOverlay = sharedPrefs.getBoolean("showBotOverlay", true); - animateAvatar = sharedPrefs.getBoolean("animateGifAvatars", false); } - public void setupWithAccount(Account account) { + public void setupWithAccount(Account account, boolean animateAvatar, boolean animateEmojis) { accountId = account.getId(); String format = username.getContext().getString(R.string.status_username_format); String formattedUsername = String.format(format, account.getUsername()); username.setText(formattedUsername); - CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName); + CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName, animateEmojis); displayName.setText(emojifiedName); int avatarRadius = avatar.getContext().getResources() .getDimensionPixelSize(R.dimen.avatar_radius_48dp); diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java index 073d76dab..13144cb87 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java @@ -34,8 +34,8 @@ import com.keylesspalace.tusky.util.ImageLoadingHelper; public class BlocksAdapter extends AccountAdapter { - public BlocksAdapter(AccountActionListener accountActionListener) { - super(accountActionListener); + public BlocksAdapter(AccountActionListener accountActionListener, boolean animateAvatar, boolean animateEmojis) { + super(accountActionListener, animateAvatar, animateEmojis); } @NonNull @@ -60,7 +60,7 @@ public class BlocksAdapter extends AccountAdapter { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) { BlockedUserViewHolder holder = (BlockedUserViewHolder) viewHolder; - holder.setupWithAccount(accountList.get(position)); + holder.setupWithAccount(accountList.get(position), animateAvatar, animateEmojis); holder.setupActionListener(accountActionListener); } } @@ -71,7 +71,6 @@ public class BlocksAdapter extends AccountAdapter { private TextView displayName; private ImageButton unblock; private String id; - private boolean animateAvatar; BlockedUserViewHolder(View itemView) { super(itemView); @@ -79,14 +78,12 @@ public class BlocksAdapter extends AccountAdapter { username = itemView.findViewById(R.id.blocked_user_username); displayName = itemView.findViewById(R.id.blocked_user_display_name); unblock = itemView.findViewById(R.id.blocked_user_unblock); - animateAvatar = PreferenceManager.getDefaultSharedPreferences(itemView.getContext()) - .getBoolean("animateGifAvatars", false); } - void setupWithAccount(Account account) { + void setupWithAccount(Account account, boolean animateAvatar, boolean animateEmojis) { id = account.getId(); - CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName); + CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName, animateEmojis); displayName.setText(emojifiedName); String format = username.getContext().getString(R.string.status_username_format); String formattedUsername = String.format(format, account.getUsername()); diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java index 821587463..98cb9e4df 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java @@ -27,8 +27,8 @@ import com.keylesspalace.tusky.interfaces.AccountActionListener; /** Both for follows and following lists. */ public class FollowAdapter extends AccountAdapter { - public FollowAdapter(AccountActionListener accountActionListener) { - super(accountActionListener); + public FollowAdapter(AccountActionListener accountActionListener, boolean animateAvatar, boolean animateEmojis) { + super(accountActionListener, animateAvatar, animateEmojis); } @NonNull @@ -53,7 +53,7 @@ public class FollowAdapter extends AccountAdapter { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) { AccountViewHolder holder = (AccountViewHolder) viewHolder; - holder.setupWithAccount(accountList.get(position)); + holder.setupWithAccount(accountList.get(position), animateAvatar, animateEmojis); holder.setupActionListener(accountActionListener); } } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestViewHolder.kt index dec4586ba..8fa14731c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestViewHolder.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestViewHolder.kt @@ -10,27 +10,24 @@ import androidx.recyclerview.widget.RecyclerView import com.keylesspalace.tusky.R import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.interfaces.AccountActionListener -import com.keylesspalace.tusky.util.emojify -import com.keylesspalace.tusky.util.loadAvatar -import com.keylesspalace.tusky.util.unicodeWrap -import com.keylesspalace.tusky.util.visible +import com.keylesspalace.tusky.util.* import kotlinx.android.synthetic.main.item_follow_request_notification.view.* -internal class FollowRequestViewHolder(itemView: View, private val showHeader: Boolean) : RecyclerView.ViewHolder(itemView) { +internal class FollowRequestViewHolder( + itemView: View, + private val showHeader: Boolean) : RecyclerView.ViewHolder(itemView) { private var id: String? = null - private val animateAvatar: Boolean = PreferenceManager.getDefaultSharedPreferences(itemView.context) - .getBoolean("animateGifAvatars", false) - fun setupWithAccount(account: Account) { + fun setupWithAccount(account: Account, animateAvatar: Boolean, animateEmojis: Boolean) { id = account.id val wrappedName = account.name.unicodeWrap() - val emojifiedName: CharSequence = wrappedName.emojify(account.emojis, itemView) + val emojifiedName: CharSequence = wrappedName.emojify(account.emojis, itemView, animateEmojis) itemView.displayNameTextView.text = emojifiedName if (showHeader) { val wholeMessage: String = itemView.context.getString(R.string.notification_follow_request_format, wrappedName) itemView.notificationTextView?.text = SpannableStringBuilder(wholeMessage).apply { setSpan(StyleSpan(Typeface.BOLD), 0, wrappedName.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - }.emojify(account.emojis, itemView) + }.emojify(account.emojis, itemView, animateEmojis) } itemView.notificationTextView?.visible(showHeader) val format = itemView.context.getString(R.string.status_username_format) diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java index dab3d4fe3..9ba598842 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java @@ -27,8 +27,8 @@ import com.keylesspalace.tusky.interfaces.AccountActionListener; public class FollowRequestsAdapter extends AccountAdapter { - public FollowRequestsAdapter(AccountActionListener accountActionListener) { - super(accountActionListener); + public FollowRequestsAdapter(AccountActionListener accountActionListener, boolean animateAvatar, boolean animateEmojis) { + super(accountActionListener, animateAvatar, animateEmojis); } @NonNull @@ -53,7 +53,7 @@ public class FollowRequestsAdapter extends AccountAdapter { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) { FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder; - holder.setupWithAccount(accountList.get(position)); + holder.setupWithAccount(accountList.get(position), animateAvatar, animateEmojis); holder.setupActionListener(accountActionListener); } } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java index c4224c9c9..e1a30759a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java @@ -23,8 +23,8 @@ import java.util.HashMap; public class MutesAdapter extends AccountAdapter { private HashMap mutingNotificationsMap; - public MutesAdapter(AccountActionListener accountActionListener) { - super(accountActionListener); + public MutesAdapter(AccountActionListener accountActionListener, boolean animateAvatar, boolean animateEmojis) { + super(accountActionListener, animateAvatar, animateEmojis); mutingNotificationsMap = new HashMap(); } @@ -51,7 +51,7 @@ public class MutesAdapter extends AccountAdapter { if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) { MutedUserViewHolder holder = (MutedUserViewHolder) viewHolder; Account account = accountList.get(position); - holder.setupWithAccount(account, mutingNotificationsMap.get(account.getId())); + holder.setupWithAccount(account, mutingNotificationsMap.get(account.getId()), animateAvatar, animateEmojis); holder.setupActionListener(accountActionListener); } } @@ -73,7 +73,6 @@ public class MutesAdapter extends AccountAdapter { private ImageButton unmute; private ImageButton muteNotifications; private String id; - private boolean animateAvatar; private boolean notifications; MutedUserViewHolder(View itemView) { @@ -83,13 +82,11 @@ public class MutesAdapter extends AccountAdapter { displayName = itemView.findViewById(R.id.muted_user_display_name); unmute = itemView.findViewById(R.id.muted_user_unmute); muteNotifications = itemView.findViewById(R.id.muted_user_mute_notifications); - animateAvatar = PreferenceManager.getDefaultSharedPreferences(itemView.getContext()) - .getBoolean("animateGifAvatars", false); } - void setupWithAccount(Account account, Boolean mutingNotifications) { + void setupWithAccount(Account account, Boolean mutingNotifications, boolean animateAvatar, boolean animateEmojis) { id = account.getId(); - CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName); + CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName, animateEmojis); displayName.setText(emojifiedName); String format = username.getContext().getString(R.string.status_username_format); String formattedUsername = String.format(format, account.getUsername()); diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java index 2fe95385b..833d18f46 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java @@ -232,7 +232,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter { case VIEW_TYPE_FOLLOW_REQUEST: { if (payloadForHolder == null) { FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder; - holder.setupWithAccount(concreteNotificaton.getAccount()); + holder.setupWithAccount(concreteNotificaton.getAccount(), statusDisplayOptions.animateAvatars(), statusDisplayOptions.animateEmojis()); holder.setupActionListener(accountActionListener); } } @@ -255,7 +255,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter { statusDisplayOptions.useBlurhash(), CardViewMode.NONE, statusDisplayOptions.confirmReblogs(), - statusDisplayOptions.hideStats() + statusDisplayOptions.hideStats(), + statusDisplayOptions.animateEmojis() ); } @@ -336,13 +337,17 @@ public class NotificationsAdapter extends RecyclerView.Adapter { String format = context.getString(R.string.notification_follow_format); String wrappedDisplayName = StringUtils.unicodeWrap(account.getName()); String wholeMessage = String.format(format, wrappedDisplayName); - CharSequence emojifiedMessage = CustomEmojiHelper.emojify(wholeMessage, account.getEmojis(), message); + CharSequence emojifiedMessage = CustomEmojiHelper.emojify( + wholeMessage, account.getEmojis(), message, statusDisplayOptions.animateEmojis() + ); message.setText(emojifiedMessage); String username = context.getString(R.string.status_username_format, account.getUsername()); usernameView.setText(username); - CharSequence emojifiedDisplayName = CustomEmojiHelper.emojify(wrappedDisplayName, account.getEmojis(), usernameView); + CharSequence emojifiedDisplayName = CustomEmojiHelper.emojify( + wrappedDisplayName, account.getEmojis(), usernameView, statusDisplayOptions.animateEmojis() + ); displayNameView.setText(emojifiedDisplayName); @@ -425,7 +430,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter { } private void setDisplayName(String name, List emojis) { - CharSequence emojifiedName = CustomEmojiHelper.emojify(name, emojis, displayName); + CharSequence emojifiedName = CustomEmojiHelper.emojify(name, emojis, displayName, statusDisplayOptions.animateEmojis()); displayName.setText(emojifiedName); } @@ -519,7 +524,9 @@ public class NotificationsAdapter extends RecyclerView.Adapter { final SpannableStringBuilder str = new SpannableStringBuilder(wholeMessage); str.setSpan(new StyleSpan(Typeface.BOLD), 0, displayName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - CharSequence emojifiedText = CustomEmojiHelper.emojify(str, notificationViewData.getAccount().getEmojis(), message); + CharSequence emojifiedText = CustomEmojiHelper.emojify( + str, notificationViewData.getAccount().getEmojis(), message, statusDisplayOptions.animateEmojis() + ); message.setText(emojifiedText); if (statusViewData != null) { @@ -630,11 +637,17 @@ public class NotificationsAdapter extends RecyclerView.Adapter { statusContent.setFilters(NO_INPUT_FILTER); } - CharSequence emojifiedText = CustomEmojiHelper.emojify(content, emojis, statusContent); + CharSequence emojifiedText = CustomEmojiHelper.emojify( + content, emojis, statusContent, statusDisplayOptions.animateEmojis() + ); LinkHelper.setClickableText(statusContent, emojifiedText, statusViewData.getMentions(), listener); - CharSequence emojifiedContentWarning = - CustomEmojiHelper.emojify(statusViewData.getSpoilerText(), statusViewData.getStatusEmojis(), contentWarningDescriptionTextView); + CharSequence emojifiedContentWarning = CustomEmojiHelper.emojify( + statusViewData.getSpoilerText(), + statusViewData.getStatusEmojis(), + contentWarningDescriptionTextView, + statusDisplayOptions.animateEmojis() + ); contentWarningDescriptionTextView.setText(emojifiedContentWarning); } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt index a990d326a..0208b9530 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt @@ -38,6 +38,7 @@ class PollAdapter: RecyclerView.Adapter() { private var mode = RESULT private var emojis: List = emptyList() private var resultClickListener: View.OnClickListener? = null + private var animateEmojis = false fun setup( options: List, @@ -45,13 +46,15 @@ class PollAdapter: RecyclerView.Adapter() { votersCount: Int?, emojis: List, mode: Int, - resultClickListener: View.OnClickListener?) { + resultClickListener: View.OnClickListener?, + animateEmojis: Boolean) { this.pollOptions = options this.voteCount = voteCount this.votersCount = votersCount this.emojis = emojis this.mode = mode this.resultClickListener = resultClickListener + this.animateEmojis = animateEmojis notifyDataSetChanged() } @@ -81,7 +84,7 @@ class PollAdapter: RecyclerView.Adapter() { RESULT -> { val percent = calculatePercent(option.votesCount, votersCount, voteCount) val emojifiedPollOptionText = buildDescription(option.title, percent, holder.resultTextView.context) - .emojify(emojis, holder.resultTextView) + .emojify(emojis, holder.resultTextView, animateEmojis) holder.resultTextView.text = EmojiCompat.get().process(emojifiedPollOptionText) val level = percent * 100 @@ -90,7 +93,7 @@ class PollAdapter: RecyclerView.Adapter() { holder.resultTextView.setOnClickListener(resultClickListener) } SINGLE -> { - val emojifiedPollOptionText = option.title.emojify(emojis, holder.radioButton) + val emojifiedPollOptionText = option.title.emojify(emojis, holder.radioButton, animateEmojis) holder.radioButton.text = EmojiCompat.get().process(emojifiedPollOptionText) holder.radioButton.isChecked = option.selected holder.radioButton.setOnClickListener { @@ -101,7 +104,7 @@ class PollAdapter: RecyclerView.Adapter() { } } MULTIPLE -> { - val emojifiedPollOptionText = option.title.emojify(emojis, holder.checkBox) + val emojifiedPollOptionText = option.title.emojify(emojis, holder.checkBox, animateEmojis) holder.checkBox.text = EmojiCompat.get().process(emojifiedPollOptionText) holder.checkBox.isChecked = option.selected holder.checkBox.setOnCheckedChangeListener { _, isChecked -> diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java index 5bce06246..3fc27d7cd 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java @@ -181,8 +181,10 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { protected abstract int getMediaPreviewHeight(Context context); - protected void setDisplayName(String name, List customEmojis) { - CharSequence emojifiedName = CustomEmojiHelper.emojify(name, customEmojis, displayName); + protected void setDisplayName(String name, List customEmojis, StatusDisplayOptions statusDisplayOptions) { + CharSequence emojifiedName = CustomEmojiHelper.emojify( + name, customEmojis, displayName, statusDisplayOptions.animateEmojis() + ); displayName.setText(emojifiedName); } @@ -206,7 +208,9 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { final StatusActionListener listener) { boolean sensitive = !TextUtils.isEmpty(spoilerText); if (sensitive) { - CharSequence emojiSpoiler = CustomEmojiHelper.emojify(spoilerText, emojis, contentWarningDescription); + CharSequence emojiSpoiler = CustomEmojiHelper.emojify( + spoilerText, emojis, contentWarningDescription, statusDisplayOptions.animateEmojis() + ); contentWarningDescription.setText(emojiSpoiler); contentWarningDescription.setVisibility(View.VISIBLE); contentWarningButton.setVisibility(View.VISIBLE); @@ -245,7 +249,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { StatusDisplayOptions statusDisplayOptions, final StatusActionListener listener) { if (expanded) { - CharSequence emojifiedText = CustomEmojiHelper.emojify(content, emojis, this.content); + CharSequence emojifiedText = CustomEmojiHelper.emojify(content, emojis, this.content, statusDisplayOptions.animateEmojis()); LinkHelper.setClickableText(this.content, emojifiedText, mentions, listener); for (int i = 0; i < mediaLabels.length; ++i) { updateMediaLabel(i, sensitive, expanded); @@ -709,7 +713,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { StatusDisplayOptions statusDisplayOptions, @Nullable Object payloads) { if (payloads == null) { - setDisplayName(status.getUserFullName(), status.getAccountEmojis()); + setDisplayName(status.getUserFullName(), status.getAccountEmojis(), statusDisplayOptions); setUsername(status.getNickname()); setCreatedAt(status.getCreatedAt(), statusDisplayOptions); setIsReply(status.getInReplyToId() != null); @@ -927,12 +931,28 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { listener.onViewThread(position); } }; - pollAdapter.setup(poll.getOptions(), poll.getVotesCount(), poll.getVotersCount(), emojis, PollAdapter.RESULT, viewThreadListener); + pollAdapter.setup( + poll.getOptions(), + poll.getVotesCount(), + poll.getVotersCount(), + emojis, + PollAdapter.RESULT, + viewThreadListener, + statusDisplayOptions.animateEmojis() + ); pollButton.setVisibility(View.GONE); } else { // voting possible - pollAdapter.setup(poll.getOptions(), poll.getVotesCount(), poll.getVotersCount(), emojis, poll.getMultiple() ? PollAdapter.MULTIPLE : PollAdapter.SINGLE, null); + pollAdapter.setup( + poll.getOptions(), + poll.getVotesCount(), + poll.getVotersCount(), + emojis, + poll.getMultiple() ? PollAdapter.MULTIPLE : PollAdapter.SINGLE, + null, + statusDisplayOptions.animateEmojis() + ); pollButton.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java index 4a0f66790..043b7b35e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java @@ -66,7 +66,7 @@ public class StatusViewHolder extends StatusBaseViewHolder { if (rebloggedByDisplayName == null) { hideStatusInfo(); } else { - setRebloggedByDisplayName(rebloggedByDisplayName, status); + setRebloggedByDisplayName(rebloggedByDisplayName, status, statusDisplayOptions); statusInfo.setOnClickListener(v -> listener.onOpenReblog(getAdapterPosition())); } @@ -75,11 +75,15 @@ public class StatusViewHolder extends StatusBaseViewHolder { } - private void setRebloggedByDisplayName(final CharSequence name, final StatusViewData.Concrete status) { + private void setRebloggedByDisplayName(final CharSequence name, + final StatusViewData.Concrete status, + final StatusDisplayOptions statusDisplayOptions) { Context context = statusInfo.getContext(); CharSequence wrappedName = StringUtils.unicodeWrap(name); CharSequence boostedText = context.getString(R.string.status_boosted_format, wrappedName); - CharSequence emojifiedText = CustomEmojiHelper.emojify(boostedText, status.getRebloggedByAccountEmojis(), statusInfo); + CharSequence emojifiedText = CustomEmojiHelper.emojify( + boostedText, status.getRebloggedByAccountEmojis(), statusInfo, statusDisplayOptions.animateEmojis() + ); statusInfo.setText(emojifiedText); statusInfo.setVisibility(View.VISIBLE); } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java index 7963847af..4be922d62 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java @@ -66,7 +66,8 @@ public final class TimelineAdapter extends RecyclerView.Adapter { statusDisplayOptions.useBlurhash(), statusDisplayOptions.cardViewMode(), statusDisplayOptions.confirmReblogs(), - statusDisplayOptions.hideStats() + statusDisplayOptions.hideStats(), + statusDisplayOptions.animateEmojis() ); } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementAdapter.kt index c0e6bdd8e..b54b15554 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementAdapter.kt @@ -42,7 +42,8 @@ interface AnnouncementActionListener: LinkListener { class AnnouncementAdapter( private var items: List = emptyList(), private val listener: AnnouncementActionListener, - private val wellbeingEnabled: Boolean = false + private val wellbeingEnabled: Boolean = false, + private val animateEmojis: Boolean = false ) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnnouncementViewHolder { @@ -99,7 +100,8 @@ class AnnouncementAdapter( reaction.staticUrl ?: "", null )), - this + this, + animateEmojis ) isChecked = reaction.me diff --git a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt index 865574a53..ffd97191c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt @@ -82,8 +82,9 @@ class AnnouncementsActivity : BottomSheetActivity(), AnnouncementActionListener, val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) val wellbeingEnabled = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) + val animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) - adapter = AnnouncementAdapter(emptyList(), this, wellbeingEnabled) + adapter = AnnouncementAdapter(emptyList(), this, wellbeingEnabled, animateEmojis) announcementsList.adapter = adapter diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt index 75b142397..8fc48e9cb 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt @@ -69,6 +69,7 @@ import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.entity.Emoji import com.keylesspalace.tusky.entity.NewPoll import com.keylesspalace.tusky.entity.Status +import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.* import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial @@ -160,7 +161,7 @@ class ComposeActivity : BaseActivity(), composeScheduleView.setDateTime(composeOptions?.scheduledAt) } - setupComposeField(viewModel.startingText) + setupComposeField(preferences, viewModel.startingText) setupContentWarningField(composeOptions?.contentWarning) setupPollView() applyShareIntent(intent, savedInstanceState) @@ -245,13 +246,18 @@ class ComposeActivity : BaseActivity(), composeContentWarningField.onTextChanged { _, _, _, _ -> updateVisibleCharactersLeft() } } - private fun setupComposeField(startingText: String?) { + private fun setupComposeField(preferences: SharedPreferences, startingText: String?) { composeEditField.setOnCommitContentListener(this) composeEditField.setOnKeyListener { _, keyCode, event -> this.onKeyDown(keyCode, event) } composeEditField.setAdapter( - ComposeAutoCompleteAdapter(this)) + ComposeAutoCompleteAdapter( + this, + preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false), + preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) + ) + ) composeEditField.setTokenizer(ComposeTokenizer()) composeEditField.setText(startingText) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java index df9ae8a81..b2fa94c34 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java @@ -53,11 +53,15 @@ public class ComposeAutoCompleteAdapter extends BaseAdapter private final ArrayList resultList; private final AutocompletionProvider autocompletionProvider; + private final boolean animateAvatar; + private final boolean animateEmojis; - public ComposeAutoCompleteAdapter(AutocompletionProvider autocompletionProvider) { + public ComposeAutoCompleteAdapter(AutocompletionProvider autocompletionProvider, boolean animateAvatar, boolean animateEmojis) { super(); resultList = new ArrayList<>(); this.autocompletionProvider = autocompletionProvider; + this.animateAvatar = animateAvatar; + this.animateEmojis = animateEmojis; } @Override @@ -147,15 +151,12 @@ public class ComposeAutoCompleteAdapter extends BaseAdapter ); accountViewHolder.username.setText(formattedUsername); CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), - account.getEmojis(), accountViewHolder.displayName); + account.getEmojis(), accountViewHolder.displayName, animateEmojis); accountViewHolder.displayName.setText(emojifiedName); int avatarRadius = accountViewHolder.avatar.getContext().getResources() .getDimensionPixelSize(R.dimen.avatar_radius_42dp); - boolean animateAvatar = PreferenceManager.getDefaultSharedPreferences(accountViewHolder.avatar.getContext()) - .getBoolean("animateGifAvatars", false); - ImageLoadingHelper.loadAvatar( account.getAvatar(), accountViewHolder.avatar, diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java index 19ef749e9..e74be628c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java @@ -75,7 +75,7 @@ public class ConversationViewHolder extends StatusBaseViewHolder { setupCollapsedState(status.getCollapsible(), status.getCollapsed(), status.getExpanded(), status.getSpoilerText(), listener); - setDisplayName(account.getDisplayName(), account.getEmojis()); + setDisplayName(account.getDisplayName(), account.getEmojis(), statusDisplayOptions); setUsername(account.getUsername()); setCreatedAt(status.getCreatedAt(), statusDisplayOptions); setIsReply(status.getInReplyToId() != null); diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt index d50f90438..abae8702b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt @@ -67,8 +67,8 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res useBlurhash = preferences.getBoolean("useBlurhash", true), cardViewMode = CardViewMode.NONE, confirmReblogs = preferences.getBoolean("confirmReblogs", true), - hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) - + hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false), + animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) ) adapter = ConversationAdapter(statusDisplayOptions, this, ::onTopLoaded, viewModel::retry) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt index d2598c129..e0a1b6836 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt @@ -174,6 +174,13 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable { setTitle(R.string.pref_title_enable_swipe_for_tabs) isSingleLineTitle = false } + + switchPreference { + setDefaultValue(false) + key = PrefKeys.ANIMATE_CUSTOM_EMOJIS + setTitle(R.string.pref_title_animate_custom_emojis) + isSingleLineTitle = false + } } preferenceCategory(R.string.pref_title_browser_settings) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt index 93b3a7d21..8201de2e5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt @@ -75,7 +75,7 @@ class StatusViewHolder( sensitive, previewListener, viewState.isMediaShow(status.id, status.sensitive), mediaViewHeight) - statusViewHelper.setupPollReadonly(status.poll.toViewData(), status.emojis, statusDisplayOptions.useAbsoluteTime) + statusViewHelper.setupPollReadonly(status.poll.toViewData(), status.emojis, statusDisplayOptions) setCreatedAt(status.createdAt) } @@ -89,7 +89,7 @@ class StatusViewHolder( itemView.statusContentWarningButton.hide() itemView.statusContentWarningDescription.hide() } else { - val emojiSpoiler = status.spoilerText.emojify(status.emojis, itemView.statusContentWarningDescription) + val emojiSpoiler = status.spoilerText.emojify(status.emojis, itemView.statusContentWarningDescription, statusDisplayOptions.animateEmojis) itemView.statusContentWarningDescription.text = emojiSpoiler itemView.statusContentWarningDescription.show() itemView.statusContentWarningButton.show() @@ -122,7 +122,7 @@ class StatusViewHolder( emojis: List, listener: LinkListener) { if (expanded) { - val emojifiedText = content.emojify(emojis, itemView.statusContent) + val emojifiedText = content.emojify(emojis, itemView.statusContent, statusDisplayOptions.animateEmojis) LinkHelper.setClickableText(itemView.statusContent, emojifiedText, mentions, listener) } else { LinkHelper.setClickableMentions(itemView.statusContent, mentions, listener) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt index 70bc694dc..8ffe243e5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt @@ -111,7 +111,8 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje useBlurhash = preferences.getBoolean("useBlurhash", true), cardViewMode = CardViewMode.NONE, confirmReblogs = preferences.getBoolean("confirmReblogs", true), - hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) + hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false), + animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) ) adapter = StatusesAdapter(statusDisplayOptions, diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt index c135ad7d6..b6bc95681 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt @@ -25,7 +25,7 @@ import com.keylesspalace.tusky.adapter.AccountViewHolder import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.interfaces.LinkListener -class SearchAccountsAdapter(private val linkListener: LinkListener) +class SearchAccountsAdapter(private val linkListener: LinkListener, private val animateAvatars: Boolean, private val animateEmojis: Boolean) : PagedListAdapter(ACCOUNT_COMPARATOR) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { @@ -37,7 +37,7 @@ class SearchAccountsAdapter(private val linkListener: LinkListener) override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { getItem(position)?.let { item -> (holder as AccountViewHolder).apply { - setupWithAccount(item) + setupWithAccount(item, animateAvatars, animateEmojis) setupLinkListener(linkListener) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchAccountsFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchAccountsFragment.kt index 714580f7c..c453f97c5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchAccountsFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchAccountsFragment.kt @@ -18,12 +18,23 @@ package com.keylesspalace.tusky.components.search.fragments import androidx.lifecycle.LiveData import androidx.paging.PagedList import androidx.paging.PagedListAdapter +import androidx.preference.PreferenceManager import com.keylesspalace.tusky.components.search.adapter.SearchAccountsAdapter import com.keylesspalace.tusky.entity.Account +import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.NetworkState +import kotlinx.android.synthetic.main.fragment_search.* class SearchAccountsFragment : SearchFragment() { - override fun createAdapter(): PagedListAdapter = SearchAccountsAdapter(this) + override fun createAdapter(): PagedListAdapter { + val preferences = PreferenceManager.getDefaultSharedPreferences(searchRecyclerView.context) + + return SearchAccountsAdapter( + this, + preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false), + preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) + ) + } override val networkStateRefresh: LiveData get() = viewModel.networkStateAccountRefresh diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt index bc7ac2ba1..6d96bb5aa 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt @@ -87,7 +87,8 @@ class SearchStatusesFragment : SearchFragment BlocksAdapter(this) - Type.MUTES -> MutesAdapter(this) - Type.FOLLOW_REQUESTS -> FollowRequestsAdapter(this) - else -> FollowAdapter(this) + Type.BLOCKS -> BlocksAdapter(this, animateAvatar, animateEmojis) + Type.MUTES -> MutesAdapter(this, animateAvatar, animateEmojis) + Type.FOLLOW_REQUESTS -> FollowRequestsAdapter(this, animateAvatar, animateEmojis) + else -> FollowAdapter(this, animateAvatar, animateEmojis) } recyclerView.adapter = adapter diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java index 216ee2286..5db3983f6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java @@ -252,7 +252,8 @@ public class NotificationsFragment extends SFragment implements preferences.getBoolean("useBlurhash", true), CardViewMode.NONE, preferences.getBoolean("confirmReblogs", true), - preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) + preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false), + preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) ); adapter = new NotificationsAdapter(accountManager.getActiveAccount().getAccountId(), diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java index 06ca56f32..ebaa8be82 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java @@ -252,7 +252,8 @@ public class TimelineFragment extends SFragment implements CardViewMode.INDENTED : CardViewMode.NONE, preferences.getBoolean("confirmReblogs", true), - preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) + preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false), + preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) ); adapter = new TimelineAdapter(dataSource, statusDisplayOptions, this); diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java index 5ce66eee2..0c415e1fb 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java @@ -134,7 +134,8 @@ public final class ViewThreadFragment extends SFragment implements CardViewMode.INDENTED : CardViewMode.NONE, preferences.getBoolean("confirmReblogs", true), - preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) + preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false), + preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) ); adapter = new ThreadAdapter(statusDisplayOptions, this); } diff --git a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt index a35885f39..d014ec0c5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt +++ b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt @@ -31,6 +31,7 @@ object PrefKeys { const val SHOW_CARDS_IN_TIMELINES = "showCardsInTimelines" const val CONFIRM_REBLOGS = "confirmReblogs" const val ENABLE_SWIPE_FOR_TABS = "enableSwipeForTabs" + const val ANIMATE_CUSTOM_EMOJIS = "animateCustomEmojis" const val CUSTOM_TABS = "customTabs" const val WELLBEING_LIMITED_NOTIFICATIONS = "wellbeingModeLimitedNotifications" diff --git a/app/src/main/java/com/keylesspalace/tusky/util/CustomEmojiHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/CustomEmojiHelper.kt index 679f38d36..7521afe40 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/CustomEmojiHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/CustomEmojiHelper.kt @@ -16,11 +16,9 @@ @file:JvmName("CustomEmojiHelper") package com.keylesspalace.tusky.util -import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Paint -import android.graphics.drawable.BitmapDrawable -import android.graphics.drawable.Drawable +import android.graphics.drawable.* import android.text.SpannableStringBuilder import android.text.style.ReplacementSpan import android.view.View @@ -33,6 +31,8 @@ import com.keylesspalace.tusky.entity.Emoji import java.lang.ref.WeakReference import java.util.regex.Pattern +import androidx.preference.PreferenceManager +import com.keylesspalace.tusky.settings.PrefKeys /** * replaces emoji shortcodes in a text with EmojiSpans @@ -41,7 +41,7 @@ import java.util.regex.Pattern * @param view a reference to the a view the emojis will be shown in (should be the TextView, but parents of the TextView are also acceptable) * @return the text with the shortcodes replaced by EmojiSpans */ -fun CharSequence.emojify(emojis: List?, view: View) : CharSequence { +fun CharSequence.emojify(emojis: List?, view: View, animate: Boolean) : CharSequence { if(emojis.isNullOrEmpty()) return this @@ -56,9 +56,9 @@ fun CharSequence.emojify(emojis: List?, view: View) : CharSequence { builder.setSpan(span, matcher.start(), matcher.end(), 0) Glide.with(view) - .asBitmap() + .asDrawable() .load(url) - .into(span.getTarget()) + .into(span.getTarget(animate)) } } return builder @@ -97,11 +97,29 @@ class EmojiSpan(val viewWeakReference: WeakReference) : ReplacementSpan() } } - fun getTarget(): Target { - return object : CustomTarget() { - override fun onResourceReady(resource: Bitmap, transition: Transition?) { + fun getTarget(animate : Boolean): Target { + return object : CustomTarget() { + override fun onResourceReady(resource: Drawable, transition: Transition?) { viewWeakReference.get()?.let { view -> - imageDrawable = BitmapDrawable(view.context.resources, resource) + if(animate && resource is Animatable) { + val callback = resource.callback + + resource.callback = object: Drawable.Callback { + override fun unscheduleDrawable(p0: Drawable, p1: Runnable) { + callback?.unscheduleDrawable(p0, p1) + } + override fun scheduleDrawable(p0: Drawable, p1: Runnable, p2: Long) { + callback?.scheduleDrawable(p0, p1, p2) + } + override fun invalidateDrawable(p0: Drawable) { + callback?.invalidateDrawable(p0) + view.invalidate() + } + } + resource.start() + } + + imageDrawable = resource view.invalidate() } } diff --git a/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt b/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt index 93cbb3d97..ce19e00e8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt @@ -16,5 +16,7 @@ data class StatusDisplayOptions( @get:JvmName("confirmReblogs") val confirmReblogs: Boolean, @get:JvmName("hideStats") - val hideStats: Boolean + val hideStats: Boolean, + @get:JvmName("animateEmojis") + val animateEmojis: Boolean ) \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt index db0441bd8..822100290 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt @@ -243,7 +243,7 @@ class StatusViewHelper(private val itemView: View) { } } - fun setupPollReadonly(poll: PollViewData?, emojis: List, useAbsoluteTime: Boolean) { + fun setupPollReadonly(poll: PollViewData?, emojis: List, statusDisplayOptions: StatusDisplayOptions) { val pollResults = listOf( itemView.findViewById(R.id.status_poll_option_result_0), itemView.findViewById(R.id.status_poll_option_result_1), @@ -261,10 +261,10 @@ class StatusViewHelper(private val itemView: View) { val timestamp = System.currentTimeMillis() - setupPollResult(poll, emojis, pollResults) + setupPollResult(poll, emojis, pollResults, statusDisplayOptions.animateEmojis) pollDescription.visibility = View.VISIBLE - pollDescription.text = getPollInfoText(timestamp, poll, pollDescription, useAbsoluteTime) + pollDescription.text = getPollInfoText(timestamp, poll, pollDescription, statusDisplayOptions.useAbsoluteTime) } } @@ -292,7 +292,7 @@ class StatusViewHelper(private val itemView: View) { } - private fun setupPollResult(poll: PollViewData, emojis: List, pollResults: List) { + private fun setupPollResult(poll: PollViewData, emojis: List, pollResults: List, animateEmojis: Boolean) { val options = poll.options for (i in 0 until Status.MAX_POLL_OPTIONS) { @@ -300,7 +300,7 @@ class StatusViewHelper(private val itemView: View) { val percent = calculatePercent(options[i].votesCount, poll.votersCount, poll.votesCount) val pollOptionText = buildDescription(options[i].title, percent, pollResults[i].context) - pollResults[i].text = pollOptionText.emojify(emojis, pollResults[i]) + pollResults[i].text = pollOptionText.emojify(emojis, pollResults[i], animateEmojis) pollResults[i].visibility = View.VISIBLE val level = percent * 100 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c1615e040..6aa4a3a83 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -243,6 +243,7 @@ Show indicator for bots Animate GIF avatars Show colorful gradients for hidden media + Animate custom emojis Timeline filtering Tabs From 4d856365f9559f86b3986214a64fe126af8f7da6 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Sun, 7 Feb 2021 16:40:09 +0100 Subject: [PATCH 38/75] cleanup drafts when user logs out (#2067) * cleanup drafts when user logs out * delete unused method * remove unneeded sorting from loadDraftsSingle --- .../main/java/com/keylesspalace/tusky/MainActivity.kt | 5 +++++ .../tusky/components/drafts/DraftHelper.kt | 10 ++++++++++ .../main/java/com/keylesspalace/tusky/db/DraftDao.kt | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index 3f41ca00b..3fefe4bf8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -53,6 +53,7 @@ import com.keylesspalace.tusky.components.announcements.AnnouncementsActivity 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.drafts.DraftHelper import com.keylesspalace.tusky.components.drafts.DraftsActivity import com.keylesspalace.tusky.components.notifications.NotificationHelper import com.keylesspalace.tusky.components.preference.PreferencesActivity @@ -104,6 +105,9 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje @Inject lateinit var appDb: AppDatabase + @Inject + lateinit var draftHelper: DraftHelper + private lateinit var header: AccountHeaderView private var notificationTabPosition = 0 @@ -611,6 +615,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje NotificationHelper.deleteNotificationChannelsForAccount(activeAccount, this) cacheUpdater.clearForUser(activeAccount.id) conversationRepository.deleteCacheForAccount(activeAccount.id) + draftHelper.deleteAllDraftsAndAttachmentsForAccount(activeAccount.id) removeShortcut(this, activeAccount) val newAccount = accountManager.logActiveAccountOut() if (!NotificationHelper.areNotificationsEnabled(this, accountManager)) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt index 6f5f9005a..5328fef70 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt @@ -29,6 +29,7 @@ import com.keylesspalace.tusky.entity.NewPoll import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.util.IOUtils import io.reactivex.Completable +import io.reactivex.Observable import io.reactivex.Single import io.reactivex.schedulers.Schedulers import java.io.File @@ -126,6 +127,15 @@ class DraftHelper @Inject constructor( .andThen(draftDao.delete(draft.id)) } + fun deleteAllDraftsAndAttachmentsForAccount(accountId: Long) { + draftDao.loadDraftsSingle(accountId) + .flatMapObservable { Observable.fromIterable(it) } + .flatMapCompletable { draft -> + deleteDraftAndAttachments(draft) + }.subscribeOn(Schedulers.io()) + .subscribe() + } + fun deleteAttachments(draft: DraftEntity): Completable { return Completable.fromCallable { draft.attachments.forEach { attachment -> diff --git a/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt b/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt index 105fd7c5a..065af1aed 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt @@ -32,9 +32,13 @@ interface DraftDao { @Query("SELECT * FROM DraftEntity WHERE accountId = :accountId ORDER BY id ASC") fun loadDrafts(accountId: Long): DataSource.Factory + @Query("SELECT * FROM DraftEntity WHERE accountId = :accountId") + fun loadDraftsSingle(accountId: Long): Single> + @Query("DELETE FROM DraftEntity WHERE id = :id") fun delete(id: Int): Completable @Query("SELECT * FROM DraftEntity WHERE id = :id") fun find(id: Int): Single + } From b7fc4abd50d60b814b14e967a353acb9455ca7dd Mon Sep 17 00:00:00 2001 From: hiohlan Date: Sun, 7 Feb 2021 14:13:58 +0000 Subject: [PATCH 39/75] Translated using Weblate (Thai) Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/th/ --- app/src/main/res/values-th/strings.xml | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index dc5487a81..fb5bd52b5 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -455,4 +455,37 @@ ซ่อนการแจ้งเตือน ปิดเสียงการแจ้งเตือนจาก %s ซ่อนหัวข้อของแถบเครื่องมือด้านบน + ล้มเหลวในการส่งโพสต์นี้! + ข้อมูลบางอย่างที่อาจส่งผลต่อสุขภาพจิตของคุณจะถูกซ่อนไว้ซึ่งรวมถึง: +\n +\n- การแจ้งเตือน ชื่นชอบ/ดัน/ติดตาม +\n- จำนวนการ ชื่นชอบ/ดัน บนโพสต์ +\n- สถิติ ผู้ติดตาม/โพสต์ ในโปรไฟล์ +\n +\n การแจ้งเตือนแบบพุชจะไม่ได้รับผลกระทบ แต่คุณสามารถตรวจสอบการตั้งค่าการแจ้งเตือนได้ด้วยตนเอง + แจ้งเตือน Limit timeline + แจ้งเตือน Review + ใครบางคนที่ฉันได้ติดตาม ได้เผยแพร่โพสต์ใหม่ + ฟีเจอร์ฉบับร่างใน Tusky ได้รับการออกแบบใหม่ทั้งหมดเพื่อให้เร็วขึ้นเป็นมิตรกับผู้ใช้มากขึ้นและบั๊กน้อยลง +\n คุณยังสามารถเข้าถึงฉบับร่างเก่าผ่านปุ่มในหน้าฉบับร่างใหม่ แต่จะถูกลบออกในการอัปเดตในอนาคต! + ซ่อนสถิติเชิงปริมาณในโปรไฟล์ + ซ่อนสถิติเชิงปริมาณของโพสต์ + สุขภาวะ + บันทึกส่วนตัวของคุณเกี่ยวกับบัญชีนี้ + แจ้งเตือน เมื่อคนที่คุณติดตาม ได้เผยแพร่โพสต์ใหม่ + โพสต์ที่คุณได้ร่างตอบไว้ ถูกลบแลัว + ลบฉบับร่างแล้ว + ล้มเหลวในการโหลดข้อมูลตอบกลับ + ฉบับร่างเก่า + คุณต้องการลบลิสต์ %s ใช่ไหม\? + คุณไม่สามารถอัปโหลดไฟล์แนบมากกว่า %1$d ได้ + บันทึกแล้ว! + ไม่มีประกาศ + ไม่มีกำหนด + ระยะเวลา + ไฟล์แนบ + เสียง + โพสต์ใหม่ + %s พึ่งโพสต์ + ประกาศ \ No newline at end of file From 253ebb80acf80d6ef5fda8208205a06de595bfdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Gera?= Date: Sun, 7 Feb 2021 14:13:58 +0000 Subject: [PATCH 40/75] Translated using Weblate (Hungarian) Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/hu/ Translated using Weblate (Hungarian) Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/hu/ --- app/src/main/res/values-hu/strings.xml | 35 +++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 0086e2991..1a7f29e55 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -237,12 +237,12 @@ Listák Törlés Fiók zárolása - Elmented a vázlatot? + Elmented a piszkozatot\? Tülk elküldése… A tülk elküldése nem sikerült Tülkök elküldése Küldés megszakítva - A tülk másolatát elmentettük a vázlataid közé + A tülk másolatát elmentettük a piszkozataid közé Szerkesztés A %s szervernek nincsenek egyedi emoji-jai Vágólapra másolva @@ -442,7 +442,7 @@ Lista kiválasztása Lista A hangfájloknak kisebbnek kell lenniük, mint 40 MB. - Nincs egy vázlatod sem. + Nincs egy piszkozatod sem. Nincs egy ütemezett tülköd sem. A Mastodonban a legrövidebb ütemezhető időintervallum 5 perc. Követési kérelmek @@ -476,4 +476,33 @@ Saját, mások számára nem látható megjegyzés erről a fiókról Nincsenek közlemények. Közlemények + A Tülköt, melyre válaszul piszkozatot készítettél törölték + Piszkozat törölve + Nem sikerült a Válasz információit betölteni + Régi Piszkozatok + A Tusky piszkozat funkcióját teljesen újraterveztük, hogy gyorsabb, felhasználóbarátabb és hibamentesebb legyen. +\nTovábbra is elérheted a régi piszkozataidat egy gombbal az új piszkozatok képernyőjén, de ezeket egy későbbi frissítésben el fogjuk törölni! + Ez a tülk nem küldődött el! + Tényleg le akarod törölni a %s listát\? + Nem tölthetsz fel %1$d médiacsatolmányból többet. + Profilok mérőszámainak elrejtése + Tülkök mérőszámainak elrejtése + Idővonali értesítések korlátozása + Értesítések Áttekintése + Pár információ, ami befolyásolhatja a mentális egészségedet rejtve marad. Ilyenek pl.: +\n +\n - Kedvenc/Megtolás/Bekövetés értesítései +\n - Kedvenc/Megtolás számlálók a tülkökön +\n - Követő/Tülk statisztikák a profilokon +\n +\nA Push-értesítéseket ez nem befolyásolja, de kézzel átállíthatod az értesítési beállításaidat. + Végtelen + Időtartam + Csatolmányok + Audio + Értesítések általam követett személy új tülkjeiről + Új tülkök + valaki, akit követek újat tülkölt + %s épp tülkölt + Jóllét \ No newline at end of file From d87ac1caee9502d74c301abb02b8220f6a216242 Mon Sep 17 00:00:00 2001 From: Ho Nhat Duy Date: Sun, 7 Feb 2021 14:13:58 +0000 Subject: [PATCH 41/75] Translated using Weblate (Vietnamese) Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ --- app/src/main/res/values-vi/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index d0891bb7e..0bf68062f 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -134,7 +134,7 @@ Tạo bình chọn Thêm tệp Mở trong trình duyệt - Bộ sưu tập + Thư viện Yêu cầu theo dõi Máy chủ đã ẩn Người dùng đã chặn @@ -289,7 +289,7 @@ Cộng đồng xem thêm Trả lời @%s - Bộ sưu tập + Thư viện Luôn hiện nội dung bị ẩn Luôn hiện nội dung nhạy cảm Đang theo dõi bạn From 1ca9154d1bae7a1aed202023c03b6b380ad87585 Mon Sep 17 00:00:00 2001 From: Chaitanya Date: Sun, 7 Feb 2021 14:13:58 +0000 Subject: [PATCH 42/75] Translated using Weblate (Hindi) Currently translated at 83.5% (381 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/hi/ --- app/src/main/res/values-hi/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 20b6e5d0b..3d4fe2c33 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -4,7 +4,7 @@ पसंदीदा प्रारूप लॉग आउट - पसंद + प्राथमिकताएं खाता प्राथमिकताएं प्रोफाइल एडिट करें खोज From 62a2027cb6a225f8c79f399a4f3c02842af46729 Mon Sep 17 00:00:00 2001 From: Vegard Skjefstad Date: Sun, 7 Feb 2021 14:13:58 +0000 Subject: [PATCH 43/75] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (457 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/nb_NO/ --- app/src/main/res/values-no-rNB/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-no-rNB/strings.xml b/app/src/main/res/values-no-rNB/strings.xml index 36c7dc31a..f9083c8ac 100644 --- a/app/src/main/res/values-no-rNB/strings.xml +++ b/app/src/main/res/values-no-rNB/strings.xml @@ -496,4 +496,5 @@ KladdfunksjonaLiteten i Tusky er skrevet om og er nå kjappere, mer brukervennlig, og med færre feil. \nGamle kladder er fortsatt tilgjengelige via en knapp på den nye kladdskjermen, men de vil bli fjernet i en fremtidig oppdatering! Sending av toot feilet! + Animer egendefinerte emojis \ No newline at end of file From ba986379d4250bbc817e96ac14e1a6714fcb3998 Mon Sep 17 00:00:00 2001 From: Lafudoci Date: Sun, 7 Feb 2021 14:13:59 +0000 Subject: [PATCH 44/75] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (457 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/zh_Hant/ Translated using Weblate (Chinese (Hong Kong)) Currently translated at 97.5% (446 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/zh_Hant_HK/ Translated using Weblate (Chinese (Hong Kong)) Currently translated at 84.2% (385 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/zh_Hant_HK/ --- app/src/main/res/values-zh-rHK/strings.xml | 164 +++++++++++++++++---- app/src/main/res/values-zh-rTW/strings.xml | 152 +++++++++++++++---- 2 files changed, 256 insertions(+), 60 deletions(-) diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 5c50933d4..58c638919 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -1,24 +1,24 @@ - 應用程式出現異常 - 網絡請求出錯,請檢查互聯網連接並重試 - 內容不能為空 + 應用程式出現異常。 + 網絡請求出錯,請檢查互聯網連接並重試! + 內容不能為空。 該域名無效 - 無法連接此伺服器 - 沒有可用的瀏覽器 - 認證過程出現未知錯誤 - 授權被拒絕 - 無法獲取登入資訊 + 無法連接此伺服器。 + 沒有可用的瀏覽器。 + 認證過程出現未知錯誤。 + 授權被拒絕。 + 無法獲取登入資訊。 嘟文太長了! - 檔案大小限制 8MB - 影片大小限制 40MB - 無法上傳此類型的檔案 - 此檔案無法開啟 - 需要授予 Tusky 讀取媒體檔案的權限 - 需要授予 Tusky 寫入儲存空間的權限 - 無法在嘟文中同時插入影片和圖片 - 媒體檔案上傳失敗 - 嘟文發送時出錯 + 檔案大小限制 8MB。 + 影片大小限制 40MB。 + 無法上傳此類型的檔案。 + 此檔案無法開啟。 + 需要授予 Tusky 讀取媒體檔案的權限。 + 需要授予 Tusky 寫入儲存空間的權限。 + 無法在嘟文中同時插入影片和圖片。 + 媒體檔案上傳失敗。 + 嘟文發送時出錯。 主頁 通知設定 本站時間軸 @@ -47,10 +47,10 @@ 摺疊內容 展開 摺疊 - 沒有內容 - 還沒有內容,向下拉動即可重新整理 + 沒有內容。 + 還沒有內容,向下拉動即可重新整理! %s 轉嘟了你的嘟文 - %s 收藏了你的嘟文 + %s 把你的嘟文加入了最愛 %s 關注了你 檢舉使用者 @%s 的濫用行為 更多評論? @@ -112,12 +112,12 @@ 話題 打開轉嘟用戶主頁 顯示轉嘟 - 顯示收藏 + 顯示最愛 話題 提及 連結 打開媒體 #%d - 正在下載 %1$s… + 正在下載 %1$s 複製連結 打開為 %s 分享為 … @@ -130,8 +130,8 @@ 已解除封鎖 已解除靜音 已檢舉! - 成功送出回覆 - 域名 + 成功送出回覆。 + 哪一個域名? 有什麼新鮮事? 敏感內容警告 暱稱 @@ -143,8 +143,14 @@ 標題 什麼是站點? 正在連線… - 請輸入你帳號所在的 Mastodon 站點的域名或地址 - 正在完成上傳… + 輸入你帳號所在的 Mastodon 站點的域名或地址,譬如 mastodon.social、icosahedron.website、social.tchncs.de 和 更多 +\n +\n如果你還沒有帳號,你可以輸入你想要加入的域名並在此建立新帳號。 +\n +\n一個站點是一個託管你的帳號的地方,但是你可以很容易的跟不同站台的人們交流,就像是在同一個站台一樣。 +\n +\n更多資訊可以在 joinmastodon.org 查看。 + 正在完成上傳 正在上傳… 下載 移除關注請求? @@ -165,7 +171,7 @@ 被提及 有新的關注者 嘟文被轉嘟 - 嘟文被收藏 + 嘟文被加入收藏 投票已結束 外觀 佈景主題 @@ -210,7 +216,7 @@ 轉嘟 當有使用者轉嘟了我的嘟文時 收藏 - 當有使用者收藏了我的嘟文時 + 當有使用者把我的嘟文加入收藏時 投票 當我參與的投票結束時 %s 提及了你 @@ -323,7 +329,7 @@ 標籤 內容 嘟文顯示精確時間 - 以下資訊可能並不完整,要檢視完整資料請使用瀏覽器開啟 + 以下資訊可能並不完整,要檢視完整資料請使用瀏覽器開啟。 取消置頂 置頂 @@ -332,8 +338,8 @@ <b>%s</b> 次轉嘟 - 轉嘟 - 收藏 + 轉嘟由 + 收藏由 %1$s %1$s 和 %2$s %1$s, %2$s 和 %3$d 等人 @@ -402,4 +408,100 @@ 話題 關注請求 編輯 + 動態自訂表情符號 + 在隱藏的媒體上使用漸變色彩 + 動態 GIF 頭像 + 我關注的人有新嘟文 + 已送出關注請求 + 隱藏通知 + 靜音 @%s? + 封鎖 @%s? + 隱藏整個網域 + 確定要封鎖 %s 所有內容?你將不會在任何公開時間軸或是通知中看到來自這個網域的內容。你的關注者若來自這個網域則將會被移除。 + %s 已解除隱藏 + 重設 + 排程嘟文 + 排程的嘟文 + 取消靜音對話 + 靜音對話 + 取消靜音 %s + 靜音 %s + 靜音來自 %s 的通知 + 取消靜音來自 %s 的通知 + 取消靜音 %s + 新增投票 + 被隱藏的網域 + 被加入書籤 + 我的書籤 + 書籤 + 我的書籤 + %s 剛剛發了新嘟文 + %s 希望可以關注你 + 公告 + 已排程的嘟文 + 被隱藏的網域 + 聲音檔大小限制 40MB。 + 完整字詞 + 你的草稿欲回覆的原嘟文已被刪除 + 草稿已刪除 + 載入回覆資訊失敗 + 舊的草稿 + 這條嘟文發送失敗! + 你確定要刪除列表 %s? + 你無法上傳超過 %1$d 媒體附件。 + 已儲存! + 你對此帳號的個人註記 + 隱藏頂端工具列的標題 + 在轉嘟時提示確認 + 在時間軸中顯示連結預覽 + Mastodon 的最短發文間隔限制為 5 分鐘。 + 沒有公告。 + 你沒有任何已排程的嘟文。 + 你沒有任何草稿。 + 尋找嘟文時發生錯誤 %s + 選項 %d + 多個選項 + 新增選項 + 7 天 + 3 天 + 1 天 + 6 小時 + 1 小時 + 30 分鐘 + 5 分鐘 + 無限期 + 期間 + 投票 + 啟用在分頁間切換的滑動手勢 + 顯示通知過濾器 + 搜尋失敗 + 帳號 + 擷取狀態失敗 + 回報失敗 + 轉送至 %s + 額外的評論 + 成功回報 @%s + 完成 + 返回 + 繼續 + + %s 人 + + + 列表 + 選擇列表 + 加上話題標籤 + 投票選項: %1$s, %2$s, %3$s, %4$s; %5$s + Google 目前的表情符號包 + 總是顯示被標注為內容警告的嘟文 + 附件 + 錄音 + 由 Tusky 提供 + Tusky %s + 當你關注的人發布新嘟文時通知 + 新嘟文 + 關注請求的通知 + 底端 + 頂端 + 主要導覽列的位置 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 6d690ace9..46411116b 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1,24 +1,24 @@ - 應用程式出現異常 - 網絡請求出錯,請檢查互聯網連接並重試 - 內容不能為空 + 應用程式出現異常。 + 網絡請求出錯,請檢查互聯網連接並重試! + 內容不能為空。 該域名無效 - 無法連接此伺服器 - 沒有可用的瀏覽器 - 認證過程出現未知錯誤 - 授權被拒絕 - 無法獲取登入資訊 + 無法連接此伺服器。 + 沒有可用的瀏覽器。 + 認證過程出現未知錯誤。 + 授權被拒絕。 + 無法獲取登入資訊。 嘟文太長了! - 檔案大小限制 8MB - 影片大小限制 40MB - 無法上傳此類型的檔案 - 此檔案無法開啟 - 需要授予 Tusky 讀取媒體檔案的權限 - 需要授予 Tusky 寫入儲存空間的權限 - 無法在嘟文中同時插入影片和圖片 - 媒體檔案上傳失敗 - 嘟文發送時出錯 + 檔案大小限制 8MB。 + 影片大小限制 40MB。 + 無法上傳此類型的檔案。 + 此檔案無法開啟。 + 需要授予 Tusky 讀取媒體檔案的權限。 + 需要授予 Tusky 寫入儲存空間的權限。 + 無法在嘟文中同時插入影片和圖片。 + 媒體檔案上傳失敗。 + 嘟文發送時出錯。 主頁 通知 本站時間軸 @@ -47,8 +47,8 @@ 摺疊內容 展開 摺疊 - 沒有內容 - 還沒有內容,向下拉動即可重新整理 + 沒有內容。 + 還沒有內容,向下拉動即可重新整理! %s 轉嘟了你的嘟文 %s 收藏了你的嘟文 %s 關注了你 @@ -112,12 +112,12 @@ 話題 打開轉嘟用戶主頁 顯示轉嘟 - 顯示收藏 + 顯示最愛 話題 提及 連結 打開媒體 #%d - 正在下載 %1$s… + 正在下載 %1$s 複製連結 打開為 %s 分享為 … @@ -130,8 +130,8 @@ 已解除封鎖 已解除靜音 已發送! - 成功送出回覆 - 域名 + 成功送出回覆。 + 哪一個域名? 有什麼新鮮事? 敏感內容警告 暱稱 @@ -143,8 +143,14 @@ 標題 什麼是站點? 正在連線… - 請輸入你帳號所在的 Mastodon 站點的域名或地址 - 正在完成上傳… + 輸入你帳號所在的 Mastodon 站點的域名或地址,譬如 mastodon.social、icosahedron.website、social.tchncs.de 和 更多 +\n +\n如果你還沒有帳號,你可以輸入你想要加入的域名並在此建立新帳號。 +\n +\n一個站點是一個託管你的帳號的地方,但是你可以很容易的跟不同站台的人們交流,就像是在同一個站台一樣。 +\n +\n更多資訊可以在 joinmastodon.org 查看。 + 正在完成上傳 正在上傳… 下載 移除關注請求? @@ -165,7 +171,7 @@ 被提及 有新的關注者 嘟文被轉嘟 - 嘟文被收藏 + 嘟文被加入收藏 投票已結束 外觀 佈景主題 @@ -210,7 +216,7 @@ 轉嘟 當有使用者轉嘟了我的嘟文時 收藏 - 當有使用者收藏了我的嘟文時 + 當有使用者把我的嘟文加入收藏時 投票 當我參與的投票結束時 %s 提及了你 @@ -323,7 +329,7 @@ 標籤 內容 嘟文顯示精確時間 - 以下資訊可能並不完整,要檢視完整資料請使用瀏覽器開啟 + 以下資訊可能並不完整,要檢視完整資料請使用瀏覽器開啟。 取消置頂 置頂 @@ -333,7 +339,7 @@ <b>%s</b> 次轉嘟 轉嘟 - 收藏 + 收藏由 %1$s %1$s 和 %2$s %1$s, %2$s 和 %3$d 等人 @@ -428,4 +434,92 @@ 編輯 書籤 音檔必需小於40MB。 + Tusky 的草稿功能已重新設計,更快、更好用、更少問題。 +\n 你還是可以在草稿頁面中查看你的先前的舊草稿,但它們在未來的某次更新中將會被移除! + 隱藏個人頁面中的狀態數量資訊 + 隱藏貼文上的狀態數量資訊 + 限制時間軸通知 + 檢查通知設定 + 有些資訊可能會影響你的心理健康將會被隱藏。包括: +\n +\n- 收藏/轉嘟/關注 通知 +\n- 收藏/轉嘟 數量 +\n- 關注/貼文 在個人頁面的狀態 +\n +\n推播通知不會受到影響,但你可以手動檢查你的通知設定。 + 數位健康 + + %s 人 + + + %s 剛剛發了新嘟文 + %s 請求關注你 + 動態自訂表情符號 + 你的草稿欲回覆的原嘟文已被刪除 + 草稿已刪除 + 載入回覆資訊失敗 + 舊的草稿 + 這條嘟文發送失敗! + 附件 + 錄音 + 你確定要刪除列表 %s? + 7 天 + 3 天 + 1 天 + 6 小時 + 1 小時 + 30 分鐘 + 5 分鐘 + 無限期 + 期間 + 你無法上傳超過 %1$d 媒體附件。 + 當你關注的人發布新嘟文時通知 + 新嘟文 + 我關注的人有新嘟文 + 沒有公告。 + 公告 + 已儲存! + 你對此帳號的個人註記 + 隱藏頂端工具列的標題 + 隱藏通知 + 靜音來自 %s 的通知 + 取消靜音來自 %s 的通知 + 取消靜音 %s + 取消靜音 %s + 底端 + 頂端 + 主要導覽列的位置 + 在隱藏的媒體上使用漸變色彩 + 加上話題標籤 + 在轉嘟時提示確認 + 在時間軸中顯示連結預覽 + 啟用在分頁間切換的滑動手勢 + 關注請求的通知 + 已送出關注請求 + 靜音 @%s? + 封鎖 @%s? + 取消靜音對話 + 靜音對話 + Mastodon 的最短發文間隔限制為 5 分鐘。 + 你沒有任何草稿。 + 你沒有任何已排程的嘟文。 + 列表 + 選擇列表 + 被加入書籤 + 我的書籤 + 書籤 + 由 Tusky 提供 + 尋找嘟文時發生錯誤 %s + 重設 + 排程嘟文 + 排程的嘟文 + 已排程的嘟文 + 選項 %d + 多個選項 + 新增選項 + 投票 + 新增投票 + 總是顯示被標注為內容警告的嘟文 + 搜尋失敗 + 帳號 \ No newline at end of file From c178804efe96cd9ef36f70b4181678c841620006 Mon Sep 17 00:00:00 2001 From: Ivan Kupalov Date: Tue, 9 Feb 2021 18:41:40 +0100 Subject: [PATCH 45/75] Fix TalkBack not initializing old views in recycler (#2036) --- .../tusky/fragment/TimelineFragment.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java index ebaa8be82..6f42dd159 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java @@ -24,11 +24,13 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityManager; import android.widget.ProgressBar; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.arch.core.util.Function; +import androidx.core.content.ContextCompat; import androidx.core.util.Pair; import androidx.core.widget.ContentLoadingProgressBar; import androidx.lifecycle.Lifecycle; @@ -95,6 +97,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.ListIterator; +import java.util.Objects; import java.util.concurrent.TimeUnit; import javax.inject.Inject; @@ -1471,9 +1474,21 @@ public class TimelineFragment extends SFragment implements } }; + AccessibilityManager a11yManager; + boolean talkBackWasEnabled; + @Override public void onResume() { super.onResume(); + a11yManager = Objects.requireNonNull( + ContextCompat.getSystemService(requireContext(), AccessibilityManager.class) + ); + boolean wasEnabled = this.talkBackWasEnabled; + talkBackWasEnabled = a11yManager.isEnabled(); + Log.d(TAG, "talkback was enabled: " + wasEnabled + ", now " + talkBackWasEnabled); + if (talkBackWasEnabled && !wasEnabled) { + this.adapter.notifyDataSetChanged(); + } startUpdateTimestamp(); } From b380653d71a288ed27801b53261e38c53cdf77e3 Mon Sep 17 00:00:00 2001 From: Connyduck Date: Mon, 8 Feb 2021 17:38:25 +0000 Subject: [PATCH 46/75] Translated using Weblate (German) Currently translated at 97.3% (445 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/de/ --- app/src/main/res/values-de/strings.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index decfff34a..6f2c79990 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -470,4 +470,22 @@ Titel der Hauptnavigation verstecken Im Moment gibt es keine Ankündigungen. Ankündigungen + Der Beitrag auf den du antworten willst wurde gelöscht + Entwurf gelöscht + Alte Entwürfe + Das \"Entwürfe\"-Feature in Tusky wurde komplett neu gestaltet um schneller und benutzerfreundlicher zu sein. +\nDu kannst deine alten Entwürfe noch hinter einem Button bei den neuen Entwürfen finden, aber sie werden mit einem zukünftigen Update gelöscht! + Dieser Beitrag konnte nicht gesendet werden! + Willst du die Liste %s wirklich löschen\? + Du kannst nicht mehr als %1$d Anhänge hochladen. + Wohlbefinden + Dauer + Für immer + Anhänge + Audio + Benachrichtigungen, wenn jemand, den ich abonniert habe, etwas Neues veröffentlicht + Neue Beiträge + GIF-Emojis animieren + Jemand, den ich abonniert habe, etwas Neues veröffentlicht + % hat gerade etwas gepostet \ No newline at end of file From a4a6cce16c8b9fff867a18f8850cb8f7fa9de042 Mon Sep 17 00:00:00 2001 From: Ho Nhat Duy Date: Mon, 8 Feb 2021 17:38:25 +0000 Subject: [PATCH 47/75] Translated using Weblate (Vietnamese) Currently translated at 100.0% (457 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ --- app/src/main/res/values-vi/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 0bf68062f..1182609af 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -352,7 +352,7 @@ Cuộc bình chọn bạn tạo đã kết thúc Cuộc bình chọn của bạn đã kết thúc Bình chọn - Kết thúc + xong kết thúc lúc %s %s người @@ -488,4 +488,5 @@ Tính năng lên lịch đăng tút của Tusky sẽ được thiết kế lại hoàn toàn để nhanh hơn, thân thiện và ít lỗi hơn. \nBạn vẫn có thể xem lại bản nháp cũ nhưng chúng sẽ bị xóa bỏ trong bản cập nhật tương lai! Đăng tút không thành công! + Emoji động \ No newline at end of file From be77d91e9110697c6874ecf9a06804ed98e815d1 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Tue, 9 Feb 2021 19:45:43 +0100 Subject: [PATCH 48/75] change draft attachment directory (#2070) --- .../tusky/components/drafts/DraftHelper.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt index 5328fef70..5038ac00c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt @@ -59,13 +59,19 @@ class DraftHelper @Inject constructor( ): Completable { return Single.fromCallable { - val draftDirectory = context.getExternalFilesDir("Tusky") + val externalFilesDir = context.getExternalFilesDir("Tusky") - if (draftDirectory == null || !(draftDirectory.exists())) { + if (externalFilesDir == null || !(externalFilesDir.exists())) { Log.e("DraftHelper", "Error obtaining directory to save media.") throw Exception() } + val draftDirectory = File(externalFilesDir, "Drafts") + + if (!draftDirectory.exists()) { + draftDirectory.mkdir() + } + val uris = mediaUris.map { uriString -> uriString.toUri() }.map { uri -> From 8c13673662efc2e4ff9b274ebc2ec873f4582bc1 Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Tue, 9 Feb 2021 19:46:35 +0100 Subject: [PATCH 49/75] Release 79 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index fa6fc401d..e9a382e1e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,8 +20,8 @@ android { applicationId APP_ID minSdkVersion 21 targetSdkVersion 29 - versionCode 78 - versionName "13.1" + versionCode 79 + versionName "14.0 beta 1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true From 7a14296d04a8566171ce03df3f0e82ce9291ac81 Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Tue, 9 Feb 2021 20:07:34 +0100 Subject: [PATCH 50/75] prepare changelog for Tusky 14.0 --- fastlane/metadata/android/en-US/changelogs/80.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/80.txt diff --git a/fastlane/metadata/android/en-US/changelogs/80.txt b/fastlane/metadata/android/en-US/changelogs/80.txt new file mode 100644 index 000000000..cfbdb4904 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/80.txt @@ -0,0 +1,9 @@ +Tusky v14.0 + +- Add option to be notified when a followed user posts - click the bell icon on their profile! (Mastodon 3.3.0 feature) +- New Drafts implementation: The draft feature in Tusky has been completely redesigned to be faster, more user friendly and less buggy. +- Wellbeing mode: A new wellbeing mode that allows you to limit certain Tusky features has been added. You can enable the feature in the Preferences. +- Animated emoji support: Tusky can now animate custom emojis. The feature is off by default, you can enable it in the Preferences. +- Support for timed mutes: It is now possible to mute users for only a certain time. +- a lot of bug fixes and small improvements, especially for Pleroma compatibility +- improved translations From 62732bd04554a851abe59565319ada4abd429566 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Fri, 12 Feb 2021 16:40:42 +0100 Subject: [PATCH 51/75] Shorten changelog for TUsky v14.0 Closes #1834 --- fastlane/metadata/android/en-US/changelogs/80.txt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/fastlane/metadata/android/en-US/changelogs/80.txt b/fastlane/metadata/android/en-US/changelogs/80.txt index cfbdb4904..14d28f0a9 100644 --- a/fastlane/metadata/android/en-US/changelogs/80.txt +++ b/fastlane/metadata/android/en-US/changelogs/80.txt @@ -1,9 +1,7 @@ Tusky v14.0 -- Add option to be notified when a followed user posts - click the bell icon on their profile! (Mastodon 3.3.0 feature) -- New Drafts implementation: The draft feature in Tusky has been completely redesigned to be faster, more user friendly and less buggy. -- Wellbeing mode: A new wellbeing mode that allows you to limit certain Tusky features has been added. You can enable the feature in the Preferences. -- Animated emoji support: Tusky can now animate custom emojis. The feature is off by default, you can enable it in the Preferences. -- Support for timed mutes: It is now possible to mute users for only a certain time. -- a lot of bug fixes and small improvements, especially for Pleroma compatibility -- improved translations +- Get notified when a followed user posts - click the bell icon on their profile! (Mastodon 3.3.0 feature) +- The draft feature in Tusky has been completely redesigned to be faster, more user friendly and less buggy. +- A new wellbeing mode that allows you to limit certain Tusky features has been added. +- Tusky can now animate custom emojis. +Full changelog: https://github.com/tuskyapp/Tusky/releases From bdb468d02dcd4e42b10b047b76f1d09dd081a612 Mon Sep 17 00:00:00 2001 From: Vegard Skjefstad Date: Sun, 14 Feb 2021 03:55:16 +0000 Subject: [PATCH 52/75] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (12 of 12 strings) Translation: Tusky/Tusky description Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/nb_NO/ --- fastlane/metadata/android/nb_NO/changelogs/80.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 fastlane/metadata/android/nb_NO/changelogs/80.txt diff --git a/fastlane/metadata/android/nb_NO/changelogs/80.txt b/fastlane/metadata/android/nb_NO/changelogs/80.txt new file mode 100644 index 000000000..11b5b37d2 --- /dev/null +++ b/fastlane/metadata/android/nb_NO/changelogs/80.txt @@ -0,0 +1,7 @@ +Tusky v14.0 + +- Mulighet for å bli varslet dersom en bruker du følger publiserer en ny toot - trykk på bjelle-ikonet på profilen deres (krever Mastodon 3.3.0) +- Ny og forbedret kladd-funksjonalitet. +- Velværemodus: Kan brukes til å begrense utvalgt funksjonalitet i Tusky. Du kan aktivere velværemodus i innstillinger. +- Støtte for animerte emojis. Dette er skrudd av som standard, men du kan skru det på i innstillinger. +- Tidsbestemt demping. Det er mulig å dempe brukere i en angitt periode. From b4a397f036a167eb66d0cf550fa2c117e64f1026 Mon Sep 17 00:00:00 2001 From: Ho Nhat Duy Date: Sun, 14 Feb 2021 03:55:16 +0000 Subject: [PATCH 53/75] Translated using Weblate (Vietnamese) Currently translated at 100.0% (12 of 12 strings) Translation: Tusky/Tusky description Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/vi/ --- fastlane/metadata/android/vi/changelogs/80.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 fastlane/metadata/android/vi/changelogs/80.txt diff --git a/fastlane/metadata/android/vi/changelogs/80.txt b/fastlane/metadata/android/vi/changelogs/80.txt new file mode 100644 index 000000000..3d54a1a97 --- /dev/null +++ b/fastlane/metadata/android/vi/changelogs/80.txt @@ -0,0 +1,9 @@ +Tusky v14.0 + +- Thông báo khi người bạn theo dõi đăng tút - click vào biểu tượng cái chuông trên trang cá nhân của họ! (Mastodon 3.3.0) +- Tút Nháp: được thiết kế lại toàn bộ, giúp nhanh hơn, dễ dùng hơn và ít lỗi hơn. +- Chế độ Cai Nghiện: cho phép bạn giới hạn một số tính năng của Tusky. +- Hỗ trợ Emoji động: cho phép xem emoji động trong Tusky. +- Ẩn Có Thời Hạn: có thể chặn người nào đó trong khoảng thời gian cho trước. +- Sửa các lỗi vặt, đặc biệt là sự tương thích Pleroma. +- Cải thiện bản dịch From 5ccee0bcd90b58fd112a6f36d432210771922252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BC=D0=B0=D1=87=D0=BA=D0=BE?= <2w167kmuk@relay.firefox.com> Date: Sun, 14 Feb 2021 03:55:17 +0000 Subject: [PATCH 54/75] Translated using Weblate (Bulgarian) Currently translated at 75.0% (9 of 12 strings) Translation: Tusky/Tusky description Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/bg/ --- fastlane/metadata/android/bg/changelogs/61.txt | 7 +++++++ fastlane/metadata/android/bg/changelogs/67.txt | 9 +++++++++ fastlane/metadata/android/bg/changelogs/68.txt | 3 +++ fastlane/metadata/android/bg/changelogs/70.txt | 8 ++++++++ fastlane/metadata/android/bg/changelogs/74.txt | 8 ++++++++ fastlane/metadata/android/bg/changelogs/77.txt | 10 ++++++++++ fastlane/metadata/android/bg/full_description.txt | 12 ++++++++++++ fastlane/metadata/android/bg/short_description.txt | 1 + fastlane/metadata/android/bg/title.txt | 1 + 9 files changed, 59 insertions(+) create mode 100644 fastlane/metadata/android/bg/changelogs/61.txt create mode 100644 fastlane/metadata/android/bg/changelogs/67.txt create mode 100644 fastlane/metadata/android/bg/changelogs/68.txt create mode 100644 fastlane/metadata/android/bg/changelogs/70.txt create mode 100644 fastlane/metadata/android/bg/changelogs/74.txt create mode 100644 fastlane/metadata/android/bg/changelogs/77.txt create mode 100644 fastlane/metadata/android/bg/full_description.txt create mode 100644 fastlane/metadata/android/bg/short_description.txt create mode 100644 fastlane/metadata/android/bg/title.txt diff --git a/fastlane/metadata/android/bg/changelogs/61.txt b/fastlane/metadata/android/bg/changelogs/61.txt new file mode 100644 index 000000000..c6fed8113 --- /dev/null +++ b/fastlane/metadata/android/bg/changelogs/61.txt @@ -0,0 +1,7 @@ +Tusky v7.0 + +- Поддръжка за показване на анкети, гласуване и известия за анкети +- Нови бутони за филтриране на раздела за известия и за изтриване на всички известия +- изтриване и преработване на вашите собствени публикации +- нов индикатор, който показва дали даден акаунт е бот на изображението на профила (може да бъде изключен в предпочитанията) +- Нови преводи: норвежки, букмал и словенски. diff --git a/fastlane/metadata/android/bg/changelogs/67.txt b/fastlane/metadata/android/bg/changelogs/67.txt new file mode 100644 index 000000000..9cf27a894 --- /dev/null +++ b/fastlane/metadata/android/bg/changelogs/67.txt @@ -0,0 +1,9 @@ +Tusky v9.0 + +- Вече можете да създавате анкети от Tusky +- Подобрено търсене +- Нова опция в Предпочитания на акаунта за винаги разширяване на предупрежденията за съдържание +- Аватарите в навигационното чекмедже вече имат закръглена квадратна форма +- Вече е възможно да докладвате за потребители, дори когато те никога не са публикували статус +- Tusky сега ще откаже да се свързва чрез връзки с чист текст на Android 6+ +- Много други малки подобрения и корекции на грешки diff --git a/fastlane/metadata/android/bg/changelogs/68.txt b/fastlane/metadata/android/bg/changelogs/68.txt new file mode 100644 index 000000000..ae09bdf13 --- /dev/null +++ b/fastlane/metadata/android/bg/changelogs/68.txt @@ -0,0 +1,3 @@ +Tusky v9.1 + +Тази версия осигурява съвместимост с Mastodon 3 и подобрява производителността и стабилността. diff --git a/fastlane/metadata/android/bg/changelogs/70.txt b/fastlane/metadata/android/bg/changelogs/70.txt new file mode 100644 index 000000000..d1cca33af --- /dev/null +++ b/fastlane/metadata/android/bg/changelogs/70.txt @@ -0,0 +1,8 @@ +Tusky v10.0 + +- Вече можете да маркирате състояния и да показвате отметките си в Tusky. +- Вече можете да планирате публикации с Tusky. Имайте предвид, че избраното време трябва да бъде поне 5 минути в бъдеще. +- Вече можете да добавяте списъци към главния екран. +- Вече можете да публикувате аудио прикачени файлове с Tusky. + +И много други малки подобрения и корекции на грешки! diff --git a/fastlane/metadata/android/bg/changelogs/74.txt b/fastlane/metadata/android/bg/changelogs/74.txt new file mode 100644 index 000000000..4bcdadaf7 --- /dev/null +++ b/fastlane/metadata/android/bg/changelogs/74.txt @@ -0,0 +1,8 @@ +Tusky v12.0 + +- Подобрен основен интерфейс - вече можете да премествате разделите отдолу +- Когато заглушавате потребител, вече можете също да решите дали да заглушите известията му +- Вече можете да следвате колкото искате хештегове в един единствен раздел хештегове +- Подобрен е начинът, по който се показват описанията на мултимедиите, така че да работи дори за супер дълги описания + +Пълен дневник на промените: https://github.com/tuskyapp/Tusky/releases diff --git a/fastlane/metadata/android/bg/changelogs/77.txt b/fastlane/metadata/android/bg/changelogs/77.txt new file mode 100644 index 000000000..e24a0601f --- /dev/null +++ b/fastlane/metadata/android/bg/changelogs/77.txt @@ -0,0 +1,10 @@ +Tusky v13.0 + +- поддръжка за бележки в профила (функция на Mastodon 3.2.0) +- поддръжка за администраторски съобщения (функция на Mastodon 3.1.0) + +- аватарът на избрания от вас акаунт вече ще се показва в главната лента с инструменти +- щракването върху показваното име в емисия ще отвори страницата с профила на този потребител + +- много корекции на грешки и малки подобрения +- подобрени преводи diff --git a/fastlane/metadata/android/bg/full_description.txt b/fastlane/metadata/android/bg/full_description.txt new file mode 100644 index 000000000..73ce4354b --- /dev/null +++ b/fastlane/metadata/android/bg/full_description.txt @@ -0,0 +1,12 @@ +Tusky е лек клиент за Mastodon, свободен сървър за социални мрежи с отворен код. + +• Материален дизайн +• Повечето приложени API на Mastodon +• Поддръжка на няколко акаунта +• Тъмна и светла тема с възможност за автоматично превключване в зависимост от часа +• Чернови - съставете публикации и ги запазете за по-късно +• Изберете между различни стилове емоджита +• Оптимизиран за всички размери на екрана +• Напълно отворен код - няма несвободни зависимости като услугите на Google + +За да научите повече за Mastodon, посетете https://joinmastodon.org/ diff --git a/fastlane/metadata/android/bg/short_description.txt b/fastlane/metadata/android/bg/short_description.txt new file mode 100644 index 000000000..d0331150b --- /dev/null +++ b/fastlane/metadata/android/bg/short_description.txt @@ -0,0 +1 @@ +Клиент с няколко акаунта за социалната мрежа Mastodon diff --git a/fastlane/metadata/android/bg/title.txt b/fastlane/metadata/android/bg/title.txt new file mode 100644 index 000000000..0238ffc0a --- /dev/null +++ b/fastlane/metadata/android/bg/title.txt @@ -0,0 +1 @@ +Tusky From af3766dcc447974c733ee55ce09b5039261120e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Gera?= Date: Sun, 14 Feb 2021 03:55:17 +0000 Subject: [PATCH 55/75] Translated using Weblate (Hungarian) Currently translated at 100.0% (12 of 12 strings) Translation: Tusky/Tusky description Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/hu/ --- fastlane/metadata/android/hu/changelogs/80.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 fastlane/metadata/android/hu/changelogs/80.txt diff --git a/fastlane/metadata/android/hu/changelogs/80.txt b/fastlane/metadata/android/hu/changelogs/80.txt new file mode 100644 index 000000000..dd5d58042 --- /dev/null +++ b/fastlane/metadata/android/hu/changelogs/80.txt @@ -0,0 +1,7 @@ +Tusky v14.0 + +- Értesítést kaphatsz, amikor egy követett felhasználó tülköl - csak kattints a csengő ikonra a profilján! (Mastodon 3.3.0 funkció) +- A Tusky piszkozat funkcióját teljesen újraterveztük, hogy gyorsabb, felhasználóbarátabb, hibamentesebb legyen. +- Az új jóllét üzemmód lehetővé teszi, hogy bizonyos Tusky funkciókat korlátozz. +- A Tusky mostantól képes animálni az egyedi emojikat is. +Összes változás: https://github.com/tuskyapp/Tusky/releases From b3106ada64a104ff6138e97dbd107729d96a9d44 Mon Sep 17 00:00:00 2001 From: Connyduck Date: Sun, 14 Feb 2021 20:45:34 +0000 Subject: [PATCH 56/75] Added translation using Weblate (Bulgarian) --- app/src/main/res/values-bg/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/src/main/res/values-bg/strings.xml diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml new file mode 100644 index 000000000..a6b3daec9 --- /dev/null +++ b/app/src/main/res/values-bg/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 998bc677138df112703e24ed3f9c9ad13e0ef987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BC=D0=B0=D1=87=D0=BA=D0=BE?= <2w167kmuk@relay.firefox.com> Date: Sun, 14 Feb 2021 20:45:34 +0000 Subject: [PATCH 57/75] Translated using Weblate (Bulgarian) Currently translated at 99.7% (456 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/bg/ --- app/src/main/res/values-bg/strings.xml | 506 ++++++++++++++++++++++++- 1 file changed, 505 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index a6b3daec9..61c97505f 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -1,2 +1,506 @@ - \ No newline at end of file + + Публикация + Публикацията, на която сте изготвили отговор, е премахната + 1 час + 30 минути + 5 минути + Неопределено + Продължителност + Анкета + Активиране на плъзгащия жест за превключване между раздели + Показване на филтър за известия + Търсенето бе неуспешно + Акаунти + Акаунтът е от друг сървър. Да изпратите ли и там анонимно копие на доклада\? + Докладът ще бъде изпратен на модератора на вашия сървър. Можете да предоставите обяснение защо докладвате този акаунт по-долу: + Извличането на състояния бе неуспешно + Докладването бе неуспешно + Препращане към %s + Допълнителни коментари + Успешно докладване на @%s + Готово + Назад + Продължаване + + Остава %d секунда + Остават %d секунди + + + Остава %d минута + Остават %d минути + + + Остава %d час + Остават %d часа + + + Остава %d ден + Остават %d дни + + Анкета, която създадохте, приключи + Анкета, в която сте гласували, приключи + Гласуване + затворено + завършва в %s + + %s човек + %s човека + + + %s глас + %s гласа + + " <!-- 15 votes • 1 hour left --> %1$s • %2$s" + %1$s • %2$s + Действия за изображение %s + Сигурни ли сте, че искате да изчистите окончателно всичките си известия\? + Композиране + Композиране на публикация + Прилагане + Филтриране + Изчистване + Списък + Избиране на списък + Хаштагове + Хаштаг без # + Добавяне на хаштаг + Име на списък + Анкета с избори: %1$s, %2$s, %3$s, %4$s; %5$s + Директно + Последователи + Публично + Отметнато + Поставено в любими + Реблог + Без описание + Предупреждение за съдържание: %s + Мултимедия: %s + достигнати са максималните %1$d раздела + %1$s, %2$s и %3$d други + %1$s + %1$s и %2$s + Поставено в любими от + Споделено от + + <b>%s</b> Споделяне + <b>%s</b> Споделяния + + + <b>%1$s</b> Любимо + <b>%1$s</b> Любими + + Закачане + Разкачане + Информацията по-долу може да отразява непълно потребителския профил. Натиснете, за да отворите пълен профил в браузъра. + Използване на абсолютно време + Съдържание + Етикет + добавяне на данни + Профилни метаданни + CC-BY-SA 4.0 + CC-BY 4.0 + Лицензиран под лиценза Apache (копие по-долу) + Tusky съдържа код и активи от следните проекти с отворен код: + Отсподеляне + Споделяне с оригиналната аудитория + %1$s се премести в: + Бот + Изтеглянето се провали + Текущият набор от емоджита на Google + Първо ще трябва да изтеглите тези емоджи комплекти + Стандартният емоджи комплект на Mastodon + Blob емоджитата, известни от Android 4.4–7.1 + Емоджи комплектът по подразбиране в устройство ви + Рестартиране + По-късно + Ще трябва да рестартирате Tusky, за да приложите тези промени + Изисква се рестартиране на приложението + Отваряне на публикация + Разгъване/свиване на всички състояния + Извършва се търсене… + По подразбиране от системата + Стил на емоджи + Копирано в клипборда + Инстанцията ви %s няма персонализирани емоджита + Композиране + Копие от публикацията е запазено във вашите чернови + Изпращането е отменено + Изпращане на публикации + Грешка при изпращане на публикация + Изпращане на публикация… + Запазване на чернова\? + Изисква ръчно одобряване на последователи + Заключване на акаунт + Премахване + Задаване на надпис + Опишете за хора със зрителни увреждания +\n(%d ограничение на знаците) + Неуспешно задаване на надпис + Публикуване с акаунт %1$s + Премахване на акаунт от списъка + Добавяне на акаунт към списъка + Търсене на хора, които следвате + Редакция на списъка + Изтриване на списъка + Преименуване на списъка + Създаване на списък + Списъкът не можа да се изтрие + Списъкът не можа да се създаде + Списъкът не можа да се преименува + Списъчна емисия + Списъци + Списъци + Добавяне на нов Mastodon акаунт + Добавяне на акаунт + Фраза за филтриране + Когато ключовата дума или фраза е само буквено-цифрова, тя ще бъде приложена само ако съответства на цялата дума + Цяла дума + Актуализиране + Премахване + Редакция на филтър + Добавяне на филтър + Разговори + Публични емисии + зареждане на още + Отговаряне на @%s + Мултимедия + Винаги разгъване на публикации, маркирани с предупреждения за съдържание + Винаги показване на деликатно съдържание + Следва ви + %dс + %dм + %dч + %dд + %dг + след %dс + след %dм + след %dч + след %dг + след %dд + Заявено последване + Прикачени файлове + Аудио + Видео + Изображения + Споделяне на връзка към публикация + Споделяне на съдържание на публикация + Профилът на Tusky + Доклади за грешки и заявки за функции: +\n https://github.com/tuskyapp/Tusky/issues + Уебсайт на проекта: +\n https://tusky.app + Tusky е свободен софтуер с отворен код. Той е лицензиран под Общият публичен лиценз на GNU Версия 3. Можете да видите лиценза тук: https://www.gnu.org/licenses/gpl-3.0.en.html + Осъществено от Tusky + Tusky %s + Относно + Заключен акаунт + %d нови взаимодействия + %1$s и %2$s + %1$s, %2$s, и %3$s + %1$s, %2$s, %3$s и %4$d други + %s ви спомена + Известия, когато някой, за когото сте абонирани, публикува + Нови публикации + Известия за приключили анкети + Анкети + Известия, когато публикациите ви бъдат означени като любими + Любими + Известия, когато публикациите ви се споделят + Най-малък + Скрито + Раздели + Филтриране на емисия + Анимиране на персонализирани емоджита + Показване на цветни градиенти за скрита мултимедия + Анимиране на GIF аватари + Показване на индикатор за ботове + Език + Скриване на бутона за композиране, при превъртане + Използване на персонализирани раздели чрез Chrome + Браузър + Използване на системния дизайн + Автоматично при залез + Черно + Светло + Тъмно + Филтри + Емисии + Тема на приложение + Външен вид + някой, за когото съм абониран, публикува + приключили анкети + публикациите ми са сложени в любими + публикациите ми са споделени + заявка за последване + последвани + споменати + Уведомете ме когато + Уведомяване със светлина + Уведомяване с вибрация + Уведомяване със звук + Сигнали + Известия + Известия + Директно: Публикуване само за споменатите потребители + Само за последователи: Публикуване само за последователи + Публично: Публикуване в публични емисии + Скриване на известия + Заглушаване на @%s\? + Блокиране на @%s\? + Скриване на целия домейн + Сигурни ли сте, че искате да блокирате всички от %s\? Няма да виждате съдържание от този домейн в нито една публична емисия или във вашите известия. Последователите ви от този домейн ще бъдат премахнати. + Изтриване и преработване на тази публикация\? + Изтриване на тази публикация\? + Отследване на този акаунт\? + Отмяна на заявката за последване\? + Изтегляне + Качване… + Завършване на мултимедийно качване + "Тук може да се въведе адресът или домейнът на която и да е инстанция, като 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>. "more! + \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 joinmastodon.org. + + Свързване… + Какво е инстанция\? + Заглавна част + Аватар + Отговор… + Няма резултати + Търсене… + Био + Показвано име + Предупреждение за съдържание + Какво се случва\? + Коя инстанция\? + Отговорът е изпратен успешно. + Изпратено! + %s е разкрит + Потребителят е раззаглушен + Потребителят е деблокиран + Изпратено! + Споделяне на мултимедия в… + Споделяне на публикация в… + Споделяне на URL адреса на публикацията в… + Теглене на мултимедия + Изтегляне на мултимедия + Споделяне като … + Отваряне като %s + Копиране на връзката + Изтегляне на %1$s + Отваряне на мултимедия #%d + Връзки + Споменавания + Хаштагове + Показване на любими + Показване на споделяния + Отваряне на споделилия автор + Хаштагове + Споменавания + Връзки + Добавяне на раздел + Нулиране + Планиране на публикация + Емоджи клавиатура + Предупреждение за съдържание + Видимост на публикация + Планирани публикации + Чернови + Търсене + Отхвърляне + Приемане + Отмяна + Редакция + Редакция на профил + Запазване + Отваряне на чекмедже + Скриване на мултимедия + Споменаване + Раззаглушаване на разговор + Заглушаване на разговор + Раззаглушаване на %s + Заглушаване на %s + Заглушаване на известия от %s + Раззаглушаване на известия от %s + Раззаглушаване на %s + Раззаглушаване + Заглушаване + Споделяне + Снимане + Добавяне на анкета + Добавяне на мултимедия + Отваряне в браузър + Мултимедия + Заявки за последване + Скрити домейни + Блокирани потребители + Заглушени потребители + Отметки + Любими + Предпочитания за акаунт + Предпочитания + Профил + Затваряне + Повторен опит + ПУБЛИКУВАНЕ! + ИЗПРАЩАНЕ + Изтриване и преработване + Изтриване + Редакция + Докладване + Показване на споделяния + Скриване на споделяния + Деблокиране + Блокиране + Отследване + Последване + Сигурни ли сте, че искате да излезете от акаунта %1$s\? + Излизане + Влизане с Mastodon + Композиране + Още + Премахване от любими + Отмятане + Поставяне в любими + Премахване на споделяне + Споделяне + Отговор + Бърз отговор + Допълнителни коментари\? + Докладване на @%s + %s току-що публикува + %s поиска да ви последва + %s ви последва + %s постави вашата публикация в любими + %s сподели вашата публикация + Нищо тук. Дръпнете надолу, за да опресните! + Нищо тук. + Свиване + Разгъване + Покажи по-малко + Покажи повече + Щракнете за преглед + Мултимедията е скрита + Деликатно съдържание + %s сподели + \@%s + Лицензи + Оповестявания + Планирани публикации + Чернови + Редакция на профила ви + Заявки за последване + Скрити домейни + Блокирани потребители + Заглушени потребители + Отметки + Любими + Последователи + Последвани + Закачени + С отговори + Публикации + Раздели + Директни съобщения + Локално + Известия + Начало + Грешка при изпращане на публикация. + Качването бе неуспешно. + Изображения и видеоклипове не могат да бъдат прикачени към едно и също състояние. + Изисква се разрешение за съхранение на мултимедия. + Изисква се разрешение за четене на носител. + Този файл не можа да бъде отворен. + Този тип файл не може да бъде качен. + Аудио файловете трябва да са по-малки от 40MB. + Видео файловете трябва да са по-малки от 40MB. + Файлът трябва да е по-малък от 8MB. + Състоянието е твърде дълго! + Получаването на токен за вход бе неуспешно. + Упълномощаването е отказано. + Възникна неидентифицирана грешка при упълномощаване. + Неуспешно намиране на уеб браузър, който да се използва. + Неуспешно удостоверяване с тази инстанция. + Въведен е невалиден домейн + Това не може да бъде празно. + Възникна грешка в мрежата! Моля, проверете връзката си и опитайте отново! + Възникна грешка. + Черновата е изтрита + Неуспешно зареждане на информация за отговор + Стари чернови + Функцията за чернови в Tusky е напълно преработена, за да бъде по-бърза, по-лесна за ползване и по-малко бъгава. +\n Все още можете да осъществите достъп до старите си чернови чрез бутон на екрана за нови чернови, но те ще бъдат премахнати при бъдеща актуализация! + Тази публикация не успя да се изпрати! + Наистина ли искате да изтриете списъка %s\? + Не можете да качите повече от %1$d мултимедийни прикачени файлове. + Скриване на количествена статистика на профили + Скриване на количествена статистика на публикации + Ограничаване на известия от емисия + Преглед на известията + Част от информацията, която може да повлияе на вашето психично състояние, ще бъде скрита. Това включва: +\n +\n - Известия за Любими/Споделяния/Последвани +\n - Брой Любими/Споделяния на публикации +\n - Статистика за Последователи/Публикации на профили +\n +\n Изскачащите известия няма да бъдат засегнати, но можете да прегледате предпочитанията си за известяване ръчно. + Запазено! + Вашата лична бележка за този акаунт + Благосъстояние + Скриване на заглавието на горната лента с инструменти + Показване на диалоговия прозорец за потвърждение преди споделяне + Показване на визуализации на връзки в емисии + Mastodon има минимален интервал за планиране от 5 минути. + Няма оповестявания. + Нямате планирани състояния. + Нямате чернови. + Грешка при търсенето на публикация %s + Редакция + Избор %d + Множество избора + Добавяне на избор + 7 дни + 3 дни + 1 ден + 6 часа + Споделяния + Известия за заявки за последване + Заявки за последване + Известия за нови последователи + Нови последователи + Известия за нови споменавания + Нови споменавания + Най-голям + Голям + Среден + Малък + Скрито: Не се показва в публични емисии + Размер на текста на състоянието + Само за последователи + Скрито + Публично + Долу + Горе + Основна навигационна позиция + Синхронизирането на настройките бе неуспешно + Публикуване (синхронизирано със сървър) + Винаги маркиране на мултимедия като чувствителна + Поверителност на публикация по подразбиране + HTTP прокси порт + HTTP прокси сървър + Активиране на HTTP прокси + HTTP прокси + Прокси + Изтегляне на визуализации за мултимедии + Показване на отговори + Показване на споделяния + \ No newline at end of file From ee24a27ce3776af355ccdfc657e82a0172a7f265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Gera?= Date: Sun, 14 Feb 2021 20:45:34 +0000 Subject: [PATCH 58/75] Translated using Weblate (Hungarian) Currently translated at 100.0% (457 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/hu/ --- app/src/main/res/values-hu/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 1a7f29e55..0cc2f2763 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -505,4 +505,5 @@ valaki, akit követek újat tülkölt %s épp tülkölt Jóllét + Egyedi emojik animálása \ No newline at end of file From 027e6d0b49c2298251a1a213ceeba86f3f60a592 Mon Sep 17 00:00:00 2001 From: Ho Nhat Duy Date: Sun, 14 Feb 2021 20:45:34 +0000 Subject: [PATCH 59/75] Translated using Weblate (Vietnamese) Currently translated at 100.0% (457 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ Translated using Weblate (Vietnamese) Currently translated at 100.0% (457 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ --- app/src/main/res/values-vi/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 1182609af..349ab4d4a 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -476,8 +476,8 @@ người tôi đăng ký theo dõi đăng tút mới %s vừa đăng tút Bạn không thể đính kèm quá %1$d tệp. - Không giới hạn - Độ dài + Vĩnh viễn + Thời hạn Bạn thật sự muốn xóa danh sách %s\? Đính kèm Âm thanh @@ -485,7 +485,7 @@ Tút lên lịch cũ Tút lên lịch đã xóa Chưa tải được bình luận - Tính năng lên lịch đăng tút của Tusky sẽ được thiết kế lại hoàn toàn để nhanh hơn, thân thiện và ít lỗi hơn. + Tính năng lên lịch đăng tút của Tusky được thiết kế lại hoàn toàn để nhanh hơn, thân thiện hơn và ít lỗi hơn. \nBạn vẫn có thể xem lại bản nháp cũ nhưng chúng sẽ bị xóa bỏ trong bản cập nhật tương lai! Đăng tút không thành công! Emoji động From b0871ea9a236f83bb57748a5d953da6b564ad4bd Mon Sep 17 00:00:00 2001 From: papapep Date: Sun, 14 Feb 2021 20:45:35 +0000 Subject: [PATCH 60/75] Translated using Weblate (Catalan) Currently translated at 99.5% (455 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ca/ Translated using Weblate (Catalan) Currently translated at 97.1% (444 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ca/ --- app/src/main/res/values-ca/strings.xml | 84 +++++++++++++++++++------- 1 file changed, 63 insertions(+), 21 deletions(-) diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 9748a4819..daecc343b 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -2,20 +2,20 @@ S\'ha produït un error. Això no pot estar buit. - El domini introduït no és vàlid - L\'autenticació en aquesta instància ha fallat. + El domini que s\'ha introduït no és vàlid + Ha fallat l\'autenticació en aquesta instància. No s\'ha trobat cap navegador web per a utilitzar. S\'ha produït un error d\'autorització no identificat. S\'ha denegat l\'autorització. - L\'obtenció del token d\'inici de sessió ha fallat. + Ha fallat l\'obtenció del token d\'inici de sessió. L\'estat és massa llarg! - El fitxer ha de ser inferior a 8MB. - Aquest tipus de fitxer no es pot pujar. - Aquest tipus de fitxer no es pot obrir. - Cal permís d\'accés al emmagatzematge. - Cal permís d\'escriptura en el dispositiu. + El fitxer ha de ser d\'una mida menor de 8MB. + No es pot pujar aquest tipus de fitxer. + No es pot obrir aquest tipus de fitxer. + Cal permís d\'accés a l\'emmagatzematge. + Cal permís d\'escriptura a l\'emmagatzematge. No es poden adjuntar imatges i vídeos en el mateix estat. - La pujada ha fallat. + Ha fallat la pujada. Inici Notificacions Local @@ -107,7 +107,7 @@ , però pots comunicar-te fàcilment i seguir amics d\'altres instàncies com si fossiu en el mateix lloc. \n\nTens més informació a joinmastodon.org. - S\'està finalitzant la pujada de materila multimèdia + S\'està finalitzant la pujada de material multimèdia S\'està pujant… Baixa Vols deixar de seguir aquest compte? @@ -191,7 +191,7 @@ En resposta a @%s carrega\'n més Vota - S\'ha produït un error en enviar el toot. + S\'ha produït un error en enviar el tut. Pestanyes Llicències Amplia @@ -202,15 +202,15 @@ Missatges directes No hi ha res aquí. Elimina l\'impuls - S\'ha produït un error de connexió! Comprova la connexió i torna-ho a provar! - Els fitxers de vídeo han de pesar menys de 40 MB. + S\'ha produït un error de connexió! Comproveu la connexió i torneu-ho a provar! + Els fitxers de vídeo han de ser de mida menor de 40 MB. Multimèdia amagada Amaga Estàs segur de tancar la sessió de %1$s\? Amaga els retoots Mostra els impulsos Elimina i reecririu - Open drawer + Obre el menú Visibilitat del toot Contingut sensible Afegir una pestanya @@ -225,7 +225,7 @@ Baixa el fitxer Compartir la imatge a … Enviat! - Follow requested + S\'ha enviat la petició de seguiment Amb respostes Teclat d\'emojis Obrir el media #%d @@ -334,8 +334,8 @@ %1$s Favorits - - + %s impuls + %s impulsos Impulsat per Marcat favorit per @@ -361,7 +361,7 @@ Vols netejar totes les notificacions permanentment\? %1$s • %2$s - %s vots + %s vot %s vots Acaba a %s @@ -421,13 +421,13 @@ Programar el toot Reiniciar Desenvolupat per Tusky - Afegit a les adreces d\'interès. + S\'ha afegit a les adreces d\'interès Seleccionar la llista Llista S\'ha produït un error en cercar la publicació %s No tens cap estat planificat. - Els fitxers d\'àudio han de ser més petits que 40MB. - No tens cap esborrany + Els fitxers d\'àudio han de ser de mida menor de 40MB. + No teniu cap esborrany. L\'interval mínim de planificació a Mastodon és de 5 minuts. Peticions de seguiment Mostra el diàleg de confirmació abans de promoure @@ -455,4 +455,46 @@ Desactivar les notificacions per %s Activar les notificacions per %s Deixar de silenciar %s + Revisió d\'avisos + S\'ha desat! + Les vostres notes quant a aquest compte + Benestar + Amaga el títol de la barra d\'eines superior + No hi ha cap avís. + Indefinit + Durada + + falta %d segon + falten %d segons + + + falta %d minut + falten %d minuts + + + falta %d hora + falten %d hores + + + falta %d dia + falten %d dies + + Adjuncions + Àudio + Notificacions quan algú a qui esteu subscrit publica un tut nou + Tuts nous + emojis personalitzats animats + algú a qui estic subscrit acaba de publicar un tut nou + %s acaba de fer una publicació + Avisos + S\'ha esborrat el tut del qual en vau fer un esborrany de resposta + S\'ha eliminat l\'esborrany + No s\'ha pogut carregar la informació de la resposta + Esborranys antics + No s\'ha pogut enviar aquest tut! + Segur que voleu esborrar la llista %s\? + No podeu pujar més de %1$d adjunts multimèdia. + Amaga les estadístiques quantitatives dels perfils + Amaga les estadístiques quantitatives de les publicacions + Limita les notificacions de la cronologia \ No newline at end of file From 2fea6011fde90ab18cbba5b7cad5e9bba4058fbb Mon Sep 17 00:00:00 2001 From: David Date: Tue, 16 Feb 2021 18:45:45 +0100 Subject: [PATCH 61/75] NavIcon Avatar fix (#2080) * NavIcon Avatar fix * Added in all three methods --- app/src/main/java/com/keylesspalace/tusky/MainActivity.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index 3fefe4bf8..3b3af8ae3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -694,16 +694,18 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje .into(object : CustomTarget(navIconSize, navIconSize) { override fun onLoadStarted(placeholder: Drawable?) { - if(placeholder != null) { + if (placeholder != null) { mainToolbar.navigationIcon = FixedSizeDrawable(placeholder, navIconSize, navIconSize) } } override fun onResourceReady(resource: Drawable, transition: Transition?) { - mainToolbar.navigationIcon = resource + mainToolbar.navigationIcon = FixedSizeDrawable(resource, navIconSize, navIconSize) } override fun onLoadCleared(placeholder: Drawable?) { - mainToolbar.navigationIcon = placeholder + if (placeholder != null) { + mainToolbar.navigationIcon = FixedSizeDrawable(placeholder, navIconSize, navIconSize) + } } }) } From ff7d900b67c471a10b066b91a64f82f8dedcbceb Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Thu, 18 Feb 2021 19:26:03 +0100 Subject: [PATCH 62/75] fix profile note "Saved" message appearing on other account changes (#2079) --- app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt index 951017080..99d135ed1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt @@ -568,11 +568,12 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI subscribing = relation.subscribing } + // remove the listener so it doesn't fire on non-user changes + accountNoteTextInputLayout.editText?.removeTextChangedListener(noteWatcher) + accountNoteTextInputLayout.visible(relation.note != null) accountNoteTextInputLayout.editText?.setText(relation.note) - // add the listener late to avoid it firing on the first change - accountNoteTextInputLayout.editText?.removeTextChangedListener(noteWatcher) accountNoteTextInputLayout.editText?.addTextChangedListener(noteWatcher) updateButtons() From e3b79097d4afc2d2dce59af5b7b4a829d9ac80bb Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Thu, 18 Feb 2021 19:26:14 +0100 Subject: [PATCH 63/75] remove redundant options from account toolbar (#2075) --- .../keylesspalace/tusky/AccountActivity.kt | 19 +------------------ app/src/main/res/menu/account_toolbar.xml | 8 -------- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt index 99d135ed1..ec3334e18 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt @@ -661,14 +661,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI menuInflater.inflate(R.menu.account_toolbar, menu) if (!viewModel.isSelf) { - val follow = menu.findItem(R.id.action_follow) - follow.title = if (followState == FollowState.NOT_FOLLOWING) { - getString(R.string.action_follow) - } else { - getString(R.string.action_unfollow) - } - - follow.isVisible = followState != FollowState.REQUESTED val block = menu.findItem(R.id.action_block) block.title = if (blocking) { @@ -712,8 +704,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI } } else { - // It shouldn't be possible to block, follow, mute or report yourself. - menu.removeItem(R.id.action_follow) + // It shouldn't be possible to block, mute or report yourself. menu.removeItem(R.id.action_block) menu.removeItem(R.id.action_mute) menu.removeItem(R.id.action_mute_domain) @@ -805,10 +796,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { - R.id.action_mention -> { - mention() - return true - } R.id.action_open_in_web -> { // If the account isn't loaded yet, eat the input. if (loadedAccount != null) { @@ -816,10 +803,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI } return true } - R.id.action_follow -> { - viewModel.changeFollowState() - return true - } R.id.action_block -> { toggleBlock() return true diff --git a/app/src/main/res/menu/account_toolbar.xml b/app/src/main/res/menu/account_toolbar.xml index ee8811228..d25bcdc17 100644 --- a/app/src/main/res/menu/account_toolbar.xml +++ b/app/src/main/res/menu/account_toolbar.xml @@ -2,18 +2,10 @@ - - - - From d2a8254d4fc29409f32ad867d796af0a4d385836 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Thu, 18 Feb 2021 19:26:25 +0100 Subject: [PATCH 64/75] add content description to account subscription button (#2076) --- app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt | 2 ++ app/src/main/res/values/strings.xml | 3 +++ 2 files changed, 5 insertions(+) diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt index ec3334e18..397b7f64e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt @@ -623,8 +623,10 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI if(subscribing) { accountSubscribeButton.setIconResource(R.drawable.ic_notifications_active_24dp) + accountSubscribeButton.contentDescription = getString(R.string.action_unsubscribe_account) } else { accountSubscribeButton.setIconResource(R.drawable.ic_notifications_24dp) + accountSubscribeButton.contentDescription = getString(R.string.action_subscribe_account) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6aa4a3a83..c6f05b4f2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -611,4 +611,7 @@ Failed loading Reply information Draft deleted The Toot you drafted a reply to has been removed + + Subscribe + Unsubscribe From f3285b03e0b716c1d9de06e078a0e682edb3a42f Mon Sep 17 00:00:00 2001 From: Vegard Skjefstad Date: Tue, 16 Feb 2021 21:56:22 +0000 Subject: [PATCH 65/75] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (12 of 12 strings) Translation: Tusky/Tusky description Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/nb_NO/ --- fastlane/metadata/android/nb_NO/changelogs/80.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/metadata/android/nb_NO/changelogs/80.txt b/fastlane/metadata/android/nb_NO/changelogs/80.txt index 11b5b37d2..8e4b85957 100644 --- a/fastlane/metadata/android/nb_NO/changelogs/80.txt +++ b/fastlane/metadata/android/nb_NO/changelogs/80.txt @@ -4,4 +4,4 @@ Tusky v14.0 - Ny og forbedret kladd-funksjonalitet. - Velværemodus: Kan brukes til å begrense utvalgt funksjonalitet i Tusky. Du kan aktivere velværemodus i innstillinger. - Støtte for animerte emojis. Dette er skrudd av som standard, men du kan skru det på i innstillinger. -- Tidsbestemt demping. Det er mulig å dempe brukere i en angitt periode. +- Komplett endringelogg: https://github.com/tuskyapp/Tusky/releases From 3ad254fdf0e3d611c98562b6393bf0d2663e73a4 Mon Sep 17 00:00:00 2001 From: Vegard Skjefstad Date: Sun, 21 Feb 2021 17:22:13 +0000 Subject: [PATCH 66/75] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (459 of 459 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/nb_NO/ --- app/src/main/res/values-no-rNB/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-no-rNB/strings.xml b/app/src/main/res/values-no-rNB/strings.xml index f9083c8ac..455f5e5d4 100644 --- a/app/src/main/res/values-no-rNB/strings.xml +++ b/app/src/main/res/values-no-rNB/strings.xml @@ -497,4 +497,6 @@ \nGamle kladder er fortsatt tilgjengelige via en knapp på den nye kladdskjermen, men de vil bli fjernet i en fremtidig oppdatering! Sending av toot feilet! Animer egendefinerte emojis + Avslutt abonnementet + Abonner \ No newline at end of file From 71fd3be5da5d90025c8b05b940d49e8817c0af04 Mon Sep 17 00:00:00 2001 From: Ho Nhat Duy Date: Sun, 21 Feb 2021 17:22:13 +0000 Subject: [PATCH 67/75] Translated using Weblate (Vietnamese) Currently translated at 100.0% (459 of 459 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ --- app/src/main/res/values-vi/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 349ab4d4a..41f6a2a7b 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -489,4 +489,6 @@ \nBạn vẫn có thể xem lại bản nháp cũ nhưng chúng sẽ bị xóa bỏ trong bản cập nhật tương lai! Đăng tút không thành công! Emoji động + Ngưng nhận thông báo + Nhận thông báo \ No newline at end of file From 1206cd3312aad4d1f3a44029a7bbac416517cff6 Mon Sep 17 00:00:00 2001 From: vachan-maker Date: Sun, 21 Feb 2021 17:22:13 +0000 Subject: [PATCH 68/75] Translated using Weblate (Malayalam) Currently translated at 23.7% (109 of 459 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ml/ --- app/src/main/res/values-ml/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index de97414b7..4f4a7868b 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -111,4 +111,5 @@ അറിയിപ്പുകൾ ടാബുകൾ അറിയിപ്പുകൾ + പ്രഖ്യാപനങ്ങൾ \ No newline at end of file From e8200f85fa183a38a15acd7bbfc7e0f7e8002391 Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Tue, 23 Feb 2021 09:16:03 +0000 Subject: [PATCH 69/75] Translated using Weblate (Russian) Currently translated at 95.8% (440 of 459 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ru/ --- app/src/main/res/values-ru/strings.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 9e2cf800f..c7f2550da 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -505,4 +505,16 @@ Скрыть заголовок в верхней панели Объявлений нет. Объявления + "Некоторая информация, которая может повлиять на ваше психическое благополучие, будет скрыта. Это включает в себя: +\n +\n - Избранное/Продвижение/Уведомления подписок +\n - Избранное/Продвижение счета на тутах +\n - Статистика подписчиков/публикаций в профилях +\n +\n На push-уведомления это не повлияет, но вы можете просмотреть настройки уведомлений вручную." + Благосостояние + Неопределённая + Продолжительность + Вложения + Аудио \ No newline at end of file From aa8a7b89b9d9172647accba22b4ffb9a98474b3e Mon Sep 17 00:00:00 2001 From: Ho Nhat Duy Date: Tue, 23 Feb 2021 09:16:03 +0000 Subject: [PATCH 70/75] Translated using Weblate (Vietnamese) Currently translated at 100.0% (459 of 459 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ --- app/src/main/res/values-vi/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 41f6a2a7b..f3aa8bfa9 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -134,7 +134,7 @@ Tạo bình chọn Thêm tệp Mở trong trình duyệt - Thư viện + Album Yêu cầu theo dõi Máy chủ đã ẩn Người dùng đã chặn From 6ebbe5f6573ea0583d33425d7653c0afdce403cf Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Tue, 23 Feb 2021 18:53:43 +0100 Subject: [PATCH 71/75] fix crash when rotating screen in a preference dialog twice (#2083) --- .../preference/PreferencesActivity.kt | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt index f21ec4607..7bb8766ac 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt @@ -21,6 +21,7 @@ import android.content.SharedPreferences import android.os.Bundle import android.util.Log import androidx.fragment.app.Fragment +import androidx.fragment.app.commit import androidx.preference.PreferenceManager import com.keylesspalace.tusky.BaseActivity import com.keylesspalace.tusky.MainActivity @@ -58,33 +59,36 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference setDisplayShowHomeEnabled(true) } - val fragment: Fragment = when (intent.getIntExtra(EXTRA_PREFERENCE_TYPE, 0)) { - GENERAL_PREFERENCES -> { - setTitle(R.string.action_view_preferences) - PreferencesFragment.newInstance() - } - ACCOUNT_PREFERENCES -> { - setTitle(R.string.action_view_account_preferences) - AccountPreferencesFragment.newInstance() - } - NOTIFICATION_PREFERENCES -> { - setTitle(R.string.pref_title_edit_notification_settings) - NotificationPreferencesFragment.newInstance() - } - TAB_FILTER_PREFERENCES -> { - setTitle(R.string.pref_title_status_tabs) - TabFilterPreferencesFragment.newInstance() - } - PROXY_PREFERENCES -> { - setTitle(R.string.pref_title_http_proxy_settings) - ProxyPreferencesFragment.newInstance() - } - else -> throw IllegalArgumentException("preferenceType not known") - } + val fragmentTag = "preference_fragment_$EXTRA_PREFERENCE_TYPE" - supportFragmentManager.beginTransaction() - .replace(R.id.fragment_container, fragment) - .commit() + val fragment: Fragment = supportFragmentManager.findFragmentByTag(fragmentTag) + ?: when (intent.getIntExtra(EXTRA_PREFERENCE_TYPE, 0)) { + GENERAL_PREFERENCES -> { + setTitle(R.string.action_view_preferences) + PreferencesFragment.newInstance() + } + ACCOUNT_PREFERENCES -> { + setTitle(R.string.action_view_account_preferences) + AccountPreferencesFragment.newInstance() + } + NOTIFICATION_PREFERENCES -> { + setTitle(R.string.pref_title_edit_notification_settings) + NotificationPreferencesFragment.newInstance() + } + TAB_FILTER_PREFERENCES -> { + setTitle(R.string.pref_title_status_tabs) + TabFilterPreferencesFragment.newInstance() + } + PROXY_PREFERENCES -> { + setTitle(R.string.pref_title_http_proxy_settings) + ProxyPreferencesFragment.newInstance() + } + else -> throw IllegalArgumentException("preferenceType not known") + } + + supportFragmentManager.commit { + replace(R.id.fragment_container, fragment, fragmentTag) + } restartActivitiesOnExit = intent.getBooleanExtra("restart", false) From 4d0083b6b1ccee281060b41b4f1bf74e0be012dc Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Tue, 23 Feb 2021 18:54:01 +0100 Subject: [PATCH 72/75] add Bulgarian, Sorani and Sanskrit to the in-app language picker (#2085) --- app/src/main/res/values/donottranslate.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 794682c6f..20c783686 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -60,12 +60,15 @@ Taqbaylit Tiếng Việt Türkçe + български Русский العربية + کوردیی ناوەندی বাংলা (বাংলাদেশ) বাংলা (ভারত) فارسی हिंदी + संस्कृतम् தமிழ் ภาษาไทย 한국어 @@ -103,12 +106,15 @@ kab vi tr + bg ru ar + ckb bn-bd bn-in fa hi + sa ta th ko From 019051bd7dbc31e1bf184aa1be6099c533207a7b Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Tue, 23 Feb 2021 19:19:44 +0100 Subject: [PATCH 73/75] fix strings with wrong placeholders --- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 6f2c79990..4367e349a 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -487,5 +487,5 @@ Neue Beiträge GIF-Emojis animieren Jemand, den ich abonniert habe, etwas Neues veröffentlicht - % hat gerade etwas gepostet + %s hat gerade etwas gepostet \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index cd0c219a4..939349b61 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -481,7 +481,6 @@ A função de rascunhos no Tusky foi totalmente redesenhada para ser mais rápida, mais fácil e com menos erros. \nÉ possível acessar rascunhos antigos através de um botão na tela de novos rascunhos, mas serão removidos numa futura atualização! Rascunhos antigos - Deseja excluir esta lista\? Não é possível anexar mais de %1$d arquivos de mídia. Ocultar status dos perfis Ocultar status dos toots From 09317d92356c9c6efd71908d74117a55208b4100 Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Tue, 23 Feb 2021 19:19:52 +0100 Subject: [PATCH 74/75] Release 80 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e9a382e1e..00ca2a563 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,8 +20,8 @@ android { applicationId APP_ID minSdkVersion 21 targetSdkVersion 29 - versionCode 79 - versionName "14.0 beta 1" + versionCode 80 + versionName "14.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true From 13392258f6554565169f473c5dfb1632aa464fdf Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Tue, 23 Feb 2021 20:29:02 +0100 Subject: [PATCH 75/75] upgrade Material Design lib and use their new time picker (#2077) --- app/build.gradle | 2 +- .../components/compose/ComposeActivity.kt | 10 ++-- .../compose/view/ComposeScheduleView.java | 36 ++++++++++--- .../tusky/fragment/TimePickerFragment.java | 53 ------------------- 4 files changed, 34 insertions(+), 67 deletions(-) delete mode 100644 app/src/main/java/com/keylesspalace/tusky/fragment/TimePickerFragment.java diff --git a/app/build.gradle b/app/build.gradle index 00ca2a563..9ab2647c3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -134,7 +134,7 @@ dependencies { implementation "androidx.room:room-rxjava2:$roomVersion" kapt "androidx.room:room-compiler:$roomVersion" - implementation "com.google.android.material:material:1.2.1" + implementation "com.google.android.material:material:1.3.0" implementation "com.squareup.retrofit2:retrofit:$retrofitVersion" implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion" diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt index 8fc48e9cb..f3c00bde5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt @@ -18,7 +18,6 @@ package com.keylesspalace.tusky.components.compose import android.Manifest import android.app.Activity import android.app.ProgressDialog -import android.app.TimePickerDialog import android.content.Context import android.content.Intent import android.content.SharedPreferences @@ -61,6 +60,7 @@ import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener import com.keylesspalace.tusky.components.compose.dialog.makeCaptionDialog import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog import com.keylesspalace.tusky.components.compose.view.ComposeOptionsListener +import com.keylesspalace.tusky.components.compose.view.ComposeScheduleView import com.keylesspalace.tusky.db.AccountEntity import com.keylesspalace.tusky.db.DraftAttachment import com.keylesspalace.tusky.di.Injectable @@ -90,7 +90,7 @@ class ComposeActivity : BaseActivity(), OnEmojiSelectedListener, Injectable, InputConnectionCompat.OnCommitContentListener, - TimePickerDialog.OnTimeSetListener { + ComposeScheduleView.OnTimeSetListener { @Inject lateinit var viewModelFactory: ViewModelFactory @@ -348,6 +348,7 @@ class ComposeActivity : BaseActivity(), composeHideMediaButton.setOnClickListener { toggleHideMedia() } composeScheduleButton.setOnClickListener { onScheduleClick() } composeScheduleView.setResetOnClickListener { resetSchedule() } + composeScheduleView.setListener(this) atButton.setOnClickListener { atButtonClicked() } hashButton.setOnClickListener { hashButtonClicked() } @@ -992,9 +993,8 @@ class ComposeActivity : BaseActivity(), } } - override fun onTimeSet(view: TimePicker, hourOfDay: Int, minute: Int) { - composeScheduleView.onTimeSet(hourOfDay, minute) - viewModel.updateScheduledAt(composeScheduleView.time) + override fun onTimeSet(time: String) { + viewModel.updateScheduledAt(time) if (verifyScheduledTime()) { scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN } else { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.java b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.java index a1a99a7dc..14c574a15 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.java +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.java @@ -17,7 +17,6 @@ package com.keylesspalace.tusky.components.compose.view; import android.content.Context; import android.graphics.drawable.Drawable; -import android.os.Bundle; import android.util.AttributeSet; import android.widget.Button; import android.widget.TextView; @@ -31,8 +30,9 @@ import androidx.core.content.ContextCompat; import com.google.android.material.datepicker.CalendarConstraints; import com.google.android.material.datepicker.DateValidatorPointForward; import com.google.android.material.datepicker.MaterialDatePicker; +import com.google.android.material.timepicker.MaterialTimePicker; +import com.google.android.material.timepicker.TimeFormat; import com.keylesspalace.tusky.R; -import com.keylesspalace.tusky.fragment.TimePickerFragment; import java.text.DateFormat; import java.text.ParseException; @@ -44,6 +44,12 @@ import java.util.TimeZone; public class ComposeScheduleView extends ConstraintLayout { + public interface OnTimeSetListener { + void onTimeSet(String time); + } + + private OnTimeSetListener listener; + private DateFormat dateFormat; private DateFormat timeFormat; private SimpleDateFormat iso8601; @@ -92,6 +98,10 @@ public class ComposeScheduleView extends ConstraintLayout { setEditIcons(); } + public void setListener(OnTimeSetListener listener) { + this.listener = listener; + } + private void setScheduledDateTime() { if (scheduleDateTime == null) { scheduledDateTimeView.setText(""); @@ -144,13 +154,20 @@ public class ComposeScheduleView extends ConstraintLayout { } private void openPickTimeDialog() { - TimePickerFragment picker = new TimePickerFragment(); + MaterialTimePicker.Builder pickerBuilder = new MaterialTimePicker.Builder(); if (scheduleDateTime != null) { - Bundle args = new Bundle(); - args.putInt(TimePickerFragment.PICKER_TIME_HOUR, scheduleDateTime.get(Calendar.HOUR_OF_DAY)); - args.putInt(TimePickerFragment.PICKER_TIME_MINUTE, scheduleDateTime.get(Calendar.MINUTE)); - picker.setArguments(args); + pickerBuilder.setHour(scheduleDateTime.get(Calendar.HOUR_OF_DAY)) + .setMinute(scheduleDateTime.get(Calendar.MINUTE)); } + if (android.text.format.DateFormat.is24HourFormat(this.getContext())) { + pickerBuilder.setTimeFormat(TimeFormat.CLOCK_24H); + } else { + pickerBuilder.setTimeFormat(TimeFormat.CLOCK_12H); + } + + MaterialTimePicker picker = pickerBuilder.build(); + picker.addOnPositiveButtonClickListener(v -> onTimeSet(picker.getHour(), picker.getMinute())); + picker.show(((AppCompatActivity) getContext()).getSupportFragmentManager(), "time_picker"); } @@ -200,11 +217,14 @@ public class ComposeScheduleView extends ConstraintLayout { openPickTimeDialog(); } - public void onTimeSet(int hourOfDay, int minute) { + private void onTimeSet(int hourOfDay, int minute) { initializeSuggestedTime(); scheduleDateTime.set(Calendar.HOUR_OF_DAY, hourOfDay); scheduleDateTime.set(Calendar.MINUTE, minute); setScheduledDateTime(); + if (listener != null) { + listener.onTimeSet(getTime()); + } } public String getTime() { diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/TimePickerFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/TimePickerFragment.java deleted file mode 100644 index 1349a59c6..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/TimePickerFragment.java +++ /dev/null @@ -1,53 +0,0 @@ -/* Copyright 2019 kyori19 - * - * This file is a part of Tusky. - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU 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 General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Tusky; if not, - * see . */ - -package com.keylesspalace.tusky.fragment; - -import android.app.Dialog; -import android.app.TimePickerDialog; -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.fragment.app.DialogFragment; - -import com.keylesspalace.tusky.components.compose.ComposeActivity; - -import java.util.Calendar; -import java.util.TimeZone; - -public class TimePickerFragment extends DialogFragment { - - public static final String PICKER_TIME_HOUR = "picker_time_hour"; - public static final String PICKER_TIME_MINUTE = "picker_time_minute"; - - @Override - @NonNull - public Dialog onCreateDialog(Bundle savedInstanceState) { - Bundle args = getArguments(); - Calendar calendar = Calendar.getInstance(TimeZone.getDefault()); - if (args != null) { - calendar.set(Calendar.HOUR_OF_DAY, args.getInt(PICKER_TIME_HOUR)); - calendar.set(Calendar.MINUTE, args.getInt(PICKER_TIME_MINUTE)); - } - - return new TimePickerDialog(getContext(), - android.R.style.Theme_DeviceDefault_Dialog, - (ComposeActivity) getActivity(), - calendar.get(Calendar.HOUR_OF_DAY), - calendar.get(Calendar.MINUTE), - true); - } - -}