Compare commits
152 Commits
v1.1.1+for
...
v1.1.3+for
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
596799bf2f | ||
|
|
10a405ef13 | ||
|
|
a4cb05080a | ||
|
|
4f8f698911 | ||
|
|
244f2ed911 | ||
|
|
0ee494bcfc | ||
|
|
eea00b0d53 | ||
|
|
e8fa82d0de | ||
|
|
e381de812c | ||
|
|
8ff3ecb4d4 | ||
|
|
1fa8a9e858 | ||
|
|
212e8893b9 | ||
|
|
367057421b | ||
|
|
01970ab69b | ||
|
|
3aa252f681 | ||
|
|
18633291e6 | ||
|
|
8aeda56fc8 | ||
|
|
f531a90b41 | ||
|
|
ff52c37868 | ||
|
|
8fb2b454dd | ||
|
|
265b2ad32c | ||
|
|
ba3219d9fc | ||
|
|
b44e3424e3 | ||
|
|
f073eba538 | ||
|
|
7f78431eff | ||
|
|
24c1ac042c | ||
|
|
105fe68438 | ||
|
|
46057af093 | ||
|
|
750fa4c112 | ||
|
|
8b40643e63 | ||
|
|
5968dcd05b | ||
|
|
0c382bdbf6 | ||
|
|
74f03026cf | ||
|
|
8ad6bd52ef | ||
|
|
04da21edf3 | ||
|
|
ee709b6db7 | ||
|
|
b7432fe422 | ||
|
|
8a0991d533 | ||
|
|
6fffe778d3 | ||
|
|
b67b61dfe4 | ||
|
|
65d86d9238 | ||
|
|
8336bfdf5c | ||
|
|
b38bf5e431 | ||
|
|
310fb7db42 | ||
|
|
2f24977996 | ||
|
|
6c336ba89e | ||
|
|
d1b1cb2082 | ||
|
|
5bbe99be51 | ||
|
|
556d1e7433 | ||
|
|
293d7032ce | ||
|
|
48e7071450 | ||
|
|
bcc8d55c7b | ||
|
|
8d477efc28 | ||
|
|
4ae21862a5 | ||
|
|
6e6aebef35 | ||
|
|
cd138032da | ||
|
|
864e6fb9d0 | ||
|
|
9d356b0635 | ||
|
|
6c5d720a40 | ||
|
|
d733d76ccf | ||
|
|
3483d8c3c0 | ||
|
|
0f326c1362 | ||
|
|
c6eda38400 | ||
|
|
3c59c8cc0f | ||
|
|
8f1b9ec092 | ||
|
|
5a42136395 | ||
|
|
e9b347d130 | ||
|
|
d86e203127 | ||
|
|
c80417e671 | ||
|
|
55a55fbb1c | ||
|
|
b42236999b | ||
|
|
8ca8bd765b | ||
|
|
7f4cf77283 | ||
|
|
950d413bd1 | ||
|
|
475827b1c1 | ||
|
|
1c9164e559 | ||
|
|
7311a394d8 | ||
|
|
aa09bc7ab2 | ||
|
|
267a6a75ef | ||
|
|
37660b4c73 | ||
|
|
15b4d46ea1 | ||
|
|
0b99e76b25 | ||
|
|
c851f666b3 | ||
|
|
7662c81754 | ||
|
|
5c1b583448 | ||
|
|
4071c1552d | ||
|
|
89856a81a3 | ||
|
|
005ddfb651 | ||
|
|
92d10d59c6 | ||
|
|
150f70edd8 | ||
|
|
73019eaade | ||
|
|
46bac59ff5 | ||
|
|
9b162cb63b | ||
|
|
7d216314c9 | ||
|
|
28787b4068 | ||
|
|
a73ea62a9c | ||
|
|
69b399e397 | ||
|
|
fc2c033e93 | ||
|
|
1d81abca5b | ||
|
|
0f3cd5d8d0 | ||
|
|
f0476f3187 | ||
|
|
b4677d14e5 | ||
|
|
a8837bd4f8 | ||
|
|
c6991a7067 | ||
|
|
0723e942f0 | ||
|
|
52fd300d1e | ||
|
|
68c9f7a861 | ||
|
|
8eb0b12a09 | ||
|
|
68ecd7bc28 | ||
|
|
5c7d4e389f | ||
|
|
55fd74c227 | ||
|
|
b65b7c53bc | ||
|
|
7a23c9b348 | ||
|
|
32cc760272 | ||
|
|
e105764aa8 | ||
|
|
5a9a352e56 | ||
|
|
7deb2d452e | ||
|
|
c3d2df88e8 | ||
|
|
9943d19c31 | ||
|
|
51c1e115c5 | ||
|
|
f83ff93c68 | ||
|
|
7deb5d44c2 | ||
|
|
3140ae8046 | ||
|
|
772f79219b | ||
|
|
8830d67af0 | ||
|
|
89c02be41c | ||
|
|
1d092c660b | ||
|
|
a34084da5a | ||
|
|
212f5a9beb | ||
|
|
f6333de4e6 | ||
|
|
5af22f1bab | ||
|
|
02d866d7d6 | ||
|
|
fa7aa6240b | ||
|
|
cb38e0d367 | ||
|
|
a133a1d01f | ||
|
|
79bfc43431 | ||
|
|
72f3a51af7 | ||
|
|
ee73b487ae | ||
|
|
e580d2e890 | ||
|
|
4f6f53061f | ||
|
|
be23ec4176 | ||
|
|
186636c2ef | ||
|
|
f0396ff418 | ||
|
|
67e793b56a | ||
|
|
d84f011d27 | ||
|
|
f685d9ccdd | ||
|
|
380c742f54 | ||
|
|
ed84ea6162 | ||
|
|
e99ffc0d4c | ||
|
|
ca015db188 | ||
|
|
af3a3761f2 | ||
|
|
95085b6306 |
@@ -19,13 +19,13 @@
|
||||
## Fork-specific changes
|
||||
|
||||
* Custom app name
|
||||
* Custom icon: Modulate upstream icon's hue by `161%` using ImageMagick
|
||||
* Custom icon: Modulate upstream icon using ImageMagick
|
||||
|
||||
```bash
|
||||
mogrify -modulate 100,100,161 mastodon/src/main/res/mipmap-*/ic_launcher*.png
|
||||
mogrify -modulate 90,100,140 mastodon/src/main/res/mipmap-*/ic_launcher*.png
|
||||
```
|
||||
|
||||
* Custom primary color: Hue of all `primary` colors in `colors.xml` is rotated
|
||||
* Custom primary color: Hue of all `primary` colors in `colors.xml` is rotated, on basis of upstream Mastodon's [old branding](https://github.com/mastodon/mastodon-android/commit/74f03026cfcfcfd23237c38ff47d2b2a98a6f92a#diff-59134ec2a1cf3761f80b0ecccbbf8b9e433d9780d2f5c5d6ac3ac8cc254e808f)
|
||||
by `109.8°` (equivalent of `161%`, done by hand using
|
||||
[PineTools](https://pinetools.com/shift-hue-color))
|
||||
|
||||
|
||||
16
fastlane/metadata/android/cs-CZ/full_description.txt
Normal file
16
fastlane/metadata/android/cs-CZ/full_description.txt
Normal file
@@ -0,0 +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!
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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 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.
|
||||
1
fastlane/metadata/android/cs-CZ/short_description.txt
Normal file
1
fastlane/metadata/android/cs-CZ/short_description.txt
Normal file
@@ -0,0 +1 @@
|
||||
Decentralized social network
|
||||
1
fastlane/metadata/android/cs-CZ/title.txt
Normal file
1
fastlane/metadata/android/cs-CZ/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Mastodon
|
||||
@@ -1,8 +1,8 @@
|
||||
Mastodon ist das größte dezentralisierte soziale Netzwerk im Internet. Statt einer einzigen Website ist es ein Netzwerk von Millionen von Benutzer*innen in unabhängigen Gemeinschaften, die alle miteinander interagieren können. Egal was dich interessiert, auf Mastodon kannst du interessierte Leute treffen, die darüber schreiben!
|
||||
|
||||
Trete einer Gemeinschaft bei und erstelle dein Profil. Finde und verfolge faszinierende Leute und lese ihre Beiträge in einer werbefreien, chronologischen Zeitachse. Drücke dich mit benutzerdefinierten Emojis, Bilderns, GIFs, Videos und Audio in 500-Zeichen Beiträgen aus. Antworte auf Threads und teile Beiträge von anderen um großartige Sachen zu verbreiten. Finde neue Accounts zum Folgen und angesagte Hashtags, um dein Netzwerk zu erweitern.
|
||||
Tritt einer Gemeinschaft bei und erstelle dein Profil. Finde und folge faszinierenden Leuten und lies ihre Beiträge in einer werbefreien, chronologischen Zeitachse. Drücke dich mit benutzerdefinierten Emojis, Bilderns, GIFs, Videos und Audio in 500-Zeichen-Beiträgen aus. Antworte auf Threads und teile Beiträge von anderen, um großartige Sachen zu verbreiten. Finde neue Accounts zum Folgen und angesagte Hashtags, um dein Netzwerk zu erweitern.
|
||||
|
||||
Mastodon wurde mit einem Schwerpunkt auf Privatsphäre und Sicherheit gebaut. Entscheide, ob du deine Beiträge mit deinen Followern, nur mit den Menschen, die du erwähnst, oder mit der ganzen Welt teilen möchtest. Mit Inhaltswarnungen kannst du Beiträge mit sensiblem oder triggerndem Inhalt ausblenden, bis du bereit bist, dich damit auseinanderzusetzen. Jede Gemeinschaft hat ihre eigenen Regeln und Moderator*innen, um die Sicherheit ihrer Mitglieder zu gewährleisten, sowie robuste Sperr- und Meldewerkzeuge um Missbrauch vorzubeugen.
|
||||
Mastodon wurde mit einem Schwerpunkt auf Privatsphäre und Sicherheit gebaut. Entscheide, ob du deine Beiträge mit deinen Followern, nur mit den Menschen, die du erwähnst, oder mit der ganzen Welt teilen möchtest. Mit Inhaltswarnungen kannst du Beiträge mit sensiblem oder triggerndem Inhalt ausblenden, bis du bereit bist, dich damit auseinanderzusetzen. Jede Gemeinschaft hat ihre eigenen Regeln und Moderator*innen, um die Sicherheit ihrer Mitglieder zu gewährleisten, sowie robuste Sperr- und Meldewerkzeuge, um Missbrauch vorzubeugen.
|
||||
|
||||
Weitere Funktionen:
|
||||
|
||||
@@ -11,6 +11,6 @@ Weitere Funktionen:
|
||||
• Entdecken: Trending Hashtags und Accounts sind nur einen Fingertipp entfernt
|
||||
• Benachrichtigungen: Erhalte Benachrichtigungen über neue Follower, Antworten und geteilte Beiträge
|
||||
• Teilen: Veröffentliche auf Mastodon aus jeder beliebigen anderen App
|
||||
• Niedlichkeit: Unser Maskottchen ist ein entzückender Elefant und du wirst ihn von Zeit zu Zeit auftauchen sehen
|
||||
• Niedlichkeit: Unser Maskottchen ist ein entzückender Elefant, und du wirst ihn von Zeit zu Zeit auftauchen sehen
|
||||
|
||||
Mastodon ist eine eingetragene gemeinnützige Organisation und die Entwicklung wird direkt durch deine Spenden unterstützt. Es gibt keine Werbung, keine Monetisierung und kein Venture-Capital, und wir planen es so zu erhalten.
|
||||
Mastodon ist eine eingetragene gemeinnützige Organisation, und die Entwicklung wird direkt durch deine Spenden unterstützt. Es gibt keine Werbung, keine Monetisierung und kein Venture-Capital, und wir planen es so zu erhalten.
|
||||
@@ -1,6 +1,6 @@
|
||||
Mastodon es la red social descentralizada más grande de internet. En lugar de ser una sola web, es una red de millones de usuarios en comunidades independientes que pueden interactuar entre ellas de forma transparente. No importa qué es lo que hagas, podrás encontrar gente apasionada escribiendo sobre ello en Mastodon!
|
||||
|
||||
Únete a una comunidad y crea tu perfil. Encuentra y sigue a gente fascinante y lee sus publicaciones sin anuncios y de forma cronológica. Exprésate con emoticonos personalizados, imágenes, GIFs, vídeos y audio en publicaciónes de 500 caracteres. Responde a hilos e impulsa publicaciones de cualquiera para compartir contenido genial. Encuentra nuevas cuentas para seguir y los hashtags de actualidad para expandir tu red.
|
||||
Únete a una comunidad y crea tu perfil. Encuentra y sigue a gente fascinante y lee sus publicaciones sin anuncios y de forma cronológica. Exprésate con emoticonos personalizados, imágenes, GIFs, vídeos y audio en publicaciónes de 500 caracteres. Responde a hilos e rebloguea publicaciones de cualquiera para compartir contenido genial. Encuentra nuevas cuentas para seguir y los hashtags de actualidad para expandir tu red.
|
||||
|
||||
Mastodon está construída con un enfoque en la privacidad y la seguridad. Decide si tus publicaciones se comparten con tus seguidores, solo a la gente que menciones, o a todo el mundo. Las advertencias de contenido te permiten esconder publicaciones con contenido sensible o limitarlas de tu visión hasta que estés listo para interactuar con ellas. Cada comunidad tiene sus propias reglas y moderadores para mantener a salvo a sus miembros, además de herramientas robustas para bloquear y reportar contenido para prevenir el abuso.
|
||||
|
||||
@@ -9,7 +9,7 @@ Más características:
|
||||
• Modo oscuro: Lee las publicaciones en modo claro, oscuro o negro real
|
||||
• Encuestas: Pide opinión a tus seguidores y cuenta los votos
|
||||
• Explora: Hashtags y cuentas en tendencia a un solo toque
|
||||
• Notificaciones: Recibe notificaciones sobre nuevos seguidores, respuestas e impulsos
|
||||
• Notificaciones: Recibe notificaciones sobre nuevos seguidores, respuestas y reblogueos
|
||||
• Compartir: Publica directamente a Mastodon desde cualquier hoja de acción en cualquier aplicación
|
||||
• Preciosidad: Nuestra mascota es un elefante adorable, y verás que aparece de vez en cuando
|
||||
|
||||
|
||||
@@ -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 on internetin suurin hajautettu sosiaalinen verkosto. Yhden verkkopalvelun sijaan, se on miljoonien itsenäisissä yhteisöissä olevien käyttäjien verkosto, jotka voivat olla vuorovaikutuksessa toistensa kanssa saumattomasti. Riippumatta siitä, mistä olet kiinnostunut, voit tavata intohimoisia ihmisiä, jotka julkaisevat aiheesta Mastodonissa!
|
||||
|
||||
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.
|
||||
Liity yhteisöön ja luo itsellesi tili. Löydä ja seuraa kiehtovia ihmisiä ja lue heidän julkaisunsa ilman mainoksia, kronologisella aikajanalla. Ilmaise itseäsi mukautetuilla emojeilla, kuvilla, videoilla ja audiolla 500 merkin pituisissa julkaisuissa. Vastaa viestiketjuihin ja edelleen jaa julkaisuja keneltä tahansa, jakaaksesi hienoja juttuja. Löydä uusia tilejä seurattavaksi ja trendaavia hashtageja laajentaaksesi verkostoasi.
|
||||
|
||||
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 on rakennettu keskittyen yksityisyyteen ja turvallisuuteen. Päätä, jaetaanko julkaisusi seuraajille, vain mainitsemillesi ihmisille vai koko maailmalle. Sisältövaroitusten avulla, voit piilottaa julkaisut, jotka sisältävät arkaluontoista tai laukaisevaa materiaalia, kunnes olet valmis käsittelemään niitä. Jokaisella yhteisöllä on omat ohjeistonsa ja valvojansa, jotka pitävät jäsenensä turvassa, ja tehokkaat esto- ja ilmiantotyökalut auttavat torjumaan väärinkäytöksiä.
|
||||
|
||||
More features:
|
||||
Lisää ominaisuuksia:
|
||||
|
||||
• 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
|
||||
• Tumma tila: Lue julkaisut vaaleassa, tummassa tai mustan tummassa tilassa
|
||||
• Kyselyt: Kysy seuraajilta heidän mielipidettään ja laske äänet
|
||||
• Tutustu: Trendaavat hashtagit ja tilit ovat vain napsautuksen päässä
|
||||
• Ilmoitukset: Saat ilmoituksen uusista seuraajista, vastauksista ja edelleen jaoista
|
||||
• Jakaminen: Julkaise suoraan Mastodoniin minkä tahansa sovelluksen jakovalikon kautta
|
||||
• Suloisuus: Maskottimme on ihastuttava mastodontti ja näet sen ajoittain
|
||||
|
||||
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 on rekisteröity voittoa tavoittelematon organisaatio ja kehitystä tuetaan suoraan lahjoituksillasi. Ei mainontaa, kaupallistamista eikä riskipääomaa, ja aiomme pitää sen sellaisena.
|
||||
@@ -1 +1 @@
|
||||
Decentralized social network
|
||||
Hajautettu sosiaalinen verkosto
|
||||
@@ -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!
|
||||
마스토돈은 인터넷에서 가장 큰 분산 소셜 네트워크입니다. 단 하나의 통일된 웹사이트 대신, 수백만 명의 사용자들이, 서로 경계 없이 소통할 수 있는 독립적인 커뮤니티의 네트워크입니다. 당신이 어디에 속하든간에, 마스토돈에 열정적으로 게시물을 남기는 사람들을 만날 수 있습니다!
|
||||
|
||||
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.
|
||||
커뮤니티에 가입하고 프로필을 생성하세요. 매력적인 사람들을 찾아 팔로우하고 그들의 글을 광고가 없고, 시간순으로 정렬된 타임라인에서 읽으세요. 커스텀 에모지, 그림, 움짤, 동영상, 소리와 함께 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.
|
||||
마스토돈은 개인정보 보호와 안전에 중점을 두고 만들어졌습니다. 당신의 게시물을 팔로워들에게만 공개할지, 언급한 사람들에게만 공유할지, 아니면 전세계에 공유할 지 선택하세요. 열람주의는 민감하거나 남들에게 껄끄러울 수 있는 게시물을 마음의 준비가 된 사람들만 열람하도록 숨길 수 있도록 해줍니다. 각각의 커뮤니티는 구성원들을 안전하게 지키기 위한 각자의 규정과 중재자들을 가지고 있으며, 남용을 방지하기 위한 강력한 차단 도구와 신고 도구를 가지고 있습니다.
|
||||
|
||||
더 많은 기능:
|
||||
|
||||
• 다크모드: 게시물을 밝음, 어두움, 진정한 검정 모드에서 읽으세요
|
||||
• 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 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.
|
||||
마스토돈은 등록된 상호이며 여러분들의 직접적인 기부를 통해 개발되고 있습니다. 광고도, 유료화도, 벤처 캐피탈도 없습니다, 그리고 계속 그렇게 할 생각입니다.
|
||||
@@ -1,16 +1,16 @@
|
||||
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. 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.
|
||||
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. Odnajduj nowe konta do śledzenia i zyskujące popularność hashtagi, by poszerzać swoją sieć.
|
||||
|
||||
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.
|
||||
Mastodon został zaprojektowany z myślą o prywatności i bezpieczeństwie. Decyduj, czy Twoje wpisy są udostępniane osobom śledzącym Cię, tylko wzmiankowanym, czy całemu światu. 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ć. Każda społeczność ma własne wytyczne i moderatorów, aby zapewniać swoim członkom bezpieczeństwo, a także solidne narzędzia blokowania i raportowania pomagające zapobiegać nadużyciom.
|
||||
|
||||
Więcej funkcji:
|
||||
|
||||
• Tryb ciemny: Czytaj wpisy w jasnym, ciemnym lub czarnym trybie
|
||||
• Ankiety: Poproś obserwujących o ich opinię i poznaj ich głosy
|
||||
• Explore: Trending hashtags and accounts are a tap away
|
||||
• Odkrywaj: Najpopularniejsze hashtagi i konta są dostępne za jednym dotknięciem
|
||||
• Powiadomienia: Otrzymuj powiadomienia o nowych obserwacjach, odpowiedziach i udostępnieniach
|
||||
• 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
|
||||
• Udostępnianie: Publikuj bezpośrednio na Mastodonie z menu udostępniania w dowolnej aplikacji
|
||||
• Słodycz: Nasza maskotka to uroczy słoń i zobaczysz go pojawiającego się od czasu do czasu
|
||||
|
||||
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 to zarejestrowana organizacja non-profit i rozwój jest wspierany bezpośrednio przez darowizny. Nie ma reklam, monetyzacji, ani kapitału inwestycyjnego i planujemy, by tak pozostało.
|
||||
@@ -1,8 +1,8 @@
|
||||
O Mastodon é a maior rede social descentralizada da Internet. Em vez de ser um único site, é uma rede de milhões de utilizadores em comunidades independentes que podem facilmente interagir uns com os outros. No matter what you’re into, you can meet passionate people posting about it on Mastodon!
|
||||
O Mastodon é a maior rede social descentralizada da Internet. Em vez de ser um único site, é uma rede de milhões de utilizadores em comunidades independentes que podem facilmente interagir uns com os outros. Independemente dos teus gostos, consegues encontrar pessoas que os partilhem no 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.
|
||||
Junta-te a uma comunidade e cria o teu perfil. Encontra, segue gente fascinante e lê as suas publicações numa cronologia sem anúncios. Expressa-te com emojis personalizados imagens, GIFs, vídeos e áudio em publicações com até 500 caracteres. Responde a tópicos e promove publicações de qualquer pessoa para partilhares ótimas coisas. Encontra novas contas e tendências a seguir para expandires a tua rede.
|
||||
|
||||
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.
|
||||
O Mastodon é construído com foco na privacidade e segurança. Decide se as tuas publicações são partilhadas com os teus seguidores, apenas com as pessoas mencionadas, ou com o mundo inteiro. Avisos de conteúdo permitem-te esconder publicações que contenham material sensível ou provocatório até estares pronto(a) para o veres. Each community has its own guidelines and moderators to keep its members safe, and robust blocking and reporting tools help prevent abuse.
|
||||
|
||||
More features:
|
||||
|
||||
|
||||
@@ -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 är det största decentraliserade sociala nätverket på internet. I stället för en enda webbplats är det ett nätverk av miljontals användare på oberoende servrar som alla kan interagera med varandra, sömlöst. Oavsett vad du är intresserad av kan du träffa passionerade personer som diskuterar ämnet på 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.
|
||||
Gå med på en server och skapa din profil. Hitta och följ fascinerande människor och läsa deras inlägg i en annonsfri, kronologisk tidslinje. Uttryck dig med anpassade emoji, bilder, GIF:ar, videor och ljud i 500-teckensinlägg. Svara på trådar och ompostningar från vem som helst för att dela bra saker. Hitta nya konton att följa och trendande hashtaggar för att utöka ditt nätverk.
|
||||
|
||||
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 är byggt med fokus på integritet och trygghet. Bestäm om dina inlägg delas med dina följare, bara personer du omnämner, eller hela världen. Innehållsvarningar låter dig dölja inlägg som innehåller känsligt eller triggande material tills du är redo att interagera med dem. Varje server har sina egna riktlinjer och moderatorer för att hålla sina medlemmar trygga, och robusta blockerings- och rapporteringsverktyg för att förhindra missbruk.
|
||||
|
||||
More features:
|
||||
Fler funktioner:
|
||||
|
||||
• 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
|
||||
• Mörkt läge: Läs inlägg i ljust, mörkt eller helsvart läge
|
||||
• Omröstningar: Fråga följare om deras åsikt och sammanställ deras röster
|
||||
• Utforska: Trendande hashtaggar och konton är ett tryck bort
|
||||
• Notiser: Bli meddelad om nya följare, svar och ompostningar
|
||||
• Delning: Posta direkt till Mastodon från delningsbladet i alla appar
|
||||
• Gullighet: Vår maskot är en bedårande elefant, och du kommer att se dem dyka upp då och då
|
||||
|
||||
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 är en registrerad ideell förening och utvecklingen stöds direkt av dina donationer. Det finns ingen reklam, ingen monetarisering, och inget riskkapital, och vi planerar att behålla det på det sättet.
|
||||
@@ -1 +1 @@
|
||||
Decentralized social network
|
||||
Decentraliserat socialt nätverk
|
||||
@@ -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 เป็นองค์กรไม่แสวงหาผลกำไรที่จดทะเบียนแล้ว และการพัฒนาได้รับการสนับสนุนจากเงินบริจาคของคุณโดยตรง ดังนั้นจึงไม่มีโฆษณา ไม่มีการทำกำไร และไม่มีการร่วมลงทุน และเรามีแผนจะทำให้เป็นอย่างนี้ต่อไป
|
||||
@@ -4,13 +4,13 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 31
|
||||
compileSdk 33
|
||||
defaultConfig {
|
||||
applicationId "org.joinmastodon.android.sk"
|
||||
minSdk 23
|
||||
targetSdk 31
|
||||
versionCode 19
|
||||
versionName '1.1.1+fork.19'
|
||||
targetSdk 33
|
||||
versionCode 24
|
||||
versionName "1.1.3+fork.24"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ android {
|
||||
initWith release
|
||||
versionNameSuffix "-beta"
|
||||
}
|
||||
githubRelease{
|
||||
initWith release
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
@@ -46,6 +49,16 @@ android {
|
||||
appcenterPublicBeta{
|
||||
setRoot "src/appcenter"
|
||||
}
|
||||
githubRelease{
|
||||
setRoot "src/github"
|
||||
}
|
||||
debug {
|
||||
setRoot "src/github"
|
||||
}
|
||||
}
|
||||
lintOptions{
|
||||
checkReleaseBuilds false
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
21
mastodon/src/github/AndroidManifest.xml
Normal file
21
mastodon/src/github/AndroidManifest.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.joinmastodon.android">
|
||||
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
||||
|
||||
<application>
|
||||
<!-- <receiver android:name=".updater.GithubSelfUpdaterImpl$InstallerStatusReceiver" android:exported="false"/>-->
|
||||
<!-- <receiver android:name=".updater.GithubSelfUpdaterImpl$AfterUpdateRestartReceiver" android:exported="true" android:enabled="false">-->
|
||||
<!-- <intent-filter>-->
|
||||
<!-- <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>-->
|
||||
<!-- </intent-filter>-->
|
||||
<!-- </receiver>-->
|
||||
<provider
|
||||
android:authorities="${applicationId}.self_update_provider"
|
||||
android:name=".updater.SelfUpdateContentProvider"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false"/>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,343 @@
|
||||
package org.joinmastodon.android.updater;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.DownloadManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
@Keep
|
||||
public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
||||
private static final long CHECK_PERIOD=24*3600*1000L;
|
||||
private static final String TAG="GithubSelfUpdater";
|
||||
|
||||
private UpdateState state=UpdateState.NO_UPDATE;
|
||||
private UpdateInfo info;
|
||||
private long downloadID;
|
||||
private BroadcastReceiver downloadCompletionReceiver=new BroadcastReceiver(){
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent){
|
||||
if(downloadID!=0 && intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0)==downloadID){
|
||||
MastodonApp.context.unregisterReceiver(this);
|
||||
setState(UpdateState.DOWNLOADED);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public GithubSelfUpdaterImpl(){
|
||||
SharedPreferences prefs=getPrefs();
|
||||
int checkedByBuild=prefs.getInt("checkedByBuild", 0);
|
||||
if(prefs.contains("version") && checkedByBuild==BuildConfig.VERSION_CODE){
|
||||
info=new UpdateInfo();
|
||||
info.version=prefs.getString("version", null);
|
||||
info.size=prefs.getLong("apkSize", 0);
|
||||
downloadID=prefs.getLong("downloadID", 0);
|
||||
if(downloadID==0 || !getUpdateApkFile().exists()){
|
||||
state=UpdateState.UPDATE_AVAILABLE;
|
||||
}else{
|
||||
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
|
||||
state=dm.getUriForDownloadedFile(downloadID)==null ? UpdateState.DOWNLOADING : UpdateState.DOWNLOADED;
|
||||
if(state==UpdateState.DOWNLOADING){
|
||||
MastodonApp.context.registerReceiver(downloadCompletionReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
||||
}
|
||||
}
|
||||
}else if(checkedByBuild!=BuildConfig.VERSION_CODE && checkedByBuild>0){
|
||||
// We are in a new version, running for the first time after update. Gotta clean things up.
|
||||
long id=getPrefs().getLong("downloadID", 0);
|
||||
if(id!=0){
|
||||
MastodonApp.context.getSystemService(DownloadManager.class).remove(id);
|
||||
}
|
||||
getUpdateApkFile().delete();
|
||||
getPrefs().edit()
|
||||
.remove("apkSize")
|
||||
.remove("version")
|
||||
.remove("apkURL")
|
||||
.remove("checkedByBuild")
|
||||
.remove("downloadID")
|
||||
.apply();
|
||||
}
|
||||
}
|
||||
|
||||
private SharedPreferences getPrefs(){
|
||||
return MastodonApp.context.getSharedPreferences("githubUpdater", Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeCheckForUpdates(){
|
||||
if(state!=UpdateState.NO_UPDATE && state!=UpdateState.UPDATE_AVAILABLE)
|
||||
return;
|
||||
long timeSinceLastCheck=System.currentTimeMillis()-getPrefs().getLong("lastCheck", CHECK_PERIOD);
|
||||
if(timeSinceLastCheck>=CHECK_PERIOD){
|
||||
setState(UpdateState.CHECKING);
|
||||
MastodonAPIController.runInBackground(this::actuallyCheckForUpdates);
|
||||
}
|
||||
}
|
||||
|
||||
private void actuallyCheckForUpdates(){
|
||||
Request req=new Request.Builder()
|
||||
.url("https://api.github.com/repos/sk22/mastodon-android-fork/releases/latest")
|
||||
.build();
|
||||
Call call=MastodonAPIController.getHttpClient().newCall(req);
|
||||
try(Response resp=call.execute()){
|
||||
JsonObject obj=JsonParser.parseReader(resp.body().charStream()).getAsJsonObject();
|
||||
String tag=obj.get("tag_name").getAsString();
|
||||
Matcher matcher=Pattern.compile("v(\\d+)\\.(\\d+)\\.(\\d+)\\+fork\\.(\\d+)").matcher(tag);
|
||||
if(!matcher.find()){
|
||||
Log.w(TAG, "actuallyCheckForUpdates: release tag has wrong format: "+tag);
|
||||
return;
|
||||
}
|
||||
int newMajor=Integer.parseInt(matcher.group(1)),
|
||||
newMinor=Integer.parseInt(matcher.group(2)),
|
||||
newRevision=Integer.parseInt(matcher.group(3)),
|
||||
newForkNumber=Integer.parseInt(matcher.group(4));
|
||||
|
||||
String[] currentParts=BuildConfig.VERSION_NAME.split("[.+]");
|
||||
int curMajor=Integer.parseInt(currentParts[0]),
|
||||
curMinor=Integer.parseInt(currentParts[1]),
|
||||
curRevision=Integer.parseInt(currentParts[2]),
|
||||
curForkNumber=Integer.parseInt(currentParts[4]);
|
||||
long newVersion=((long)newMajor << 32) | ((long)newMinor << 16) | newRevision;
|
||||
long curVersion=((long)curMajor << 32) | ((long)curMinor << 16) | curRevision;
|
||||
if(newVersion>curVersion || newForkNumber>curForkNumber || BuildConfig.DEBUG){
|
||||
String version=newMajor+"."+newMinor+"."+newRevision+"+fork."+newForkNumber;
|
||||
Log.d(TAG, "actuallyCheckForUpdates: new version: "+version);
|
||||
for(JsonElement el:obj.getAsJsonArray("assets")){
|
||||
JsonObject asset=el.getAsJsonObject();
|
||||
if("application/vnd.android.package-archive".equals(asset.get("content_type").getAsString()) && "uploaded".equals(asset.get("state").getAsString())){
|
||||
long size=asset.get("size").getAsLong();
|
||||
String url=asset.get("browser_download_url").getAsString();
|
||||
|
||||
UpdateInfo info=new UpdateInfo();
|
||||
info.size=size;
|
||||
info.version=version;
|
||||
this.info=info;
|
||||
|
||||
getPrefs().edit()
|
||||
.putLong("apkSize", size)
|
||||
.putString("version", version)
|
||||
.putString("apkURL", url)
|
||||
.putInt("checkedByBuild", BuildConfig.VERSION_CODE)
|
||||
.remove("downloadID")
|
||||
.apply();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
getPrefs().edit().putLong("lastCheck", System.currentTimeMillis()).apply();
|
||||
}catch(Exception x){
|
||||
Log.w(TAG, "actuallyCheckForUpdates", x);
|
||||
}finally{
|
||||
setState(info==null ? UpdateState.NO_UPDATE : UpdateState.UPDATE_AVAILABLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void setState(UpdateState state){
|
||||
this.state=state;
|
||||
E.post(new SelfUpdateStateChangedEvent(state));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateState getState(){
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateInfo getUpdateInfo(){
|
||||
return info;
|
||||
}
|
||||
|
||||
public File getUpdateApkFile(){
|
||||
return new File(MastodonApp.context.getExternalCacheDir(), "update.apk");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void downloadUpdate(){
|
||||
if(state==UpdateState.DOWNLOADING)
|
||||
throw new IllegalStateException();
|
||||
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
|
||||
MastodonApp.context.registerReceiver(downloadCompletionReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
||||
downloadID=dm.enqueue(
|
||||
new DownloadManager.Request(Uri.parse(getPrefs().getString("apkURL", null)))
|
||||
.setDestinationUri(Uri.fromFile(getUpdateApkFile()))
|
||||
);
|
||||
getPrefs().edit().putLong("downloadID", downloadID).apply();
|
||||
setState(UpdateState.DOWNLOADING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installUpdate(Activity activity){
|
||||
if(state!=UpdateState.DOWNLOADED)
|
||||
throw new IllegalStateException();
|
||||
Uri uri;
|
||||
Intent intent=new Intent(Intent.ACTION_INSTALL_PACKAGE);
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
|
||||
uri=new Uri.Builder().scheme("content").authority(activity.getPackageName()+".self_update_provider").path("update.apk").build();
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
}else{
|
||||
uri=Uri.fromFile(getUpdateApkFile());
|
||||
}
|
||||
intent.setDataAndType(uri, "application/vnd.android.package-archive");
|
||||
activity.startActivity(intent);
|
||||
|
||||
// TODO figure out how to restart the app when updating via this new API
|
||||
/*
|
||||
PackageInstaller installer=activity.getPackageManager().getPackageInstaller();
|
||||
try{
|
||||
final int sid=installer.createSession(new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL));
|
||||
installer.registerSessionCallback(new PackageInstaller.SessionCallback(){
|
||||
@Override
|
||||
public void onCreated(int i){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBadgingChanged(int i){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveChanged(int i, boolean b){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(int id, float progress){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinished(int id, boolean success){
|
||||
activity.getPackageManager().setComponentEnabledSetting(new ComponentName(activity, AfterUpdateRestartReceiver.class), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
|
||||
}
|
||||
});
|
||||
activity.getPackageManager().setComponentEnabledSetting(new ComponentName(activity, AfterUpdateRestartReceiver.class), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
|
||||
PackageInstaller.Session session=installer.openSession(sid);
|
||||
try(OutputStream out=session.openWrite("mastodon.apk", 0, info.size); InputStream in=new FileInputStream(getUpdateApkFile())){
|
||||
byte[] buffer=new byte[16384];
|
||||
int read;
|
||||
while((read=in.read(buffer))>0){
|
||||
out.write(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
// PendingIntent intent=PendingIntent.getBroadcast(activity, 1, new Intent(activity, InstallerStatusReceiver.class), PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
|
||||
PendingIntent intent=PendingIntent.getActivity(activity, 1, new Intent(activity, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
session.commit(intent.getIntentSender());
|
||||
}catch(IOException x){
|
||||
Log.w(TAG, "installUpdate", x);
|
||||
Toast.makeText(activity, x.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDownloadProgress(){
|
||||
if(state!=UpdateState.DOWNLOADING)
|
||||
throw new IllegalStateException();
|
||||
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
|
||||
try(Cursor cursor=dm.query(new DownloadManager.Query().setFilterById(downloadID))){
|
||||
if(cursor.moveToFirst()){
|
||||
long loaded=cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
|
||||
long total=cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
|
||||
// Log.d(TAG, "getDownloadProgress: "+loaded+" of "+total);
|
||||
return total>0 ? (float)loaded/total : 0f;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelDownload(){
|
||||
if(state!=UpdateState.DOWNLOADING)
|
||||
throw new IllegalStateException();
|
||||
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
|
||||
dm.remove(downloadID);
|
||||
downloadID=0;
|
||||
getPrefs().edit().remove("downloadID").apply();
|
||||
setState(UpdateState.UPDATE_AVAILABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleIntentFromInstaller(Intent intent, Activity activity){
|
||||
int status=intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);
|
||||
if(status==PackageInstaller.STATUS_PENDING_USER_ACTION){
|
||||
Intent confirmIntent=intent.getParcelableExtra(Intent.EXTRA_INTENT);
|
||||
activity.startActivity(confirmIntent);
|
||||
}else if(status!=PackageInstaller.STATUS_SUCCESS){
|
||||
String msg=intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
|
||||
Toast.makeText(activity, activity.getString(R.string.error)+":\n"+msg, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
/*public static class InstallerStatusReceiver extends BroadcastReceiver{
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent){
|
||||
int status=intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);
|
||||
if(status==PackageInstaller.STATUS_PENDING_USER_ACTION){
|
||||
Intent confirmIntent=intent.getParcelableExtra(Intent.EXTRA_INTENT);
|
||||
context.startActivity(confirmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
}else if(status!=PackageInstaller.STATUS_SUCCESS){
|
||||
String msg=intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
|
||||
Toast.makeText(context, context.getString(R.string.error)+":\n"+msg, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class AfterUpdateRestartReceiver extends BroadcastReceiver{
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent){
|
||||
if(Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction())){
|
||||
context.getPackageManager().setComponentEnabledSetting(new ComponentName(context, AfterUpdateRestartReceiver.class), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
|
||||
Toast.makeText(context, R.string.update_installed, Toast.LENGTH_SHORT).show();
|
||||
Intent restartIntent=new Intent(context, MainActivity.class)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.setPackage(context.getPackageName());
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.P){
|
||||
context.startActivity(restartIntent);
|
||||
}else{
|
||||
// Bypass activity starting restrictions by starting it from a notification
|
||||
NotificationManager nm=context.getSystemService(NotificationManager.class);
|
||||
NotificationChannel chan=new NotificationChannel("selfUpdateRestart", context.getString(R.string.update_installed), NotificationManager.IMPORTANCE_HIGH);
|
||||
nm.createNotificationChannel(chan);
|
||||
Notification n=new Notification.Builder(context, "selfUpdateRestart")
|
||||
.setContentTitle(context.getString(R.string.update_installed))
|
||||
.setContentIntent(PendingIntent.getActivity(context, 1, restartIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
|
||||
.setFullScreenIntent(PendingIntent.getActivity(context, 1, restartIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE), true)
|
||||
.setSmallIcon(R.drawable.ic_ntf_logo)
|
||||
.build();
|
||||
nm.notify(1, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.joinmastodon.android.updater;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class SelfUpdateContentProvider extends ContentProvider{
|
||||
@Override
|
||||
public boolean onCreate(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder){
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getType(@NonNull Uri uri){
|
||||
if(isCorrectUri(uri))
|
||||
return "application/vnd.android.package-archive";
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values){
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException{
|
||||
if(isCorrectUri(uri)){
|
||||
return ParcelFileDescriptor.open(((GithubSelfUpdaterImpl)GithubSelfUpdater.getInstance()).getUpdateApkFile(), ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
}
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
private boolean isCorrectUri(Uri uri){
|
||||
return "/update.apk".equals(uri.getPath());
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
|
||||
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>
|
||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
|
||||
<permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 46 KiB |
@@ -1,8 +1,12 @@
|
||||
package org.joinmastodon.android;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Application;
|
||||
import android.app.Fragment;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -17,6 +21,7 @@ import org.joinmastodon.android.fragments.ThreadFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@@ -59,6 +64,8 @@ public class MainActivity extends FragmentStackActivity{
|
||||
showFragmentForNotification(notification, session.getID());
|
||||
}else if(intent.getBooleanExtra("compose", false)){
|
||||
showCompose();
|
||||
}else{
|
||||
maybeRequestNotificationsPermission();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,6 +75,8 @@ public class MainActivity extends FragmentStackActivity{
|
||||
try{
|
||||
Class.forName("org.joinmastodon.android.AppCenterWrapper").getMethod("init", Application.class).invoke(null, getApplication());
|
||||
}catch(ClassNotFoundException|NoSuchMethodException|IllegalAccessException|InvocationTargetException ignore){}
|
||||
}else if(GithubSelfUpdater.needSelfUpdating()){
|
||||
GithubSelfUpdater.getInstance().maybeCheckForUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +105,9 @@ public class MainActivity extends FragmentStackActivity{
|
||||
}
|
||||
}else if(intent.getBooleanExtra("compose", false)){
|
||||
showCompose();
|
||||
}
|
||||
}/*else if(intent.hasExtra(PackageInstaller.EXTRA_STATUS) && GithubSelfUpdater.needSelfUpdating()){
|
||||
GithubSelfUpdater.getInstance().handleIntentFromInstaller(intent, this);
|
||||
}*/
|
||||
}
|
||||
|
||||
private void showFragmentForNotification(Notification notification, String accountID){
|
||||
@@ -131,4 +142,10 @@ public class MainActivity extends FragmentStackActivity{
|
||||
compose.setArguments(composeArgs);
|
||||
showFragment(compose);
|
||||
}
|
||||
|
||||
private void maybeRequestNotificationsPermission(){
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.TIRAMISU && checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)!=PackageManager.PERMISSION_GRANTED){
|
||||
requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ public class CacheController{
|
||||
.exec(accountID);
|
||||
}catch(SQLiteException x){
|
||||
Log.w(TAG, x);
|
||||
uiHandler.post(()->callback.onError(new MastodonErrorResponse(x.getLocalizedMessage(), 500)));
|
||||
uiHandler.post(()->callback.onError(new MastodonErrorResponse(x.getLocalizedMessage(), 500, x)));
|
||||
}finally{
|
||||
closeDelayed();
|
||||
}
|
||||
@@ -184,7 +184,7 @@ public class CacheController{
|
||||
.exec(accountID);
|
||||
}catch(SQLiteException x){
|
||||
Log.w(TAG, x);
|
||||
uiHandler.post(()->callback.onError(new MastodonErrorResponse(x.getLocalizedMessage(), 500)));
|
||||
uiHandler.post(()->callback.onError(new MastodonErrorResponse(x.getLocalizedMessage(), 500, x)));
|
||||
}finally{
|
||||
closeDelayed();
|
||||
}
|
||||
|
||||
@@ -96,11 +96,11 @@ public class MastodonAPIController{
|
||||
if(call.isCanceled())
|
||||
return;
|
||||
if(BuildConfig.DEBUG)
|
||||
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+hreq+" failed: "+e);
|
||||
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+hreq+" failed", e);
|
||||
synchronized(req){
|
||||
req.okhttpCall=null;
|
||||
}
|
||||
req.onError(e.getLocalizedMessage(), 0);
|
||||
req.onError(e.getLocalizedMessage(), 0, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -133,7 +133,7 @@ public class MastodonAPIController{
|
||||
}catch(JsonIOException|JsonSyntaxException x){
|
||||
if(BuildConfig.DEBUG)
|
||||
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error parsing or reading body", x);
|
||||
req.onError(x.getLocalizedMessage(), response.code());
|
||||
req.onError(x.getLocalizedMessage(), response.code(), x);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ public class MastodonAPIController{
|
||||
}catch(IOException x){
|
||||
if(BuildConfig.DEBUG)
|
||||
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error post-processing or validating response", x);
|
||||
req.onError(x.getLocalizedMessage(), response.code());
|
||||
req.onError(x.getLocalizedMessage(), response.code(), x);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ public class MastodonAPIController{
|
||||
JsonObject error=JsonParser.parseReader(reader).getAsJsonObject();
|
||||
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" received error: "+error);
|
||||
if(error.has("details")){
|
||||
MastodonDetailedErrorResponse err=new MastodonDetailedErrorResponse(error.get("error").getAsString(), response.code());
|
||||
MastodonDetailedErrorResponse err=new MastodonDetailedErrorResponse(error.get("error").getAsString(), response.code(), null);
|
||||
HashMap<String, List<MastodonDetailedErrorResponse.FieldError>> details=new HashMap<>();
|
||||
JsonObject errorDetails=error.getAsJsonObject("details");
|
||||
for(String key:errorDetails.keySet()){
|
||||
@@ -172,12 +172,12 @@ public class MastodonAPIController{
|
||||
err.detailedErrors=details;
|
||||
req.onError(err);
|
||||
}else{
|
||||
req.onError(error.get("error").getAsString(), response.code());
|
||||
req.onError(error.get("error").getAsString(), response.code(), null);
|
||||
}
|
||||
}catch(JsonIOException|JsonSyntaxException x){
|
||||
req.onError(response.code()+" "+response.message(), response.code());
|
||||
req.onError(response.code()+" "+response.message(), response.code(), x);
|
||||
}catch(Exception x){
|
||||
req.onError("Error parsing an API error", response.code());
|
||||
req.onError("Error parsing an API error", response.code(), x);
|
||||
}
|
||||
}
|
||||
}catch(Exception x){
|
||||
@@ -189,7 +189,7 @@ public class MastodonAPIController{
|
||||
}catch(Exception x){
|
||||
if(BuildConfig.DEBUG)
|
||||
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] error creating and sending http request", x);
|
||||
req.onError(x.getLocalizedMessage(), 0);
|
||||
req.onError(x.getLocalizedMessage(), 0, x);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
@@ -197,4 +197,8 @@ public class MastodonAPIController{
|
||||
public static void runInBackground(Runnable action){
|
||||
thread.postRunnable(action, 0);
|
||||
}
|
||||
|
||||
public static OkHttpClient getHttpClient(){
|
||||
return httpClient;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||
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));
|
||||
invokeErrorCallback(new MastodonErrorResponse(x.getLocalizedMessage(), -1, x));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -194,8 +194,8 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||
invokeErrorCallback(err);
|
||||
}
|
||||
|
||||
void onError(String msg, int httpStatus){
|
||||
invokeErrorCallback(new MastodonErrorResponse(msg, httpStatus));
|
||||
void onError(String msg, int httpStatus, Throwable exception){
|
||||
invokeErrorCallback(new MastodonErrorResponse(msg, httpStatus, exception));
|
||||
}
|
||||
|
||||
void onSuccess(T resp){
|
||||
|
||||
@@ -7,8 +7,8 @@ import java.util.Map;
|
||||
public class MastodonDetailedErrorResponse extends MastodonErrorResponse{
|
||||
public Map<String, List<FieldError>> detailedErrors;
|
||||
|
||||
public MastodonDetailedErrorResponse(String error, int httpStatus){
|
||||
super(error, httpStatus);
|
||||
public MastodonDetailedErrorResponse(String error, int httpStatus, Throwable exception){
|
||||
super(error, httpStatus, exception);
|
||||
}
|
||||
|
||||
public static class FieldError{
|
||||
|
||||
@@ -12,10 +12,12 @@ import me.grishka.appkit.api.ErrorResponse;
|
||||
public class MastodonErrorResponse extends ErrorResponse{
|
||||
public final String error;
|
||||
public final int httpStatus;
|
||||
public final Throwable underlyingException;
|
||||
|
||||
public MastodonErrorResponse(String error, int httpStatus){
|
||||
public MastodonErrorResponse(String error, int httpStatus, Throwable exception){
|
||||
this.error=error;
|
||||
this.httpStatus=httpStatus;
|
||||
this.underlyingException=exception;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,6 +27,7 @@ public class GetAccountStatuses extends MastodonAPIRequest<List<Status>>{
|
||||
addQueryParameter("exclude_replies", "true");
|
||||
addQueryParameter("exclude_reblogs", "true");
|
||||
}
|
||||
case OWN_POSTS_AND_REPLIES -> addQueryParameter("exclude_reblogs", "true");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +36,7 @@ public class GetAccountStatuses extends MastodonAPIRequest<List<Status>>{
|
||||
INCLUDE_REPLIES,
|
||||
PINNED,
|
||||
MEDIA,
|
||||
NO_REBLOGS
|
||||
NO_REBLOGS,
|
||||
OWN_POSTS_AND_REPLIES
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Preferences;
|
||||
|
||||
public class GetPreferences extends MastodonAPIRequest<Preferences> {
|
||||
public GetPreferences(){
|
||||
super(HttpMethod.GET, "/preferences", Preferences.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
public class EditStatus extends MastodonAPIRequest<Status>{
|
||||
public EditStatus(CreateStatus.Request req, String id){
|
||||
super(HttpMethod.PUT, "/statuses/"+id, Status.class);
|
||||
setRequestBody(req);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import okhttp3.Response;
|
||||
|
||||
public class GetAttachmentByID extends MastodonAPIRequest<Attachment>{
|
||||
public GetAttachmentByID(String id){
|
||||
super(HttpMethod.GET, "/media/"+id, Attachment.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateAndPostprocessResponse(Attachment respObj, Response httpResponse) throws IOException{
|
||||
if(httpResponse.code()==206)
|
||||
respObj.url="";
|
||||
super.validateAndPostprocessResponse(respObj, httpResponse);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import okhttp3.Response;
|
||||
|
||||
public class GetStatusEditHistory extends MastodonAPIRequest<List<Status>>{
|
||||
public GetStatusEditHistory(String id){
|
||||
super(HttpMethod.GET, "/statuses/"+id+"/history", new TypeToken<>(){});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateAndPostprocessResponse(List<Status> respObj, Response httpResponse) throws IOException{
|
||||
int i=0;
|
||||
for(Status s:respObj){
|
||||
s.uri="";
|
||||
s.id="fakeID"+i;
|
||||
s.visibility=StatusPrivacy.PUBLIC;
|
||||
s.mentions=Collections.emptyList();
|
||||
s.tags=Collections.emptyList();
|
||||
i++;
|
||||
}
|
||||
super.validateAndPostprocessResponse(respObj, httpResponse);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.BaseModel;
|
||||
|
||||
public class GetStatusSourceText extends MastodonAPIRequest<GetStatusSourceText.Response>{
|
||||
public GetStatusSourceText(String id){
|
||||
super(HttpMethod.GET, "/statuses/"+id+"/source", Response.class);
|
||||
}
|
||||
|
||||
@AllFieldsAreRequired
|
||||
public static class Response extends BaseModel{
|
||||
public String id;
|
||||
public String text;
|
||||
public String spoilerText;
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import java.io.IOException;
|
||||
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class UploadAttachment extends MastodonAPIRequest<Attachment>{
|
||||
private Uri uri;
|
||||
@@ -40,6 +41,18 @@ public class UploadAttachment extends MastodonAPIRequest<Attachment>{
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPathPrefix(){
|
||||
return "/api/v2";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateAndPostprocessResponse(Attachment respObj, Response httpResponse) throws IOException{
|
||||
if(respObj.url==null)
|
||||
respObj.url="";
|
||||
super.validateAndPostprocessResponse(respObj, httpResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestBody getRequestBody() throws IOException{
|
||||
MultipartBody.Builder builder=new MultipartBody.Builder()
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.events;
|
||||
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
|
||||
public class SelfUpdateStateChangedEvent{
|
||||
public final GithubSelfUpdater.UpdateState state;
|
||||
|
||||
public SelfUpdateStateChangedEvent(GithubSelfUpdater.UpdateState state){
|
||||
this.state=state;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.events;
|
||||
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
public class StatusUpdatedEvent{
|
||||
public Status status;
|
||||
|
||||
public StatusUpdatedEvent(Status status){
|
||||
this.status=status;
|
||||
}
|
||||
}
|
||||
@@ -457,7 +457,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
status.spoilerRevealed=true;
|
||||
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
|
||||
if(text!=null)
|
||||
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()+getMainAdapterOffset());
|
||||
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()-getMainAdapterOffset());
|
||||
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
|
||||
if(header!=null)
|
||||
header.rebind();
|
||||
@@ -579,6 +579,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
return true;
|
||||
}
|
||||
|
||||
public ArrayList<StatusDisplayItem> getDisplayItems(){
|
||||
return displayItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyWindowInsets(WindowInsets insets){
|
||||
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0 && wantsOverlaySystemNavigation()){
|
||||
|
||||
@@ -4,13 +4,18 @@ import android.animation.ObjectAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.RenderEffect;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.icu.text.BreakIterator;
|
||||
import android.media.MediaMetadataRetriever;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@@ -52,19 +57,27 @@ import com.twitter.twittertext.TwitterTextEmojiRegex;
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||
import org.joinmastodon.android.api.ProgressListener;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetPreferences;
|
||||
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
||||
import org.joinmastodon.android.api.requests.statuses.EditStatus;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetAttachmentByID;
|
||||
import org.joinmastodon.android.api.requests.statuses.UploadAttachment;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.EmojiCategory;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Mention;
|
||||
import org.joinmastodon.android.model.Poll;
|
||||
import org.joinmastodon.android.model.Preferences;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
import org.joinmastodon.android.ui.ComposeAutocompleteViewController;
|
||||
@@ -76,6 +89,7 @@ import org.joinmastodon.android.ui.text.ComposeAutocompleteSpan;
|
||||
import org.joinmastodon.android.ui.text.ComposeHashtagOrMentionSpan;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||
import org.joinmastodon.android.ui.utils.TransferSpeedTracker;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ComposeEditText;
|
||||
import org.joinmastodon.android.ui.views.ComposeMediaLayout;
|
||||
@@ -84,6 +98,9 @@ import org.joinmastodon.android.ui.views.SizeListenerLinearLayout;
|
||||
import org.parceler.Parcel;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -105,6 +122,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
private static final int MEDIA_RESULT=717;
|
||||
private static final int IMAGE_DESCRIPTION_RESULT=363;
|
||||
private static final int MAX_ATTACHMENTS=4;
|
||||
private static final String TAG="ComposeFragment";
|
||||
|
||||
private static final Pattern MENTION_PATTERN=Pattern.compile("(^|[^\\/\\w])@(([a-z0-9_]+)@[a-z0-9\\.\\-]+[a-z0-9]+)", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
@@ -152,8 +170,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
|
||||
private ArrayList<DraftPollOption> pollOptions=new ArrayList<>();
|
||||
|
||||
private ArrayList<DraftMediaAttachment> queuedAttachments=new ArrayList<>(), failedAttachments=new ArrayList<>(), attachments=new ArrayList<>(), allAttachments=new ArrayList<>();
|
||||
private DraftMediaAttachment uploadingAttachment;
|
||||
private ArrayList<DraftMediaAttachment> attachments=new ArrayList<>();
|
||||
|
||||
private List<EmojiCategory> customEmojis;
|
||||
private CustomEmojiPopupKeyboard emojiKeyboard;
|
||||
@@ -175,6 +192,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
private Instance instance;
|
||||
private boolean attachmentsErrorShowing;
|
||||
|
||||
private Status editingStatus;
|
||||
private boolean pollChanged;
|
||||
private boolean creatingView;
|
||||
private boolean ignoreSelectionChanges=false;
|
||||
private Runnable updateUploadEtaRunnable;
|
||||
|
||||
public static DraftMediaAttachment redraftAttachment(Attachment att) {
|
||||
DraftMediaAttachment draft=new DraftMediaAttachment();
|
||||
draft.serverAttachment=att;
|
||||
@@ -194,6 +217,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
instanceDomain=session.domain;
|
||||
customEmojis=AccountSessionManager.getInstance().getCustomEmojis(instanceDomain);
|
||||
instance=AccountSessionManager.getInstance().getInstanceInfo(instanceDomain);
|
||||
if(getArguments().containsKey("editStatus")){
|
||||
editingStatus=Parcels.unwrap(getArguments().getParcelable("editStatus"));
|
||||
}
|
||||
if(instance==null){
|
||||
Nav.finish(this);
|
||||
return;
|
||||
@@ -209,25 +235,20 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
else
|
||||
charLimit=500;
|
||||
|
||||
if(getArguments().containsKey("replyTo")){
|
||||
replyTo=Parcels.unwrap(getArguments().getParcelable("replyTo"));
|
||||
statusVisibility=replyTo.visibility;
|
||||
}
|
||||
|
||||
if(getArguments().containsKey("visibility")){
|
||||
statusVisibility=(StatusPrivacy) getArguments().getSerializable("visibility");
|
||||
}
|
||||
|
||||
if(savedInstanceState!=null){
|
||||
statusVisibility=(StatusPrivacy) savedInstanceState.getSerializable("visibility");
|
||||
}
|
||||
loadDefaultStatusVisibility(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy(){
|
||||
super.onDestroy();
|
||||
if(uploadingAttachment!=null && uploadingAttachment.uploadRequest!=null)
|
||||
uploadingAttachment.uploadRequest.cancel();
|
||||
for(DraftMediaAttachment att:attachments){
|
||||
if(att.isUploadingOrProcessing())
|
||||
att.cancelUpload();
|
||||
}
|
||||
if(updateUploadEtaRunnable!=null){
|
||||
UiUtils.removeCallbacks(updateUploadEtaRunnable);
|
||||
updateUploadEtaRunnable=null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -239,6 +260,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
|
||||
@Override
|
||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||
creatingView=true;
|
||||
emojiKeyboard=new CustomEmojiPopupKeyboard(getActivity(), customEmojis, instanceDomain);
|
||||
emojiKeyboard.setListener(this::onCustomEmojiClick);
|
||||
|
||||
@@ -317,6 +339,16 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}
|
||||
updatePollOptionHints();
|
||||
pollDurationView.setText(getString(R.string.compose_poll_duration, pollDurationStr));
|
||||
}else if(savedInstanceState==null && editingStatus!=null && editingStatus.poll!=null){
|
||||
pollBtn.setSelected(true);
|
||||
mediaBtn.setEnabled(false);
|
||||
pollWrap.setVisibility(View.VISIBLE);
|
||||
for(Poll.Option eopt:editingStatus.poll.options){
|
||||
DraftPollOption opt=createDraftPollOption();
|
||||
opt.edit.setText(eopt.title);
|
||||
}
|
||||
updatePollOptionHints();
|
||||
pollDurationView.setText(getString(R.string.compose_poll_duration, pollDurationStr));
|
||||
}else{
|
||||
pollDurationView.setText(getString(R.string.compose_poll_duration, pollDurationStr=getResources().getQuantityString(R.plurals.x_days, 1, 1)));
|
||||
}
|
||||
@@ -329,6 +361,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
if((savedInstanceState!=null && savedInstanceState.getBoolean("hasSpoiler", false)) || hasSpoiler){
|
||||
spoilerEdit.setVisibility(View.VISIBLE);
|
||||
spoilerBtn.setSelected(true);
|
||||
}else if(editingStatus!=null && !TextUtils.isEmpty(editingStatus.spoilerText)){
|
||||
spoilerEdit.setVisibility(View.VISIBLE);
|
||||
spoilerEdit.setText(getArguments().getString("sourceSpoiler", editingStatus.spoilerText));
|
||||
spoilerBtn.setSelected(true);
|
||||
}
|
||||
|
||||
ArrayList<Parcelable> serializedAttachments=(savedInstanceState!=null ? savedInstanceState : getArguments())
|
||||
@@ -340,9 +376,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
attachments.add(att);
|
||||
}
|
||||
attachmentsView.setVisibility(View.VISIBLE);
|
||||
}else if(!allAttachments.isEmpty()){
|
||||
}else if(!attachments.isEmpty()){
|
||||
attachmentsView.setVisibility(View.VISIBLE);
|
||||
for(DraftMediaAttachment att:allAttachments){
|
||||
for(DraftMediaAttachment att:attachments){
|
||||
attachmentsView.addView(createMediaAttachmentView(att));
|
||||
}
|
||||
}
|
||||
@@ -354,6 +390,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
autocompleteView.setVisibility(View.GONE);
|
||||
mainEditTextWrap.addView(autocompleteView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(178), Gravity.TOP));
|
||||
|
||||
creatingView=false;
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -455,9 +493,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s){
|
||||
updateCharCounter(s);
|
||||
updateCharCounter();
|
||||
}
|
||||
});
|
||||
spoilerEdit.addTextChangedListener(new SimpleTextWatcher(e->updateCharCounter()));
|
||||
if(replyTo!=null){
|
||||
replyText.setText(getString(R.string.in_reply_to, replyTo.account.displayName));
|
||||
ArrayList<String> mentions=new ArrayList<>();
|
||||
@@ -474,46 +513,65 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
initialText=mentions.isEmpty() ? "" : TextUtils.join(" ", mentions)+" ";
|
||||
if(savedInstanceState==null){
|
||||
mainEditText.setText(initialText);
|
||||
ignoreSelectionChanges=true;
|
||||
mainEditText.setSelection(mainEditText.length());
|
||||
// TODO: setting for preserving cw always / only when replying to own posts
|
||||
// && AccountSessionManager.getInstance().isSelf(accountID, replyTo.account)
|
||||
ignoreSelectionChanges=false;
|
||||
if(!TextUtils.isEmpty(replyTo.spoilerText)){
|
||||
insertSpoiler(replyTo.spoilerText);
|
||||
hasSpoiler=true;
|
||||
spoilerEdit.setVisibility(View.VISIBLE);
|
||||
spoilerEdit.setText(replyTo.spoilerText);
|
||||
spoilerBtn.setSelected(true);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
replyText.setVisibility(View.GONE);
|
||||
}
|
||||
if(savedInstanceState==null){
|
||||
String prefilledText=getArguments().getString("prefilledText");
|
||||
if(!TextUtils.isEmpty(prefilledText)){
|
||||
mainEditText.setText(prefilledText);
|
||||
if(editingStatus!=null){
|
||||
initialText=getArguments().getString("sourceText", "");
|
||||
mainEditText.setText(initialText);
|
||||
ignoreSelectionChanges=true;
|
||||
mainEditText.setSelection(mainEditText.length());
|
||||
initialText=prefilledText;
|
||||
}
|
||||
String spoilerText=getArguments().getString("spoilerText");
|
||||
if(!TextUtils.isEmpty(spoilerText)) insertSpoiler(spoilerText);
|
||||
ArrayList<Uri> mediaUris=getArguments().getParcelableArrayList("mediaAttachments");
|
||||
if(mediaUris!=null && !mediaUris.isEmpty()){
|
||||
for(Uri uri:mediaUris){
|
||||
addMediaAttachment(uri, null);
|
||||
ignoreSelectionChanges=false;
|
||||
if(!editingStatus.mediaAttachments.isEmpty()){
|
||||
attachmentsView.setVisibility(View.VISIBLE);
|
||||
for(Attachment att:editingStatus.mediaAttachments){
|
||||
DraftMediaAttachment da=new DraftMediaAttachment();
|
||||
da.serverAttachment=att;
|
||||
da.description=att.description;
|
||||
da.uri=Uri.parse(att.previewUrl);
|
||||
attachmentsView.addView(createMediaAttachmentView(da));
|
||||
attachments.add(da);
|
||||
}
|
||||
pollBtn.setEnabled(false);
|
||||
}
|
||||
}else{
|
||||
String prefilledText=getArguments().getString("prefilledText");
|
||||
if(!TextUtils.isEmpty(prefilledText)){
|
||||
mainEditText.setText(prefilledText);
|
||||
ignoreSelectionChanges=true;
|
||||
mainEditText.setSelection(mainEditText.length());
|
||||
ignoreSelectionChanges=false;
|
||||
initialText=prefilledText;
|
||||
}
|
||||
ArrayList<Uri> mediaUris=getArguments().getParcelableArrayList("mediaAttachments");
|
||||
if(mediaUris!=null && !mediaUris.isEmpty()){
|
||||
for(Uri uri:mediaUris){
|
||||
addMediaAttachment(uri, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void insertSpoiler(String text) {
|
||||
hasSpoiler=true;
|
||||
if (text!=null) spoilerEdit.setText(text);
|
||||
spoilerEdit.setVisibility(View.VISIBLE);
|
||||
spoilerBtn.setSelected(true);
|
||||
if(editingStatus!=null){
|
||||
updateCharCounter();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||
publishButton=new Button(getActivity());
|
||||
publishButton.setText(R.string.publish);
|
||||
publishButton.setText(editingStatus==null ? R.string.publish : R.string.save);
|
||||
publishButton.setOnClickListener(this::onPublishClick);
|
||||
LinearLayout wrap=new LinearLayout(getActivity());
|
||||
wrap.setOrientation(LinearLayout.HORIZONTAL);
|
||||
@@ -536,7 +594,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
wrap.addView(publishButton, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
wrap.setPadding(V.dp(16), V.dp(4), V.dp(16), V.dp(8));
|
||||
wrap.setClipToPadding(false);
|
||||
MenuItem item=menu.add(R.string.publish);
|
||||
MenuItem item=menu.add(editingStatus==null ? R.string.publish : R.string.save);
|
||||
item.setActionView(wrap);
|
||||
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
updatePublishButtonState();
|
||||
@@ -554,7 +612,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
private void updateCharCounter(CharSequence text){
|
||||
private void updateCharCounter(){
|
||||
CharSequence text=mainEditText.getText();
|
||||
|
||||
String countableText=TwitterTextEmojiRegex.VALID_EMOJI_PATTERN.matcher(
|
||||
MENTION_PATTERN.matcher(
|
||||
URL_PATTERN.matcher(text).replaceAll("$2xxxxxxxxxxxxxxxxxxxxxxx")
|
||||
@@ -566,6 +626,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
charCount++;
|
||||
}
|
||||
|
||||
if(hasSpoiler){
|
||||
charCount+=spoilerEdit.length();
|
||||
}
|
||||
charCounter.setText(String.valueOf(charLimit-charCount));
|
||||
trimmedCharCount=text.toString().trim().length();
|
||||
updatePublishButtonState();
|
||||
@@ -578,11 +641,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
if(opt.edit.length()>0)
|
||||
nonEmptyPollOptionsCount++;
|
||||
}
|
||||
if(publishButton!=null){
|
||||
publishButton.setEnabled((trimmedCharCount>0 || !attachments.isEmpty()) && charCount<=charLimit
|
||||
&& uploadingAttachment==null && failedAttachments.isEmpty() && queuedAttachments.isEmpty()
|
||||
&& (pollOptions.isEmpty() || nonEmptyPollOptionsCount>1));
|
||||
if(publishButton==null)
|
||||
return;
|
||||
int nonDoneAttachmentCount=0;
|
||||
for(DraftMediaAttachment att:attachments){
|
||||
if(att.state!=AttachmentUploadState.DONE)
|
||||
nonDoneAttachmentCount++;
|
||||
}
|
||||
publishButton.setEnabled((trimmedCharCount>0 || !attachments.isEmpty()) && charCount<=charLimit && nonDoneAttachmentCount==0 && (pollOptions.isEmpty() || nonEmptyPollOptionsCount>1));
|
||||
}
|
||||
|
||||
private void onCustomEmojiClick(Emoji emoji){
|
||||
@@ -638,41 +704,59 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
sendProgress.setVisibility(View.VISIBLE);
|
||||
sendError.setVisibility(View.GONE);
|
||||
|
||||
new CreateStatus(req, uuid)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
wm.removeView(sendingOverlay);
|
||||
sendingOverlay=null;
|
||||
E.post(new StatusCreatedEvent(result));
|
||||
if(replyTo!=null){
|
||||
replyTo.repliesCount++;
|
||||
E.post(new StatusCountersUpdatedEvent(replyTo));
|
||||
}
|
||||
Nav.finish(ComposeFragment.this);
|
||||
Callback<Status> resCallback=new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
wm.removeView(sendingOverlay);
|
||||
sendingOverlay=null;
|
||||
if(editingStatus==null){
|
||||
E.post(new StatusCreatedEvent(result));
|
||||
if(replyTo!=null){
|
||||
replyTo.repliesCount++;
|
||||
E.post(new StatusCountersUpdatedEvent(replyTo));
|
||||
}
|
||||
}else{
|
||||
E.post(new StatusUpdatedEvent(result));
|
||||
}
|
||||
Nav.finish(ComposeFragment.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
wm.removeView(sendingOverlay);
|
||||
sendingOverlay=null;
|
||||
sendProgress.setVisibility(View.GONE);
|
||||
sendError.setVisibility(View.VISIBLE);
|
||||
publishButton.setEnabled(true);
|
||||
error.showToast(getActivity());
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
wm.removeView(sendingOverlay);
|
||||
sendingOverlay=null;
|
||||
sendProgress.setVisibility(View.GONE);
|
||||
sendError.setVisibility(View.VISIBLE);
|
||||
publishButton.setEnabled(true);
|
||||
error.showToast(getActivity());
|
||||
}
|
||||
};
|
||||
|
||||
if(editingStatus!=null){
|
||||
new EditStatus(req, editingStatus.id)
|
||||
.setCallback(resCallback)
|
||||
.exec(accountID);
|
||||
}else{
|
||||
new CreateStatus(req, uuid)
|
||||
.setCallback(resCallback)
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasDraft(){
|
||||
if(getArguments().getBoolean("hasDraft", false)) return true;
|
||||
if(editingStatus!=null){
|
||||
if(!mainEditText.getText().toString().equals(initialText))
|
||||
return true;
|
||||
List<String> existingMediaIDs=editingStatus.mediaAttachments.stream().map(a->a.id).collect(Collectors.toList());
|
||||
if(!existingMediaIDs.equals(attachments.stream().map(a->a.serverAttachment.id).collect(Collectors.toList())))
|
||||
return true;
|
||||
return pollChanged;
|
||||
}
|
||||
boolean pollFieldsHaveContent=false;
|
||||
for(DraftPollOption opt:pollOptions)
|
||||
pollFieldsHaveContent|=opt.edit.length()>0;
|
||||
return getArguments().getBoolean("hasDraft", false)
|
||||
|| (mainEditText.length()>0 && !mainEditText.getText().toString().equals(initialText))
|
||||
|| !attachments.isEmpty() || uploadingAttachment!=null || !queuedAttachments.isEmpty()
|
||||
|| !failedAttachments.isEmpty() || pollFieldsHaveContent;
|
||||
return (mainEditText.length()>0 && !mainEditText.getText().toString().equals(initialText)) || !attachments.isEmpty() || pollFieldsHaveContent;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -716,7 +800,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
|
||||
private void confirmDiscardDraftAndFinish(){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.discard_draft)
|
||||
.setTitle(editingStatus==null ? R.string.discard_draft : R.string.discard_changes)
|
||||
.setPositiveButton(R.string.discard, (dialog, which)->Nav.finish(this))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
@@ -773,7 +857,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}
|
||||
if(size>sizeLimit){
|
||||
float mb=sizeLimit/(float) (1024*1024);
|
||||
String sMb=String.format(Locale.getDefault(), mb%1f==0f ? "%f" : "%.2f", mb);
|
||||
String sMb=String.format(Locale.getDefault(), mb%1f==0f ? "%.0f" : "%.2f", mb);
|
||||
showMediaAttachmentError(getString(R.string.media_attachment_too_big, UiUtils.getFileName(uri), sMb));
|
||||
return false;
|
||||
}
|
||||
@@ -782,18 +866,16 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
pollBtn.setEnabled(false);
|
||||
DraftMediaAttachment draft=new DraftMediaAttachment();
|
||||
draft.uri=uri;
|
||||
draft.mimeType=type;
|
||||
draft.description=description;
|
||||
|
||||
attachmentsView.addView(createMediaAttachmentView(draft));
|
||||
allAttachments.add(draft);
|
||||
attachments.add(draft);
|
||||
attachmentsView.setVisibility(View.VISIBLE);
|
||||
draft.overlay.setVisibility(View.VISIBLE);
|
||||
draft.infoBar.setVisibility(View.GONE);
|
||||
draft.setOverlayVisible(true, false);
|
||||
|
||||
if(uploadingAttachment==null){
|
||||
uploadMediaAttachment(draft);
|
||||
}else{
|
||||
queuedAttachments.add(draft);
|
||||
if(!areThereAnyUploadingAttachments()){
|
||||
uploadNextQueuedAttachment();
|
||||
}
|
||||
updatePublishButtonState();
|
||||
if(getMediaAttachmentsCount()==MAX_ATTACHMENTS)
|
||||
@@ -812,25 +894,35 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
private View createMediaAttachmentView(DraftMediaAttachment draft){
|
||||
View thumb=getActivity().getLayoutInflater().inflate(R.layout.compose_media_thumb, attachmentsView, false);
|
||||
ImageView img=thumb.findViewById(R.id.thumb);
|
||||
ViewImageLoader.load(img, null, new UrlImageLoaderRequest(draft.uri, V.dp(250), V.dp(250)));
|
||||
if(draft.serverAttachment!=null){
|
||||
ViewImageLoader.load(img, draft.serverAttachment.blurhashPlaceholder, new UrlImageLoaderRequest(draft.serverAttachment.previewUrl, V.dp(250), V.dp(250)));
|
||||
}else{
|
||||
if(draft.mimeType.startsWith("image/")){
|
||||
ViewImageLoader.load(img, null, new UrlImageLoaderRequest(draft.uri, V.dp(250), V.dp(250)));
|
||||
}else if(draft.mimeType.startsWith("video/")){
|
||||
loadVideoThumbIntoView(img, draft.uri);
|
||||
}
|
||||
}
|
||||
TextView fileName=thumb.findViewById(R.id.file_name);
|
||||
fileName.setText(UiUtils.getFileName(draft.uri));
|
||||
fileName.setText(UiUtils.getFileName(draft.serverAttachment!=null ? Uri.parse(draft.serverAttachment.url) : draft.uri));
|
||||
|
||||
draft.view=thumb;
|
||||
draft.imageView=img;
|
||||
draft.progressBar=thumb.findViewById(R.id.progress);
|
||||
draft.infoBar=thumb.findViewById(R.id.info_bar);
|
||||
draft.overlay=thumb.findViewById(R.id.overlay);
|
||||
draft.descriptionView=thumb.findViewById(R.id.description);
|
||||
draft.uploadStateTitle=thumb.findViewById(R.id.state_title);
|
||||
draft.uploadStateText=thumb.findViewById(R.id.state_text);
|
||||
ImageButton btn=thumb.findViewById(R.id.remove_btn);
|
||||
btn.setTag(draft);
|
||||
btn.setOnClickListener(this::onRemoveMediaAttachmentClick);
|
||||
btn=thumb.findViewById(R.id.remove_btn2);
|
||||
btn.setTag(draft);
|
||||
btn.setOnClickListener(this::onRemoveMediaAttachmentClick);
|
||||
Button retry=thumb.findViewById(R.id.retry_upload);
|
||||
ImageButton retry=thumb.findViewById(R.id.retry_or_cancel_upload);
|
||||
retry.setTag(draft);
|
||||
retry.setOnClickListener(this::onRetryMediaUploadClick);
|
||||
retry.setVisibility(View.GONE);
|
||||
retry.setOnClickListener(this::onRetryOrCancelMediaUploadClick);
|
||||
draft.retryButton=retry;
|
||||
draft.infoBar.setTag(draft);
|
||||
draft.infoBar.setOnClickListener(this::onEditMediaDescriptionClick);
|
||||
@@ -838,12 +930,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
if(!TextUtils.isEmpty(draft.description))
|
||||
draft.descriptionView.setText(draft.description);
|
||||
|
||||
if(uploadingAttachment!=draft && !queuedAttachments.contains(draft)){
|
||||
draft.progressBar.setVisibility(View.GONE);
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.S){
|
||||
draft.overlay.setBackgroundColor(0xA6000000);
|
||||
}
|
||||
if(failedAttachments.contains(draft)){
|
||||
draft.infoBar.setVisibility(View.GONE);
|
||||
draft.overlay.setVisibility(View.VISIBLE);
|
||||
|
||||
if(draft.state==AttachmentUploadState.UPLOADING || draft.state==AttachmentUploadState.PROCESSING || draft.state==AttachmentUploadState.QUEUED){
|
||||
draft.progressBar.setVisibility(View.GONE);
|
||||
}else if(draft.state==AttachmentUploadState.ERROR){
|
||||
draft.setOverlayVisible(true, false);
|
||||
}
|
||||
|
||||
return thumb;
|
||||
@@ -855,67 +949,92 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
draft.uri=uri;
|
||||
draft.description=description;
|
||||
attachmentsView.addView(createMediaAttachmentView(draft));
|
||||
allAttachments.add(draft);
|
||||
attachments.add(draft);
|
||||
attachmentsView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void uploadMediaAttachment(DraftMediaAttachment attachment){
|
||||
if(uploadingAttachment!=null)
|
||||
throw new IllegalStateException("there is already an attachment being uploaded");
|
||||
uploadingAttachment=attachment;
|
||||
if(areThereAnyUploadingAttachments()){
|
||||
throw new IllegalStateException("there is already an attachment being uploaded");
|
||||
}
|
||||
attachment.state=AttachmentUploadState.UPLOADING;
|
||||
attachment.progressBar.setVisibility(View.VISIBLE);
|
||||
ObjectAnimator rotationAnimator=ObjectAnimator.ofFloat(attachment.progressBar, View.ROTATION, 0f, 360f);
|
||||
rotationAnimator.setInterpolator(new LinearInterpolator());
|
||||
rotationAnimator.setDuration(1500);
|
||||
rotationAnimator.setRepeatCount(ObjectAnimator.INFINITE);
|
||||
rotationAnimator.start();
|
||||
attachment.progressBarAnimator=rotationAnimator;
|
||||
int maxSize=0;
|
||||
String contentType=getActivity().getContentResolver().getType(attachment.uri);
|
||||
if(contentType!=null && contentType.startsWith("image/")){
|
||||
maxSize=2_073_600; // TODO get this from instance configuration when it gets added there
|
||||
}
|
||||
attachment.uploadStateTitle.setText("");
|
||||
attachment.uploadStateText.setText("");
|
||||
attachment.progressBar.setProgress(0);
|
||||
attachment.speedTracker.reset();
|
||||
attachment.speedTracker.addSample(0);
|
||||
attachment.uploadRequest=(UploadAttachment) new UploadAttachment(attachment.uri, maxSize, attachment.description)
|
||||
.setProgressListener(new ProgressListener(){
|
||||
@Override
|
||||
public void onProgress(long transferred, long total){
|
||||
if(updateUploadEtaRunnable==null){
|
||||
UiUtils.runOnUiThread(updateUploadEtaRunnable=ComposeFragment.this::updateUploadETAs, 100);
|
||||
}
|
||||
int progress=Math.round(transferred/(float)total*attachment.progressBar.getMax());
|
||||
if(Build.VERSION.SDK_INT>=24)
|
||||
attachment.progressBar.setProgress(progress, true);
|
||||
else
|
||||
attachment.progressBar.setProgress(progress);
|
||||
|
||||
attachment.speedTracker.setTotalBytes(total);
|
||||
attachment.uploadStateTitle.setText(getString(R.string.file_upload_progress, UiUtils.formatFileSize(getActivity(), transferred, true), UiUtils.formatFileSize(getActivity(), total, true)));
|
||||
attachment.speedTracker.addSample(transferred);
|
||||
}
|
||||
})
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Attachment result){
|
||||
attachment.serverAttachment=result;
|
||||
attachment.uploadRequest=null;
|
||||
uploadingAttachment=null;
|
||||
attachments.add(attachment);
|
||||
attachment.progressBar.setVisibility(View.GONE);
|
||||
if(!queuedAttachments.isEmpty())
|
||||
uploadMediaAttachment(queuedAttachments.remove(0));
|
||||
updatePublishButtonState();
|
||||
|
||||
rotationAnimator.cancel();
|
||||
V.setVisibilityAnimated(attachment.overlay, View.GONE);
|
||||
V.setVisibilityAnimated(attachment.infoBar, View.VISIBLE);
|
||||
if(TextUtils.isEmpty(result.url)){
|
||||
attachment.state=AttachmentUploadState.PROCESSING;
|
||||
attachment.processingPollingRunnable=()->pollForMediaAttachmentProcessing(attachment);
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
attachment.uploadStateTitle.setText(R.string.upload_processing);
|
||||
attachment.uploadStateText.setText("");
|
||||
UiUtils.runOnUiThread(attachment.processingPollingRunnable, 1000);
|
||||
if(!areThereAnyUploadingAttachments())
|
||||
uploadNextQueuedAttachment();
|
||||
}else{
|
||||
finishMediaAttachmentUpload(attachment);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
attachment.uploadRequest=null;
|
||||
uploadingAttachment=null;
|
||||
failedAttachments.add(attachment);
|
||||
// error.showToast(getActivity());
|
||||
Toast.makeText(getActivity(), R.string.image_upload_failed, Toast.LENGTH_SHORT).show();
|
||||
attachment.progressBarAnimator=null;
|
||||
attachment.state=AttachmentUploadState.ERROR;
|
||||
attachment.uploadStateTitle.setText(R.string.upload_failed);
|
||||
if(error instanceof MastodonErrorResponse er){
|
||||
if(er.underlyingException instanceof SocketException || er.underlyingException instanceof UnknownHostException || er.underlyingException instanceof InterruptedIOException)
|
||||
attachment.uploadStateText.setText(R.string.upload_error_connection_lost);
|
||||
else
|
||||
attachment.uploadStateText.setText(er.error);
|
||||
}else{
|
||||
attachment.uploadStateText.setText("");
|
||||
}
|
||||
attachment.retryButton.setImageResource(R.drawable.ic_fluent_arrow_clockwise_24_filled);
|
||||
attachment.retryButton.setContentDescription(getString(R.string.retry_upload));
|
||||
|
||||
rotationAnimator.cancel();
|
||||
V.setVisibilityAnimated(attachment.retryButton, View.VISIBLE);
|
||||
V.setVisibilityAnimated(attachment.progressBar, View.GONE);
|
||||
|
||||
if(!queuedAttachments.isEmpty())
|
||||
uploadMediaAttachment(queuedAttachments.remove(0));
|
||||
if(!areThereAnyUploadingAttachments())
|
||||
uploadNextQueuedAttachment();
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
@@ -923,37 +1042,109 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
|
||||
private void onRemoveMediaAttachmentClick(View v){
|
||||
DraftMediaAttachment att=(DraftMediaAttachment) v.getTag();
|
||||
if(att==uploadingAttachment){
|
||||
att.uploadRequest.cancel();
|
||||
uploadingAttachment=null;
|
||||
if(!queuedAttachments.isEmpty())
|
||||
uploadMediaAttachment(queuedAttachments.remove(0));
|
||||
}else{
|
||||
attachments.remove(att);
|
||||
queuedAttachments.remove(att);
|
||||
failedAttachments.remove(att);
|
||||
}
|
||||
allAttachments.remove(att);
|
||||
if(att.isUploadingOrProcessing())
|
||||
att.cancelUpload();
|
||||
attachments.remove(att);
|
||||
uploadNextQueuedAttachment();
|
||||
attachmentsView.removeView(att.view);
|
||||
if(getMediaAttachmentsCount()==0)
|
||||
attachmentsView.setVisibility(View.GONE);
|
||||
updatePublishButtonState();
|
||||
pollBtn.setEnabled(attachments.isEmpty() && queuedAttachments.isEmpty() && failedAttachments.isEmpty() && uploadingAttachment==null);
|
||||
pollBtn.setEnabled(attachments.isEmpty());
|
||||
mediaBtn.setEnabled(true);
|
||||
}
|
||||
|
||||
private void onRetryMediaUploadClick(View v){
|
||||
private void onRetryOrCancelMediaUploadClick(View v){
|
||||
DraftMediaAttachment att=(DraftMediaAttachment) v.getTag();
|
||||
if(failedAttachments.remove(att)){
|
||||
V.setVisibilityAnimated(att.retryButton, View.GONE);
|
||||
if(att.state==AttachmentUploadState.ERROR){
|
||||
att.retryButton.setImageResource(R.drawable.ic_fluent_dismiss_24_filled);
|
||||
att.retryButton.setContentDescription(getString(R.string.cancel));
|
||||
V.setVisibilityAnimated(att.progressBar, View.VISIBLE);
|
||||
if(uploadingAttachment==null)
|
||||
uploadMediaAttachment(att);
|
||||
else
|
||||
queuedAttachments.add(att);
|
||||
att.state=AttachmentUploadState.QUEUED;
|
||||
if(!areThereAnyUploadingAttachments()){
|
||||
uploadNextQueuedAttachment();
|
||||
}
|
||||
}else{
|
||||
onRemoveMediaAttachmentClick(v);
|
||||
}
|
||||
}
|
||||
|
||||
private void pollForMediaAttachmentProcessing(DraftMediaAttachment attachment){
|
||||
attachment.processingPollingRequest=(GetAttachmentByID) new GetAttachmentByID(attachment.serverAttachment.id)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Attachment result){
|
||||
attachment.processingPollingRequest=null;
|
||||
if(!TextUtils.isEmpty(result.url)){
|
||||
attachment.processingPollingRunnable=null;
|
||||
attachment.serverAttachment=result;
|
||||
finishMediaAttachmentUpload(attachment);
|
||||
}else if(getActivity()!=null){
|
||||
UiUtils.runOnUiThread(attachment.processingPollingRunnable, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
attachment.processingPollingRequest=null;
|
||||
if(getActivity()!=null)
|
||||
UiUtils.runOnUiThread(attachment.processingPollingRunnable, 1000);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
private void finishMediaAttachmentUpload(DraftMediaAttachment attachment){
|
||||
if(attachment.state!=AttachmentUploadState.PROCESSING && attachment.state!=AttachmentUploadState.UPLOADING)
|
||||
throw new IllegalStateException("Unexpected state "+attachment.state);
|
||||
attachment.uploadRequest=null;
|
||||
attachment.state=AttachmentUploadState.DONE;
|
||||
attachment.progressBar.setVisibility(View.GONE);
|
||||
if(!areThereAnyUploadingAttachments())
|
||||
uploadNextQueuedAttachment();
|
||||
updatePublishButtonState();
|
||||
|
||||
if(attachment.progressBarAnimator!=null){
|
||||
attachment.progressBarAnimator.cancel();
|
||||
attachment.progressBarAnimator=null;
|
||||
}
|
||||
attachment.setOverlayVisible(false, true);
|
||||
}
|
||||
|
||||
private void uploadNextQueuedAttachment(){
|
||||
for(DraftMediaAttachment att:attachments){
|
||||
if(att.state==AttachmentUploadState.QUEUED){
|
||||
uploadMediaAttachment(att);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean areThereAnyUploadingAttachments(){
|
||||
for(DraftMediaAttachment att:attachments){
|
||||
if(att.state==AttachmentUploadState.UPLOADING)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void updateUploadETAs(){
|
||||
if(!areThereAnyUploadingAttachments()){
|
||||
UiUtils.removeCallbacks(updateUploadEtaRunnable);
|
||||
updateUploadEtaRunnable=null;
|
||||
return;
|
||||
}
|
||||
for(DraftMediaAttachment att:attachments){
|
||||
if(att.state==AttachmentUploadState.UPLOADING){
|
||||
long eta=att.speedTracker.updateAndGetETA();
|
||||
// Log.i(TAG, "onProgress: transfer speed "+UiUtils.formatFileSize(getActivity(), Math.round(att.speedTracker.getLastSpeed()), false)+" average "+UiUtils.formatFileSize(getActivity(), Math.round(att.speedTracker.getAverageSpeed()), false)+" eta "+eta);
|
||||
String time=String.format("%d:%02d", eta/60, eta%60);
|
||||
att.uploadStateText.setText(getString(R.string.file_upload_time_remaining, time));
|
||||
}
|
||||
}
|
||||
UiUtils.runOnUiThread(updateUploadEtaRunnable, 100);
|
||||
}
|
||||
|
||||
private void onEditMediaDescriptionClick(View v){
|
||||
DraftMediaAttachment att=(DraftMediaAttachment) v.getTag();
|
||||
if(att.serverAttachment==null)
|
||||
@@ -996,7 +1187,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
pollOptionsView.startDragging(option.view);
|
||||
return true;
|
||||
});
|
||||
option.edit.addTextChangedListener(new SimpleTextWatcher(e->updatePublishButtonState()));
|
||||
option.edit.addTextChangedListener(new SimpleTextWatcher(e->{
|
||||
if(!creatingView)
|
||||
pollChanged=true;
|
||||
updatePublishButtonState();
|
||||
}));
|
||||
option.edit.setFilters(new InputFilter[]{new InputFilter.LengthFilter(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxCharactersPerOption>0 ? instance.configuration.polls.maxCharactersPerOption : 50)});
|
||||
|
||||
pollOptionsView.addView(option.view);
|
||||
@@ -1016,6 +1211,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
private void onSwapPollOptions(int oldIndex, int newIndex){
|
||||
pollOptions.add(newIndex, pollOptions.remove(oldIndex));
|
||||
updatePollOptionHints();
|
||||
pollChanged=true;
|
||||
}
|
||||
|
||||
private void showPollDurationMenu(){
|
||||
@@ -1039,6 +1235,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
default -> throw new IllegalStateException("Unexpected value: "+item.getItemId());
|
||||
};
|
||||
pollDurationView.setText(getString(R.string.compose_poll_duration, pollDurationStr=item.getTitle().toString()));
|
||||
pollChanged=true;
|
||||
return true;
|
||||
});
|
||||
menu.show();
|
||||
@@ -1055,11 +1252,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
spoilerEdit.setText("");
|
||||
spoilerBtn.setSelected(false);
|
||||
mainEditText.requestFocus();
|
||||
updateCharCounter();
|
||||
}
|
||||
}
|
||||
|
||||
private int getMediaAttachmentsCount(){
|
||||
return allAttachments.size();
|
||||
return attachments.size();
|
||||
}
|
||||
|
||||
private void onVisibilityClick(View v){
|
||||
@@ -1095,6 +1293,47 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
menu.show();
|
||||
}
|
||||
|
||||
private void loadDefaultStatusVisibility(Bundle savedInstanceState) {
|
||||
if(getArguments().containsKey("replyTo")){
|
||||
replyTo=Parcels.unwrap(getArguments().getParcelable("replyTo"));
|
||||
statusVisibility = replyTo.visibility;
|
||||
}
|
||||
|
||||
// A saved privacy setting from a previous compose session wins over the reply visibility
|
||||
if(savedInstanceState !=null){
|
||||
statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility");
|
||||
}
|
||||
|
||||
new GetPreferences()
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Preferences result){
|
||||
// Only override the reply visibility if our preference is more private
|
||||
if (result.postingDefaultVisibility.isLessVisibleThan(statusVisibility)) {
|
||||
statusVisibility = switch (result.postingDefaultVisibility) {
|
||||
case PUBLIC -> StatusPrivacy.PUBLIC;
|
||||
case UNLISTED -> StatusPrivacy.UNLISTED;
|
||||
case PRIVATE -> StatusPrivacy.PRIVATE;
|
||||
case DIRECT -> StatusPrivacy.DIRECT;
|
||||
};
|
||||
}
|
||||
|
||||
// A saved privacy setting from a previous compose session wins over all
|
||||
if(savedInstanceState !=null){
|
||||
statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility");
|
||||
}
|
||||
|
||||
updateVisibilityIcon ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
Log.w(TAG, "Unable to get user preferences to set default post privacy");
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
private void updateVisibilityIcon(){
|
||||
if(statusVisibility==null){ // TODO find out why this happens
|
||||
statusVisibility=StatusPrivacy.PUBLIC;
|
||||
@@ -1109,6 +1348,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
|
||||
@Override
|
||||
public void onSelectionChanged(int start, int end){
|
||||
if(ignoreSelectionChanges)
|
||||
return;
|
||||
if(start==end && mainEditText.length()>0){
|
||||
ComposeAutocompleteSpan[] spans=mainEditText.getText().getSpans(start, end, ComposeAutocompleteSpan.class);
|
||||
if(spans.length>0){
|
||||
@@ -1179,6 +1420,30 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
finishAutocomplete();
|
||||
}
|
||||
|
||||
private void loadVideoThumbIntoView(ImageView target, Uri uri){
|
||||
MastodonAPIController.runInBackground(()->{
|
||||
Context context=getActivity();
|
||||
if(context==null)
|
||||
return;
|
||||
try{
|
||||
MediaMetadataRetriever mmr=new MediaMetadataRetriever();
|
||||
mmr.setDataSource(context, uri);
|
||||
Bitmap frame=mmr.getFrameAtTime(3_000_000);
|
||||
mmr.release();
|
||||
int size=Math.max(frame.getWidth(), frame.getHeight());
|
||||
int maxSize=V.dp(250);
|
||||
if(size>maxSize){
|
||||
float factor=maxSize/(float)size;
|
||||
frame=Bitmap.createScaledBitmap(frame, Math.round(frame.getWidth()*factor), Math.round(frame.getHeight()*factor), true);
|
||||
}
|
||||
Bitmap finalFrame=frame;
|
||||
target.post(()->target.setImageBitmap(finalFrame));
|
||||
}catch(Exception x){
|
||||
Log.w(TAG, "loadVideoThumbIntoView: error getting video frame", x);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getTitle(){
|
||||
return getString(R.string.new_post);
|
||||
@@ -1199,14 +1464,75 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
public Attachment serverAttachment;
|
||||
public Uri uri;
|
||||
public transient UploadAttachment uploadRequest;
|
||||
public transient GetAttachmentByID processingPollingRequest;
|
||||
public String description;
|
||||
public String mimeType;
|
||||
public AttachmentUploadState state=AttachmentUploadState.QUEUED;
|
||||
|
||||
public transient View view;
|
||||
public transient ProgressBar progressBar;
|
||||
public transient TextView descriptionView;
|
||||
public transient View overlay;
|
||||
public transient View infoBar;
|
||||
public transient Button retryButton;
|
||||
public transient ImageButton retryButton;
|
||||
public transient ObjectAnimator progressBarAnimator;
|
||||
public transient Runnable processingPollingRunnable;
|
||||
public transient ImageView imageView;
|
||||
public transient TextView uploadStateTitle, uploadStateText;
|
||||
public transient TransferSpeedTracker speedTracker=new TransferSpeedTracker();
|
||||
|
||||
public void cancelUpload(){
|
||||
switch(state){
|
||||
case UPLOADING -> {
|
||||
if(uploadRequest!=null){
|
||||
uploadRequest.cancel();
|
||||
uploadRequest=null;
|
||||
}
|
||||
}
|
||||
case PROCESSING -> {
|
||||
if(processingPollingRunnable!=null){
|
||||
UiUtils.removeCallbacks(processingPollingRunnable);
|
||||
processingPollingRunnable=null;
|
||||
}
|
||||
if(processingPollingRequest!=null){
|
||||
processingPollingRequest.cancel();
|
||||
processingPollingRequest=null;
|
||||
}
|
||||
}
|
||||
default -> throw new IllegalStateException("Unexpected state "+state);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isUploadingOrProcessing(){
|
||||
return state==AttachmentUploadState.UPLOADING || state==AttachmentUploadState.PROCESSING;
|
||||
}
|
||||
|
||||
public void setOverlayVisible(boolean visible, boolean animated){
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.S){
|
||||
if(visible){
|
||||
imageView.setRenderEffect(RenderEffect.createBlurEffect(V.dp(16), V.dp(16), Shader.TileMode.REPEAT));
|
||||
}else{
|
||||
imageView.setRenderEffect(null);
|
||||
}
|
||||
}
|
||||
int infoBarVis=visible ? View.GONE : View.VISIBLE;
|
||||
int overlayVis=visible ? View.VISIBLE : View.GONE;
|
||||
if(animated){
|
||||
V.setVisibilityAnimated(infoBar, infoBarVis);
|
||||
V.setVisibilityAnimated(overlay, overlayVis);
|
||||
}else{
|
||||
infoBar.setVisibility(infoBarVis);
|
||||
overlay.setVisibility(overlayVis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum AttachmentUploadState{
|
||||
QUEUED,
|
||||
UPLOADING,
|
||||
PROCESSING,
|
||||
ERROR,
|
||||
DONE
|
||||
}
|
||||
|
||||
private static class DraftPollOption{
|
||||
|
||||
@@ -2,13 +2,9 @@ package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Fragment;
|
||||
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;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -18,20 +14,14 @@ 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;
|
||||
import org.joinmastodon.android.PushNotificationReceiver;
|
||||
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.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;
|
||||
import org.parceler.Parcels;
|
||||
@@ -41,15 +31,12 @@ import java.util.ArrayList;
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.FragmentStackActivity;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.fragments.LoaderFragment;
|
||||
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{
|
||||
@@ -141,7 +128,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
}
|
||||
});
|
||||
}
|
||||
}else{
|
||||
}
|
||||
|
||||
return content;
|
||||
|
||||
@@ -23,9 +23,11 @@ import android.widget.Toolbar;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
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.SelfUpdateStateChangedEvent;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
@@ -33,6 +35,7 @@ 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.updater.GithubSelfUpdater;
|
||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -101,6 +104,11 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(GithubSelfUpdater.needSelfUpdating()){
|
||||
E.register(this);
|
||||
updateUpdateState(GithubSelfUpdater.getInstance().getState());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -397,4 +405,22 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
scrollToTop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView(){
|
||||
super.onDestroyView();
|
||||
if(GithubSelfUpdater.needSelfUpdating()){
|
||||
E.unregister(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUpdateState(GithubSelfUpdater.UpdateState state){
|
||||
if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING)
|
||||
getToolbar().getMenu().findItem(R.id.settings).setIcon(R.drawable.ic_settings_24_badged);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onSelfUpdateStateChanged(SelfUpdateStateChangedEvent ev){
|
||||
updateUpdateState(ev.state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
@@ -16,15 +12,12 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.PaginatedResponse;
|
||||
import org.joinmastodon.android.model.Poll;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -33,7 +26,6 @@ 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.SimpleCallback;
|
||||
@@ -160,91 +152,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private int bgColor=UiUtils.getThemeColor(getActivity(), android.R.attr.colorBackground);
|
||||
private int borderColor=UiUtils.getThemeColor(getActivity(), R.attr.colorPollVoted);
|
||||
private RectF rect=new RectF();
|
||||
|
||||
@Override
|
||||
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
int pos=0;
|
||||
for(int i=0;i<parent.getChildCount();i++){
|
||||
View child=parent.getChildAt(i);
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
||||
pos=holder.getAbsoluteAdapterPosition();
|
||||
boolean inset=(holder instanceof StatusDisplayItem.Holder<?> sdi) && sdi.getItem().inset;
|
||||
if(inset){
|
||||
if(rect.isEmpty()){
|
||||
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());
|
||||
}
|
||||
}else if(!rect.isEmpty()){
|
||||
drawInsetBackground(c);
|
||||
rect.setEmpty();
|
||||
}
|
||||
}
|
||||
if(!rect.isEmpty()){
|
||||
if(pos<displayItems.size()-1 && displayItems.get(pos+1).inset){
|
||||
rect.bottom=parent.getHeight()+V.dp(10);
|
||||
}
|
||||
drawInsetBackground(c);
|
||||
rect.setEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
paint.setStrokeWidth(V.dp(1));
|
||||
paint.setColor(borderColor);
|
||||
rect.inset(paint.getStrokeWidth()/2f, paint.getStrokeWidth()/2f);
|
||||
c.drawRoundRect(rect, V.dp(4), V.dp(4), paint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
|
||||
if(holder instanceof StatusDisplayItem.Holder<?> sdi){
|
||||
boolean inset=sdi.getItem().inset;
|
||||
int pos=holder.getAbsoluteAdapterPosition();
|
||||
if(inset){
|
||||
boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
|
||||
boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
|
||||
int pad;
|
||||
if(holder instanceof ImageStatusDisplayItem.Holder || holder instanceof LinkCardStatusDisplayItem.Holder)
|
||||
pad=V.dp(16);
|
||||
else
|
||||
pad=V.dp(12);
|
||||
boolean insetLeft=true, insetRight=true;
|
||||
if(holder instanceof ImageStatusDisplayItem.Holder<?> img){
|
||||
PhotoLayoutHelper.TiledLayoutResult layout=img.getItem().tiledLayout;
|
||||
PhotoLayoutHelper.TiledLayoutResult.Tile tile=img.getItem().thisTile;
|
||||
// only inset those items that are on the edges of the layout
|
||||
insetLeft=tile.startCol==0;
|
||||
insetRight=tile.startCol+tile.colSpan==layout.columnSizes.length;
|
||||
// inset all items in the bottom row
|
||||
if(tile.startRow+tile.rowSpan==layout.rowSizes.length)
|
||||
bottomSiblingInset=false;
|
||||
}
|
||||
if(insetLeft)
|
||||
outRect.left=pad;
|
||||
if(insetRight)
|
||||
outRect.right=pad;
|
||||
if(!topSiblingInset)
|
||||
outRect.top=pad;
|
||||
if(!bottomSiblingInset)
|
||||
outRect.bottom=pad;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
list.addItemDecoration(new InsetStatusItemDecoration(this));
|
||||
}
|
||||
|
||||
private Notification getNotificationByID(String id){
|
||||
@@ -268,4 +176,5 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -326,7 +326,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
if(postsFragment==null){
|
||||
postsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.DEFAULT, true);
|
||||
postsWithRepliesFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.INCLUDE_REPLIES, false);
|
||||
pinnedPostsFragment =AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.PINNED, false);
|
||||
pinnedPostsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.PINNED, false);
|
||||
mediaFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.MEDIA, false);
|
||||
aboutFragment=new ProfileAboutFragment();
|
||||
aboutFragment.setFields(fields);
|
||||
@@ -421,9 +421,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
HtmlParser.parseCustomEmoji(ssb, account.emojis);
|
||||
name.setText(ssb);
|
||||
setTitle(ssb);
|
||||
|
||||
boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account);
|
||||
|
||||
if(account.locked){
|
||||
ssb=new SpannableStringBuilder("@");
|
||||
ssb.append(account.acct);
|
||||
if(isSelf){
|
||||
ssb.append('@');
|
||||
ssb.append(AccountSessionManager.getInstance().getAccount(accountID).domain);
|
||||
}
|
||||
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());
|
||||
@@ -431,7 +438,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
ssb.append(getString(R.string.manually_approves_followers), new ImageSpan(lock, ImageSpan.ALIGN_BOTTOM), 0);
|
||||
username.setText(ssb);
|
||||
}else{
|
||||
username.setText('@'+account.acct);
|
||||
// noinspection SetTextI18n
|
||||
username.setText('@'+account.acct+(isSelf ? ('@'+AccountSessionManager.getInstance().getAccount(accountID).domain) : ""));
|
||||
}
|
||||
CharSequence parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||
if(TextUtils.isEmpty(parsedBio)){
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
@@ -14,15 +15,21 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
@@ -31,11 +38,13 @@ import org.joinmastodon.android.api.MastodonAPIController;
|
||||
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.events.SelfUpdateStateChangedEvent;
|
||||
import org.joinmastodon.android.model.PushNotification;
|
||||
import org.joinmastodon.android.model.PushSubscription;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Consumer;
|
||||
@@ -47,7 +56,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||
import me.grishka.appkit.imageloader.ImageCache;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
@@ -73,6 +81,14 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
accountID=getArguments().getString("account");
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
|
||||
if(GithubSelfUpdater.needSelfUpdating()){
|
||||
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
||||
GithubSelfUpdater.UpdateState state=updater.getState();
|
||||
if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING){
|
||||
items.add(new UpdateItem());
|
||||
}
|
||||
}
|
||||
|
||||
items.add(new HeaderItem(R.string.settings_theme));
|
||||
items.add(themeItem=new ThemeItem());
|
||||
items.add(new SwitchItem(R.string.theme_true_black, R.drawable.ic_fluent_dark_theme_24_regular, GlobalUserPreferences.trueBlackTheme, this::onTrueBlackThemeChanged));
|
||||
@@ -131,7 +147,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
// Add 32dp gaps between sections
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
|
||||
if((holder instanceof HeaderViewHolder || holder instanceof FooterViewHolder) && holder.getAbsoluteAdapterPosition()>0)
|
||||
if((holder instanceof HeaderViewHolder || holder instanceof FooterViewHolder) && holder.getAbsoluteAdapterPosition()>1)
|
||||
outRect.top=V.dp(32);
|
||||
}
|
||||
});
|
||||
@@ -155,6 +171,20 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
if(GithubSelfUpdater.needSelfUpdating())
|
||||
E.register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView(){
|
||||
super.onDestroyView();
|
||||
if(GithubSelfUpdater.needSelfUpdating())
|
||||
E.unregister(this);
|
||||
}
|
||||
|
||||
private void onThemePreferenceClick(GlobalUserPreferences.ThemePreference theme){
|
||||
GlobalUserPreferences.theme=theme;
|
||||
GlobalUserPreferences.save();
|
||||
@@ -294,6 +324,16 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
});
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onSelfUpdateStateChanged(SelfUpdateStateChangedEvent ev){
|
||||
if(items.get(0) instanceof UpdateItem item){
|
||||
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(0);
|
||||
if(holder instanceof UpdateViewHolder uvh){
|
||||
uvh.bind(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class Item{
|
||||
public abstract int getViewType();
|
||||
}
|
||||
@@ -395,6 +435,14 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateItem extends Item{
|
||||
|
||||
@Override
|
||||
public int getViewType(){
|
||||
return 7;
|
||||
}
|
||||
}
|
||||
|
||||
private class SettingsAdapter extends RecyclerView.Adapter<BindableViewHolder<Item>>{
|
||||
@NonNull
|
||||
@Override
|
||||
@@ -408,6 +456,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
case 4 -> new TextViewHolder();
|
||||
case 5 -> new HeaderViewHolder(true);
|
||||
case 6 -> new FooterViewHolder();
|
||||
case 7 -> new UpdateViewHolder();
|
||||
default -> throw new IllegalStateException("Unexpected value: "+viewType);
|
||||
};
|
||||
}
|
||||
@@ -609,4 +658,74 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
text.setText(item.text);
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateViewHolder extends BindableViewHolder<UpdateItem>{
|
||||
|
||||
private final TextView text;
|
||||
private final Button button;
|
||||
private final ImageButton cancelBtn;
|
||||
private final ProgressBar progress;
|
||||
|
||||
private ObjectAnimator rotationAnimator;
|
||||
private Runnable progressUpdater=this::updateProgress;
|
||||
|
||||
public UpdateViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_update, list);
|
||||
text=findViewById(R.id.text);
|
||||
button=findViewById(R.id.button);
|
||||
cancelBtn=findViewById(R.id.cancel_btn);
|
||||
progress=findViewById(R.id.progress);
|
||||
button.setOnClickListener(v->{
|
||||
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
||||
switch(updater.getState()){
|
||||
case UPDATE_AVAILABLE -> updater.downloadUpdate();
|
||||
case DOWNLOADED -> updater.installUpdate(getActivity());
|
||||
}
|
||||
});
|
||||
cancelBtn.setOnClickListener(v->GithubSelfUpdater.getInstance().cancelDownload());
|
||||
rotationAnimator=ObjectAnimator.ofFloat(progress, View.ROTATION, 0f, 360f);
|
||||
rotationAnimator.setInterpolator(new LinearInterpolator());
|
||||
rotationAnimator.setDuration(1500);
|
||||
rotationAnimator.setRepeatCount(ObjectAnimator.INFINITE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(UpdateItem item){
|
||||
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
||||
GithubSelfUpdater.UpdateInfo info=updater.getUpdateInfo();
|
||||
GithubSelfUpdater.UpdateState state=updater.getState();
|
||||
if(state!=GithubSelfUpdater.UpdateState.DOWNLOADED){
|
||||
text.setText(getString(R.string.update_available, info.version));
|
||||
button.setText(getString(R.string.download_update, UiUtils.formatFileSize(getActivity(), info.size, false)));
|
||||
}else{
|
||||
text.setText(getString(R.string.update_ready, info.version));
|
||||
button.setText(R.string.install_update);
|
||||
}
|
||||
if(state==GithubSelfUpdater.UpdateState.DOWNLOADING){
|
||||
rotationAnimator.start();
|
||||
button.setVisibility(View.INVISIBLE);
|
||||
cancelBtn.setVisibility(View.VISIBLE);
|
||||
progress.setVisibility(View.VISIBLE);
|
||||
updateProgress();
|
||||
}else{
|
||||
rotationAnimator.cancel();
|
||||
button.setVisibility(View.VISIBLE);
|
||||
cancelBtn.setVisibility(View.GONE);
|
||||
progress.setVisibility(View.GONE);
|
||||
progress.removeCallbacks(progressUpdater);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateProgress(){
|
||||
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
||||
if(updater.getState()!=GithubSelfUpdater.UpdateState.DOWNLOADING)
|
||||
return;
|
||||
int value=Math.round(progress.getMax()*updater.getDownloadProgress());
|
||||
if(Build.VERSION.SDK_INT>=24)
|
||||
progress.setProgress(value, true);
|
||||
else
|
||||
progress.setProgress(value);
|
||||
progress.postDelayed(progressUpdater, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusEditHistory;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class StatusEditHistoryFragment extends StatusListFragment{
|
||||
private String id;
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
id=getArguments().getString("id");
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
setTitle(R.string.edit_history);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
new GetStatusEditHistory(id)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
Collections.sort(result, Comparator.comparing((Status s)->s.createdAt).reversed());
|
||||
onDataLoaded(result, false);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false);
|
||||
int idx=data.indexOf(s);
|
||||
if(idx>=0){
|
||||
String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault()));
|
||||
String action="";
|
||||
if(idx==data.size()-1){
|
||||
action=getString(R.string.edit_original_post);
|
||||
}else{
|
||||
enum StatusEditChangeType{
|
||||
TEXT_CHANGED,
|
||||
SPOILER_ADDED,
|
||||
SPOILER_REMOVED,
|
||||
SPOILER_CHANGED,
|
||||
POLL_ADDED,
|
||||
POLL_REMOVED,
|
||||
POLL_CHANGED,
|
||||
MEDIA_ADDED,
|
||||
MEDIA_REMOVED,
|
||||
MEDIA_REORDERED,
|
||||
MARKED_SENSITIVE,
|
||||
MARKED_NOT_SENSITIVE
|
||||
}
|
||||
EnumSet<StatusEditChangeType> changes=EnumSet.noneOf(StatusEditChangeType.class);
|
||||
Status prev=data.get(idx+1);
|
||||
|
||||
if(!Objects.equals(s.content, prev.content)){
|
||||
changes.add(StatusEditChangeType.TEXT_CHANGED);
|
||||
}
|
||||
if(!Objects.equals(s.spoilerText, prev.spoilerText)){
|
||||
if(s.spoilerText==null){
|
||||
changes.add(StatusEditChangeType.SPOILER_REMOVED);
|
||||
}else if(prev.spoilerText==null){
|
||||
changes.add(StatusEditChangeType.SPOILER_ADDED);
|
||||
}else{
|
||||
changes.add(StatusEditChangeType.SPOILER_CHANGED);
|
||||
}
|
||||
}
|
||||
if(s.poll!=null || prev.poll!=null){
|
||||
if(s.poll==null){
|
||||
changes.add(StatusEditChangeType.POLL_REMOVED);
|
||||
}else if(prev.poll==null){
|
||||
changes.add(StatusEditChangeType.POLL_ADDED);
|
||||
}else if(!s.poll.id.equals(prev.poll.id)){
|
||||
changes.add(StatusEditChangeType.POLL_CHANGED);
|
||||
}
|
||||
}
|
||||
List<String> newAttachmentIDs=s.mediaAttachments.stream().map(att->att.id).collect(Collectors.toList());
|
||||
List<String> prevAttachmentIDs=s.mediaAttachments.stream().map(att->att.id).collect(Collectors.toList());
|
||||
boolean addedOrRemoved=false;
|
||||
if(!newAttachmentIDs.containsAll(prevAttachmentIDs)){
|
||||
changes.add(StatusEditChangeType.MEDIA_REMOVED);
|
||||
addedOrRemoved=true;
|
||||
}
|
||||
if(!prevAttachmentIDs.containsAll(newAttachmentIDs)){
|
||||
changes.add(StatusEditChangeType.MEDIA_ADDED);
|
||||
addedOrRemoved=true;
|
||||
}
|
||||
if(!addedOrRemoved && !newAttachmentIDs.equals(prevAttachmentIDs)){
|
||||
changes.add(StatusEditChangeType.MEDIA_REORDERED);
|
||||
}
|
||||
if(s.sensitive && !prev.sensitive){
|
||||
changes.add(StatusEditChangeType.MARKED_SENSITIVE);
|
||||
}else if(prev.sensitive && !s.sensitive){
|
||||
changes.add(StatusEditChangeType.MARKED_NOT_SENSITIVE);
|
||||
}
|
||||
|
||||
if(changes.size()==1){
|
||||
action=getString(switch(changes.iterator().next()){
|
||||
case TEXT_CHANGED -> R.string.edit_text_edited;
|
||||
case SPOILER_ADDED -> R.string.edit_spoiler_added;
|
||||
case SPOILER_REMOVED -> R.string.edit_spoiler_removed;
|
||||
case SPOILER_CHANGED -> R.string.edit_spoiler_edited;
|
||||
case POLL_ADDED -> R.string.edit_poll_added;
|
||||
case POLL_REMOVED -> R.string.edit_poll_removed;
|
||||
case POLL_CHANGED -> R.string.edit_poll_edited;
|
||||
case MEDIA_ADDED -> R.string.edit_media_added;
|
||||
case MEDIA_REMOVED -> R.string.edit_media_removed;
|
||||
case MEDIA_REORDERED -> R.string.edit_media_reordered;
|
||||
case MARKED_SENSITIVE -> R.string.edit_marked_sensitive;
|
||||
case MARKED_NOT_SENSITIVE -> R.string.edit_marked_not_sensitive;
|
||||
});
|
||||
}else{
|
||||
action=getString(R.string.edit_multiple_changed);
|
||||
}
|
||||
}
|
||||
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" · "+date, Collections.emptyList(), 0));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addItemDecoration(new InsetStatusItemDecoration(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isItemEnabled(String id){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.events.StatusDeletedEvent;
|
||||
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||
import org.joinmastodon.android.events.StatusUnpinnedEvent;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||
@@ -16,6 +17,7 @@ import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@@ -61,8 +63,6 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
||||
|
||||
protected void onStatusCreated(StatusCreatedEvent ev){}
|
||||
|
||||
protected void onStatusUnpinned(StatusUnpinnedEvent ev){}
|
||||
|
||||
protected Status getContentStatusByID(String id){
|
||||
Status s=getStatusByID(id);
|
||||
return s==null ? null : s.getContentStatus();
|
||||
@@ -138,11 +138,6 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
||||
StatusListFragment.this.onStatusCreated(ev);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onStatusUnpinned(StatusUnpinnedEvent ev){
|
||||
StatusListFragment.this.onStatusUnpinned(ev);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPollUpdated(PollUpdatedEvent ev){
|
||||
if(!ev.accountID.equals(accountID))
|
||||
|
||||
@@ -43,7 +43,7 @@ public class ThreadFragment extends StatusListFragment{
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||
List<StatusDisplayItem> items=super.buildDisplayItems(s);
|
||||
if(s==mainStatus){
|
||||
if(s.id.equals(mainStatus.id)){
|
||||
for(StatusDisplayItem item:items){
|
||||
if(item instanceof TextStatusDisplayItem text)
|
||||
text.textSelectable=true;
|
||||
|
||||
@@ -85,7 +85,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetAccountStatuses(reportAccount.id, offset>0 ? getMaxID() : null, null, count, GetAccountStatuses.Filter.NO_REBLOGS)
|
||||
currentRequest=new GetAccountStatuses(reportAccount.id, offset>0 ? getMaxID() : null, null, count, GetAccountStatuses.Filter.OWN_POSTS_AND_REPLIES)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
@@ -102,6 +102,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
||||
else
|
||||
selectedIDs.add(id);
|
||||
list.invalidate();
|
||||
btn.setEnabled(!selectedIDs.isEmpty());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public enum ExpandMedia {
|
||||
@SerializedName("default")
|
||||
DEFAULT,
|
||||
@SerializedName("show_all")
|
||||
SHOW_ALL,
|
||||
@SerializedName("hide_all")
|
||||
HIDE_ALL;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Preferred common behaviors to be shared across clients.
|
||||
*/
|
||||
public class Preferences extends BaseModel {
|
||||
/**
|
||||
* Default visibility for new posts
|
||||
*/
|
||||
@SerializedName("posting:default:visibility")
|
||||
public StatusPrivacy postingDefaultVisibility;
|
||||
|
||||
/**
|
||||
* Default sensitivity flag for new posts
|
||||
*/
|
||||
@SerializedName("posting:default:sensitive")
|
||||
public boolean postingDefaultSensitive;
|
||||
|
||||
/**
|
||||
* Default language for new posts
|
||||
*/
|
||||
@SerializedName("posting:default:language")
|
||||
public String postingDefaultLanguage;
|
||||
|
||||
/**
|
||||
* Whether media attachments should be automatically displayed or blurred/hidden.
|
||||
*/
|
||||
@SerializedName("reading:expand:media")
|
||||
public ExpandMedia readingExpandMedia;
|
||||
|
||||
/**
|
||||
* Whether CWs should be expanded by default.
|
||||
*/
|
||||
@SerializedName("reading:expand:spoilers")
|
||||
public boolean readingExpandSpoilers;
|
||||
}
|
||||
@@ -37,6 +37,7 @@ public class Status extends BaseModel implements DisplayItemsParent{
|
||||
public int reblogsCount;
|
||||
public int favouritesCount;
|
||||
public int repliesCount;
|
||||
public Instant editedAt;
|
||||
|
||||
public String url;
|
||||
public String inReplyToId;
|
||||
|
||||
@@ -4,11 +4,25 @@ import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public enum StatusPrivacy{
|
||||
@SerializedName("public")
|
||||
PUBLIC,
|
||||
PUBLIC(0),
|
||||
@SerializedName("unlisted")
|
||||
UNLISTED,
|
||||
UNLISTED(1),
|
||||
@SerializedName("private")
|
||||
PRIVATE,
|
||||
PRIVATE(2),
|
||||
@SerializedName("direct")
|
||||
DIRECT;
|
||||
DIRECT(3);
|
||||
|
||||
private int privacy;
|
||||
|
||||
StatusPrivacy(int privacy) {
|
||||
this.privacy = privacy;
|
||||
}
|
||||
|
||||
public boolean isLessVisibleThan(StatusPrivacy other) {
|
||||
return privacy > other.getPrivacy();
|
||||
}
|
||||
|
||||
public int getPrivacy() {
|
||||
return privacy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.ui.displayitems;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableStringBuilder;
|
||||
@@ -12,6 +13,7 @@ import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.StatusEditHistoryFragment;
|
||||
import org.joinmastodon.android.fragments.account_list.StatusFavoritesListFragment;
|
||||
import org.joinmastodon.android.fragments.account_list.StatusReblogsListFragment;
|
||||
import org.joinmastodon.android.fragments.account_list.StatusRelatedAccountListFragment;
|
||||
@@ -43,39 +45,35 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
public static class Holder extends StatusDisplayItem.Holder<ExtendedFooterStatusDisplayItem>{
|
||||
private final TextView reblogs, favorites, time;
|
||||
private final View buttonsView;
|
||||
private final TextView time, favoritesCount, reblogsCount, lastEditTime;
|
||||
private final View favorites, reblogs, editHistory;
|
||||
|
||||
public Holder(Context context, ViewGroup parent){
|
||||
super(context, R.layout.display_item_extended_footer, parent);
|
||||
reblogs=findViewById(R.id.reblogs);
|
||||
favorites=findViewById(R.id.favorites);
|
||||
editHistory=findViewById(R.id.edit_history);
|
||||
time=findViewById(R.id.timestamp);
|
||||
buttonsView=findViewById(R.id.button_bar);
|
||||
favoritesCount=findViewById(R.id.favorites_count);
|
||||
reblogsCount=findViewById(R.id.reblogs_count);
|
||||
lastEditTime=findViewById(R.id.last_edited);
|
||||
|
||||
reblogs.setOnClickListener(v->startAccountListFragment(StatusReblogsListFragment.class));
|
||||
favorites.setOnClickListener(v->startAccountListFragment(StatusFavoritesListFragment.class));
|
||||
editHistory.setOnClickListener(v->startEditHistoryFragment());
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
@Override
|
||||
public void onBind(ExtendedFooterStatusDisplayItem item){
|
||||
Status s=item.status;
|
||||
if(s.favouritesCount>0){
|
||||
favorites.setVisibility(View.VISIBLE);
|
||||
favorites.setText(getFormattedPlural(R.plurals.x_favorites, s.favouritesCount));
|
||||
favoritesCount.setText(String.format("%,d", s.favouritesCount));
|
||||
reblogsCount.setText(String.format("%,d", s.reblogsCount));
|
||||
if(s.editedAt!=null){
|
||||
editHistory.setVisibility(View.VISIBLE);
|
||||
lastEditTime.setText(item.parentFragment.getString(R.string.last_edit_at_x, UiUtils.formatRelativeTimestampAsMinutesAgo(itemView.getContext(), s.editedAt)));
|
||||
}else{
|
||||
favorites.setVisibility(View.GONE);
|
||||
}
|
||||
if(s.reblogsCount>0){
|
||||
reblogs.setVisibility(View.VISIBLE);
|
||||
reblogs.setText(getFormattedPlural(R.plurals.x_reblogs, s.reblogsCount));
|
||||
}else{
|
||||
reblogs.setVisibility(View.GONE);
|
||||
}
|
||||
if(s.favouritesCount==0 && s.reblogsCount==0){
|
||||
buttonsView.setVisibility(View.GONE);
|
||||
}else{
|
||||
buttonsView.setVisibility(View.VISIBLE);
|
||||
editHistory.setVisibility(View.GONE);
|
||||
}
|
||||
String timeStr=TIME_FORMATTER.format(item.status.createdAt.atZone(ZoneId.systemDefault()));
|
||||
if(item.status.application!=null && !TextUtils.isEmpty(item.status.application.name)){
|
||||
@@ -108,5 +106,12 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
|
||||
args.putParcelable("status", Parcels.wrap(item.status));
|
||||
Nav.go(item.parentFragment.getActivity(), cls, args);
|
||||
}
|
||||
|
||||
private void startEditHistoryFragment(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", item.parentFragment.getAccountID());
|
||||
args.putString("id", item.status.id);
|
||||
Nav.go(item.parentFragment.getActivity(), StatusEditHistoryFragment.class, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,10 @@ import android.widget.Toast;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusSourceText;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
@@ -135,7 +137,31 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
optionsMenu.setOnMenuItemClickListener(menuItem->{
|
||||
Account account=item.user;
|
||||
int id=menuItem.getItemId();
|
||||
if(id==R.id.delete){
|
||||
if(id==R.id.edit){
|
||||
final Bundle args=new Bundle();
|
||||
args.putString("account", item.parentFragment.getAccountID());
|
||||
args.putParcelable("editStatus", Parcels.wrap(item.status));
|
||||
if(TextUtils.isEmpty(item.status.content) && TextUtils.isEmpty(item.status.spoilerText)){
|
||||
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
||||
}else{
|
||||
new GetStatusSourceText(item.status.id)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(GetStatusSourceText.Response result){
|
||||
args.putString("sourceText", result.text);
|
||||
args.putString("sourceSpoiler", result.spoilerText);
|
||||
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(item.parentFragment.getActivity());
|
||||
}
|
||||
})
|
||||
.wrapProgress(item.parentFragment.getActivity(), R.string.loading, true)
|
||||
.exec(item.parentFragment.getAccountID());
|
||||
}
|
||||
}else if(id==R.id.delete){
|
||||
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{});
|
||||
}else if(id==R.id.delete_and_redraft) {
|
||||
UiUtils.confirmDeleteAndRedraftPost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{});
|
||||
@@ -179,7 +205,10 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
public void onBind(HeaderStatusDisplayItem item){
|
||||
name.setText(item.parsedName);
|
||||
username.setText('@'+item.user.acct);
|
||||
timestamp.setText(UiUtils.formatRelativeTimestamp(itemView.getContext(), item.createdAt));
|
||||
if(item.status==null || item.status.editedAt==null)
|
||||
timestamp.setText(UiUtils.formatRelativeTimestamp(itemView.getContext(), item.createdAt));
|
||||
else
|
||||
timestamp.setText(item.parentFragment.getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(itemView.getContext(), item.status.editedAt)));
|
||||
visibility.setVisibility(item.hasVisibilityToggle && !item.inset ? View.VISIBLE : View.GONE);
|
||||
if(item.hasVisibilityToggle){
|
||||
visibility.setImageResource(item.status.spoilerRevealed ? R.drawable.ic_visibility_off : R.drawable.ic_visibility);
|
||||
@@ -253,6 +282,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
Account account=item.user;
|
||||
Menu menu=optionsMenu.getMenu();
|
||||
boolean isOwnPost=AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account);
|
||||
menu.findItem(R.id.edit).setVisible(item.status!=null && isOwnPost);
|
||||
menu.findItem(R.id.delete).setVisible(item.status!=null && isOwnPost);
|
||||
menu.findItem(R.id.delete_and_redraft).setVisible(item.status!=null && isOwnPost);
|
||||
menu.findItem(R.id.pin).setVisible(item.status!=null && isOwnPost && !item.status.pinned);
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.view.ViewGroup;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.model.DisplayItemsParent;
|
||||
@@ -114,7 +115,7 @@ public abstract class StatusDisplayItem{
|
||||
}
|
||||
if(addFooter){
|
||||
items.add(new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID));
|
||||
if(status.hasGapAfter)
|
||||
if(status.hasGapAfter && !(fragment instanceof ThreadFragment))
|
||||
items.add(new GapStatusDisplayItem(parentID, fragment));
|
||||
}
|
||||
int i=1;
|
||||
|
||||
@@ -129,7 +129,16 @@ public class HtmlParser{
|
||||
}
|
||||
|
||||
public static void parseCustomEmoji(SpannableStringBuilder ssb, List<Emoji> emojis){
|
||||
Map<String, Emoji> emojiByCode=emojis.stream().collect(Collectors.toMap(e->e.shortcode, Function.identity()));
|
||||
Map<String, Emoji> emojiByCode =
|
||||
emojis.stream()
|
||||
.collect(
|
||||
Collectors.toMap(e->e.shortcode, Function.identity(), (emoji1, emoji2) -> {
|
||||
// Ignore duplicate shortcodes and just take the first, it will be
|
||||
// the same emoji anyway
|
||||
return emoji1;
|
||||
})
|
||||
);
|
||||
|
||||
Matcher matcher=EMOJI_CODE_PATTERN.matcher(ssb);
|
||||
int spanCount=0;
|
||||
CustomEmojiSpan lastSpan=null;
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package org.joinmastodon.android.ui.utils;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.NotificationsListFragment;
|
||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{
|
||||
private final BaseStatusListFragment<?> listFragment;
|
||||
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private int bgColor;
|
||||
private int borderColor;
|
||||
private RectF rect=new RectF();
|
||||
|
||||
public InsetStatusItemDecoration(BaseStatusListFragment<?> listFragment){
|
||||
this.listFragment=listFragment;
|
||||
bgColor=UiUtils.getThemeColor(listFragment.getActivity(), android.R.attr.colorBackground);
|
||||
borderColor=UiUtils.getThemeColor(listFragment.getActivity(), R.attr.colorPollVoted);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
List<StatusDisplayItem> displayItems=listFragment.getDisplayItems();
|
||||
int pos=0;
|
||||
for(int i=0; i<parent.getChildCount(); i++){
|
||||
View child=parent.getChildAt(i);
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
||||
pos=holder.getAbsoluteAdapterPosition();
|
||||
boolean inset=(holder instanceof StatusDisplayItem.Holder<?> sdi) && sdi.getItem().inset;
|
||||
if(inset){
|
||||
if(rect.isEmpty()){
|
||||
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());
|
||||
}
|
||||
}else if(!rect.isEmpty()){
|
||||
drawInsetBackground(parent, c);
|
||||
rect.setEmpty();
|
||||
}
|
||||
}
|
||||
if(!rect.isEmpty()){
|
||||
if(pos<displayItems.size()-1 && displayItems.get(pos+1).inset){
|
||||
rect.bottom=parent.getHeight()+V.dp(10);
|
||||
}
|
||||
drawInsetBackground(parent, c);
|
||||
rect.setEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
private void drawInsetBackground(RecyclerView list, 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);
|
||||
paint.setStrokeWidth(V.dp(1));
|
||||
paint.setColor(borderColor);
|
||||
rect.inset(paint.getStrokeWidth()/2f, paint.getStrokeWidth()/2f);
|
||||
c.drawRoundRect(rect, V.dp(4), V.dp(4), paint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
List<StatusDisplayItem> displayItems=listFragment.getDisplayItems();
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
|
||||
if(holder instanceof StatusDisplayItem.Holder<?> sdi){
|
||||
boolean inset=sdi.getItem().inset;
|
||||
int pos=holder.getAbsoluteAdapterPosition();
|
||||
if(inset){
|
||||
boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
|
||||
boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
|
||||
int pad;
|
||||
if(holder instanceof ImageStatusDisplayItem.Holder || holder instanceof LinkCardStatusDisplayItem.Holder)
|
||||
pad=V.dp(16);
|
||||
else
|
||||
pad=V.dp(12);
|
||||
boolean insetLeft=true, insetRight=true;
|
||||
if(holder instanceof ImageStatusDisplayItem.Holder<?> img){
|
||||
PhotoLayoutHelper.TiledLayoutResult layout=img.getItem().tiledLayout;
|
||||
PhotoLayoutHelper.TiledLayoutResult.Tile tile=img.getItem().thisTile;
|
||||
// only inset those items that are on the edges of the layout
|
||||
insetLeft=tile.startCol==0;
|
||||
insetRight=tile.startCol+tile.colSpan==layout.columnSizes.length;
|
||||
// inset all items in the bottom row
|
||||
if(tile.startRow+tile.rowSpan==layout.rowSizes.length)
|
||||
bottomSiblingInset=false;
|
||||
}
|
||||
if(insetLeft)
|
||||
outRect.left=pad;
|
||||
if(insetRight)
|
||||
outRect.right=pad;
|
||||
if(!topSiblingInset)
|
||||
outRect.top=pad;
|
||||
if(!bottomSiblingInset)
|
||||
outRect.bottom=pad;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.joinmastodon.android.ui.utils;
|
||||
|
||||
import android.os.SystemClock;
|
||||
|
||||
public class TransferSpeedTracker{
|
||||
private final double SMOOTHING_FACTOR=0.05;
|
||||
|
||||
private long lastKnownPos;
|
||||
private long lastKnownPosTime;
|
||||
private double lastSpeed;
|
||||
private double averageSpeed;
|
||||
private long totalBytes;
|
||||
|
||||
public void addSample(long position){
|
||||
if(lastKnownPosTime==0){
|
||||
lastKnownPosTime=SystemClock.uptimeMillis();
|
||||
lastKnownPos=position;
|
||||
}else{
|
||||
long time=SystemClock.uptimeMillis();
|
||||
lastSpeed=(position-lastKnownPos)/((double)(time-lastKnownPosTime)/1000.0);
|
||||
lastKnownPos=position;
|
||||
lastKnownPosTime=time;
|
||||
}
|
||||
}
|
||||
|
||||
public double getLastSpeed(){
|
||||
return lastSpeed;
|
||||
}
|
||||
|
||||
public double getAverageSpeed(){
|
||||
return averageSpeed;
|
||||
}
|
||||
|
||||
public long updateAndGetETA(){ // must be called at a constant interval
|
||||
if(averageSpeed==0.0)
|
||||
averageSpeed=lastSpeed;
|
||||
else
|
||||
averageSpeed=SMOOTHING_FACTOR*lastSpeed+(1.0-SMOOTHING_FACTOR)*averageSpeed;
|
||||
return Math.round((totalBytes-lastKnownPos)/averageSpeed);
|
||||
}
|
||||
|
||||
public void setTotalBytes(long totalBytes){
|
||||
this.totalBytes=totalBytes;
|
||||
}
|
||||
|
||||
public void reset(){
|
||||
lastKnownPos=lastKnownPosTime=0;
|
||||
lastSpeed=averageSpeed=0.0;
|
||||
totalBytes=0;
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,7 @@ import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -98,6 +99,7 @@ import okhttp3.MediaType;
|
||||
public class UiUtils{
|
||||
private static Handler mainHandler=new Handler(Looper.getMainLooper());
|
||||
private static final DateTimeFormatter DATE_FORMATTER_SHORT_WITH_YEAR=DateTimeFormatter.ofPattern("d MMM uuuu"), DATE_FORMATTER_SHORT=DateTimeFormatter.ofPattern("d MMM");
|
||||
public static final DateTimeFormatter DATE_TIME_FORMATTER=DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT);
|
||||
|
||||
private UiUtils(){}
|
||||
|
||||
@@ -142,6 +144,23 @@ public class UiUtils{
|
||||
}
|
||||
}
|
||||
|
||||
public static String formatRelativeTimestampAsMinutesAgo(Context context, Instant instant){
|
||||
long t=instant.toEpochMilli();
|
||||
long now=System.currentTimeMillis();
|
||||
long diff=now-t;
|
||||
if(diff<1000L){
|
||||
return context.getString(R.string.time_just_now);
|
||||
}else if(diff<60_000L){
|
||||
int secs=(int)(diff/1000L);
|
||||
return context.getResources().getQuantityString(R.plurals.x_seconds_ago, secs, secs);
|
||||
}else if(diff<3600_000L){
|
||||
int mins=(int)(diff/60_000L);
|
||||
return context.getResources().getQuantityString(R.plurals.x_minutes_ago, mins, mins);
|
||||
}else{
|
||||
return DATE_TIME_FORMATTER.format(instant.atZone(ZoneId.systemDefault()));
|
||||
}
|
||||
}
|
||||
|
||||
public static String formatTimeLeft(Context context, Instant instant){
|
||||
long t=instant.toEpochMilli();
|
||||
long now=System.currentTimeMillis();
|
||||
@@ -195,6 +214,14 @@ public class UiUtils{
|
||||
mainHandler.post(runnable);
|
||||
}
|
||||
|
||||
public static void runOnUiThread(Runnable runnable, long delay){
|
||||
mainHandler.postDelayed(runnable, delay);
|
||||
}
|
||||
|
||||
public static void removeCallbacks(Runnable runnable){
|
||||
mainHandler.removeCallbacks(runnable);
|
||||
}
|
||||
|
||||
/** Linear interpolation between {@code startValue} and {@code endValue} by {@code fraction}. */
|
||||
public static int lerp(int startValue, int endValue, float fraction) {
|
||||
return startValue + Math.round(fraction * (endValue - startValue));
|
||||
@@ -212,6 +239,18 @@ public class UiUtils{
|
||||
return uri.getLastPathSegment();
|
||||
}
|
||||
|
||||
public static String formatFileSize(Context context, long size, boolean atLeastKB){
|
||||
if(size<1024 && !atLeastKB){
|
||||
return context.getString(R.string.file_size_bytes, size);
|
||||
}else if(size<1024*1024){
|
||||
return context.getString(R.string.file_size_kb, size/1024.0);
|
||||
}else if(size<1024*1024*1024){
|
||||
return context.getString(R.string.file_size_mb, size/(1024.0*1024.0));
|
||||
}else{
|
||||
return context.getString(R.string.file_size_gb, size/(1024.0*1024.0*1024.0));
|
||||
}
|
||||
}
|
||||
|
||||
public static MediaType getFileMediaType(File file){
|
||||
String name=file.getName();
|
||||
return MediaType.parse(MimeTypeMap.getSingleton().getMimeTypeFromExtension(name.substring(name.lastIndexOf('.')+1)));
|
||||
|
||||
@@ -54,12 +54,13 @@ public class ComposeEditText extends EditText{
|
||||
// Support receiving images from keyboards
|
||||
@Override
|
||||
public InputConnection onCreateInputConnection(EditorInfo outAttrs){
|
||||
final var ic = super.onCreateInputConnection(outAttrs);
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N_MR1){
|
||||
outAttrs.contentMimeTypes=selectionListener.onGetAllowedMediaMimeTypes();
|
||||
inputConnectionWrapper.setTarget(super.onCreateInputConnection(outAttrs));
|
||||
inputConnectionWrapper.setTarget(ic);
|
||||
return inputConnectionWrapper;
|
||||
}
|
||||
return super.onCreateInputConnection(outAttrs);
|
||||
return ic;
|
||||
}
|
||||
|
||||
// Support pasting images
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.joinmastodon.android.updater;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
|
||||
public abstract class GithubSelfUpdater{
|
||||
private static GithubSelfUpdater instance;
|
||||
|
||||
public static GithubSelfUpdater getInstance(){
|
||||
if(instance==null){
|
||||
try{
|
||||
Class<?> c=Class.forName("org.joinmastodon.android.updater.GithubSelfUpdaterImpl");
|
||||
instance=(GithubSelfUpdater) c.newInstance();
|
||||
}catch(IllegalAccessException|InstantiationException|ClassNotFoundException ignored){
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static boolean needSelfUpdating(){
|
||||
return BuildConfig.BUILD_TYPE.equals("githubRelease") || BuildConfig.BUILD_TYPE.equals("debug");
|
||||
}
|
||||
|
||||
public abstract void maybeCheckForUpdates();
|
||||
|
||||
public abstract GithubSelfUpdater.UpdateState getState();
|
||||
|
||||
public abstract GithubSelfUpdater.UpdateInfo getUpdateInfo();
|
||||
|
||||
public abstract void downloadUpdate();
|
||||
|
||||
public abstract void installUpdate(Activity activity);
|
||||
|
||||
public abstract float getDownloadProgress();
|
||||
|
||||
public abstract void cancelDownload();
|
||||
|
||||
public abstract void handleIntentFromInstaller(Intent intent, Activity activity);
|
||||
|
||||
public enum UpdateState{
|
||||
NO_UPDATE,
|
||||
CHECKING,
|
||||
UPDATE_AVAILABLE,
|
||||
DOWNLOADING,
|
||||
DOWNLOADED
|
||||
}
|
||||
|
||||
public static class UpdateInfo{
|
||||
public String version;
|
||||
public long size;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M16.7096,17.7682C19.4819,17.4391 21.8955,15.7408 22.199,14.1888C22.6769,11.7442 22.6376,8.2231 22.6376,8.2231C22.6376,3.4504 19.4929,2.0516 19.4929,2.0516C17.9073,1.3274 15.1846,1.023 12.356,1H12.2865C9.4579,1.023 6.7369,1.3274 5.1513,2.0516C5.1513,2.0516 2.0066,3.4504 2.0066,8.2231C2.0066,8.5125 2.0051,8.8169 2.0035,9.1339C1.9991,10.0135 1.9943,10.9896 2.0199,12.0083C2.1341,16.6755 2.8805,21.2752 7.2202,22.4175C9.2213,22.944 10.9392,23.0542 12.323,22.9785C14.832,22.8403 16.2406,22.0883 16.2406,22.0883L16.1577,20.2779C16.1577,20.2779 14.3648,20.8402 12.3511,20.7717C10.356,20.7037 8.2496,20.5577 7.9269,18.1221C7.8972,17.9082 7.8823,17.6794 7.8823,17.4391C7.8823,17.4391 9.8408,17.9152 12.323,18.0283C13.8407,18.0974 15.2639,17.9399 16.7096,17.7682ZM18.8747,14.3719V8.5932C18.8747,7.4121 18.5723,6.4736 17.9648,5.7792C17.3382,5.0849 16.518,4.729 15.4997,4.729C14.3212,4.729 13.4291,5.1792 12.8392,6.0799L12.2657,7.0359L11.692,6.0799C11.1023,5.1792 10.21,4.729 9.0316,4.729C8.0134,4.729 7.193,5.0849 6.5664,5.7792C5.9589,6.4736 5.6565,7.4121 5.6565,8.5932V14.3719H7.959V8.763C7.959,7.5805 8.4594,6.9806 9.4602,6.9806C10.5665,6.9806 11.1211,7.6925 11.1211,9.1001V12.1701H13.4101V9.1001C13.4101,7.6925 13.9647,6.9806 15.071,6.9806C16.0718,6.9806 16.5722,7.5805 16.5722,8.763V14.3719H18.8747Z"
|
||||
android:fillColor="#fff"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
5
mastodon/src/main/res/drawable/bg_settings_update.xml
Normal file
5
mastodon/src/main/res/drawable/bg_settings_update.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?colorPollVoted"/>
|
||||
<corners android:radius="4dp"/>
|
||||
</shape>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorForeground">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="#18000000"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="?android:colorBackground"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
8
mastodon/src/main/res/drawable/bg_upload_progress.xml
Normal file
8
mastodon/src/main/res/drawable/bg_upload_progress.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?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 android:shape="oval">
|
||||
<solid android:color="@color/gray_600"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -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 4.75c-4.004 0-7.25 3.246-7.25 7.25s3.246 7.25 7.25 7.25 7.25-3.246 7.25-7.25c0-0.286-0.017-0.567-0.049-0.844C19.133 10.568 19.56 10 20.151 10c0.515 0 0.968 0.358 1.03 0.87 0.046 0.37 0.069 0.747 0.069 1.13 0 5.109-4.141 9.25-9.25 9.25S2.75 17.109 2.75 12 6.891 2.75 12 2.75c2.173 0 4.171 0.75 5.75 2.004V4.25c0-0.552 0.448-1 1-1s1 0.448 1 1v2.698L19.784 7H19.75v0.25c0 0.552-0.448 1-1 1h-3c-0.552 0-1-0.448-1-1s0.448-1 1-1h0.666c-1.222-0.94-2.754-1.5-4.416-1.5z" 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="M2.397 2.554L2.47 2.47c0.266-0.267 0.683-0.29 0.976-0.073L3.53 2.47 8 6.939l4.47-4.47c0.293-0.292 0.767-0.292 1.06 0 0.293 0.294 0.293 0.768 0 1.061L9.061 8l4.47 4.47c0.266 0.266 0.29 0.683 0.072 0.976L13.53 13.53c-0.266 0.267-0.683 0.29-0.976 0.073L12.47 13.53 8 9.061l-4.47 4.47c-0.293 0.292-0.767 0.292-1.06 0-0.293-0.294-0.293-0.768 0-1.061L6.939 8l-4.47-4.47C2.204 3.264 2.18 2.847 2.398 2.554L2.47 2.47 2.397 2.554z" 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="M4.21 4.387l0.083-0.094c0.36-0.36 0.928-0.388 1.32-0.083l0.094 0.083L12 10.585l6.293-6.292c0.39-0.39 1.024-0.39 1.414 0 0.39 0.39 0.39 1.024 0 1.414L13.415 12l6.292 6.293c0.36 0.36 0.388 0.928 0.083 1.32l-0.083 0.094c-0.36 0.36-0.928 0.388-1.32 0.083l-0.094-0.083L12 13.415l-6.293 6.292c-0.39 0.39-1.024 0.39-1.414 0-0.39-0.39-0.39-1.024 0-1.414L10.585 12 4.293 5.707c-0.36-0.36-0.388-0.928-0.083-1.32l0.083-0.094L4.21 4.387z" 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="M21.03 2.97c1.398 1.397 1.398 3.663 0 5.06L9.062 20c-0.277 0.277-0.621 0.477-0.999 0.58l-5.116 1.395c-0.56 0.153-1.073-0.361-0.92-0.921l1.395-5.116c0.103-0.377 0.302-0.722 0.58-0.999L15.97 2.97c1.397-1.398 3.663-1.398 5.06 0zM15 6.06L5.062 16c-0.092 0.092-0.159 0.207-0.193 0.333l-1.05 3.85 3.85-1.05C7.793 19.096 7.908 19.03 8 18.938L17.94 9 15 6.06zm2.03-2.03L16.06 5 19 7.94l0.97-0.97c0.811-0.812 0.811-2.128 0-2.94-0.812-0.811-2.128-0.811-2.94 0z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
10
mastodon/src/main/res/drawable/ic_settings_24_badged.xml
Normal file
10
mastodon/src/main/res/drawable/ic_settings_24_badged.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/ic_fluent_settings_24_regular" android:left="2dp" android:right="2dp" android:top="2dp" android:bottom="2dp"/>
|
||||
<item android:width="14dp" android:height="14dp" android:gravity="top|right">
|
||||
<shape android:shape="oval">
|
||||
<stroke android:color="?android:colorPrimary" android:width="2dp"/>
|
||||
<solid android:color="@color/primary_600"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
12
mastodon/src/main/res/drawable/update_progress.xml
Normal file
12
mastodon/src/main/res/drawable/update_progress.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:id="@android:id/progress">
|
||||
<shape
|
||||
android:innerRadius="16dp"
|
||||
android:shape="ring"
|
||||
android:thickness="4dp"
|
||||
android:useLevel="true">
|
||||
<solid android:color="?colorSearchHint"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -6,7 +6,7 @@
|
||||
android:shape="ring"
|
||||
android:thickness="4dp"
|
||||
android:useLevel="true">
|
||||
<solid android:color="?android:colorAccent"/>
|
||||
<solid android:color="@color/gray_100"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -65,30 +65,68 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#cc000000"
|
||||
android:backgroundTint="?colorWindowBackground"
|
||||
android:padding="8dp"
|
||||
android:clipToPadding="false"
|
||||
tools:visibility="visible"
|
||||
android:visibility="gone">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp"
|
||||
android:layout_gravity="center"
|
||||
android:progressDrawable="@drawable/upload_progress"
|
||||
android:max="1000"
|
||||
android:padding="0dp"
|
||||
android:indeterminateOnly="false"
|
||||
android:indeterminate="false"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/retry_upload"
|
||||
android:layout_width="wrap_content"
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|bottom"
|
||||
style="?secondaryButtonStyle"
|
||||
android:text="@string/retry_upload"/>
|
||||
android:layout_gravity="center_vertical">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/retry_or_cancel_upload"
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:src="@drawable/ic_fluent_dismiss_24_filled"
|
||||
android:contentDescription="@string/cancel"
|
||||
android:tint="@color/gray_100"
|
||||
android:background="@drawable/bg_upload_progress"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:progressDrawable="@drawable/upload_progress"
|
||||
android:max="1000"
|
||||
android:padding="0dp"
|
||||
android:indeterminateOnly="false"
|
||||
android:indeterminate="false"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/state_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="16dp"
|
||||
android:layout_below="@id/retry_or_cancel_upload"
|
||||
android:layout_marginTop="16dp"
|
||||
android:textColor="@color/gray_200"
|
||||
android:textSize="14dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:includeFontPadding="false"
|
||||
tools:text="Upload failed"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/state_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="32dp"
|
||||
android:layout_below="@id/state_title"
|
||||
android:includeFontPadding="false"
|
||||
android:textColor="@color/gray_200"
|
||||
android:gravity="center_horizontal|top"
|
||||
android:lines="2"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
tools:text="Your device lost connection to the internet"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/remove_btn2"
|
||||
@@ -98,6 +136,7 @@
|
||||
android:layout_gravity="end|bottom"
|
||||
android:background="?android:selectableItemBackgroundBorderless"
|
||||
android:tint="#D92C2C"
|
||||
android:contentDescription="@string/delete"
|
||||
android:src="@drawable/ic_fluent_delete_20_regular"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
@@ -2,49 +2,146 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?colorBackgroundLightest">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<org.joinmastodon.android.ui.views.AutoOrientationLinearLayout
|
||||
android:id="@+id/button_bar"
|
||||
<RelativeLayout
|
||||
android:id="@+id/reblogs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="64dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
|
||||
<Button
|
||||
android:id="@+id/reblogs"
|
||||
android:layout_width="wrap_content"
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:src="@drawable/ic_fluent_arrow_repeat_all_24_regular"
|
||||
android:tint="?android:textColorSecondary"
|
||||
android:importantForAccessibility="no"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="8dp"
|
||||
android:textSize="14sp"
|
||||
android:minHeight="36dp"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:background="@drawable/bg_text_button"
|
||||
android:fontFamily="sans-serif"
|
||||
tools:text="4 reblogs"/>
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_toEndOf="@id/icon"
|
||||
android:minHeight="22dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/post_info_reblogs"
|
||||
android:textAppearance="@style/m3_body_large" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/favorites"
|
||||
android:layout_width="wrap_content"
|
||||
<TextView
|
||||
android:id="@+id/reblogs_count"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="8dp"
|
||||
android:textSize="14sp"
|
||||
android:minHeight="36dp"
|
||||
android:layout_below="@id/title"
|
||||
android:layout_alignStart="@id/title"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:background="@drawable/bg_text_button"
|
||||
android:fontFamily="sans-serif"
|
||||
tools:text="12 favorites"/>
|
||||
tools:text="123 456"/>
|
||||
|
||||
</org.joinmastodon.android.ui.views.AutoOrientationLinearLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/favorites"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:src="@drawable/ic_fluent_star_24_regular"
|
||||
android:tint="?android:textColorSecondary"
|
||||
android:importantForAccessibility="no"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_toEndOf="@id/icon"
|
||||
android:minHeight="22dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/post_info_favorites"
|
||||
android:textAppearance="@style/m3_body_large" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/favorites_count"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/title"
|
||||
android:layout_alignStart="@id/title"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
tools:text="123 456"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/edit_history"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:src="@drawable/ic_fluent_edit_24_regular"
|
||||
android:tint="?android:textColorSecondary"
|
||||
android:importantForAccessibility="no"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_toEndOf="@id/icon"
|
||||
android:minHeight="22dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/edit_history"
|
||||
android:textAppearance="@style/m3_body_large" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/last_edited"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/title"
|
||||
android:layout_alignStart="@id/title"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
tools:text="123 456"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/timestamp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:layout_margin="16dp"
|
||||
android:minHeight="20dp"
|
||||
android:gravity="center_vertical"
|
||||
android:textSize="14sp"
|
||||
|
||||
75
mastodon/src/main/res/layout/item_settings_update.xml
Normal file
75
mastodon/src/main/res/layout/item_settings_update.xml
Normal file
@@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="64dp"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:background="@drawable/bg_settings_update"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
tools:text="@string/update_available"/>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Button
|
||||
android:id="@+id/button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:textColor="?colorAccentLight"
|
||||
android:textAllCaps="true"
|
||||
android:textSize="14dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:stateListAnimator="@null"
|
||||
tools:text="@string/install_update"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/cancel_btn"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:background="@drawable/bg_update_download_progress"
|
||||
android:tint="?colorSearchHint"
|
||||
android:contentDescription="@string/cancel"
|
||||
android:visibility="gone"
|
||||
android:src="@drawable/ic_fluent_dismiss_16_filled"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:progressDrawable="@drawable/update_progress"
|
||||
android:max="1000"
|
||||
android:padding="0dp"
|
||||
android:visibility="gone"
|
||||
android:indeterminateOnly="false"
|
||||
android:indeterminate="false"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/edit" android:title="@string/edit"/>
|
||||
<item android:id="@+id/delete" android:title="@string/delete"/>
|
||||
<item android:id="@+id/delete_and_redraft" android:title="@string/delete_and_redraft"/>
|
||||
<item android:id="@+id/pin" android:title="@string/pin_post"/>
|
||||
|
||||
@@ -2,4 +2,9 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome>
|
||||
<layer-list>
|
||||
<item android:drawable="@drawable/ic_launcher_monochrome" android:gravity="center"/>
|
||||
</layer-list>
|
||||
</monochrome>
|
||||
</adaptive-icon>
|
||||
@@ -2,4 +2,9 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome>
|
||||
<layer-list>
|
||||
<item android:drawable="@drawable/ic_launcher_monochrome" android:gravity="center"/>
|
||||
</layer-list>
|
||||
</monochrome>
|
||||
</adaptive-icon>
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">مَاستودُون</string>
|
||||
<string name="get_started">ابدأ</string>
|
||||
<string name="get_started">الخطوات الأولى</string>
|
||||
<string name="log_in">تسجيل الدخول</string>
|
||||
<string name="next">التالي</string>
|
||||
<string name="loading_instance">يَجري الحُصُول على معلومات المَثيل…</string>
|
||||
@@ -55,7 +54,7 @@
|
||||
<string name="posts">منشورات</string>
|
||||
<string name="posts_and_replies">مَنشُوراتٌ وَرُدُود</string>
|
||||
<string name="media">وسائط</string>
|
||||
<string name="profile_about">حَول</string>
|
||||
<string name="profile_about">عن</string>
|
||||
<string name="button_follow">تابِع</string>
|
||||
<string name="button_following">يُتابِع</string>
|
||||
<string name="edit_profile">حرّر الملف الشخصي</string>
|
||||
@@ -227,7 +226,7 @@
|
||||
<string name="report_personal_title">لاترغب في مشاهدة هذا؟</string>
|
||||
<string name="report_personal_subtitle">عندما ترى ما لا يعجبك في ماستدون، يمكنك إزالة صاحبها من تجربتك كمستخدم.</string>
|
||||
<string name="back">العودة</string>
|
||||
<string name="instance_catalog_title">ماستدون مكون من مستخدمين منقسمين عبر خوادم مختلفة.</string>
|
||||
<string name="instance_catalog_title">يتكوّن ماستدون من مستخدمين موزّعين عبر خوادم مختلفة.</string>
|
||||
<string name="instance_catalog_subtitle">اختر خادمًا بناءً على اهتماماتك، منطقتك أو يمكنك حتى اختيارُ مجتمعٍ ذي غرضٍ عام. وسيضل بامكانك التواصل مع المستخدمين من الخوادم الأخرى.</string>
|
||||
<string name="search_communities">ابحث عن خادم أو أدخل رابطه</string>
|
||||
<string name="instance_rules_title">بعض القواعد الأساسية</string>
|
||||
@@ -278,10 +277,11 @@
|
||||
<string name="skip">تخطى</string>
|
||||
<string name="notification_type_follow">متابعُون جُدُد</string>
|
||||
<string name="notification_type_favorite">المفضلة</string>
|
||||
<string name="notification_type_reblog">المعاد تدوينها</string>
|
||||
<string name="notification_type_mention">الذِكر</string>
|
||||
<string name="notification_type_poll">استطلاع رأي</string>
|
||||
<string name="choose_account">اختر حسابًا</string>
|
||||
<string name="err_not_logged_in">سجل الدخول إلى حساب ماستودون أولًا</string>
|
||||
<string name="err_not_logged_in">يرجى تسجيل الدخول إلى حساب ماستدون أولًا</string>
|
||||
<plurals name="cant_add_more_than_x_attachments">
|
||||
<item quantity="zero">يجب عليك إرفاق ملف</item>
|
||||
<item quantity="one">لا يمكنك إرفاق ملف</item>
|
||||
@@ -301,14 +301,14 @@
|
||||
<string name="settings_gif">تشغيل الصور الرمزية المتحركة والرموز التعبيرية المتحركة</string>
|
||||
<string name="settings_custom_tabs">استخدم المتصفح المضمن</string>
|
||||
<string name="settings_notifications">الإشعارات</string>
|
||||
<string name="notify_me_when">أشعِرني بـ</string>
|
||||
<string name="notify_anyone">أيُّ شخصٍ</string>
|
||||
<string name="notify_follower">مُتابِعٌ</string>
|
||||
<string name="notify_me_when">أشعِرني عند قيام</string>
|
||||
<string name="notify_anyone">أيَّ شخصٍ</string>
|
||||
<string name="notify_follower">مُتابِع</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_follow">بمتابعتي</string>
|
||||
<string name="notify_reblog">بإعادة تدوين مَنشوري</string>
|
||||
<string name="notify_mention">ذكرني</string>
|
||||
<string name="settings_boring">المنطِقَةُ المُملَّة</string>
|
||||
<string name="settings_account">إعدادات الحساب</string>
|
||||
@@ -317,7 +317,7 @@
|
||||
<string name="settings_privacy_policy">سياسة الخصوصية</string>
|
||||
<string name="settings_spicy">المنطِقَةُ اللَّاذِعَة</string>
|
||||
<string name="settings_clear_cache">امسح التخزين المؤقت للوسائط</string>
|
||||
<string name="settings_app_version">تطبيق ماستودون لأندرويد نسخة %1$s (%2$d)</string>
|
||||
<string name="settings_app_version">تطبيق ماستدون لأندرويد نسخة %1$s (%2$d)</string>
|
||||
<string name="media_cache_cleared">مُسح التخزين المؤقت للوسائط</string>
|
||||
<string name="confirm_log_out">أمتأكد من الخروج؟</string>
|
||||
<string name="sensitive_content">محتوى حساس</string>
|
||||
@@ -344,6 +344,8 @@
|
||||
<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>
|
||||
@@ -389,4 +391,22 @@
|
||||
<item quantity="many">%,d متابَعًا</item>
|
||||
<item quantity="other">%,d متابَع</item>
|
||||
</plurals>
|
||||
<plurals name="x_favorites">
|
||||
<item quantity="zero">دون تفضيلات</item>
|
||||
<item quantity="one">تفضيل واحد</item>
|
||||
<item quantity="two">تفضيلان</item>
|
||||
<item quantity="few">%,d تفضيلات</item>
|
||||
<item quantity="many">%,d تفضيلًا</item>
|
||||
<item quantity="other">%,d تفضيل</item>
|
||||
</plurals>
|
||||
<plurals name="x_reblogs">
|
||||
<item quantity="zero">لا إعادات تدوين</item>
|
||||
<item quantity="one">إعاد تدوين واحدة</item>
|
||||
<item quantity="two">إعادتا تدوين</item>
|
||||
<item quantity="few">%,d إعادات تدوين</item>
|
||||
<item quantity="many">%,d إعادة تدوين</item>
|
||||
<item quantity="other">%,d إعادة تدوين</item>
|
||||
</plurals>
|
||||
<string name="timestamp_via_app">%1$s عبر %2$s</string>
|
||||
<string name="time_now">الآن</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Mastodon</string>
|
||||
<string name="get_started">Kreni</string>
|
||||
<string name="log_in">Loguj se</string>
|
||||
<string name="next">Dalje</string>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Mastodon</string>
|
||||
<string name="get_started">Començar</string>
|
||||
<string name="log_in">Iniciar sessió</string>
|
||||
<string name="next">Següent</string>
|
||||
@@ -168,6 +167,7 @@
|
||||
<string name="report_personal_title">No vols veure això?</string>
|
||||
<string name="report_personal_subtitle">Quan veus alguna cosa que no t\'agrada a Mastodon, pots eliminar la persona de la vostra experiència.</string>
|
||||
<string name="back">Enrere</string>
|
||||
<string name="search_communities">Cerca servidors o introdueix l\'URL</string>
|
||||
<string name="instance_rules_title">Algunes normes bàsiques</string>
|
||||
<string name="instance_rules_subtitle">Pren un minut per revisar les normes establertes i aplicades pels administradors de %s.</string>
|
||||
<string name="signup_title">Deixa que et posem en marxa a %s</string>
|
||||
@@ -298,5 +298,25 @@
|
||||
<string name="trending_links_info_banner">Aquestes són les notícies que més es comparteixen en el teu racó de Mastodon.</string>
|
||||
<string name="local_timeline_info_banner">Aquestes són les publicacions més recents de les persones que utilitzen el mateix servidor Mastodon que tu.</string>
|
||||
<string name="dismiss">Ometre</string>
|
||||
<string name="see_new_posts">Veure noves publicacions</string>
|
||||
<string name="load_missing_posts">Carregar les publicacions faltants</string>
|
||||
<string name="button_follow_pending">Pendent</string>
|
||||
<string name="follows_you">Te segueix</string>
|
||||
<string name="current_account">Compte actual</string>
|
||||
<string name="log_out_account">Tancar sessió %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 seguidor</item>
|
||||
<item quantity="other">%,d seguidors</item>
|
||||
</plurals>
|
||||
<plurals name="x_following">
|
||||
<item quantity="one">%,d seguint</item>
|
||||
<item quantity="other">%,d seguint</item>
|
||||
</plurals>
|
||||
<plurals name="x_favorites">
|
||||
<item quantity="one">%,d favorit</item>
|
||||
<item quantity="other">%,d favorits</item>
|
||||
</plurals>
|
||||
<string name="timestamp_via_app">%1$s través de %2$s</string>
|
||||
<string name="time_now">ara</string>
|
||||
</resources>
|
||||
|
||||
218
mastodon/src/main/res/values-cs-rCZ/strings.xml
Normal file
218
mastodon/src/main/res/values-cs-rCZ/strings.xml
Normal file
@@ -0,0 +1,218 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="get_started">Začínáme</string>
|
||||
<string name="log_in">Přihlásit</string>
|
||||
<string name="next">Další</string>
|
||||
<string name="loading_instance">Získávání informací o instanci…</string>
|
||||
<string name="error">Chyba</string>
|
||||
<string name="not_a_mastodon_instance">Zdá se, že %s není instancí Mastodonu.</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="preparing_auth">Příprava na ověřování…</string>
|
||||
<string name="finishing_auth">Dokončení ověřování…</string>
|
||||
<string name="user_boosted">%s sdíleno</string>
|
||||
<string name="in_reply_to">V odpovědi na %s</string>
|
||||
<string name="notifications">Upozornění</string>
|
||||
<string name="user_followed_you">vás sleduje</string>
|
||||
<string name="user_sent_follow_request">vám poslal žádost o sledování</string>
|
||||
<string name="user_favorited">si oblíbil váš příspěvek</string>
|
||||
<string name="notification_boosted">sdílel váš příspěvek</string>
|
||||
<string name="poll_ended">anketa skončila</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 d</string>
|
||||
<string name="share_toot_title">Sdílet</string>
|
||||
<string name="settings">Nastavení</string>
|
||||
<string name="publish">Zveřejnit</string>
|
||||
<string name="discard_draft">Zahodit koncept?</string>
|
||||
<string name="discard">Zahodit</string>
|
||||
<string name="cancel">Zrušit</string>
|
||||
<string name="posts">Příspěvky</string>
|
||||
<string name="posts_and_replies">Příspěvky a odpovědi</string>
|
||||
<string name="media">Média</string>
|
||||
<string name="profile_about">O uživateli</string>
|
||||
<string name="button_follow">Sledovat</string>
|
||||
<string name="button_following">Sledovaní</string>
|
||||
<string name="edit_profile">Upravit profil</string>
|
||||
<string name="mention_user">Zmínit @%s</string>
|
||||
<string name="share_user">Sdílet %s</string>
|
||||
<string name="mute_user">Skrýt %s</string>
|
||||
<string name="unmute_user">Odkrýt @%s</string>
|
||||
<string name="block_user">Blokovat %s</string>
|
||||
<string name="unblock_user">Odblokovat %s</string>
|
||||
<string name="report_user">Nahlásit %s</string>
|
||||
<string name="block_domain">Blokovat %s</string>
|
||||
<string name="unblock_domain">Odblokovat %s</string>
|
||||
<string name="profile_joined">Účet vytvořen</string>
|
||||
<string name="done">Hotovo</string>
|
||||
<string name="loading">Načítání…</string>
|
||||
<string name="field_label">Označení</string>
|
||||
<string name="field_content">Obsah</string>
|
||||
<string name="saving">Ukládání…</string>
|
||||
<string name="post_from_user">Příspěvek od %s</string>
|
||||
<string name="poll_option_hint">Možnost %d</string>
|
||||
<plurals name="x_minutes">
|
||||
<item quantity="one">%d minuta</item>
|
||||
<item quantity="few">%d minuty</item>
|
||||
<item quantity="many">%d minut</item>
|
||||
<item quantity="other">%d minut</item>
|
||||
</plurals>
|
||||
<plurals name="x_hours">
|
||||
<item quantity="one">%d hodina</item>
|
||||
<item quantity="few">%d hodiny</item>
|
||||
<item quantity="many">%d hodin</item>
|
||||
<item quantity="other">%d hodin</item>
|
||||
</plurals>
|
||||
<plurals name="x_days">
|
||||
<item quantity="one">%d den</item>
|
||||
<item quantity="few">%d dny</item>
|
||||
<item quantity="many">%d dnů</item>
|
||||
<item quantity="other">%d dnů</item>
|
||||
</plurals>
|
||||
<string name="compose_poll_duration">Doba trvání: %s</string>
|
||||
<plurals name="x_seconds_left">
|
||||
<item quantity="one">Zbývá %d sekunda</item>
|
||||
<item quantity="few">Zbývá %d sekundy</item>
|
||||
<item quantity="many">Zbývá %d sekund</item>
|
||||
<item quantity="other">Zbývá %d sekund</item>
|
||||
</plurals>
|
||||
<plurals name="x_minutes_left">
|
||||
<item quantity="one">Zbývá %d minuta</item>
|
||||
<item quantity="few">Zbývá %d minuty</item>
|
||||
<item quantity="many">Zbývá %d minut</item>
|
||||
<item quantity="other">Zbývá %d minut</item>
|
||||
</plurals>
|
||||
<plurals name="x_hours_left">
|
||||
<item quantity="one">Zbývá %d hodina</item>
|
||||
<item quantity="few">Zbývá %d hodiny</item>
|
||||
<item quantity="many">Zbývá %d hodin</item>
|
||||
<item quantity="other">Zbývá %d hodin</item>
|
||||
</plurals>
|
||||
<plurals name="x_days_left">
|
||||
<item quantity="one">Zbývá %d den</item>
|
||||
<item quantity="few">Zbývají %d dny</item>
|
||||
<item quantity="many">Zbývá %d dní</item>
|
||||
<item quantity="other">Zbývá %d dní</item>
|
||||
</plurals>
|
||||
<plurals name="x_voters">
|
||||
<item quantity="one">%,d hlasující</item>
|
||||
<item quantity="few">%,d hlasující</item>
|
||||
<item quantity="many">%,d hlasujících</item>
|
||||
<item quantity="other">%,d hlasujících</item>
|
||||
</plurals>
|
||||
<string name="poll_closed">Uzavřeno</string>
|
||||
<string name="confirm_mute_title">Skrýt účet</string>
|
||||
<string name="confirm_mute">Potvrdit skrytí %s</string>
|
||||
<string name="do_mute">Skrýt</string>
|
||||
<string name="confirm_unmute_title">Zrušit skrytí účtu</string>
|
||||
<string name="confirm_unmute">Potvrďte zrušení skrytí %s</string>
|
||||
<string name="do_unmute">Odkrýt</string>
|
||||
<string name="confirm_block_title">Blokovat účet</string>
|
||||
<string name="confirm_block_domain_title">Blokovat doménu</string>
|
||||
<string name="confirm_block">Potvrďte blokování %s</string>
|
||||
<string name="do_block">Blokovat</string>
|
||||
<string name="confirm_unblock_title">Odblokovat účet</string>
|
||||
<string name="confirm_unblock_domain_title">Odblokovat doménu</string>
|
||||
<string name="confirm_unblock">Potvrďte odblokování %s</string>
|
||||
<string name="do_unblock">Odblokovat</string>
|
||||
<string name="button_muted">Skrytý</string>
|
||||
<string name="button_blocked">Blokovaný</string>
|
||||
<string name="action_vote">Hlasovat</string>
|
||||
<string name="tap_to_reveal">Klepnutím zobraz</string>
|
||||
<string name="delete">Smazat</string>
|
||||
<string name="confirm_delete_title">Smazat příspěvek</string>
|
||||
<string name="confirm_delete">Opravdu chcete smazat tento příspěvek?</string>
|
||||
<string name="deleting">Mazání…</string>
|
||||
<string name="notification_channel_audio_player">Přehrávání zvuku</string>
|
||||
<string name="play">Přehrát</string>
|
||||
<string name="pause">Pozastavit</string>
|
||||
<string name="log_out">Odhlásit se</string>
|
||||
<string name="add_account">Přidat účet</string>
|
||||
<string name="search_hint">Hledat</string>
|
||||
<string name="hashtags">Hashtagy</string>
|
||||
<string name="news">Zprávy</string>
|
||||
<string name="for_you">Pro vás</string>
|
||||
<string name="all_notifications">Vše</string>
|
||||
<string name="mentions">Zmínky</string>
|
||||
<plurals name="x_people_talking">
|
||||
<item quantity="one">%d člověk mluví</item>
|
||||
<item quantity="few">%d lidé mluví</item>
|
||||
<item quantity="many">%d lidí mluví</item>
|
||||
<item quantity="other">%d lidí mluví</item>
|
||||
</plurals>
|
||||
<plurals name="discussed_x_times">
|
||||
<item quantity="one">Diskutováno %d krát</item>
|
||||
<item quantity="few">Diskutováno %d krát</item>
|
||||
<item quantity="many">Diskutováno %d krát</item>
|
||||
<item quantity="other">Diskutováno %d krát</item>
|
||||
</plurals>
|
||||
<string name="report_title">Nahlásit %s</string>
|
||||
<string name="report_choose_reason">Co je na tomto příspěvku špatně?</string>
|
||||
<string name="report_choose_reason_account">Co je špatně na %s?</string>
|
||||
<string name="report_choose_reason_subtitle">Vyberte nejbližší možnost</string>
|
||||
<string name="report_reason_personal">Nelíbí se mi</string>
|
||||
<string name="report_reason_personal_subtitle">Není to něco, co chcete vidět</string>
|
||||
<string name="report_reason_spam">Je to spam</string>
|
||||
<string name="report_reason_spam_subtitle">Škodlivé odkazy, falešné interakce nebo opakované odpovědi</string>
|
||||
<string name="report_reason_violation">Porušuje pravidla serveru</string>
|
||||
<string name="report_reason_violation_subtitle">Máte za to, že porušuje konkrétní pravidla</string>
|
||||
<string name="report_reason_other">Jde o něco jiného</string>
|
||||
<string name="report_reason_other_subtitle">Problém neodpovídá ostatním kategoriím</string>
|
||||
<string name="report_choose_rule">Která pravidla porušuje?</string>
|
||||
<string name="report_choose_rule_subtitle">Vyberte všechna relevantní</string>
|
||||
<string name="report_choose_posts">Existují příspěvky dokládající toto hlášení?</string>
|
||||
<string name="report_choose_posts_subtitle">Vyberte všechna relevantní</string>
|
||||
<string name="report_comment_title">Je ještě něco jiného, co bychom měli vědět?</string>
|
||||
<string name="report_comment_hint">Dodatečné komentáře</string>
|
||||
<string name="sending_report">Odesílání hlášení…</string>
|
||||
<string name="report_sent_title">Děkujeme za nahlášení, podíváme se na to.</string>
|
||||
<string name="report_sent_subtitle">Zatímco to posuzujeme, můžete podniknout kroky proti %s.</string>
|
||||
<string name="unfollow_user">Přestat sledovat %s</string>
|
||||
<string name="unfollow">Přestat sledovat</string>
|
||||
<string name="mute_user_explain">Neuvidíte jejich příspěvky nebo sdílení v domovském kanálu. Nebudou vědět, že jsou skrytí.</string>
|
||||
<string name="block_user_explain">Už nebudou moci sledovat nebo vidět vaše příspěvky, ale mohou vidět, že byli blokováni.</string>
|
||||
<string name="report_personal_title">Nechcete tohle vidět?</string>
|
||||
<string name="report_personal_subtitle">Když uvidíte něco, co se vám nelíbí na Mastodonu, můžete odstranit tuto osobu ze svého zážitku.</string>
|
||||
<string name="back">Zpět</string>
|
||||
<string name="instance_catalog_title">Mastodon tvoří uživatelé z různých serverů.</string>
|
||||
<string name="instance_catalog_subtitle">Vyberte si server podle na svých zájmů, regionu nebo obecného účelu. Stále se můžete spojit se všemi bez ohledu na server.</string>
|
||||
<string name="search_communities">Hledat nebo zadat URL</string>
|
||||
<string name="instance_rules_title">Některá základní pravidla</string>
|
||||
<string name="instance_rules_subtitle">Udělejte si chvíli čas a zkontrolujte pravidla, která admini %s nastavili a vynucují.</string>
|
||||
<string name="signup_title">Pojďme si nastavit %s</string>
|
||||
<string name="edit_photo">upravit</string>
|
||||
<string name="display_name">zobrazované jméno</string>
|
||||
<string name="username">uživatelské jméno</string>
|
||||
<string name="email">e-mail</string>
|
||||
<string name="password">heslo</string>
|
||||
<string name="password_note">Použijte velká písmena, speciální znaky a čísla, abyste zvýšili sílu hesla.</string>
|
||||
<string name="category_academia">Akademická sféra</string>
|
||||
<string name="category_activism">Aktivismus</string>
|
||||
<string name="category_all">Vše</string>
|
||||
<string name="category_art">Umění</string>
|
||||
<string name="category_food">Jídlo</string>
|
||||
<string name="category_furry">Furry</string>
|
||||
<string name="category_games">Hry</string>
|
||||
<string name="category_general">Obecné</string>
|
||||
<string name="category_journalism">Žurnalistika</string>
|
||||
<string name="category_lgbt">LGBT</string>
|
||||
<string name="category_music">Hudba</string>
|
||||
<string name="category_regional">Regionální</string>
|
||||
<string name="category_tech">Technologie</string>
|
||||
<string name="confirm_email_title">Ještě jedna věc</string>
|
||||
<string name="confirm_email_subtitle">Klepněte na odkaz, který jsme vám poslali e-mailem, abyste účet ověřili.</string>
|
||||
<string name="resend">Poslat znovu</string>
|
||||
<string name="open_email_app">Otevřít e-mailovou aplikaci</string>
|
||||
<string name="resent_email">Potvrzující e-mail odeslán</string>
|
||||
<string name="notify_none">nikoho</string>
|
||||
<string name="notify_favorites">Oblíbil si můj příspěvek</string>
|
||||
<string name="notify_follow">Sleduje mě</string>
|
||||
<string name="notify_reblog">Sdílel můj příspěvek</string>
|
||||
<string name="notify_mention">Zmiňuje mě</string>
|
||||
<string name="settings_boring">Nudná část</string>
|
||||
<string name="settings_account">Nastavení účtu</string>
|
||||
<string name="settings_contribute">Přispějte do Mastodonu</string>
|
||||
<string name="settings_tos">Podmínky používání</string>
|
||||
<string name="settings_privacy_policy">Zásady ochrany osobních údajů</string>
|
||||
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
||||
</resources>
|
||||
@@ -1,6 +1,5 @@
|
||||
<?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>
|
||||
@@ -107,7 +106,7 @@
|
||||
<string name="confirm_mute">Bestätigen um %s stummzuschalten</string>
|
||||
<string name="do_mute">Stummschalten</string>
|
||||
<string name="confirm_unmute_title">Konto nicht mehr stummschalten</string>
|
||||
<string name="confirm_unmute">Bestätigen um %s nicht mehr stummzuschalten</string>
|
||||
<string name="confirm_unmute">Bestätigen, um %s nicht mehr stummzuschalten</string>
|
||||
<string name="do_unmute">Nicht mehr stummschalten</string>
|
||||
<string name="confirm_block_title">Konto blockieren</string>
|
||||
<string name="confirm_block_domain_title">Domain blockieren</string>
|
||||
@@ -178,7 +177,7 @@
|
||||
<string name="report_sent_subtitle">Während wir dies überprüfen, kannst du gegen %s vorgehen.</string>
|
||||
<string name="unfollow_user">%s entfolgen</string>
|
||||
<string name="unfollow">Entfolgen</string>
|
||||
<string name="mute_user_explain">Du wirst die Beiträge vom Konto nicht mehr sehen. Das Konto kann dir immernoch folgen und die Person hinter dem Konto wird deine Beiträge sehen können und nicht wissen, dass du sie stumm geschaltet hast.</string>
|
||||
<string name="mute_user_explain">Du wirst die Beiträge vom Konto nicht mehr sehen. Das Konto kann dir immer noch folgen, und die Person hinter dem Konto wird deine Beiträge sehen können und nicht wissen, dass du sie stummgeschaltet hast.</string>
|
||||
<string name="block_user_explain">Du wirst die Beiträge von diesem Konto nicht sehen. Das Konto wird nicht in der Lage sein, deine Beiträge zu sehen oder dir zu folgen. Die Person hinter dem Konto wird wissen, dass du das Konto blockiert hast.</string>
|
||||
<string name="report_personal_title">Du willst das nicht mehr sehen?</string>
|
||||
<string name="report_personal_subtitle">Wenn du etwas auf Mastodon nicht sehen willst, kannst du den Nutzer aus deiner Erfahrung streichen.</string>
|
||||
@@ -256,10 +255,10 @@
|
||||
<string name="settings_gif">Spiele animierte Avatare und Emojis ab</string>
|
||||
<string name="settings_custom_tabs">Verwende In-App-Browser</string>
|
||||
<string name="settings_notifications">Benachrichtigungen</string>
|
||||
<string name="notify_me_when">Benachrichtige mich wenn</string>
|
||||
<string name="notify_me_when">Benachrichtige mich, wenn</string>
|
||||
<string name="notify_anyone">jeder</string>
|
||||
<string name="notify_follower">ein Follower</string>
|
||||
<string name="notify_followed">jemand, den ich folge</string>
|
||||
<string name="notify_followed">jemand, dem ich folge</string>
|
||||
<string name="notify_none">niemand</string>
|
||||
<string name="notify_favorites">Meinen Beitrag favorisiert</string>
|
||||
<string name="notify_follow">Mir folgt</string>
|
||||
@@ -322,13 +321,13 @@
|
||||
<string name="local_timeline_info_banner">Dies sind die neuesten Beiträge der Leute, die den gleichen Mastodon-Server verwenden wie du.</string>
|
||||
<string name="dismiss">Verwerfen</string>
|
||||
<string name="see_new_posts">Neue Beiträge anzeigen</string>
|
||||
<string name="load_missing_posts">Fehlende Beiträge Laden</string>
|
||||
<string name="load_missing_posts">Fehlende Beiträge laden</string>
|
||||
<string name="follow_back">Zurück folgen</string>
|
||||
<string name="button_follow_pending">Ausstehend</string>
|
||||
<string name="follows_you">Folgt dir</string>
|
||||
<string name="manually_approves_followers">Genehmigt Folgende manuell</string>
|
||||
<string name="current_account">Aktuelles Konto</string>
|
||||
<string name="log_out_account">%s ausloggen</string>
|
||||
<string name="log_out_account">Ausloggen</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>
|
||||
@@ -346,4 +345,6 @@
|
||||
<item quantity="one">%,d Reblog</item>
|
||||
<item quantity="other">%,d Reblogs</item>
|
||||
</plurals>
|
||||
<string name="timestamp_via_app">%1$s via %2$s</string>
|
||||
<string name="time_now">jetzt</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Mastodon</string>
|
||||
<string name="get_started">Empezar</string>
|
||||
<string name="log_in">Iniciar sesión</string>
|
||||
<string name="next">Siguiente</string>
|
||||
@@ -10,10 +9,13 @@
|
||||
<string name="ok">Aceptar</string>
|
||||
<string name="preparing_auth">Preparando para autenticación…</string>
|
||||
<string name="finishing_auth">Terminando autenticación…</string>
|
||||
<string name="user_boosted">%s reblogueado</string>
|
||||
<string name="in_reply_to">En respuesta a %s</string>
|
||||
<string name="notifications">Notificaciones</string>
|
||||
<string name="user_followed_you">te siguió</string>
|
||||
<string name="user_sent_follow_request">te quiere seguir</string>
|
||||
<string name="user_favorited">ha marcado como favorita tu publicación</string>
|
||||
<string name="notification_boosted">ha reblogueado tu publicación</string>
|
||||
<string name="poll_ended">encuesta finalizada</string>
|
||||
<string name="time_seconds">%ds</string>
|
||||
<string name="time_minutes">%dm</string>
|
||||
@@ -163,11 +165,14 @@
|
||||
<string name="report_sent_subtitle">Mientras revisamos esto, puedes tomar medidas contra %s.</string>
|
||||
<string name="unfollow_user">Dejar de seguir a %s</string>
|
||||
<string name="unfollow">Dejar de seguir</string>
|
||||
<string name="mute_user_explain">No verás sus publicaciones o impulsos en tu línea temporal. No sabrán que han sido silenciados.</string>
|
||||
<string name="mute_user_explain">No verás sus publicaciones o reblogueos en tu línea temporal. No sabrán que han sido silenciados.</string>
|
||||
<string name="block_user_explain">Ya no podrán seguir o ver tus mensajes, pero pueden ver si han sido bloqueados.</string>
|
||||
<string name="report_personal_title">¿No quieres ver esto?</string>
|
||||
<string name="report_personal_subtitle">Cuando veas algo que no te gusta en Mastodon, puedes quitar a la persona de tu experiencia.</string>
|
||||
<string name="back">Atrás</string>
|
||||
<string name="instance_catalog_title">Mastodon está hecho por usuarios den diferentes servidores.</string>
|
||||
<string name="instance_catalog_subtitle">Selecciona un servidor basado en tus intereses, región o un propósito general. Aun así puedes conectarte con todo el mundo, sin importar el servidor.</string>
|
||||
<string name="search_communities">Busca servidores o introduce la URL</string>
|
||||
<string name="instance_rules_title">Algunas reglas básicas</string>
|
||||
<string name="instance_rules_subtitle">Tómate un minuto para revisar las reglas establecidas y aplicadas por las personas que administran %s.</string>
|
||||
<string name="signup_title">Deja que te preparemos en %s</string>
|
||||
@@ -216,6 +221,7 @@
|
||||
<string name="skip">Saltar</string>
|
||||
<string name="notification_type_follow">Nuevos seguidores</string>
|
||||
<string name="notification_type_favorite">Favoritos</string>
|
||||
<string name="notification_type_reblog">Reblogueos</string>
|
||||
<string name="notification_type_mention">Menciones</string>
|
||||
<string name="notification_type_poll">Encuestas</string>
|
||||
<string name="choose_account">Elegir cuenta</string>
|
||||
@@ -278,6 +284,8 @@
|
||||
<string name="unfollowed_user">No sigues a %s</string>
|
||||
<string name="followed_user">Ahora estás siguiendo a %s</string>
|
||||
<string name="open_in_browser">Abrir en el navegador</string>
|
||||
<string name="hide_boosts_from_user">Ocultar reblogueos de %s</string>
|
||||
<string name="show_boosts_from_user">Mostrar reblogueos de %s</string>
|
||||
<string name="signup_reason">¿por qué quieres unirte?</string>
|
||||
<string name="signup_reason_note">Esto nos ayudará a revisar su solicitud.</string>
|
||||
<string name="clear">Borrar</string>
|
||||
@@ -291,5 +299,38 @@
|
||||
<string name="error_saving_file">Error al guardar el archivo</string>
|
||||
<string name="file_saved">Archivo guardado</string>
|
||||
<string name="downloading">Descargando…</string>
|
||||
<string name="no_app_to_handle_action">No hay ninguna aplicación para manejar esta acción</string>
|
||||
<string name="local_timeline">Comunidad</string>
|
||||
<string name="trending_posts_info_banner">Estas son las publicaciones que están ganando tracción en tu rincón de Mastodon.</string>
|
||||
<string name="trending_hashtags_info_banner">Estos son los hashtags que están ganando tracción en tu rincón de Mastodon.</string>
|
||||
<string name="trending_links_info_banner">Estas son las nuevas historias que más están siendo compartidas en tu rincón de Mastodon.</string>
|
||||
<string name="local_timeline_info_banner">Estas son las publicaciones más recientes de la gente que usa el mismo servidor de Mastodon que tú.</string>
|
||||
<string name="dismiss">Descartar</string>
|
||||
<string name="see_new_posts">Ver nuevas publicaciones</string>
|
||||
<string name="load_missing_posts">Cargar publicaciones faltantes</string>
|
||||
<string name="follow_back">Seguir de vuelta</string>
|
||||
<string name="button_follow_pending">Pendiente</string>
|
||||
<string name="follows_you">Te sigue</string>
|
||||
<string name="manually_approves_followers">Aprueba seguidores manualmente</string>
|
||||
<string name="current_account">Cuenta actual</string>
|
||||
<string name="log_out_account">Cerrar sesión %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 seguidor</item>
|
||||
<item quantity="other">%,d seguidores</item>
|
||||
</plurals>
|
||||
<plurals name="x_following">
|
||||
<item quantity="one">%,d siguiendo</item>
|
||||
<item quantity="other">%,d siguiendo</item>
|
||||
</plurals>
|
||||
<plurals name="x_favorites">
|
||||
<item quantity="one">%,d favorito</item>
|
||||
<item quantity="other">%,d favoritos</item>
|
||||
</plurals>
|
||||
<plurals name="x_reblogs">
|
||||
<item quantity="one">%,d reblogueo</item>
|
||||
<item quantity="other">%,d reblogueos</item>
|
||||
</plurals>
|
||||
<string name="timestamp_via_app">%1$s a través de %2$s</string>
|
||||
<string name="time_now">ahora</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Mastodon</string>
|
||||
<string name="get_started">Nola hasi</string>
|
||||
<string name="log_in">Hasi saioa</string>
|
||||
<string name="next">Hurrengoa</string>
|
||||
<string name="loading_instance">Instantziaren informazioa eskuratzen…</string>
|
||||
<string name="error">Errorea</string>
|
||||
<string name="not_a_mastodon_instance">%s ex dirudi Mastodon instantzia bat.</string>
|
||||
<string name="not_a_mastodon_instance">%s(e)k ez dirudi Mastodon instantzia bat denik.</string>
|
||||
<string name="ok">Ados</string>
|
||||
<string name="preparing_auth">Autentifikaziorako prestatzen…</string>
|
||||
<string name="finishing_auth">Autentikazioa bukatzen…</string>
|
||||
|
||||
@@ -1,4 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="log_in">Kirjaudu sisään</string>
|
||||
<string name="next">Seuraava</string>
|
||||
<string name="error">Virhe</string>
|
||||
<string name="time_seconds">%ds</string>
|
||||
<string name="time_minutes">%dm</string>
|
||||
<string name="time_hours">%dh</string>
|
||||
<string name="time_days">%dd</string>
|
||||
<string name="share_toot_title">Jaa</string>
|
||||
<string name="settings">Asetukset</string>
|
||||
<string name="edit_profile">Muokkaa profiilia</string>
|
||||
<string name="share_user">Jaa %s</string>
|
||||
<string name="done">Valmis</string>
|
||||
<string name="saving">Tallennetaan…</string>
|
||||
<string name="all_notifications">Kaikki</string>
|
||||
<string name="back">Takaisin</string>
|
||||
<string name="edit_photo">muokkaa</string>
|
||||
<string name="display_name">näyttönimi</string>
|
||||
<string name="email">sähköposti</string>
|
||||
<string name="password">salasana</string>
|
||||
<string name="category_academia">Akateeminen</string>
|
||||
<string name="category_activism">Aktivismi</string>
|
||||
<string name="category_all">Kaikki</string>
|
||||
<string name="category_art">Taide</string>
|
||||
<string name="category_food">Ruoka</string>
|
||||
<string name="category_furry">Turri</string>
|
||||
<string name="category_games">Pelit</string>
|
||||
<string name="category_general">Yleinen</string>
|
||||
<string name="category_journalism">Journalismi</string>
|
||||
<string name="category_lgbt">HLBT</string>
|
||||
<string name="category_music">Musiikki</string>
|
||||
<string name="category_regional">Alueellinen</string>
|
||||
<string name="category_tech">Teknologia</string>
|
||||
<string name="save">Tallenna</string>
|
||||
<string name="theme_auto">Automaattinen</string>
|
||||
<string name="theme_light">Vaalea</string>
|
||||
<string name="theme_dark">Tumma</string>
|
||||
<string name="button_reply">Vastaa</string>
|
||||
<string name="button_share">Jaa</string>
|
||||
<string name="emoji">Emoji</string>
|
||||
<string name="follow_user">Follow %s</string>
|
||||
<string name="open_in_browser">Avaa selaimessa</string>
|
||||
<string name="clear">Tyhjennä</string>
|
||||
<string name="profile_picture">Profiilikuva</string>
|
||||
<string name="download">Lataa</string>
|
||||
<string name="open_settings">Avaa asetukset</string>
|
||||
<string name="file_saved">Tiedosto tallennettu</string>
|
||||
<string name="downloading">Ladataan…</string>
|
||||
<string name="follow_back">Seuraa takaisin</string>
|
||||
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Mastodon</string>
|
||||
<string name="get_started">Premiers pas</string>
|
||||
<string name="log_in">Se connecter</string>
|
||||
<string name="next">Suivant</string>
|
||||
@@ -10,10 +9,13 @@
|
||||
<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 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>
|
||||
<string name="user_sent_follow_request">vous a envoyé une demande de suivi</string>
|
||||
<string name="user_favorited">a ajouté votre message à ses favoris</string>
|
||||
<string name="notification_boosted">a partagé votre message</string>
|
||||
<string name="poll_ended">sondage terminé</string>
|
||||
<string name="time_seconds">%d s</string>
|
||||
<string name="time_minutes">%d m</string>
|
||||
@@ -42,7 +44,7 @@
|
||||
<string name="media">Médias</string>
|
||||
<string name="profile_about">À propos</string>
|
||||
<string name="button_follow">Suivre</string>
|
||||
<string name="button_following">Abonnement</string>
|
||||
<string name="button_following">Abonné·e</string>
|
||||
<string name="edit_profile">Modifier le profil</string>
|
||||
<string name="mention_user">Mentionner %s</string>
|
||||
<string name="share_user">Partager %s</string>
|
||||
@@ -219,6 +221,7 @@
|
||||
<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>
|
||||
@@ -281,6 +284,8 @@
|
||||
<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>
|
||||
@@ -296,16 +301,36 @@
|
||||
<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>
|
||||
<string name="local_timeline">Communauté</string>
|
||||
<string name="trending_posts_info_banner">Ce sont les postes qui gagnent en popularité sur votre serveur Mastodon.</string>
|
||||
<string name="trending_posts_info_banner">Ce sont les messages qui gagnent en popularité sur votre serveur Mastodon.</string>
|
||||
<string name="trending_hashtags_info_banner">Ce sont les hashtags qui gagnent en popularité sur votre serveur Mastodon.</string>
|
||||
<string name="trending_links_info_banner">Ce sont les nouvelles les plus partagées sur votre serveur Mastodon.</string>
|
||||
<string name="local_timeline_info_banner">Ce sont les messages les plus récents des personnes qui utilisent le même serveur Mastodon que vous.</string>
|
||||
<string name="dismiss">Rejeter</string>
|
||||
<string name="see_new_posts">Voir les nouveaux postes</string>
|
||||
<string name="load_missing_posts">Charger les postes manquants</string>
|
||||
<string name="see_new_posts">Voir les nouveaux messages</string>
|
||||
<string name="load_missing_posts">Charger les messages manquants</string>
|
||||
<string name="follow_back">Suivre en retour</string>
|
||||
<string name="button_follow_pending">En attente</string>
|
||||
<string name="follows_you">Vous suit</string>
|
||||
<string name="manually_approves_followers">Approuver manuellement les demande de suivie</string>
|
||||
<string name="current_account">Compte actuel</string>
|
||||
<string name="log_out_account">Déconnexion %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 abonné·e</item>
|
||||
<item quantity="other">%,d abonné·e·s</item>
|
||||
</plurals>
|
||||
<plurals name="x_following">
|
||||
<item quantity="one">%,d abonnement</item>
|
||||
<item quantity="other">%,d abonnements</item>
|
||||
</plurals>
|
||||
<plurals name="x_favorites">
|
||||
<item quantity="one">%,d favori</item>
|
||||
<item quantity="other">%,d favoris</item>
|
||||
</plurals>
|
||||
<plurals name="x_reblogs">
|
||||
<item quantity="one">%,d partage</item>
|
||||
<item quantity="other">%,d partages</item>
|
||||
</plurals>
|
||||
<string name="timestamp_via_app">%1$s via %2$s</string>
|
||||
<string name="time_now">à l’instant</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Mastodon</string>
|
||||
<string name="get_started">Comezar</string>
|
||||
<string name="get_started">Crear conta</string>
|
||||
<string name="log_in">Acceder</string>
|
||||
<string name="next">Seguinte</string>
|
||||
<string name="loading_instance">Obtendo info da instancia…</string>
|
||||
@@ -214,7 +213,7 @@
|
||||
<string name="alt_text_hint">ex. Un can mirando excépticamente á cámara como se non entendese nada.</string>
|
||||
<string name="visibility_public">Público</string>
|
||||
<string name="visibility_followers_only">Só para seguidoras</string>
|
||||
<string name="visibility_private">Só para persoas mencionadas</string>
|
||||
<string name="visibility_private">Só para as mencionadas</string>
|
||||
<string name="search_all">Todo</string>
|
||||
<string name="search_people">Persoas</string>
|
||||
<string name="recent_searches">Buscas recentes</string>
|
||||
@@ -309,7 +308,7 @@
|
||||
<string name="dismiss">Desbotar</string>
|
||||
<string name="see_new_posts">Ver novas publicacións</string>
|
||||
<string name="load_missing_posts">Cargar publicacións que faltan</string>
|
||||
<string name="follow_back">Seguir recíprocamente</string>
|
||||
<string name="follow_back">Seguir tamén</string>
|
||||
<string name="button_follow_pending">Pendente</string>
|
||||
<string name="follows_you">Séguete</string>
|
||||
<string name="manually_approves_followers">Aprobar manualmente os seguimentos</string>
|
||||
@@ -333,4 +332,5 @@
|
||||
<item quantity="other">%,d promocións</item>
|
||||
</plurals>
|
||||
<string name="timestamp_via_app">%1$s vía %2$s</string>
|
||||
<string name="time_now">agora</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Mastodon</string>
|
||||
<string name="get_started">Započni</string>
|
||||
<string name="log_in">Prijavi se</string>
|
||||
<string name="next">Nastavi</string>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Մաստոդոն</string>
|
||||
<string name="get_started">Սկսել</string>
|
||||
<string name="log_in">Մուտք գործել</string>
|
||||
<string name="next">Հաջորդը</string>
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Mastodonte</string>
|
||||
<string name="get_started">Iniziare</string>
|
||||
<string name="get_started">Iniziamo</string>
|
||||
<string name="log_in">Accedi</string>
|
||||
<string name="next">Prossimo</string>
|
||||
<string name="loading_instance">Recupero delle informazioni sull\'istanza…</string>
|
||||
<string name="next">Avanti</string>
|
||||
<string name="loading_instance">Recupero informazioni istanza…</string>
|
||||
<string name="error">Errore</string>
|
||||
<string name="not_a_mastodon_instance">%s non sembra essere un\'istanza Mastodon.</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="preparing_auth">Preparazione all\'autenticazione…</string>
|
||||
<string name="finishing_auth">Completamento autenticazione…</string>
|
||||
<string name="user_boosted">%s ribloggato</string>
|
||||
<string name="user_boosted">%s ha condiviso</string>
|
||||
<string name="in_reply_to">In risposta a %s</string>
|
||||
<string name="notifications">Notifiche</string>
|
||||
<string name="user_followed_you">ti segue</string>
|
||||
<string name="user_sent_follow_request">ha richiesto di seguirti</string>
|
||||
<string name="user_favorited">ha preferito il tuo post</string>
|
||||
<string name="notification_boosted">ha ribloggato il tuo post</string>
|
||||
<string name="user_favorited">ha apprezzato il tuo post</string>
|
||||
<string name="notification_boosted">ha condiviso il tuo post</string>
|
||||
<string name="poll_ended">sondaggio terminato</string>
|
||||
<string name="time_seconds">%ds</string>
|
||||
<string name="time_minutes">%dm</string>
|
||||
@@ -222,7 +221,7 @@
|
||||
<string name="skip">Salta</string>
|
||||
<string name="notification_type_follow">Nuovi seguaci</string>
|
||||
<string name="notification_type_favorite">Preferiti</string>
|
||||
<string name="notification_type_reblog">Reblog</string>
|
||||
<string name="notification_type_reblog">Condivisioni</string>
|
||||
<string name="notification_type_mention">Menzioni</string>
|
||||
<string name="notification_type_poll">Sondaggi</string>
|
||||
<string name="choose_account">Seleziona l\'account</string>
|
||||
@@ -285,8 +284,8 @@
|
||||
<string name="unfollowed_user">Smetti di seguire %s</string>
|
||||
<string name="followed_user">Hai cominciato a seguire %s</string>
|
||||
<string name="open_in_browser">Apri nel browser</string>
|
||||
<string name="hide_boosts_from_user">Nascondi i reblog da %s</string>
|
||||
<string name="show_boosts_from_user">Mostra i reblog da %s</string>
|
||||
<string name="hide_boosts_from_user">Nascondi le condivisioni di %s</string>
|
||||
<string name="show_boosts_from_user">Mostra le condivisioni di %s</string>
|
||||
<string name="signup_reason">perché vuoi unirti?</string>
|
||||
<string name="signup_reason_note">Questo ci aiuterà ad esaminare la tua richiesta.</string>
|
||||
<string name="clear">Cancella</string>
|
||||
@@ -326,10 +325,12 @@
|
||||
</plurals>
|
||||
<plurals name="x_favorites">
|
||||
<item quantity="one">%,d preferito</item>
|
||||
<item quantity="other">%,d favoriti</item>
|
||||
<item quantity="other">%,d preferiti</item>
|
||||
</plurals>
|
||||
<plurals name="x_reblogs">
|
||||
<item quantity="one">%,d reblog</item>
|
||||
<item quantity="other">%,d reblog</item>
|
||||
<item quantity="one">%,d condivisione</item>
|
||||
<item quantity="other">%,d condivisioni</item>
|
||||
</plurals>
|
||||
<string name="timestamp_via_app">%1$s tramite %2$s</string>
|
||||
<string name="time_now">ora</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Mastodon</string>
|
||||
<string name="get_started">はじめる</string>
|
||||
<string name="log_in">ログイン</string>
|
||||
<string name="next">次へ</string>
|
||||
@@ -13,15 +12,15 @@
|
||||
<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_followed_you">がフォロー</string>
|
||||
<string name="user_sent_follow_request">フォローリクエストを送信しました</string>
|
||||
<string name="user_favorited">あなたの投稿をお気に入りに登録</string>
|
||||
<string name="notification_boosted">さんがあなたの投稿をブーストしました</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="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>
|
||||
@@ -54,7 +53,7 @@
|
||||
<string name="block_domain">ブロック %s</string>
|
||||
<string name="unblock_domain">ブロック %s</string>
|
||||
<plurals name="x_posts">
|
||||
<item quantity="other">%d 件の投稿</item>
|
||||
<item quantity="other">%d件の投稿</item>
|
||||
</plurals>
|
||||
<string name="profile_joined">登録日</string>
|
||||
<string name="done">完了</string>
|
||||
@@ -214,12 +213,12 @@
|
||||
<string name="choose_account">アカウントを選択</string>
|
||||
<string name="err_not_logged_in">まずはアカウントにログインしてください</string>
|
||||
<plurals name="cant_add_more_than_x_attachments">
|
||||
<item quantity="other">%d 件以上のメディア添付ファイルを追加することはできません</item>
|
||||
<item quantity="other">%d件以上のメディア添付ファイルを追加することはできません</item>
|
||||
</plurals>
|
||||
<string name="media_attachment_unsupported_type">ファイル %s はサポートされていないファイル形式です</string>
|
||||
<string name="media_attachment_too_big">f</string>
|
||||
<string name="settings_theme">テーマを選択</string>
|
||||
<string name="theme_auto">システムに合わせる</string>
|
||||
<string name="theme_auto">システム</string>
|
||||
<string name="theme_light">ライト</string>
|
||||
<string name="theme_dark">ダーク</string>
|
||||
<string name="theme_true_black">真っ黒なダークテーマを使用する</string>
|
||||
@@ -314,4 +313,5 @@
|
||||
<item quantity="other">%,d ブースト</item>
|
||||
</plurals>
|
||||
<string name="timestamp_via_app">%1$sに%2$s経由</string>
|
||||
<string name="time_now">今</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Mastodon</string>
|
||||
<string name="get_started">Aha bdu tura</string>
|
||||
<string name="log_in">Qqen</string>
|
||||
<string name="next">Uḍfiṛ</string>
|
||||
@@ -168,6 +167,7 @@
|
||||
<string name="report_personal_title">Ur tebɣiḍ ara ad twaliḍ aya?</string>
|
||||
<string name="report_personal_subtitle">Mi ara twaliḍ kra ur ak•am-neɛǧib ara ɣef Mastodon, tzemreḍ ad tekkseḍ amdan-nni seg tirmit-ik•im.</string>
|
||||
<string name="back">Tuɣalin</string>
|
||||
<string name="search_communities">Nadi timɣiwnin neɣ sekcem URL</string>
|
||||
<string name="instance_rules_title">Kra n yilugan igejdanen</string>
|
||||
<string name="instance_rules_subtitle">Mudd kra n tesdatin i usenqed n yilugan yettusbadun akked yettusnasen sɣur yinedbalen %s.</string>
|
||||
<string name="signup_title">Aha ad nebdu asbadu ɣef %s</string>
|
||||
@@ -298,5 +298,11 @@
|
||||
<string name="trending_links_info_banner">Tigi d tiqsiḍin timaynutin i yettwabḍan s waṭas deg tama-inek•inem n Mastodon.</string>
|
||||
<string name="local_timeline_info_banner">Tigi d tuget n tsuffaɣ timaynutin sɣur yimdanen ara yesseqdacen aqeddac n Mastodon kifkif d win i tesseqdaceḍ.</string>
|
||||
<string name="dismiss">Ffer</string>
|
||||
<string name="see_new_posts">Ẓer tissufaɣ timaynutin</string>
|
||||
<string name="load_missing_posts">Sali tisuffaɣ i iruḥen</string>
|
||||
<string name="button_follow_pending">Yettraǧu</string>
|
||||
<string name="current_account">Amiḍan amiran</string>
|
||||
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
||||
<string name="timestamp_via_app">%1$s s %2$s</string>
|
||||
<string name="time_now">tura</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">마스토돈</string>
|
||||
<string name="get_started">시작하기</string>
|
||||
<string name="log_in">로그인</string>
|
||||
<string name="next">다음</string>
|
||||
@@ -8,6 +7,15 @@
|
||||
<string name="error">오류</string>
|
||||
<string name="not_a_mastodon_instance">%s는 마스토돈 인스턴스인 것 같지 않습니다.</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>
|
||||
@@ -25,6 +33,9 @@
|
||||
<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>
|
||||
@@ -141,13 +152,22 @@
|
||||
<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">마스토돈에서 보기 싫은 것을 보았다면, 해당하는 사람을 지울 수 있습니다.</string>
|
||||
<string name="back">뒤로가기</string>
|
||||
<string name="instance_catalog_title">마스토돈은 여러 다른 서버에 존재하는 사용자들로 이루어져 있습니다.</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>
|
||||
@@ -162,25 +182,49 @@
|
||||
<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>
|
||||
<string name="compose_hint">무슨 생각을 하고 있는지 입력하거나 붙여넣으세요</string>
|
||||
<string name="content_warning">열람 주의</string>
|
||||
<string name="add_image_description">이미지 설명 추가…</string>
|
||||
<string name="retry_upload">업로드 재시도</string>
|
||||
<string name="image_upload_failed">이미지 업로드 실패</string>
|
||||
<string name="video_upload_failed">동영상 업로드 실패</string>
|
||||
<string name="edit_image">이미지 편집</string>
|
||||
<string name="save">저장</string>
|
||||
<string name="add_alt_text">대체 텍스트 추가</string>
|
||||
<string name="alt_text_subtitle">대체 텍스트는 저시력자나 시각장애인들에게 당신의 사진을 설명합니다. 문맥을 파악할 수 있는 설명들만 넣도록 하세요.</string>
|
||||
<string name="alt_text_hint">예시: 강아지가 의심스러운 가는 눈초리로 카메라를 쳐다보고 있습니다.</string>
|
||||
<string name="visibility_public">공개</string>
|
||||
<string name="visibility_followers_only">팔로워 전용</string>
|
||||
<string name="visibility_private">멘션한 사람만</string>
|
||||
<string name="search_all">모두</string>
|
||||
<string name="search_people">사람들</string>
|
||||
<string name="recent_searches">최근 검색</string>
|
||||
<string name="step_x_of_n">%2$d개 중 %1$d번째 단계</string>
|
||||
<string name="skip">건너뛰기</string>
|
||||
<string name="notification_type_follow">새 팔로워</string>
|
||||
<string name="notification_type_favorite">관심글 지정</string>
|
||||
<string name="notification_type_reblog">리블로그</string>
|
||||
<string name="notification_type_mention">멘션</string>
|
||||
<string name="notification_type_poll">투표</string>
|
||||
<string name="choose_account">계정 선택</string>
|
||||
<string name="err_not_logged_in">먼저 마스토돈에 로그인해야 합니다</string>
|
||||
<plurals name="cant_add_more_than_x_attachments">
|
||||
<item quantity="other">%d개의 미디어를 초과해서 첨부할 수 없습니다</item>
|
||||
</plurals>
|
||||
<string name="media_attachment_unsupported_type">%s 파일은 지원되지 않는 타입입니다</string>
|
||||
<string name="media_attachment_too_big">%1$s 파일은 업로드 제한인 %2$s MB를 초과합니다</string>
|
||||
<string name="settings_theme">테마</string>
|
||||
<string name="theme_auto">자동</string>
|
||||
<string name="theme_light">밝음</string>
|
||||
<string name="theme_dark">어두움</string>
|
||||
<string name="theme_true_black">진정한 검정 모드</string>
|
||||
<string name="settings_behavior">동작</string>
|
||||
<string name="settings_gif">아바타와 에모지의 애니메이션을 재생</string>
|
||||
<string name="settings_custom_tabs">앱내 브라우저 사용</string>
|
||||
<string name="settings_notifications">알림</string>
|
||||
<string name="notify_me_when">알림을 보낼 조건</string>
|
||||
<string name="notify_anyone">누구든</string>
|
||||
@@ -204,13 +248,52 @@
|
||||
<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>
|
||||
<string name="storage_permission_to_download">파일을 저장하기 위해서는 앱이 스토리지에 접근할 수 있는 권한이 필요합니다.</string>
|
||||
<string name="open_settings">설정 열기</string>
|
||||
<string name="error_saving_file">파일 저장 오류</string>
|
||||
<string name="file_saved">파일 저장됨</string>
|
||||
<string name="downloading">다운로드중…</string>
|
||||
<string name="no_app_to_handle_action">이 동작을 처리할 앱이 없습니다</string>
|
||||
<string name="local_timeline">커뮤니티</string>
|
||||
<string name="trending_posts_info_banner">당신이 속한 마스토돈 서버에서 관심을 끌고 있는 게시물들입니다.</string>
|
||||
<string name="trending_hashtags_info_banner">당신이 속한 마스토돈 서버에서 관심을 끌고 있는 해시태그들입니다.</string>
|
||||
<string name="trending_links_info_banner">당신이 속한 마스토돈 서버에서 가장 많이 공유되고 있는 소식들입니다.</string>
|
||||
<string name="local_timeline_info_banner">당신과 같은 마스토돈 서버에 존재하는 사람들이 공유한 최신 게시물들입니다.</string>
|
||||
<string name="dismiss">지우기</string>
|
||||
<string name="see_new_posts">새 게시물 보기</string>
|
||||
<string name="load_missing_posts">빈 게시물 불러오기</string>
|
||||
<string name="follow_back">맞팔로우</string>
|
||||
<string name="button_follow_pending">대기중</string>
|
||||
<string name="follows_you">나를 팔로우합니다</string>
|
||||
<string name="manually_approves_followers">수동으로 팔로워 승인</string>
|
||||
@@ -230,4 +313,5 @@
|
||||
<item quantity="other">%,d 개의 리블로그</item>
|
||||
</plurals>
|
||||
<string name="timestamp_via_app">%1$s에 %2$s에서</string>
|
||||
<string name="time_now">방금</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Mastodon</string>
|
||||
<string name="get_started">Rozpocznij</string>
|
||||
<string name="log_in">Zaloguj się</string>
|
||||
<string name="next">Dalej</string>
|
||||
@@ -10,10 +9,13 @@
|
||||
<string name="ok">OK</string>
|
||||
<string name="preparing_auth">Przygotowywanie do uwierzytelniania…</string>
|
||||
<string name="finishing_auth">Kończenie uwierzytelniania…</string>
|
||||
<string name="user_boosted">%s podbił</string>
|
||||
<string name="in_reply_to">W odpowiedzi do %s</string>
|
||||
<string name="notifications">Powiadomienia</string>
|
||||
<string name="user_followed_you">zaczął(-ęła) Cię śledzić</string>
|
||||
<string name="user_sent_follow_request">wysłał(-a) prośbę o możliwość obserwacji</string>
|
||||
<string name="user_favorited">dodał(a) Twój wpis do ulubionych</string>
|
||||
<string name="notification_boosted">podbił Twój wpis</string>
|
||||
<string name="poll_ended">głosowanie zakończyło się</string>
|
||||
<string name="time_seconds">%d sek.</string>
|
||||
<string name="time_minutes">%d min.</string>
|
||||
@@ -25,6 +27,24 @@
|
||||
<string name="discard_draft">Odrzucić szkic?</string>
|
||||
<string name="discard">Odrzuć</string>
|
||||
<string name="cancel">Anuluj</string>
|
||||
<plurals name="followers">
|
||||
<item quantity="one">śledzący</item>
|
||||
<item quantity="few">śledzących</item>
|
||||
<item quantity="many">śledzących</item>
|
||||
<item quantity="other">śledzących</item>
|
||||
</plurals>
|
||||
<plurals name="following">
|
||||
<item quantity="one">śledzony</item>
|
||||
<item quantity="few">śledzonych</item>
|
||||
<item quantity="many">śledzonych</item>
|
||||
<item quantity="other">śledzonych</item>
|
||||
</plurals>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">wpis</item>
|
||||
<item quantity="few">wpisy</item>
|
||||
<item quantity="many">wpisów</item>
|
||||
<item quantity="other">wpisów</item>
|
||||
</plurals>
|
||||
<string name="posts">Wpisy</string>
|
||||
<string name="posts_and_replies">Wpisy i odpowiedzi</string>
|
||||
<string name="media">Media</string>
|
||||
@@ -41,6 +61,12 @@
|
||||
<string name="report_user">Zgłoś %s</string>
|
||||
<string name="block_domain">Zablokuj %s</string>
|
||||
<string name="unblock_domain">Odblokuj %s</string>
|
||||
<plurals name="x_posts">
|
||||
<item quantity="one">%,d wpis</item>
|
||||
<item quantity="few">%,d wpisy</item>
|
||||
<item quantity="many">%,d wpisów</item>
|
||||
<item quantity="other">%,d wpisów</item>
|
||||
</plurals>
|
||||
<string name="profile_joined">Dołączył(a)</string>
|
||||
<string name="done">Gotowe</string>
|
||||
<string name="loading">Ładowanie…</string>
|
||||
@@ -49,7 +75,55 @@
|
||||
<string name="saving">Zapisywanie…</string>
|
||||
<string name="post_from_user">Wpis od %s</string>
|
||||
<string name="poll_option_hint">Opcja %d</string>
|
||||
<plurals name="x_minutes">
|
||||
<item quantity="one">%d minuta</item>
|
||||
<item quantity="few">%d minuty</item>
|
||||
<item quantity="many">%d minut</item>
|
||||
<item quantity="other">%d minut</item>
|
||||
</plurals>
|
||||
<plurals name="x_hours">
|
||||
<item quantity="one">%d godzina</item>
|
||||
<item quantity="few">%d godziny</item>
|
||||
<item quantity="many">%d godzin</item>
|
||||
<item quantity="other">%d godzin</item>
|
||||
</plurals>
|
||||
<plurals name="x_days">
|
||||
<item quantity="one">%d dzień</item>
|
||||
<item quantity="few">%d dni</item>
|
||||
<item quantity="many">%d dni</item>
|
||||
<item quantity="other">%d dni</item>
|
||||
</plurals>
|
||||
<string name="compose_poll_duration">Czas trwania: %s</string>
|
||||
<plurals name="x_seconds_left">
|
||||
<item quantity="one">Została %d sekunda</item>
|
||||
<item quantity="few">Zostały %d sekundy</item>
|
||||
<item quantity="many">Zostało %d sekund</item>
|
||||
<item quantity="other">Zostało %d sekund</item>
|
||||
</plurals>
|
||||
<plurals name="x_minutes_left">
|
||||
<item quantity="one">Została %d minuta</item>
|
||||
<item quantity="few">Zostały %d minuty</item>
|
||||
<item quantity="many">Zostało %d minut</item>
|
||||
<item quantity="other">Zostało %d minut</item>
|
||||
</plurals>
|
||||
<plurals name="x_hours_left">
|
||||
<item quantity="one">Została %d godzina</item>
|
||||
<item quantity="few">Zostały %d godziny</item>
|
||||
<item quantity="many">Zostało %d godzin</item>
|
||||
<item quantity="other">Zostało %d godzin</item>
|
||||
</plurals>
|
||||
<plurals name="x_days_left">
|
||||
<item quantity="one">Został %d dzień</item>
|
||||
<item quantity="few">Zostały %d dni</item>
|
||||
<item quantity="many">Zostało %d dni</item>
|
||||
<item quantity="other">Zostało %d dni</item>
|
||||
</plurals>
|
||||
<plurals name="x_voters">
|
||||
<item quantity="one">%,d głosujący</item>
|
||||
<item quantity="few">%,d głosujących</item>
|
||||
<item quantity="many">%,d głosujących</item>
|
||||
<item quantity="other">%,d głosujących</item>
|
||||
</plurals>
|
||||
<string name="poll_closed">Zamknięte</string>
|
||||
<string name="confirm_mute_title">Wycisz konto</string>
|
||||
<string name="confirm_mute">Potwierdź wyciszenie %s</string>
|
||||
@@ -84,6 +158,18 @@
|
||||
<string name="for_you">Dla Ciebie</string>
|
||||
<string name="all_notifications">Wszystkie</string>
|
||||
<string name="mentions">Wspomienia</string>
|
||||
<plurals name="x_people_talking">
|
||||
<item quantity="one">%d mówi o tym</item>
|
||||
<item quantity="few">%d osoby mówią o tym</item>
|
||||
<item quantity="many">%d osób mówi o tym</item>
|
||||
<item quantity="other">%d osób mówi o tym</item>
|
||||
</plurals>
|
||||
<plurals name="discussed_x_times">
|
||||
<item quantity="one">Omówione %d raz</item>
|
||||
<item quantity="few">Omawiane %d razy</item>
|
||||
<item quantity="many">Omawiane %d razy</item>
|
||||
<item quantity="other">Omawiane %d razy</item>
|
||||
</plurals>
|
||||
<string name="report_title">Zgłoś %s</string>
|
||||
<string name="report_choose_reason">Co jest nie tak z tym wpisem?</string>
|
||||
<string name="report_choose_reason_account">Co jest nie tak z %s?</string>
|
||||
@@ -110,8 +196,14 @@
|
||||
<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="report_personal_subtitle">Gdy zobaczysz na Mastodonie coś, co Ci się nie spodoba, możesz usunąć daną osobę ze swojego doświadczenia.</string>
|
||||
<string name="back">Wróć</string>
|
||||
<string name="instance_catalog_title">Mastodon składa się z użytkowników na różnych serwerach.</string>
|
||||
<string name="instance_catalog_subtitle">Wybierz serwer według Twoich zainteresowań, regionu lub ogólnotematyczny. Możesz nadal łączyć się ze wszystkimi, niezależnie od serwera.</string>
|
||||
<string name="search_communities">Szukaj serwerów lub wprowadź adres URL</string>
|
||||
<string name="instance_rules_title">Kilka podstawowych zasad</string>
|
||||
<string name="instance_rules_subtitle">Poświęć chwilę, aby przejrzeć reguły ustalone i realizowane przez administratorów %s.</string>
|
||||
<string name="signup_title">Skonfigurujmy Twoje konto na %s</string>
|
||||
<string name="edit_photo">edytuj</string>
|
||||
<string name="display_name">wyświetlana nazwa</string>
|
||||
<string name="username">nazwa użytkownika</string>
|
||||
@@ -157,10 +249,17 @@
|
||||
<string name="skip">Pomiń</string>
|
||||
<string name="notification_type_follow">Nowi śledzący</string>
|
||||
<string name="notification_type_favorite">Polubienia</string>
|
||||
<string name="notification_type_reblog">Podbicia</string>
|
||||
<string name="notification_type_mention">Wspomienia</string>
|
||||
<string name="notification_type_poll">Głosowania</string>
|
||||
<string name="choose_account">Wybierz konto</string>
|
||||
<string name="err_not_logged_in">Najpierw zaloguj się do Mastodona</string>
|
||||
<plurals name="cant_add_more_than_x_attachments">
|
||||
<item quantity="one">Nie możesz dodać więcej niż %d załącznik</item>
|
||||
<item quantity="few">Nie możesz dodać więcej niż %d załączniki</item>
|
||||
<item quantity="many">Nie możesz dodać więcej niż %d załączników</item>
|
||||
<item quantity="other">Nie możesz dodać więcej niż %d załączników</item>
|
||||
</plurals>
|
||||
<string name="media_attachment_unsupported_type">Plik %s jest nieobsługiwanego rodzaju</string>
|
||||
<string name="media_attachment_too_big">Plik %1$s przekracza limit rozmiaru %2$s MB</string>
|
||||
<string name="settings_theme">Ustawienia wizualne</string>
|
||||
@@ -179,7 +278,7 @@
|
||||
<string name="notify_none">nikt</string>
|
||||
<string name="notify_favorites">Doda mój wpis do ulubionych</string>
|
||||
<string name="notify_follow">Zacznie mnie śledzić</string>
|
||||
<string name="notify_reblog">Podbije mój wpis</string>
|
||||
<string name="notify_reblog">Podbija mój wpis</string>
|
||||
<string name="notify_mention">Wspomni o mnie</string>
|
||||
<string name="settings_boring">Nudne opcje</string>
|
||||
<string name="settings_account">Ustawienia konta</string>
|
||||
@@ -215,6 +314,8 @@
|
||||
<string name="unfollowed_user">Przestano obserwować %s</string>
|
||||
<string name="followed_user">Od teraz śledzisz %s</string>
|
||||
<string name="open_in_browser">Otwórz w przeglądarce</string>
|
||||
<string name="hide_boosts_from_user">Ukryj podbicia od %s</string>
|
||||
<string name="show_boosts_from_user">Pokaż podbicia od %s</string>
|
||||
<string name="signup_reason">dlaczego chcesz dołączyć?</string>
|
||||
<string name="signup_reason_note">To pomoże nam ocenić Twoją aplikację.</string>
|
||||
<string name="clear">Wyczyść</string>
|
||||
@@ -229,5 +330,45 @@
|
||||
<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>
|
||||
<string name="local_timeline">Społeczność</string>
|
||||
<string name="trending_posts_info_banner">Oto wpisy zyskujące popularność w Twoim zakątku Mastodona.</string>
|
||||
<string name="trending_hashtags_info_banner">Oto hashtagi zyskujące popularność w Twoim zakątku Mastodona.</string>
|
||||
<string name="trending_links_info_banner">Oto wiadomości, które są najczęściej udostępniane w Twoim zakątku Mastodona.</string>
|
||||
<string name="local_timeline_info_banner">To są najnowsze wpisy osób używających tego samego serwera Mastodon co Ty.</string>
|
||||
<string name="dismiss">Odrzuć</string>
|
||||
<string name="see_new_posts">Zobacz nowe wpisy</string>
|
||||
<string name="load_missing_posts">Załaduj brakujące wpisy</string>
|
||||
<string name="follow_back">Śledź wzajemnie</string>
|
||||
<string name="button_follow_pending">Oczekujące</string>
|
||||
<string name="follows_you">Obserwuje cię</string>
|
||||
<string name="manually_approves_followers">Ręcznie zatwierdza obserwujących</string>
|
||||
<string name="current_account">Bieżące konto</string>
|
||||
<string name="log_out_account">Wyloguj %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 śledzący</item>
|
||||
<item quantity="few">%,d śledzących</item>
|
||||
<item quantity="many">%,d śledzących</item>
|
||||
<item quantity="other">%,d śledzących</item>
|
||||
</plurals>
|
||||
<plurals name="x_following">
|
||||
<item quantity="one">%,d śledzący</item>
|
||||
<item quantity="few">%,d śledzących</item>
|
||||
<item quantity="many">%,d śledzących</item>
|
||||
<item quantity="other">%,d śledzących</item>
|
||||
</plurals>
|
||||
<plurals name="x_favorites">
|
||||
<item quantity="one">%,d ulubiony</item>
|
||||
<item quantity="few">%,d ulubione</item>
|
||||
<item quantity="many">%,d ulubionych</item>
|
||||
<item quantity="other">%,d ulubionych</item>
|
||||
</plurals>
|
||||
<plurals name="x_reblogs">
|
||||
<item quantity="one">%,d podbicie</item>
|
||||
<item quantity="few">%,d podbicia</item>
|
||||
<item quantity="many">%,d podbić</item>
|
||||
<item quantity="other">%,d podbić</item>
|
||||
</plurals>
|
||||
<string name="timestamp_via_app">%1$s przez %2$s</string>
|
||||
<string name="time_now">przed chwilą</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Mastodon</string>
|
||||
<string name="get_started">Comece já</string>
|
||||
<string name="log_in">Entrar</string>
|
||||
<string name="next">Próximo</string>
|
||||
<string name="loading_instance">Obtendo 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 do Mastodon.</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="preparing_auth">Preparando para autenticação…</string>
|
||||
<string name="finishing_auth">Finalizando autenticação…</string>
|
||||
<string name="in_reply_to">Em resposta à %s</string>
|
||||
<string name="notifications">Notificações</string>
|
||||
<string name="user_followed_you">seguiu você</string>
|
||||
<string name="user_sent_follow_request">enviou uma solicitação para você seguir</string>
|
||||
<string name="user_favorited">favoritou seu post</string>
|
||||
<string name="poll_ended">enquete encerrada</string>
|
||||
<string name="time_seconds">%ds</string>
|
||||
<string name="time_minutes">%dm</string>
|
||||
<string name="time_hours">%dh</string>
|
||||
<string name="time_days">%dd</string>
|
||||
<string name="share_toot_title">Compartilhar</string>
|
||||
<string name="settings">Confirgurações</string>
|
||||
<string name="publish">Publicar</string>
|
||||
@@ -22,7 +29,16 @@
|
||||
<item quantity="one">seguidor</item>
|
||||
<item quantity="other">seguidores</item>
|
||||
</plurals>
|
||||
<plurals name="following">
|
||||
<item quantity="one">seguindo</item>
|
||||
<item quantity="other">seguindo</item>
|
||||
</plurals>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">post</item>
|
||||
<item quantity="other">posts</item>
|
||||
</plurals>
|
||||
<string name="posts">Publicações</string>
|
||||
<string name="posts_and_replies">Posts e respostas</string>
|
||||
<string name="media">Mídia</string>
|
||||
<string name="profile_about">Sobre</string>
|
||||
<string name="button_follow">Seguir</string>
|
||||
@@ -37,11 +53,18 @@
|
||||
<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 post</item>
|
||||
<item quantity="other">%,d posts</item>
|
||||
</plurals>
|
||||
<string name="profile_joined">Entrou</string>
|
||||
<string name="done">Concluído</string>
|
||||
<string name="loading">Carregando…</string>
|
||||
<string name="field_label">Rótulo</string>
|
||||
<string name="field_content">Conteúdo</string>
|
||||
<string name="saving">Salvando…</string>
|
||||
<string name="post_from_user">Post de %s</string>
|
||||
<string name="poll_option_hint">Opção %d</string>
|
||||
<plurals name="x_minutes">
|
||||
<item quantity="one">%d minuto</item>
|
||||
<item quantity="other">%d minutos</item>
|
||||
@@ -71,17 +94,24 @@
|
||||
<item quantity="one">%d dia restante</item>
|
||||
<item quantity="other">%d dias restantes</item>
|
||||
</plurals>
|
||||
<plurals name="x_voters">
|
||||
<item quantity="one">%,d votante</item>
|
||||
<item quantity="other">%,d votantes</item>
|
||||
</plurals>
|
||||
<string name="poll_closed">Terminou</string>
|
||||
<string name="confirm_mute_title">Silenciar Conta</string>
|
||||
<string name="confirm_mute">Confirme para silenciar %s</string>
|
||||
<string name="do_mute">Silenciar</string>
|
||||
<string name="confirm_unmute_title">Silenciar conta</string>
|
||||
<string name="confirm_unmute">Confirme para silenciar %s</string>
|
||||
<string name="do_unmute">Reativar</string>
|
||||
<string name="confirm_block_title">Bloquear Conta</string>
|
||||
<string name="confirm_block_domain_title">Bloquear Domínio</string>
|
||||
<string name="confirm_block">Confirme para bloquear %s</string>
|
||||
<string name="do_block">Bloquear</string>
|
||||
<string name="confirm_unblock_title">Desbloquear Conta</string>
|
||||
<string name="confirm_unblock_domain_title">Desbloquear Domínio</string>
|
||||
<string name="confirm_unblock">Confirme para bloquear %s</string>
|
||||
<string name="do_unblock">Desbloquear</string>
|
||||
<string name="button_muted">Silenciado</string>
|
||||
<string name="button_blocked">Bloqueado</string>
|
||||
@@ -92,10 +122,13 @@
|
||||
<string name="confirm_delete">Você tem certeza de que deseja excluir esta publicação?</string>
|
||||
<string name="deleting">Excluindo…</string>
|
||||
<string name="notification_channel_audio_player">Reprodução de áudio</string>
|
||||
<string name="play">Executar</string>
|
||||
<string name="pause">Pausar</string>
|
||||
<string name="log_out">Sair</string>
|
||||
<string name="add_account">Adicionar conta</string>
|
||||
<string name="search_hint">Buscar</string>
|
||||
<string name="hashtags">Hashtags</string>
|
||||
<string name="news">Notícias</string>
|
||||
<string name="for_you">Para você</string>
|
||||
<string name="all_notifications">Todos</string>
|
||||
<string name="mentions">Menções</string>
|
||||
@@ -107,16 +140,42 @@
|
||||
<string name="report_choose_reason">O que há de errado com esta publicação?</string>
|
||||
<string name="report_choose_reason_account">O que há de errado com %s?</string>
|
||||
<string name="report_choose_reason_subtitle">Selecione a melhor correspondência</string>
|
||||
<string name="report_reason_personal">Eu não gostei</string>
|
||||
<string name="report_reason_personal_subtitle">Não é algo que você quer ver</string>
|
||||
<string name="report_reason_spam">É spam</string>
|
||||
<string name="report_reason_spam_subtitle">Links maliciosos, envolvimento falso ou respostas repetitivas</string>
|
||||
<string name="report_reason_violation">Viola as regras do servidor</string>
|
||||
<string name="report_reason_violation_subtitle">Você está ciente de que isso quebra regras específicas</string>
|
||||
<string name="report_reason_other">É outra coisa</string>
|
||||
<string name="report_reason_other_subtitle">O problema não se encaixa em outras categorias</string>
|
||||
<string name="report_choose_rule">Que regras estão sendo violadas?</string>
|
||||
<string name="report_choose_rule_subtitle">Selecione tudo que se aplica</string>
|
||||
<string name="report_choose_posts">Existem postagens que respaldam esse relatório?</string>
|
||||
<string name="report_choose_posts_subtitle">Selecione tudo que se aplica</string>
|
||||
<string name="report_comment_title">Há algo que você acredita que devemos saber?</string>
|
||||
<string name="report_comment_hint">Comentários adicionais</string>
|
||||
<string name="sending_report">Enviando denúncia…</string>
|
||||
<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="unfollow">Deixar de seguir</string>
|
||||
<string name="block_user_explain">Eles não poderão mais seguir ou ver seus post, mas poderão ver se foram bloqueados.</string>
|
||||
<string name="report_personal_title">Não quer ver isto?</string>
|
||||
<string name="report_personal_subtitle">Quando você vê algo que não gosta no Mastodon, pode remover a pessoa da sua experiência.</string>
|
||||
<string name="back">Voltar</string>
|
||||
<string name="instance_catalog_title">Mastodon é feito de usuários em servidores diferentes.</string>
|
||||
<string name="instance_catalog_subtitle">Escolha um servidor com base em seus interesses, região ou um de uso geral. Você ainda pode se conectar com todos, independentemente do servidor.</string>
|
||||
<string name="search_communities">Pesquise servidores ou digite a URL</string>
|
||||
<string name="instance_rules_title">Algumas regras básicas</string>
|
||||
<string name="instance_rules_subtitle">Reserve um minuto para revisar as regras definidas e aplicadas pelos %s administradores.</string>
|
||||
<string name="signup_title">Vamos configurar você em %s</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="password_note">Inclua letras maiúsculas, caracteres especiais e números para aumentar a força da sua senha.</string>
|
||||
<string name="category_academia">Acadêmico</string>
|
||||
<string name="category_activism">Ativismo</string>
|
||||
<string name="category_all">Todas</string>
|
||||
<string name="category_art">Arte</string>
|
||||
@@ -134,6 +193,7 @@
|
||||
<string name="resend">Reenviar</string>
|
||||
<string name="open_email_app">Abrir aplicativo de e-mail</string>
|
||||
<string name="resent_email">E-mail de confirmação enviado</string>
|
||||
<string name="compose_hint">Digite ou cole o que está na sua mente</string>
|
||||
<string name="content_warning">Aviso de conteúdo</string>
|
||||
<string name="add_image_description">Adicionar descrição da imagem…</string>
|
||||
<string name="retry_upload">Tentar enviar novamente</string>
|
||||
@@ -142,12 +202,15 @@
|
||||
<string name="edit_image">Editar imagem</string>
|
||||
<string name="save">Salvar</string>
|
||||
<string name="add_alt_text">Adicionar texto alternativo</string>
|
||||
<string name="alt_text_subtitle">O texto alternativo descreve suas fotos para pessoas com baixa ou nenhuma visão. Tente incluir apenas detalhes suficientes para entender o contexto.</string>
|
||||
<string name="alt_text_hint">por exemplo: Um cachorro olhando desconfiado com os olhos apertados para a câmera.</string>
|
||||
<string name="visibility_public">Público</string>
|
||||
<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="step_x_of_n">Passo %1$d de %2$d</string>
|
||||
<string name="skip">Pular</string>
|
||||
<string name="notification_type_follow">Novos seguidores</string>
|
||||
<string name="notification_type_favorite">Favoritos</string>
|
||||
@@ -165,26 +228,37 @@
|
||||
<string name="theme_auto">Automático</string>
|
||||
<string name="theme_light">Claro</string>
|
||||
<string name="theme_dark">Escuro</string>
|
||||
<string name="theme_true_black">Modo escuro absoluto</string>
|
||||
<string name="settings_behavior">Comportamento</string>
|
||||
<string name="settings_gif">Reproduzir emojis e avatares animados</string>
|
||||
<string name="settings_custom_tabs">Usar navegador interno</string>
|
||||
<string name="settings_notifications">Notificações</string>
|
||||
<string name="notify_me_when">Notificar-me quando</string>
|
||||
<string name="notify_anyone">qualquer pessoa</string>
|
||||
<string name="notify_follower">um seguidor</string>
|
||||
<string name="notify_followed">pessoas que eu sigo</string>
|
||||
<string name="notify_none">ninguém</string>
|
||||
<string name="notify_favorites">Favoritaram minha publicação</string>
|
||||
<string name="notify_follow">Sigam-me</string>
|
||||
<string name="notify_mention">Mencione-me</string>
|
||||
<string name="settings_boring">A zona entediante</string>
|
||||
<string name="settings_account">Configurações da conta</string>
|
||||
<string name="settings_contribute">Contribua para o Mastodon</string>
|
||||
<string name="settings_tos">Termos de serviço</string>
|
||||
<string name="settings_privacy_policy">Política de privacidade</string>
|
||||
<string name="settings_spicy">A zona picante</string>
|
||||
<string name="settings_clear_cache">Limpar cache de mídia</string>
|
||||
<string name="settings_app_version">Mastodon para Android v%1$s (%2$d)</string>
|
||||
<string name="media_cache_cleared">Cache de mídia limpo</string>
|
||||
<string name="confirm_log_out">Você tem certeza que deseja sair?</string>
|
||||
<string name="sensitive_content">Conteúdo sensível</string>
|
||||
<string name="sensitive_content_explain">O autor marcou essa mídia como sensível. Toque para revelar.</string>
|
||||
<string name="media_hidden">Tocar para exibir</string>
|
||||
<string name="avatar_description">Ir para o perfil de %s</string>
|
||||
<string name="more_options">Mais opções</string>
|
||||
<string name="reveal_content">Revelar conteúdo</string>
|
||||
<string name="hide_content">Ocultar conteúdo</string>
|
||||
<string name="new_post">Novo post</string>
|
||||
<string name="button_reply">Responder</string>
|
||||
<string name="button_favorite">Favoritar</string>
|
||||
<string name="button_share">Compartilhar</string>
|
||||
@@ -192,6 +266,7 @@
|
||||
<string name="add_media">Adicionar mídia</string>
|
||||
<string name="add_poll">Adicionar enquete</string>
|
||||
<string name="emoji">Emoji</string>
|
||||
<string name="post_visibility">Visibility do post</string>
|
||||
<string name="home_timeline">Página Inicial</string>
|
||||
<string name="my_profile">Meu perfil</string>
|
||||
<string name="media_viewer">Visualizador de mídia</string>
|
||||
@@ -212,5 +287,34 @@
|
||||
<string name="error_saving_file">Erro ao salvar arquivo</string>
|
||||
<string name="file_saved">Arquivo salvo</string>
|
||||
<string name="downloading">Baixando…</string>
|
||||
<string name="no_app_to_handle_action">Não há app para executar esta ação</string>
|
||||
<string name="local_timeline">Comunidade</string>
|
||||
<string name="trending_posts_info_banner">Esses são os posts que estão ganhando força no seu canto do Mastodon.</string>
|
||||
<string name="trending_hashtags_info_banner">Essas são as hashtags que estão ganhando força no seu canto do Mastodon.</string>
|
||||
<string name="trending_links_info_banner">Estas são as recentes histórias que estão sendo mais compartilhadas no seu canto do Mastodon.</string>
|
||||
<string name="local_timeline_info_banner">Estas são as publicações mais recentes das pessoas que usam o mesmo servidor Mastodon que você.</string>
|
||||
<string name="dismiss">Dispensar</string>
|
||||
<string name="see_new_posts">Veja novas postagens</string>
|
||||
<string name="load_missing_posts">Carregar postagens perdidas</string>
|
||||
<string name="follow_back">Seguir de volta</string>
|
||||
<string name="button_follow_pending">Pendente</string>
|
||||
<string name="follows_you">Segue você</string>
|
||||
<string name="manually_approves_followers">Aprovar seguidores manualmente</string>
|
||||
<string name="current_account">Conta atual</string>
|
||||
<string name="log_out_account">Sair %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 seguidor</item>
|
||||
<item quantity="other">%,d seguidores</item>
|
||||
</plurals>
|
||||
<plurals name="x_following">
|
||||
<item quantity="one">%,d seguindo</item>
|
||||
<item quantity="other">%,d seguindo</item>
|
||||
</plurals>
|
||||
<plurals name="x_favorites">
|
||||
<item quantity="one">%,d favorito</item>
|
||||
<item quantity="other">%,d favoritos</item>
|
||||
</plurals>
|
||||
<string name="timestamp_via_app">%1$s via %2$s</string>
|
||||
<string name="time_now">agora</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Mastodon</string>
|
||||
<string name="get_started">Iniciar</string>
|
||||
<string name="log_in">Entrar</string>
|
||||
<string name="next">Seguinte</string>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Mastodon</string>
|
||||
<string name="get_started">Присоединиться</string>
|
||||
<string name="log_in">Войти</string>
|
||||
<string name="next">Далее</string>
|
||||
@@ -250,6 +249,7 @@
|
||||
<string name="skip">Пропустить</string>
|
||||
<string name="notification_type_follow">Новые подписчики</string>
|
||||
<string name="notification_type_favorite">Избранное</string>
|
||||
<string name="notification_type_reblog">Продвижения</string>
|
||||
<string name="notification_type_mention">Упоминания</string>
|
||||
<string name="notification_type_poll">Опросы</string>
|
||||
<string name="choose_account">Выберите аккаунт</string>
|
||||
@@ -358,4 +358,5 @@
|
||||
<item quantity="other">%,d подписок</item>
|
||||
</plurals>
|
||||
<string name="timestamp_via_app">%1$s через %2$s</string>
|
||||
<string name="time_now">только что</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,4 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="get_started">Kom igång</string>
|
||||
<string name="log_in">Logga in</string>
|
||||
<string name="next">Nästa</string>
|
||||
<string name="error">Fel</string>
|
||||
<string name="user_boosted">%s ompostade</string>
|
||||
<string name="share_toot_title">Dela</string>
|
||||
<string name="settings">Inställningar</string>
|
||||
<string name="publish">Publicera</string>
|
||||
<string name="profile_about">Om</string>
|
||||
<string name="done">Klar</string>
|
||||
<string name="do_mute">Tysta</string>
|
||||
<string name="do_block">Blockera</string>
|
||||
<string name="delete">Radera</string>
|
||||
<string name="category_all">Allt</string>
|
||||
<string name="category_art">Konst</string>
|
||||
<string name="category_food">Mat</string>
|
||||
<string name="category_furry">Furry</string>
|
||||
<string name="category_games">Spel</string>
|
||||
<string name="category_general">Allmänt</string>
|
||||
<string name="category_journalism">Journalistik</string>
|
||||
<string name="category_lgbt">HBTQ</string>
|
||||
<string name="category_music">Musik</string>
|
||||
<string name="category_regional">Regionalt</string>
|
||||
<string name="category_tech">Teknik</string>
|
||||
<string name="button_reply">Svara</string>
|
||||
<string name="button_share">Dela</string>
|
||||
<string name="emoji">Emoji</string>
|
||||
<string name="open_in_browser">Öppna i webbläsare</string>
|
||||
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Mastodon</string>
|
||||
<string name="get_started">เริ่มต้นใช้งาน</string>
|
||||
<string name="log_in">เข้าสู่ระบบ</string>
|
||||
<string name="next">ถัดไป</string>
|
||||
@@ -169,7 +168,19 @@
|
||||
<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="category_lgbt">LGBT</string>
|
||||
<string name="category_music">ดนตรี</string>
|
||||
<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>
|
||||
@@ -184,6 +195,8 @@
|
||||
<string name="edit_image">แก้ไขภาพ</string>
|
||||
<string name="save">บันทึก</string>
|
||||
<string name="add_alt_text">เพิ่มข้อความแสดงแทน</string>
|
||||
<string name="alt_text_subtitle">ข้อความแสดงแทนอธิบายรูปถ่ายของคุณสำหรับผู้ที่มีการมองเห็นเลือนรางหรือมองไม่เห็น พยายามรวมรายละเอียดให้เพียงพอเพื่อให้เข้าใจบริบท</string>
|
||||
<string name="alt_text_hint">เช่น สุนัขมองไปรอบ ๆ อย่างสงสัยโดยหรี่ตาลงที่กล้อง</string>
|
||||
<string name="visibility_public">สาธารณะ</string>
|
||||
<string name="visibility_followers_only">ผู้ติดตามเท่านั้น</string>
|
||||
<string name="visibility_private">เฉพาะผู้คนที่ฉันกล่าวถึงเท่านั้น</string>
|
||||
@@ -202,6 +215,8 @@
|
||||
<plurals name="cant_add_more_than_x_attachments">
|
||||
<item quantity="other">คุณไม่สามารถเพิ่มมากกว่า %d ไฟล์แนบสื่อ</item>
|
||||
</plurals>
|
||||
<string name="media_attachment_unsupported_type">ไฟล์ %s เป็นชนิดที่ไม่รองรับ</string>
|
||||
<string name="media_attachment_too_big">ไฟล์ %1$s เกินขีดจำกัดขนาด %2$s MB</string>
|
||||
<string name="settings_theme">ลักษณะที่มองเห็น</string>
|
||||
<string name="theme_auto">อัตโนมัติ</string>
|
||||
<string name="theme_light">สว่าง</string>
|
||||
@@ -297,4 +312,6 @@
|
||||
<plurals name="x_reblogs">
|
||||
<item quantity="other">%,d การดัน</item>
|
||||
</plurals>
|
||||
<string name="timestamp_via_app">%1$s ผ่าน %2$s</string>
|
||||
<string name="time_now">ตอนนี้</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Mastodon</string>
|
||||
<string name="get_started">Başlayın</string>
|
||||
<string name="log_in">Giriş Yap</string>
|
||||
<string name="next">Sonraki</string>
|
||||
@@ -10,10 +9,13 @@
|
||||
<string name="ok">Tamam</string>
|
||||
<string name="preparing_auth">Kimlik doğrulama için hazırlanıyor…</string>
|
||||
<string name="finishing_auth">Kimlik doğrulama tamamlanıyor…</string>
|
||||
<string name="user_boosted">%s gönderinizi yeniden paylaştı</string>
|
||||
<string name="in_reply_to">%s için yanıt</string>
|
||||
<string name="notifications">Bildirimler</string>
|
||||
<string name="user_followed_you">seni takip etti</string>
|
||||
<string name="user_sent_follow_request">sana bir takip isteği gönderdi</string>
|
||||
<string name="user_favorited">gönderinizi favorilerine ekledi</string>
|
||||
<string name="notification_boosted">gönderinizi yeniden paylaştı</string>
|
||||
<string name="poll_ended">oylama sona erdi</string>
|
||||
<string name="time_seconds">%ds</string>
|
||||
<string name="time_minutes">%dd</string>
|
||||
@@ -219,6 +221,7 @@
|
||||
<string name="skip">Atla</string>
|
||||
<string name="notification_type_follow">Yeni takipçiler</string>
|
||||
<string name="notification_type_favorite">Favoriler</string>
|
||||
<string name="notification_type_reblog">Yeniden paylaşımlar</string>
|
||||
<string name="notification_type_mention">Bahsetmeler</string>
|
||||
<string name="notification_type_poll">Anketler</string>
|
||||
<string name="choose_account">Hesap seç</string>
|
||||
@@ -281,6 +284,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 kişisinin yeniden paylaşımlarını gizle</string>
|
||||
<string name="show_boosts_from_user">%s kişisinin yeniden 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>
|
||||
@@ -310,4 +315,21 @@
|
||||
<string name="current_account">Kullanılan hesap</string>
|
||||
<string name="log_out_account">%s oturumunu kapat</string>
|
||||
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
||||
<plurals name="x_followers">
|
||||
<item quantity="one">%,d takipçi</item>
|
||||
<item quantity="other">%,d takipçi</item>
|
||||
</plurals>
|
||||
<plurals name="x_following">
|
||||
<item quantity="one">%,d takip edilen</item>
|
||||
<item quantity="other">%,d takip edilen</item>
|
||||
</plurals>
|
||||
<plurals name="x_favorites">
|
||||
<item quantity="one">%,d favori</item>
|
||||
<item quantity="other">%,d favori</item>
|
||||
</plurals>
|
||||
<plurals name="x_reblogs">
|
||||
<item quantity="one">%,d yeniden paylaşım</item>
|
||||
<item quantity="other">%,d yeniden paylaşım</item>
|
||||
</plurals>
|
||||
<string name="timestamp_via_app">%1$s tarihinde %2$s uygulamasıyla</string>
|
||||
</resources>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user