Compare commits
98 Commits
v1.0.1
...
v1.0.4-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12599db0ff | ||
|
|
c751c85c1c | ||
|
|
f1331a0f6d | ||
|
|
c75c9b60f9 | ||
|
|
eb3adf1dfd | ||
|
|
6533163fd0 | ||
|
|
fa75570254 | ||
|
|
1406ea376d | ||
|
|
1becad6016 | ||
|
|
d34653750e | ||
|
|
705592aefd | ||
|
|
583325d6e8 | ||
|
|
318d271127 | ||
|
|
5562bf936e | ||
|
|
02a1f2ef8c | ||
|
|
7b26649521 | ||
|
|
8059120136 | ||
|
|
ec38210dde | ||
|
|
38eadca4e2 | ||
|
|
31cb17d549 | ||
|
|
10a5bf0a82 | ||
|
|
a58a279e8c | ||
|
|
0fe58e49b6 | ||
|
|
089e297656 | ||
|
|
2a65bdb08f | ||
|
|
93fbc52f6a | ||
|
|
4e4b5fcfe4 | ||
|
|
620bc2285c | ||
|
|
f73849dbb7 | ||
|
|
e8eb12532a | ||
|
|
9a0c383da8 | ||
|
|
ed79cebc57 | ||
|
|
8e65459cb5 | ||
|
|
e32063fa09 | ||
|
|
d979389715 | ||
|
|
d0f2af2913 | ||
|
|
309ccc0a70 | ||
|
|
89c7a13c59 | ||
|
|
ea4d520e23 | ||
|
|
d6a42d0d6b | ||
|
|
025458ce8c | ||
|
|
650822f3b3 | ||
|
|
0422a8c590 | ||
|
|
b640f6c68d | ||
|
|
a4b89b8a52 | ||
|
|
3c9670bbaa | ||
|
|
22d27b13e7 | ||
|
|
233f87d90b | ||
|
|
0dd5e5af8d | ||
|
|
61d537779b | ||
|
|
faa6ed336d | ||
|
|
f499444a86 | ||
|
|
d6202d005f | ||
|
|
2df42396c3 | ||
|
|
73002a8dbf | ||
|
|
a50a14492f | ||
|
|
d896402d39 | ||
|
|
571e593041 | ||
|
|
b7a6d5313d | ||
|
|
c6ea07e43e | ||
|
|
1d33f476d6 | ||
|
|
9dca538f96 | ||
|
|
823a6d7905 | ||
|
|
4b12adc0f8 | ||
|
|
1720665212 | ||
|
|
83e6bb2ced | ||
|
|
78342fbe74 | ||
|
|
0cb502b244 | ||
|
|
559835d849 | ||
|
|
fd4854adae | ||
|
|
2b7d9e5536 | ||
|
|
15d73ab9a2 | ||
|
|
05a7dd9636 | ||
|
|
899e016594 | ||
|
|
2b752be6d0 | ||
|
|
0dd42a07dc | ||
|
|
9e527d9ab1 | ||
|
|
3ee0782a61 | ||
|
|
2721f43e23 | ||
|
|
622b76e27e | ||
|
|
805b2120d6 | ||
|
|
161be10c4b | ||
|
|
95be6ece9d | ||
|
|
1e73ffdba8 | ||
|
|
7b07fe0ead | ||
|
|
64c91c7df6 | ||
|
|
1a36440c9a | ||
|
|
b3b9d848c9 | ||
|
|
acb898ae1f | ||
|
|
2fd3240aca | ||
|
|
12945a9da2 | ||
|
|
2e3fc22185 | ||
|
|
9925c3ac75 | ||
|
|
30d767441f | ||
|
|
3df196181f | ||
|
|
1ba447db33 | ||
|
|
8b8fcfcd8c | ||
|
|
6a1e7ef866 |
14
README.md
14
README.md
@@ -1,11 +1,17 @@
|
||||
# Mastodon for Android
|
||||
# Forked Mastodon for Android
|
||||
[](https://crowdin.com/project/mastodon-for-android)
|
||||
|
||||
<a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android"><img src="img/google-play-badge.png" height="50"></a>
|
||||
|
||||
This is the repository for the official Android app for Mastodon.
|
||||
This is the repository for an officially forked Android app for Mastodon.
|
||||
|
||||
Learn more about this app in the [blog post](https://blog.joinmastodon.org/2022/02/official-mastodon-for-android-app-is-coming-soon/).
|
||||
|
||||
## Changes
|
||||
|
||||
* [Enable "Unlisted" as a visibility option](https://github.com/sk22/mastodon-android-fork/tree/feature/enable-unlisted)
|
||||
([Pull request](https://github.com/mastodon/mastodon-android/pull/103)) and
|
||||
[set as default](https://github.com/sk22/mastodon-android-fork/tree/feature/enable-unlisted-as-default)
|
||||
* [Add "Federation" tab and change Discover tab order](https://github.com/sk22/mastodon-android-fork/tree/feature/add-federated-timeline)
|
||||
|
||||
## Building
|
||||
|
||||
As this app is using Java 17 features, you need JDK 17 or newer to build it. Other than that, everything is pretty standard. You can either import the project into Android Studio and build it from there, or run the following command in the project directory:
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
Mastodon is the largest decentralized social network on the internet. Instead of a single website, it’s a network of millions of users in independent communities that can all interact with one another, seamlessly. No matter what you’re into, you can meet passionate people posting about it on Mastodon!
|
||||
Mastodon est le plus grand réseau social décentralisé sur Internet. Au lieu d’un site Web unique, c’est un réseau de millions d’utilisateurs dans des communautés indépendantes qui peuvent tous interagir les uns avec les autres, de manière transparente. Peu importe ce que vous êtes, vous pouvez rencontrer des gens passionnés qui publient à ce sujet sur Mastodon !
|
||||
|
||||
Join a community and create your profile. Find and and follow fascinating folks and read their posts in an ad-free, chronological timeline. Express yourself with custom emoji, images, GIFs, videos, and audio in 500-character posts. Reply to threads and reblog posts from anyone to share great stuff. Find new accounts to follow and trending hashtags to expand your network.
|
||||
Rejoignez une communauté et créez votre profil. Trouvez et suivez des gens fascinants et lisez leurs messages dans une chronologie chronologique sans publicité. Exprimez-vous avec des émojis personnalisés, des images, des GIFs, des vidéos et de l’audio dans des messages de 500 caractères. Répondez aux sujets de discussions et aux reblogues de n’importe qui pour partager des choses géniales. Trouvez de nouveaux comptes à suivre et des hashtags tendance pour étendre votre réseau.
|
||||
|
||||
Mastodon is built with a focus on privacy and safety. Decide whether your posts are shared with your followers, just the people you mention, or the whole world. Content warnings let you hide posts containing sensitive or triggering material until you're ready to engage with them. Each community has its own guidelines and moderators to keep its members safe, and robust blocking and reporting tools help prevent abuse.
|
||||
Mastodon est construit en mettant l’accent sur la vie privée et la sécurité. Décidez si vos messages sont partagés avec vos abonnés, les personnes que vous mentionnez, ou le monde entier. Les avertissements de contenu vous permettent de masquer les messages au contenu sensible jusqu’à ce que vous soyez prêt à vous engager avec eux. Chaque communauté a ses propres directives et modérateurs pour assurer la sécurité de ses membres, et de solides outils de blocage et de signalement aident à prévenir les abus.
|
||||
|
||||
More features:
|
||||
Plus de fonctionnalités :
|
||||
|
||||
• Dark Mode: Read posts in light, dark, or true black mode
|
||||
• Polls: Ask followers for their opinion and tally the votes
|
||||
• Explore: Trending hashtags and accounts are a tap away
|
||||
• Notifications: Get notified about new follows, replies, and reblogs
|
||||
• Sharing: Post directly to Mastodon from any share sheet in any app
|
||||
• Cuteness: Our mascot is an adorable elephant, and you'll see them pop up from time to time
|
||||
• Mode Sombre : Lisez les messages en mode clair, sombre ou vrai noir
|
||||
• Sondages : Demandez l’opinion à vos abonnés et comptez les votes
|
||||
• Explorer : Les hashtags et les comptes tendance sont à portée de main
|
||||
• Notifications : Soyez informé des nouveaux abonnements, réponses et reblogs
|
||||
• Partage : Publiez directement sur Mastodon à partir de n’importe quelle feuille de partage dans n’importe quelle application
|
||||
• Cuteness : Notre mascotte est un adorable éléphant, et vous la verrez apparaître de temps en temps
|
||||
|
||||
Mastodon is a registered nonprofit and development is supported directly by your donations. There’s no advertising, no monetization, and no venture capital, and we plan to keep it that way.
|
||||
Mastodon est un organisme sans but lucratif enregistré et le développement est soutenu directement par vos dons. Il n’y a pas de publicité, pas de monétisation, pas de capital-risque, et nous prévoyons de continuer ainsi.
|
||||
@@ -1 +1 @@
|
||||
Decentralized social network
|
||||
Réseau social décentralisé
|
||||
@@ -1,16 +1,16 @@
|
||||
Mastodon é a rede social descentralizada máis grande de internet. Non é unha soa web, é unha rede de millóns de persoas en comunidades independentes que poden interactuar entre elas, sen problema. Sexan cales fosen os teus intereses, podes atopar persoas comentando ese tema en Mastodon!
|
||||
|
||||
Únete a unha comunidade e crea un perfil. Atopa e segue a persoas interesantes e lé o que publican, nunha cronoloxía limpa de publicidade e ordenada. Exprésate usando emojis personalizados, imaxes, GIFs, vídeos e audio con publicacións de 500 caracteres. Responde aos fíos e promove publicacións doutras persoas que creas relevantes. Atopa novas contas e segue os cancelos en voga para facer medrar a túa rede.
|
||||
Únete a unha comunidade e crea un perfil. Atopa e segue a persoas interesantes e le o que publican, nunha cronoloxía limpa de publicidade e ordenada. Exprésate usando emojis personalizados, imaxes, GIFs, vídeos e audio con publicacións de 500 caracteres. Responde aos fíos e promove publicacións doutras persoas que creas relevantes. Atopa novas contas e segue os cancelos en voga para facer medrar a túa rede.
|
||||
|
||||
Mastodon está creado pensando na privacidade e seguridade. Decide con quen compartes as túas publicacións, só coas seguidoras ou persoas que mencionas, ou con todo o mundo. Os avisos sobre o contido permiten agochar contido sensible ou material que podería crear ansiedade ata que estás preparada para velo. Cada comunidade ten as súas normas e a moderación coida da seguridade das persoas da instancia, con ferramentas para denunciar e bloquear e así evitar abusos.
|
||||
|
||||
Máis características:
|
||||
|
||||
• Modo Escuro: ler publicacións en modo claro, escuro ou negro total
|
||||
• Modo Escuro: ler publicacións en modo claro, escuro ou negro verdadeiro
|
||||
• Enquisas: pregúntalle ás seguidoras a súa opinión e recolle os votos
|
||||
• Explorar: Cancelos e contas en voga fácilmente accesibles
|
||||
• Explorar: Cancelos e contas en voga facilmente accesibles
|
||||
• Notificacións: recibe notificacións sobre seguimentos, respostas e promocións
|
||||
• Compartir: publica directamente en Mastodon desde o menú de calquera app
|
||||
• Fermosiña: A nosa mascota é un elefante moi feitiño, que verás a miúdo por aquí
|
||||
• Fermosura: a nosa mascota é un elefante moi feitiño, que verás a miúdo por aquí
|
||||
|
||||
Mastodon é unha organización rexistrada sen ánimo de lucro cuxo desenvolvemento está financiado por doazóns. Non hai publicidade, nen monetización, sen inversións de capital risco, e pretendemos seguir así.
|
||||
@@ -1,16 +1,16 @@
|
||||
Mastodon is the largest decentralized social network on the internet. Instead of a single website, it’s a network of millions of users in independent communities that can all interact with one another, seamlessly. No matter what you’re into, you can meet passionate people posting about it on Mastodon!
|
||||
Mastodon je najveća decentralizirana društvena mreža na internetu. Umjesto jedne web stranice, to je mreža milijuna korisnika u neovisnim zajednicama koje sve mogu međusobno komunicirati. Bez obzira na to što te zanima, možeš upoznati strastvene ljude koji o tome objavljuju na Mastodonu!
|
||||
|
||||
Join a community and create your profile. Find and and follow fascinating folks and read their posts in an ad-free, chronological timeline. Express yourself with custom emoji, images, GIFs, videos, and audio in 500-character posts. Reply to threads and reblog posts from anyone to share great stuff. Find new accounts to follow and trending hashtags to expand your network.
|
||||
Pridruži se zajednici i kreiraj svoj profil. Pronađi i prati fascinantne ljude i čitaj njihove postove u kronološkoj vremenskoj liniji bez oglasa. Izrazi se prilagođenim emojijima, slikama, GIF-ovima, videozapisima i zvukom u objavama od 500 znakova. Odgovori na teme i reblogaj postove od bilo koga da podijeliš sjajne stvari. Pronađi nove račune koje ćeš pratiti i popularne hashtagove kako bi proširio/la svoju mrežu.
|
||||
|
||||
Mastodon is built with a focus on privacy and safety. Decide whether your posts are shared with your followers, just the people you mention, or the whole world. Content warnings let you hide posts containing sensitive or triggering material until you're ready to engage with them. Each community has its own guidelines and moderators to keep its members safe, and robust blocking and reporting tools help prevent abuse.
|
||||
Mastodon je izgrađen s fokusom na privatnost i sigurnost. Odluči hoće li se tvoje objave dijeliti s tvojim sljedbenicima, samo s osobama koje spominjete ili s cijelim svijetom. Upozorenja o sadržaju omogućuju ti da sakriješ postove koji sadrže osjetljivi ili izazovni materijal dok ne budeš spreman za interakciju s njima. Svaka zajednica ima vlastite smjernice i moderatore kako bi zaštitili svoje članove, a robusni alati za blokiranje i prijavljivanje pomažu u sprječavanju zlouporabe.
|
||||
|
||||
More features:
|
||||
Više značajki:
|
||||
|
||||
• Dark Mode: Read posts in light, dark, or true black mode
|
||||
• Polls: Ask followers for their opinion and tally the votes
|
||||
• Explore: Trending hashtags and accounts are a tap away
|
||||
• Notifications: Get notified about new follows, replies, and reblogs
|
||||
• Sharing: Post directly to Mastodon from any share sheet in any app
|
||||
• Cuteness: Our mascot is an adorable elephant, and you'll see them pop up from time to time
|
||||
• Tamni način rada: čitaj postove u svijetlom, tamnom ili stvarno crnom načinu
|
||||
• Ankete: Pitaj sljedbenike za mišljenje i zbroji glasove
|
||||
• Istraži: popularni hashtagovi i računi udaljeni su samo jedan dodir
|
||||
• Obavijesti: primaj obavijesti o novim pratiteljima, odgovorima i reblogovima
|
||||
• Dijeljenje: objavi izravno na Mastodonu s bilo kojeg lista za dijeljenje u bilo kojoj aplikaciji
|
||||
• Slatkoća: Naša maskota je ljupki slon i vidjet ćeš ih kako iskaču s vremena na vrijeme
|
||||
|
||||
Mastodon is a registered nonprofit and development is supported directly by your donations. There’s no advertising, no monetization, and no venture capital, and we plan to keep it that way.
|
||||
Mastodon je registrirana neprofitna organizacija i razvoj je podržan izravno vašim donacijama. Nema oglašavanja, nema monetizacije i rizičnog kapitala, a planiramo takvi i ostati.
|
||||
@@ -1 +1 @@
|
||||
Decentralized social network
|
||||
Decentralizirana društvena mreža
|
||||
@@ -1,8 +1,8 @@
|
||||
Mastodon to największa zdecentralizowana sieć społecznościowa w Internecie. Zamiast jednej strony internetowej, jest to sieć milionów użytkowników w niezależnych społecznościach, które mogą ze sobą wchodzić w interakcje. Niezależnie od swoich zainteresowań, momżesz poznać interesujących ludzi piszących o nich na Mastodonie!
|
||||
|
||||
Dołącz do społeczności i utwórz swój profil. Poznaj i obserwuj fascynujących ludzi i czytaj ich wpisy w chronologicznym osi czasu. Wyrażaj siebie za pomocą niestandardowych emoji, obrazów, GIFów, filmów i audio w 500-znakowych wpisach. Reply to threads and reblog posts from anyone to share great stuff. Find new accounts to follow and trending hashtags to expand your network.
|
||||
Dołącz do społeczności i utwórz swój profil. Poznaj i obserwuj fascynujących ludzi i czytaj ich wpisy w chronologicznym osi czasu. Wyrażaj siebie za pomocą niestandardowych emoji, obrazów, GIFów, filmów i audio w 500-znakowych wpisach. Odpowiadaj na wątki i podawaj dalej posty od każdego, aby dzielić się wspaniałymi rzeczami. Find new accounts to follow and trending hashtags to expand your network.
|
||||
|
||||
Mastodon is built with a focus on privacy and safety. Decide whether your posts are shared with your followers, just the people you mention, or the whole world. Content warnings let you hide posts containing sensitive or triggering material until you're ready to engage with them. Each community has its own guidelines and moderators to keep its members safe, and robust blocking and reporting tools help prevent abuse.
|
||||
Mastodon został zaprojektowany z myślą o prywatności i bezpieczeństwie. Decide whether your posts are shared with your followers, just the people you mention, or the whole world. Ostrzeżenia dotyczące zawartości pozwalają ukrywać posty zawierające wrażliwe lub wyzywające materiały, dopóki nie będziesz gotów się z nimi pogodzić. Each community has its own guidelines and moderators to keep its members safe, and robust blocking and reporting tools help prevent abuse.
|
||||
|
||||
Więcej funkcji:
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
Mastodon is the largest decentralized social network on the internet. Instead of a single website, it’s a network of millions of users in independent communities that can all interact with one another, seamlessly. No matter what you’re into, you can meet passionate people posting about it on Mastodon!
|
||||
Mastodon, internetteki merkezi olmayan en büyük sosyal ağdır. Tek bir web siteye bağlı kalmaksızın, milyonlarca kullanıcının bağımsız olarak birbiri ile kolayca etkileşebileceği bir ağdır. Hangi konuyla ilgili olduğun önemli değil, Mastodon'da onunla ilgili gönderi paylaşan tutkulu insanlarla tanışabilirsin!
|
||||
|
||||
Join a community and create your profile. Find and and follow fascinating folks and read their posts in an ad-free, chronological timeline. Express yourself with custom emoji, images, GIFs, videos, and audio in 500-character posts. Reply to threads and reblog posts from anyone to share great stuff. Find new accounts to follow and trending hashtags to expand your network.
|
||||
Bir topluluğa katıl ve profilini oluştur. Olağanüstü kişileri bul ve takip et, gönderilerini kronolojik ve reklamsız sunan bir akışta oku. Gönderilerinde 500 karakter sınırlamasıyla kendini emojiler, görseller, GIFler, videolar ve sesler ile ifade et. Harika içerikler paylaşmak için başlıklara yanıt yaz, insanların gönderilerini yeniden paylaş. Ağınızı genişletmek için takip edilecek yeni hesaplar ve hashtagler bul.
|
||||
|
||||
Mastodon is built with a focus on privacy and safety. Decide whether your posts are shared with your followers, just the people you mention, or the whole world. Content warnings let you hide posts containing sensitive or triggering material until you're ready to engage with them. Each community has its own guidelines and moderators to keep its members safe, and robust blocking and reporting tools help prevent abuse.
|
||||
Mastodon gizlilik ve güvenlik odaklı yapılmıştır. Her postunuz için takipçilerinizle mi, bahsettiğiniz kişilerle mi ya da tüm dünyayla mı paylaşılacağına karar verin. Gönderi uyarıları, hassas ve tetikleyici olabilecek içerikleri kişi görmeyi hazır olana kadar gizler. Her topluluk, üyelerini güvende tutmak için kendi kurallarına ve moderatörlerine; istismarı önlemek için de güçlü engelleme ve bildirme araçlarına sahiptir.
|
||||
|
||||
More features:
|
||||
Diğer özellikler:
|
||||
|
||||
• Dark Mode: Read posts in light, dark, or true black mode
|
||||
• Polls: Ask followers for their opinion and tally the votes
|
||||
• Explore: Trending hashtags and accounts are a tap away
|
||||
• Notifications: Get notified about new follows, replies, and reblogs
|
||||
• Sharing: Post directly to Mastodon from any share sheet in any app
|
||||
• Cuteness: Our mascot is an adorable elephant, and you'll see them pop up from time to time
|
||||
• Koyu Mod: Gönderileri aydınlık, karanlık ya da gerçek karanlık modunda okuyabilirsin
|
||||
• Anketler: Takipçilerine görüşlerini sor ve oylarını gör
|
||||
• Keşfet: Trend hashtagler ve hesaplar bir tık uzağında
|
||||
• Bildirimler: Yeni takipçilerden, yanıtlardan ve yeniden paylaşımlardan haberin olsun
|
||||
• Paylaşım: Doğrudan Mastodon'a herhangi bir tipte gönderi paylaş
|
||||
• Sevimlilik: Maskotumuz şirin bir fil ve onu uygulamada zaman zaman göreceksin
|
||||
|
||||
Mastodon is a registered nonprofit and development is supported directly by your donations. There’s no advertising, no monetization, and no venture capital, and we plan to keep it that way.
|
||||
Mastodon kar amacı gütmeyen bir kuruluştur ve geliştirilmesi doğrudan bağışlarınızla sağlanmaktadır. Reklam, para kazanma amacı, risk sermayesi yoktur ve bunu böyle tutmayı planlıyoruz.
|
||||
@@ -1 +1 @@
|
||||
Decentralized social network
|
||||
Merkezsizleştirilmiş sosyal ağ
|
||||
@@ -1,16 +1,16 @@
|
||||
Mastodon is the largest decentralized social network on the internet. Instead of a single website, it’s a network of millions of users in independent communities that can all interact with one another, seamlessly. No matter what you’re into, you can meet passionate people posting about it on Mastodon!
|
||||
Mastodon 是互联网上最大的去中心化社交网络。 它不是一个网站,而是由独立社区节点及其数以百万计的用户组成的网络,所有这些用户都能够无缝地相互交流。 无论你进入哪一个节点,你都可以与所有在 Mastodon 的人之间进行交流。
|
||||
|
||||
Join a community and create your profile. Find and and follow fascinating folks and read their posts in an ad-free, chronological timeline. Express yourself with custom emoji, images, GIFs, videos, and audio in 500-character posts. Reply to threads and reblog posts from anyone to share great stuff. Find new accounts to follow and trending hashtags to expand your network.
|
||||
加入一个社区节点并创建你的账户。 寻找并关注感兴趣的同好,无广告地浏览他们的时间线。 用自定义表情、图像、GIF、视频和音频在 500 个字符的帖子里写下你的想法。 回复及转发其他人的帖子来共同分享美好的事物。 寻找新的账户以及热门的话题来拓展你的网络。
|
||||
|
||||
Mastodon is built with a focus on privacy and safety. Decide whether your posts are shared with your followers, just the people you mention, or the whole world. Content warnings let you hide posts containing sensitive or triggering material until you're ready to engage with them. Each community has its own guidelines and moderators to keep its members safe, and robust blocking and reporting tools help prevent abuse.
|
||||
Mastodon 以隐私和安全为首要目标。 自由选择你的帖子是分享给关注者,或是你提到的人,亦或是整个世界。 内容警告允许让你隐藏可能剧透的内容,让其他人在交互之前做好充分准备。 每个社区都有自己的规则和管理员,让其用户保持安全。通过强有力的屏蔽和举报工具防止滥用。
|
||||
|
||||
More features:
|
||||
更多功能:
|
||||
|
||||
• Dark Mode: Read posts in light, dark, or true black mode
|
||||
• Polls: Ask followers for their opinion and tally the votes
|
||||
• Explore: Trending hashtags and accounts are a tap away
|
||||
• Notifications: Get notified about new follows, replies, and reblogs
|
||||
• Sharing: Post directly to Mastodon from any share sheet in any app
|
||||
• Cuteness: Our mascot is an adorable elephant, and you'll see them pop up from time to time
|
||||
• 暗色模式:以浅色、深色、纯黑模式下进行阅读
|
||||
• 投票:询问关注者的意见并统计他们的投票
|
||||
• 探索:热门的话题以及账号
|
||||
• 通知:获得新关注、回复和转发的通知
|
||||
• 分享:从其他应用中的分享菜单中直接发布到 Mastodon
|
||||
• 吉祥物:你会不时地看到我们可爱的长毛象
|
||||
|
||||
Mastodon is a registered nonprofit and development is supported directly by your donations. There’s no advertising, no monetization, and no venture capital, and we plan to keep it that way.
|
||||
Mastodon 是一个注册的非营利开发项目,直接由您的捐赠支持。 没有广告,没有商业化,也没有风险资本,我们计划保持这种方式。
|
||||
@@ -1 +1 @@
|
||||
Decentralized social network
|
||||
分布式社交网络
|
||||
@@ -9,8 +9,8 @@ android {
|
||||
applicationId "org.joinmastodon.android"
|
||||
minSdk 23
|
||||
targetSdk 31
|
||||
versionCode 30
|
||||
versionName "1.0.1"
|
||||
versionCode 3
|
||||
versionName '1.0.4-dev+fork.1.1'
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ dependencies {
|
||||
implementation 'me.grishka.litex:dynamicanimation:1.1.0-alpha03'
|
||||
implementation 'me.grishka.litex:viewpager:1.0.0'
|
||||
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
||||
implementation 'me.grishka.appkit:appkit:1.2.2'
|
||||
implementation 'me.grishka.appkit:appkit:1.2.6'
|
||||
implementation 'com.google.code.gson:gson:2.8.9'
|
||||
implementation 'org.jsoup:jsoup:1.14.3'
|
||||
implementation 'com.squareup:otto:1.3.8'
|
||||
|
||||
@@ -14,11 +14,13 @@ import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
|
||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.PaginatedResponse;
|
||||
import org.joinmastodon.android.model.SearchResult;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
@@ -41,6 +43,8 @@ public class CacheController{
|
||||
private DatabaseHelper db;
|
||||
private final Runnable databaseCloseRunnable=this::closeDatabase;
|
||||
|
||||
private static final int POST_FLAG_GAP_AFTER=1;
|
||||
|
||||
static{
|
||||
databaseThread.start();
|
||||
}
|
||||
@@ -49,14 +53,14 @@ public class CacheController{
|
||||
this.accountID=accountID;
|
||||
}
|
||||
|
||||
public void getHomeTimeline(String maxID, int count, boolean forceReload, Callback<PaginatedResponse<List<Status>>> callback){
|
||||
public void getHomeTimeline(String maxID, int count, boolean forceReload, Callback<CacheablePaginatedResponse<List<Status>>> callback){
|
||||
cancelDelayedClose();
|
||||
databaseThread.postRunnable(()->{
|
||||
try{
|
||||
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.HOME)).collect(Collectors.toList());
|
||||
if(!forceReload){
|
||||
SQLiteDatabase db=getOrOpenDatabase();
|
||||
try(Cursor cursor=db.query("home_timeline", new String[]{"json"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`id` DESC", count+"")){
|
||||
try(Cursor cursor=db.query("home_timeline", new String[]{"json", "flags"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`id` DESC", count+"")){
|
||||
if(cursor.getCount()==count){
|
||||
ArrayList<Status> result=new ArrayList<>();
|
||||
cursor.moveToFirst();
|
||||
@@ -65,6 +69,8 @@ public class CacheController{
|
||||
do{
|
||||
Status status=MastodonAPIController.gson.fromJson(cursor.getString(0), Status.class);
|
||||
status.postprocess();
|
||||
int flags=cursor.getInt(1);
|
||||
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0);
|
||||
newMaxID=status.id;
|
||||
for(Filter filter:filters){
|
||||
if(filter.matches(status.getContentStatus().content))
|
||||
@@ -73,25 +79,18 @@ public class CacheController{
|
||||
result.add(status);
|
||||
}while(cursor.moveToNext());
|
||||
String _newMaxID=newMaxID;
|
||||
uiHandler.post(()->callback.onSuccess(new PaginatedResponse<>(result, _newMaxID)));
|
||||
uiHandler.post(()->callback.onSuccess(new CacheablePaginatedResponse<>(result, _newMaxID, true)));
|
||||
return;
|
||||
}
|
||||
}catch(IOException x){
|
||||
Log.w(TAG, "getHomeTimeline: corrupted status object in database", x);
|
||||
}
|
||||
}
|
||||
new GetHomeTimeline(maxID, null, count)
|
||||
new GetHomeTimeline(maxID, null, count, null)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
callback.onSuccess(new PaginatedResponse<>(result.stream().filter(post->{
|
||||
for(Filter filter:filters){
|
||||
if(filter.matches(post.getContentStatus().content)){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id));
|
||||
callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(new StatusFilterPredicate(filters)).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
|
||||
putHomeTimeline(result, maxID==null);
|
||||
}
|
||||
|
||||
@@ -110,14 +109,18 @@ public class CacheController{
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private void putHomeTimeline(List<Status> posts, boolean clear){
|
||||
public void putHomeTimeline(List<Status> posts, boolean clear){
|
||||
runOnDbThread((db)->{
|
||||
if(clear)
|
||||
db.delete("home_timeline", null, null);
|
||||
ContentValues values=new ContentValues(2);
|
||||
ContentValues values=new ContentValues(3);
|
||||
for(Status s:posts){
|
||||
values.put("id", s.id);
|
||||
values.put("json", MastodonAPIController.gson.toJson(s));
|
||||
int flags=0;
|
||||
if(s.hasGapAfter)
|
||||
flags|=POST_FLAG_GAP_AFTER;
|
||||
values.put("flags", flags);
|
||||
db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
package org.joinmastodon.android.api;
|
||||
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonIOException;
|
||||
import com.google.gson.JsonObject;
|
||||
@@ -16,11 +12,9 @@ import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.api.gson.IsoInstantTypeAdapter;
|
||||
import org.joinmastodon.android.api.gson.IsoLocalDateTypeAdapter;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.model.BaseModel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
@@ -144,7 +138,7 @@ public class MastodonAPIController{
|
||||
}
|
||||
|
||||
try{
|
||||
req.validateAndPostprocessResponse(respObj);
|
||||
req.validateAndPostprocessResponse(respObj, response);
|
||||
}catch(IOException x){
|
||||
if(BuildConfig.DEBUG)
|
||||
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error post-processing or validating response", x);
|
||||
|
||||
@@ -28,6 +28,7 @@ import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||
private static final String TAG="MastodonAPIRequest";
|
||||
@@ -75,9 +76,14 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||
}
|
||||
|
||||
public MastodonAPIRequest<T> exec(String accountID){
|
||||
account=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
domain=account.domain;
|
||||
account.getApiController().submitRequest(this);
|
||||
try{
|
||||
account=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
domain=account.domain;
|
||||
account.getApiController().submitRequest(this);
|
||||
}catch(Exception x){
|
||||
Log.e(TAG, "exec: this shouldn't happen, but it still did", x);
|
||||
invokeErrorCallback(new MastodonErrorResponse(x.getLocalizedMessage(), -1));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -153,7 +159,7 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
public void validateAndPostprocessResponse(T respObj) throws IOException{
|
||||
public void validateAndPostprocessResponse(T respObj, Response httpResponse) throws IOException{
|
||||
if(respObj instanceof BaseModel){
|
||||
((BaseModel) respObj).postprocess();
|
||||
}else if(respObj instanceof List){
|
||||
|
||||
@@ -121,10 +121,6 @@ public class PushSubscriptionManager{
|
||||
return !TextUtils.isEmpty(deviceToken);
|
||||
}
|
||||
|
||||
public void registerAccountForPush(){
|
||||
registerAccountForPush(null);
|
||||
}
|
||||
|
||||
public void registerAccountForPush(PushSubscription subscription){
|
||||
if(TextUtils.isEmpty(deviceToken))
|
||||
throw new IllegalStateException("No device push token available");
|
||||
@@ -141,7 +137,9 @@ public class PushSubscriptionManager{
|
||||
encodedPublicKey=Base64.encodeToString(serializeRawPublicKey(publicKey), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
|
||||
authKey=new byte[16];
|
||||
new SecureRandom().nextBytes(authKey);
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
AccountSession session=AccountSessionManager.getInstance().tryGetAccount(accountID);
|
||||
if(session==null)
|
||||
return;
|
||||
session.pushPrivateKey=Base64.encodeToString(privateKey.getEncoded(), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
|
||||
session.pushPublicKey=Base64.encodeToString(publicKey.getEncoded(), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
|
||||
session.pushAuthKey=encodedAuthKey=Base64.encodeToString(authKey, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
|
||||
@@ -162,7 +160,9 @@ public class PushSubscriptionManager{
|
||||
MastodonAPIController.runInBackground(()->{
|
||||
serverKey=deserializeRawPublicKey(Base64.decode(result.serverKey, Base64.URL_SAFE));
|
||||
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
AccountSession session=AccountSessionManager.getInstance().tryGetAccount(accountID);
|
||||
if(session==null)
|
||||
return;
|
||||
session.pushSubscription=result;
|
||||
AccountSessionManager.getInstance().writeAccountsFile();
|
||||
Log.d(TAG, "Successfully registered "+accountID+" for push notifications");
|
||||
@@ -183,7 +183,9 @@ public class PushSubscriptionManager{
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(PushSubscription result){
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
AccountSession session=AccountSessionManager.getInstance().tryGetAccount(accountID);
|
||||
if(session==null)
|
||||
return;
|
||||
if(result.policy!=subscription.policy)
|
||||
result.policy=subscription.policy;
|
||||
session.pushSubscription=result;
|
||||
@@ -196,7 +198,9 @@ public class PushSubscriptionManager{
|
||||
if(((MastodonErrorResponse)error).httpStatus==404){ // Not registered for push, register now
|
||||
registerAccountForPush(subscription);
|
||||
}else{
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
AccountSession session=AccountSessionManager.getInstance().tryGetAccount(accountID);
|
||||
if(session==null)
|
||||
return;
|
||||
session.needUpdatePushSettings=true;
|
||||
session.pushSubscription=subscription;
|
||||
AccountSessionManager.getInstance().writeAccountsFile();
|
||||
@@ -359,7 +363,7 @@ public class PushSubscriptionManager{
|
||||
private static void registerAllAccountsForPush(boolean forceReRegister){
|
||||
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
|
||||
if(session.pushSubscription==null || forceReRegister)
|
||||
session.getPushSubscriptionManager().registerAccountForPush();
|
||||
session.getPushSubscriptionManager().registerAccountForPush(session.pushSubscription);
|
||||
else if(session.needUpdatePushSettings)
|
||||
session.getPushSubscriptionManager().updatePushSettings(session.pushSubscription);
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public class ResizedImageRequestBody extends CountingRequestBody{
|
||||
}
|
||||
if(factor%1f!=0f){
|
||||
Bitmap scaled=Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_8888);
|
||||
new Canvas(scaled).drawBitmap(bitmap, null, new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()), new Paint(Paint.FILTER_BITMAP_FLAG));
|
||||
new Canvas(scaled).drawBitmap(bitmap, null, new Rect(0, 0, targetWidth, targetHeight), new Paint(Paint.FILTER_BITMAP_FLAG));
|
||||
bitmap=scaled;
|
||||
}
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.joinmastodon.android.api.requests;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import okhttp3.Response;
|
||||
|
||||
public abstract class HeaderPaginationRequest<I> extends MastodonAPIRequest<HeaderPaginationList<I>>{
|
||||
private static final Pattern LINK_HEADER_PATTERN=Pattern.compile("(?:(?:,\\s*)?<([^>]+)>|;\\s*(\\w+)=['\"](\\w+)['\"])");
|
||||
|
||||
public HeaderPaginationRequest(HttpMethod method, String path, Class<HeaderPaginationList<I>> respClass){
|
||||
super(method, path, respClass);
|
||||
}
|
||||
|
||||
public HeaderPaginationRequest(HttpMethod method, String path, TypeToken<HeaderPaginationList<I>> respTypeToken){
|
||||
super(method, path, respTypeToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateAndPostprocessResponse(HeaderPaginationList<I> respObj, Response httpResponse) throws IOException{
|
||||
super.validateAndPostprocessResponse(respObj, httpResponse);
|
||||
String link=httpResponse.header("Link");
|
||||
if(!TextUtils.isEmpty(link)){
|
||||
Matcher matcher=LINK_HEADER_PATTERN.matcher(link);
|
||||
String url=null;
|
||||
while(matcher.find()){
|
||||
if(url==null){
|
||||
String _url=matcher.group(1);
|
||||
if(_url==null)
|
||||
continue;
|
||||
url=_url;
|
||||
}else{
|
||||
String paramName=matcher.group(2);
|
||||
String paramValue=matcher.group(3);
|
||||
if(paramName==null || paramValue==null)
|
||||
return;
|
||||
if("rel".equals(paramName)){
|
||||
switch(paramValue){
|
||||
case "next" -> respObj.nextPageUri=Uri.parse(url);
|
||||
case "prev" -> respObj.prevPageUri=Uri.parse(url);
|
||||
}
|
||||
url=null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
|
||||
public class GetAccountFollowers extends HeaderPaginationRequest<Account>{
|
||||
public GetAccountFollowers(String id, String maxID, int limit){
|
||||
super(HttpMethod.GET, "/accounts/"+id+"/followers", new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", limit+"");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
|
||||
public class GetAccountFollowing extends HeaderPaginationRequest<Account>{
|
||||
public GetAccountFollowing(String id, String maxID, int limit){
|
||||
super(HttpMethod.GET, "/accounts/"+id+"/following", new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", limit+"");
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,14 @@ import org.joinmastodon.android.model.Status;
|
||||
import java.util.List;
|
||||
|
||||
public class GetHomeTimeline extends MastodonAPIRequest<List<Status>>{
|
||||
public GetHomeTimeline(String maxID, String minID, int limit){
|
||||
public GetHomeTimeline(String maxID, String minID, int limit, String sinceID){
|
||||
super(HttpMethod.GET, "/timelines/home", new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(minID!=null)
|
||||
addQueryParameter("min_id", minID);
|
||||
if(sinceID!=null)
|
||||
addQueryParameter("since_id", sinceID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", ""+limit);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.joinmastodon.android.api.requests.timelines;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GetPublicTimeline extends MastodonAPIRequest<List<Status>>{
|
||||
public GetPublicTimeline(boolean local, boolean remote, String maxID, int limit){
|
||||
super(HttpMethod.GET, "/timelines/public", new TypeToken<>(){});
|
||||
if(local)
|
||||
addQueryParameter("local", "true");
|
||||
if(remote)
|
||||
addQueryParameter("remote", "true");
|
||||
if(!TextUtils.isEmpty(maxID))
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", limit+"");
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ public class AccountSessionManager{
|
||||
writeAccountsFile();
|
||||
updateInstanceEmojis(instance, instance.uri);
|
||||
if(PushSubscriptionManager.arePushNotificationsAvailable()){
|
||||
session.getPushSubscriptionManager().registerAccountForPush();
|
||||
session.getPushSubscriptionManager().registerAccountForPush(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,10 +133,20 @@ public class AccountSessionManager{
|
||||
return session;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AccountSession tryGetAccount(String id){
|
||||
return sessions.get(id);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AccountSession getLastActiveAccount(){
|
||||
if(sessions.isEmpty() || lastActiveAccountID==null)
|
||||
return null;
|
||||
if(!sessions.containsKey(lastActiveAccountID)){
|
||||
// TODO figure out why this happens. It should not be possible.
|
||||
lastActiveAccountID=getLoggedInAccounts().get(0).getID();
|
||||
writeAccountsFile();
|
||||
}
|
||||
return getAccount(lastActiveAccountID);
|
||||
}
|
||||
|
||||
@@ -197,6 +207,7 @@ public class AccountSessionManager{
|
||||
|
||||
new CustomTabsIntent.Builder()
|
||||
.setShareState(CustomTabsIntent.SHARE_STATE_OFF)
|
||||
.setShowTitle(true)
|
||||
.build()
|
||||
.launchUrl(activity, uri);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ public class AccountTimelineFragment extends StatusListFragment{
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
if(user==null) // TODO figure out why this happens
|
||||
return;
|
||||
currentRequest=new GetAccountStatuses(user.id, offset>0 ? getMaxID() : null, null, count, filter)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,381 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.APIRequest;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseAccountListFragment.AccountItem>{
|
||||
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
||||
protected String accountID;
|
||||
protected ArrayList<APIRequest<?>> relationshipsRequests=new ArrayList<>();
|
||||
|
||||
public BaseAccountListFragment(){
|
||||
super(40);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
accountID=getArguments().getString("account");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDataLoaded(List<AccountItem> d, boolean more){
|
||||
if(refreshing){
|
||||
relationships.clear();
|
||||
}
|
||||
loadRelationships(d);
|
||||
super.onDataLoaded(d, more);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRefresh(){
|
||||
for(APIRequest<?> req:relationshipsRequests){
|
||||
req.cancel();
|
||||
}
|
||||
relationshipsRequests.clear();
|
||||
super.onRefresh();
|
||||
}
|
||||
|
||||
protected void loadRelationships(List<AccountItem> accounts){
|
||||
Set<String> ids=accounts.stream().map(ai->ai.account.id).collect(Collectors.toSet());
|
||||
GetAccountRelationships req=new GetAccountRelationships(ids);
|
||||
relationshipsRequests.add(req);
|
||||
req.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Relationship> result){
|
||||
relationshipsRequests.remove(req);
|
||||
for(Relationship rel:result){
|
||||
relationships.put(rel.id, rel);
|
||||
}
|
||||
if(list==null)
|
||||
return;
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
if(list.getChildViewHolder(list.getChildAt(i)) instanceof AccountViewHolder avh){
|
||||
avh.bindRelationship();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
relationshipsRequests.remove(req);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
return new AccountsAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
// list.setPadding(0, V.dp(16), 0, V.dp(16));
|
||||
list.setClipToPadding(false);
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 72, 16));
|
||||
updateToolbar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig){
|
||||
super.onConfigurationChanged(newConfig);
|
||||
updateToolbar();
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
protected void updateToolbar(){
|
||||
Toolbar toolbar=getToolbar();
|
||||
if(toolbar!=null && toolbar.getNavigationIcon()!=null){
|
||||
toolbar.setNavigationContentDescription(R.string.back);
|
||||
toolbar.setTitleTextAppearance(getActivity(), R.style.m3_title_medium);
|
||||
toolbar.setSubtitleTextAppearance(getActivity(), R.style.m3_body_medium);
|
||||
int color=UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary);
|
||||
toolbar.setTitleTextColor(color);
|
||||
toolbar.setSubtitleTextColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyWindowInsets(WindowInsets insets){
|
||||
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0){
|
||||
list.setPadding(0, V.dp(16), 0, V.dp(16)+insets.getSystemWindowInsetBottom());
|
||||
insets=insets.inset(0, 0, 0, insets.getSystemWindowInsetBottom());
|
||||
}else{
|
||||
list.setPadding(0, V.dp(16), 0, V.dp(16));
|
||||
}
|
||||
super.onApplyWindowInsets(insets);
|
||||
}
|
||||
|
||||
protected class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
public AccountsAdapter(){
|
||||
super(imgLoader);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AccountViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new AccountViewHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(AccountViewHolder holder, int position){
|
||||
holder.bind(data.get(position));
|
||||
super.onBindViewHolder(holder, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return data.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getImageCountForItem(int position){
|
||||
return data.get(position).emojiHelper.getImageCount()+1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||
AccountItem item=data.get(position);
|
||||
return image==0 ? item.avaRequest : item.emojiHelper.getImageRequest(image-1);
|
||||
}
|
||||
}
|
||||
|
||||
protected class AccountViewHolder extends BindableViewHolder<AccountItem> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable, UsableRecyclerView.LongClickable{
|
||||
private final TextView name, username;
|
||||
private final ImageView avatar;
|
||||
private final Button button;
|
||||
private final PopupMenu contextMenu;
|
||||
private final View menuAnchor;
|
||||
|
||||
public AccountViewHolder(){
|
||||
super(getActivity(), R.layout.item_account_list, list);
|
||||
name=findViewById(R.id.name);
|
||||
username=findViewById(R.id.username);
|
||||
avatar=findViewById(R.id.avatar);
|
||||
button=findViewById(R.id.button);
|
||||
menuAnchor=findViewById(R.id.menu_anchor);
|
||||
|
||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||
avatar.setClipToOutline(true);
|
||||
|
||||
button.setOnClickListener(this::onButtonClick);
|
||||
|
||||
contextMenu=new PopupMenu(getActivity(), menuAnchor);
|
||||
contextMenu.inflate(R.menu.profile);
|
||||
contextMenu.setOnMenuItemClickListener(this::onContextMenuItemSelected);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(AccountItem item){
|
||||
name.setText(item.parsedName);
|
||||
username.setText("@"+item.account.acct);
|
||||
bindRelationship();
|
||||
}
|
||||
|
||||
public void bindRelationship(){
|
||||
Relationship rel=relationships.get(item.account.id);
|
||||
if(rel==null){
|
||||
button.setVisibility(View.GONE);
|
||||
}else{
|
||||
button.setVisibility(View.VISIBLE);
|
||||
UiUtils.setRelationshipToActionButton(rel, button);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(int index, Drawable image){
|
||||
if(index==0){
|
||||
avatar.setImageDrawable(image);
|
||||
}else{
|
||||
item.emojiHelper.setImageDrawable(index-1, image);
|
||||
name.invalidate();
|
||||
}
|
||||
|
||||
if(image instanceof Animatable a && !a.isRunning())
|
||||
a.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearImage(int index){
|
||||
setImage(index, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(item.account));
|
||||
Nav.go(getActivity(), ProfileFragment.class, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(float x, float y){
|
||||
Relationship relationship=relationships.get(item.account.id);
|
||||
if(relationship==null)
|
||||
return false;
|
||||
Menu menu=contextMenu.getMenu();
|
||||
Account account=item.account;
|
||||
|
||||
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getDisplayUsername()));
|
||||
menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername()));
|
||||
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername()));
|
||||
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getDisplayUsername()));
|
||||
if(relationship.following)
|
||||
menu.findItem(R.id.hide_boosts).setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getDisplayUsername()));
|
||||
else
|
||||
menu.findItem(R.id.hide_boosts).setVisible(false);
|
||||
if(!account.isLocal())
|
||||
menu.findItem(R.id.block_domain).setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
|
||||
else
|
||||
menu.findItem(R.id.block_domain).setVisible(false);
|
||||
|
||||
menuAnchor.setTranslationX(x);
|
||||
menuAnchor.setTranslationY(y);
|
||||
contextMenu.show();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onButtonClick(View v){
|
||||
ProgressDialog progress=new ProgressDialog(getActivity());
|
||||
progress.setMessage(getString(R.string.loading));
|
||||
progress.setCancelable(false);
|
||||
UiUtils.performAccountAction(getActivity(), item.account, accountID, relationships.get(item.account.id), button, progressShown->{
|
||||
itemView.setHasTransientState(progressShown);
|
||||
if(progressShown)
|
||||
progress.show();
|
||||
else
|
||||
progress.dismiss();
|
||||
}, result->{
|
||||
relationships.put(item.account.id, result);
|
||||
bindRelationship();
|
||||
});
|
||||
}
|
||||
|
||||
private boolean onContextMenuItemSelected(MenuItem item){
|
||||
Relationship relationship=relationships.get(this.item.account.id);
|
||||
if(relationship==null)
|
||||
return false;
|
||||
Account account=this.item.account;
|
||||
|
||||
int id=item.getItemId();
|
||||
if(id==R.id.share){
|
||||
Intent intent=new Intent(Intent.ACTION_SEND);
|
||||
intent.setType("text/plain");
|
||||
intent.putExtra(Intent.EXTRA_TEXT, account.url);
|
||||
startActivity(Intent.createChooser(intent, item.getTitle()));
|
||||
}else if(id==R.id.mute){
|
||||
UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship);
|
||||
}else if(id==R.id.block){
|
||||
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
|
||||
}else if(id==R.id.report){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("reportAccount", Parcels.wrap(account));
|
||||
Nav.go(getActivity(), ReportReasonChoiceFragment.class, args);
|
||||
}else if(id==R.id.open_in_browser){
|
||||
UiUtils.launchWebBrowser(getActivity(), account.url);
|
||||
}else if(id==R.id.block_domain){
|
||||
UiUtils.confirmToggleBlockDomain(getActivity(), accountID, account.getDomain(), relationship.domainBlocking, ()->{
|
||||
relationship.domainBlocking=!relationship.domainBlocking;
|
||||
bindRelationship();
|
||||
});
|
||||
}else if(id==R.id.hide_boosts){
|
||||
new SetAccountFollowed(account.id, true, !relationship.showingReblogs)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
relationships.put(AccountViewHolder.this.item.account.id, result);
|
||||
bindRelationship();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(getActivity());
|
||||
}
|
||||
})
|
||||
.wrapProgress(getActivity(), R.string.loading, false)
|
||||
.exec(accountID);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateRelationship(Relationship r){
|
||||
relationships.put(item.account.id, r);
|
||||
bindRelationship();
|
||||
}
|
||||
}
|
||||
|
||||
protected static class AccountItem{
|
||||
public final Account account;
|
||||
public final ImageLoaderRequest avaRequest;
|
||||
public final CustomEmojiHelper emojiHelper;
|
||||
public final CharSequence parsedName;
|
||||
|
||||
public AccountItem(Account account){
|
||||
this.account=account;
|
||||
avaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(50), V.dp(50));
|
||||
emojiHelper=new CustomEmojiHelper();
|
||||
emojiHelper.setText(parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||
import org.joinmastodon.android.ui.TileGridLayoutManager;
|
||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.PollFooterStatusDisplayItem;
|
||||
@@ -243,6 +244,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
}
|
||||
|
||||
private ImageStatusDisplayItem.Holder<?> findPhotoViewHolder(int index){
|
||||
if(list==null)
|
||||
return null;
|
||||
int offset=0;
|
||||
for(StatusDisplayItem item:displayItems){
|
||||
if(item.parentID.equals(parentID)){
|
||||
@@ -279,6 +282,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
list.getDecoratedBoundsWithMargins(view, outRect);
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(view);
|
||||
if(holder instanceof StatusDisplayItem.Holder){
|
||||
if(((StatusDisplayItem.Holder<?>) holder).getItem().getType()==StatusDisplayItem.Type.GAP){
|
||||
outRect.setEmpty();
|
||||
return;
|
||||
}
|
||||
String id=((StatusDisplayItem.Holder<?>) holder).getItemID();
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
View child=list.getChildAt(i);
|
||||
@@ -472,6 +479,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
}
|
||||
}
|
||||
|
||||
public void onGapClick(GapStatusDisplayItem.Holder item){}
|
||||
|
||||
public String getAccountID(){
|
||||
return accountID;
|
||||
}
|
||||
@@ -651,8 +660,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
View bottomSibling=parent.getChildAt(i+1);
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
||||
RecyclerView.ViewHolder siblingHolder=parent.getChildViewHolder(bottomSibling);
|
||||
if(holder instanceof StatusDisplayItem.Holder && siblingHolder instanceof StatusDisplayItem.Holder
|
||||
&& !((StatusDisplayItem.Holder<?>) holder).getItemID().equals(((StatusDisplayItem.Holder<?>) siblingHolder).getItemID())){
|
||||
if(holder instanceof StatusDisplayItem.Holder<?> ih && siblingHolder instanceof StatusDisplayItem.Holder<?> sh
|
||||
&& !ih.getItemID().equals(sh.getItemID()) && ih.getItem().getType()!=StatusDisplayItem.Type.GAP){
|
||||
drawDivider(child, bottomSibling, holder, siblingHolder, parent, c, dividerPaint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
private ImageView sendError;
|
||||
private View sendingOverlay;
|
||||
private WindowManager wm;
|
||||
private StatusPrivacy statusVisibility=StatusPrivacy.PUBLIC;
|
||||
private StatusPrivacy statusVisibility=StatusPrivacy.UNLISTED;
|
||||
private ComposeAutocompleteSpan currentAutocompleteSpan;
|
||||
private FrameLayout mainEditTextWrap;
|
||||
private ComposeAutocompleteViewController autocompleteViewController;
|
||||
@@ -382,6 +382,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count){
|
||||
if(s.length()==0)
|
||||
return;
|
||||
// offset one char back to catch an already typed '@' or '#' or ':'
|
||||
int realStart=start;
|
||||
start=Math.max(0, start-1);
|
||||
@@ -447,7 +449,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
if(!mentions.contains(m))
|
||||
mentions.add(m);
|
||||
}
|
||||
initialText=TextUtils.join(" ", mentions)+" ";
|
||||
initialText=mentions.isEmpty() ? "" : TextUtils.join(" ", mentions)+" ";
|
||||
if(savedInstanceState==null){
|
||||
mainEditText.setText(initialText);
|
||||
mainEditText.setSelection(mainEditText.length());
|
||||
@@ -714,25 +716,27 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
return false;
|
||||
}
|
||||
String type=getActivity().getContentResolver().getType(uri);
|
||||
if(instance.configuration!=null && instance.configuration.mediaAttachments!=null){
|
||||
if(instance!=null && instance.configuration!=null && instance.configuration.mediaAttachments!=null){
|
||||
if(instance.configuration.mediaAttachments.supportedMimeTypes!=null && !instance.configuration.mediaAttachments.supportedMimeTypes.contains(type)){
|
||||
showMediaAttachmentError(getString(R.string.media_attachment_unsupported_type, UiUtils.getFileName(uri)));
|
||||
return false;
|
||||
}
|
||||
int sizeLimit=type.startsWith("image/") ? instance.configuration.mediaAttachments.imageSizeLimit : instance.configuration.mediaAttachments.videoSizeLimit;
|
||||
int size;
|
||||
try(Cursor cursor=MastodonApp.context.getContentResolver().query(uri, new String[]{OpenableColumns.SIZE}, null, null, null)){
|
||||
cursor.moveToFirst();
|
||||
size=cursor.getInt(0);
|
||||
}catch(Exception x){
|
||||
Log.w("ComposeFragment", x);
|
||||
return false;
|
||||
}
|
||||
if(size>sizeLimit){
|
||||
float mb=sizeLimit/(float)(1024*1024);
|
||||
String sMb=String.format(Locale.getDefault(), mb%1f==0f ? "%f" : "%.2f", mb);
|
||||
showMediaAttachmentError(getString(R.string.media_attachment_too_big, UiUtils.getFileName(uri), sMb));
|
||||
return false;
|
||||
if(!type.startsWith("image/")){
|
||||
int sizeLimit=instance.configuration.mediaAttachments.videoSizeLimit;
|
||||
int size;
|
||||
try(Cursor cursor=MastodonApp.context.getContentResolver().query(uri, new String[]{OpenableColumns.SIZE}, null, null, null)){
|
||||
cursor.moveToFirst();
|
||||
size=cursor.getInt(0);
|
||||
}catch(Exception x){
|
||||
Log.w("ComposeFragment", x);
|
||||
return false;
|
||||
}
|
||||
if(size>sizeLimit){
|
||||
float mb=sizeLimit/(float) (1024*1024);
|
||||
String sMb=String.format(Locale.getDefault(), mb%1f==0f ? "%f" : "%.2f", mb);
|
||||
showMediaAttachmentError(getString(R.string.media_attachment_too_big, UiUtils.getFileName(uri), sMb));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
pollBtn.setEnabled(false);
|
||||
@@ -1025,7 +1029,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
UiUtils.enablePopupMenuIcons(getActivity(), menu);
|
||||
m.setGroupCheckable(0, true, true);
|
||||
m.findItem(switch(statusVisibility){
|
||||
case PUBLIC, UNLISTED -> R.id.vis_public;
|
||||
case PUBLIC -> R.id.vis_public;
|
||||
case UNLISTED -> R.id.vis_unlisted;
|
||||
case PRIVATE -> R.id.vis_followers;
|
||||
case DIRECT -> R.id.vis_private;
|
||||
}).setChecked(true);
|
||||
@@ -1035,6 +1040,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
int id=item.getItemId();
|
||||
if(id==R.id.vis_public){
|
||||
statusVisibility=StatusPrivacy.PUBLIC;
|
||||
}else if(id==R.id.vis_unlisted){
|
||||
statusVisibility=StatusPrivacy.UNLISTED;
|
||||
}else if(id==R.id.vis_followers){
|
||||
statusVisibility=StatusPrivacy.PRIVATE;
|
||||
}else if(id==R.id.vis_private){
|
||||
@@ -1049,6 +1056,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}
|
||||
|
||||
private void updateVisibilityIcon(){
|
||||
if(statusVisibility==null){ // TODO find out why this happens
|
||||
statusVisibility=StatusPrivacy.PUBLIC;
|
||||
}
|
||||
visibilityBtn.setImageResource(switch(statusVisibility){
|
||||
case PUBLIC -> R.drawable.ic_fluent_earth_24_filled;
|
||||
case UNLISTED -> R.drawable.ic_fluent_people_community_24_regular;
|
||||
@@ -1059,7 +1069,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
|
||||
@Override
|
||||
public void onSelectionChanged(int start, int end){
|
||||
if(start==end){
|
||||
if(start==end && mainEditText.length()>0){
|
||||
ComposeAutocompleteSpan[] spans=mainEditText.getText().getSpans(start, end, ComposeAutocompleteSpan.class);
|
||||
if(spans.length>0){
|
||||
assert spans.length==1;
|
||||
@@ -1093,7 +1103,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
|
||||
@Override
|
||||
public String[] onGetAllowedMediaMimeTypes(){
|
||||
if(instance.configuration!=null && instance.configuration.mediaAttachments!=null && instance.configuration.mediaAttachments.supportedMimeTypes!=null)
|
||||
if(instance!=null && instance.configuration!=null && instance.configuration.mediaAttachments!=null && instance.configuration.mediaAttachments.supportedMimeTypes!=null)
|
||||
return instance.configuration.mediaAttachments.supportedMimeTypes.toArray(new String[0]);
|
||||
return new String[]{"image/jpeg", "image/gif", "image/png", "video/mp4"};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountFollowers;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class FollowerListFragment extends BaseAccountListFragment{
|
||||
private Account account;
|
||||
private String nextMaxID;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
account=Parcels.unwrap(getArguments().getParcelable("targetAccount"));
|
||||
setTitle("@"+account.acct);
|
||||
setSubtitle(getResources().getQuantityString(R.plurals.x_followers, account.followersCount, account.followersCount));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume(){
|
||||
super.onResume();
|
||||
if(!loaded && !dataLoading)
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetAccountFollowers(account.id, offset==0 ? null : nextMaxID, count)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(HeaderPaginationList<Account> result){
|
||||
if(result.nextPageUri!=null)
|
||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||
else
|
||||
nextMaxID=null;
|
||||
onDataLoaded(result.stream().map(AccountItem::new).collect(Collectors.toList()), nextMaxID!=null);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountFollowing;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class FollowingListFragment extends BaseAccountListFragment{
|
||||
private Account account;
|
||||
private String nextMaxID;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
account=Parcels.unwrap(getArguments().getParcelable("targetAccount"));
|
||||
setTitle("@"+account.acct);
|
||||
setSubtitle(getResources().getQuantityString(R.plurals.x_following, account.followingCount, account.followingCount));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume(){
|
||||
super.onResume();
|
||||
if(!loaded && !dataLoading)
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetAccountFollowing(account.id, offset==0 ? null : nextMaxID, count)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(HeaderPaginationList<Account> result){
|
||||
if(result.nextPageUri!=null)
|
||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||
else
|
||||
nextMaxID=null;
|
||||
onDataLoaded(result.stream().map(AccountItem::new).collect(Collectors.toList()), nextMaxID!=null);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import android.app.NotificationManager;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
@@ -17,6 +18,7 @@ import android.view.WindowInsets;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
@@ -28,6 +30,7 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
|
||||
import org.joinmastodon.android.fragments.discover.SearchFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.TabBar;
|
||||
@@ -46,6 +49,7 @@ import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.BottomSheet;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
|
||||
public class HomeFragment extends AppKitFragment implements OnBackPressedListener{
|
||||
@@ -238,21 +242,11 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
|
||||
private boolean onTabLongClick(@IdRes int tab){
|
||||
if(tab==R.id.tab_profile){
|
||||
ArrayList<String> options=new ArrayList<>();
|
||||
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
|
||||
options.add(session.self.displayName+"\n("+session.self.username+"@"+session.domain+")");
|
||||
}
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setItems(options.toArray(new String[0]), (dialog, which)->{
|
||||
AccountSession session=AccountSessionManager.getInstance().getLoggedInAccounts().get(which);
|
||||
AccountSessionManager.getInstance().setLastActiveAccountID(session.getID());
|
||||
getActivity().finish();
|
||||
getActivity().startActivity(new Intent(getActivity(), MainActivity.class));
|
||||
})
|
||||
.setNegativeButton(R.string.add_account, (dialog, which)->{
|
||||
Nav.go(getActivity(), SplashFragment.class, null);
|
||||
})
|
||||
.show();
|
||||
ArrayList<String> options=new ArrayList<>();
|
||||
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
|
||||
options.add(session.self.displayName+"\n("+session.self.username+"@"+session.domain+")");
|
||||
}
|
||||
new AccountSwitcherSheet(getActivity()).show();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.Activity;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.Gravity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toolbar;
|
||||
@@ -16,20 +24,38 @@ import android.widget.Toolbar;
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.model.PaginatedResponse;
|
||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class HomeTimelineFragment extends StatusListFragment{
|
||||
private ImageButton fab;
|
||||
private ImageView toolbarLogo;
|
||||
private Button toolbarShowNewPostsBtn;
|
||||
private boolean newPostsBtnShown;
|
||||
private AnimatorSet currentNewPostsAnim;
|
||||
|
||||
private String maxID;
|
||||
|
||||
@@ -50,11 +76,13 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
.getAccount(accountID).getCacheController()
|
||||
.getHomeTimeline(offset>0 ? maxID : null, count, refreshing, new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(PaginatedResponse<List<Status>> result){
|
||||
public void onSuccess(CacheablePaginatedResponse<List<Status>> result){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
onDataLoaded(result.items, !result.items.isEmpty());
|
||||
maxID=result.maxID;
|
||||
if(result.isFromCache())
|
||||
loadNewPosts();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -65,6 +93,14 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
fab=view.findViewById(R.id.fab);
|
||||
fab.setOnClickListener(this::onFabClick);
|
||||
updateToolbarLogo();
|
||||
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
|
||||
if(newPostsBtnShown && list.getChildAdapterPosition(list.getChildAt(0))<=getMainAdapterOffset()){
|
||||
hideNewPostsButton();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,8 +125,13 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
@Override
|
||||
protected void onShown(){
|
||||
super.onShown();
|
||||
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
||||
loadData();
|
||||
if(!getArguments().getBoolean("noAutoLoad")){
|
||||
if(!loaded && !dataLoading){
|
||||
loadData();
|
||||
}else if(!dataLoading){
|
||||
loadNewPosts();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
@@ -104,12 +145,256 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
Nav.go(getActivity(), ComposeFragment.class, args);
|
||||
}
|
||||
|
||||
private void loadNewPosts(){
|
||||
dataLoading=true;
|
||||
// The idea here is that we request the timeline such that if there are fewer than `limit` posts,
|
||||
// we'll get the currently topmost post as last in the response. This way we know there's no gap
|
||||
// between the existing and newly loaded parts of the timeline.
|
||||
String sinceID=data.size()>1 ? data.get(1).id : "1";
|
||||
currentRequest=new GetHomeTimeline(null, null, 20, sinceID)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
currentRequest=null;
|
||||
dataLoading=false;
|
||||
if(result.isEmpty() || getActivity()==null)
|
||||
return;
|
||||
Status last=result.get(result.size()-1);
|
||||
List<Status> toAdd;
|
||||
if(last.id.equals(data.get(0).id)){ // This part intersects with the existing one
|
||||
toAdd=result.subList(0, result.size()-1); // Remove the already known last post
|
||||
}else{
|
||||
result.get(result.size()-1).hasGapAfter=true;
|
||||
toAdd=result;
|
||||
}
|
||||
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.HOME)).collect(Collectors.toList());
|
||||
if(!filters.isEmpty()){
|
||||
toAdd=toAdd.stream().filter(new StatusFilterPredicate(filters)).collect(Collectors.toList());
|
||||
}
|
||||
if(!toAdd.isEmpty()){
|
||||
prependItems(toAdd, true);
|
||||
showNewPostsButton();
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(toAdd, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
currentRequest=null;
|
||||
dataLoading=false;
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGapClick(GapStatusDisplayItem.Holder item){
|
||||
if(dataLoading)
|
||||
return;
|
||||
item.getItem().loading=true;
|
||||
V.setVisibilityAnimated(item.progress, View.VISIBLE);
|
||||
V.setVisibilityAnimated(item.text, View.GONE);
|
||||
GapStatusDisplayItem gap=item.getItem();
|
||||
dataLoading=true;
|
||||
currentRequest=new GetHomeTimeline(item.getItemID(), null, 20, null)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
currentRequest=null;
|
||||
dataLoading=false;
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
int gapPos=displayItems.indexOf(gap);
|
||||
if(gapPos==-1)
|
||||
return;
|
||||
if(result.isEmpty()){
|
||||
displayItems.remove(gapPos);
|
||||
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
|
||||
Status gapStatus=getStatusByID(gap.parentID);
|
||||
if(gapStatus!=null){
|
||||
gapStatus.hasGapAfter=false;
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
|
||||
}
|
||||
}else{
|
||||
Set<String> idsBelowGap=new HashSet<>();
|
||||
boolean belowGap=false;
|
||||
int gapPostIndex=0;
|
||||
for(Status s:data){
|
||||
if(belowGap){
|
||||
idsBelowGap.add(s.id);
|
||||
}else if(s.id.equals(gap.parentID)){
|
||||
belowGap=true;
|
||||
s.hasGapAfter=false;
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false);
|
||||
}else{
|
||||
gapPostIndex++;
|
||||
}
|
||||
}
|
||||
int endIndex=0;
|
||||
for(Status s:result){
|
||||
endIndex++;
|
||||
if(idsBelowGap.contains(s.id))
|
||||
break;
|
||||
}
|
||||
if(endIndex==result.size()){
|
||||
result.get(result.size()-1).hasGapAfter=true;
|
||||
}else{
|
||||
result=result.subList(0, endIndex);
|
||||
}
|
||||
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
||||
targetList.clear();
|
||||
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
||||
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.HOME)).collect(Collectors.toList());
|
||||
outer:
|
||||
for(Status s:result){
|
||||
if(idsBelowGap.contains(s.id))
|
||||
break;
|
||||
for(Filter filter:filters){
|
||||
if(filter.matches(s.getContentStatus().content)){
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
targetList.addAll(buildDisplayItems(s));
|
||||
insertedPosts.add(s);
|
||||
}
|
||||
if(targetList.isEmpty()){
|
||||
// oops. We didn't add new posts, but at least we know there are none.
|
||||
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
|
||||
}else{
|
||||
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
|
||||
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
|
||||
}
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
currentRequest=null;
|
||||
dataLoading=false;
|
||||
gap.loading=false;
|
||||
Activity a=getActivity();
|
||||
if(a!=null){
|
||||
error.showToast(a);
|
||||
int gapPos=displayItems.indexOf(gap);
|
||||
if(gapPos>=0)
|
||||
adapter.notifyItemChanged(gapPos);
|
||||
}
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRefresh(){
|
||||
if(currentRequest!=null){
|
||||
currentRequest.cancel();
|
||||
currentRequest=null;
|
||||
dataLoading=false;
|
||||
}
|
||||
super.onRefresh();
|
||||
}
|
||||
|
||||
private void updateToolbarLogo(){
|
||||
ImageView logo=new ImageView(getActivity());
|
||||
logo.setScaleType(ImageView.ScaleType.CENTER);
|
||||
logo.setImageResource(R.drawable.logo);
|
||||
logo.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary)));
|
||||
toolbarLogo=new ImageView(getActivity());
|
||||
toolbarLogo.setScaleType(ImageView.ScaleType.CENTER);
|
||||
toolbarLogo.setImageResource(R.drawable.logo);
|
||||
toolbarLogo.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary)));
|
||||
|
||||
toolbarShowNewPostsBtn=new Button(getActivity());
|
||||
toolbarShowNewPostsBtn.setTextAppearance(R.style.m3_title_medium);
|
||||
toolbarShowNewPostsBtn.setTextColor(0xffffffff);
|
||||
toolbarShowNewPostsBtn.setStateListAnimator(null);
|
||||
toolbarShowNewPostsBtn.setBackgroundResource(R.drawable.bg_button_new_posts);
|
||||
toolbarShowNewPostsBtn.setText(R.string.see_new_posts);
|
||||
toolbarShowNewPostsBtn.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_fluent_arrow_up_16_filled, 0, 0, 0);
|
||||
toolbarShowNewPostsBtn.setCompoundDrawableTintList(toolbarShowNewPostsBtn.getTextColors());
|
||||
toolbarShowNewPostsBtn.setCompoundDrawablePadding(V.dp(8));
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N)
|
||||
UiUtils.fixCompoundDrawableTintOnAndroid6(toolbarShowNewPostsBtn);
|
||||
toolbarShowNewPostsBtn.setOnClickListener(this::onNewPostsBtnClick);
|
||||
|
||||
if(newPostsBtnShown){
|
||||
toolbarShowNewPostsBtn.setVisibility(View.VISIBLE);
|
||||
toolbarLogo.setVisibility(View.INVISIBLE);
|
||||
toolbarLogo.setAlpha(0f);
|
||||
}else{
|
||||
toolbarShowNewPostsBtn.setVisibility(View.INVISIBLE);
|
||||
toolbarShowNewPostsBtn.setAlpha(0f);
|
||||
toolbarShowNewPostsBtn.setScaleX(.8f);
|
||||
toolbarShowNewPostsBtn.setScaleY(.8f);
|
||||
toolbarLogo.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
FrameLayout logoWrap=new FrameLayout(getActivity());
|
||||
logoWrap.addView(toolbarLogo, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
|
||||
logoWrap.addView(toolbarShowNewPostsBtn, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, V.dp(32), Gravity.CENTER));
|
||||
|
||||
Toolbar toolbar=getToolbar();
|
||||
toolbar.addView(logo, new Toolbar.LayoutParams(Gravity.CENTER));
|
||||
toolbar.addView(logoWrap, new Toolbar.LayoutParams(Gravity.CENTER));
|
||||
}
|
||||
|
||||
private void showNewPostsButton(){
|
||||
if(newPostsBtnShown)
|
||||
return;
|
||||
newPostsBtnShown=true;
|
||||
if(currentNewPostsAnim!=null){
|
||||
currentNewPostsAnim.cancel();
|
||||
}
|
||||
toolbarShowNewPostsBtn.setVisibility(View.VISIBLE);
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(
|
||||
ObjectAnimator.ofFloat(toolbarLogo, View.ALPHA, 0f),
|
||||
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.ALPHA, 1f),
|
||||
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_X, 1f),
|
||||
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_Y, 1f)
|
||||
);
|
||||
set.setDuration(300);
|
||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
toolbarLogo.setVisibility(View.INVISIBLE);
|
||||
currentNewPostsAnim=null;
|
||||
}
|
||||
});
|
||||
currentNewPostsAnim=set;
|
||||
set.start();
|
||||
}
|
||||
|
||||
private void hideNewPostsButton(){
|
||||
if(!newPostsBtnShown)
|
||||
return;
|
||||
newPostsBtnShown=false;
|
||||
if(currentNewPostsAnim!=null){
|
||||
currentNewPostsAnim.cancel();
|
||||
}
|
||||
toolbarLogo.setVisibility(View.VISIBLE);
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(
|
||||
ObjectAnimator.ofFloat(toolbarLogo, View.ALPHA, 1f),
|
||||
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.ALPHA, 0f),
|
||||
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_X, .8f),
|
||||
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_Y, .8f)
|
||||
);
|
||||
set.setDuration(300);
|
||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
toolbarShowNewPostsBtn.setVisibility(View.INVISIBLE);
|
||||
currentNewPostsAnim=null;
|
||||
}
|
||||
});
|
||||
currentNewPostsAnim=set;
|
||||
set.start();
|
||||
}
|
||||
|
||||
private void onNewPostsBtnClick(View v){
|
||||
if(newPostsBtnShown){
|
||||
hideNewPostsButton();
|
||||
scrollToTop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,6 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
rect.set(child.getX(), i==0 && pos>0 && displayItems.get(pos-1).inset ? V.dp(-10) : child.getY(), child.getX()+child.getWidth(), child.getY()+child.getHeight());
|
||||
}else{
|
||||
rect.bottom=Math.max(rect.bottom, child.getY()+child.getHeight());
|
||||
rect.right=Math.max(rect.right, child.getX()+child.getHeight());
|
||||
}
|
||||
}else if(!rect.isEmpty()){
|
||||
drawInsetBackground(c);
|
||||
@@ -198,6 +197,8 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
private void drawInsetBackground(Canvas c){
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setColor(bgColor);
|
||||
rect.left=V.dp(12);
|
||||
rect.right=list.getWidth()-V.dp(12);
|
||||
rect.inset(V.dp(4), V.dp(4));
|
||||
c.drawRoundRect(rect, V.dp(4), V.dp(4), paint);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
|
||||
@@ -14,6 +14,8 @@ import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ImageSpan;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -104,6 +106,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
private ProgressBar actionProgress;
|
||||
private FrameLayout[] tabViews;
|
||||
private TabLayoutMediator tabLayoutMediator;
|
||||
private TextView followsYouView;
|
||||
|
||||
private Account account;
|
||||
private String accountID;
|
||||
@@ -178,6 +181,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
bioEdit=content.findViewById(R.id.bio_edit);
|
||||
actionProgress=content.findViewById(R.id.action_progress);
|
||||
fab=content.findViewById(R.id.fab);
|
||||
followsYouView=content.findViewById(R.id.follows_you);
|
||||
|
||||
avatar.setOutlineProvider(new ViewOutlineProvider(){
|
||||
@Override
|
||||
@@ -257,6 +261,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
fab.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
followersBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
||||
followingBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
||||
|
||||
return sizeWrapper;
|
||||
}
|
||||
|
||||
@@ -400,8 +407,25 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
HtmlParser.parseCustomEmoji(ssb, account.emojis);
|
||||
name.setText(ssb);
|
||||
setTitle(ssb);
|
||||
username.setText('@'+account.acct);
|
||||
bio.setText(HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID));
|
||||
if(account.locked){
|
||||
ssb=new SpannableStringBuilder("@");
|
||||
ssb.append(account.acct);
|
||||
ssb.append(" ");
|
||||
Drawable lock=username.getResources().getDrawable(R.drawable.ic_fluent_lock_closed_20_filled, getActivity().getTheme()).mutate();
|
||||
lock.setBounds(0, 0, lock.getIntrinsicWidth(), lock.getIntrinsicHeight());
|
||||
lock.setTint(username.getCurrentTextColor());
|
||||
ssb.append(getString(R.string.manually_approves_followers), new ImageSpan(lock, ImageSpan.ALIGN_BOTTOM), 0);
|
||||
username.setText(ssb);
|
||||
}else{
|
||||
username.setText('@'+account.acct);
|
||||
}
|
||||
CharSequence parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||
if(TextUtils.isEmpty(parsedBio)){
|
||||
bio.setVisibility(View.GONE);
|
||||
}else{
|
||||
bio.setVisibility(View.VISIBLE);
|
||||
bio.setText(parsedBio);
|
||||
}
|
||||
followersCount.setText(UiUtils.abbreviateNumber(account.followersCount));
|
||||
followingCount.setText(UiUtils.abbreviateNumber(account.followingCount));
|
||||
postsCount.setText(UiUtils.abbreviateNumber(account.statusesCount));
|
||||
@@ -565,6 +589,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
invalidateOptionsMenu();
|
||||
actionButton.setVisibility(View.VISIBLE);
|
||||
UiUtils.setRelationshipToActionButton(relationship, actionButton);
|
||||
actionProgress.setIndeterminateTintList(actionButton.getTextColors());
|
||||
followsYouView.setVisibility(relationship.followedBy ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
|
||||
@@ -825,6 +851,20 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
scrollView.smoothScrollTo(0, 0);
|
||||
}
|
||||
|
||||
private void onFollowersOrFollowingClick(View v){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("targetAccount", Parcels.wrap(account));
|
||||
Class<? extends Fragment> cls;
|
||||
if(v.getId()==R.id.followers_btn)
|
||||
cls=FollowerListFragment.class;
|
||||
else if(v.getId()==R.id.following_btn)
|
||||
cls=FollowingListFragment.class;
|
||||
else
|
||||
return;
|
||||
Nav.go(getActivity(), cls, args);
|
||||
}
|
||||
|
||||
private class ProfilePagerAdapter extends RecyclerView.Adapter<SimpleViewHolder>{
|
||||
@NonNull
|
||||
@Override
|
||||
|
||||
@@ -56,6 +56,8 @@ public class ThreadFragment extends StatusListFragment{
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(StatusContext result){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
if(refreshing){
|
||||
data.clear();
|
||||
displayItems.clear();
|
||||
@@ -64,7 +66,8 @@ public class ThreadFragment extends StatusListFragment{
|
||||
}
|
||||
result.descendants=filterStatuses(result.descendants);
|
||||
result.ancestors=filterStatuses(result.ancestors);
|
||||
footerProgress.setVisibility(View.GONE);
|
||||
if(footerProgress!=null)
|
||||
footerProgress.setVisibility(View.GONE);
|
||||
data.addAll(result.descendants);
|
||||
int prevCount=displayItems.size();
|
||||
onAppendItems(result.descendants);
|
||||
|
||||
@@ -50,6 +50,8 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
private DiscoverNewsFragment newsFragment;
|
||||
private DiscoverAccountsFragment accountsFragment;
|
||||
private SearchFragment searchFragment;
|
||||
private LocalTimelineFragment localTimelineFragment;
|
||||
private FederatedTimelineFragment federatedTimelineFragment;
|
||||
|
||||
private String accountID;
|
||||
private Runnable searchDebouncer=this::onSearchChangedDebounced;
|
||||
@@ -71,14 +73,16 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
tabLayout=view.findViewById(R.id.tabbar);
|
||||
pager=view.findViewById(R.id.pager);
|
||||
|
||||
tabViews=new FrameLayout[4];
|
||||
tabViews=new FrameLayout[6];
|
||||
for(int i=0;i<tabViews.length;i++){
|
||||
FrameLayout tabView=new FrameLayout(getActivity());
|
||||
tabView.setId(switch(i){
|
||||
case 0 -> R.id.discover_posts;
|
||||
case 1 -> R.id.discover_hashtags;
|
||||
case 2 -> R.id.discover_news;
|
||||
case 3 -> R.id.discover_users;
|
||||
case 0 -> R.id.discover_local_timeline;
|
||||
case 1 -> R.id.discover_federated_timeline;
|
||||
case 2 -> R.id.discover_hashtags;
|
||||
case 3 -> R.id.discover_posts;
|
||||
case 4 -> R.id.discover_news;
|
||||
case 5 -> R.id.discover_users;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+i);
|
||||
});
|
||||
tabView.setVisibility(View.GONE);
|
||||
@@ -104,7 +108,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
}
|
||||
});
|
||||
|
||||
if(postsFragment==null){
|
||||
if(localTimelineFragment==null){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putBoolean("__is_tab", true);
|
||||
@@ -121,8 +125,16 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
accountsFragment=new DiscoverAccountsFragment();
|
||||
accountsFragment.setArguments(args);
|
||||
|
||||
localTimelineFragment=new LocalTimelineFragment();
|
||||
localTimelineFragment.setArguments(args);
|
||||
|
||||
federatedTimelineFragment=new FederatedTimelineFragment();
|
||||
federatedTimelineFragment.setArguments(args);
|
||||
|
||||
getChildFragmentManager().beginTransaction()
|
||||
.add(R.id.discover_posts, postsFragment)
|
||||
.add(R.id.discover_local_timeline, localTimelineFragment)
|
||||
.add(R.id.discover_federated_timeline, federatedTimelineFragment)
|
||||
.add(R.id.discover_hashtags, hashtagsFragment)
|
||||
.add(R.id.discover_news, newsFragment)
|
||||
.add(R.id.discover_users, accountsFragment)
|
||||
@@ -133,10 +145,12 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
@Override
|
||||
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
|
||||
tab.setText(switch(position){
|
||||
case 0 -> R.string.posts;
|
||||
case 1 -> R.string.hashtags;
|
||||
case 2 -> R.string.news;
|
||||
case 3 -> R.string.for_you;
|
||||
case 0 -> R.string.local_timeline;
|
||||
case 1 -> R.string.federated_timeline;
|
||||
case 2 -> R.string.hashtags;
|
||||
case 3 -> R.string.posts;
|
||||
case 4 -> R.string.news;
|
||||
case 5 -> R.string.for_you;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||
});
|
||||
tab.view.textView.setAllCaps(true);
|
||||
@@ -210,8 +224,8 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
}
|
||||
|
||||
public void loadData(){
|
||||
if(postsFragment!=null && !postsFragment.loaded && !postsFragment.dataLoading)
|
||||
postsFragment.loadData();
|
||||
if(localTimelineFragment!=null && !localTimelineFragment.loaded && !localTimelineFragment.dataLoading)
|
||||
localTimelineFragment.loadData();
|
||||
}
|
||||
|
||||
private void onSearchEditFocusChanged(View v, boolean hasFocus){
|
||||
@@ -247,10 +261,12 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
|
||||
private Fragment getFragmentForPage(int page){
|
||||
return switch(page){
|
||||
case 0 -> postsFragment;
|
||||
case 1 -> hashtagsFragment;
|
||||
case 2 -> newsFragment;
|
||||
case 3 -> accountsFragment;
|
||||
case 0 -> localTimelineFragment;
|
||||
case 1 -> federatedTimelineFragment;
|
||||
case 2 -> hashtagsFragment;
|
||||
case 3 -> postsFragment;
|
||||
case 4 -> newsFragment;
|
||||
case 5 -> accountsFragment;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||
};
|
||||
}
|
||||
@@ -299,7 +315,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return 4;
|
||||
return tabViews.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.joinmastodon.android.model.Card;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
|
||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -36,6 +37,7 @@ import me.grishka.appkit.views.UsableRecyclerView;
|
||||
public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements ScrollableToTop{
|
||||
private String accountID;
|
||||
private List<ImageLoaderRequest> imageRequests=Collections.emptyList();
|
||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS);
|
||||
|
||||
public DiscoverNewsFragment(){
|
||||
super(10);
|
||||
@@ -71,6 +73,7 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 0, 0));
|
||||
bannerHelper.maybeAddBanner(contentWrap);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses;
|
||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class DiscoverPostsFragment extends StatusListFragment{
|
||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_POSTS);
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetTrendingStatuses(count)
|
||||
@@ -19,4 +25,10 @@ public class DiscoverPostsFragment extends StatusListFragment{
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
bannerHelper.maybeAddBanner(contentWrap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class FederatedTimelineFragment extends StatusListFragment{
|
||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.FEDERATED_TIMELINE);
|
||||
private String maxID;
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetPublicTimeline(false, false, refreshing ? null : maxID, count)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
if(!result.isEmpty())
|
||||
maxID=result.get(result.size()-1).id;
|
||||
onDataLoaded(result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList()), !result.isEmpty());
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
bannerHelper.maybeAddBanner(contentWrap);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class LocalTimelineFragment extends StatusListFragment{
|
||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.LOCAL_TIMELINE);
|
||||
private String maxID;
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetPublicTimeline(true, false, refreshing ? null : maxID, count)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
if(!result.isEmpty())
|
||||
maxID=result.get(result.size()-1).id;
|
||||
onDataLoaded(result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList()), !result.isEmpty());
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
bannerHelper.maybeAddBanner(contentWrap);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import org.joinmastodon.android.api.requests.trends.GetTrendingHashtags;
|
||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.HashtagChartView;
|
||||
|
||||
@@ -24,6 +25,7 @@ import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop{
|
||||
private String accountID;
|
||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_HASHTAGS);
|
||||
|
||||
public TrendingHashtagsFragment(){
|
||||
super(10);
|
||||
@@ -56,6 +58,7 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, .5f, 16, 16));
|
||||
bannerHelper.maybeAddBanner(contentWrap);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
@@ -13,6 +14,7 @@ import android.view.WindowInsets;
|
||||
import android.widget.Button;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
||||
@@ -110,7 +112,11 @@ public class AccountActivationFragment extends AppKitFragment{
|
||||
}
|
||||
|
||||
private void onButtonClick(){
|
||||
startActivity(Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_EMAIL).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
try{
|
||||
startActivity(Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_EMAIL).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
}catch(ActivityNotFoundException x){
|
||||
Toast.makeText(getActivity(), R.string.no_app_to_handle_action, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void onBackButtonClick(){
|
||||
@@ -131,6 +137,13 @@ public class AccountActivationFragment extends AppKitFragment{
|
||||
}
|
||||
|
||||
private void tryGetAccount(){
|
||||
if(AccountSessionManager.getInstance().tryGetAccount(accountID)==null){
|
||||
uiHandler.removeCallbacks(pollRunnable);
|
||||
getActivity().finish();
|
||||
Intent intent=new Intent(getActivity(), MainActivity.class);
|
||||
startActivity(intent);
|
||||
return;
|
||||
}
|
||||
currentRequest=new GetOwnAccount()
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
|
||||
@@ -105,6 +105,8 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstanc
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<CatalogInstance> result){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
Map<String, List<CatalogInstance>> byLang=result.stream().collect(Collectors.groupingBy(ci->ci.language));
|
||||
for(List<CatalogInstance> group:byLang.values()){
|
||||
Collections.sort(group, (a, b)->{
|
||||
|
||||
@@ -15,6 +15,7 @@ import android.widget.TextView;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||
import org.joinmastodon.android.events.FinishReportFragmentsEvent;
|
||||
@@ -60,6 +61,13 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
||||
setRetainInstance(true);
|
||||
setListLayoutId(R.layout.fragment_content_report_posts);
|
||||
setLayout(R.layout.fragment_report_posts);
|
||||
E.register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy(){
|
||||
E.unregister(this);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,7 +5,9 @@ import android.os.Bundle;
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.FinishReportFragmentsEvent;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.ReportReason;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
@@ -21,7 +23,10 @@ public class ReportReasonChoiceFragment extends BaseReportChoiceFragment{
|
||||
protected void populateItems(){
|
||||
items.add(new Item(getString(R.string.report_reason_personal), getString(R.string.report_reason_personal_subtitle), ReportReason.PERSONAL.name()));
|
||||
items.add(new Item(getString(R.string.report_reason_spam), getString(R.string.report_reason_spam_subtitle), ReportReason.SPAM.name()));
|
||||
items.add(new Item(getString(R.string.report_reason_violation), getString(R.string.report_reason_violation_subtitle), ReportReason.VIOLATION.name()));
|
||||
Instance inst=AccountSessionManager.getInstance().getInstanceInfo(AccountSessionManager.getInstance().getAccount(accountID).domain);
|
||||
if(inst!=null && inst.rules!=null && !inst.rules.isEmpty()){
|
||||
items.add(new Item(getString(R.string.report_reason_violation), getString(R.string.report_reason_violation_subtitle), ReportReason.VIOLATION.name()));
|
||||
}
|
||||
items.add(new Item(getString(R.string.report_reason_other), getString(R.string.report_reason_other_subtitle), ReportReason.OTHER.name()));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
public class CacheablePaginatedResponse<T> extends PaginatedResponse<T>{
|
||||
private final boolean fromCache;
|
||||
|
||||
public CacheablePaginatedResponse(T items, String maxID, boolean fromCache){
|
||||
super(items, maxID);
|
||||
this.fromCache=fromCache;
|
||||
}
|
||||
|
||||
public boolean isFromCache(){
|
||||
return fromCache;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class HeaderPaginationList<T> extends ArrayList<T>{
|
||||
public Uri nextPageUri, prevPageUri;
|
||||
|
||||
public HeaderPaginationList(int initialCapacity){
|
||||
super(initialCapacity);
|
||||
}
|
||||
|
||||
public HeaderPaginationList(){
|
||||
super();
|
||||
}
|
||||
|
||||
public HeaderPaginationList(@NonNull Collection<? extends T> c){
|
||||
super(c);
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@ public class Status extends BaseModel implements DisplayItemsParent{
|
||||
public boolean pinned;
|
||||
|
||||
public transient boolean spoilerRevealed;
|
||||
public transient boolean hasGapAfter;
|
||||
|
||||
@Override
|
||||
public void postprocess() throws ObjectValidationException{
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
package org.joinmastodon.android.ui;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.SplashFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
|
||||
import me.grishka.appkit.imageloader.RecyclerViewDelegate;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.BottomSheet;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class AccountSwitcherSheet extends BottomSheet{
|
||||
private final Activity activity;
|
||||
private UsableRecyclerView list;
|
||||
private List<WrappedAccount> accounts;
|
||||
private ListImageLoaderWrapper imgLoader;
|
||||
|
||||
public AccountSwitcherSheet(@NonNull Activity activity){
|
||||
super(activity);
|
||||
this.activity=activity;
|
||||
|
||||
accounts=AccountSessionManager.getInstance().getLoggedInAccounts().stream().map(WrappedAccount::new).collect(Collectors.toList());
|
||||
|
||||
list=new UsableRecyclerView(activity);
|
||||
imgLoader=new ListImageLoaderWrapper(activity, list, new RecyclerViewDelegate(list), null);
|
||||
list.setClipToPadding(false);
|
||||
list.setLayoutManager(new LinearLayoutManager(activity));
|
||||
|
||||
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
|
||||
View handle=new View(activity);
|
||||
handle.setBackgroundResource(R.drawable.bg_bottom_sheet_handle);
|
||||
adapter.addAdapter(new SingleViewRecyclerAdapter(handle));
|
||||
adapter.addAdapter(new AccountsAdapter());
|
||||
AccountViewHolder holder=new AccountViewHolder();
|
||||
holder.more.setVisibility(View.GONE);
|
||||
holder.currentIcon.setVisibility(View.GONE);
|
||||
holder.name.setText(R.string.add_account);
|
||||
holder.avatar.setScaleType(ImageView.ScaleType.CENTER);
|
||||
holder.avatar.setImageResource(R.drawable.ic_fluent_add_circle_24_filled);
|
||||
holder.avatar.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(activity, android.R.attr.textColorPrimary)));
|
||||
adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(holder.itemView, ()->{
|
||||
Nav.go(activity, SplashFragment.class, null);
|
||||
dismiss();
|
||||
}));
|
||||
|
||||
list.setAdapter(adapter);
|
||||
DividerItemDecoration divider=new DividerItemDecoration(activity, R.attr.colorPollVoted, .5f, 72, 16, DividerItemDecoration.NOT_FIRST);
|
||||
divider.setDrawBelowLastItem(true);
|
||||
list.addItemDecoration(divider);
|
||||
|
||||
FrameLayout content=new FrameLayout(activity);
|
||||
content.setBackgroundResource(R.drawable.bg_bottom_sheet);
|
||||
content.addView(list);
|
||||
setContentView(content);
|
||||
setNavigationBarBackground(new ColorDrawable(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground)), !UiUtils.isDarkTheme());
|
||||
}
|
||||
|
||||
private void confirmLogOut(String accountID){
|
||||
new M3AlertDialogBuilder(activity)
|
||||
.setTitle(R.string.log_out)
|
||||
.setMessage(R.string.confirm_log_out)
|
||||
.setPositiveButton(R.string.log_out, (dialog, which) -> logOut(accountID))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void logOut(String accountID){
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
new RevokeOauthToken(session.app.clientId, session.app.clientSecret, session.token.accessToken)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Object result){
|
||||
onLoggedOut(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
onLoggedOut(accountID);
|
||||
}
|
||||
})
|
||||
.wrapProgress(activity, R.string.loading, false)
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
private void onLoggedOut(String accountID){
|
||||
AccountSessionManager.getInstance().removeAccount(accountID);
|
||||
dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onWindowInsetsUpdated(WindowInsets insets){
|
||||
if(Build.VERSION.SDK_INT>=29){
|
||||
int tappableBottom=insets.getTappableElementInsets().bottom;
|
||||
int insetBottom=insets.getSystemWindowInsetBottom();
|
||||
if(tappableBottom==0 && insetBottom>0){
|
||||
list.setPadding(0, 0, 0, V.dp(48)-insetBottom);
|
||||
}else{
|
||||
list.setPadding(0, 0, 0, V.dp(24));
|
||||
}
|
||||
}else{
|
||||
list.setPadding(0, 0, 0, V.dp(24));
|
||||
}
|
||||
}
|
||||
|
||||
private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
public AccountsAdapter(){
|
||||
super(imgLoader);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AccountViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new AccountViewHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return accounts.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(AccountViewHolder holder, int position){
|
||||
holder.bind(accounts.get(position).session);
|
||||
super.onBindViewHolder(holder, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getImageCountForItem(int position){
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||
return accounts.get(position).req;
|
||||
}
|
||||
}
|
||||
|
||||
private class AccountViewHolder extends BindableViewHolder<AccountSession> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
|
||||
private final TextView name;
|
||||
private final ImageView avatar;
|
||||
private final ImageButton more;
|
||||
private final View currentIcon;
|
||||
private final PopupMenu menu;
|
||||
|
||||
public AccountViewHolder(){
|
||||
super(activity, R.layout.item_account_switcher, list);
|
||||
name=findViewById(R.id.name);
|
||||
avatar=findViewById(R.id.avatar);
|
||||
more=findViewById(R.id.more);
|
||||
currentIcon=findViewById(R.id.current);
|
||||
|
||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||
avatar.setClipToOutline(true);
|
||||
|
||||
menu=new PopupMenu(activity, more);
|
||||
menu.inflate(R.menu.account_switcher);
|
||||
menu.setOnMenuItemClickListener(item1 -> {
|
||||
confirmLogOut(item.getID());
|
||||
return true;
|
||||
});
|
||||
more.setOnClickListener(v->menu.show());
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@Override
|
||||
public void onBind(AccountSession item){
|
||||
name.setText("@"+item.self.username+"@"+item.domain);
|
||||
if(AccountSessionManager.getInstance().getLastActiveAccountID().equals(item.getID())){
|
||||
more.setVisibility(View.GONE);
|
||||
currentIcon.setVisibility(View.VISIBLE);
|
||||
}else{
|
||||
more.setVisibility(View.VISIBLE);
|
||||
currentIcon.setVisibility(View.GONE);
|
||||
}
|
||||
menu.getMenu().findItem(R.id.log_out).setTitle(activity.getString(R.string.log_out_account, "@"+item.self.username));
|
||||
UiUtils.enablePopupMenuIcons(activity, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(int index, Drawable image){
|
||||
avatar.setImageDrawable(image);
|
||||
if(image instanceof Animatable a)
|
||||
a.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearImage(int index){
|
||||
setImage(index, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
AccountSessionManager.getInstance().setLastActiveAccountID(item.getID());
|
||||
activity.finish();
|
||||
activity.startActivity(new Intent(activity, MainActivity.class));
|
||||
}
|
||||
}
|
||||
|
||||
private static class WrappedAccount{
|
||||
public final AccountSession session;
|
||||
public final ImageLoaderRequest req;
|
||||
|
||||
public WrappedAccount(AccountSession session){
|
||||
this.session=session;
|
||||
req=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? session.self.avatar : session.self.avatarStatic, V.dp(50), V.dp(50));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.joinmastodon.android.ui;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class ClickableSingleViewRecyclerAdapter extends SingleViewRecyclerAdapter{
|
||||
private final Runnable onClick;
|
||||
|
||||
public ClickableSingleViewRecyclerAdapter(View view, Runnable onClick){
|
||||
super(view);
|
||||
this.onClick=onClick;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new ClickableViewViewHolder(view);
|
||||
}
|
||||
|
||||
public class ClickableViewViewHolder extends ViewViewHolder implements UsableRecyclerView.Clickable{
|
||||
public ClickableViewViewHolder(@NonNull View itemView){
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
onClick.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ public class DividerItemDecoration extends RecyclerView.ItemDecoration{
|
||||
private Paint paint=new Paint();
|
||||
private int paddingStart, paddingEnd;
|
||||
private Predicate<RecyclerView.ViewHolder> drawDividerPredicate;
|
||||
private boolean drawBelowLastItem;
|
||||
|
||||
public static final Predicate<RecyclerView.ViewHolder> NOT_FIRST=vh->vh.getAbsoluteAdapterPosition()>0;
|
||||
|
||||
@@ -34,6 +35,10 @@ public class DividerItemDecoration extends RecyclerView.ItemDecoration{
|
||||
this.drawDividerPredicate=drawDividerPredicate;
|
||||
}
|
||||
|
||||
public void setDrawBelowLastItem(boolean drawBelowLastItem){
|
||||
this.drawBelowLastItem=drawBelowLastItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
boolean isRTL=parent.getLayoutDirection()==View.LAYOUT_DIRECTION_RTL;
|
||||
@@ -43,7 +48,7 @@ public class DividerItemDecoration extends RecyclerView.ItemDecoration{
|
||||
for(int i=0;i<parent.getChildCount();i++){
|
||||
View child=parent.getChildAt(i);
|
||||
int pos=parent.getChildAdapterPosition(child);
|
||||
if(pos<totalItems-1 && (drawDividerPredicate==null || drawDividerPredicate.test(parent.getChildViewHolder(child)))){
|
||||
if((drawBelowLastItem || pos<totalItems-1) && (drawDividerPredicate==null || drawDividerPredicate.test(parent.getChildViewHolder(child)))){
|
||||
float y=Math.round(child.getY()+child.getHeight());
|
||||
y-=(y-paint.getStrokeWidth()/2f)%1f; // Make sure the line aligns with the pixel grid
|
||||
paint.setAlpha(Math.round(255f*child.getAlpha()));
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.joinmastodon.android.ui.displayitems;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.ui.drawables.SawtoothTearDrawable;
|
||||
|
||||
// Mind the gap!
|
||||
public class GapStatusDisplayItem extends StatusDisplayItem{
|
||||
public boolean loading;
|
||||
|
||||
public GapStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment){
|
||||
super(parentID, parentFragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType(){
|
||||
return Type.GAP;
|
||||
}
|
||||
|
||||
public static class Holder extends StatusDisplayItem.Holder<GapStatusDisplayItem>{
|
||||
public final ProgressBar progress;
|
||||
public final TextView text;
|
||||
|
||||
public Holder(Context context, ViewGroup parent){
|
||||
super(context, R.layout.display_item_gap, parent);
|
||||
progress=findViewById(R.id.progress);
|
||||
text=findViewById(R.id.text);
|
||||
itemView.setForeground(new SawtoothTearDrawable(context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(GapStatusDisplayItem item){
|
||||
text.setVisibility(item.loading ? View.GONE : View.VISIBLE);
|
||||
progress.setVisibility(item.loading ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
item.parentFragment.onGapClick(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -250,6 +250,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
Menu menu=optionsMenu.getMenu();
|
||||
boolean isOwnPost=AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account);
|
||||
menu.findItem(R.id.delete).setVisible(item.status!=null && isOwnPost);
|
||||
menu.findItem(R.id.open_in_browser).setVisible(item.status!=null);
|
||||
MenuItem blockDomain=menu.findItem(R.id.block_domain);
|
||||
MenuItem mute=menu.findItem(R.id.mute);
|
||||
MenuItem block=menu.findItem(R.id.block);
|
||||
|
||||
@@ -63,6 +63,7 @@ public abstract class StatusDisplayItem{
|
||||
case ACCOUNT_CARD -> new AccountCardStatusDisplayItem.Holder(activity, parent);
|
||||
case ACCOUNT -> new AccountStatusDisplayItem.Holder(activity, parent);
|
||||
case HASHTAG -> new HashtagStatusDisplayItem.Holder(activity, parent);
|
||||
case GAP -> new GapStatusDisplayItem.Holder(activity, parent);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -112,6 +113,8 @@ public abstract class StatusDisplayItem{
|
||||
}
|
||||
if(addFooter){
|
||||
items.add(new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID));
|
||||
if(status.hasGapAfter)
|
||||
items.add(new GapStatusDisplayItem(parentID, fragment));
|
||||
}
|
||||
int i=1;
|
||||
for(StatusDisplayItem item:items){
|
||||
@@ -142,7 +145,8 @@ public abstract class StatusDisplayItem{
|
||||
FOOTER,
|
||||
ACCOUNT_CARD,
|
||||
ACCOUNT,
|
||||
HASHTAG
|
||||
HASHTAG,
|
||||
GAP
|
||||
}
|
||||
|
||||
public static abstract class Holder<T extends StatusDisplayItem> extends BindableViewHolder<T> implements UsableRecyclerView.DisableableClickable{
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.joinmastodon.android.ui.drawables;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapShader;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class SawtoothTearDrawable extends Drawable{
|
||||
private final Paint topPaint, bottomPaint;
|
||||
|
||||
private static final int TOP_SAWTOOTH_HEIGHT=5;
|
||||
private static final int BOTTOM_SAWTOOTH_HEIGHT=3;
|
||||
private static final int STROKE_WIDTH=2;
|
||||
private static final int SAWTOOTH_PERIOD=14;
|
||||
|
||||
public SawtoothTearDrawable(Context context){
|
||||
topPaint=makeShaderPaint(makeSawtoothTexture(context, TOP_SAWTOOTH_HEIGHT, SAWTOOTH_PERIOD, false, STROKE_WIDTH));
|
||||
bottomPaint=makeShaderPaint(makeSawtoothTexture(context, BOTTOM_SAWTOOTH_HEIGHT, SAWTOOTH_PERIOD, true, STROKE_WIDTH));
|
||||
Matrix matrix=new Matrix();
|
||||
//noinspection IntegerDivisionInFloatingPointContext
|
||||
matrix.setTranslate(V.dp(SAWTOOTH_PERIOD/2), 0);
|
||||
bottomPaint.getShader().setLocalMatrix(matrix);
|
||||
}
|
||||
|
||||
private Bitmap makeSawtoothTexture(Context context, int height, int period, boolean fillBottom, int strokeWidth){
|
||||
int actualStrokeWidth=V.dp(strokeWidth);
|
||||
int actualPeriod=V.dp(period);
|
||||
int actualHeight=V.dp(height);
|
||||
Bitmap bitmap=Bitmap.createBitmap(actualPeriod, actualHeight+actualStrokeWidth*2, Bitmap.Config.ARGB_8888);
|
||||
Canvas c=new Canvas(bitmap);
|
||||
Path path=new Path();
|
||||
//noinspection SuspiciousNameCombination
|
||||
path.moveTo(-actualPeriod/2f, actualStrokeWidth);
|
||||
path.lineTo(0, actualHeight+actualStrokeWidth);
|
||||
//noinspection SuspiciousNameCombination
|
||||
path.lineTo(actualPeriod/2f, actualStrokeWidth);
|
||||
path.lineTo(actualPeriod, actualHeight+actualStrokeWidth);
|
||||
//noinspection SuspiciousNameCombination
|
||||
path.lineTo(actualPeriod*1.5f, actualStrokeWidth);
|
||||
if(fillBottom){
|
||||
path.lineTo(actualPeriod*1.5f, actualHeight*20);
|
||||
path.lineTo(-actualPeriod/2f, actualHeight*20);
|
||||
}else{
|
||||
path.lineTo(actualPeriod*1.5f, -actualHeight);
|
||||
path.lineTo(-actualPeriod/2f, -actualHeight);
|
||||
}
|
||||
path.close();
|
||||
Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setColor(UiUtils.getThemeColor(context, R.attr.colorWindowBackground));
|
||||
c.drawPath(path, paint);
|
||||
paint.setColor(UiUtils.getThemeColor(context, R.attr.colorPollVoted));
|
||||
paint.setStrokeWidth(actualStrokeWidth);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
c.drawPath(path, paint);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private Paint makeShaderPaint(Bitmap bitmap){
|
||||
BitmapShader shader=new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
|
||||
Paint paint=new Paint();
|
||||
paint.setShader(shader);
|
||||
return paint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas){
|
||||
int strokeWidth=V.dp(STROKE_WIDTH);
|
||||
Rect bounds=getBounds();
|
||||
canvas.save();
|
||||
canvas.translate(bounds.left, bounds.top);
|
||||
canvas.drawRect(0, 0, bounds.width(), V.dp(TOP_SAWTOOTH_HEIGHT)+strokeWidth*2, topPaint);
|
||||
int bottomHeight=V.dp(BOTTOM_SAWTOOTH_HEIGHT)+strokeWidth*2;
|
||||
canvas.translate(0, bounds.height()-bottomHeight);
|
||||
canvas.drawRect(0, 0, bounds.width(), bottomHeight, bottomPaint);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity(){
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
}
|
||||
@@ -133,18 +133,18 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets){
|
||||
if(Build.VERSION.SDK_INT>=29){
|
||||
DisplayCutout cutout=insets.getDisplayCutout();
|
||||
Insets tappable=insets.getTappableElementInsets();
|
||||
if(cutout!=null){
|
||||
// Make controls extend beneath the cutout, and replace insets to avoid cutout insets being filled with "navigation bar color"
|
||||
Insets tappable=insets.getTappableElementInsets();
|
||||
int leftInset=Math.max(0, cutout.getSafeInsetLeft()-tappable.left);
|
||||
int rightInset=Math.max(0, cutout.getSafeInsetRight()-tappable.right);
|
||||
insets=insets.replaceSystemWindowInsets(tappable.left, tappable.top, tappable.right, tappable.bottom);
|
||||
toolbarWrap.setPadding(leftInset, 0, rightInset, 0);
|
||||
videoControls.setPadding(leftInset, 0, rightInset, 0);
|
||||
}else{
|
||||
toolbarWrap.setPadding(0, 0, 0, 0);
|
||||
videoControls.setPadding(0, 0, 0, 0);
|
||||
}
|
||||
insets=insets.replaceSystemWindowInsets(tappable.left, tappable.top, tappable.right, tappable.bottom);
|
||||
}
|
||||
uiOverlay.dispatchApplyWindowInsets(insets);
|
||||
int bottomInset=insets.getSystemWindowInsetBottom();
|
||||
@@ -514,7 +514,11 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
}
|
||||
|
||||
private void saveViaDownloadManager(Attachment att){
|
||||
DownloadManager.Request req=new DownloadManager.Request(Uri.parse(att.url));
|
||||
Uri uri=Uri.parse(att.url);
|
||||
DownloadManager.Request req=new DownloadManager.Request(uri);
|
||||
req.allowScanningByMediaScanner();
|
||||
req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
|
||||
req.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, uri.getLastPathSegment());
|
||||
activity.getSystemService(DownloadManager.class).enqueue(req);
|
||||
Toast.makeText(activity, R.string.downloading, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.joinmastodon.android.ui.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
|
||||
public class DiscoverInfoBannerHelper{
|
||||
private View banner;
|
||||
private final BannerType type;
|
||||
|
||||
public DiscoverInfoBannerHelper(BannerType type){
|
||||
this.type=type;
|
||||
}
|
||||
|
||||
private SharedPreferences getPrefs(){
|
||||
return MastodonApp.context.getSharedPreferences("onboarding", Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public void maybeAddBanner(FrameLayout view){
|
||||
if(!getPrefs().getBoolean("bannerHidden_"+type, false)){
|
||||
((Activity)view.getContext()).getLayoutInflater().inflate(R.layout.discover_info_banner, view);
|
||||
banner=view.findViewById(R.id.discover_info_banner);
|
||||
view.findViewById(R.id.banner_dismiss).setOnClickListener(this::onDismissClick);
|
||||
TextView text=view.findViewById(R.id.banner_text);
|
||||
text.setText(switch(type){
|
||||
case TRENDING_POSTS -> R.string.trending_posts_info_banner;
|
||||
case TRENDING_HASHTAGS -> R.string.trending_hashtags_info_banner;
|
||||
case TRENDING_LINKS -> R.string.trending_links_info_banner;
|
||||
case LOCAL_TIMELINE -> R.string.local_timeline_info_banner;
|
||||
case FEDERATED_TIMELINE -> R.string.federated_timeline_info_banner;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void onDismissClick(View v){
|
||||
if(banner==null)
|
||||
return;
|
||||
View _banner=banner;
|
||||
banner.animate()
|
||||
.alpha(0)
|
||||
.setDuration(200)
|
||||
.setInterpolator(CubicBezierInterpolator.DEFAULT)
|
||||
.withEndAction(()->((ViewGroup)_banner.getParent()).removeView(_banner))
|
||||
.start();
|
||||
getPrefs().edit().putBoolean("bannerHidden_"+type, true).apply();
|
||||
banner=null;
|
||||
}
|
||||
|
||||
public enum BannerType{
|
||||
TRENDING_POSTS,
|
||||
TRENDING_HASHTAGS,
|
||||
TRENDING_LINKS,
|
||||
LOCAL_TIMELINE,
|
||||
FEDERATED_TIMELINE,
|
||||
// ACCOUNTS
|
||||
}
|
||||
}
|
||||
@@ -141,12 +141,15 @@ public class UiUtils{
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
public static String abbreviateNumber(int n){
|
||||
if(n<1000)
|
||||
if(n<1000){
|
||||
return String.format("%,d", n);
|
||||
else if(n<1_000_000)
|
||||
return String.format("%,.1fK", n/1000f);
|
||||
else
|
||||
return String.format("%,.1fM", n/1_000_000f);
|
||||
}else if(n<1_000_000){
|
||||
float a=n/1000f;
|
||||
return a>99f ? String.format("%,dK", (int)Math.floor(a)) : String.format("%,.1fK", a);
|
||||
}else{
|
||||
float a=n/1_000_000f;
|
||||
return a>99f ? String.format("%,dM", (int)Math.floor(a)) : String.format("%,.1fM", n/1_000_000f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,7 +185,7 @@ public class UiUtils{
|
||||
String name=cursor.getString(0);
|
||||
if(name!=null)
|
||||
return name;
|
||||
}
|
||||
}catch(Throwable ignore){}
|
||||
}
|
||||
return uri.getLastPathSegment();
|
||||
}
|
||||
@@ -340,13 +343,38 @@ public class UiUtils{
|
||||
}
|
||||
|
||||
public static void setRelationshipToActionButton(Relationship relationship, Button button){
|
||||
boolean secondaryStyle;
|
||||
if(relationship.blocking){
|
||||
button.setText(R.string.button_blocked);
|
||||
}else if(relationship.muting){
|
||||
button.setText(R.string.button_muted);
|
||||
secondaryStyle=true;
|
||||
}else if(relationship.blockedBy){
|
||||
button.setText(R.string.button_follow);
|
||||
secondaryStyle=false;
|
||||
}else if(relationship.requested){
|
||||
button.setText(R.string.button_follow_pending);
|
||||
secondaryStyle=true;
|
||||
}else if(!relationship.following){
|
||||
button.setText(relationship.followedBy ? R.string.follow_back : R.string.button_follow);
|
||||
secondaryStyle=false;
|
||||
}else{
|
||||
button.setText(relationship.following ? R.string.button_following : R.string.button_follow);
|
||||
button.setText(R.string.button_following);
|
||||
secondaryStyle=true;
|
||||
}
|
||||
|
||||
button.setEnabled(!relationship.blockedBy);
|
||||
int attr=secondaryStyle ? R.attr.secondaryButtonStyle : android.R.attr.buttonStyle;
|
||||
TypedArray ta=button.getContext().obtainStyledAttributes(new int[]{attr});
|
||||
int styleRes=ta.getResourceId(0, 0);
|
||||
ta.recycle();
|
||||
ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background});
|
||||
button.setBackground(ta.getDrawable(0));
|
||||
ta.recycle();
|
||||
ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.textColor});
|
||||
if(relationship.blocking)
|
||||
button.setTextColor(button.getResources().getColorStateList(R.color.error_600));
|
||||
else
|
||||
button.setTextColor(ta.getColorStateList(0));
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
public static void performAccountAction(Activity activity, Account account, String accountID, Relationship relationship, Button button, Consumer<Boolean> progressCallback, Consumer<Relationship> resultCallback){
|
||||
@@ -356,7 +384,7 @@ public class UiUtils{
|
||||
confirmToggleMuteUser(activity, accountID, account, true, resultCallback);
|
||||
}else{
|
||||
progressCallback.accept(true);
|
||||
new SetAccountFollowed(account.id, !relationship.following, true)
|
||||
new SetAccountFollowed(account.id, !relationship.following && !relationship.requested, true)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
public class AutoOrientationLinearLayout extends LinearLayout{
|
||||
public AutoOrientationLinearLayout(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public AutoOrientationLinearLayout(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public AutoOrientationLinearLayout(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
||||
int hPadding=getPaddingLeft()+getPaddingRight();
|
||||
int childrenTotalWidth=0;
|
||||
for(int i=0;i<getChildCount();i++){
|
||||
View child=getChildAt(i);
|
||||
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
|
||||
childrenTotalWidth+=child.getMeasuredWidth();
|
||||
}
|
||||
if(childrenTotalWidth>MeasureSpec.getSize(widthMeasureSpec)-hPadding){
|
||||
setOrientation(VERTICAL);
|
||||
}else{
|
||||
setOrientation(HORIZONTAL);
|
||||
}
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.joinmastodon.android.utils;
|
||||
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class StatusFilterPredicate implements Predicate<Status>{
|
||||
private final List<Filter> filters;
|
||||
|
||||
public StatusFilterPredicate(List<Filter> filters){
|
||||
this.filters=filters;
|
||||
}
|
||||
|
||||
public StatusFilterPredicate(String accountID, Filter.FilterContext context){
|
||||
filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(context)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(Status status){
|
||||
CharSequence content=status.getContentStatus().content;
|
||||
for(Filter filter:filters){
|
||||
if(filter.matches(content))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
9
mastodon/src/main/res/drawable/bg_bottom_sheet.xml
Normal file
9
mastodon/src/main/res/drawable/bg_bottom_sheet.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="?colorWindowBackground"/>
|
||||
<corners android:topLeftRadius="12dp" android:topRightRadius="12dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
12
mastodon/src/main/res/drawable/bg_bottom_sheet_handle.xml
Normal file
12
mastodon/src/main/res/drawable/bg_bottom_sheet_handle.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:gravity="center" android:width="36dp" android:height="4dp">
|
||||
<shape>
|
||||
<solid android:color="?android:textColorSecondary"/>
|
||||
<corners android:radius="2dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:height="20dp">
|
||||
<color android:color="#00000000"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
10
mastodon/src/main/res/drawable/bg_button_new_posts.xml
Normal file
10
mastodon/src/main/res/drawable/bg_button_new_posts.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/highlight_over_dark">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="?android:colorAccent"/>
|
||||
<corners android:radius="16dp"/>
|
||||
<padding android:left="16dp" android:right="16dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#E6000000"/>
|
||||
<corners android:radius="5dp"/>
|
||||
</shape>
|
||||
7
mastodon/src/main/res/drawable/bg_timeline_gap.xml
Normal file
7
mastodon/src/main/res/drawable/bg_timeline_gap.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<color android:color="?colorBackgroundLightest"/>
|
||||
</item>
|
||||
<item android:drawable="?android:selectableItemBackground"/>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2zm0 5c-0.38 0-0.694 0.282-0.743 0.648L11.25 7.75v3.5h-3.5C7.336 11.25 7 11.586 7 12c0 0.38 0.282 0.694 0.648 0.743L7.75 12.75h3.5v3.5c0 0.414 0.336 0.75 0.75 0.75 0.38 0 0.694-0.282 0.743-0.648l0.007-0.102v-3.5h3.5c0.414 0 0.75-0.336 0.75-0.75 0-0.38-0.282-0.694-0.648-0.743L16.25 11.25h-3.5v-3.5C12.75 7.336 12.414 7 12 7z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="16dp" android:height="16dp" android:viewportWidth="16" android:viewportHeight="16">
|
||||
<path android:pathData="M8 14c-0.414 0-0.75-0.336-0.75-0.75V4.463L4.309 7.75c-0.276 0.31-0.75 0.335-1.06 0.06-0.308-0.276-0.334-0.75-0.058-1.06L7.441 2C7.583 1.842 7.787 1.75 8 1.75c0.213 0 0.417 0.09 0.559 0.25l4.25 4.75c0.276 0.309 0.25 0.783-0.059 1.059-0.309 0.276-0.783 0.25-1.059-0.059L8.75 4.463v8.787C8.75 13.664 8.414 14 8 14z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M8.5 16.586l-3.793-3.793c-0.39-0.39-1.024-0.39-1.414 0-0.39 0.39-0.39 1.024 0 1.414l4.5 4.5c0.39 0.39 1.024 0.39 1.414 0l11-11c0.39-0.39 0.39-1.024 0-1.414-0.39-0.39-1.024-0.39-1.414 0L8.5 16.586z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="16dp" android:height="16dp" android:viewportWidth="16" android:viewportHeight="16">
|
||||
<path android:pathData="M4.146 6.354c0.196 0.195 0.512 0.195 0.708 0L8 3.207l3.146 3.147c0.196 0.195 0.512 0.195 0.708 0 0.195-0.196 0.195-0.512 0-0.708l-3.5-3.5c-0.196-0.195-0.512-0.195-0.707 0l-3.5 3.5c-0.196 0.196-0.196 0.512 0 0.708zm0 3.292c0.196-0.195 0.512-0.195 0.708 0L8 12.793l3.146-3.146c0.196-0.196 0.512-0.196 0.708 0 0.195 0.195 0.195 0.511 0 0.707l-3.5 3.5c-0.196 0.195-0.512 0.195-0.707 0l-3.5-3.5c-0.196-0.196-0.196-0.512 0-0.707z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
|
||||
<path android:pathData="M10 2c1.657 0 3 1.343 3 3v1h1c1.105 0 2 0.895 2 2v7c0 1.105-0.895 2-2 2H6c-1.105 0-2-0.895-2-2V8c0-1.105 0.895-2 2-2h1V5c0-1.657 1.343-3 3-3zm0 8.5c-0.552 0-1 0.448-1 1s0.448 1 1 1 1-0.448 1-1-0.448-1-1-1zM10 4C9.448 4 9 4.448 9 5v1h2V5c0-0.552-0.448-1-1-1z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M12 7.75c-0.966 0-1.75-0.784-1.75-1.75S11.034 4.25 12 4.25 13.75 5.034 13.75 6 12.966 7.75 12 7.75zm0 6c-0.966 0-1.75-0.784-1.75-1.75s0.784-1.75 1.75-1.75 1.75 0.784 1.75 1.75-0.784 1.75-1.75 1.75zM10.25 18c0 0.966 0.784 1.75 1.75 1.75s1.75-0.784 1.75-1.75-0.784-1.75-1.75-1.75-1.75 0.784-1.75 1.75z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="25dp" android:height="24dp" android:viewportWidth="25" android:viewportHeight="24">
|
||||
<path android:pathData="M11.416 17.5c0-1.288 0.375-2.49 1.022-3.5h-7.77c-1.242 0-2.249 1.007-2.249 2.25v0.919c0 0.572 0.179 1.13 0.51 1.596C4.473 20.929 6.996 22 10.417 22c0.931 0 1.796-0.08 2.592-0.238-0.992-1.142-1.592-2.632-1.592-4.263zm-1-15.495c2.761 0 5 2.239 5 5s-2.239 5-5 5c-2.762 0-5-2.239-5-5s2.238-5 5-5zm13 15.495c0 3.038-2.463 5.5-5.5 5.5-3.038 0-5.5-2.462-5.5-5.5 0-3.037 2.462-5.5 5.5-5.5 3.037 0 5.5 2.463 5.5 5.5zm-4.647-2.853c-0.195-0.196-0.511-0.196-0.707 0-0.195 0.195-0.195 0.512 0 0.707L19.71 17h-4.293c-0.276 0-0.5 0.224-0.5 0.5s0.224 0.5 0.5 0.5h4.293l-1.647 1.647c-0.195 0.195-0.195 0.512 0 0.707 0.196 0.195 0.512 0.195 0.707 0l2.5-2.5c0.054-0.053 0.092-0.116 0.117-0.182 0.018-0.05 0.029-0.105 0.03-0.163V17.5c0-0.077-0.018-0.15-0.049-0.215-0.015-0.032-0.034-0.063-0.057-0.092-0.013-0.018-0.028-0.035-0.045-0.05l-2.496-2.496z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
34
mastodon/src/main/res/layout/discover_info_banner.xml
Normal file
34
mastodon/src/main/res/layout/discover_info_banner.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/discover_info_banner"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top"
|
||||
android:elevation="1dp"
|
||||
android:outlineProvider="background"
|
||||
android:background="?colorWindowBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/banner_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
tools:text="@string/trending_posts_info_banner"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/banner_dismiss"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_margin="8dp"
|
||||
android:src="@drawable/ic_fluent_dismiss_circle_24_filled"
|
||||
android:tint="?android:textColorSecondary"
|
||||
android:contentDescription="@string/dismiss"
|
||||
android:background="?android:selectableItemBackgroundBorderless"/>
|
||||
|
||||
</LinearLayout>
|
||||
24
mastodon/src/main/res/layout/display_item_gap.xml
Normal file
24
mastodon/src/main/res/layout/display_item_gap.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="75dp"
|
||||
android:background="@drawable/bg_timeline_gap">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:text="@string/load_missing_posts"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -15,6 +15,13 @@
|
||||
android:paddingBottom="16dp"
|
||||
android:background="?android:statusBarColor">
|
||||
|
||||
<!-- https://github.com/mastodon/mastodon-android/issues/95 -->
|
||||
<View
|
||||
android:layout_width="1px"
|
||||
android:layout_height="1px"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"/>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="23dp"
|
||||
android:paddingBottom="15dp"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<org.joinmastodon.android.ui.views.CoverImageView
|
||||
@@ -33,6 +33,25 @@
|
||||
android:contentDescription="@string/profile_header"
|
||||
android:scaleType="centerCrop"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/follows_you"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="28dp"
|
||||
android:layout_alignEnd="@id/cover"
|
||||
android:layout_alignBottom="@id/cover"
|
||||
android:layout_margin="16dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:textColor="@color/gray_50t"
|
||||
android:textAllCaps="true"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:textSize="14dp"
|
||||
android:gravity="center"
|
||||
android:background="@drawable/bg_profile_follows_you"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
android:text="@string/follows_you"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/avatar_border"
|
||||
android:layout_width="102dp"
|
||||
@@ -56,80 +75,95 @@
|
||||
tools:src="#0f0" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/following_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="56dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:id="@+id/profile_counters"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/cover"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:padding="4dp"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal">
|
||||
<TextView
|
||||
android:id="@+id/following_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_title_large"
|
||||
tools:text="123"/>
|
||||
<TextView
|
||||
android:id="@+id/following_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
tools:text="following"/>
|
||||
</LinearLayout>
|
||||
android:layout_toEndOf="@id/avatar_border"
|
||||
android:gravity="end">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/followers_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="56dp"
|
||||
android:layout_toStartOf="@id/following_btn"
|
||||
android:layout_below="@id/cover"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:padding="4dp"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal">
|
||||
<TextView
|
||||
android:id="@+id/followers_count"
|
||||
<LinearLayout
|
||||
android:id="@+id/posts_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_title_large"
|
||||
tools:text="123"/>
|
||||
<TextView
|
||||
android:id="@+id/followers_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
tools:text="following"/>
|
||||
</LinearLayout>
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:padding="4dp">
|
||||
<TextView
|
||||
android:id="@+id/posts_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_title_large"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
tools:text="123" />
|
||||
<TextView
|
||||
android:id="@+id/posts_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="middle"
|
||||
tools:text="posts" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/posts_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="56dp"
|
||||
android:layout_below="@id/cover"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_toStartOf="@id/followers_btn"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:padding="4dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/posts_count"
|
||||
<LinearLayout
|
||||
android:id="@+id/following_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_title_large"
|
||||
tools:text="123" />
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:padding="4dp"
|
||||
android:orientation="vertical"
|
||||
android:background="?android:selectableItemBackgroundBorderless"
|
||||
android:gravity="center_horizontal">
|
||||
<TextView
|
||||
android:id="@+id/following_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_title_large"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
tools:text="123"/>
|
||||
<TextView
|
||||
android:id="@+id/following_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="middle"
|
||||
tools:text="following"/>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/posts_label"
|
||||
<LinearLayout
|
||||
android:id="@+id/followers_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
tools:text="following" />
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:padding="4dp"
|
||||
android:orientation="vertical"
|
||||
android:background="?android:selectableItemBackgroundBorderless"
|
||||
android:gravity="center_horizontal">
|
||||
<TextView
|
||||
android:id="@+id/followers_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_title_large"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
tools:text="123"/>
|
||||
<TextView
|
||||
android:id="@+id/followers_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="middle"
|
||||
tools:text="followers"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
@@ -137,7 +171,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_below="@id/following_btn"
|
||||
android:layout_below="@id/profile_counters"
|
||||
android:padding="16dp"
|
||||
android:clipToPadding="false">
|
||||
<org.joinmastodon.android.ui.views.ProgressBarButton
|
||||
|
||||
62
mastodon/src/main/res/layout/item_account_list.xml
Normal file
62
mastodon/src/main/res/layout/item_account_list.xml
Normal file
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="62dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="46dp"
|
||||
android:layout_height="46dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:importantForAccessibility="no"
|
||||
tools:src="#0f0"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="8dp"
|
||||
tools:text="Follow"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="24dp"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_toStartOf="@id/button"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
tools:text="User"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_below="@id/name"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_toStartOf="@id/button"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
tools:text="\@user@server"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/menu_anchor"
|
||||
android:layout_width="1px"
|
||||
android:layout_height="1px"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_marginLeft="-16dp"/>
|
||||
|
||||
</RelativeLayout>
|
||||
44
mastodon/src/main/res/layout/item_account_switcher.xml
Normal file
44
mastodon/src/main/res/layout/item_account_switcher.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingRight="20dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:importantForAccessibility="no"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="24dp"
|
||||
android:textSize="16dp"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/current"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:background="@drawable/ic_fluent_checkmark_24_filled"
|
||||
android:backgroundTint="?android:textColorSecondary"
|
||||
android:contentDescription="@string/current_account"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/more"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_fluent_more_vertical_24_regular"
|
||||
android:tint="?android:textColorSecondary"
|
||||
android:contentDescription="@string/more_options"
|
||||
android:background="?android:selectableItemBackgroundBorderless"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,16 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<org.joinmastodon.android.ui.views.AutoOrientationLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="32dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layoutDirection="locale"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="20dp"
|
||||
@@ -19,12 +20,10 @@
|
||||
android:ellipsize="end"
|
||||
android:text="@string/notify_me_when"/>
|
||||
|
||||
<!-- TODO make this LL vertical when the button doesn't fit -->
|
||||
<Button
|
||||
android:id="@+id/button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_height="32dp"
|
||||
android:background="@drawable/bg_inline_button"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="20dp"
|
||||
@@ -37,4 +36,4 @@
|
||||
android:ellipsize="middle"
|
||||
tools:text="@string/notify_followed"/>
|
||||
|
||||
</LinearLayout>
|
||||
</org.joinmastodon.android.ui.views.AutoOrientationLinearLayout>
|
||||
@@ -12,11 +12,11 @@
|
||||
android:id="@+id/tabbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="52dp"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingRight="20dp">
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
<ImageView
|
||||
android:id="@+id/tab_home"
|
||||
android:layout_width="52dp"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="52dp"
|
||||
android:scaleType="center"
|
||||
android:contentDescription="@string/home_timeline"
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/tab_search"
|
||||
android:layout_width="52dp"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="52dp"
|
||||
android:scaleType="center"
|
||||
android:contentDescription="@string/search_hint"
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/tab_notifications"
|
||||
android:layout_width="52dp"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="52dp"
|
||||
android:scaleType="center"
|
||||
android:contentDescription="@string/notifications"
|
||||
@@ -61,7 +61,7 @@
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/tab_profile"
|
||||
android:layout_width="52dp"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="52dp"
|
||||
android:contentDescription="@string/my_profile"
|
||||
android:foreground="@drawable/bg_tab_profile"
|
||||
@@ -73,6 +73,13 @@
|
||||
android:layout_gravity="center"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@null"/>
|
||||
<View
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_marginEnd="-4dp"
|
||||
android:backgroundTint="?android:colorPrimary"
|
||||
android:background="@drawable/ic_fluent_chevron_up_down_16_regular"/>
|
||||
</FrameLayout>
|
||||
</org.joinmastodon.android.ui.views.TabBar>
|
||||
|
||||
|
||||
4
mastodon/src/main/res/menu/account_switcher.xml
Normal file
4
mastodon/src/main/res/menu/account_switcher.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/log_out" android:title="@string/log_out_account" android:icon="@drawable/ic_fluent_person_arrow_right_24_filled"/>
|
||||
</menu>
|
||||
@@ -3,6 +3,9 @@
|
||||
<item android:id="@+id/vis_public"
|
||||
android:icon="@drawable/ic_fluent_earth_24_filled"
|
||||
android:title="@string/visibility_public"/>
|
||||
<item android:id="@+id/vis_unlisted"
|
||||
android:icon="@drawable/ic_fluent_people_community_24_regular"
|
||||
android:title="@string/visibility_unlisted"/>
|
||||
<item android:id="@+id/vis_followers"
|
||||
android:icon="@drawable/ic_fluent_people_checkmark_24_regular"
|
||||
android:title="@string/visibility_followers_only"/>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Mastodon</string>
|
||||
|
||||
<string name="get_started">Loslegen</string>
|
||||
<string name="log_in">Anmelden</string>
|
||||
<string name="next">Weiter</string>
|
||||
@@ -14,7 +13,6 @@
|
||||
<string name="user_boosted">%s hat geteilt</string>
|
||||
<string name="in_reply_to">Antwort auf %s</string>
|
||||
<string name="notifications">Benachrichtigungen</string>
|
||||
|
||||
<string name="user_followed_you">ist dir gefolgt</string>
|
||||
<string name="user_sent_follow_request">hat dir eine Folgeanfrage gesendet</string>
|
||||
<string name="user_favorited">favorisierte deinen Beitrag</string>
|
||||
@@ -215,6 +213,7 @@
|
||||
<string name="alt_text_subtitle">Alternativtext erscheint für blinde Menschen. Versuche, nur so viele Details einzubeziehen, um den Kontext zu verstehen.</string>
|
||||
<string name="alt_text_hint">z.B. Eine Giraffe auf einem Dreirad während sie eine Banane isst</string>
|
||||
<string name="visibility_public">Öffentlich</string>
|
||||
<string name="visibility_unlisted">Nicht gelistet</string>
|
||||
<string name="visibility_followers_only">Nur Folgende</string>
|
||||
<string name="visibility_private">Nur Leute, die ich erwähne</string>
|
||||
<string name="search_all">Alle</string>
|
||||
|
||||
@@ -81,6 +81,22 @@
|
||||
<item quantity="other">%d egun</item>
|
||||
</plurals>
|
||||
<string name="compose_poll_duration">Iraupena: %s</string>
|
||||
<plurals name="x_seconds_left">
|
||||
<item quantity="one">%d segundo geratzen da</item>
|
||||
<item quantity="other">%d segundo geratzen dira</item>
|
||||
</plurals>
|
||||
<plurals name="x_minutes_left">
|
||||
<item quantity="one">%d minutu geratzen da</item>
|
||||
<item quantity="other">%d minutu geratzen dira</item>
|
||||
</plurals>
|
||||
<plurals name="x_hours_left">
|
||||
<item quantity="one">%d ordu geratzen da</item>
|
||||
<item quantity="other">%d ordu geratzen dira</item>
|
||||
</plurals>
|
||||
<plurals name="x_days_left">
|
||||
<item quantity="one">%d egun falta da</item>
|
||||
<item quantity="other">%d egun falta dira</item>
|
||||
</plurals>
|
||||
<string name="poll_closed">Itxita</string>
|
||||
<string name="confirm_mute_title">Mututu kontua</string>
|
||||
<string name="confirm_mute">Berretsi %s mututzea</string>
|
||||
@@ -115,6 +131,10 @@
|
||||
<string name="for_you">Zuretzat</string>
|
||||
<string name="all_notifications">Denak</string>
|
||||
<string name="mentions">Aipamenak</string>
|
||||
<plurals name="x_people_talking">
|
||||
<item quantity="one">Pertsona %d hizketan</item>
|
||||
<item quantity="other">%d pertsona hizketan</item>
|
||||
</plurals>
|
||||
<string name="report_title">Salatu %s</string>
|
||||
<string name="report_choose_reason">Zer du txarra argitalpen honek?</string>
|
||||
<string name="report_choose_reason_account">Zer du txarra %s?</string>
|
||||
@@ -140,12 +160,15 @@
|
||||
<string name="unfollow">Utzi jarraitzeari</string>
|
||||
<string name="report_personal_title">Ez duzu hau ikusi nahi?</string>
|
||||
<string name="back">Atzera</string>
|
||||
<string name="instance_catalog_title">Mastodon komunitate ezberdinetako erabiltzaileez egina dago.</string>
|
||||
<string name="search_communities">Buscar comunidades o ingresar URL</string>
|
||||
<string name="instance_rules_title">Oinarrizko arau batzuk</string>
|
||||
<string name="edit_photo">editatu</string>
|
||||
<string name="display_name">pantaila-izena</string>
|
||||
<string name="username">erabiltzaile-izena</string>
|
||||
<string name="email">eposta</string>
|
||||
<string name="password">pasahitza</string>
|
||||
<string name="password_note">Sartu letra larriak, karaktere bereziak eta zenbakiak zure pasahitzaren segurtasuna areagotzeko.</string>
|
||||
<string name="category_academia">Akademia</string>
|
||||
<string name="category_activism">Aktibismoa</string>
|
||||
<string name="category_all">Denak</string>
|
||||
@@ -159,6 +182,7 @@
|
||||
<string name="category_regional">Herrialdekoa</string>
|
||||
<string name="category_tech">Teknologia</string>
|
||||
<string name="confirm_email_title">Eta azkenik...</string>
|
||||
<string name="confirm_email_subtitle">Sakatu epostaz bidali dizugun loturan zure kontua egiaztatzeko.</string>
|
||||
<string name="resend">Berbidali</string>
|
||||
<string name="open_email_app">Ireki eposta aplikazioa</string>
|
||||
<string name="resent_email">Berretzi eposta bidaltzea</string>
|
||||
@@ -184,6 +208,7 @@
|
||||
<string name="notification_type_mention">Aipamenak</string>
|
||||
<string name="notification_type_poll">Inkestak</string>
|
||||
<string name="choose_account">Aukeratu kontua</string>
|
||||
<string name="err_not_logged_in">Mesedez, hasi saioa lehenengo Mastodonen</string>
|
||||
<string name="theme_auto">Automatikoa</string>
|
||||
<string name="theme_light">Argia</string>
|
||||
<string name="theme_dark">Iluna</string>
|
||||
@@ -200,6 +225,7 @@
|
||||
<string name="notify_mention">Aipatu nau</string>
|
||||
<string name="settings_boring">Eremu aspergarria</string>
|
||||
<string name="settings_account">Kontuaren ezarpenak</string>
|
||||
<string name="settings_contribute">Lagundu Mastodon</string>
|
||||
<string name="settings_tos">Erabilera baldintzak</string>
|
||||
<string name="settings_privacy_policy">Pribatutasun gidalerroak</string>
|
||||
<string name="settings_spicy">Eremu beroa</string>
|
||||
@@ -235,6 +261,7 @@
|
||||
<string name="reorder">Berriro antolatu</string>
|
||||
<string name="download">Jeitsi</string>
|
||||
<string name="permission_required">Baimena beharrezkoa</string>
|
||||
<string name="storage_permission_to_download">Aplikazioak zure biltegira sarbidea behar du fitxategia gordetzeko.</string>
|
||||
<string name="open_settings">Ireki ezarpenak</string>
|
||||
<string name="error_saving_file">Errorea fitxategia gordetzerakoan</string>
|
||||
<string name="file_saved">Fitxategia gorde da</string>
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
<string name="get_started">Premiers pas</string>
|
||||
<string name="log_in">Se connecter</string>
|
||||
<string name="next">Suivant</string>
|
||||
<string name="loading_instance">Chargement des informations de l\'instance…</string>
|
||||
<string name="loading_instance">Chargement des informations de l’instance…</string>
|
||||
<string name="error">Erreur</string>
|
||||
<string name="not_a_mastodon_instance">%s ne semble pas être une instance Mastodon.</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="preparing_auth">Préparation à l\'authentification…</string>
|
||||
<string name="finishing_auth">Fin de l\'authentification…</string>
|
||||
<string name="user_boosted">%s partagés</string>
|
||||
<string name="preparing_auth">Préparation à l’authentification…</string>
|
||||
<string name="finishing_auth">Fin de l’authentification…</string>
|
||||
<string name="user_boosted">%s a partagé</string>
|
||||
<string name="in_reply_to">En réponse à %s</string>
|
||||
<string name="notifications">Notifications</string>
|
||||
<string name="user_followed_you">s’est abonné à vous</string>
|
||||
@@ -18,10 +18,10 @@
|
||||
<string name="user_favorited">a mis votre pouet en favori</string>
|
||||
<string name="notification_boosted">a partagé votre pouet</string>
|
||||
<string name="poll_ended">sondage terminé</string>
|
||||
<string name="time_seconds">%ds</string>
|
||||
<string name="time_minutes">%dm</string>
|
||||
<string name="time_hours">%dh</string>
|
||||
<string name="time_days">%dj</string>
|
||||
<string name="time_seconds">%d s</string>
|
||||
<string name="time_minutes">%d m</string>
|
||||
<string name="time_hours">%d h</string>
|
||||
<string name="time_days">%d j</string>
|
||||
<string name="share_toot_title">Partager</string>
|
||||
<string name="settings">Paramètres</string>
|
||||
<string name="publish">Publier</string>
|
||||
@@ -32,7 +32,15 @@
|
||||
<item quantity="one">abonné·e</item>
|
||||
<item quantity="other">abonné·e·s</item>
|
||||
</plurals>
|
||||
<string name="posts">Publication</string>
|
||||
<plurals name="following">
|
||||
<item quantity="one">abonnement</item>
|
||||
<item quantity="other">abonnements</item>
|
||||
</plurals>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">message</item>
|
||||
<item quantity="other">messages</item>
|
||||
</plurals>
|
||||
<string name="posts">Messages</string>
|
||||
<string name="posts_and_replies">Messages et réponses</string>
|
||||
<string name="media">Médias</string>
|
||||
<string name="profile_about">À propos</string>
|
||||
@@ -41,20 +49,24 @@
|
||||
<string name="edit_profile">Modifier le profil</string>
|
||||
<string name="mention_user">Mentionner %s</string>
|
||||
<string name="share_user">Partager %s</string>
|
||||
<string name="mute_user">Mettre %s en sourdine</string>
|
||||
<string name="unmute_user">Rétablir le son de %s</string>
|
||||
<string name="mute_user">Masquer %s</string>
|
||||
<string name="unmute_user">Ne plus masquer %s</string>
|
||||
<string name="block_user">Bloquer %s</string>
|
||||
<string name="unblock_user">Débloquer %s</string>
|
||||
<string name="report_user">Signaler %s</string>
|
||||
<string name="block_domain">Bloquer %s</string>
|
||||
<string name="unblock_domain">Débloquer %s</string>
|
||||
<string name="profile_joined">Rejoint</string>
|
||||
<plurals name="x_posts">
|
||||
<item quantity="one">%,d message</item>
|
||||
<item quantity="other">%,d messages</item>
|
||||
</plurals>
|
||||
<string name="profile_joined">Ici depuis</string>
|
||||
<string name="done">Terminé</string>
|
||||
<string name="loading">Chargement…</string>
|
||||
<string name="field_label">Étiquette</string>
|
||||
<string name="field_content">Contenu</string>
|
||||
<string name="saving">Enregistrement en cours…</string>
|
||||
<string name="post_from_user">Publication de %s</string>
|
||||
<string name="post_from_user">Message de %s</string>
|
||||
<string name="poll_option_hint">Option %d</string>
|
||||
<plurals name="x_minutes">
|
||||
<item quantity="one">%d minute</item>
|
||||
@@ -85,13 +97,17 @@
|
||||
<item quantity="one">%d jour restant</item>
|
||||
<item quantity="other">%d jours restants</item>
|
||||
</plurals>
|
||||
<plurals name="x_voters">
|
||||
<item quantity="one">%,d votant</item>
|
||||
<item quantity="other">%,d votants</item>
|
||||
</plurals>
|
||||
<string name="poll_closed">Fermé</string>
|
||||
<string name="confirm_mute_title">Masquer le compte</string>
|
||||
<string name="confirm_mute">Êtes-vous sûr de vouloir mettre en sourdine %s</string>
|
||||
<string name="do_mute">Mettre en sourdine</string>
|
||||
<string name="confirm_unmute_title">Ne plus mettre en sourdine ce compte</string>
|
||||
<string name="confirm_unmute">Êtes-vous sûr de vouloir désactiver la sourdine de %s</string>
|
||||
<string name="do_unmute">Ne plus ignorer</string>
|
||||
<string name="do_unmute">Ne plus masquer</string>
|
||||
<string name="confirm_block_title">Bloquer le compte</string>
|
||||
<string name="confirm_block_domain_title">Bloquer le domaine</string>
|
||||
<string name="confirm_block">Confirmer le blocage de %s</string>
|
||||
@@ -100,14 +116,189 @@
|
||||
<string name="confirm_unblock_domain_title">Débloquer le domaine</string>
|
||||
<string name="confirm_unblock">Confirmer le déblocage de %s</string>
|
||||
<string name="do_unblock">Débloquer</string>
|
||||
<string name="button_muted">En sourdine</string>
|
||||
<string name="button_muted">Masqué</string>
|
||||
<string name="button_blocked">Bloqué</string>
|
||||
<string name="action_vote">Voter</string>
|
||||
<string name="tap_to_reveal">Appuyer pour afficher</string>
|
||||
<string name="delete">Supprimer</string>
|
||||
<string name="confirm_delete_title">Supprimer la publication</string>
|
||||
<string name="confirm_delete">Voulez-vous vraiment supprimer cette publication ?</string>
|
||||
<string name="confirm_delete_title">Supprimer le message</string>
|
||||
<string name="confirm_delete">Voulez-vous vraiment supprimer ce message ?</string>
|
||||
<string name="deleting">Suppression en cours…</string>
|
||||
<string name="notification_channel_audio_player">Lecture audio</string>
|
||||
<string name="play">Lire</string>
|
||||
<string name="pause">Pause</string>
|
||||
<string name="log_out">Se déconnecter</string>
|
||||
<string name="add_account">Ajouter un compte</string>
|
||||
<string name="search_hint">Rechercher</string>
|
||||
<string name="hashtags">Hashtags</string>
|
||||
<string name="news">Actualités</string>
|
||||
<string name="for_you">Pour vous</string>
|
||||
<string name="all_notifications">Tout</string>
|
||||
<string name="mentions">Mentions</string>
|
||||
<plurals name="x_people_talking">
|
||||
<item quantity="one">%d personne en parle</item>
|
||||
<item quantity="other">%d personnes en parlent</item>
|
||||
</plurals>
|
||||
<plurals name="discussed_x_times">
|
||||
<item quantity="one">Discuté %d fois</item>
|
||||
<item quantity="other">Discuté %d fois</item>
|
||||
</plurals>
|
||||
<string name="report_title">Signaler %s</string>
|
||||
<string name="report_choose_reason">Quel est le problème avec ce post ?</string>
|
||||
<string name="report_choose_reason_account">Quel est le problème avec %s ?</string>
|
||||
<string name="report_choose_reason_subtitle">Sélectionnez la meilleure correspondance</string>
|
||||
<string name="report_reason_personal">Je n’aime pas ça</string>
|
||||
<string name="report_reason_personal_subtitle">C’est quelque chose que je ne souhaite pas voir</string>
|
||||
<string name="report_reason_spam">C’est du spam</string>
|
||||
<string name="report_reason_spam_subtitle">Liens malveillants, faux engagement ou réponses répétitives</string>
|
||||
<string name="report_reason_violation">Viole les règles du serveur</string>
|
||||
<string name="report_reason_violation_subtitle">Enfreint des règles spécifiques</string>
|
||||
<string name="report_reason_other">Pour une autre raison</string>
|
||||
<string name="report_reason_other_subtitle">Le problème ne correspond à aucune des catégories</string>
|
||||
<string name="report_choose_rule">Quelles règles sont enfreintes ?</string>
|
||||
<string name="report_choose_rule_subtitle">Sélectionnez toutes les réponses qui s’appliquent</string>
|
||||
<string name="report_choose_posts">Existe-t-il des messages pour étayer ce rapport ?</string>
|
||||
<string name="report_choose_posts_subtitle">Sélectionnez toutes les réponses qui s’appliquent</string>
|
||||
<string name="report_comment_title">Y a-t-il autre chose que nous devrions savoir ?</string>
|
||||
<string name="report_comment_hint">Commentaires supplémentaires</string>
|
||||
<string name="sending_report">Envoi du rapport en cours…</string>
|
||||
<string name="report_sent_title">Merci de nous l’avoir signalé, nous allons examiner cela.</string>
|
||||
<string name="report_sent_subtitle">Pendant que nous étudions votre requête, vous pouvez prendre des mesures contre %s.</string>
|
||||
<string name="unfollow_user">Ne plus suivre %s</string>
|
||||
<string name="unfollow">Ne plus suivre</string>
|
||||
<string name="mute_user_explain">Vous ne verrez pas leurs messages ou leurs reblogs dans votre flux personnel. Ils ne sauront pas qu’ils ont été mis en sourdine.</string>
|
||||
<string name="block_user_explain">Ils ne seront plus en mesure de suivre ou de voir vos messages, mais ils peuvent voir s’ils ont été bloqués.</string>
|
||||
<string name="report_personal_title">Vous ne voulez pas voir cela ?</string>
|
||||
<string name="report_personal_subtitle">Quand vous voyez quelque chose que vous n’aimez pas sur Mastodon, vous pouvez retirer la personne de votre expérience.</string>
|
||||
<string name="back">Retour</string>
|
||||
<string name="instance_catalog_title">Mastodon est composé d’utilisateurs de différentes communautés.</string>
|
||||
<string name="instance_catalog_subtitle">Choisissez une communauté basée sur vos intérêts, votre région ou un but général. Vous pouvez toujours vous connecter avec tout le monde, quelle que soit la communauté.</string>
|
||||
<string name="search_communities">Rechercher parmi les communautés ou entrer une URL</string>
|
||||
<string name="instance_rules_title">Quelques règles de base</string>
|
||||
<string name="instance_rules_subtitle">Prenez une minute pour revoir les règles définies et appliquées par les administrateurs de %s.</string>
|
||||
<string name="signup_title">Mettons les choses en place pour %s</string>
|
||||
<string name="edit_photo">modifier</string>
|
||||
<string name="display_name">nom affiché</string>
|
||||
<string name="username">nom d’utilisateur</string>
|
||||
<string name="email">courriel</string>
|
||||
<string name="password">mot de passe</string>
|
||||
<string name="password_note">Inclure les lettres majuscules, les caractères spéciaux et les chiffres pour augmenter la force de votre mot de passe.</string>
|
||||
<string name="category_academia">Universitaire</string>
|
||||
<string name="category_activism">Activisme</string>
|
||||
<string name="category_all">Tout</string>
|
||||
<string name="category_art">Art</string>
|
||||
<string name="category_food">Nourriture</string>
|
||||
<string name="category_furry">Fourrure</string>
|
||||
<string name="category_games">Jeux</string>
|
||||
<string name="category_general">Général</string>
|
||||
<string name="category_journalism">Journalisme</string>
|
||||
<string name="category_lgbt">LGBT</string>
|
||||
<string name="category_music">Musique</string>
|
||||
<string name="category_regional">Régional</string>
|
||||
<string name="category_tech">Technologie</string>
|
||||
<string name="confirm_email_title">Une dernière chose</string>
|
||||
<string name="confirm_email_subtitle">Appuyez sur le lien que nous vous avons envoyé par courriel pour vérifier votre compte.</string>
|
||||
<string name="resend">Renvoyer</string>
|
||||
<string name="open_email_app">Ouvrir l’application courriel</string>
|
||||
<string name="resent_email">Courriel de confirmation envoyé</string>
|
||||
<string name="compose_hint">Saisissez ou collez ce qui vous passe par la tête</string>
|
||||
<string name="content_warning">Avertissement sur le contenu</string>
|
||||
<string name="add_image_description">Ajouter une description de l’image…</string>
|
||||
<string name="retry_upload">Réessayer le téléversement</string>
|
||||
<string name="image_upload_failed">Échec du téléversement de l’image</string>
|
||||
<string name="video_upload_failed">Échec du téléversement de la vidéo</string>
|
||||
<string name="edit_image">Modifier l’image</string>
|
||||
<string name="save">Enregistrer</string>
|
||||
<string name="add_alt_text">Ajouter un texte alternatif</string>
|
||||
<string name="alt_text_subtitle">Le texte alternatif décrit vos photos pour les personnes qui ont une vision faible ou nulle. Essayez d’inclure uniquement assez de détails pour comprendre le contexte.</string>
|
||||
<string name="alt_text_hint">p. ex. : Chien regardant autour suspectement avec les yeux fixés sur la caméra.</string>
|
||||
<string name="visibility_public">Public</string>
|
||||
<string name="visibility_followers_only">Abonné·e·s uniquement</string>
|
||||
<string name="visibility_private">Seulement les personnes mentionnées</string>
|
||||
<string name="search_all">Tout</string>
|
||||
<string name="search_people">Personnes</string>
|
||||
<string name="recent_searches">Recherches récentes</string>
|
||||
<string name="step_x_of_n">Étape %1$d de %2$d</string>
|
||||
<string name="skip">Passer</string>
|
||||
<string name="notification_type_follow">Nouveaux⋅elles abonné⋅e⋅s</string>
|
||||
<string name="notification_type_favorite">Favoris</string>
|
||||
<string name="notification_type_reblog">Partages</string>
|
||||
<string name="notification_type_mention">Mentions</string>
|
||||
<string name="notification_type_poll">Sondages</string>
|
||||
<string name="choose_account">Choisir un compte</string>
|
||||
<string name="err_not_logged_in">Veuillez d’abord vous connecter à Mastodon</string>
|
||||
<plurals name="cant_add_more_than_x_attachments">
|
||||
<item quantity="one">Vous ne pouvez pas ajouter plus d’une pièce jointe</item>
|
||||
<item quantity="other">Vous ne pouvez pas ajouter plus de %d pièces jointes</item>
|
||||
</plurals>
|
||||
<string name="media_attachment_unsupported_type">Le fichier %s n’est pas pris en charge</string>
|
||||
<string name="media_attachment_too_big">Le fichier %1$s dépasse la taille limite de %2$s Mo</string>
|
||||
<string name="settings_theme">Apparence visuelle</string>
|
||||
<string name="theme_auto">Automatique</string>
|
||||
<string name="theme_light">Clair</string>
|
||||
<string name="theme_dark">Sombre</string>
|
||||
<string name="theme_true_black">Mode vrai noir</string>
|
||||
<string name="settings_behavior">Comportement</string>
|
||||
<string name="settings_gif">Jouer les avatars animés et émojis</string>
|
||||
<string name="settings_custom_tabs">Utiliser le navigateur web intégré</string>
|
||||
<string name="settings_notifications">Notifications</string>
|
||||
<string name="notify_me_when">Me notifier lorsque</string>
|
||||
<string name="notify_anyone">tout le monde</string>
|
||||
<string name="notify_follower">un·e abonné·e</string>
|
||||
<string name="notify_followed">quelqu’un que je suis</string>
|
||||
<string name="notify_none">personne</string>
|
||||
<string name="notify_favorites">Ajoute un de mes messages à ses favoris</string>
|
||||
<string name="notify_follow">Me suit</string>
|
||||
<string name="notify_reblog">Partage mon message</string>
|
||||
<string name="notify_mention">Me mentionne</string>
|
||||
<string name="settings_boring">La zone ennuyeuse</string>
|
||||
<string name="settings_account">Paramètres du compte</string>
|
||||
<string name="settings_contribute">Contribuer à Mastodon</string>
|
||||
<string name="settings_tos">Conditions d’utilisation</string>
|
||||
<string name="settings_privacy_policy">Politique de confidentialité</string>
|
||||
<string name="settings_spicy">La zone épicée</string>
|
||||
<string name="settings_clear_cache">Vider le cache des médias</string>
|
||||
<string name="settings_app_version">Mastodon pour Android v%1$s (%2$d)</string>
|
||||
<string name="media_cache_cleared">Cache des médias vidé</string>
|
||||
<string name="confirm_log_out">Êtes-vous sûr de vouloir vous déconnecter ?</string>
|
||||
<string name="sensitive_content">Contenu sensible</string>
|
||||
<string name="sensitive_content_explain">L’auteur·rice a marqué ce média comme sensible. Appuyez pour le révéler.</string>
|
||||
<string name="media_hidden">Appuyez pour révéler</string>
|
||||
<string name="avatar_description">Aller au profil de %s</string>
|
||||
<string name="more_options">Paramètres supplémentaires</string>
|
||||
<string name="reveal_content">Révéler le contenu</string>
|
||||
<string name="hide_content">Masquer le contenu</string>
|
||||
<string name="new_post">Nouveau message</string>
|
||||
<string name="button_reply">Répondre</string>
|
||||
<string name="button_reblog">Rebloguer</string>
|
||||
<string name="button_favorite">Ajouter aux favoris</string>
|
||||
<string name="button_share">Partager</string>
|
||||
<string name="media_no_description">Média sans description</string>
|
||||
<string name="add_media">Ajouter un média</string>
|
||||
<string name="add_poll">Ajouter un sondage</string>
|
||||
<string name="emoji">Émoji</string>
|
||||
<string name="post_visibility">Visibilité du message</string>
|
||||
<string name="home_timeline">Fil principal</string>
|
||||
<string name="my_profile">Mon profil</string>
|
||||
<string name="media_viewer">Visionneuse de média</string>
|
||||
<string name="follow_user">Suivre %s</string>
|
||||
<string name="unfollowed_user">Vous ne suivez plus %s</string>
|
||||
<string name="followed_user">Vous suivez maintenant %s</string>
|
||||
<string name="open_in_browser">Ouvrir dans le navigateur</string>
|
||||
<string name="hide_boosts_from_user">Masquer les partages de %s</string>
|
||||
<string name="show_boosts_from_user">Afficher les partages de %s</string>
|
||||
<string name="signup_reason">pourquoi voulez-vous vous inscrire ?</string>
|
||||
<string name="signup_reason_note">Cela nous aidera à examiner votre demande.</string>
|
||||
<string name="clear">Effacer</string>
|
||||
<string name="profile_header">Image d’en-tête</string>
|
||||
<string name="profile_picture">Image de profil</string>
|
||||
<string name="reorder">Réordonner</string>
|
||||
<string name="download">Télécharger</string>
|
||||
<string name="permission_required">Autorisation requise</string>
|
||||
<string name="storage_permission_to_download">L’application a besoin d’accéder à votre espace de stockage pour enregistrer ce fichier.</string>
|
||||
<string name="open_settings">Ouvrir les paramètres</string>
|
||||
<string name="error_saving_file">Erreur lors de l’enregistrement du fichier</string>
|
||||
<string name="file_saved">Fichier enregistré</string>
|
||||
<string name="downloading">Téléchargement…</string>
|
||||
<string name="no_app_to_handle_action">Aucune application ne permet de gérer ce type d\'action</string>
|
||||
</resources>
|
||||
|
||||
@@ -300,4 +300,6 @@
|
||||
<string name="error_saving_file">Produciuse un erro ao gardar o ficheiro</string>
|
||||
<string name="file_saved">Ficheiro gardado</string>
|
||||
<string name="downloading">Descargando…</string>
|
||||
<string name="no_app_to_handle_action">Non hai apps para esta acción</string>
|
||||
<string name="local_timeline">Comunidade</string>
|
||||
</resources>
|
||||
|
||||
@@ -59,6 +59,11 @@
|
||||
<string name="report_user">Prijavi %s</string>
|
||||
<string name="block_domain">Blokiraj %s</string>
|
||||
<string name="unblock_domain">Deblokiraj %s</string>
|
||||
<plurals name="x_posts">
|
||||
<item quantity="one">%,d objava</item>
|
||||
<item quantity="few">%,d objave</item>
|
||||
<item quantity="other">%,d objava</item>
|
||||
</plurals>
|
||||
<string name="profile_joined">Pridružen</string>
|
||||
<string name="done">Učinjeno</string>
|
||||
<string name="loading">Učitavanje…</string>
|
||||
@@ -66,4 +71,249 @@
|
||||
<string name="field_content">Sadržaj</string>
|
||||
<string name="saving">Snimam…</string>
|
||||
<string name="post_from_user">Post od %s</string>
|
||||
<string name="poll_option_hint">Opcija %d</string>
|
||||
<plurals name="x_minutes">
|
||||
<item quantity="one">%d minuta</item>
|
||||
<item quantity="few">%d minute</item>
|
||||
<item quantity="other">%d minuta</item>
|
||||
</plurals>
|
||||
<plurals name="x_hours">
|
||||
<item quantity="one">%d sat</item>
|
||||
<item quantity="few">%d sata</item>
|
||||
<item quantity="other">%d sati</item>
|
||||
</plurals>
|
||||
<plurals name="x_days">
|
||||
<item quantity="one">%d dan</item>
|
||||
<item quantity="few">%d dana</item>
|
||||
<item quantity="other">%d dana</item>
|
||||
</plurals>
|
||||
<string name="compose_poll_duration">Trajanje: %s</string>
|
||||
<plurals name="x_seconds_left">
|
||||
<item quantity="one">%d sekunda preostala</item>
|
||||
<item quantity="few">%d sekunde preostalo</item>
|
||||
<item quantity="other">%d sekundi preostalo</item>
|
||||
</plurals>
|
||||
<plurals name="x_minutes_left">
|
||||
<item quantity="one">%d minuta preostala</item>
|
||||
<item quantity="few">%d minute preostalo</item>
|
||||
<item quantity="other">%d minuta preostalo</item>
|
||||
</plurals>
|
||||
<plurals name="x_hours_left">
|
||||
<item quantity="one">%d sat preostalo</item>
|
||||
<item quantity="few">%d sata preostalo</item>
|
||||
<item quantity="other">%d sati preostalo</item>
|
||||
</plurals>
|
||||
<plurals name="x_days_left">
|
||||
<item quantity="one">%d dan preostalo</item>
|
||||
<item quantity="few">%d dana preostalo</item>
|
||||
<item quantity="other">%d dana preostalo</item>
|
||||
</plurals>
|
||||
<plurals name="x_voters">
|
||||
<item quantity="one">%,d glasač</item>
|
||||
<item quantity="few">%,d glasača</item>
|
||||
<item quantity="other">%,d glasača</item>
|
||||
</plurals>
|
||||
<string name="poll_closed">Završeno</string>
|
||||
<string name="confirm_mute_title">Utišaj Račun</string>
|
||||
<string name="confirm_mute">Potvrdi utišavanje %s</string>
|
||||
<string name="do_mute">Utišaj</string>
|
||||
<string name="confirm_unmute_title">Pojačaj Račun</string>
|
||||
<string name="confirm_unmute">Potvrdi pojačavanje %s</string>
|
||||
<string name="do_unmute">Pojačaj</string>
|
||||
<string name="confirm_block_title">Blokiraj Račun</string>
|
||||
<string name="confirm_block_domain_title">Blokiraj Domenu</string>
|
||||
<string name="confirm_block">Potvrdi blokiranje %s</string>
|
||||
<string name="do_block">Blokiraj</string>
|
||||
<string name="confirm_unblock_title">Uključi Račun</string>
|
||||
<string name="confirm_unblock_domain_title">Uključi Domenu</string>
|
||||
<string name="confirm_unblock">Potvrdi uključivanje %s</string>
|
||||
<string name="do_unblock">Uključi</string>
|
||||
<string name="button_muted">Utišan</string>
|
||||
<string name="button_blocked">Blokiran</string>
|
||||
<string name="action_vote">Glasaj</string>
|
||||
<string name="tap_to_reveal">Dodirni za otkrivanje</string>
|
||||
<string name="delete">Obriši</string>
|
||||
<string name="confirm_delete_title">Obriši objavu</string>
|
||||
<string name="confirm_delete">Jesi li siguran da želiš obrisati ovaj post?</string>
|
||||
<string name="deleting">Brisanje…</string>
|
||||
<string name="notification_channel_audio_player">Reprodukcija glazbe</string>
|
||||
<string name="play">Reproduciraj</string>
|
||||
<string name="pause">Pauziraj</string>
|
||||
<string name="log_out">Odjavi se</string>
|
||||
<string name="add_account">Dodaj račun</string>
|
||||
<string name="search_hint">Traži</string>
|
||||
<string name="hashtags">Hashtagovi</string>
|
||||
<string name="news">Vijesti</string>
|
||||
<string name="for_you">Za tebe</string>
|
||||
<string name="all_notifications">Sve</string>
|
||||
<string name="mentions">Spominjanja</string>
|
||||
<plurals name="x_people_talking">
|
||||
<item quantity="one">%d osoba govori</item>
|
||||
<item quantity="few">%d osobe govore</item>
|
||||
<item quantity="other">%d osoba govori</item>
|
||||
</plurals>
|
||||
<plurals name="discussed_x_times">
|
||||
<item quantity="one">Raspravljano %d put</item>
|
||||
<item quantity="few">Raspravljano %d puta</item>
|
||||
<item quantity="other">Raspravljano %d puta</item>
|
||||
</plurals>
|
||||
<string name="report_title">Prijavi %s</string>
|
||||
<string name="report_choose_reason">Što nije u redu s ovom objavom?</string>
|
||||
<string name="report_choose_reason_account">Što nije u redu s %s?</string>
|
||||
<string name="report_choose_reason_subtitle">Odaberi približan razlog</string>
|
||||
<string name="report_reason_personal">Ne sviđa mi se</string>
|
||||
<string name="report_reason_personal_subtitle">Nije ništa što želiš vidjeti</string>
|
||||
<string name="report_reason_spam">Spam je</string>
|
||||
<string name="report_reason_spam_subtitle">Zlonamjerne veze, lažni angažman ili odgovori koji se ponavljaju</string>
|
||||
<string name="report_reason_violation">Krši pravila instance</string>
|
||||
<string name="report_reason_violation_subtitle">Svjestan/a si da krši određena pravila</string>
|
||||
<string name="report_reason_other">Nešto drugo nije u redu</string>
|
||||
<string name="report_reason_other_subtitle">Problem ne spada u druge kategorije</string>
|
||||
<string name="report_choose_rule">Koja pravila se krše?</string>
|
||||
<string name="report_choose_rule_subtitle">Odaberi sve što je primjenjivo</string>
|
||||
<string name="report_choose_posts">Postoje li postovi koji podupiru ovu prijavu?</string>
|
||||
<string name="report_choose_posts_subtitle">Odaberi sve što je primjenjivo</string>
|
||||
<string name="report_comment_title">Postoji li još nešto što bismo trebali znati?</string>
|
||||
<string name="report_comment_hint">Dodatni komentari</string>
|
||||
<string name="sending_report">Slanje izvještaja…</string>
|
||||
<string name="report_sent_title">Hvala na prijavi, razmotrit ćemo ovo.</string>
|
||||
<string name="report_sent_subtitle">Dok mi ovo pregledavamo, možete poduzeti mjere protiv %s.</string>
|
||||
<string name="unfollow_user">Prestani pratiti %s</string>
|
||||
<string name="unfollow">Prestani pratiti</string>
|
||||
<string name="mute_user_explain">Nećeš vidjeti njihove postove ili reblogove u svom feedu na početnoj stranici. Neće znati da su utišani.</string>
|
||||
<string name="block_user_explain">Više te neće moći pratiti ili vidjeti tvoje objave, ali mogu vidjeti jesu li blokirani.</string>
|
||||
<string name="report_personal_title">Ne želiš vidjeti ovo?</string>
|
||||
<string name="report_personal_subtitle">Kada vidiš nešto što ti se ne sviđa na Mastodonu, možeš ukloniti osobu iz svog iskustva.</string>
|
||||
<string name="back">Natrag</string>
|
||||
<string name="instance_catalog_title">Mastodon čine korisnici različitih zajednica.</string>
|
||||
<string name="instance_catalog_subtitle">Odaberi zajednicu na temelju svojih interesa, regije ili zajednicu opće namjene. I dalje se možete povezati sa svima, bez obzira na zajednicu.</string>
|
||||
<string name="search_communities">Pretraži zajednice ili unesi URL</string>
|
||||
<string name="instance_rules_title">Neka osnovna pravila</string>
|
||||
<string name="instance_rules_subtitle">Uzmi minutu za čitanje pravila koje provode %s administratori.</string>
|
||||
<string name="signup_title">Hajmo te postaviti na %s</string>
|
||||
<string name="edit_photo">uredi</string>
|
||||
<string name="display_name">prikazano ime</string>
|
||||
<string name="username">korisničko ime</string>
|
||||
<string name="email">e-pošta</string>
|
||||
<string name="password">lozinka</string>
|
||||
<string name="password_note">Upotrijebi velika slova, posebne znakove i brojeve kako bi podigao jačinu tvoje lozinke.</string>
|
||||
<string name="category_academia">Akademija</string>
|
||||
<string name="category_activism">Aktivizam</string>
|
||||
<string name="category_all">Sve</string>
|
||||
<string name="category_art">Umjetnost</string>
|
||||
<string name="category_food">Hrana</string>
|
||||
<string name="category_furry">Krzneni</string>
|
||||
<string name="category_games">Igre</string>
|
||||
<string name="category_general">Općenito</string>
|
||||
<string name="category_journalism">Novinarstvo</string>
|
||||
<string name="category_lgbt">LGBT</string>
|
||||
<string name="category_music">Glazba</string>
|
||||
<string name="category_regional">Lokalno</string>
|
||||
<string name="category_tech">Tehnologija</string>
|
||||
<string name="confirm_email_title">Još jedna stvar</string>
|
||||
<string name="confirm_email_subtitle">Dodirni vezu koju smo ti poslali e-poštom da potvrdiš svoj račun.</string>
|
||||
<string name="resend">Ponovno pošalji</string>
|
||||
<string name="open_email_app">Otvori aplikaciju za e-poštu</string>
|
||||
<string name="resent_email">Poslana je e-mail potvrda</string>
|
||||
<string name="compose_hint">Upiši ili zalijepi ono što ti je na umu</string>
|
||||
<string name="content_warning">Upozorenje za sadržaj</string>
|
||||
<string name="add_image_description">Dodaj opis slike…</string>
|
||||
<string name="retry_upload">Ponovi prijenos</string>
|
||||
<string name="image_upload_failed">Prijenos slike nije uspio</string>
|
||||
<string name="video_upload_failed">Prijenos videa nije uspio</string>
|
||||
<string name="edit_image">Uredi sliku</string>
|
||||
<string name="save">Spremi</string>
|
||||
<string name="add_alt_text">Dodaj altenativni tekst</string>
|
||||
<string name="alt_text_subtitle">Alternativni tekst opisuje vaše fotografije za osobe lošeg vida ili bez vida. Pokušajte uključiti samo dovoljno detalja da biste razumjeli kontekst.</string>
|
||||
<string name="alt_text_hint">npr. Pas koji sumnjičavo gleda okolo suženih očiju u kameru.</string>
|
||||
<string name="visibility_public">Javno</string>
|
||||
<string name="visibility_followers_only">Samo pratitelji</string>
|
||||
<string name="visibility_private">Samo ljudi koje spominjem</string>
|
||||
<string name="search_all">Sve</string>
|
||||
<string name="search_people">Ljudi</string>
|
||||
<string name="recent_searches">Nedavne pretrage</string>
|
||||
<string name="step_x_of_n">Korak %1$d od %2$d</string>
|
||||
<string name="skip">Preskoči</string>
|
||||
<string name="notification_type_follow">Novi pratitelji</string>
|
||||
<string name="notification_type_favorite">Favoriti</string>
|
||||
<string name="notification_type_reblog">Boostovi</string>
|
||||
<string name="notification_type_mention">Spominjanja</string>
|
||||
<string name="notification_type_poll">Ankete</string>
|
||||
<string name="choose_account">Odaberi račun</string>
|
||||
<string name="err_not_logged_in">Molimo te da se prvo prijaviš na Mastodon</string>
|
||||
<plurals name="cant_add_more_than_x_attachments">
|
||||
<item quantity="one">Ne možeš dodati više od %d medijskog privitka</item>
|
||||
<item quantity="few">Ne možeš dodati više od %d medijska privitka</item>
|
||||
<item quantity="other">Ne možeš dodati više od %d medijskih privitaka</item>
|
||||
</plurals>
|
||||
<string name="media_attachment_unsupported_type">Datoteka %s je nepodržane vrste</string>
|
||||
<string name="media_attachment_too_big">Datoteka %1$s premašuje ograničenje veličine od %2$s MB</string>
|
||||
<string name="settings_theme">Vizualni izgled</string>
|
||||
<string name="theme_auto">Automatski</string>
|
||||
<string name="theme_light">Svijetla</string>
|
||||
<string name="theme_dark">Tamna</string>
|
||||
<string name="theme_true_black">Crna</string>
|
||||
<string name="settings_behavior">Ponašanje</string>
|
||||
<string name="settings_gif">Animiraj avatare i emotikone</string>
|
||||
<string name="settings_custom_tabs">Koristi interni preglednik</string>
|
||||
<string name="settings_notifications">Obavijesti</string>
|
||||
<string name="notify_me_when">Obavijesti me kada</string>
|
||||
<string name="notify_anyone">bilo tko</string>
|
||||
<string name="notify_follower">pratitelj</string>
|
||||
<string name="notify_followed">netko koga pratim</string>
|
||||
<string name="notify_none">nitko</string>
|
||||
<string name="notify_favorites">Favorizira moj post</string>
|
||||
<string name="notify_follow">Prati me</string>
|
||||
<string name="notify_reblog">Rebloga moju objavu</string>
|
||||
<string name="notify_mention">Spominje me</string>
|
||||
<string name="settings_boring">Dosadna zona</string>
|
||||
<string name="settings_account">Postavke računa</string>
|
||||
<string name="settings_contribute">Doprinesi Mastodonu</string>
|
||||
<string name="settings_tos">Uvjeti pružanja usluga</string>
|
||||
<string name="settings_privacy_policy">Politika privatnosti</string>
|
||||
<string name="settings_spicy">Začinjena zona</string>
|
||||
<string name="settings_clear_cache">Izbrišite predmemoriju medija</string>
|
||||
<string name="settings_app_version">Mastodon za Android v%1$s (%2$d)</string>
|
||||
<string name="media_cache_cleared">Medijska predmemorija je očišćena</string>
|
||||
<string name="confirm_log_out">Jeste li siguran da se želiš odjaviti?</string>
|
||||
<string name="sensitive_content">Osjetljiv sadržaj</string>
|
||||
<string name="sensitive_content_explain">Autor je ovaj medij označio osjetljivim. Dodirni za otkrivanje.</string>
|
||||
<string name="media_hidden">Dodirni za otkrivanje</string>
|
||||
<string name="avatar_description">Idi na %s profil</string>
|
||||
<string name="more_options">Više opcija</string>
|
||||
<string name="reveal_content">Otkrij sadržaj</string>
|
||||
<string name="hide_content">Sakrij sadržaj</string>
|
||||
<string name="new_post">Nova objava</string>
|
||||
<string name="button_reply">Odgovori</string>
|
||||
<string name="button_reblog">Reblog</string>
|
||||
<string name="button_favorite">Favorit</string>
|
||||
<string name="button_share">Podijeli</string>
|
||||
<string name="media_no_description">Medij bez opisa</string>
|
||||
<string name="add_media">Dodaj multimediju</string>
|
||||
<string name="add_poll">Dodaj anketu</string>
|
||||
<string name="emoji">Emotikoni</string>
|
||||
<string name="post_visibility">Vidljivost objave</string>
|
||||
<string name="home_timeline">Lokalna vremenska crta</string>
|
||||
<string name="my_profile">Moj profil</string>
|
||||
<string name="media_viewer">Preglednik medija</string>
|
||||
<string name="follow_user">Prati %s</string>
|
||||
<string name="unfollowed_user">Otpratio si %s</string>
|
||||
<string name="followed_user">Sada pratiš %s</string>
|
||||
<string name="open_in_browser">Otvori u pregledniku</string>
|
||||
<string name="hide_boosts_from_user">Sakrij boostove od %s</string>
|
||||
<string name="show_boosts_from_user">Prikaži boostove od %s</string>
|
||||
<string name="signup_reason">zašto se želiš pridružiti?</string>
|
||||
<string name="signup_reason_note">Ovo će nam pomoći da razmotrimo vašu prijavu.</string>
|
||||
<string name="clear">Poništi</string>
|
||||
<string name="profile_header">Slika zaglavlja</string>
|
||||
<string name="profile_picture">Profilna slika</string>
|
||||
<string name="reorder">Promjeni redoslijed</string>
|
||||
<string name="download">Preuzmi</string>
|
||||
<string name="permission_required">Potrebno je dopuštenje</string>
|
||||
<string name="storage_permission_to_download">Aplikacija treba pristup vašoj pohrani za spremanje ove datoteke.</string>
|
||||
<string name="open_settings">Otvori postavke</string>
|
||||
<string name="error_saving_file">Greška prilikom spremanja datoteke</string>
|
||||
<string name="file_saved">Datoteka spremljena</string>
|
||||
<string name="downloading">Preuzimanje…</string>
|
||||
<string name="no_app_to_handle_action">Ne postoji aplikacija za rukovanje ovom radnjom</string>
|
||||
</resources>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<string name="media">Media</string>
|
||||
<string name="profile_about">Info</string>
|
||||
<string name="button_follow">Segui</string>
|
||||
<string name="button_following">Seguiti</string>
|
||||
<string name="button_following">Stai seguendo</string>
|
||||
<string name="edit_profile">Modifica profilo</string>
|
||||
<string name="mention_user">Menziona %s</string>
|
||||
<string name="share_user">Condividi %s</string>
|
||||
@@ -300,4 +300,6 @@
|
||||
<string name="error_saving_file">Errore durante il salvataggio del file</string>
|
||||
<string name="file_saved">File salvato</string>
|
||||
<string name="downloading">Scaricamento…</string>
|
||||
<string name="no_app_to_handle_action">Impossibile trovare applicazioni per gestire quest\'azione</string>
|
||||
<string name="local_timeline">Comunità</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,88 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="app_name">Mastodon</string>
|
||||
<string name="get_started">Per començar</string>
|
||||
<string name="log_in">Se connectar</string>
|
||||
<string name="next">Seguent</string>
|
||||
<string name="error">Error</string>
|
||||
<string name="ok">D’acòrdi</string>
|
||||
<string name="user_boosted">%s a partejat</string>
|
||||
<string name="in_reply_to">En responsa a %s</string>
|
||||
<string name="notifications">Notificacions</string>
|
||||
<string name="user_followed_you">vos sèc</string>
|
||||
<string name="share_toot_title">Partejar</string>
|
||||
<string name="settings">Paramètres</string>
|
||||
<string name="publish">Publicar</string>
|
||||
<string name="discard_draft">Suprimir lo borrolhon ?</string>
|
||||
<string name="discard">Ignorar</string>
|
||||
<string name="cancel">Anullar</string>
|
||||
<plurals name="followers">
|
||||
<item quantity="one">seguidor</item>
|
||||
<item quantity="other">seguidors</item>
|
||||
</plurals>
|
||||
<plurals name="following">
|
||||
<item quantity="one">abonament</item>
|
||||
<item quantity="other">abonaments</item>
|
||||
</plurals>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">publicacion</item>
|
||||
<item quantity="other">publicacions</item>
|
||||
</plurals>
|
||||
<string name="posts">Publicacions</string>
|
||||
<string name="posts_and_replies">Publicacions e responsas</string>
|
||||
<string name="media">Mèdias</string>
|
||||
<string name="profile_about">A prepaus</string>
|
||||
<string name="button_follow">Seguir</string>
|
||||
<string name="edit_profile">Modificar lo perfil</string>
|
||||
<string name="mention_user">Mencionar %s</string>
|
||||
<string name="share_user">Partejar %s</string>
|
||||
<string name="mute_user">Amudar %s</string>
|
||||
<string name="block_domain">Blocar %s</string>
|
||||
<string name="search_hint">Recercar</string>
|
||||
<string name="hashtags">Etiquetas</string>
|
||||
<string name="news">Novèls</string>
|
||||
<string name="mentions">Mencions</string>
|
||||
<string name="back">Tornar</string>
|
||||
<string name="edit_photo">modificar</string>
|
||||
<string name="display_name">escais</string>
|
||||
<string name="username">nom d’utilizaire</string>
|
||||
<string name="email">email</string>
|
||||
<string name="password">senhal</string>
|
||||
<string name="category_music">Musica</string>
|
||||
<string name="category_tech">Tecnologia</string>
|
||||
<string name="confirm_email_title">Una darrièra causa</string>
|
||||
<string name="resend">Tornar enviar</string>
|
||||
<string name="content_warning">Avís de contengut</string>
|
||||
<string name="add_image_description">Apondre una descripcion d’imatge…</string>
|
||||
<string name="save">Enregistrar</string>
|
||||
<string name="skip">Sautar</string>
|
||||
<string name="notification_type_favorite">Favorits</string>
|
||||
<string name="notification_type_reblog">Partages</string>
|
||||
<string name="notification_type_mention">Mencions</string>
|
||||
<string name="notification_type_poll">Sondatge</string>
|
||||
<string name="choose_account">Seleccionar un compte</string>
|
||||
<string name="theme_auto">Automatic</string>
|
||||
<string name="theme_light">Clar</string>
|
||||
<string name="theme_dark">Escur</string>
|
||||
<string name="settings_behavior">Compòrtament</string>
|
||||
<string name="settings_notifications">Notificacions</string>
|
||||
<string name="notify_me_when">Me notificar quand</string>
|
||||
<string name="settings_account">Paramètres de compte</string>
|
||||
<string name="sensitive_content">Contengut sensible</string>
|
||||
<string name="media_hidden">Tocar per afichar</string>
|
||||
<string name="avatar_description">Anar al perfil de %s</string>
|
||||
<string name="more_options">Mai d\'opcions</string>
|
||||
<string name="reveal_content">Afichar lo contengut</string>
|
||||
<string name="hide_content">Amagar lo contengut</string>
|
||||
<string name="new_post">Publicacion novèla</string>
|
||||
<string name="button_reply">Respondre</string>
|
||||
<string name="button_favorite">Aimar</string>
|
||||
<string name="button_share">Partejar</string>
|
||||
<string name="my_profile">Mon perfil</string>
|
||||
<string name="follow_user">Sègre %s</string>
|
||||
<string name="unfollowed_user">Quitar de sègre %s</string>
|
||||
<string name="open_settings">Dobrir los paramètres</string>
|
||||
<string name="error_saving_file">Error en enregistrant lo fichièr</string>
|
||||
<string name="file_saved">Fichièr salvagardat</string>
|
||||
<string name="downloading">Telecargament…</string>
|
||||
</resources>
|
||||
|
||||
@@ -110,9 +110,12 @@
|
||||
<string name="report_sent_subtitle">W trakcie gdy będziemy się przyglądać tej sprawie, możesz podjąć działania dotyczące %s.</string>
|
||||
<string name="unfollow_user">Przestań śledzić %s</string>
|
||||
<string name="unfollow">Przestań śledzić</string>
|
||||
<string name="mute_user_explain">Nie będziesz widzieć wpisów ani podbić tego użytkownika na stronie głównej. Ta osoba nie dowie się, że została wyciszona.</string>
|
||||
<string name="block_user_explain">Ta osoba nie będzie mogła mogła Cię śledzić ani widzieć Twoich wpisów, ale może dowiedzieć się, że została zablokowana.</string>
|
||||
<string name="report_personal_title">Nie chcesz tego widzieć?</string>
|
||||
<string name="back">Wróć</string>
|
||||
<string name="instance_catalog_title">Mastodon składa się z użytkowników w różnych społecznościach.</string>
|
||||
<string name="instance_catalog_subtitle">Wybierz społeczność według swoich zainteresowań, regionu lub ogólnotematyczną. Możesz pozostać w kontakcie z innymi, niezależnie od wybranej społeczności.</string>
|
||||
<string name="search_communities">Szukaj społeczności lub wprowadź adres URL</string>
|
||||
<string name="instance_rules_subtitle">Poświęć chwilę, aby przejrzeć reguły ustalone i realizowane przez administratorów %s.</string>
|
||||
<string name="edit_photo">edytuj</string>
|
||||
@@ -120,6 +123,7 @@
|
||||
<string name="username">nazwa użytkownika</string>
|
||||
<string name="email">adres e-mail</string>
|
||||
<string name="password">hasło</string>
|
||||
<string name="password_note">Użyj wielkich liter, znaków specjalnych i liczb, aby zwiększyć siłę swojego hasła.</string>
|
||||
<string name="category_academia">Środowiska akademickie</string>
|
||||
<string name="category_activism">Aktywizm</string>
|
||||
<string name="category_all">Wszystkie</string>
|
||||
@@ -138,6 +142,7 @@
|
||||
<string name="resend">Wyślij ponownie</string>
|
||||
<string name="open_email_app">Otwórz aplikację e-mail</string>
|
||||
<string name="resent_email">Wysłano potwierdzający e-mail</string>
|
||||
<string name="compose_hint">Wpisz lub wklej co masz na myśli</string>
|
||||
<string name="content_warning">Ostrzeżenie o zawartości</string>
|
||||
<string name="add_image_description">Dodaj opis obrazu…</string>
|
||||
<string name="retry_upload">Ponów wysyłanie</string>
|
||||
@@ -147,6 +152,7 @@
|
||||
<string name="save">Zapisz</string>
|
||||
<string name="add_alt_text">Dodaj tekst alternatywny</string>
|
||||
<string name="alt_text_subtitle">Tekst alternatywny opisuje zdjęcia dla osób niewidomych lub niedowidzących. Spróbuj opisać tylko tyle, ile potrzeba aby zrozumieć kontekst.</string>
|
||||
<string name="alt_text_hint">np. pies rozglądający się podejrzliwie wokół kamery z przymrużonymi oczami.</string>
|
||||
<string name="visibility_public">Publiczny</string>
|
||||
<string name="visibility_followers_only">Tylko dla śledzących</string>
|
||||
<string name="visibility_private">Tylko dla wspomnianych</string>
|
||||
@@ -231,4 +237,5 @@
|
||||
<string name="error_saving_file">Błąd podczas zapisywania pliku</string>
|
||||
<string name="file_saved">Zapisano plik</string>
|
||||
<string name="downloading">Pobieranie…</string>
|
||||
<string name="no_app_to_handle_action">Brak aplikacji mogącej obsłużyć tę akcję</string>
|
||||
</resources>
|
||||
|
||||
@@ -111,12 +111,15 @@
|
||||
<string name="report_sent_title">Obrigado por reportar. Vamos analisar.</string>
|
||||
<string name="report_sent_subtitle">Enquanto revisamos isso, você pode tomar medidas contra @%s.</string>
|
||||
<string name="unfollow_user">Deixar de seguir @%s</string>
|
||||
<string name="back">Voltar</string>
|
||||
<string name="search_communities">Pesquise comunidades ou insira URL</string>
|
||||
<string name="edit_photo">editar</string>
|
||||
<string name="display_name">nome de exibição</string>
|
||||
<string name="username">nome de usuário</string>
|
||||
<string name="email">e-mail</string>
|
||||
<string name="password">senha</string>
|
||||
<string name="category_activism">Ativismo</string>
|
||||
<string name="category_all">Todas</string>
|
||||
<string name="category_art">Arte</string>
|
||||
<string name="category_food">Comida</string>
|
||||
<string name="category_furry">Furry</string>
|
||||
@@ -144,6 +147,7 @@
|
||||
<string name="visibility_followers_only">Apenas seguidores</string>
|
||||
<string name="visibility_private">Apenas pessoas que menciono</string>
|
||||
<string name="search_all">Todos</string>
|
||||
<string name="search_people">Pessoas</string>
|
||||
<string name="recent_searches">Pesquisas recentes</string>
|
||||
<string name="skip">Pular</string>
|
||||
<string name="notification_type_follow">Novos seguidores</string>
|
||||
|
||||
@@ -1,2 +1,62 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="app_name">Mastodon</string>
|
||||
<string name="get_started">Iniciar</string>
|
||||
<string name="log_in">Entrar</string>
|
||||
<string name="next">Seguinte</string>
|
||||
<string name="loading_instance">A obter informações da instância…</string>
|
||||
<string name="error">Erro</string>
|
||||
<string name="not_a_mastodon_instance">%s não parece ser uma instância de Mastodon.</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="preparing_auth">A preparar a autenticação…</string>
|
||||
<string name="finishing_auth">A finalizar a autenticação…</string>
|
||||
<string name="user_boosted">%s impulsionado</string>
|
||||
<string name="in_reply_to">Responder a %s</string>
|
||||
<string name="notifications">Notificações</string>
|
||||
<string name="user_followed_you">seguiu-te</string>
|
||||
<string name="user_sent_follow_request">pediu para te seguir</string>
|
||||
<string name="user_favorited">adicionou a tua publicação aos favoritos</string>
|
||||
<string name="notification_boosted">partilhou a tua publicação</string>
|
||||
<string name="poll_ended">sondagem terminada</string>
|
||||
<string name="share_toot_title">Partilhar</string>
|
||||
<string name="settings">Configurações</string>
|
||||
<string name="publish">Publicar</string>
|
||||
<string name="discard_draft">Descartar o rascunho?</string>
|
||||
<string name="discard">Descartar</string>
|
||||
<string name="cancel">Cancelar</string>
|
||||
<plurals name="followers">
|
||||
<item quantity="one">seguidor</item>
|
||||
<item quantity="other">seguidores</item>
|
||||
</plurals>
|
||||
<plurals name="following">
|
||||
<item quantity="one">a seguir</item>
|
||||
<item quantity="other">a seguir</item>
|
||||
</plurals>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">publicação</item>
|
||||
<item quantity="other">publicações</item>
|
||||
</plurals>
|
||||
<string name="posts">Publicações</string>
|
||||
<string name="posts_and_replies">Publicações e respostas</string>
|
||||
<string name="media">Multimédia</string>
|
||||
<string name="profile_about">Sobre</string>
|
||||
<string name="button_follow">Seguir</string>
|
||||
<string name="button_following">A seguir</string>
|
||||
<string name="edit_profile">Editar Perfil</string>
|
||||
<string name="mention_user">Mencionar @%s</string>
|
||||
<string name="share_user">Partilhar %s</string>
|
||||
<string name="mute_user">Silenciar %s</string>
|
||||
<string name="unmute_user">Deixar de silenciar @%s</string>
|
||||
<string name="block_user">Bloquear %s</string>
|
||||
<string name="unblock_user">Desbloquear @%s</string>
|
||||
<string name="report_user">Denunciar %s</string>
|
||||
<string name="block_domain">Bloquear %s</string>
|
||||
<string name="unblock_domain">Desbloquear @%s</string>
|
||||
<plurals name="x_posts">
|
||||
<item quantity="one">%,d publicação</item>
|
||||
<item quantity="other">%,d publicações</item>
|
||||
</plurals>
|
||||
<string name="profile_joined">Juntou-se a</string>
|
||||
<string name="done">Concluído</string>
|
||||
<string name="for_you">Para si</string>
|
||||
</resources>
|
||||
|
||||
@@ -160,10 +160,16 @@
|
||||
<string name="all_notifications">Все</string>
|
||||
<string name="mentions">Упоминания</string>
|
||||
<plurals name="x_people_talking">
|
||||
<item quantity="one">%d человек разговаривает</item>
|
||||
<item quantity="few">%d человека разговаривают</item>
|
||||
<item quantity="many">%d людей разговаривают</item>
|
||||
<item quantity="other">%d людей разговаривают</item>
|
||||
<item quantity="one">%d человек обсуждает</item>
|
||||
<item quantity="few">%d человека обсуждают</item>
|
||||
<item quantity="many">%d человек обсуждают</item>
|
||||
<item quantity="other">%d человека обсуждает</item>
|
||||
</plurals>
|
||||
<plurals name="discussed_x_times">
|
||||
<item quantity="one">Обсуждалась %d раз</item>
|
||||
<item quantity="few">Обсуждалась %d раза</item>
|
||||
<item quantity="many">Обсуждалась %d раз</item>
|
||||
<item quantity="other">Обсуждалась %d раза</item>
|
||||
</plurals>
|
||||
<string name="report_title">Пожаловаться на %s</string>
|
||||
<string name="report_choose_reason">Что не так с этим постом?</string>
|
||||
@@ -189,6 +195,7 @@
|
||||
<string name="unfollow_user">Отписаться от %s</string>
|
||||
<string name="unfollow">Отписаться</string>
|
||||
<string name="mute_user_explain">Вы перестаните видеть посты и продвижения этого пользователя в домашней ленте. Он(а) не узнает, что вы игнорируете его/её.</string>
|
||||
<string name="block_user_explain">Этот пользователь больше не сможет подписаться на вас или читать ваши посты. Он(а) сможет видеть, что заблокирован(а).</string>
|
||||
<string name="report_personal_title">Не хотите видеть это?</string>
|
||||
<string name="report_personal_subtitle">Если кто-то в Mastodon публикует то, что вам не нравится, вы можете скрыть его.</string>
|
||||
<string name="back">Назад</string>
|
||||
@@ -218,6 +225,7 @@
|
||||
<string name="category_regional">Региональные</string>
|
||||
<string name="category_tech">Технологии</string>
|
||||
<string name="confirm_email_title">И ещё кое-что</string>
|
||||
<string name="confirm_email_subtitle">Мы отправили вам письмо со специальной ссылкой. Нажмите её, чтобы подтвердить свою учётную запись.</string>
|
||||
<string name="resend">Отправить заново</string>
|
||||
<string name="open_email_app">Открыть почту</string>
|
||||
<string name="resent_email">Письмо подтверждения отправлено</string>
|
||||
@@ -234,7 +242,7 @@
|
||||
<string name="alt_text_hint">Пример: собака с подозрительным прищуром глядит в камеру.</string>
|
||||
<string name="visibility_public">Публичный</string>
|
||||
<string name="visibility_followers_only">Для подписчиков</string>
|
||||
<string name="visibility_private">Только для упомянутых людей</string>
|
||||
<string name="visibility_private">Для упомянутых</string>
|
||||
<string name="search_all">Все</string>
|
||||
<string name="search_people">Люди</string>
|
||||
<string name="recent_searches">Последние запросы</string>
|
||||
@@ -270,7 +278,7 @@
|
||||
<string name="notify_followed">любой, на кого я подписан(а)</string>
|
||||
<string name="notify_none">никто</string>
|
||||
<string name="notify_favorites">Добавляет мой пост в избранное</string>
|
||||
<string name="notify_follow">Подписался на меня</string>
|
||||
<string name="notify_follow">Подписывается на меня</string>
|
||||
<string name="notify_reblog">Продвигает мой пост</string>
|
||||
<string name="notify_mention">Упоминает меня</string>
|
||||
<string name="settings_boring">Зона скукотищи</string>
|
||||
@@ -278,7 +286,7 @@
|
||||
<string name="settings_contribute">Внести вклад в Mastodon</string>
|
||||
<string name="settings_tos">Условия использования</string>
|
||||
<string name="settings_privacy_policy">Политика конфиденциальности</string>
|
||||
<string name="settings_spicy">Пикантная зона</string>
|
||||
<string name="settings_spicy">Зона остряка</string>
|
||||
<string name="settings_clear_cache">Очистить кэш медиа</string>
|
||||
<string name="settings_app_version">Mastodon для Android v%1$s (%2$d)</string>
|
||||
<string name="media_cache_cleared">Кэш медиа очищен</string>
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
<string name="share_toot_title">Paylaş</string>
|
||||
<string name="settings">Ayarlar</string>
|
||||
<string name="publish">Yayınla</string>
|
||||
<string name="discard_draft">Taslak temizlensin mi?</string>
|
||||
<string name="discard">Temizle</string>
|
||||
<string name="discard_draft">Taslak silinsin mi?</string>
|
||||
<string name="discard">Sil</string>
|
||||
<string name="cancel">İptal</string>
|
||||
<plurals name="followers">
|
||||
<item quantity="one">Takipçi</item>
|
||||
@@ -65,7 +65,7 @@
|
||||
<string name="loading">Yükleniyor...</string>
|
||||
<string name="field_label">Etiket</string>
|
||||
<string name="field_content">İçerik</string>
|
||||
<string name="saving">Kayıt ediliyor...</string>
|
||||
<string name="saving">Kaydediliyor…</string>
|
||||
<string name="post_from_user">Gönderen %s</string>
|
||||
<string name="poll_option_hint">Seçenek %d</string>
|
||||
<plurals name="x_minutes">
|
||||
@@ -105,9 +105,9 @@
|
||||
<string name="confirm_mute_title">Hesabı sustur</string>
|
||||
<string name="confirm_mute">%s susturmak için onaylayın</string>
|
||||
<string name="do_mute">Sustur</string>
|
||||
<string name="confirm_unmute_title">Hesabın sesini aç</string>
|
||||
<string name="confirm_unmute">%s Sesi açmak için onaylayın</string>
|
||||
<string name="do_unmute">Sesini aç</string>
|
||||
<string name="confirm_unmute_title">Susturmayı kaldır</string>
|
||||
<string name="confirm_unmute">%s susturmasını kaldırmak için onaylayın</string>
|
||||
<string name="do_unmute">Susturmayı kaldır</string>
|
||||
<string name="confirm_block_title">Hesabı Engelle</string>
|
||||
<string name="confirm_block_domain_title">Alan adını engelle</string>
|
||||
<string name="confirm_block">%s engellemeyi onayla</string>
|
||||
@@ -134,7 +134,7 @@
|
||||
<string name="news">Haberler</string>
|
||||
<string name="for_you">Senin için</string>
|
||||
<string name="all_notifications">Hepsi</string>
|
||||
<string name="mentions">Behsetmeler</string>
|
||||
<string name="mentions">Bahsetmeler</string>
|
||||
<plurals name="x_people_talking">
|
||||
<item quantity="one">%d kişi bahsetti</item>
|
||||
<item quantity="other">%d kişi bahsetti</item>
|
||||
@@ -153,7 +153,7 @@
|
||||
<string name="report_reason_spam_subtitle">Kötü amaçlı bağlantılar, sahte etkileşim veya tekrarlayan yanıtlar</string>
|
||||
<string name="report_reason_violation">Sunucu kurallarını ihlal ediyor</string>
|
||||
<string name="report_reason_violation_subtitle">Belirli kuralları çiğnediğinin farkındasınız</string>
|
||||
<string name="report_reason_other">Bu başka bir şey</string>
|
||||
<string name="report_reason_other">Başka bir şey</string>
|
||||
<string name="report_reason_other_subtitle">Sorun diğer kategorilere uymuyor</string>
|
||||
<string name="report_choose_rule">Hangi kurallar ihlal ediliyor?</string>
|
||||
<string name="report_choose_rule_subtitle">Geçerli olanların tümünü seçin</string>
|
||||
@@ -181,7 +181,7 @@
|
||||
<string name="display_name">görünen ad</string>
|
||||
<string name="username">kullanıcı adı</string>
|
||||
<string name="email">e-posta</string>
|
||||
<string name="password">şifre</string>
|
||||
<string name="password">parola</string>
|
||||
<string name="password_note">Parolanızın gücünü artırmak için büyük harfler, özel karakterler ve sayılar ekleyin.</string>
|
||||
<string name="category_academia">Akademi</string>
|
||||
<string name="category_activism">aktivizm</string>
|
||||
@@ -195,7 +195,7 @@
|
||||
<string name="category_lgbt">LGBT</string>
|
||||
<string name="category_music">Müzik</string>
|
||||
<string name="category_regional">Bölgesel</string>
|
||||
<string name="category_tech">teknoloji</string>
|
||||
<string name="category_tech">Teknoloji</string>
|
||||
<string name="confirm_email_title">Son bir şey</string>
|
||||
<string name="confirm_email_subtitle">Hesabınızı doğrulamak için size e-postayla gönderdiğimiz bağlantıya dokunun.</string>
|
||||
<string name="resend">Yeniden gönder</string>
|
||||
@@ -211,11 +211,11 @@
|
||||
<string name="save">Kaydet</string>
|
||||
<string name="add_alt_text">Alternatif metin ekle</string>
|
||||
<string name="alt_text_subtitle">Alternatif metin, az gören veya hiç görmeyen kişiler için fotoğraflarınızı açıklar. Yalnızca bağlamı anlamak için yeterli bilgiyi eklemeyi çalışın.</string>
|
||||
<string name="alt_text_hint">Örneğin. Kameraya kısılmış gözlerle şüpheyle bakan bir köpek.</string>
|
||||
<string name="visibility_public">Genel</string>
|
||||
<string name="alt_text_hint">Örnek: Kameraya kısılmış gözler ve şüpheyle bakan bir köpek fotoğrafı.</string>
|
||||
<string name="visibility_public">Herkese açık</string>
|
||||
<string name="visibility_followers_only">Yalnızca takipçiler</string>
|
||||
<string name="visibility_private">Sadece bahsettiğim insanlar</string>
|
||||
<string name="search_all">Her şey</string>
|
||||
<string name="search_all">Tümü</string>
|
||||
<string name="search_people">İnsanlar</string>
|
||||
<string name="recent_searches">Son aramalar</string>
|
||||
<string name="step_x_of_n">Adım %1$d/%2$d</string>
|
||||
@@ -233,30 +233,30 @@
|
||||
</plurals>
|
||||
<string name="media_attachment_unsupported_type">%s dosyası desteklenmeyen türde</string>
|
||||
<string name="media_attachment_too_big">%1$s dosyası, %2$s MB\'lik boyut sınırını aşıyor</string>
|
||||
<string name="settings_theme">Görünüm</string>
|
||||
<string name="settings_theme">Uygulama görünümü</string>
|
||||
<string name="theme_auto">Otomatik</string>
|
||||
<string name="theme_light">Aydınlık</string>
|
||||
<string name="theme_dark">Karanlık</string>
|
||||
<string name="theme_true_black">Gerçek siyah mod</string>
|
||||
<string name="settings_behavior">Davranış</string>
|
||||
<string name="settings_behavior">Ayarlar</string>
|
||||
<string name="settings_gif">Animasyonlu avatarları ve emojileri oynat</string>
|
||||
<string name="settings_custom_tabs">Uygulama içi tarayıcıyı kullan</string>
|
||||
<string name="settings_notifications">Bildirimler</string>
|
||||
<string name="notify_me_when">Ne zaman haber ver</string>
|
||||
<string name="notify_anyone">herkes</string>
|
||||
<string name="notify_follower">takipçim</string>
|
||||
<string name="notify_followed">takip ettiğim</string>
|
||||
<string name="notify_none">hiçkimse</string>
|
||||
<string name="notify_favorites">Gönderim favorilendiğinde</string>
|
||||
<string name="notify_follow">Takipçi geldiğinde</string>
|
||||
<string name="notify_reblog">Gönderi desteklendiğinde</string>
|
||||
<string name="notify_mention">Benden bahsedildiğinde</string>
|
||||
<string name="notify_me_when">Beni şu durumda bilgilendir: </string>
|
||||
<string name="notify_anyone">herhangi biri</string>
|
||||
<string name="notify_follower">bir takipçim</string>
|
||||
<string name="notify_followed">takip ettiğim biri</string>
|
||||
<string name="notify_none">bilgilendirme</string>
|
||||
<string name="notify_favorites">Gönderiğimi beğendiğinde</string>
|
||||
<string name="notify_follow">Beni takip ettiğinde</string>
|
||||
<string name="notify_reblog">Gönderimi yeniden paylaştığında</string>
|
||||
<string name="notify_mention">Benden bahsettiğinde</string>
|
||||
<string name="settings_boring">Sıkıcı bölge</string>
|
||||
<string name="settings_account">Hesap ayarları</string>
|
||||
<string name="settings_contribute">Mastodon\'a katkıda bulunun</string>
|
||||
<string name="settings_tos">Kullanım Şartları</string>
|
||||
<string name="settings_privacy_policy">Gizlilik Politikası</string>
|
||||
<string name="settings_spicy">kırmızı bölge</string>
|
||||
<string name="settings_spicy">Tehlikeli bölge</string>
|
||||
<string name="settings_clear_cache">Medya önbelleğini temizle</string>
|
||||
<string name="settings_app_version">Android için Mastodon v%1$s (%2$d)</string>
|
||||
<string name="media_cache_cleared">Medya önbelleği temizlendi</string>
|
||||
@@ -285,8 +285,8 @@
|
||||
<string name="unfollowed_user">%s takip edilmedi</string>
|
||||
<string name="followed_user">%s\'i takip ediyorsunuz</string>
|
||||
<string name="open_in_browser">Tarayıcıda aç</string>
|
||||
<string name="hide_boosts_from_user">%s tarafından sağlanan desteklemeleri gizle</string>
|
||||
<string name="show_boosts_from_user">%s tarafından sağlanan desteklemeleri göster</string>
|
||||
<string name="hide_boosts_from_user">%s kişisinin alıntı paylaşımlarını gizle</string>
|
||||
<string name="show_boosts_from_user">%s kişisinin alıntı paylaşımlarını göster</string>
|
||||
<string name="signup_reason">neden katılmak istiyorsun?</string>
|
||||
<string name="signup_reason_note">Bu, başvurunuzu incelememize yardımcı olacaktır.</string>
|
||||
<string name="clear">Temizle</string>
|
||||
@@ -300,4 +300,6 @@
|
||||
<string name="error_saving_file">Dosya kaydedilirken hata oluştu</string>
|
||||
<string name="file_saved">Dosya kaydedildi</string>
|
||||
<string name="downloading">İndiriliyor…</string>
|
||||
<string name="no_app_to_handle_action">Bu eylemi gerçekleştirebilecek bir uygulama bulunmuyor</string>
|
||||
<string name="local_timeline">Topluluk</string>
|
||||
</resources>
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
<string name="preparing_auth">Chuẩn bị xác thực…</string>
|
||||
<string name="finishing_auth">Hoàn tất xác thực…</string>
|
||||
<string name="user_boosted">%s đăng lại</string>
|
||||
<string name="in_reply_to">Trả lời đến %s</string>
|
||||
<string name="in_reply_to">trả lời %s</string>
|
||||
<string name="notifications">Thông báo</string>
|
||||
<string name="user_followed_you">đã theo dõi bạn</string>
|
||||
<string name="user_sent_follow_request">đã yêu cầu theo dõi bạn</string>
|
||||
<string name="user_favorited">đã thích tút của bạn</string>
|
||||
<string name="notification_boosted">đã đăng lại tút của bạn</string>
|
||||
<string name="user_favorited">thích tút của bạn</string>
|
||||
<string name="notification_boosted">đăng lại tút của bạn</string>
|
||||
<string name="poll_ended">cuộc bình chọn kết thúc</string>
|
||||
<string name="time_seconds">%ds</string>
|
||||
<string name="time_minutes">%dm</string>
|
||||
@@ -54,7 +54,7 @@
|
||||
<string name="block_domain">Chặn %s</string>
|
||||
<string name="unblock_domain">Bỏ chặn %s</string>
|
||||
<plurals name="x_posts">
|
||||
<item quantity="other">%d tút</item>
|
||||
<item quantity="other">%,d tút</item>
|
||||
</plurals>
|
||||
<string name="profile_joined">Đã tham gia</string>
|
||||
<string name="done">Xong</string>
|
||||
@@ -87,7 +87,7 @@
|
||||
<item quantity="other">%d ngày còn lại</item>
|
||||
</plurals>
|
||||
<plurals name="x_voters">
|
||||
<item quantity="other">%d người bình chọn</item>
|
||||
<item quantity="other">%,d người bình chọn</item>
|
||||
</plurals>
|
||||
<string name="poll_closed">Kết thúc</string>
|
||||
<string name="confirm_mute_title">Ẩn người dùng</string>
|
||||
@@ -200,7 +200,7 @@
|
||||
<string name="alt_text_hint">vd: Một con chó nhíu mắt lại nhìn máy ảnh.</string>
|
||||
<string name="visibility_public">Công khai</string>
|
||||
<string name="visibility_followers_only">Riêng tư</string>
|
||||
<string name="visibility_private">Chỉ người được nhắc đến</string>
|
||||
<string name="visibility_private">Nhắn riêng</string>
|
||||
<string name="search_all">Tất cả</string>
|
||||
<string name="search_people">Người dùng</string>
|
||||
<string name="recent_searches">Tìm kiếm gần đây</string>
|
||||
@@ -224,12 +224,12 @@
|
||||
<string name="theme_dark">Tối</string>
|
||||
<string name="theme_true_black">Chế độ tối chân thật</string>
|
||||
<string name="settings_behavior">Thao tác</string>
|
||||
<string name="settings_gif">Phát ảnh đại diện và emoji động</string>
|
||||
<string name="settings_gif">Ảnh đại diện và emoji động</string>
|
||||
<string name="settings_custom_tabs">Dùng trình duyệt tích hợp</string>
|
||||
<string name="settings_notifications">Thông báo</string>
|
||||
<string name="notify_me_when">Thông báo khi</string>
|
||||
<string name="notify_anyone">ai đó</string>
|
||||
<string name="notify_follower">một người theo dõi</string>
|
||||
<string name="notify_follower">người theo dõi tôi</string>
|
||||
<string name="notify_followed">người tôi theo dõi</string>
|
||||
<string name="notify_none">không một ai</string>
|
||||
<string name="notify_favorites">Thích tút của tôi</string>
|
||||
@@ -285,4 +285,6 @@
|
||||
<string name="error_saving_file">Lỗi lưu tập tin</string>
|
||||
<string name="file_saved">Đã lưu tập tin</string>
|
||||
<string name="downloading">Đang tải về…</string>
|
||||
<string name="no_app_to_handle_action">Không tìm thấy ứng dụng để thực hiện thao tác này</string>
|
||||
<string name="local_timeline">Cộng đồng</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,163 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="app_name">Mastodon</string>
|
||||
<string name="get_started">开始使用</string>
|
||||
<string name="log_in">登录</string>
|
||||
<string name="next">下一步</string>
|
||||
<string name="loading_instance">正在获取实例信息…</string>
|
||||
<string name="error">错误</string>
|
||||
<string name="not_a_mastodon_instance">%s 似乎不是Mastodon实例。</string>
|
||||
<string name="ok">确定</string>
|
||||
<string name="preparing_auth">正在跳转...</string>
|
||||
<string name="finishing_auth">正在完成身份验证…</string>
|
||||
<string name="user_boosted">%s 加成</string>
|
||||
<string name="in_reply_to">回复给 %s</string>
|
||||
<string name="notifications">通知</string>
|
||||
<string name="user_followed_you">关注了你</string>
|
||||
<string name="user_sent_follow_request">向您发送了关注请求</string>
|
||||
<string name="user_favorited">喜欢了你的帖子</string>
|
||||
<string name="notification_boosted">加成了你的帖子</string>
|
||||
<string name="poll_ended">投票已结束</string>
|
||||
<string name="time_seconds">%d 秒前</string>
|
||||
<string name="time_minutes">%d 分钟前</string>
|
||||
<string name="time_hours">%d 小时前</string>
|
||||
<string name="time_days">%d 天前</string>
|
||||
<string name="share_toot_title">分享</string>
|
||||
<string name="settings">设置</string>
|
||||
<string name="publish">发布</string>
|
||||
<string name="discard_draft">舍弃草稿?</string>
|
||||
<string name="discard">丢弃</string>
|
||||
<string name="cancel">取消</string>
|
||||
<plurals name="followers">
|
||||
<item quantity="other">关注者</item>
|
||||
</plurals>
|
||||
<plurals name="following">
|
||||
<item quantity="other">正在关注</item>
|
||||
</plurals>
|
||||
<plurals name="posts">
|
||||
<item quantity="other">帖子</item>
|
||||
</plurals>
|
||||
<string name="posts">帖子</string>
|
||||
<string name="posts_and_replies">帖子与回复</string>
|
||||
<string name="media">媒体文件</string>
|
||||
<string name="profile_about">关于</string>
|
||||
<string name="button_follow">关注</string>
|
||||
<string name="button_following">正在关注</string>
|
||||
<string name="edit_profile">修改个人资料</string>
|
||||
<string name="mention_user">提及 %s</string>
|
||||
<string name="share_user">分享 %s</string>
|
||||
<string name="mute_user">静音 %s</string>
|
||||
<string name="unmute_user">不再静音 %s</string>
|
||||
<string name="block_user">屏蔽 %s</string>
|
||||
<string name="unblock_user">解除屏蔽 %s</string>
|
||||
<string name="report_user">举报 %s</string>
|
||||
<string name="block_domain">屏蔽 %s</string>
|
||||
<string name="unblock_domain">解除屏蔽 %s</string>
|
||||
<plurals name="x_posts">
|
||||
<item quantity="other">%d 个帖子</item>
|
||||
</plurals>
|
||||
<string name="profile_joined">加入于</string>
|
||||
<string name="done">完成</string>
|
||||
<plurals name="discussed_x_times">
|
||||
<item quantity="other">讨论了 %d 次</item>
|
||||
</plurals>
|
||||
<string name="report_title">举报 %s</string>
|
||||
<string name="report_choose_reason">这个帖子有什么问题?</string>
|
||||
<string name="report_choose_reason_account">%s 有什么问题?</string>
|
||||
<string name="report_choose_reason_subtitle">选择最佳匹配</string>
|
||||
<string name="report_reason_personal">我不喜欢它</string>
|
||||
<string name="report_reason_personal_subtitle">这不是你想看到的东西</string>
|
||||
<string name="report_reason_spam">它是垃圾信息</string>
|
||||
<string name="report_reason_spam_subtitle">恶意链接,虚假互动或重复回复</string>
|
||||
<string name="report_reason_violation">它违反了服务器规则</string>
|
||||
<string name="report_reason_violation_subtitle">你知道它会破坏特定规则</string>
|
||||
<string name="report_reason_other">其他原因</string>
|
||||
<string name="report_reason_other_subtitle">该问题不符合其他类别</string>
|
||||
<string name="report_choose_rule">违反了哪些规则?</string>
|
||||
<string name="report_choose_rule_subtitle">选择所有适用项</string>
|
||||
<string name="report_choose_posts">是否有任何帖子支持此报告?</string>
|
||||
<string name="report_choose_posts_subtitle">选择所有适用项</string>
|
||||
<string name="report_comment_title">还有什么你认为我们应该知道的吗?</string>
|
||||
<string name="report_comment_hint">备注</string>
|
||||
<string name="sending_report">报告发送中...</string>
|
||||
<string name="report_sent_title">感谢提交举报,我们将会进行处理。</string>
|
||||
<string name="report_sent_subtitle">当我们审查这个问题时,你可以对 %s 采取行动。</string>
|
||||
<string name="unfollow_user">取消关注 %s</string>
|
||||
<string name="unfollow">取消关注</string>
|
||||
<string name="mute_user_explain">你不会在你的主页里看到他们的帖子或重新博客。他们不会知道他们被静音了。</string>
|
||||
<string name="block_user_explain">他们将不再能够关注或看到你的帖子,但他们可以看到他们是否被阻止。</string>
|
||||
<string name="report_personal_title">不想看到这个内容?</string>
|
||||
<string name="report_personal_subtitle">当您在Mastodon看到您不喜欢的东西时,您可以从您的体验中移除该人。</string>
|
||||
<string name="back">返回</string>
|
||||
<string name="instance_catalog_title">Mastodon是由不同社区的用户制作的。</string>
|
||||
<string name="instance_catalog_subtitle">选择一个基于您的兴趣、区域或一般目的的社区。您仍然可以与每个人联系,不管社区如何。</string>
|
||||
<string name="search_communities">搜索社区或输入 URL</string>
|
||||
<string name="instance_rules_title">一些基本规则</string>
|
||||
<string name="instance_rules_subtitle">请花一分钟来审阅规则设置,并由 %s 管理员执行。</string>
|
||||
<string name="signup_title">让我们让您在 %s 上设置</string>
|
||||
<string name="edit_photo">编辑</string>
|
||||
<string name="display_name">昵称</string>
|
||||
<string name="username">用户名</string>
|
||||
<string name="email">电子邮箱</string>
|
||||
<string name="password">密码</string>
|
||||
<string name="password_note">包括大写字母、特殊字符和数字以增加您的密码强度。</string>
|
||||
<string name="category_academia">学术</string>
|
||||
<string name="category_activism">行动主义</string>
|
||||
<string name="category_all">全部</string>
|
||||
<string name="category_art">艺术</string>
|
||||
<string name="category_food">食品</string>
|
||||
<string name="category_furry">兽迷</string>
|
||||
<string name="category_games">游戏</string>
|
||||
<string name="category_general">通用</string>
|
||||
<string name="category_journalism">新闻</string>
|
||||
<string name="notify_followed">我关注的</string>
|
||||
<string name="notify_none">没有人</string>
|
||||
<string name="notify_favorites">喜欢我的帖子</string>
|
||||
<string name="notify_follow">关注我</string>
|
||||
<string name="notify_reblog">转发我的帖子</string>
|
||||
<string name="notify_mention">提及我</string>
|
||||
<string name="settings_boring">潜水区</string>
|
||||
<string name="settings_account">帐户设置</string>
|
||||
<string name="settings_contribute">贡献给Mastodon</string>
|
||||
<string name="settings_tos">使用条款</string>
|
||||
<string name="settings_privacy_policy">隐私政策</string>
|
||||
<string name="settings_spicy">The Spicy Zone</string>
|
||||
<string name="settings_clear_cache">清除图片缓存</string>
|
||||
<string name="settings_app_version">Mastodon for Android v%1$s (%2$d)</string>
|
||||
<string name="media_cache_cleared">媒体缓存已清除</string>
|
||||
<string name="confirm_log_out">您确定要退出吗?</string>
|
||||
<string name="sensitive_content">敏感内容</string>
|
||||
<string name="sensitive_content_explain">作者将此媒体标记为敏感。点击以显示。</string>
|
||||
<string name="media_hidden">点击以显示</string>
|
||||
<string name="avatar_description">跳转到 %s 的资料</string>
|
||||
<string name="more_options">更多选项</string>
|
||||
<string name="reveal_content">显示内容</string>
|
||||
<string name="hide_content">隐藏内容</string>
|
||||
<string name="new_post">新帖子</string>
|
||||
<string name="button_reply">回复</string>
|
||||
<string name="button_reblog">转发</string>
|
||||
<string name="button_favorite">喜欢</string>
|
||||
<string name="button_share">分享</string>
|
||||
<string name="media_no_description">没有描述的媒体</string>
|
||||
<string name="add_media">添加媒体</string>
|
||||
<string name="add_poll">添加投票</string>
|
||||
<string name="emoji">表情</string>
|
||||
<string name="post_visibility">帖子可见性</string>
|
||||
<string name="home_timeline">主页时间轴</string>
|
||||
<string name="my_profile">我的资料</string>
|
||||
<string name="media_viewer">媒体查看器</string>
|
||||
<string name="follow_user">关注 %s</string>
|
||||
<string name="unfollowed_user">取消关注 %s</string>
|
||||
<string name="followed_user">您正在关注 %s</string>
|
||||
<string name="open_in_browser">在浏览器中打开</string>
|
||||
<string name="hide_boosts_from_user">不接收来自 %s 的动态</string>
|
||||
<string name="show_boosts_from_user">接收来自 %s 的动态</string>
|
||||
<string name="signup_reason">加入的理由是?</string>
|
||||
<string name="signup_reason_note">这会有助于我们处理你的申请.</string>
|
||||
<string name="clear">清除</string>
|
||||
<string name="profile_header">顶部图片</string>
|
||||
<string name="profile_picture">个人资料照片</string>
|
||||
<string name="reorder">重新排序</string>
|
||||
<string name="download">下载</string>
|
||||
<string name="permission_required">需要相应权限</string>
|
||||
</resources>
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
<item name="discover_hashtags" type="id"/>
|
||||
<item name="discover_news" type="id"/>
|
||||
<item name="discover_users" type="id"/>
|
||||
<item name="discover_local_timeline" type="id"/>
|
||||
<item name="discover_federated_timeline" type="id"/>
|
||||
|
||||
<item name="notifications_all" type="id"/>
|
||||
<item name="notifications_mentions" type="id"/>
|
||||
|
||||
@@ -11,14 +11,14 @@
|
||||
<string name="ok">OK</string>
|
||||
<string name="preparing_auth">Preparing for authentication…</string>
|
||||
<string name="finishing_auth">Finishing authentication…</string>
|
||||
<string name="user_boosted">%s boosted</string>
|
||||
<string name="user_boosted">%s reblogged</string>
|
||||
<string name="in_reply_to">In reply to %s</string>
|
||||
<string name="notifications">Notifications</string>
|
||||
|
||||
<string name="user_followed_you">followed you</string>
|
||||
<string name="user_sent_follow_request">sent you a follow request</string>
|
||||
<string name="user_favorited">favorited your toot</string>
|
||||
<string name="notification_boosted">boosted your toot</string>
|
||||
<string name="user_favorited">favorited your post</string>
|
||||
<string name="notification_boosted">reblogged your post</string>
|
||||
<string name="poll_ended">poll ended</string>
|
||||
|
||||
<string name="time_seconds">%ds</string>
|
||||
@@ -176,9 +176,9 @@
|
||||
<string name="report_personal_title">Don\'t want to see this?</string>
|
||||
<string name="report_personal_subtitle">When you see something you don\'t like on Mastodon, you can remove the person from your experience.</string>
|
||||
<string name="back">Back</string>
|
||||
<string name="instance_catalog_title">Mastodon is made of users in different communities.</string>
|
||||
<string name="instance_catalog_subtitle">Pick a community based on your interests, region, or a general purpose one. You can still connect with everyone, regardless of community.</string>
|
||||
<string name="search_communities">Search communities or enter URL</string>
|
||||
<string name="instance_catalog_title">Mastodon is made of users on different servers.</string>
|
||||
<string name="instance_catalog_subtitle">Pick a server based on your interests, region, or a general purpose one. You can still connect with everyone, regardless of server.</string>
|
||||
<string name="search_communities">Search servers or enter URL</string>
|
||||
<string name="instance_rules_title">Some ground rules</string>
|
||||
<string name="instance_rules_subtitle">Take a minute to review the rules set and enforced by %s admins.</string>
|
||||
<string name="signup_title">Let\'s get you set up on %s</string>
|
||||
@@ -218,6 +218,7 @@
|
||||
<string name="alt_text_subtitle">Alt text describes your photos for people with low or no vision. Try to only include enough detail to understand the context.</string>
|
||||
<string name="alt_text_hint">e.g. A dog looking around suspiciously with narrowed eyes at the camera.</string>
|
||||
<string name="visibility_public">Public</string>
|
||||
<string name="visibility_unlisted">Unlisted</string>
|
||||
<string name="visibility_followers_only">Followers only</string>
|
||||
<string name="visibility_private">Only people I mention</string>
|
||||
<string name="search_all">All</string>
|
||||
@@ -227,7 +228,7 @@
|
||||
<string name="skip">Skip</string>
|
||||
<string name="notification_type_follow">New followers</string>
|
||||
<string name="notification_type_favorite">Favorites</string>
|
||||
<string name="notification_type_reblog">Boosts</string>
|
||||
<string name="notification_type_reblog">Reblogs</string>
|
||||
<string name="notification_type_mention">Mentions</string>
|
||||
<string name="notification_type_poll">Polls</string>
|
||||
<string name="choose_account">Choose account</string>
|
||||
@@ -290,8 +291,8 @@
|
||||
<string name="unfollowed_user">Unfollowed %s</string>
|
||||
<string name="followed_user">You\'re now following %s</string>
|
||||
<string name="open_in_browser">Open in browser</string>
|
||||
<string name="hide_boosts_from_user">Hide boosts from %s</string>
|
||||
<string name="show_boosts_from_user">Show boosts from %s</string>
|
||||
<string name="hide_boosts_from_user">Hide reblogs from %s</string>
|
||||
<string name="show_boosts_from_user">Show reblogs from %s</string>
|
||||
<string name="signup_reason">why do you want to join?</string>
|
||||
<string name="signup_reason_note">This will help us review your application.</string>
|
||||
<string name="clear">Clear</string>
|
||||
@@ -305,4 +306,39 @@
|
||||
<string name="error_saving_file">Error saving file</string>
|
||||
<string name="file_saved">File saved</string>
|
||||
<string name="downloading">Downloading…</string>
|
||||
<string name="no_app_to_handle_action">There\'s no app to handle this action</string>
|
||||
<string name="local_timeline">Community</string>
|
||||
<string name="federated_timeline">Federation</string>
|
||||
<string name="trending_posts_info_banner">These are the posts gaining traction in your corner of Mastodon.</string>
|
||||
<string name="trending_hashtags_info_banner">These are the hashtags gaining traction in your corner of Mastodon.</string>
|
||||
<string name="trending_links_info_banner">These are the news stories being shared the most in your corner of Mastodon.</string>
|
||||
<string name="local_timeline_info_banner">These are the most recent posts by the people who use the same Mastodon server as you.</string>
|
||||
<string name="federated_timeline_info_banner">These are the most recent posts by the people in your federation.</string>
|
||||
<string name="dismiss">Dismiss</string>
|
||||
<string name="see_new_posts">See new posts</string>
|
||||
<string name="load_missing_posts">Load missing posts</string>
|
||||
<string name="follow_back">Follow Back</string>
|
||||
<string name="button_follow_pending">Pending</string>
|
||||
<string name="follows_you">Follows you</string>
|
||||
<string name="manually_approves_followers">Manually approves followers</string>
|
||||
<string name="current_account">Current account</string>
|
||||
<string name="log_out_account">Log Out %s</string>
|
||||
|
||||
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
||||
<plurals name="x_followers">
|
||||
<item quantity="one">%,d follower</item>
|
||||
<item quantity="other">%,d followers</item>
|
||||
</plurals>
|
||||
<plurals name="x_following">
|
||||
<item quantity="one">%,d following</item>
|
||||
<item quantity="other">%,d following</item>
|
||||
</plurals>
|
||||
<plurals name="x_favorites">
|
||||
<item quantity="one">%,d favorite</item>
|
||||
<item quantity="other">%,d favorites</item>
|
||||
</plurals>
|
||||
<plurals name="x_reblogs">
|
||||
<item quantity="one">%,d reblog</item>
|
||||
<item quantity="other">%,d reblogs</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
@@ -66,7 +66,7 @@
|
||||
<item name="colorButtonText">@color/gray_800</item>
|
||||
<item name="colorSecondary">#E9EDF2</item>
|
||||
<item name="colorBackgroundLight">@color/gray_700</item>
|
||||
<item name="colorBackgroundLightest">@color/gray_700</item>
|
||||
<item name="colorBackgroundLightest">@color/gray_900</item>
|
||||
<item name="colorDarkIcon">@color/gray_25</item>
|
||||
<item name="colorWindowBackground">@color/gray_800</item>
|
||||
<item name="android:statusBarColor">@color/gray_800</item>
|
||||
|
||||
Reference in New Issue
Block a user