Compare commits
416 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f241d74e27 | ||
|
|
ff0f3b414b | ||
|
|
f9fd62db09 | ||
|
|
75f1caf022 | ||
|
|
6f6d2e1e08 | ||
|
|
482b7d5e7d | ||
|
|
1e729d97a0 | ||
|
|
357e348681 | ||
|
|
65c1b4def0 | ||
|
|
461eac8932 | ||
|
|
12d4ec2129 | ||
|
|
26f9737e5e | ||
|
|
771740ca93 | ||
|
|
ed070118d6 | ||
|
|
e7094beab9 | ||
|
|
501a2715be | ||
|
|
bc2380dfb1 | ||
|
|
4b584af0a9 | ||
|
|
4bc1083939 | ||
|
|
b34b5032ee | ||
|
|
d10b70cc62 | ||
|
|
a8ba45c14b | ||
|
|
9d34f0896f | ||
|
|
e492aca885 | ||
|
|
12208b849a | ||
|
|
7f2af61244 | ||
|
|
f745f95286 | ||
|
|
cf1c8eb4e3 | ||
|
|
6d5ab6b4ba | ||
|
|
ab27e36560 | ||
|
|
f368026124 | ||
|
|
bb4c68c6bd | ||
|
|
b675c14767 | ||
|
|
5e1b63ba21 | ||
|
|
5a462df797 | ||
|
|
6d96ab5b7c | ||
|
|
1f051f3fd8 | ||
|
|
e4e91dd283 | ||
|
|
aca0773874 | ||
|
|
2e5d73162d | ||
|
|
0c04701750 | ||
|
|
591ee62cc9 | ||
|
|
1ff3ec5431 | ||
|
|
c8277624d2 | ||
|
|
a7ab35a6b0 | ||
|
|
89d1498956 | ||
|
|
cc585c9b43 | ||
|
|
43a500c150 | ||
|
|
b107523d65 | ||
|
|
b2e01188ee | ||
|
|
bfd3387fa1 | ||
|
|
433161b08d | ||
|
|
b3ab900e47 | ||
|
|
15d400c758 | ||
|
|
2023e55bfd | ||
|
|
d0352b86e0 | ||
|
|
ecdc6c05b0 | ||
|
|
c4e3566ff0 | ||
|
|
109d103cc8 | ||
|
|
115a6378d3 | ||
|
|
264f9a933c | ||
|
|
cc42110d49 | ||
|
|
5e9900378e | ||
|
|
bacd11f4f3 | ||
|
|
dd7aa01b3f | ||
|
|
3cfed238fb | ||
|
|
8725ed9d3c | ||
|
|
30cfd883f5 | ||
|
|
03e0b2af51 | ||
|
|
da155fa5b1 | ||
|
|
fe2adb28e2 | ||
|
|
95a9d44fcf | ||
|
|
4a712b1f26 | ||
|
|
ee8ed127f6 | ||
|
|
c0ba23af3c | ||
|
|
c5197975c5 | ||
|
|
e66751dc06 | ||
|
|
8a219ab58d | ||
|
|
f80f240d4e | ||
|
|
92de40dd23 | ||
|
|
513b29f57d | ||
|
|
a8f4efa929 | ||
|
|
767e414c94 | ||
|
|
7e1f63348a | ||
|
|
0e5c09626f | ||
|
|
0dabe89bcd | ||
|
|
c3e48d20f3 | ||
|
|
1352f884cb | ||
|
|
f1700c9573 | ||
|
|
256eb472c2 | ||
|
|
5d0ebac464 | ||
|
|
b2956a901a | ||
|
|
bc592a97f2 | ||
|
|
414ad1df9c | ||
|
|
868e60db22 | ||
|
|
8d16c31e75 | ||
|
|
83b4a5b222 | ||
|
|
c6483f2de2 | ||
|
|
2884c6a8dd | ||
|
|
8e01c18484 | ||
|
|
f39b204e45 | ||
|
|
121708ca55 | ||
|
|
49c5ae3730 | ||
|
|
6b10ffd71a | ||
|
|
bcdbe6e6e8 | ||
|
|
1df9fbdf21 | ||
|
|
15091f2a15 | ||
|
|
34b719178e | ||
|
|
790ef36b1b | ||
|
|
1cbe0f10c4 | ||
|
|
0b806bfee1 | ||
|
|
bd737e97f1 | ||
|
|
1b8d21a3e0 | ||
|
|
4e0552e978 | ||
|
|
de2280dd2c | ||
|
|
04b4f05642 | ||
|
|
ef05232e4e | ||
|
|
21dda592b4 | ||
|
|
4e42a97b4b | ||
|
|
3a029bd242 | ||
|
|
055bbab36c | ||
|
|
576d37633c | ||
|
|
cbccc231f2 | ||
|
|
2928301a9f | ||
|
|
47e105228a | ||
|
|
3228deec4c | ||
|
|
62a4393e03 | ||
|
|
9fd47f4825 | ||
|
|
1823919e28 | ||
|
|
1037d39168 | ||
|
|
7da0974ef0 | ||
|
|
aa7a307cc1 | ||
|
|
bf44f7ef13 | ||
|
|
1b4afe7ba9 | ||
|
|
832d371b2e | ||
|
|
15984eabdf | ||
|
|
1f72ff3ed7 | ||
|
|
9456a0045a | ||
|
|
0035a41fe9 | ||
|
|
8c3fe2ff52 | ||
|
|
7678d7b809 | ||
|
|
9be424fa77 | ||
|
|
e65dfe16ac | ||
|
|
84c555e3bc | ||
|
|
c4d7018531 | ||
|
|
b2c797fb46 | ||
|
|
b888f71d2a | ||
|
|
6ffdbb7b71 | ||
|
|
7cb40f1556 | ||
|
|
975999df96 | ||
|
|
a6f1943e05 | ||
|
|
91ff898ac7 | ||
|
|
5c705b0f22 | ||
|
|
77a07ffbb1 | ||
|
|
559ec3ecb3 | ||
|
|
17bdd54f63 | ||
|
|
cfdf6ac27e | ||
|
|
4fbf9aa1f8 | ||
|
|
d66463c7c6 | ||
|
|
e2d5d4c30b | ||
|
|
5db90188b2 | ||
|
|
cfe6332005 | ||
|
|
d1a706860f | ||
|
|
29ab502d2e | ||
|
|
629e65edba | ||
|
|
f4c2c5b7f3 | ||
|
|
2b334e973a | ||
|
|
3212d3ce04 | ||
|
|
094acf86c3 | ||
|
|
51e91efb12 | ||
|
|
fe48a9ece2 | ||
|
|
97152ffcdf | ||
|
|
25f8649ee5 | ||
|
|
359b9d04be | ||
|
|
359392d77d | ||
|
|
755b56a8db | ||
|
|
9a99223d19 | ||
|
|
b77ae93290 | ||
|
|
688b6b3f82 | ||
|
|
ff8d2e7010 | ||
|
|
a29672d758 | ||
|
|
7b2c1c1f32 | ||
|
|
7f4a51d1cd | ||
|
|
7486bf4c06 | ||
|
|
97e16b9f73 | ||
|
|
16b3352e65 | ||
|
|
4936127655 | ||
|
|
aa7453855e | ||
|
|
c789c71768 | ||
|
|
37de5b79a8 | ||
|
|
11b24a1821 | ||
|
|
5963b5ab1e | ||
|
|
b4f738cd98 | ||
|
|
d44107d64d | ||
|
|
ad2006c853 | ||
|
|
296c3bbcc8 | ||
|
|
c349293e24 | ||
|
|
c624bb69b6 | ||
|
|
db58e8f214 | ||
|
|
4379f5cd12 | ||
|
|
bc78c61009 | ||
|
|
51b2fc7dc5 | ||
|
|
2bd2dbe624 | ||
|
|
4a1b1e19e8 | ||
|
|
2456f07128 | ||
|
|
e517db3002 | ||
|
|
c7b8cc72fc | ||
|
|
06e832848b | ||
|
|
7848bef09b | ||
|
|
943a7fcff3 | ||
|
|
c67194606f | ||
|
|
2c823b9eb5 | ||
|
|
238abc20b5 | ||
|
|
657e1b9c36 | ||
|
|
7164bca3c8 | ||
|
|
7b7e0e1f8d | ||
|
|
4058a844b7 | ||
|
|
2f8b2e4069 | ||
|
|
21323fd396 | ||
|
|
35efc6a1bc | ||
|
|
8bbef30a1d | ||
|
|
70c61dcbf5 | ||
|
|
6375850da6 | ||
|
|
8a18e2f167 | ||
|
|
61dd972ee5 | ||
|
|
30efd8c45b | ||
|
|
6b31a3e2d6 | ||
|
|
0e868f0be0 | ||
|
|
67847d90da | ||
|
|
730804409a | ||
|
|
567347e66a | ||
|
|
b3f033e30b | ||
|
|
c44dbc82c8 | ||
|
|
00cffd24fe | ||
|
|
51469e1198 | ||
|
|
78f75d4cc1 | ||
|
|
9fbc4ad851 | ||
|
|
bcc62ed222 | ||
|
|
9cc8769d45 | ||
|
|
9ed3e2f897 | ||
|
|
9bf399070a | ||
|
|
dd72723198 | ||
|
|
3f4567fa5f | ||
|
|
065d6398e6 | ||
|
|
68e3e54ae0 | ||
|
|
cae5622d7c | ||
|
|
5e8230291e | ||
|
|
e2c9115d63 | ||
|
|
cae67ad3a9 | ||
|
|
1f7939c90d | ||
|
|
b6b2d873a0 | ||
|
|
f1393ee02e | ||
|
|
316593e32c | ||
|
|
78af4bfb59 | ||
|
|
b0c77d42c0 | ||
|
|
2ec8ddbebe | ||
|
|
bfc0076429 | ||
|
|
1b1a1e4449 | ||
|
|
5fa6ad31a8 | ||
|
|
21e59d9a68 | ||
|
|
5739d8a5ae | ||
|
|
1f7ad8e535 | ||
|
|
ced0112561 | ||
|
|
e0f11440d9 | ||
|
|
75d92cdbbe | ||
|
|
2c6ecb7d42 | ||
|
|
e8391c2463 | ||
|
|
c0d631b3c3 | ||
|
|
89806139a8 | ||
|
|
210b0b3be2 | ||
|
|
49d4421635 | ||
|
|
bf3cae3da7 | ||
|
|
e93730a5dc | ||
|
|
7e544749d5 | ||
|
|
1e23d2f510 | ||
|
|
5bcda74066 | ||
|
|
b599b4d6cc | ||
|
|
3f3610102f | ||
|
|
e4e6ebf91c | ||
|
|
589d011d4c | ||
|
|
325d40c1bf | ||
|
|
f2dc402869 | ||
|
|
c19a0be8f8 | ||
|
|
445fea5237 | ||
|
|
d1b38b926b | ||
|
|
fcd128d8d9 | ||
|
|
0025f812b8 | ||
|
|
d7c7e05ec1 | ||
|
|
c932bafc12 | ||
|
|
a0376b344a | ||
|
|
1cbc117878 | ||
|
|
ea8c1abec9 | ||
|
|
a410eb424f | ||
|
|
8624de5705 | ||
|
|
358d5820f6 | ||
|
|
7c06aecf31 | ||
|
|
5d7544e58c | ||
|
|
17423efb96 | ||
|
|
a28793edd2 | ||
|
|
50760471d5 | ||
|
|
a6df8cb62d | ||
|
|
873711939d | ||
|
|
2bd13eb3ba | ||
|
|
d423f17342 | ||
|
|
a02c99452d | ||
|
|
fd854912fc | ||
|
|
5999991209 | ||
|
|
50ff46465f | ||
|
|
c1040907d9 | ||
|
|
2cae19a75d | ||
|
|
c302473798 | ||
|
|
4107636f00 | ||
|
|
ee07ea9426 | ||
|
|
c806f20f2c | ||
|
|
98b5121a72 | ||
|
|
fb842f72c8 | ||
|
|
982a043342 | ||
|
|
09e1baf19f | ||
|
|
fd2dbcf120 | ||
|
|
9ff0d76c3a | ||
|
|
48f0186151 | ||
|
|
45a945e983 | ||
|
|
971c07a7c2 | ||
|
|
eeb85ae3db | ||
|
|
fe13376a56 | ||
|
|
0d55c1fc37 | ||
|
|
d5ebe761d5 | ||
|
|
9774b2a215 | ||
|
|
ba442511b7 | ||
|
|
e831a01a28 | ||
|
|
884ab72ac6 | ||
|
|
6edb483a5b | ||
|
|
d1c788c639 | ||
|
|
b17680d897 | ||
|
|
707d9d2332 | ||
|
|
9c67d68d05 | ||
|
|
a37ae88d3b | ||
|
|
b8489b0379 | ||
|
|
87e2bdd39d | ||
|
|
52b95ef5f9 | ||
|
|
81375f501e | ||
|
|
d37d977c79 | ||
|
|
654967b6f0 | ||
|
|
8ad48a04a0 | ||
|
|
a4c04bb279 | ||
|
|
d4e0797586 | ||
|
|
8c6009cd63 | ||
|
|
09be5b3f97 | ||
|
|
dee5e3b365 | ||
|
|
8224801024 | ||
|
|
f5377dc276 | ||
|
|
94f6b49487 | ||
|
|
acaacf063e | ||
|
|
881705fa9c | ||
|
|
5440674498 | ||
|
|
c1770c428d | ||
|
|
db3cb118e1 | ||
|
|
085cce211f | ||
|
|
671318a810 | ||
|
|
8accae1105 | ||
|
|
cbf36eb999 | ||
|
|
26e25268f7 | ||
|
|
2d93e98dc7 | ||
|
|
7764ff22dc | ||
|
|
a5e0b96af5 | ||
|
|
f30d58446f | ||
|
|
a4041d3bcd | ||
|
|
e72b7f3d29 | ||
|
|
2bfe5cdfc9 | ||
|
|
92603217e7 | ||
|
|
b6a9c1b879 | ||
|
|
bf839b8d52 | ||
|
|
a13c98e6c9 | ||
|
|
65806d2491 | ||
|
|
0450701548 | ||
|
|
9f1b329a46 | ||
|
|
22a71dd2d2 | ||
|
|
595690aea9 | ||
|
|
deae7d100c | ||
|
|
0397fcb9be | ||
|
|
6df27566d2 | ||
|
|
0b919e3815 | ||
|
|
1d5f07a658 | ||
|
|
571a09364c | ||
|
|
92f94d7a82 | ||
|
|
87cb7dbc39 | ||
|
|
2c3dd3960d | ||
|
|
d1a16a3202 | ||
|
|
3bfb665a76 | ||
|
|
1ebf6eb8f9 | ||
|
|
1af6ce2ce6 | ||
|
|
f0040620c8 | ||
|
|
41e91371d5 | ||
|
|
c2a00f88d9 | ||
|
|
4a0ac11883 | ||
|
|
a50082f738 | ||
|
|
19dc448a08 | ||
|
|
ad06dc14b0 | ||
|
|
c1aa2775a5 | ||
|
|
ec58a7e369 | ||
|
|
973ffb0fe5 | ||
|
|
5e4c993525 | ||
|
|
3ffc427cd6 | ||
|
|
24b065ba03 | ||
|
|
47bd910727 | ||
|
|
a02f654b14 | ||
|
|
d1c6afcebd | ||
|
|
75d64c3c31 | ||
|
|
97b16e879f | ||
|
|
00db01d163 | ||
|
|
56610538ce | ||
|
|
d555f5d3e4 | ||
|
|
89d5b8fb69 | ||
|
|
69e36b0f71 | ||
|
|
9b5ef3ad33 | ||
|
|
ec5dd81018 |
2
fastlane/metadata/android/en-US/changelogs/102.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/102.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
- Updated look for link previews and the News tab
|
||||
- Bug fixes and improvements
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 213 KiB After Width: | Height: | Size: 46 KiB |
@@ -1,4 +1,4 @@
|
||||
Mastodon est le plus grand réseau social décentralisé sur Internet. Au lieu d’un site Web unique, c’est un réseau de millions d’utilisateurs dans des communautés indépendantes qui peuvent tous interagir les uns avec les autres, de manière transparente. Peu importe ce que vous êtes, vous pouvez rencontrer des gens passionnés qui publient à ce sujet sur Mastodon !
|
||||
Mastodon est le plus grand réseau social décentralisé sur Internet. Au lieu d’un site Web unique, c’est un réseau de millions d’utilisateurs dans des communautés indépendantes qui peuvent tous interagir les uns avec les autres, de manière transparente. Peu importe ce que vous aimez, vous pouvez rencontrer des gens passionnés qui en parlent sur Mastodon !
|
||||
|
||||
Rejoignez une communauté et créez votre profil. Trouvez et suivez des personnes fascinantes et lisez leurs messages chronologiquement et sans publicité. Exprimez-vous avec des émojis personnalisés, des images, des GIFs, des vidéos et de l’audio dans des messages de 500 caractères. Répondez aux sujets de discussions et aux reblogues de n’importe qui pour partager des choses géniales. Trouvez de nouveaux comptes à suivre et des hashtags tendance pour étendre votre réseau.
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
A Mastodon a legnagyobb decentralizált közösségi hálózat az interneten. Egyetlen weboldal helyett, ez több millió felhasználóból álló, független közösségek hálózata, amelyek egymással kapcsolatba tudnak lépni, zökkenőmentesen. Nem számít, mi az érdeklődésed, a Mastodonon találkozhatsz róla posztoló lelkes emberekkel!
|
||||
|
||||
Csatlakozz egy közösséghez és készítsd el a profilodat. Találj meg és kövess be fantasztikus embereket, olvasd el a bejegyzéseiket reklámmentesen, időrendben. Fejezd ki magad egyedi hangulatjelekkel, képekkel, GIFekkel, videókkal és hanggal, 500 karakter hosszúságú bejegyzésekben. Válaszolj szálakban, told meg bárki bejegyzését, hogy megoszthass szuper dolgokat. Fedezz fel új fiókokat amiket követhetsz és felkapott hashtageket, hogy bővíthesd a kapcsolataidat.
|
||||
Csatlakozz egy közösséghez és készítsd el a profilodat. Találj meg és kövess fantasztikus embereket, olvasd el a bejegyzéseiket reklámmentesen, időrendben. Fejezd ki magad egyedi hangulatjelekkel, képekkel, GIFekkel, videókkal és hanggal, 500 karakter hosszúságú bejegyzésekben. Válaszolj szálakban, told meg bárki bejegyzését, hogy megoszthass szuper dolgokat. Fedezz fel új fiókokat amiket követhetsz és felkapott hashtageket, hogy bővíthesd a kapcsolataidat.
|
||||
|
||||
A Mastodon az adatvédelemre és a biztonságra összpontosítva épült. Döntsd el, hogy a bejegyzéseidet csak a követőiddel, csak azokkal akiket megemlítesz, vagy az egész világgal osztod meg. A tartalomfigyelmeztetések elrejthetővé teszik az érzékeny vagy ingerlő tartalmakat addig, amíg nem vagy kész azok megtekintésére. Minden közösségnek saját irényelvei és moderátorai vannak arra, hogy biztonságban tudják a tagjaikat. Erőteljes letiltási és bejelentési eszközök segítik a visszaélések megelőzését.
|
||||
A Mastodon az adatvédelemre és a biztonságra összpontosítva épült. Döntsd el, hogy a bejegyzéseidet csak a követőiddel, csak azokkal akiket megemlítesz, vagy az egész világgal osztod meg. A tartalmi figyelmeztetések elrejthetővé teszik az érzékeny vagy ingerlő tartalmakat addig, amíg nem vagy kész azok megtekintésére. Minden közösségnek saját irányelvei és moderátorai vannak arra, hogy biztonságban tudják a tagjaikat. Robusztus letiltási és jelentési eszközök segítik a visszaélések megelőzését.
|
||||
|
||||
További funkciók:
|
||||
|
||||
• Sötét mód: Olvasd a bejegyzéseket világos, sötét vagy teljesen fekete módban
|
||||
• Sötét mód: olvasd a bejegyzéseket világos, sötét vagy valódi fekete módban
|
||||
• Szavazás: Kérd ki a követőid véleményét és összesítsd a szavazataikat
|
||||
• Felfedezés: A felkapott hashtagek és fiókok egyetlen kattintásra vannak
|
||||
• Értesítések: Értesülj az új követőidről, válaszokról, megtolásokról
|
||||
• Megosztás: Írj bejegyzést Mastodonra bármely app megosztási funkciójával
|
||||
• Cukiság: A kabalánk egy cuki elefánt, mely fel fog bukkanni időről időre
|
||||
|
||||
A Mastodon egy bejegyzett non-profit szervezet, a fejlesztés közvetlenül a te adományaidból történik. Nincs hirdetés, se monetizáció, se kockázati tőke, és ez így is fog maradni.
|
||||
A Mastodon egy bejegyzett nonprofit szervezet, a fejlesztés közvetlenül a felhasználók adományaiból történik. Nincs hirdetés, se monetizáció, se kockázati tőke, és ez így is fog maradni.
|
||||
|
||||
16
fastlane/metadata/android/ia/full_description.txt
Normal file
16
fastlane/metadata/android/ia/full_description.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
Mastodon es le rete social decentralisate le plus grande sur internet. In vice que un sol sito web, illo es un rete de milliones de usatores in communitates independente que pote interager le unes con le alteres, transparentemente. Non importa lo que te interessa, tu pote incontrar personas passionate que parla de illo sur Mastodon!
|
||||
|
||||
Inscribe te a un communitate e crea tu profilo. Trova e seque personas fascinante e lege lor messages in un chronologia libere de publicitate. Exprime te con emojis personalisate, imagines, GIFs, videos e audio in messages de 500 characteres. Responde a discussiones e impulsa messages de quicunque pro compartir cosas genial. Trova nove contos a sequer e hashtags de tendentia pro expander tu rete.
|
||||
|
||||
Mastodon es producite con attention a confidentialitate e securitate. Decide si tu messages es compartite con tu sequitores, con solo le gente que tu mentiona, o con tote le mundo. Le advertimentos de contento te permitte celar messages que contine material sensibile o provocatori usque tu es preste a interager con illos. Cata communitate ha su proprie directivas e moderatores pro mantener su membros secur, e robuste utensiles de blocage e de reportage pro adjutar a impedir abusos.
|
||||
|
||||
Altere functiones:
|
||||
|
||||
• Modo obscur: lege messages in modo clar, obscur, o vermente nigre
|
||||
• Sondages: Demanda al sequitores lor opinion e conta le votos
|
||||
• Explorar: le hashtags e contos popular es a portata de mano
|
||||
• Notificationes: sia notificate de nove sequitores, responsas e impulsos
|
||||
• Compartir: publica directemente sur Mastodon desde le function Compartir de qualcunque app
|
||||
• Pachydermo: nostre mascotte es un adorabile elephante, e tu lo videra apparer de tempore in tempore
|
||||
|
||||
Mastodon es un organisation registrate sin scopo lucrative e le disveloppamento es sustenite directemente per tu donationes. Il non ha publicitate, ni monetisation, ni capital de risco, e nos intende mantener lo assi.
|
||||
1
fastlane/metadata/android/ia/short_description.txt
Normal file
1
fastlane/metadata/android/ia/short_description.txt
Normal file
@@ -0,0 +1 @@
|
||||
Rete social decentralisate
|
||||
1
fastlane/metadata/android/ia/title.txt
Normal file
1
fastlane/metadata/android/ia/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Mastodon
|
||||
@@ -1,16 +1,16 @@
|
||||
Mastodon – didžiausias decentralizuotas socialinis tinklas internete. Vietoj vienos svetainės tai yra milijonų naudotojų, priklausančių nepriklausomoms bendruomenėms, kurios gali sklandžiai bendrauti tarpusavyje, tinklas. Nesvarbu, kuo domiesi, Mastodon gali sutikti aistringų žmonių, skelbiančių apie tai!
|
||||
„Mastodon“ – tai didžiausias decentralizuotas socialinis tinklas internete. Vietoj vienos svetainės tai yra milijonų naudotojų, priklausančių nepriklausomoms bendruomenėms, kurios gali sklandžiai bendrauti tarpusavyje, tinklas. Nesvarbu, kuo domiesi, „Mastodon“ platformoje gali sutikti aistringų žmonių, skelbiančių apie tai!
|
||||
|
||||
Prisijunk prie bendruomenės ir susikurk savo profilį. Rask ir sek žavius žmones bei skaityk jų įrašus chronologinėje laiko skalėje be reklamų. Išreikšk save su pasirinktais jaustukais, vaizdais, GIF, vaizdo ir garso įrašais 500 simbolių įrašuose. Atsakyk į gijas ir perrašyk bet kurio asmens įrašus, kad galėtum dalytis puikiais dalykais. Ieškok naujų paskyrų sekti ir tendencingų saitažodžių, kad praplėstum savo tinklą.
|
||||
Prisijunk prie bendruomenės ir susikurk savo profilį. Rask ir sek žavius žmones bei skaityk jų įrašus chronologinėje laiko skalėje be reklamų. Išreikšk save su pasirinktais jaustukais, vaizdais, GIF, vaizdo ir garso įrašais 500 simbolių įrašuose. Atsakyk į gijas ir perdalyk bet kurio asmens įrašus, kad galėtum bendrinti puikiais dalykais. Ieškok naujų paskyrų sekti ir tendencingų saitažodžių, kad praplėstum savo tinklą.
|
||||
|
||||
Mastodon sukurtas daugiausia dėmesio skiriant privatumui ir saugumui. Nuspręsk, ar tavo įrašai bus bendrinami tavo sekėjams, tik tavo paminėtiems žmonėms, ar visam pasauliui. Turinio įspėjimai leidžia paslėpti įrašus, kuriuose yra jautrios ar dirginančios medžiagos, kol būsi pasiruošęs (-usi) su jais bendrauti. Kiekviena bendruomenė turi savo gaires ir prižiūrėtojus, kad jos nariai būtų saugūs, o patikimi blokavimo ir pranešimo įrankiai padeda užkirsti kelią piktnaudžiavimui.
|
||||
„Mastodon“ sukurtas daugiausia dėmesio skiriant privatumui ir saugumui. Nuspręsk, ar tavo įrašai bus bendrinami tavo sekėjams, tik tavo paminėtiems žmonėms, ar visam pasauliui. Turinio įspėjimai leidžia paslėpti įrašus, kuriuose yra jautrios ar dirginančios medžiagos, kol būsi pasiruošęs (-usi) su jais bendrauti. Kiekviena bendruomenė turi savo gaires ir prižiūrėtojus, kad jos nariai būtų saugūs, o patikimi blokavimo ir pranešimo įrankiai padeda užkirsti kelią piktnaudžiavimui.
|
||||
|
||||
Daugiau funkcijų:
|
||||
|
||||
• Tamsusis režimas: skaityk įrašus šviesiu, tamsiu arba tikru juodu režimu.
|
||||
• Apklausos: paklausk sekėjų nuomonės ir suskaičiuok balsus.
|
||||
• Naršyti: tendencingos saitažodžiai ir paskyros – vos nuo vieno prisilietimo.
|
||||
• Pranešimai: gauk pranešimus apie naujus sekėjus, atsakymus ir perrašymus.
|
||||
• Bendrinti: skelbk tiesiogiai į Mastodon iš bet kurio bendrinimo lapo bet kurioje programėlėje.
|
||||
• Pranešimai: gauk pranešimus apie naujus sekėjus, atsakymus ir pasidalinimus.
|
||||
• Bendrinti: skelbk tiesiogiai į „Mastodon“ iš bet kurio bendrinimo lapo bet kurioje programėlėje
|
||||
• Mielumas: mūsų talismanas – žavus drambliukas, kurį retkarčiais pamatysi.
|
||||
|
||||
Mastodon – registruota ne pelno siekianti organizacija, kurios plėtra yra tiesiogiai paremta aukomis. Nėra jokios reklamos, jokių monetizacijos ir rizikos kapitalo, ir mes planuojame, kad taip ir liks.
|
||||
„Mastodon“ registruota ne pelno siekianti organizacija, kurios plėtra yra tiesiogiai paremta aukomis. Nėra jokių reklamų, jokių monetizacijos ir rizikos kapitalo, ir mes planuojame, kad taip ir liks.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Mastodon 是互联网上最大的去中心化社交网络。 它不是一个网站,而是由独立社区节点及其数以百万计的用户组成的网络,所有这些用户都能够无缝地相互交流。 无论你关注什么话题,你都能在 Mastodon 上找到兴趣相投的人进行交流。
|
||||
Mastodon 是互联网上最大的去中心化社交网络。 它不是一个单一的网站,而是由一个个独立社区中的数百万用户组成的网络,所有这些用户都可以无缝交流。 无论你关注什么话题,你都能在 Mastodon 上找到兴趣相投的人进行交流。
|
||||
|
||||
加入一个社区节点并创建你的账户。 查找、关注有趣的同好,无广告、无时间线干扰地阅读他们的帖子。 借助自定义 emoji、图像、GIF、视频和音频,在最多 500 字的帖文中表达自我。 通过回复或转发其他人的帖文来分享美好的事物。 通过准寻新账户并关注热门话题标签来扩展你的社交网络。
|
||||
加入一个社区并创建你的账户。 查找、关注有趣的同好,无广告、无时间线干扰地阅读他们的帖子。 借助自定义 emoji、图像、GIF、视频和音频,在最多 500 字的帖文中表达自我。 通过回复或转发其他人的帖文来分享美好的事物。 通过准寻新账户并关注热门话题标签来扩展你的社交网络。
|
||||
|
||||
Mastodon 以隐私和安全为首要目标。 你可以自主决定帖文的分享分享对象,可以是你的关注者、你提到的人或是整个世界。 在你做好充足的互动准备之前,内容警告可以隐藏包含敏感或刺激内容的帖文。 每个社区都有自己的规则和管理员来保证其成员安全,同时还有强力的屏蔽和举报工具来避免滥用。
|
||||
|
||||
@@ -10,7 +10,7 @@ Mastodon 以隐私和安全为首要目标。 你可以自主决定帖文的分
|
||||
• 投票:询问关注者的意见并统计他们的投票
|
||||
• 探索:热门的话题标签及账号只有一触之遥
|
||||
• 通知:获取关注、回复和转发相关的通知提醒
|
||||
• 分享:从其他应用中的分享菜单中直接发布到 Mastodon
|
||||
• 分享:从任意应用的分享菜单直接发布到 Mastodon
|
||||
• 吉祥物:你会不时地看到我们可爱的长毛象
|
||||
|
||||
Mastodon 是一个直接由用户捐赠支持、已注册非营利开发项目。 它没有广告、没有商业化,也没有风险资本,并且我们也计划保持这种方式。
|
||||
|
||||
@@ -13,8 +13,8 @@ android {
|
||||
applicationId "org.joinmastodon.android"
|
||||
minSdk 23
|
||||
targetSdk 33
|
||||
versionCode 93
|
||||
versionName "2.5.0"
|
||||
versionCode 107
|
||||
versionName "2.5.5"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ dependencies {
|
||||
implementation 'me.grishka.litex:viewpager:1.0.0'
|
||||
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
||||
implementation 'me.grishka.litex:palette:1.0.0'
|
||||
implementation 'me.grishka.appkit:appkit:1.2.17'
|
||||
implementation 'me.grishka.appkit:appkit:1.3.0'
|
||||
implementation 'com.google.code.gson:gson:2.8.9'
|
||||
implementation 'org.jsoup:jsoup:1.14.3'
|
||||
implementation 'com.squareup:otto:1.3.8'
|
||||
|
||||
@@ -31,9 +31,11 @@
|
||||
android:allowBackup="true"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:theme="@style/Theme.Mastodon.AutoLightDark"
|
||||
android:largeHeap="true">
|
||||
android:largeHeap="true"
|
||||
android:enableOnBackInvokedCallback="true">
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.mlkit.vision.DEPENDENCIES"
|
||||
@@ -79,6 +81,7 @@
|
||||
</activity>
|
||||
|
||||
<service android:name=".AudioPlayerService" android:foregroundServiceType="mediaPlayback"/>
|
||||
<service android:name=".NotificationActionHandlerService" android:exported="false"/>
|
||||
|
||||
<receiver android:name=".PushNotificationReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND">
|
||||
<intent-filter>
|
||||
|
||||
@@ -10,7 +10,7 @@ public class GlobalUserPreferences{
|
||||
public static boolean playGifs;
|
||||
public static boolean useCustomTabs;
|
||||
public static boolean altTextReminders, confirmUnfollow, confirmBoost, confirmDeletePost;
|
||||
public static ThemePreference theme;
|
||||
public static ThemePreference theme=ThemePreference.AUTO;
|
||||
|
||||
private static SharedPreferences getPrefs(){
|
||||
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
||||
|
||||
@@ -61,6 +61,7 @@ public class MainActivity extends FragmentStackActivity{
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent){
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
if(intent.getBooleanExtra("fromNotification", false)){
|
||||
String accountID=intent.getStringExtra("accountID");
|
||||
AccountSession accountSession;
|
||||
@@ -85,6 +86,8 @@ public class MainActivity extends FragmentStackActivity{
|
||||
showCompose();
|
||||
}else if(Intent.ACTION_VIEW.equals(intent.getAction())){
|
||||
handleURL(intent.getData(), null);
|
||||
}else if(intent.getBooleanExtra("explore", false)){
|
||||
restartHomeFragment();
|
||||
}/*else if(intent.hasExtra(PackageInstaller.EXTRA_STATUS) && GithubSelfUpdater.needSelfUpdating()){
|
||||
GithubSelfUpdater.getInstance().handleIntentFromInstaller(intent, this);
|
||||
}*/
|
||||
@@ -211,6 +214,8 @@ public class MainActivity extends FragmentStackActivity{
|
||||
}
|
||||
}else if(intent.getBooleanExtra("compose", false)){
|
||||
showCompose();
|
||||
}else if(intent.getBooleanExtra("explore", false) && fragment instanceof HomeFragment hf){
|
||||
getWindow().getDecorView().post(()->hf.setCurrentTab(R.id.tab_search));
|
||||
}else if(Intent.ACTION_VIEW.equals(intent.getAction())){
|
||||
handleURL(intent.getData(), null);
|
||||
}else{
|
||||
@@ -218,4 +223,10 @@ public class MainActivity extends FragmentStackActivity{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Fragment getTopmostFragment(){
|
||||
if(fragmentContainers.isEmpty())
|
||||
return null;
|
||||
return getFragmentManager().findFragmentById(fragmentContainers.get(fragmentContainers.size()-1).getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
package org.joinmastodon.android;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.RemoteInput;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
|
||||
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
|
||||
public class NotificationActionHandlerService extends Service{
|
||||
private static final String TAG="NotificationActionHandl";
|
||||
private int runningRequestCount=0;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent){
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId){
|
||||
String action=intent.getStringExtra("action");
|
||||
String account=intent.getStringExtra("account");
|
||||
String postID=intent.getStringExtra("post");
|
||||
String notificationTag=intent.getStringExtra("notificationTag");
|
||||
if(action==null || account==null || postID==null || notificationTag==null){
|
||||
maybeStopSelf();
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
NotificationManager nm=getSystemService(NotificationManager.class);
|
||||
StatusBarNotification notification=findNotification(notificationTag);
|
||||
if("reply".equals(action)){
|
||||
Bundle remoteInputResults=RemoteInput.getResultsFromIntent(intent);
|
||||
if(remoteInputResults==null){
|
||||
maybeStopSelf();
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
CharSequence replyText=remoteInputResults.getCharSequence("replyText");
|
||||
if(replyText==null){
|
||||
maybeStopSelf();
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
CreateStatus.Request req=new CreateStatus.Request();
|
||||
req.inReplyToId=postID;
|
||||
req.status=intent.getStringExtra("replyPrefix")+replyText;
|
||||
req.visibility=StatusPrivacy.valueOf(intent.getStringExtra("visibility"));
|
||||
runningRequestCount++;
|
||||
new CreateStatus(req, UUID.randomUUID().toString())
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
E.post(new StatusCreatedEvent(result, account));
|
||||
if(notification!=null){
|
||||
Notification n=notification.getNotification();
|
||||
nm.notify(notificationTag, PushNotificationReceiver.NOTIFICATION_ID, n);
|
||||
}
|
||||
runningRequestCount--;
|
||||
maybeStopSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(NotificationActionHandlerService.this);
|
||||
if(notification!=null){
|
||||
Notification n=notification.getNotification();
|
||||
nm.notify(notificationTag, PushNotificationReceiver.NOTIFICATION_ID, n);
|
||||
}
|
||||
runningRequestCount--;
|
||||
maybeStopSelf();
|
||||
}
|
||||
})
|
||||
.exec(account);
|
||||
}else if("favorite".equals(action)){
|
||||
PendingIntent prevActionIntent;
|
||||
if(notification!=null){
|
||||
Notification n=notification.getNotification();
|
||||
prevActionIntent=n.actions[1].actionIntent;
|
||||
n.actions[1].actionIntent=null;
|
||||
n.actions[1].title=getString(R.string.button_favorited);
|
||||
nm.notify(notificationTag, PushNotificationReceiver.NOTIFICATION_ID, n);
|
||||
}else{
|
||||
prevActionIntent=null;
|
||||
}
|
||||
runningRequestCount++;
|
||||
new SetStatusFavorited(postID, true)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
E.post(new StatusCountersUpdatedEvent(result));
|
||||
runningRequestCount--;
|
||||
maybeStopSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
if(notification!=null){
|
||||
Notification n=notification.getNotification();
|
||||
n.actions[1].actionIntent=prevActionIntent;
|
||||
n.actions[1].title=getString(R.string.button_favorite);
|
||||
nm.notify(notificationTag, PushNotificationReceiver.NOTIFICATION_ID, n);
|
||||
}
|
||||
error.showToast(NotificationActionHandlerService.this);
|
||||
runningRequestCount--;
|
||||
maybeStopSelf();
|
||||
}
|
||||
})
|
||||
.exec(account);
|
||||
}else if("boost".equals(action)){
|
||||
PendingIntent prevActionIntent;
|
||||
if(notification!=null){
|
||||
Notification n=notification.getNotification();
|
||||
prevActionIntent=n.actions[2].actionIntent;
|
||||
n.actions[2].actionIntent=null;
|
||||
n.actions[2].title=getString(R.string.button_reblogged);
|
||||
nm.notify(notificationTag, PushNotificationReceiver.NOTIFICATION_ID, n);
|
||||
}else{
|
||||
prevActionIntent=null;
|
||||
}
|
||||
runningRequestCount++;
|
||||
new SetStatusReblogged(postID, true)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
E.post(new StatusCountersUpdatedEvent(result));
|
||||
runningRequestCount--;
|
||||
maybeStopSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
if(notification!=null){
|
||||
Notification n=notification.getNotification();
|
||||
n.actions[2].actionIntent=prevActionIntent;
|
||||
n.actions[2].title=getString(R.string.button_reblog);
|
||||
nm.notify(notificationTag, PushNotificationReceiver.NOTIFICATION_ID, n);
|
||||
}
|
||||
error.showToast(NotificationActionHandlerService.this);
|
||||
runningRequestCount--;
|
||||
maybeStopSelf();
|
||||
}
|
||||
})
|
||||
.exec(account);
|
||||
}
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
private void maybeStopSelf(){
|
||||
if(runningRequestCount==0)
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
private StatusBarNotification findNotification(String tag){
|
||||
for(StatusBarNotification sbn:getSystemService(NotificationManager.class).getActiveNotifications()){
|
||||
if(tag.equals(sbn.getTag())){
|
||||
return sbn;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,15 @@ import android.app.NotificationChannel;
|
||||
import android.app.NotificationChannelGroup;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.RemoteInput;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -21,10 +22,13 @@ import org.joinmastodon.android.api.requests.notifications.GetNotificationByID;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Mention;
|
||||
import org.joinmastodon.android.model.PushNotification;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -75,7 +79,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||
}
|
||||
String accountID=account.getID();
|
||||
PushNotification pn=AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().decryptNotification(k, p, s);
|
||||
new GetNotificationByID(pn.notificationId+"")
|
||||
new GetNotificationByID(pn.notificationId)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(org.joinmastodon.android.model.Notification result){
|
||||
@@ -144,19 +148,114 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||
.setContentText(pn.body)
|
||||
.setStyle(new Notification.BigTextStyle().bigText(pn.body))
|
||||
.setSmallIcon(R.drawable.ic_ntf_logo)
|
||||
.setContentIntent(PendingIntent.getActivity(context, accountID.hashCode() & 0xFFFF, contentIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT))
|
||||
.setContentIntent(PendingIntent.getActivity(context, (accountID+pn.notificationId).hashCode() & 0xFFFF, contentIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT))
|
||||
.setWhen(notification==null ? System.currentTimeMillis() : notification.createdAt.toEpochMilli())
|
||||
.setShowWhen(true)
|
||||
.setCategory(Notification.CATEGORY_SOCIAL)
|
||||
.setAutoCancel(true)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setLights(context.getColor(R.color.primary_700), 500, 1000)
|
||||
.setColor(context.getColor(R.color.primary_700));
|
||||
.setColor(context.getColor(R.color.primary_700))
|
||||
.setGroup(accountID);
|
||||
if(avatar!=null){
|
||||
builder.setLargeIcon(UiUtils.getBitmapFromDrawable(avatar));
|
||||
}
|
||||
if(AccountSessionManager.getInstance().getLoggedInAccounts().size()>1){
|
||||
builder.setSubText(accountName);
|
||||
}
|
||||
nm.notify(accountID, NOTIFICATION_ID, builder.build());
|
||||
String notificationTag=accountID+"_"+(notification==null ? 0 : notification.id);
|
||||
if(notification!=null && (notification.type==org.joinmastodon.android.model.Notification.Type.MENTION)){
|
||||
ArrayList<String> mentions=new ArrayList<>();
|
||||
String ownID=AccountSessionManager.getInstance().getAccount(accountID).self.id;
|
||||
if(!notification.status.account.id.equals(ownID))
|
||||
mentions.add('@'+notification.status.account.acct);
|
||||
for(Mention mention:notification.status.mentions){
|
||||
if(mention.id.equals(ownID))
|
||||
continue;
|
||||
String m='@'+mention.acct;
|
||||
if(!mentions.contains(m))
|
||||
mentions.add(m);
|
||||
}
|
||||
String replyPrefix=mentions.isEmpty() ? "" : TextUtils.join(" ", mentions)+" ";
|
||||
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
|
||||
Intent replyIntent=new Intent(context, NotificationActionHandlerService.class);
|
||||
replyIntent.putExtra("action", "reply");
|
||||
replyIntent.putExtra("account", accountID);
|
||||
replyIntent.putExtra("post", notification.status.id);
|
||||
replyIntent.putExtra("notificationTag", notificationTag);
|
||||
replyIntent.putExtra("visibility", notification.status.visibility.toString());
|
||||
replyIntent.putExtra("replyPrefix", replyPrefix);
|
||||
builder.addAction(new Notification.Action.Builder(Icon.createWithResource(context, R.drawable.ic_reply_24px),
|
||||
context.getString(R.string.button_reply), PendingIntent.getService(context, (accountID+pn.notificationId+"reply").hashCode(), replyIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE))
|
||||
.addRemoteInput(new RemoteInput.Builder("replyText").setLabel(context.getString(R.string.button_reply)).build())
|
||||
.build());
|
||||
}
|
||||
|
||||
Intent favIntent=new Intent(context, NotificationActionHandlerService.class);
|
||||
favIntent.putExtra("action", "favorite");
|
||||
favIntent.putExtra("account", accountID);
|
||||
favIntent.putExtra("post", notification.status.id);
|
||||
favIntent.putExtra("notificationTag", notificationTag);
|
||||
builder.addAction(new Notification.Action.Builder(Icon.createWithResource(context, R.drawable.ic_star_24px),
|
||||
context.getString(R.string.button_favorite), PendingIntent.getService(context, (accountID+pn.notificationId+"favorite").hashCode(), favIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE)).build());
|
||||
|
||||
PendingIntent boostActionIntent;
|
||||
if(notification.status.visibility!=StatusPrivacy.DIRECT){
|
||||
Intent boostIntent=new Intent(context, NotificationActionHandlerService.class);
|
||||
boostIntent.putExtra("action", "boost");
|
||||
boostIntent.putExtra("account", accountID);
|
||||
boostIntent.putExtra("post", notification.status.id);
|
||||
boostIntent.putExtra("notificationTag", notificationTag);
|
||||
boostActionIntent=PendingIntent.getService(context, (accountID+pn.notificationId+"boost").hashCode(), boostIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||
}else{
|
||||
boostActionIntent=null;
|
||||
}
|
||||
builder.addAction(new Notification.Action.Builder(Icon.createWithResource(context, R.drawable.ic_boost_24px),
|
||||
context.getString(R.string.button_reblog), boostActionIntent).build());
|
||||
}
|
||||
nm.notify(notificationTag, NOTIFICATION_ID, builder.build());
|
||||
|
||||
StatusBarNotification[] activeNotifications=nm.getActiveNotifications();
|
||||
ArrayList<String> summaryLines=new ArrayList<>();
|
||||
int notificationCount=0;
|
||||
for(StatusBarNotification sbn:activeNotifications){
|
||||
String tag=sbn.getTag();
|
||||
if(tag!=null && tag.startsWith(accountID+"_")){
|
||||
if((sbn.getNotification().flags & Notification.FLAG_GROUP_SUMMARY)==0){
|
||||
if(summaryLines.size()<5){
|
||||
summaryLines.add(sbn.getNotification().extras.getString("android.title"));
|
||||
}
|
||||
notificationCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(summaryLines.size()>1){
|
||||
Notification.Builder summaryBuilder;
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
|
||||
summaryBuilder=new Notification.Builder(context, accountID+"_"+pn.notificationType);
|
||||
}else{
|
||||
summaryBuilder=new Notification.Builder(context)
|
||||
.setPriority(Notification.PRIORITY_DEFAULT);
|
||||
}
|
||||
Notification.InboxStyle inboxStyle=new Notification.InboxStyle();
|
||||
for(String line:summaryLines){
|
||||
inboxStyle.addLine(line);
|
||||
}
|
||||
summaryBuilder.setContentTitle(context.getString(R.string.app_name))
|
||||
.setContentText(context.getResources().getQuantityString(R.plurals.x_new_notifications, notificationCount, notificationCount))
|
||||
.setSmallIcon(R.drawable.ic_ntf_logo)
|
||||
.setColor(context.getColor(R.color.primary_700))
|
||||
.setContentIntent(PendingIntent.getActivity(context, accountID.hashCode() & 0xFFFF, contentIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT))
|
||||
.setWhen(notification==null ? System.currentTimeMillis() : notification.createdAt.toEpochMilli())
|
||||
.setShowWhen(true)
|
||||
.setCategory(Notification.CATEGORY_SOCIAL)
|
||||
.setAutoCancel(true)
|
||||
.setGroup(accountID)
|
||||
.setGroupSummary(true)
|
||||
.setStyle(inboxStyle.setSummaryText(accountName));
|
||||
nm.notify(accountID+"_summary", NOTIFICATION_ID, summaryBuilder.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,8 +113,10 @@ public class MastodonAPIController{
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException{
|
||||
if(req.canceled)
|
||||
if(req.canceled){
|
||||
response.close();
|
||||
return;
|
||||
}
|
||||
if(BuildConfig.DEBUG)
|
||||
Log.d(TAG, logTag(session)+hreq+" received response: "+response);
|
||||
synchronized(req){
|
||||
|
||||
@@ -5,31 +5,62 @@ import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.gson.JsonIOException;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
|
||||
public class MastodonErrorResponse extends ErrorResponse{
|
||||
public final String error;
|
||||
public final int httpStatus;
|
||||
public final Throwable underlyingException;
|
||||
public final int messageResource;
|
||||
|
||||
public MastodonErrorResponse(String error, int httpStatus, Throwable exception){
|
||||
this.error=error;
|
||||
this.httpStatus=httpStatus;
|
||||
this.underlyingException=exception;
|
||||
|
||||
if(exception instanceof UnknownHostException){
|
||||
this.messageResource=R.string.could_not_reach_server;
|
||||
}else if(exception instanceof SocketTimeoutException){
|
||||
this.messageResource=R.string.connection_timed_out;
|
||||
}else if(exception instanceof JsonSyntaxException || exception instanceof JsonIOException || httpStatus>=500){
|
||||
this.messageResource=R.string.server_error;
|
||||
}else if(httpStatus==404){
|
||||
this.messageResource=R.string.not_found;
|
||||
}else{
|
||||
this.messageResource=0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindErrorView(View view){
|
||||
TextView text=view.findViewById(R.id.error_text);
|
||||
text.setText(error);
|
||||
String message;
|
||||
if(messageResource>0){
|
||||
message=view.getContext().getString(messageResource, error);
|
||||
}else{
|
||||
message=error;
|
||||
}
|
||||
text.setText(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showToast(Context context){
|
||||
if(context==null)
|
||||
return;
|
||||
Toast.makeText(context, error, Toast.LENGTH_SHORT).show();
|
||||
String message;
|
||||
if(messageResource>0){
|
||||
message=context.getString(messageResource, error);
|
||||
}else{
|
||||
message=error;
|
||||
}
|
||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,13 @@ import java.util.List;
|
||||
public class GetCatalogInstances extends MastodonAPIRequest<List<CatalogInstance>>{
|
||||
|
||||
private String lang, category;
|
||||
private boolean includeClosedSignups;
|
||||
|
||||
public GetCatalogInstances(String lang, String category){
|
||||
public GetCatalogInstances(String lang, String category, boolean includeClosedSignups){
|
||||
super(HttpMethod.GET, null, new TypeToken<>(){});
|
||||
this.lang=lang;
|
||||
this.category=category;
|
||||
this.includeClosedSignups=includeClosedSignups;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -30,6 +32,8 @@ public class GetCatalogInstances extends MastodonAPIRequest<List<CatalogInstance
|
||||
builder.appendQueryParameter("language", lang);
|
||||
if(!TextUtils.isEmpty(category))
|
||||
builder.appendQueryParameter("category", category);
|
||||
if(includeClosedSignups)
|
||||
builder.appendQueryParameter("registrations", "all");
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ public class AccountSessionManager{
|
||||
Log.e(TAG, "Error loading accounts", x);
|
||||
}
|
||||
lastActiveAccountID=prefs.getString("lastActiveAccount", null);
|
||||
MastodonAPIController.runInBackground(()->readInstanceInfo(domains));
|
||||
readInstanceInfo(domains);
|
||||
maybeUpdateShortcuts();
|
||||
}
|
||||
|
||||
@@ -270,11 +270,11 @@ public class AccountSessionManager{
|
||||
}
|
||||
}
|
||||
if(loadedInstances){
|
||||
maybeUpdateCustomEmojis(domains);
|
||||
maybeUpdateInstanceInfo(domains);
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeUpdateCustomEmojis(Set<String> domains){
|
||||
private void maybeUpdateInstanceInfo(Set<String> domains){
|
||||
long now=System.currentTimeMillis();
|
||||
for(String domain:domains){
|
||||
Long lastUpdated=instancesLastUpdated.get(domain);
|
||||
@@ -388,7 +388,7 @@ public class AccountSessionManager{
|
||||
}
|
||||
if(!loadedInstances){
|
||||
loadedInstances=true;
|
||||
maybeUpdateCustomEmojis(domains);
|
||||
MastodonAPIController.runInBackground(()->maybeUpdateInstanceInfo(domains));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,7 +425,7 @@ public class AccountSessionManager{
|
||||
ShortcutManager sm=MastodonApp.context.getSystemService(ShortcutManager.class);
|
||||
if((sm.getDynamicShortcuts().isEmpty() || BuildConfig.DEBUG) && !sessions.isEmpty()){
|
||||
// There are no shortcuts, but there are accounts. Add a compose shortcut.
|
||||
ShortcutInfo info=new ShortcutInfo.Builder(MastodonApp.context, "compose")
|
||||
ShortcutInfo compose=new ShortcutInfo.Builder(MastodonApp.context, "compose")
|
||||
.setActivity(ComponentName.createRelative(MastodonApp.context, MainActivity.class.getName()))
|
||||
.setShortLabel(MastodonApp.context.getString(R.string.new_post))
|
||||
.setIcon(Icon.createWithResource(MastodonApp.context, R.mipmap.ic_shortcut_compose))
|
||||
@@ -433,12 +433,20 @@ public class AccountSessionManager{
|
||||
.setAction(Intent.ACTION_MAIN)
|
||||
.putExtra("compose", true))
|
||||
.build();
|
||||
sm.setDynamicShortcuts(Collections.singletonList(info));
|
||||
ShortcutInfo explore=new ShortcutInfo.Builder(MastodonApp.context, "explore")
|
||||
.setActivity(ComponentName.createRelative(MastodonApp.context, MainActivity.class.getName()))
|
||||
.setShortLabel(MastodonApp.context.getString(R.string.tab_search))
|
||||
.setIcon(Icon.createWithResource(MastodonApp.context, R.mipmap.ic_shortcut_explore))
|
||||
.setIntent(new Intent(MastodonApp.context, MainActivity.class)
|
||||
.setAction(Intent.ACTION_MAIN)
|
||||
.putExtra("explore", true))
|
||||
.build();
|
||||
sm.setDynamicShortcuts(List.of(compose, explore));
|
||||
}else if(sessions.isEmpty()){
|
||||
// There are shortcuts, but no accounts. Disable existing shortcuts.
|
||||
sm.disableShortcuts(Collections.singletonList("compose"), MastodonApp.context.getString(R.string.err_not_logged_in));
|
||||
sm.disableShortcuts(List.of("compose", "explore"), MastodonApp.context.getString(R.string.err_not_logged_in));
|
||||
}else{
|
||||
sm.enableShortcuts(Collections.singletonList("compose"));
|
||||
sm.enableShortcuts(List.of("compose", "explore"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,9 @@ import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringForce;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
@@ -79,6 +82,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
||||
protected Rect tmpRect=new Rect();
|
||||
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView);
|
||||
private SpringAnimation listShakeAnimation;
|
||||
|
||||
public BaseStatusListFragment(){
|
||||
super(20);
|
||||
@@ -283,8 +287,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
outRect.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
|
||||
}
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(view);
|
||||
if(holder instanceof StatusDisplayItem.Holder){
|
||||
if(((StatusDisplayItem.Holder<?>) holder).getItem().getType()==StatusDisplayItem.Type.GAP){
|
||||
if(holder instanceof StatusDisplayItem.Holder<?> sih){
|
||||
if(sih.getItem() instanceof StatusDisplayItem sdi && sdi.getType()==StatusDisplayItem.Type.GAP){
|
||||
outRect.setEmpty();
|
||||
return;
|
||||
}
|
||||
@@ -292,8 +296,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
View child=list.getChildAt(i);
|
||||
holder=list.getChildViewHolder(child);
|
||||
if(holder instanceof StatusDisplayItem.Holder){
|
||||
String otherID=((StatusDisplayItem.Holder<?>) holder).getItemID();
|
||||
if(holder instanceof StatusDisplayItem.Holder<?> sih2){
|
||||
String otherID=sih2.getItemID();
|
||||
if(otherID.equals(id)){
|
||||
list.getDecoratedBoundsWithMargins(child, tmpRect);
|
||||
outRect.left=Math.min(outRect.left, tmpRect.left);
|
||||
@@ -662,7 +666,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
proceed.run();
|
||||
}, status.account, accountID).show();
|
||||
}else if(!GlobalUserPreferences.isOptedOutOfPreReplySheet(GlobalUserPreferences.PreReplySheetType.OLD_POST, null, null) &&
|
||||
status.createdAt.isBefore(Instant.now().minus(90, ChronoUnit.DAYS))){
|
||||
status.createdAt.isBefore(Instant.now().minus(90, ChronoUnit.DAYS)) && !status.account.id.equals(AccountSessionManager.get(accountID).self.id)){
|
||||
new OldPostPreReplySheet(getActivity(), notAgain->{
|
||||
if(notAgain)
|
||||
GlobalUserPreferences.optOutOfPreReplySheet(GlobalUserPreferences.PreReplySheetType.OLD_POST, null, null);
|
||||
@@ -675,6 +679,17 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
|
||||
protected void onModifyItemViewHolder(BindableViewHolder<StatusDisplayItem> holder){}
|
||||
|
||||
public void shakeListView(){
|
||||
if(listShakeAnimation!=null)
|
||||
listShakeAnimation.cancel();
|
||||
SpringAnimation anim=new SpringAnimation(list, DynamicAnimation.TRANSLATION_X, 0);
|
||||
anim.setStartVelocity(V.dp(-500));
|
||||
anim.getSpring().setStiffness(500).setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
|
||||
listShakeAnimation=anim;
|
||||
anim.addEndListener((animation, canceled, value, velocity)->listShakeAnimation=null);
|
||||
anim.start();
|
||||
}
|
||||
|
||||
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
|
||||
|
||||
public DisplayItemsAdapter(){
|
||||
@@ -745,7 +760,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
// Do not draw dividers between hashtag and/or account rows
|
||||
if((ih instanceof HashtagStatusDisplayItem.Holder || ih instanceof AccountStatusDisplayItem.Holder) && (sh instanceof HashtagStatusDisplayItem.Holder || sh instanceof AccountStatusDisplayItem.Holder))
|
||||
return false;
|
||||
return !ih.getItemID().equals(sh.getItemID()) && ih.getItem().getType()!=StatusDisplayItem.Type.GAP;
|
||||
return !ih.getItemID().equals(sh.getItemID()) && ih.getItem() instanceof StatusDisplayItem sdi && sdi.getType()!=StatusDisplayItem.Type.GAP;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@ import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.style.BackgroundColorSpan;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.transition.ChangeBounds;
|
||||
import android.transition.Fade;
|
||||
import android.transition.TransitionManager;
|
||||
import android.transition.TransitionSet;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -32,15 +36,14 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.view.WindowManager;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
@@ -66,7 +69,9 @@ import org.joinmastodon.android.model.Mention;
|
||||
import org.joinmastodon.android.model.Preferences;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||
import org.joinmastodon.android.ui.CustomEmojiPopupKeyboard;
|
||||
import org.joinmastodon.android.ui.ExtendedPopupMenu;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.PopupKeyboard;
|
||||
@@ -87,21 +92,22 @@ import org.parceler.Parcels;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.CustomTransitionsFragment;
|
||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class ComposeFragment extends MastodonToolbarFragment implements OnBackPressedListener, ComposeEditText.SelectionListener, CustomTransitionsFragment{
|
||||
public class ComposeFragment extends MastodonToolbarFragment implements ComposeEditText.SelectionListener, CustomTransitionsFragment{
|
||||
|
||||
private static final int MEDIA_RESULT=717;
|
||||
public static final int IMAGE_DESCRIPTION_RESULT=363;
|
||||
@@ -131,7 +137,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
|
||||
private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn, languageBtn;
|
||||
private TextView replyText;
|
||||
private Button visibilityBtn;
|
||||
private LinearLayout visibilityBtn;
|
||||
private TextView visibilityText1, visibilityText2, visibilityCurrentText;
|
||||
private LinearLayout bottomBar;
|
||||
private View autocompleteDivider;
|
||||
|
||||
@@ -165,6 +172,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
private BackgroundColorSpan overLimitBG;
|
||||
private ForegroundColorSpan overLimitFG;
|
||||
|
||||
private Runnable emojiKeyboardHider;
|
||||
private Runnable sendingBackButtonBlocker=()->{};
|
||||
private Runnable discardConfirmationCallback=this::confirmDiscardDraftAndFinish;
|
||||
private boolean prevHadDraft;
|
||||
|
||||
public ComposeFragment(){
|
||||
super(R.layout.toolbar_fragment_with_progressbar);
|
||||
}
|
||||
@@ -241,6 +253,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
getActivity().dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
|
||||
}
|
||||
});
|
||||
emojiKeyboardHider=emojiKeyboard::hide;
|
||||
|
||||
View view=inflater.inflate(R.layout.fragment_compose, container, false);
|
||||
mainLayout=view.findViewById(R.id.compose_main_ll);
|
||||
@@ -271,6 +284,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
emojiBtn=view.findViewById(R.id.btn_emoji);
|
||||
spoilerBtn=view.findViewById(R.id.btn_spoiler);
|
||||
visibilityBtn=view.findViewById(R.id.btn_visibility);
|
||||
visibilityText1=view.findViewById(R.id.visibility_text1);
|
||||
visibilityText2=view.findViewById(R.id.visibility_text2);
|
||||
visibilityCurrentText=visibilityText1;
|
||||
languageBtn=view.findViewById(R.id.btn_language);
|
||||
replyText=view.findViewById(R.id.reply_text);
|
||||
|
||||
@@ -280,14 +296,24 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
spoilerBtn.setOnClickListener(v->toggleSpoiler());
|
||||
languageBtn.setOnClickListener(v->showLanguageAlert());
|
||||
visibilityBtn.setOnClickListener(this::onVisibilityClick);
|
||||
visibilityBtn.setAccessibilityDelegate(new View.AccessibilityDelegate(){
|
||||
@Override
|
||||
public void onInitializeAccessibilityNodeInfo(@NonNull View host, @NonNull AccessibilityNodeInfo info){
|
||||
super.onInitializeAccessibilityNodeInfo(host, info);
|
||||
info.setClassName("android.widget.Spinner");
|
||||
}
|
||||
});
|
||||
Drawable arrow=getResources().getDrawable(R.drawable.ic_baseline_arrow_drop_down_18, getActivity().getTheme()).mutate();
|
||||
arrow.setTint(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurface));
|
||||
visibilityBtn.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, arrow, null);
|
||||
emojiKeyboard.setOnIconChangedListener(new PopupKeyboard.OnIconChangeListener(){
|
||||
@Override
|
||||
public void onIconChanged(int icon){
|
||||
emojiBtn.setSelected(icon!=PopupKeyboard.ICON_HIDDEN);
|
||||
updateNavigationBarColor(icon!=PopupKeyboard.ICON_HIDDEN);
|
||||
if(icon!=PopupKeyboard.ICON_HIDDEN)
|
||||
addBackCallback(emojiKeyboardHider);
|
||||
else
|
||||
removeBackCallback(emojiKeyboardHider);
|
||||
if(autocompleteViewController.getMode()==ComposeAutocompleteViewController.Mode.EMOJIS){
|
||||
contentView.layout(contentView.getLeft(), contentView.getTop(), contentView.getRight(), contentView.getBottom());
|
||||
if(icon==PopupKeyboard.ICON_HIDDEN)
|
||||
@@ -323,7 +349,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
if(editingStatus!=null && editingStatus.visibility!=null) {
|
||||
statusVisibility=editingStatus.visibility;
|
||||
}
|
||||
updateVisibilityIcon();
|
||||
updateVisibilityIcon(false);
|
||||
|
||||
autocompleteViewController=new ComposeAutocompleteViewController(getActivity(), accountID);
|
||||
autocompleteViewController.setCompletionSelectedListener(new ComposeAutocompleteViewController.AutocompleteListener(){
|
||||
@@ -378,6 +404,21 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHidden(){
|
||||
super.onHidden();
|
||||
if(prevHadDraft){
|
||||
prevHadDraft=false;
|
||||
removeBackCallback(discardConfirmationCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShown(){
|
||||
super.onShown();
|
||||
updateDraftState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
@@ -463,6 +504,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}
|
||||
|
||||
updateCharCounter();
|
||||
updateDraftState();
|
||||
}
|
||||
});
|
||||
spoilerEdit.addTextChangedListener(new SimpleTextWatcher(e->updateCharCounter()));
|
||||
@@ -502,7 +544,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
ignoreSelectionChanges=true;
|
||||
mainEditText.setSelection(mainEditText.length());
|
||||
ignoreSelectionChanges=false;
|
||||
mediaViewController.onViewCreated(savedInstanceState);;
|
||||
mediaViewController.onViewCreated(savedInstanceState);
|
||||
}else{
|
||||
String prefilledText=getArguments().getString("prefilledText");
|
||||
if(!TextUtils.isEmpty(prefilledText)){
|
||||
@@ -604,6 +646,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
if(publishButton==null)
|
||||
return;
|
||||
publishButton.setEnabled((trimmedCharCount>0 || !mediaViewController.isEmpty()) && charCount<=charLimit && mediaViewController.getNonDoneAttachmentCount()==0 && (pollViewController.isEmpty() || pollViewController.getNonEmptyOptionsCount()>1));
|
||||
updateDraftState();
|
||||
}
|
||||
|
||||
private void onCustomEmojiClick(Emoji emoji){
|
||||
@@ -679,6 +722,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
overlayParams.softInputMode=WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
|
||||
overlayParams.token=mainEditText.getWindowToken();
|
||||
wm.addView(sendingOverlay, overlayParams);
|
||||
addBackCallback(sendingBackButtonBlocker);
|
||||
|
||||
publishButton.setEnabled(false);
|
||||
V.setVisibilityAnimated(sendProgress, View.VISIBLE);
|
||||
@@ -703,8 +747,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
if(!pollViewController.isEmpty()){
|
||||
req.poll=pollViewController.getPollForRequest();
|
||||
}
|
||||
if(hasSpoiler && spoilerEdit.length()>0){
|
||||
req.spoilerText=spoilerEdit.getText().toString();
|
||||
if(hasSpoiler){
|
||||
if(spoilerEdit.length()>0)
|
||||
req.spoilerText=spoilerEdit.getText().toString();
|
||||
else
|
||||
req.sensitive=true;
|
||||
}
|
||||
if(postLang!=null){
|
||||
req.language=postLang.locale.toLanguageTag();
|
||||
@@ -717,6 +764,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
public void onSuccess(Status result){
|
||||
wm.removeView(sendingOverlay);
|
||||
sendingOverlay=null;
|
||||
removeBackCallback(sendingBackButtonBlocker);
|
||||
removeBackCallback(discardConfirmationCallback);
|
||||
removeBackCallback(emojiKeyboardHider);
|
||||
if(editingStatus==null){
|
||||
E.post(new StatusCreatedEvent(result, accountID));
|
||||
if(replyTo!=null){
|
||||
@@ -749,6 +799,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
private void handlePublishError(ErrorResponse error){
|
||||
wm.removeView(sendingOverlay);
|
||||
sendingOverlay=null;
|
||||
removeBackCallback(sendingBackButtonBlocker);
|
||||
V.setVisibilityAnimated(sendProgress, View.GONE);
|
||||
publishButton.setEnabled(true);
|
||||
if(error instanceof MastodonErrorResponse me){
|
||||
@@ -776,19 +827,16 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
return (mainEditText.length()>0 && !mainEditText.getText().toString().equals(initialText)) || !mediaViewController.isEmpty() || pollFieldsHaveContent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed(){
|
||||
if(emojiKeyboard.isVisible()){
|
||||
emojiKeyboard.hide();
|
||||
return true;
|
||||
private void updateDraftState(){
|
||||
boolean hasDraft=hasDraft();
|
||||
if(hasDraft!=prevHadDraft){
|
||||
prevHadDraft=hasDraft;
|
||||
if(hasDraft){
|
||||
addBackCallback(discardConfirmationCallback);
|
||||
}else{
|
||||
removeBackCallback(discardConfirmationCallback);
|
||||
}
|
||||
}
|
||||
if(hasDraft()){
|
||||
confirmDiscardDraftAndFinish();
|
||||
return true;
|
||||
}
|
||||
if(sendingOverlay!=null)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -822,7 +870,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
private void confirmDiscardDraftAndFinish(){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(editingStatus==null ? R.string.discard_draft : R.string.discard_changes)
|
||||
.setPositiveButton(R.string.discard, (dialog, which)->Nav.finish(this))
|
||||
.setPositiveButton(R.string.discard, (dialog, which)->{
|
||||
removeBackCallback(discardConfirmationCallback);
|
||||
Nav.finish(this);
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
@@ -909,24 +960,20 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}
|
||||
|
||||
private void onVisibilityClick(View v){
|
||||
PopupMenu menu=new PopupMenu(getActivity(), v);
|
||||
menu.inflate(R.menu.compose_visibility);
|
||||
menu.setOnMenuItemClickListener(item->{
|
||||
int id=item.getItemId();
|
||||
if(id==R.id.vis_public){
|
||||
statusVisibility=StatusPrivacy.PUBLIC;
|
||||
}else if(id==R.id.vis_unlisted){
|
||||
statusVisibility=StatusPrivacy.UNLISTED;
|
||||
}else if(id==R.id.vis_followers){
|
||||
statusVisibility=StatusPrivacy.PRIVATE;
|
||||
}else if(id==R.id.vis_private){
|
||||
statusVisibility=StatusPrivacy.DIRECT;
|
||||
ArrayList<ListItem<StatusPrivacy>> items=new ArrayList<>();
|
||||
ExtendedPopupMenu menu=new ExtendedPopupMenu(getActivity(), items);
|
||||
Consumer<ListItem<StatusPrivacy>> onClick=i->{
|
||||
if(statusVisibility!=i.parentObject){
|
||||
statusVisibility=i.parentObject;
|
||||
updateVisibilityIcon(true);
|
||||
}
|
||||
item.setChecked(true);
|
||||
updateVisibilityIcon();
|
||||
return true;
|
||||
});
|
||||
menu.show();
|
||||
menu.dismiss();
|
||||
};
|
||||
items.add(new ListItem<>(R.string.visibility_public, R.string.visibility_subtitle_public, R.drawable.ic_public_24px, StatusPrivacy.PUBLIC, onClick));
|
||||
items.add(new ListItem<>(R.string.visibility_unlisted, R.string.visibility_subtitle_unlisted, R.drawable.ic_clear_night_24px, StatusPrivacy.UNLISTED, onClick));
|
||||
items.add(new ListItem<>(R.string.visibility_followers_only, R.string.visibility_subtitle_followers, R.drawable.ic_lock_24px, StatusPrivacy.PRIVATE, onClick));
|
||||
items.add(new ListItem<>(R.string.visibility_private, R.string.visibility_subtitle_private, R.drawable.ic_alternate_email_24px, StatusPrivacy.DIRECT, onClick));
|
||||
menu.showAsDropDown(v);
|
||||
}
|
||||
|
||||
private void loadDefaultStatusVisibility(Bundle savedInstanceState){
|
||||
@@ -960,16 +1007,31 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
statusVisibility=(StatusPrivacy) savedInstanceState.getSerializable("visibility");
|
||||
}
|
||||
|
||||
updateVisibilityIcon();
|
||||
updateVisibilityIcon(false);
|
||||
}
|
||||
|
||||
private void updateVisibilityIcon(){
|
||||
private void updateVisibilityIcon(boolean animated){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
if(statusVisibility==null){ // TODO find out why this happens
|
||||
statusVisibility=StatusPrivacy.PUBLIC;
|
||||
}
|
||||
visibilityBtn.setText(switch(statusVisibility){
|
||||
TextView visibilityText;
|
||||
if(!animated){
|
||||
visibilityText=visibilityCurrentText;
|
||||
}else{
|
||||
TransitionManager.beginDelayedTransition(visibilityBtn, new TransitionSet()
|
||||
.addTransition(new Fade(Fade.IN | Fade.OUT))
|
||||
.addTransition(new ChangeBounds().excludeTarget(TextView.class, true))
|
||||
.setDuration(250)
|
||||
.setInterpolator(CubicBezierInterpolator.DEFAULT)
|
||||
);
|
||||
visibilityText=visibilityCurrentText==visibilityText1 ? visibilityText2 : visibilityText1;
|
||||
visibilityText.setVisibility(View.VISIBLE);
|
||||
visibilityCurrentText.setVisibility(View.GONE);
|
||||
visibilityCurrentText=visibilityText;
|
||||
}
|
||||
visibilityText.setText(switch(statusVisibility){
|
||||
case PUBLIC -> R.string.visibility_public;
|
||||
case UNLISTED -> R.string.visibility_unlisted;
|
||||
case PRIVATE -> R.string.visibility_followers_only;
|
||||
@@ -983,7 +1045,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}, getActivity().getTheme()).mutate();
|
||||
icon.setBounds(0, 0, V.dp(18), V.dp(18));
|
||||
icon.setTint(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary));
|
||||
visibilityBtn.setCompoundDrawablesRelative(icon, null, visibilityBtn.getCompoundDrawablesRelative()[2], null);
|
||||
visibilityText.setCompoundDrawablesRelative(icon, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -32,12 +32,11 @@ import java.util.Collections;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
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;
|
||||
|
||||
public class ComposeImageDescriptionFragment extends MastodonToolbarFragment implements OnBackPressedListener{
|
||||
public class ComposeImageDescriptionFragment extends MastodonToolbarFragment{
|
||||
private static final String TAG="ComposeImageDescription";
|
||||
|
||||
private String accountID, attachmentID;
|
||||
@@ -138,9 +137,9 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment imp
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed(){
|
||||
public void onStop(){
|
||||
super.onStop();
|
||||
deliverResult();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -42,13 +42,11 @@ import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.fragments.WindowInsetsAwareFragment;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
|
||||
public class CreateListAddMembersFragment extends BaseAccountListFragment implements OnBackPressedListener, AddNewListMembersFragment.Listener{
|
||||
public class CreateListAddMembersFragment extends BaseAccountListFragment implements AddNewListMembersFragment.Listener{
|
||||
private FollowList followList;
|
||||
private Button nextButton;
|
||||
private View buttonBar;
|
||||
@@ -59,6 +57,7 @@ public class CreateListAddMembersFragment extends BaseAccountListFragment implem
|
||||
private WindowInsets lastInsets;
|
||||
private boolean dismissingSearchFragment;
|
||||
private HashSet<String> accountIDsInList=new HashSet<>();
|
||||
private Runnable searchFragmentDismisser=this::dismissSearchFragment;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -156,6 +155,7 @@ public class CreateListAddMembersFragment extends BaseAccountListFragment implem
|
||||
searchFragmentContainer.animate().translationX(0).alpha(1).setDuration(300).withLayer().setInterpolator(CubicBezierInterpolator.DEFAULT).withEndAction(()->{
|
||||
rootView.setVisibility(View.GONE);
|
||||
}).start();
|
||||
addBackCallback(searchFragmentDismisser);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -183,6 +183,7 @@ public class CreateListAddMembersFragment extends BaseAccountListFragment implem
|
||||
private void dismissSearchFragment(){
|
||||
if(searchFragment==null || dismissingSearchFragment)
|
||||
return;
|
||||
removeBackCallback(searchFragmentDismisser);
|
||||
dismissingSearchFragment=true;
|
||||
rootView.setVisibility(View.VISIBLE);
|
||||
searchFragmentContainer.animate().translationX(V.dp(100)).alpha(0).setDuration(200).withLayer().setInterpolator(CubicBezierInterpolator.DEFAULT).withEndAction(()->{
|
||||
@@ -201,15 +202,6 @@ public class CreateListAddMembersFragment extends BaseAccountListFragment implem
|
||||
Nav.finish(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed(){
|
||||
if(searchFragment!=null){
|
||||
dismissSearchFragment();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountInList(AccountViewModel account){
|
||||
return accountIDsInList.contains(account.account.id);
|
||||
|
||||
@@ -275,4 +275,8 @@ public class HashtagTimelineFragment extends StatusListFragment{
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
public String getHashtagName(){
|
||||
return hashtagName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ import org.joinmastodon.android.fragments.onboarding.OnboardingFollowSuggestions
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.PaginatedResponse;
|
||||
import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.TabBar;
|
||||
import org.joinmastodon.android.utils.ObjectIdComparator;
|
||||
@@ -48,13 +48,12 @@ import me.grishka.appkit.api.Callback;
|
||||
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.FragmentRootLinearLayout;
|
||||
|
||||
public class HomeFragment extends AppKitFragment implements OnBackPressedListener{
|
||||
public class HomeFragment extends AppKitFragment{
|
||||
private FragmentRootLinearLayout content;
|
||||
private HomeTimelineFragment homeTimelineFragment;
|
||||
private NotificationsListFragment notificationsFragment;
|
||||
@@ -272,15 +271,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed(){
|
||||
if(currentTab==R.id.tab_profile)
|
||||
return profileFragment.onBackPressed();
|
||||
if(currentTab==R.id.tab_search)
|
||||
return searchFragment.onBackPressed();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState){
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
@@ -44,12 +44,11 @@ import java.util.stream.Collectors;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
|
||||
public class ListMembersFragment extends PaginatedAccountListFragment implements AddNewListMembersFragment.Listener, OnBackPressedListener{
|
||||
public class ListMembersFragment extends PaginatedAccountListFragment implements AddNewListMembersFragment.Listener{
|
||||
private ImageButton fab;
|
||||
private FollowList followList;
|
||||
private boolean inSelectionMode;
|
||||
@@ -63,6 +62,8 @@ public class ListMembersFragment extends PaginatedAccountListFragment implements
|
||||
private WindowInsets lastInsets;
|
||||
private HashSet<String> accountIDsInList=new HashSet<>();
|
||||
private boolean dismissingSearchFragment;
|
||||
private Runnable searchFragmentDismisser=this::dismissSearchFragment;;
|
||||
private Runnable actionModeDismisser=()->actionMode.finish();
|
||||
|
||||
public ListMembersFragment(){
|
||||
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
||||
@@ -214,6 +215,7 @@ public class ListMembersFragment extends PaginatedAccountListFragment implements
|
||||
searchFragmentContainer.animate().translationX(0).alpha(1).setDuration(300).withLayer().setInterpolator(CubicBezierInterpolator.DEFAULT).withEndAction(()->{
|
||||
rootView.setVisibility(View.GONE);
|
||||
}).start();
|
||||
addBackCallback(searchFragmentDismisser);
|
||||
}
|
||||
|
||||
private void onItemClick(AccountViewHolder holder){
|
||||
@@ -293,9 +295,11 @@ public class ListMembersFragment extends PaginatedAccountListFragment implements
|
||||
selectedAccounts.clear();
|
||||
updateItemsForSelectionModeTransition();
|
||||
V.setVisibilityAnimated(fab, View.VISIBLE);
|
||||
removeBackCallback(actionModeDismisser);
|
||||
}
|
||||
});
|
||||
updateActionModeTitle();
|
||||
addBackCallback(actionModeDismisser);
|
||||
}
|
||||
|
||||
private void updateActionModeTitle(){
|
||||
@@ -371,15 +375,6 @@ public class ListMembersFragment extends PaginatedAccountListFragment implements
|
||||
removeAccounts(Set.of(account.account.id), onDone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed(){
|
||||
if(searchFragment!=null){
|
||||
dismissSearchFragment();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void dismissSearchFragment(){
|
||||
if(searchFragment==null || dismissingSearchFragment)
|
||||
return;
|
||||
@@ -393,6 +388,7 @@ public class ListMembersFragment extends PaginatedAccountListFragment implements
|
||||
searchFragment=null;
|
||||
dismissingSearchFragment=false;
|
||||
}).start();
|
||||
removeBackCallback(searchFragmentDismisser);
|
||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -100,14 +100,13 @@ import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
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.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
|
||||
public class ProfileFragment extends LoaderFragment implements OnBackPressedListener, ScrollableToTop{
|
||||
public class ProfileFragment extends LoaderFragment implements ScrollableToTop{
|
||||
private static final int AVATAR_RESULT=722;
|
||||
private static final int COVER_RESULT=343;
|
||||
|
||||
@@ -158,6 +157,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
private Animator tabBarColorAnim;
|
||||
private MenuItem editSaveMenuItem;
|
||||
private boolean savingEdits;
|
||||
private Runnable editModeBackCallback=this::onEditModeBackCallback;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -687,7 +687,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
notifications.setTitle(getString(relationship.notifying ? R.string.disable_new_post_notifications : R.string.enable_new_post_notifications, account.getDisplayUsername()));
|
||||
}
|
||||
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P && !UiUtils.isEMUI()){
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P && !UiUtils.isEMUI() && !UiUtils.isMagic()){
|
||||
menu.setGroupDividerEnabled(true);
|
||||
}
|
||||
}
|
||||
@@ -983,12 +983,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
refreshLayout.setEnabled(false);
|
||||
editDirty=false;
|
||||
V.setVisibilityAnimated(fab, View.GONE);
|
||||
addBackCallback(editModeBackCallback);
|
||||
}
|
||||
|
||||
private void exitEditMode(){
|
||||
if(!isInEditMode)
|
||||
throw new IllegalStateException();
|
||||
isInEditMode=false;
|
||||
removeBackCallback(editModeBackCallback);
|
||||
|
||||
invalidateOptionsMenu();
|
||||
actionButton.setText(R.string.edit_profile);
|
||||
@@ -1098,23 +1100,18 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
updateRelationship();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed(){
|
||||
if(isInEditMode){
|
||||
if(savingEdits)
|
||||
return true;
|
||||
if(editDirty || aboutFragment.isEditDirty()){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.discard_changes)
|
||||
.setPositiveButton(R.string.discard, (dlg, btn)->exitEditMode())
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}else{
|
||||
exitEditMode();
|
||||
}
|
||||
return true;
|
||||
private void onEditModeBackCallback(){
|
||||
if(savingEdits)
|
||||
return;
|
||||
if(editDirty || aboutFragment.isEditDirty()){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.discard_changes)
|
||||
.setPositiveButton(R.string.discard, (dlg, btn)->exitEditMode())
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}else{
|
||||
exitEditMode();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<Attachment> createFakeAttachments(String url, Drawable drawable){
|
||||
@@ -1149,7 +1146,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
startImagePicker(COVER_RESULT);
|
||||
}else{
|
||||
Drawable drawable=cover.getDrawable();
|
||||
if(drawable==null || drawable instanceof ColorDrawable)
|
||||
if(drawable==null || drawable instanceof ColorDrawable || account.headerStatic.endsWith("/missing.png"))
|
||||
return;
|
||||
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.header, drawable), 0,
|
||||
null, accountID, new SingleImagePhotoViewerListener(cover, cover, null, this, ()->currentPhotoViewer=null, ()->drawable, ()->avatarBorder.setTranslationZ(2), ()->avatarBorder.setTranslationZ(0)));
|
||||
|
||||
@@ -53,6 +53,8 @@ import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.window.OnBackInvokedCallback;
|
||||
import android.window.OnBackInvokedDispatcher;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
@@ -144,12 +146,16 @@ public class ProfileQrCodeFragment extends AppKitFragment{
|
||||
if(!isTablet){
|
||||
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
}
|
||||
dlg.setOnKeyListener((dialog, keyCode, event)->{
|
||||
if(keyCode==KeyEvent.KEYCODE_BACK && event.getAction()==KeyEvent.ACTION_DOWN){
|
||||
dismiss();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.TIRAMISU){
|
||||
dlg.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, this::dismiss);
|
||||
}else{
|
||||
dlg.setOnKeyListener((dialog, keyCode, event)->{
|
||||
if(keyCode==KeyEvent.KEYCODE_BACK && event.getAction()==KeyEvent.ACTION_DOWN){
|
||||
dismiss();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -36,10 +36,9 @@ import androidx.viewpager2.widget.ViewPager2;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, OnBackPressedListener{
|
||||
public class DiscoverFragment extends AppKitFragment implements ScrollableToTop{
|
||||
private static final int QUERY_RESULT=937;
|
||||
private static final int SCAN_RESULT=456;
|
||||
|
||||
@@ -62,6 +61,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
private String accountID;
|
||||
private String currentQuery;
|
||||
private Intent scannerIntent;
|
||||
private Runnable searchExitCallback=this::exitSearch;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -232,6 +232,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
searchBack.setEnabled(true);
|
||||
searchBack.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
|
||||
tabsDivider.setVisibility(View.GONE);
|
||||
addBackCallback(searchExitCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,6 +249,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
searchBack.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
|
||||
tabsDivider.setVisibility(View.VISIBLE);
|
||||
currentQuery=null;
|
||||
removeBackCallback(searchExitCallback);
|
||||
}
|
||||
|
||||
private Fragment getFragmentForPage(int page){
|
||||
@@ -260,15 +262,6 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed(){
|
||||
if(searchActive){
|
||||
exitSearch();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFragmentResult(int reqCode, boolean success, Bundle result){
|
||||
if(reqCode==QUERY_RESULT && success){
|
||||
|
||||
@@ -2,56 +2,35 @@ package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.trends.GetTrendingLinks;
|
||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||
import org.joinmastodon.android.model.Card;
|
||||
import org.joinmastodon.android.model.viewmodel.CardViewModel;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
|
||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
import org.joinmastodon.android.ui.utils.HorizontalScrollingTouchListener;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.viewholders.LinkCardHolder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.ListImageLoaderAdapter;
|
||||
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
|
||||
import me.grishka.appkit.imageloader.RecyclerViewDelegate;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> implements ScrollableToTop{
|
||||
public class DiscoverNewsFragment extends BaseRecyclerFragment<DiscoverNewsFragment.CardItem> implements ScrollableToTop{
|
||||
private String accountID;
|
||||
private DiscoverInfoBannerHelper bannerHelper;
|
||||
private MergeRecyclerAdapter mergeAdapter;
|
||||
private UsableRecyclerView cardsList;
|
||||
private ArrayList<CardViewModel> top3=new ArrayList<>();
|
||||
private CardLinksAdapter cardsAdapter;
|
||||
|
||||
public DiscoverNewsFragment(){
|
||||
super(10);
|
||||
@@ -70,12 +49,14 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> im
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Card> result){
|
||||
top3.clear();
|
||||
top3.addAll(result.subList(0, Math.min(3, result.size())).stream().map(card->new CardViewModel(card, 280, 140)).collect(Collectors.toList()));
|
||||
cardsAdapter.notifyDataSetChanged();
|
||||
|
||||
onDataLoaded(result.subList(top3.size(), result.size()).stream()
|
||||
.map(card->new CardViewModel(card, 56, 56))
|
||||
int[] index={0};
|
||||
onDataLoaded(result.stream()
|
||||
.map(card->{
|
||||
int actualIndex=index[0]+(refreshing ? 0 : (data.size()+preloadedData.size()));
|
||||
index[0]++;
|
||||
int size=actualIndex==0 ? 1000 : 192;
|
||||
return new CardItem(new CardViewModel(card, size, size, card, accountID));
|
||||
})
|
||||
.collect(Collectors.toList()), false);
|
||||
bannerHelper.onBannerBecameVisible();
|
||||
}
|
||||
@@ -86,27 +67,9 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> im
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
cardsList=new UsableRecyclerView(getActivity());
|
||||
cardsList.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false));
|
||||
ListImageLoaderWrapper cardsImageLoader=new ListImageLoaderWrapper(getActivity(), cardsList, new RecyclerViewDelegate(cardsList), this);
|
||||
cardsList.setAdapter(cardsAdapter=new CardLinksAdapter(cardsImageLoader, top3));
|
||||
cardsList.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(256)));
|
||||
cardsList.setPadding(V.dp(16), V.dp(8), 0, 0);
|
||||
cardsList.setClipToPadding(false);
|
||||
cardsList.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
outRect.right=V.dp(16);
|
||||
}
|
||||
});
|
||||
cardsList.setSelector(R.drawable.bg_rect_12dp_ripple);
|
||||
cardsList.setDrawSelectorOnTop(true);
|
||||
cardsList.setOnTouchListener(new HorizontalScrollingTouchListener(getActivity()));
|
||||
|
||||
mergeAdapter=new MergeRecyclerAdapter();
|
||||
bannerHelper.maybeAddBanner(list, mergeAdapter);
|
||||
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(cardsList));
|
||||
mergeAdapter.addAdapter(new LinksAdapter(imgLoader, data));
|
||||
mergeAdapter.addAdapter(new LinksAdapter(imgLoader));
|
||||
return mergeAdapter;
|
||||
}
|
||||
|
||||
@@ -115,18 +78,46 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> im
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
|
||||
private class LinksAdapter extends UsableRecyclerView.Adapter<BaseLinkViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
private final List<CardViewModel> data;
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
if(parent.getChildAdapterPosition(view)==0 && !bannerHelper.isBannerShown()){
|
||||
outRect.top=V.dp(16);
|
||||
}
|
||||
if(parent.getChildViewHolder(view) instanceof LinkCardHolder<?>){
|
||||
outRect.bottom=V.dp(8);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LinksAdapter(ListImageLoaderWrapper imgLoader, List<CardViewModel> data){
|
||||
public static class CardItem implements LinkCardHolder.LinkCardProvider{
|
||||
public final CardViewModel card;
|
||||
|
||||
private CardItem(CardViewModel card){
|
||||
this.card=card;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CardViewModel getCard(){
|
||||
return card;
|
||||
}
|
||||
}
|
||||
|
||||
private class LinksAdapter extends UsableRecyclerView.Adapter<LinkCardHolder<CardItem>> implements ImageLoaderRecyclerAdapter{
|
||||
public LinksAdapter(ListImageLoaderWrapper imgLoader){
|
||||
super(imgLoader);
|
||||
this.data=data;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public BaseLinkViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new LinkViewHolder();
|
||||
public LinkCardHolder<CardItem> onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
LinkCardHolder<CardItem> vh=new LinkCardHolder<>(getActivity(), list, viewType==1, accountID);
|
||||
vh.setTryResolving(false);
|
||||
return vh;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -135,91 +126,24 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> im
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(BaseLinkViewHolder holder, int position){
|
||||
holder.bind(data.get(position).card);
|
||||
public void onBindViewHolder(LinkCardHolder<CardItem> holder, int position){
|
||||
holder.bind(data.get(position));
|
||||
super.onBindViewHolder(holder, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getImageCountForItem(int position){
|
||||
return data.get(position).imageRequest==null ? 0 : 1;
|
||||
return data.get(position).card.getImageCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||
return data.get(position).imageRequest;
|
||||
}
|
||||
}
|
||||
|
||||
private class CardLinksAdapter extends LinksAdapter{
|
||||
public CardLinksAdapter(ListImageLoaderWrapper imgLoader, List<CardViewModel> data){
|
||||
super(imgLoader, data);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public BaseLinkViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new LinkCardViewHolder();
|
||||
}
|
||||
}
|
||||
|
||||
private class BaseLinkViewHolder extends BindableViewHolder<Card> implements UsableRecyclerView.Clickable, ImageLoaderViewHolder{
|
||||
protected final TextView name, title;
|
||||
protected final ImageView photo;
|
||||
private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable();
|
||||
private boolean didClear;
|
||||
|
||||
public BaseLinkViewHolder(int layout){
|
||||
super(getActivity(), layout, list);
|
||||
name=findViewById(R.id.name);
|
||||
title=findViewById(R.id.title);
|
||||
photo=findViewById(R.id.photo);
|
||||
return data.get(position).card.getImageRequest(image);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(Card item){
|
||||
name.setText(item.providerName);
|
||||
title.setText(item.title);
|
||||
crossfadeDrawable.setSize(item.width, item.height);
|
||||
crossfadeDrawable.setBlurhashDrawable(item.blurhashPlaceholder);
|
||||
crossfadeDrawable.setCrossfadeAlpha(0f);
|
||||
photo.setImageDrawable(null);
|
||||
photo.setImageDrawable(crossfadeDrawable);
|
||||
didClear=false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(int index, Drawable drawable){
|
||||
crossfadeDrawable.setImageDrawable(drawable);
|
||||
if(didClear)
|
||||
crossfadeDrawable.animateAlpha(0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearImage(int index){
|
||||
crossfadeDrawable.setCrossfadeAlpha(1f);
|
||||
didClear=true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
UiUtils.launchWebBrowser(getActivity(), item.url);
|
||||
}
|
||||
}
|
||||
|
||||
private class LinkViewHolder extends BaseLinkViewHolder{
|
||||
public LinkViewHolder(){
|
||||
super(R.layout.item_trending_link);
|
||||
photo.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||
photo.setClipToOutline(true);
|
||||
}
|
||||
}
|
||||
|
||||
private class LinkCardViewHolder extends BaseLinkViewHolder{
|
||||
public LinkCardViewHolder(){
|
||||
super(R.layout.item_trending_link_card);
|
||||
itemView.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||
itemView.setClipToOutline(true);
|
||||
public int getItemViewType(int position){
|
||||
return position==0 ? 1 : 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,14 +57,13 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.fragments.CustomTransitionsFragment;
|
||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultViewModel> implements CustomTransitionsFragment, OnBackPressedListener{
|
||||
public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultViewModel> implements CustomTransitionsFragment{
|
||||
private static final Pattern HASHTAG_REGEX=Pattern.compile("^(\\w*[a-zA-Z·]\\w*)$", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern USERNAME_REGEX=Pattern.compile("^@?([a-z0-9_-]+)(@[^\\s]+)?$", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
@@ -371,6 +370,11 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
||||
container.invalidateOutline();
|
||||
navigationIcon.invalidateSelf();
|
||||
});
|
||||
if(!enter){
|
||||
String initialQuery=getArguments().getString("query");
|
||||
searchViewHelper.setQuery(TextUtils.isEmpty(initialQuery) ? "" : initialQuery);
|
||||
currentQuery=initialQuery;
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
@@ -437,14 +441,6 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
||||
Nav.finish(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed(){
|
||||
String initialQuery=getArguments().getString("query");
|
||||
searchViewHelper.setQuery(TextUtils.isEmpty(initialQuery) ? "" : initialQuery);
|
||||
currentQuery=initialQuery;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class AnimatableOutlineProvider extends ViewOutlineProvider{
|
||||
private float boundsFraction, radius;
|
||||
private final Rect boundsFrom, boundsTo;
|
||||
|
||||
@@ -73,8 +73,6 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
protected boolean isSignup;
|
||||
protected CatalogInstance fakeInstance=new CatalogInstance();
|
||||
|
||||
private static final double DUNBAR=Math.log(800);
|
||||
|
||||
public InstanceCatalogFragment(int layout, int perPage){
|
||||
super(layout, perPage);
|
||||
}
|
||||
@@ -155,7 +153,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
}
|
||||
|
||||
protected void loadInstanceInfo(String _domain, boolean isFromRedirect, Consumer<Object> onError){
|
||||
if(TextUtils.isEmpty(_domain))
|
||||
if(TextUtils.isEmpty(_domain) || _domain.indexOf('.')==-1)
|
||||
return;
|
||||
String domain=normalizeInstanceDomain(_domain);
|
||||
Instance cachedInstance=instancesCache.get(domain);
|
||||
@@ -316,8 +314,10 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
loadingInstanceRedirectRequest=null;
|
||||
loadingInstanceDomain=null;
|
||||
Activity a=getActivity();
|
||||
if(a==null)
|
||||
if(a==null) {
|
||||
response.close();
|
||||
return;
|
||||
}
|
||||
try(response){
|
||||
if(!response.isSuccessful()){
|
||||
a.runOnUiThread(()->{
|
||||
|
||||
@@ -5,8 +5,9 @@ import android.app.AlertDialog;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
@@ -26,16 +27,12 @@ import android.widget.PopupMenu;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||
import org.joinmastodon.android.api.requests.accounts.CheckInviteLink;
|
||||
import org.joinmastodon.android.api.requests.catalog.GetCatalogCategories;
|
||||
import org.joinmastodon.android.api.requests.catalog.GetCatalogInstances;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.catalog.CatalogCategory;
|
||||
import org.joinmastodon.android.model.catalog.CatalogInstance;
|
||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
@@ -49,11 +46,9 @@ import org.parceler.Parcels;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -63,17 +58,11 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class InstanceCatalogSignupFragment extends InstanceCatalogFragment implements OnBackPressedListener{
|
||||
private MastodonAPIRequest<?> getCategoriesRequest;
|
||||
private String currentCategory="all";
|
||||
private List<CatalogCategory> categories=new ArrayList<>();
|
||||
public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
||||
private View topBar;
|
||||
|
||||
private List<String> languages=Collections.emptyList();
|
||||
@@ -94,6 +83,8 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
private String inviteCode, inviteCodeHost;
|
||||
private AlertDialog currentInviteLinkAlert;
|
||||
|
||||
private Runnable exitQueryModeCallback=()->setSearchQueryMode(false);
|
||||
|
||||
public InstanceCatalogSignupFragment(){
|
||||
super(R.layout.fragment_onboarding_common, 10);
|
||||
}
|
||||
@@ -113,7 +104,7 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetCatalogInstances(null, null)
|
||||
currentRequest=new GetCatalogInstances(null, null, false)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<CatalogInstance> result){
|
||||
@@ -149,58 +140,17 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
}
|
||||
})
|
||||
.execNoAuth("");
|
||||
getCategoriesRequest=new GetCatalogCategories(null)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<CatalogCategory> result){
|
||||
getCategoriesRequest=null;
|
||||
CatalogCategory all=new CatalogCategory();
|
||||
all.category="all";
|
||||
categories.add(all);
|
||||
result.stream().sorted(Comparator.comparingInt((CatalogCategory cc)->cc.serversCount).reversed()).forEach(categories::add);
|
||||
updateCategories();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
getCategoriesRequest=null;
|
||||
error.showToast(getActivity());
|
||||
CatalogCategory all=new CatalogCategory();
|
||||
all.category="all";
|
||||
categories.add(all);
|
||||
updateCategories();
|
||||
}
|
||||
})
|
||||
.execNoAuth("");
|
||||
}
|
||||
|
||||
private void updateCategories(){
|
||||
// categoriesList.removeAllTabs();
|
||||
// for(CatalogCategory cat:categories){
|
||||
// int titleRes=getTitleForCategory(cat.category);
|
||||
// TabLayout.Tab tab=categoriesList.newTab().setText(titleRes!=0 ? getString(titleRes) : cat.category).setCustomView(R.layout.item_instance_category);
|
||||
// ImageView emoji=tab.getCustomView().findViewById(R.id.emoji);
|
||||
// emoji.setImageResource(getEmojiForCategory(cat.category));
|
||||
// categoriesList.addTab(tab);
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy(){
|
||||
removeBackCallback(exitQueryModeCallback);
|
||||
super.onDestroy();
|
||||
if(getCategoriesRequest!=null)
|
||||
getCategoriesRequest.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
View headerView=new View(getActivity());
|
||||
headerView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1));
|
||||
|
||||
mergeAdapter=new MergeRecyclerAdapter();
|
||||
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
||||
mergeAdapter.addAdapter(adapter=new InstancesAdapter());
|
||||
return mergeAdapter;
|
||||
return adapter=new InstancesAdapter();
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@@ -222,7 +172,16 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
setStatusBarColor(0);
|
||||
topBar=view.findViewById(R.id.top_bar);
|
||||
|
||||
list.addOnScrollListener(new ElevationOnScrollListener(null, topBar, buttonBar));
|
||||
list.addOnScrollListener(new ElevationOnScrollListener(null, topBar));
|
||||
if(buttonBar.getBackground() instanceof LayerDrawable ld){
|
||||
ld=(LayerDrawable) ld.mutate();
|
||||
buttonBar.setBackground(ld);
|
||||
Drawable overlay=ld.findDrawableByLayerId(R.id.color_overlay);
|
||||
if(overlay!=null){
|
||||
overlay.setAlpha(20);
|
||||
}
|
||||
}
|
||||
buttonBar.setElevation(V.dp(3));
|
||||
|
||||
searchEdit=view.findViewById(R.id.search_edit);
|
||||
searchEdit.setOnEditorActionListener(this::onSearchEnterPressed);
|
||||
@@ -572,6 +531,9 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
filteredData.add(instance);
|
||||
}
|
||||
}
|
||||
setEmptyText(getString(R.string.no_servers_found, currentSearchQuery));
|
||||
}else{
|
||||
setEmptyText("");
|
||||
}
|
||||
}else{
|
||||
for(CatalogInstance instance:data){
|
||||
@@ -591,27 +553,29 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
}
|
||||
}
|
||||
}
|
||||
DiffUtil.calculateDiff(new DiffUtil.Callback(){
|
||||
@Override
|
||||
public int getOldListSize(){
|
||||
return prevData.size();
|
||||
}
|
||||
UiUtils.updateRecyclerViewKeepingAbsoluteScrollPosition(list, ()->{
|
||||
DiffUtil.calculateDiff(new DiffUtil.Callback(){
|
||||
@Override
|
||||
public int getOldListSize(){
|
||||
return prevData.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNewListSize(){
|
||||
return filteredData.size();
|
||||
}
|
||||
@Override
|
||||
public int getNewListSize(){
|
||||
return filteredData.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition){
|
||||
return prevData.get(oldItemPosition)==filteredData.get(newItemPosition);
|
||||
}
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition){
|
||||
return prevData.get(oldItemPosition)==filteredData.get(newItemPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition){
|
||||
return prevData.get(oldItemPosition)==filteredData.get(newItemPosition);
|
||||
}
|
||||
}).dispatchUpdatesTo(adapter);
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition){
|
||||
return prevData.get(oldItemPosition)==filteredData.get(newItemPosition);
|
||||
}
|
||||
}).dispatchUpdatesTo(adapter);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -620,19 +584,13 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed(){
|
||||
if(searchQueryMode){
|
||||
setSearchQueryMode(false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void setSearchQueryMode(boolean enabled){
|
||||
if(searchQueryMode==enabled)
|
||||
return;
|
||||
searchQueryMode=enabled;
|
||||
RelativeLayout.LayoutParams lp=(RelativeLayout.LayoutParams) searchEdit.getLayoutParams();
|
||||
if(searchQueryMode){
|
||||
addBackCallback(exitQueryModeCallback);
|
||||
filtersScroll.setVisibility(View.GONE);
|
||||
lp.removeRule(RelativeLayout.END_OF);
|
||||
backBtn.setScaleX(0.83333333f);
|
||||
@@ -640,6 +598,7 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
backBtn.setTranslationX(V.dp(8));
|
||||
searchEdit.setCompoundDrawableTintList(ColorStateList.valueOf(0));
|
||||
}else{
|
||||
removeBackCallback(exitQueryModeCallback);
|
||||
filtersScroll.setVisibility(View.VISIBLE);
|
||||
focusThing.requestFocus();
|
||||
searchEdit.setText("");
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -65,7 +64,7 @@ public class InstanceChooserLoginFragment extends InstanceCatalogFragment{
|
||||
protected void updateFilteredList(){
|
||||
ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData);
|
||||
filteredData.clear();
|
||||
if(currentSearchQuery.length()>0){
|
||||
if(!TextUtils.isEmpty(currentSearchQuery)){
|
||||
boolean foundExactMatch=false;
|
||||
for(CatalogInstance inst:data){
|
||||
if(inst.normalizedDomain.contains(currentSearchQuery)){
|
||||
@@ -74,9 +73,16 @@ public class InstanceChooserLoginFragment extends InstanceCatalogFragment{
|
||||
foundExactMatch=true;
|
||||
}
|
||||
}
|
||||
if(!foundExactMatch)
|
||||
if(!foundExactMatch && currentSearchQuery.indexOf('.')!=-1)
|
||||
filteredData.add(0, fakeInstance);
|
||||
}
|
||||
if(filteredData.isEmpty()){
|
||||
for(CatalogInstance inst:data){
|
||||
if(inst.normalizedDomain.equals("mastodon.social") || inst.normalizedDomain.equals("mastodon.online")){
|
||||
filteredData.add(inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
UiUtils.updateList(prevData, filteredData, list, adapter, Objects::equals);
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
list.getChildAt(i).invalidateOutline();
|
||||
@@ -90,12 +96,15 @@ public class InstanceChooserLoginFragment extends InstanceCatalogFragment{
|
||||
|
||||
private void loadAutocompleteServers(){
|
||||
loadedAutocomplete=true;
|
||||
new GetCatalogInstances(null, null)
|
||||
new GetCatalogInstances(null, null, true)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<CatalogInstance> result){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
data.clear();
|
||||
data.addAll(sortInstances(result));
|
||||
updateFilteredList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -112,6 +121,9 @@ public class InstanceChooserLoginFragment extends InstanceCatalogFragment{
|
||||
Toolbar toolbar=getToolbar();
|
||||
toolbar.setElevation(0);
|
||||
toolbar.setBackground(null);
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
|
||||
toolbar.setContentInsetStartWithNavigation(V.dp(80));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -50,6 +50,7 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -62,6 +63,7 @@ import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
|
||||
public class SignupFragment extends ToolbarFragment{
|
||||
private static final String TAG="SignupFragment";
|
||||
private final Pattern emailRegex=Pattern.compile("^[^@]+@[^@]+\\.[^@]{2,}$");
|
||||
|
||||
private Instance instance;
|
||||
|
||||
@@ -97,6 +99,7 @@ public class SignupFragment extends ToolbarFragment{
|
||||
View view=inflater.inflate(R.layout.fragment_onboarding_signup, container, false);
|
||||
|
||||
TextView domain=view.findViewById(R.id.domain);
|
||||
TextView atSign=view.findViewById(R.id.at_sign);
|
||||
displayName=view.findViewById(R.id.display_name);
|
||||
username=view.findViewById(R.id.username);
|
||||
email=view.findViewById(R.id.email);
|
||||
@@ -118,7 +121,7 @@ public class SignupFragment extends ToolbarFragment{
|
||||
@Override
|
||||
public boolean onPreDraw(){
|
||||
username.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
username.setPadding(username.getPaddingLeft(), username.getPaddingTop(), domain.getWidth(), username.getPaddingBottom());
|
||||
username.setPadding(atSign.getWidth(), username.getPaddingTop(), domain.getWidth(), username.getPaddingBottom());
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@@ -145,6 +148,10 @@ public class SignupFragment extends ToolbarFragment{
|
||||
reasonExplain.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
password.setOnFocusChangeListener(this::onPasswordFieldFocusChange);
|
||||
passwordConfirm.setOnFocusChangeListener(this::onPasswordFieldFocusChange);
|
||||
email.setOnFocusChangeListener(this::onEmailFieldFocusChange);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -281,34 +288,44 @@ public class SignupFragment extends ToolbarFragment{
|
||||
.exec(instance.uri, apiToken);
|
||||
}
|
||||
|
||||
private CharSequence makeLinkInErrorMessage(String source, LinkSpan.OnLinkClickListener onClick){
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder();
|
||||
Jsoup.parseBodyFragment(source).body().traverse(new NodeVisitor(){
|
||||
private int spanStart;
|
||||
@Override
|
||||
public void head(Node node, int depth){
|
||||
if(node instanceof TextNode tn){
|
||||
ssb.append(tn.text());
|
||||
}else if(node instanceof Element){
|
||||
spanStart=ssb.length();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tail(Node node, int depth){
|
||||
if(node instanceof Element){
|
||||
ssb.setSpan(new LinkSpan("", onClick, LinkSpan.Type.CUSTOM, null, null, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
ssb.setSpan(new TypefaceSpan("sans-serif-medium"), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
});
|
||||
return ssb;
|
||||
}
|
||||
|
||||
private CharSequence getErrorDescription(MastodonDetailedErrorResponse.FieldError error, String fieldName){
|
||||
return switch(fieldName){
|
||||
case "email" -> switch(error.error){
|
||||
case "ERR_BLOCKED" -> {
|
||||
String emailAddr=email.getText().toString();
|
||||
String s=getResources().getString(R.string.signup_email_domain_blocked, TextUtils.htmlEncode(instance.uri), TextUtils.htmlEncode(emailAddr.substring(emailAddr.lastIndexOf('@')+1)));
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder();
|
||||
Jsoup.parseBodyFragment(s).body().traverse(new NodeVisitor(){
|
||||
private int spanStart;
|
||||
@Override
|
||||
public void head(Node node, int depth){
|
||||
if(node instanceof TextNode tn){
|
||||
ssb.append(tn.text());
|
||||
}else if(node instanceof Element){
|
||||
spanStart=ssb.length();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tail(Node node, int depth){
|
||||
if(node instanceof Element){
|
||||
ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null, null, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
ssb.setSpan(new TypefaceSpan("sans-serif-medium"), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
});
|
||||
yield ssb;
|
||||
yield makeLinkInErrorMessage(s, this::onGoBackLinkClick);
|
||||
}
|
||||
case "ERR_INVALID" -> getString(R.string.signup_email_invalid);
|
||||
case "ERR_TAKEN" -> makeLinkInErrorMessage(getString(R.string.signup_email_taken), this::onForgotPasswordLinkClick);
|
||||
default -> error.description;
|
||||
};
|
||||
case "username" -> switch(error.error){
|
||||
case "ERR_TAKEN" -> makeLinkInErrorMessage(getString(R.string.signup_username_taken), this::onGoBackLinkClick);
|
||||
default -> error.description;
|
||||
};
|
||||
default -> error.description;
|
||||
@@ -345,7 +362,9 @@ public class SignupFragment extends ToolbarFragment{
|
||||
}
|
||||
|
||||
private void updateButtonState(){
|
||||
btn.setEnabled(username.length()>0 && email.length()>0 && email.getText().toString().contains("@") && password.length()>=8 && passwordConfirm.length()>=8 && (!instance.approvalRequired || reason.length()>0));
|
||||
btn.setEnabled(username.length()>0 && email.length()>0 && emailRegex.matcher(email.getText()).find()
|
||||
&& password.length()>=8 && passwordConfirm.length()>=8 && password.getText().toString().equals(passwordConfirm.getText().toString())
|
||||
&& (!instance.approvalRequired || reason.length()>0));
|
||||
}
|
||||
|
||||
private void createAppAndGetToken(){
|
||||
@@ -406,6 +425,24 @@ public class SignupFragment extends ToolbarFragment{
|
||||
Nav.finish(this);
|
||||
}
|
||||
|
||||
private void onForgotPasswordLinkClick(LinkSpan span){
|
||||
UiUtils.launchWebBrowser(getActivity(), "https://"+instance.uri+"/auth/password/new");
|
||||
}
|
||||
|
||||
private void onPasswordFieldFocusChange(View v, boolean hasFocus){
|
||||
if(hasFocus || password.length()==0 || passwordConfirm.length()==0)
|
||||
return;
|
||||
if(!password.getText().toString().equals(passwordConfirm.getText().toString())){
|
||||
passwordConfirmWrap.setErrorState(getString(R.string.signup_passwords_dont_match));
|
||||
}
|
||||
}
|
||||
|
||||
private void onEmailFieldFocusChange(View v, boolean hasFocus){
|
||||
if(!hasFocus && email.length()>0 && !emailRegex.matcher(email.getText()).find()){
|
||||
emailWrap.setErrorState(getString(R.string.signup_email_invalid));
|
||||
}
|
||||
}
|
||||
|
||||
private class ErrorClearingListener implements TextWatcher{
|
||||
public final EditText editText;
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.joinmastodon.android.model.FilterKeyword;
|
||||
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
|
||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout;
|
||||
import org.parceler.Parcels;
|
||||
@@ -44,11 +45,10 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
|
||||
public class EditFilterFragment extends BaseSettingsFragment<Void> implements OnBackPressedListener{
|
||||
public class EditFilterFragment extends BaseSettingsFragment<Void>{
|
||||
private static final int WORDS_RESULT=370;
|
||||
private static final int CONTEXT_RESULT=651;
|
||||
|
||||
@@ -63,6 +63,13 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
|
||||
private ArrayList<String> deletedWordIDs=new ArrayList<>();
|
||||
private EnumSet<FilterContext> context=EnumSet.allOf(FilterContext.class);
|
||||
private boolean dirty;
|
||||
private boolean wasDirty;
|
||||
|
||||
private Runnable confirmCallback=()->{
|
||||
if(isDirty()){
|
||||
UiUtils.showConfirmationAlert(getActivity(), R.string.discard_changes, 0, R.string.discard, ()->Nav.finish(this));
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -90,6 +97,12 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
|
||||
setRetainInstance(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy(){
|
||||
removeBackCallback(confirmCallback);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){}
|
||||
|
||||
@@ -101,6 +114,7 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
|
||||
titleEditLayout.updateHint();
|
||||
if(filter!=null)
|
||||
titleEdit.setText(filter.title);
|
||||
titleEdit.addTextChangedListener(new SimpleTextWatcher(e->updateBackCallback()));
|
||||
|
||||
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
|
||||
adapter.addAdapter(new SingleViewRecyclerAdapter(titleEditLayout));
|
||||
@@ -158,6 +172,7 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
|
||||
}
|
||||
a.dismiss();
|
||||
}
|
||||
updateBackCallback();
|
||||
})
|
||||
.show();
|
||||
alert.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
@@ -309,6 +324,7 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
|
||||
}
|
||||
deletedWordIDs.addAll(result.getStringArrayList("deleted"));
|
||||
}
|
||||
updateBackCallback();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,11 +333,19 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed(){
|
||||
if(isDirty()){
|
||||
UiUtils.showConfirmationAlert(getActivity(), R.string.discard_changes, 0, R.string.discard, ()->Nav.finish(this));
|
||||
return true;
|
||||
protected void toggleCheckableItem(ListItem<?> item){
|
||||
super.toggleCheckableItem(item);
|
||||
updateBackCallback();
|
||||
}
|
||||
|
||||
private void updateBackCallback(){
|
||||
boolean dirty=isDirty();
|
||||
if(dirty!=wasDirty){
|
||||
wasDirty=dirty;
|
||||
if(dirty)
|
||||
addBackCallback(confirmCallback);
|
||||
else
|
||||
removeBackCallback(confirmCallback);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,7 @@ import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
|
||||
public class FilterContextFragment extends BaseSettingsFragment<FilterContext> implements OnBackPressedListener{
|
||||
public class FilterContextFragment extends BaseSettingsFragment<FilterContext>{
|
||||
private EnumSet<FilterContext> context;
|
||||
|
||||
@Override
|
||||
@@ -33,7 +31,8 @@ public class FilterContextFragment extends BaseSettingsFragment<FilterContext> i
|
||||
protected void doLoadData(int offset, int count){}
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed(){
|
||||
public void onStop(){
|
||||
super.onStop();
|
||||
context=EnumSet.noneOf(FilterContext.class);
|
||||
for(ListItem<FilterContext> item:data){
|
||||
if(((CheckableListItem<FilterContext>) item).checked)
|
||||
@@ -42,6 +41,5 @@ public class FilterContextFragment extends BaseSettingsFragment<FilterContext> i
|
||||
Bundle args=new Bundle();
|
||||
args.putSerializable("context", context);
|
||||
setResult(true, args);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.joinmastodon.android.fragments.settings;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.text.InputType;
|
||||
@@ -11,11 +10,9 @@ import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.FilterKeyword;
|
||||
@@ -33,15 +30,15 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> implements OnBackPressedListener{
|
||||
public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword>{
|
||||
private Button fab;
|
||||
private ActionMode actionMode;
|
||||
private ArrayList<ListItem<FilterKeyword>> selectedItems=new ArrayList<>();
|
||||
private ArrayList<String> deletedItemIDs=new ArrayList<>();
|
||||
private MenuItem deleteItem;
|
||||
private Runnable actionModeDismisser=()->actionMode.finish();
|
||||
|
||||
public FilterWordsFragment(){
|
||||
setListLayoutId(R.layout.recycler_fragment_with_text_fab);
|
||||
@@ -80,12 +77,12 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed(){
|
||||
public void onStop(){
|
||||
super.onStop();
|
||||
Bundle result=new Bundle();
|
||||
result.putParcelableArrayList("words", (ArrayList<? extends Parcelable>) data.stream().map(i->i.parentObject).map(Parcels::wrap).collect(Collectors.toCollection(ArrayList::new)));
|
||||
result.putStringArrayList("deleted", deletedItemIDs);
|
||||
setResult(true, result);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -259,6 +256,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
|
||||
}
|
||||
itemsAdapter.notifyItemRangeChanged(0, data.size());
|
||||
updateActionModeTitle();
|
||||
addBackCallback(actionModeDismisser);
|
||||
}
|
||||
|
||||
private void leaveSelectionMode(boolean fromActionMode){
|
||||
@@ -280,6 +278,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
|
||||
data.set(i, newItem);
|
||||
}
|
||||
itemsAdapter.notifyItemRangeChanged(0, data.size());
|
||||
removeBackCallback(actionModeDismisser);
|
||||
}
|
||||
|
||||
private void updateActionModeTitle(){
|
||||
|
||||
@@ -100,15 +100,19 @@ public class SettingsServerAboutFragment extends LoaderFragment{
|
||||
scroller.setClipToPadding(false);
|
||||
scroller.addView(scrollingLayout);
|
||||
|
||||
FixedAspectRatioImageView banner=new FixedAspectRatioImageView(getActivity());
|
||||
banner.setAspectRatio(1.914893617f);
|
||||
banner.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
||||
banner.setOutlineProvider(OutlineProviders.bottomRoundedRect(16));
|
||||
banner.setClipToOutline(true);
|
||||
ViewImageLoader.loadWithoutAnimation(banner, getResources().getDrawable(R.drawable.image_placeholder, getActivity().getTheme()), new UrlImageLoaderRequest(instance.thumbnail));
|
||||
LinearLayout.LayoutParams blp=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
blp.bottomMargin=V.dp(24);
|
||||
scrollingLayout.addView(banner, blp);
|
||||
if(!TextUtils.isEmpty(instance.thumbnail)){
|
||||
FixedAspectRatioImageView banner=new FixedAspectRatioImageView(getActivity());
|
||||
banner.setAspectRatio(1.914893617f);
|
||||
banner.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
||||
banner.setOutlineProvider(OutlineProviders.bottomRoundedRect(16));
|
||||
banner.setClipToOutline(true);
|
||||
ViewImageLoader.loadWithoutAnimation(banner, getResources().getDrawable(R.drawable.image_placeholder, getActivity().getTheme()), new UrlImageLoaderRequest(instance.thumbnail));
|
||||
LinearLayout.LayoutParams blp=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
blp.bottomMargin=V.dp(24);
|
||||
scrollingLayout.addView(banner, blp);
|
||||
}else{
|
||||
scrollingLayout.setPadding(0, V.dp(24), 0, 0);
|
||||
}
|
||||
|
||||
boolean needDivider=false;
|
||||
if(instance.contactAccount!=null){
|
||||
|
||||
@@ -36,6 +36,8 @@ public class Card extends BaseModel{
|
||||
public String blurhash;
|
||||
public List<History> history;
|
||||
public Instant publishedAt;
|
||||
public Account authorAccount;
|
||||
public List<Author> authors;
|
||||
|
||||
public transient Drawable blurhashPlaceholder;
|
||||
|
||||
@@ -49,6 +51,13 @@ public class Card extends BaseModel{
|
||||
if(placeholder!=null)
|
||||
blurhashPlaceholder=new BlurHashDrawable(placeholder, width, height);
|
||||
}
|
||||
if(authorAccount!=null)
|
||||
authorAccount.postprocess();
|
||||
if(authors!=null){
|
||||
for(Author a:authors){
|
||||
a.postprocess();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,4 +91,19 @@ public class Card extends BaseModel{
|
||||
@SerializedName("rich")
|
||||
RICH
|
||||
}
|
||||
|
||||
@Parcel
|
||||
public static class Author extends BaseModel{
|
||||
@RequiredField
|
||||
public String name;
|
||||
public String url;
|
||||
public Account account;
|
||||
|
||||
@Override
|
||||
public void postprocess() throws ObjectValidationException{
|
||||
super.postprocess();
|
||||
if(account!=null)
|
||||
account.postprocess();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import androidx.annotation.StringRes;
|
||||
public class PushNotification extends BaseModel{
|
||||
public String accessToken;
|
||||
public String preferredLocale;
|
||||
public long notificationId;
|
||||
public String notificationId;
|
||||
@RequiredField
|
||||
public Type notificationType;
|
||||
@RequiredField
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
||||
import org.joinmastodon.android.api.ObjectValidationException;
|
||||
import org.joinmastodon.android.api.RequiredField;
|
||||
import org.joinmastodon.android.model.BaseModel;
|
||||
|
||||
import java.net.IDN;
|
||||
@@ -15,14 +16,18 @@ import java.util.List;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
@AllFieldsAreRequired
|
||||
public class CatalogInstance extends BaseModel{
|
||||
@RequiredField
|
||||
public String domain;
|
||||
@RequiredField
|
||||
public String version;
|
||||
@RequiredField
|
||||
public String description;
|
||||
@RequiredField
|
||||
public List<String> languages;
|
||||
@SerializedName("region")
|
||||
private String _region;
|
||||
@RequiredField
|
||||
public List<String> categories;
|
||||
public String proxiedThumbnail;
|
||||
public int totalUsers;
|
||||
|
||||
@@ -1,19 +1,59 @@
|
||||
package org.joinmastodon.android.model.viewmodel;
|
||||
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Card;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class CardViewModel{
|
||||
public final Object parentObject;
|
||||
public final Card card;
|
||||
public final ImageLoaderRequest imageRequest;
|
||||
public final UrlImageLoaderRequest authorAvaRequest;
|
||||
public final SpannableStringBuilder parsedAuthorName;
|
||||
public final CustomEmojiHelper authorNameEmojiHelper=new CustomEmojiHelper();
|
||||
|
||||
public CardViewModel(Card card, int width, int height){
|
||||
public CardViewModel(Card card, int width, int height, Object parentObject, String accountID){
|
||||
this.card=card;
|
||||
this.parentObject=parentObject;
|
||||
this.imageRequest=TextUtils.isEmpty(card.image) ? null : new UrlImageLoaderRequest(card.image, V.dp(width), V.dp(height));
|
||||
|
||||
Account authorAccount;
|
||||
if(card.authors!=null && !card.authors.isEmpty() && card.authors.get(0).account!=null)
|
||||
authorAccount=card.authors.get(0).account;
|
||||
else
|
||||
authorAccount=card.authorAccount;
|
||||
|
||||
if(authorAccount!=null){
|
||||
parsedAuthorName=new SpannableStringBuilder(authorAccount.displayName);
|
||||
if(AccountSessionManager.get(accountID).getLocalPreferences().customEmojiInNames)
|
||||
HtmlParser.parseCustomEmoji(parsedAuthorName, authorAccount.emojis);
|
||||
authorNameEmojiHelper.setText(parsedAuthorName);
|
||||
authorAvaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? authorAccount.avatar : authorAccount.avatarStatic, V.dp(50), V.dp(50));
|
||||
}else{
|
||||
parsedAuthorName=null;
|
||||
authorAvaRequest=null;
|
||||
}
|
||||
}
|
||||
|
||||
public int getImageCount(){
|
||||
return 1+(card.authorAccount!=null ? (1+authorNameEmojiHelper.getImageCount()) : 0);
|
||||
}
|
||||
|
||||
public ImageLoaderRequest getImageRequest(int index){
|
||||
return switch(index){
|
||||
case 0 -> imageRequest;
|
||||
case 1 -> authorAvaRequest;
|
||||
default -> authorNameEmojiHelper.getImageRequest(index-2);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,12 @@ public class ListItem<T>{
|
||||
this.subtitleRes=subtitleRes;
|
||||
}
|
||||
|
||||
public ListItem(@StringRes int titleRes, @StringRes int subtitleRes, @DrawableRes int iconRes, T parentObject, Consumer<ListItem<T>> onClick){
|
||||
this(null, null, iconRes, onClick, parentObject, 0, false);
|
||||
this.titleRes=titleRes;
|
||||
this.subtitleRes=subtitleRes;
|
||||
}
|
||||
|
||||
public ListItem(@StringRes int titleRes, @StringRes int subtitleRes, @DrawableRes int iconRes, Consumer<ListItem<T>> onClick, int colorOverrideAttr, boolean dividerAfter){
|
||||
this(null, null, iconRes, onClick, null, colorOverrideAttr, dividerAfter);
|
||||
this.titleRes=titleRes;
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.joinmastodon.android.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.PopupWindow;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||
import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter;
|
||||
import org.joinmastodon.android.ui.viewholders.ListItemViewHolder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class ExtendedPopupMenu extends PopupWindow{
|
||||
private UsableRecyclerView list;
|
||||
|
||||
public <T> ExtendedPopupMenu(Context context, List<ListItem<T>> items){
|
||||
super(context, null, 0, R.style.Widget_Mastodon_PopupMenu);
|
||||
setWidth(V.dp(200));
|
||||
setElevation(V.dp(3));
|
||||
setOutsideTouchable(true);
|
||||
setFocusable(true);
|
||||
setInputMethodMode(INPUT_METHOD_NOT_NEEDED);
|
||||
list=new UsableRecyclerView(context);
|
||||
list.setLayoutManager(new LinearLayoutManager(context));
|
||||
list.setAdapter(new ReducedPaddingItemsAdapter<>(items));
|
||||
list.setClipToPadding(false);
|
||||
setContentView(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity){
|
||||
super.showAsDropDown(anchor, xoff, yoff, gravity);
|
||||
View bgView=(View) list.getParent();
|
||||
list.setPadding(0, bgView.getPaddingTop(), 0, bgView.getPaddingBottom());
|
||||
bgView.setPadding(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
private static class ReducedPaddingItemsAdapter<T> extends GenericListItemsAdapter<T>{
|
||||
public ReducedPaddingItemsAdapter(List<ListItem<T>> listItems){
|
||||
super(listItems);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ListItemViewHolder<?> onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
ListItemViewHolder<?> holder=super.onCreateViewHolder(parent, viewType);
|
||||
int padH=V.dp(12), padV=V.dp(8);
|
||||
holder.itemView.setPadding(padH, padV, padH, padV);
|
||||
View icon=holder.itemView.findViewById(R.id.icon);
|
||||
((ViewGroup.MarginLayoutParams)icon.getLayoutParams()).setMarginEnd(padH);
|
||||
return holder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
@@ -19,6 +20,9 @@ import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||
import org.joinmastodon.android.fragments.account_list.StatusFavoritesListFragment;
|
||||
import org.joinmastodon.android.fragments.account_list.StatusReblogsListFragment;
|
||||
import org.joinmastodon.android.fragments.account_list.StatusRelatedAccountListFragment;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
@@ -48,6 +52,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
private final ImageView share;
|
||||
private final ColorStateList buttonColors;
|
||||
private final View replyBtn, boostBtn, favoriteBtn, shareBtn;
|
||||
private final PopupMenu boostLongTapMenu, favoriteLongTapMenu;
|
||||
|
||||
private final View.AccessibilityDelegate buttonAccessibilityDelegate=new View.AccessibilityDelegate(){
|
||||
@Override
|
||||
@@ -97,11 +102,20 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
replyBtn.setOnClickListener(this::onReplyClick);
|
||||
replyBtn.setAccessibilityDelegate(buttonAccessibilityDelegate);
|
||||
boostBtn.setOnClickListener(this::onBoostClick);
|
||||
boostBtn.setOnLongClickListener(this::onBoostLongClick);
|
||||
boostBtn.setAccessibilityDelegate(buttonAccessibilityDelegate);
|
||||
favoriteBtn.setOnClickListener(this::onFavoriteClick);
|
||||
favoriteBtn.setOnLongClickListener(this::onFavoriteLongClick);
|
||||
favoriteBtn.setAccessibilityDelegate(buttonAccessibilityDelegate);
|
||||
shareBtn.setOnClickListener(this::onShareClick);
|
||||
shareBtn.setAccessibilityDelegate(buttonAccessibilityDelegate);
|
||||
|
||||
favoriteLongTapMenu=new PopupMenu(activity, favoriteBtn);
|
||||
favoriteLongTapMenu.inflate(R.menu.favorite_longtap);
|
||||
favoriteLongTapMenu.setOnMenuItemClickListener(this::onLongTapMenuItemSelected);
|
||||
boostLongTapMenu=new PopupMenu(activity, boostBtn);
|
||||
boostLongTapMenu.inflate(R.menu.boost_longtap);
|
||||
boostLongTapMenu.setOnMenuItemClickListener(this::onLongTapMenuItemSelected);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -172,6 +186,45 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
UiUtils.openSystemShareSheet(v.getContext(), item.status);
|
||||
}
|
||||
|
||||
private boolean onBoostLongClick(View v){
|
||||
MenuItem boost=boostLongTapMenu.getMenu().findItem(R.id.boost);
|
||||
boost.setTitle(item.status.reblogged ? R.string.undo_reblog : R.string.button_reblog);
|
||||
boostLongTapMenu.show();
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean onFavoriteLongClick(View v){
|
||||
MenuItem favorite=favoriteLongTapMenu.getMenu().findItem(R.id.favorite);
|
||||
MenuItem bookmark=favoriteLongTapMenu.getMenu().findItem(R.id.bookmark);
|
||||
favorite.setTitle(item.status.favourited ? R.string.undo_favorite : R.string.button_favorite);
|
||||
bookmark.setTitle(item.status.bookmarked ? R.string.remove_bookmark : R.string.add_bookmark);
|
||||
favoriteLongTapMenu.show();
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean onLongTapMenuItemSelected(MenuItem item){
|
||||
int id=item.getItemId();
|
||||
if(id==R.id.favorite){
|
||||
onFavoriteClick(null);
|
||||
}else if(id==R.id.boost){
|
||||
onBoostClick(null);
|
||||
}else if(id==R.id.bookmark){
|
||||
AccountSessionManager.getInstance().getAccount(this.item.accountID).getStatusInteractionController().setBookmarked(this.item.status, !this.item.status.bookmarked);
|
||||
}else if(id==R.id.view_favorites){
|
||||
startAccountListFragment(StatusFavoritesListFragment.class);
|
||||
}else if(id==R.id.view_boosts){
|
||||
startAccountListFragment(StatusReblogsListFragment.class);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void startAccountListFragment(Class<? extends StatusRelatedAccountListFragment> cls){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", item.parentFragment.getAccountID());
|
||||
args.putParcelable("status", Parcels.wrap(item.status));
|
||||
Nav.go(item.parentFragment.getActivity(), cls, args);
|
||||
}
|
||||
|
||||
private int descriptionForId(int id){
|
||||
if(id==R.id.reply_btn)
|
||||
return R.string.button_reply;
|
||||
|
||||
@@ -119,6 +119,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
private final TextView name, timeAndUsername, extraText;
|
||||
private final ImageView avatar, more;
|
||||
private final PopupMenu optionsMenu;
|
||||
private final View clickableThing;
|
||||
|
||||
public Holder(Activity activity, ViewGroup parent){
|
||||
this(activity, R.layout.display_item_header, parent);
|
||||
@@ -131,14 +132,16 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
avatar=findViewById(R.id.avatar);
|
||||
more=findViewById(R.id.more);
|
||||
extraText=findViewById(R.id.extra_text);
|
||||
avatar.setOnClickListener(this::onAvaClick);
|
||||
clickableThing=findViewById(R.id.clickable_thing);
|
||||
if(clickableThing!=null)
|
||||
clickableThing.setOnClickListener(this::onAvaClick);
|
||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(10));
|
||||
avatar.setClipToOutline(true);
|
||||
more.setOnClickListener(this::onMoreClick);
|
||||
|
||||
optionsMenu=new PopupMenu(activity, more);
|
||||
optionsMenu.inflate(R.menu.post);
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P && !UiUtils.isEMUI())
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P && !UiUtils.isEMUI() && !UiUtils.isMagic())
|
||||
optionsMenu.getMenu().setGroupDividerEnabled(true);
|
||||
optionsMenu.setOnMenuItemClickListener(menuItem->{
|
||||
Account account=item.user;
|
||||
@@ -262,7 +265,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
time=item.parentFragment.getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(itemView.getContext(), item.status.editedAt));
|
||||
|
||||
timeAndUsername.setText(time+" · @"+item.user.acct);
|
||||
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : 0);
|
||||
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : V.dp(4));
|
||||
if(TextUtils.isEmpty(item.extraText)){
|
||||
extraText.setVisibility(View.GONE);
|
||||
}else{
|
||||
@@ -270,8 +273,10 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
extraText.setText(item.extraText);
|
||||
}
|
||||
more.setVisibility(item.inset ? View.GONE : View.VISIBLE);
|
||||
avatar.setClickable(!item.inset);
|
||||
avatar.setContentDescription(item.parentFragment.getString(R.string.avatar_description, item.user.acct));
|
||||
if(clickableThing!=null){
|
||||
clickableThing.setClickable(!item.inset);
|
||||
clickableThing.setContentDescription(item.parentFragment.getString(R.string.avatar_description, item.user.acct));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,143 +1,55 @@
|
||||
package org.joinmastodon.android.ui.displayitems;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.app.Activity;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.model.Card;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.model.viewmodel.CardViewModel;
|
||||
import org.joinmastodon.android.ui.viewholders.LinkCardHolder;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
|
||||
public class LinkCardStatusDisplayItem extends StatusDisplayItem{
|
||||
public class LinkCardStatusDisplayItem extends StatusDisplayItem implements LinkCardHolder.LinkCardProvider{
|
||||
private final Status status;
|
||||
private final UrlImageLoaderRequest imgRequest;
|
||||
private final CardViewModel cardViewModel;
|
||||
|
||||
public LinkCardStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Status status){
|
||||
super(parentID, parentFragment);
|
||||
this.status=status;
|
||||
if(status.card.image!=null)
|
||||
imgRequest=new UrlImageLoaderRequest(status.card.image, 1000, 1000);
|
||||
else
|
||||
imgRequest=null;
|
||||
int size=shouldUseLargeCard() ? 1000 : 192;
|
||||
cardViewModel=new CardViewModel(status.card, size, size, status, parentFragment.getAccountID());
|
||||
}
|
||||
|
||||
private boolean shouldUseLargeCard(){
|
||||
return status.card.type==Card.Type.VIDEO || (status.card.image!=null && status.card.width>status.card.height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType(){
|
||||
return status.card.type==Card.Type.VIDEO || (status.card.image!=null && status.card.width>status.card.height) ? Type.CARD_LARGE : Type.CARD_COMPACT;
|
||||
return shouldUseLargeCard() ? Type.CARD_LARGE : Type.CARD_COMPACT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getImageCount(){
|
||||
return imgRequest==null ? 0 : 1;
|
||||
return cardViewModel.getImageCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageLoaderRequest getImageRequest(int index){
|
||||
return imgRequest;
|
||||
return cardViewModel.getImageRequest(index);
|
||||
}
|
||||
|
||||
public static class Holder extends StatusDisplayItem.Holder<LinkCardStatusDisplayItem> implements ImageLoaderViewHolder{
|
||||
private final TextView title, description, domain, timestamp;
|
||||
private final ImageView photo;
|
||||
private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable();
|
||||
private boolean didClear;
|
||||
private final View inner;
|
||||
private final boolean isLarge;
|
||||
@Override
|
||||
public CardViewModel getCard(){
|
||||
return cardViewModel;
|
||||
}
|
||||
|
||||
public Holder(Context context, ViewGroup parent, boolean isLarge){
|
||||
super(context, isLarge ? R.layout.display_item_link_card : R.layout.display_item_link_card_compact, parent);
|
||||
this.isLarge=isLarge;
|
||||
title=findViewById(R.id.title);
|
||||
description=findViewById(R.id.description);
|
||||
domain=findViewById(R.id.domain);
|
||||
timestamp=findViewById(R.id.timestamp);
|
||||
photo=findViewById(R.id.photo);
|
||||
inner=findViewById(R.id.inner);
|
||||
inner.setOnClickListener(this::onClick);
|
||||
inner.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||
inner.setClipToOutline(true);
|
||||
}
|
||||
public static class Holder extends LinkCardHolder<LinkCardStatusDisplayItem>{
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@Override
|
||||
public void onBind(LinkCardStatusDisplayItem item){
|
||||
Card card=item.status.card;
|
||||
title.setText(card.title);
|
||||
if(description!=null){
|
||||
description.setText(card.description);
|
||||
description.setVisibility(TextUtils.isEmpty(card.description) ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
String cardDomain=HtmlParser.normalizeDomain(Objects.requireNonNull(Uri.parse(card.url).getHost()));
|
||||
if(isLarge && !TextUtils.isEmpty(card.authorName)){
|
||||
domain.setText(itemView.getContext().getString(R.string.article_by_author, card.authorName)+" · "+cardDomain);
|
||||
}else{
|
||||
domain.setText(cardDomain);
|
||||
}
|
||||
if(card.publishedAt!=null){
|
||||
timestamp.setVisibility(View.VISIBLE);
|
||||
timestamp.setText(" · "+UiUtils.formatRelativeTimestamp(itemView.getContext(), card.publishedAt));
|
||||
}else{
|
||||
timestamp.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
photo.setImageDrawable(null);
|
||||
if(item.imgRequest!=null){
|
||||
photo.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
||||
photo.setBackground(null);
|
||||
photo.setImageTintList(null);
|
||||
crossfadeDrawable.setSize(card.width, card.height);
|
||||
crossfadeDrawable.setBlurhashDrawable(card.blurhashPlaceholder);
|
||||
crossfadeDrawable.setCrossfadeAlpha(0f);
|
||||
photo.setImageDrawable(null);
|
||||
photo.setImageDrawable(crossfadeDrawable);
|
||||
didClear=false;
|
||||
}else{
|
||||
photo.setBackgroundColor(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3SurfaceVariant));
|
||||
photo.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3Outline)));
|
||||
photo.setScaleType(ImageView.ScaleType.CENTER);
|
||||
photo.setImageResource(R.drawable.ic_feed_48px);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(int index, Drawable drawable){
|
||||
crossfadeDrawable.setImageDrawable(drawable);
|
||||
if(didClear)
|
||||
crossfadeDrawable.animateAlpha(0f);
|
||||
Card card=item.status.card;
|
||||
// Make sure the image is not stretched if the server returned wrong dimensions
|
||||
if(drawable!=null && (drawable.getIntrinsicWidth()!=card.width || drawable.getIntrinsicHeight()!=card.height)){
|
||||
photo.setImageDrawable(null);
|
||||
photo.setImageDrawable(crossfadeDrawable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearImage(int index){
|
||||
crossfadeDrawable.setCrossfadeAlpha(1f);
|
||||
didClear=true;
|
||||
}
|
||||
|
||||
private void onClick(View v){
|
||||
UiUtils.openURL(itemView.getContext(), item.parentFragment.getAccountID(), item.status.card.url, item.status);
|
||||
public Holder(Activity context, ViewGroup parent, boolean isLarge, String accountID){
|
||||
super(context, parent, isLarge, accountID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
controllers.add(c);
|
||||
|
||||
if (item.status.translation != null){
|
||||
if (item.status.translation!=null && item.status.translation.mediaAttachments!=null){
|
||||
if(item.status.translationState==Status.TranslationState.SHOWN){
|
||||
if(!item.translatedAttachments.containsKey(att.id)){
|
||||
Optional<Translation.MediaAttachment> translatedAttachment=Arrays.stream(item.status.translation.mediaAttachments).filter(mediaAttachment->mediaAttachment.id.equals(att.id)).findFirst();
|
||||
|
||||
@@ -125,7 +125,8 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
public void onBind(NotificationHeaderStatusDisplayItem item){
|
||||
text.setText(item.text);
|
||||
avatar.setVisibility(item.notification.type==Notification.Type.POLL ? View.GONE : View.VISIBLE);
|
||||
// TODO use real icons
|
||||
if(item.notification.type!=Notification.Type.POLL)
|
||||
avatar.setContentDescription(item.parentFragment.getString(R.string.avatar_description, item.notification.account.acct));
|
||||
icon.setImageResource(switch(item.notification.type){
|
||||
case FAVORITE -> R.drawable.ic_star_fill1_24px;
|
||||
case REBLOG -> R.drawable.ic_repeat_fill1_24px;
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.view.ViewGroup;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
@@ -68,8 +69,8 @@ public abstract class StatusDisplayItem{
|
||||
case AUDIO -> new AudioStatusDisplayItem.Holder(activity, parent);
|
||||
case POLL_OPTION -> new PollOptionStatusDisplayItem.Holder(activity, parent);
|
||||
case POLL_FOOTER -> new PollFooterStatusDisplayItem.Holder(activity, parent);
|
||||
case CARD_LARGE -> new LinkCardStatusDisplayItem.Holder(activity, parent, true);
|
||||
case CARD_COMPACT -> new LinkCardStatusDisplayItem.Holder(activity, parent, false);
|
||||
case CARD_LARGE -> new LinkCardStatusDisplayItem.Holder(activity, parent, true, ((BaseStatusListFragment<?>)parentFragment).getAccountID());
|
||||
case CARD_COMPACT -> new LinkCardStatusDisplayItem.Holder(activity, parent, false, ((BaseStatusListFragment<?>)parentFragment).getAccountID());
|
||||
case FOOTER -> new FooterStatusDisplayItem.Holder(activity, parent);
|
||||
case ACCOUNT -> new AccountStatusDisplayItem.Holder(new AccountViewHolder(parentFragment, parent, null));
|
||||
case HASHTAG -> new HashtagStatusDisplayItem.Holder(activity, parent);
|
||||
@@ -226,7 +227,7 @@ public abstract class StatusDisplayItem{
|
||||
FILTER_SPOILER
|
||||
}
|
||||
|
||||
public static abstract class Holder<T extends StatusDisplayItem> extends BindableViewHolder<T> implements UsableRecyclerView.DisableableClickable{
|
||||
public static abstract class Holder<T> extends BindableViewHolder<T> implements UsableRecyclerView.DisableableClickable{
|
||||
public Holder(View itemView){
|
||||
super(itemView);
|
||||
}
|
||||
@@ -236,17 +237,18 @@ public abstract class StatusDisplayItem{
|
||||
}
|
||||
|
||||
public String getItemID(){
|
||||
return item.parentID;
|
||||
return item instanceof StatusDisplayItem sdi ? sdi.parentID : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
item.parentFragment.onItemClick(item.parentID);
|
||||
if(item instanceof StatusDisplayItem sdi)
|
||||
sdi.parentFragment.onItemClick(sdi.parentID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(){
|
||||
return item.parentFragment.isItemEnabled(item.parentID);
|
||||
return item instanceof StatusDisplayItem sdi && sdi.parentFragment.isItemEnabled(sdi.parentID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
||||
text.setTextIsSelectable(item.textSelectable);
|
||||
text.setInvalidateOnEveryFrame(false);
|
||||
itemView.setClickable(false);
|
||||
text.setPadding(text.getPaddingLeft(), item.reduceTopPadding ? V.dp(8) : V.dp(16), text.getPaddingRight(), text.getPaddingBottom());
|
||||
text.setPadding(text.getPaddingLeft(), item.reduceTopPadding ? V.dp(8) : V.dp(12), text.getPaddingRight(), text.getPaddingBottom());
|
||||
text.setTextColor(UiUtils.getThemeColor(text.getContext(), item.inset ? R.attr.colorM3OnSurfaceVariant : R.attr.colorM3OnSurface));
|
||||
updateTranslation(false);
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.Toolbar;
|
||||
import android.window.OnBackInvokedDispatcher;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
@@ -169,7 +170,7 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
windowView=new FrameLayout(activity){
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event){
|
||||
if(event.getKeyCode()==KeyEvent.KEYCODE_BACK){
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.TIRAMISU && event.getKeyCode()==KeyEvent.KEYCODE_BACK){
|
||||
if(event.getAction()==KeyEvent.ACTION_DOWN){
|
||||
onStartSwipeToDismissTransition(0f);
|
||||
}
|
||||
@@ -257,6 +258,10 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
wlp.layoutInDisplayCutoutMode=Build.VERSION.SDK_INT>=30 ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS : WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||
windowView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||
wm.addView(windowView, wlp);
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.TIRAMISU){
|
||||
// TODO make use of the progress callback for nicer animation
|
||||
windowView.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, ()->onStartSwipeToDismissTransition(0));
|
||||
}
|
||||
|
||||
windowView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||
@Override
|
||||
|
||||
@@ -69,6 +69,7 @@ public class PhotoViewerInfoSheet extends BottomSheet{
|
||||
backButton.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
|
||||
backButton.setElevation(V.dp(2));
|
||||
backButton.setAlpha(0f);
|
||||
backButton.setContentDescription(context.getString(R.string.back));
|
||||
backButton.setOnClickListener(v->{
|
||||
listener.onDismissEntireViewer();
|
||||
dismiss();
|
||||
@@ -82,6 +83,7 @@ public class PhotoViewerInfoSheet extends BottomSheet{
|
||||
infoButton.setElevation(V.dp(2));
|
||||
infoButton.setAlpha(0f);
|
||||
infoButton.setSelected(true);
|
||||
infoButton.setContentDescription(context.getString(R.string.info));
|
||||
infoButton.setOnClickListener(v->dismiss());
|
||||
|
||||
FrameLayout.LayoutParams lp=new FrameLayout.LayoutParams(V.dp(48), V.dp(48));
|
||||
|
||||
@@ -85,6 +85,10 @@ public class DiscoverInfoBannerHelper{
|
||||
bannerTypesToShow=EnumSet.allOf(BannerType.class);
|
||||
}
|
||||
|
||||
public boolean isBannerShown(){
|
||||
return added;
|
||||
}
|
||||
|
||||
public enum BannerType{
|
||||
TRENDING_POSTS,
|
||||
TRENDING_LINKS,
|
||||
|
||||
@@ -38,7 +38,7 @@ public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{
|
||||
View child=parent.getChildAt(i);
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
||||
pos=holder.getAbsoluteAdapterPosition()-listFragment.getMainAdapterOffset();
|
||||
boolean inset=(holder instanceof StatusDisplayItem.Holder<?> sdi) && sdi.getItem().inset;
|
||||
boolean inset=holder instanceof StatusDisplayItem.Holder<?> sdi && sdi.getItem() instanceof StatusDisplayItem item && item.inset;
|
||||
if(inset){
|
||||
if(rect.isEmpty()){
|
||||
float childY=child.getY();
|
||||
@@ -80,13 +80,13 @@ public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{
|
||||
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;
|
||||
if(holder instanceof StatusDisplayItem.Holder<?> sdi && sdi.getItem() instanceof StatusDisplayItem item){
|
||||
boolean inset=item.inset;
|
||||
int pos=holder.getAbsoluteAdapterPosition()-listFragment.getMainAdapterOffset();
|
||||
if(inset){
|
||||
boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
|
||||
boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
|
||||
StatusDisplayItem.Type type=sdi.getItem().getType();
|
||||
StatusDisplayItem.Type type=item.getType();
|
||||
if(type==StatusDisplayItem.Type.CARD_LARGE || type==StatusDisplayItem.Type.MEDIA_GRID)
|
||||
outRect.left=outRect.right=V.dp(16);
|
||||
else
|
||||
|
||||
@@ -52,6 +52,7 @@ import android.widget.Toast;
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.FileProvider;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetAccountBlocked;
|
||||
@@ -368,6 +369,8 @@ public class UiUtils{
|
||||
}
|
||||
|
||||
public static void openHashtagTimeline(Context context, String accountID, Hashtag hashtag){
|
||||
if(checkIfAlreadyDisplayingSameHashtag(context, hashtag.name))
|
||||
return;
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("hashtag", Parcels.wrap(hashtag));
|
||||
@@ -375,12 +378,22 @@ public class UiUtils{
|
||||
}
|
||||
|
||||
public static void openHashtagTimeline(Context context, String accountID, String hashtag){
|
||||
if(checkIfAlreadyDisplayingSameHashtag(context, hashtag))
|
||||
return;
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putString("hashtagName", hashtag);
|
||||
Nav.go((Activity)context, HashtagTimelineFragment.class, args);
|
||||
}
|
||||
|
||||
private static boolean checkIfAlreadyDisplayingSameHashtag(Context context, String hashtag){
|
||||
if(context instanceof MainActivity ma && ma.getTopmostFragment() instanceof HashtagTimelineFragment htf && htf.getHashtagName().equalsIgnoreCase(hashtag)){
|
||||
htf.shakeListView();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, Runnable onConfirmed){
|
||||
showConfirmationAlert(context, context.getString(title), message==0 ? null : context.getString(message), context.getString(confirmButton), onConfirmed);
|
||||
}
|
||||
@@ -796,6 +809,10 @@ public class UiUtils{
|
||||
return !TextUtils.isEmpty(getSystemProperty("ro.build.version.emui"));
|
||||
}
|
||||
|
||||
public static boolean isMagic() {
|
||||
return !TextUtils.isEmpty(getSystemProperty("ro.build.version.magic"));
|
||||
}
|
||||
|
||||
public static int alphaBlendColors(int color1, int color2, float alpha){
|
||||
float alpha0=1f-alpha;
|
||||
int r=Math.round(((color1 >> 16) & 0xFF)*alpha0+((color2 >> 16) & 0xFF)*alpha);
|
||||
@@ -1040,4 +1057,20 @@ public class UiUtils{
|
||||
button.setTextColor(origColor);
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateRecyclerViewKeepingAbsoluteScrollPosition(RecyclerView rv, Runnable onUpdate){
|
||||
int topItem=-1;
|
||||
int topItemOffset=0;
|
||||
if(rv.getChildCount()>0){
|
||||
View item=rv.getChildAt(0);
|
||||
topItem=rv.getChildAdapterPosition(item);
|
||||
topItemOffset=item.getTop();
|
||||
}
|
||||
onUpdate.run();
|
||||
int newCount=rv.getAdapter().getItemCount();
|
||||
if(newCount>=topItem){
|
||||
rv.scrollToPosition(topItem);
|
||||
rv.scrollBy(0, -topItemOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,7 +313,12 @@ public class ComposeMediaViewController{
|
||||
int maxSize=0;
|
||||
String contentType=fragment.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
|
||||
Instance instance=fragment.instance;
|
||||
if(instance.configuration!=null && instance.configuration.mediaAttachments!=null && instance.configuration.mediaAttachments.imageMatrixLimit>0){
|
||||
maxSize=instance.configuration.mediaAttachments.imageMatrixLimit;
|
||||
}else{
|
||||
maxSize=2_073_600;
|
||||
}
|
||||
}
|
||||
attachment.progressBar.setProgress(0);
|
||||
attachment.speedTracker.reset();
|
||||
@@ -545,7 +550,7 @@ public class ComposeMediaViewController{
|
||||
}
|
||||
|
||||
public List<String> getAttachmentIDs(){
|
||||
return attachments.stream().map(a->a.serverAttachment.id).collect(Collectors.toList());
|
||||
return attachments.stream().filter(a->a.serverAttachment!=null).map(a->a.serverAttachment.id).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<CreateStatus.Request.MediaAttribute> getAttachmentAttributes(){
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
@@ -19,6 +20,7 @@ import android.view.ViewTreeObserver;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.Toolbar;
|
||||
import android.window.OnBackInvokedDispatcher;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
@@ -83,6 +85,15 @@ public class ToolbarDropdownMenuController{
|
||||
.withLayer()
|
||||
.start();
|
||||
controllerStack.add(initialSubmenu);
|
||||
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.TIRAMISU){
|
||||
windowView.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, ()->{
|
||||
if(controllerStack.size()>1)
|
||||
popSubmenuController();
|
||||
else
|
||||
dismiss();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void dismiss(){
|
||||
@@ -243,7 +254,7 @@ public class ToolbarDropdownMenuController{
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event){
|
||||
if(event.getKeyCode()==KeyEvent.KEYCODE_BACK){
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.TIRAMISU && event.getKeyCode()==KeyEvent.KEYCODE_BACK){
|
||||
if(event.getAction()==KeyEvent.ACTION_DOWN){
|
||||
if(controllerStack.size()>1)
|
||||
popSubmenuController();
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
package org.joinmastodon.android.ui.viewholders;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Card;
|
||||
import org.joinmastodon.android.model.viewmodel.CardViewModel;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class LinkCardHolder<T extends LinkCardHolder.LinkCardProvider> extends StatusDisplayItem.Holder<T> implements ImageLoaderViewHolder{
|
||||
private final TextView title, description, domain, timestamp, authorBefore, authorAfter, authorName;
|
||||
private final ImageView photo, authorAva;
|
||||
private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable();
|
||||
private boolean didClear;
|
||||
private final View inner, authorFooter, authorChip;
|
||||
private final boolean isLarge;
|
||||
private final Drawable logoIcon;
|
||||
private final String accountID;
|
||||
private Activity activity;
|
||||
private boolean tryResolving=true;
|
||||
|
||||
public LinkCardHolder(Activity context, ViewGroup parent, boolean isLarge, String accountID){
|
||||
super(context, isLarge ? R.layout.display_item_link_card : R.layout.display_item_link_card_compact, parent);
|
||||
this.isLarge=isLarge;
|
||||
this.accountID=accountID;
|
||||
activity=context;
|
||||
title=findViewById(R.id.title);
|
||||
description=findViewById(R.id.description);
|
||||
domain=findViewById(R.id.domain);
|
||||
timestamp=findViewById(R.id.timestamp);
|
||||
photo=findViewById(R.id.photo);
|
||||
inner=findViewById(R.id.inner);
|
||||
authorBefore=findViewById(R.id.author_before);
|
||||
authorAfter=findViewById(R.id.author_after);
|
||||
authorName=findViewById(R.id.author_name);
|
||||
authorAva=findViewById(R.id.author_ava);
|
||||
authorChip=findViewById(R.id.author_chip);
|
||||
authorFooter=findViewById(R.id.author_footer);
|
||||
|
||||
inner.setOnClickListener(this::onClick);
|
||||
inner.setOutlineProvider(OutlineProviders.roundedRect(8));
|
||||
inner.setClipToOutline(true);
|
||||
if(!isLarge){
|
||||
photo.setOutlineProvider(OutlineProviders.roundedRect(4));
|
||||
photo.setClipToOutline(true);
|
||||
}
|
||||
authorAva.setOutlineProvider(OutlineProviders.roundedRect(3));
|
||||
authorAva.setClipToOutline(true);
|
||||
authorChip.setOnClickListener(this::onAuthorChipClick);
|
||||
|
||||
logoIcon=context.getResources().getDrawable(R.drawable.ic_ntf_logo, context.getTheme()).mutate();
|
||||
logoIcon.setBounds(0, 0, V.dp(17), V.dp(17));
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@Override
|
||||
public void onBind(T item){
|
||||
CardViewModel cardVM=item.getCard();
|
||||
Card card=cardVM.card;
|
||||
title.setText(card.title);
|
||||
if(description!=null){
|
||||
description.setText(card.description);
|
||||
description.setVisibility(TextUtils.isEmpty(card.description) ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
String cardDomain=HtmlParser.normalizeDomain(Objects.requireNonNull(Uri.parse(card.url).getHost()));
|
||||
domain.setText(TextUtils.isEmpty(card.providerName) ? cardDomain : card.providerName);
|
||||
String authorName=card.authors!=null && !card.authors.isEmpty() ? card.authors.get(0).name : card.authorName;
|
||||
|
||||
if(cardVM.parsedAuthorName!=null){
|
||||
authorFooter.setVisibility(View.VISIBLE);
|
||||
authorChip.setVisibility(View.VISIBLE);
|
||||
authorBefore.setVisibility(View.VISIBLE);
|
||||
String[] authorParts=itemView.getContext().getString(R.string.article_by_author, "{author}").split("\\{author\\}");
|
||||
String before=authorParts[0].trim();
|
||||
String after=authorParts.length>1 ? authorParts[1].trim() : "";
|
||||
if(!TextUtils.isEmpty(before)){
|
||||
authorBefore.setText(before);
|
||||
}
|
||||
if(TextUtils.isEmpty(after)){
|
||||
authorAfter.setVisibility(View.GONE);
|
||||
}else{
|
||||
authorAfter.setVisibility(View.VISIBLE);
|
||||
authorAfter.setText(after);
|
||||
}
|
||||
this.authorName.setText(cardVM.parsedAuthorName);
|
||||
authorBefore.setCompoundDrawablesRelative(logoIcon, null, null, null);
|
||||
}else if(!TextUtils.isEmpty(authorName)){
|
||||
authorFooter.setVisibility(View.VISIBLE);
|
||||
authorBefore.setVisibility(View.VISIBLE);
|
||||
authorBefore.setCompoundDrawables(null, null, null, null);
|
||||
authorChip.setVisibility(View.GONE);
|
||||
authorAfter.setVisibility(View.GONE);
|
||||
authorBefore.setText(itemView.getContext().getString(R.string.article_by_author, authorName));
|
||||
}else{
|
||||
authorFooter.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if(card.publishedAt!=null){
|
||||
timestamp.setVisibility(View.VISIBLE);
|
||||
timestamp.setText(" · "+UiUtils.formatRelativeTimestamp(itemView.getContext(), card.publishedAt));
|
||||
}else{
|
||||
timestamp.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
photo.setImageDrawable(null);
|
||||
if(cardVM.imageRequest!=null){
|
||||
photo.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
||||
photo.setBackground(null);
|
||||
photo.setImageTintList(null);
|
||||
crossfadeDrawable.setSize(card.width, card.height);
|
||||
crossfadeDrawable.setBlurhashDrawable(card.blurhashPlaceholder);
|
||||
crossfadeDrawable.setCrossfadeAlpha(0f);
|
||||
photo.setImageDrawable(null);
|
||||
photo.setImageDrawable(crossfadeDrawable);
|
||||
didClear=false;
|
||||
}else{
|
||||
photo.setBackgroundColor(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3SurfaceVariant));
|
||||
photo.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3Outline)));
|
||||
photo.setScaleType(ImageView.ScaleType.CENTER);
|
||||
photo.setImageResource(R.drawable.ic_feed_48px);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(int index, Drawable drawable){
|
||||
if(index==0){
|
||||
crossfadeDrawable.setImageDrawable(drawable);
|
||||
if(didClear)
|
||||
crossfadeDrawable.animateAlpha(0f);
|
||||
CardViewModel card=item.getCard();
|
||||
// Make sure the image is not stretched if the server returned wrong dimensions
|
||||
if(drawable!=null && (drawable.getIntrinsicWidth()!=card.card.width || drawable.getIntrinsicHeight()!=card.card.height)){
|
||||
photo.setImageDrawable(null);
|
||||
photo.setImageDrawable(crossfadeDrawable);
|
||||
}
|
||||
}else if(index==1){
|
||||
authorAva.setImageDrawable(drawable);
|
||||
}else{
|
||||
item.getCard().authorNameEmojiHelper.setImageDrawable(index-2, drawable);
|
||||
authorName.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearImage(int index){
|
||||
if(index==0){
|
||||
crossfadeDrawable.setCrossfadeAlpha(1f);
|
||||
didClear=true;
|
||||
}else{
|
||||
setImage(index, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void setTryResolving(boolean tryResolving){
|
||||
this.tryResolving=tryResolving;
|
||||
}
|
||||
|
||||
private void onClick(View v){
|
||||
CardViewModel card=item.getCard();
|
||||
if(tryResolving)
|
||||
UiUtils.openURL(activity, accountID, card.card.url, card.parentObject);
|
||||
else
|
||||
UiUtils.launchWebBrowser(activity, card.card.url);
|
||||
}
|
||||
|
||||
private void onAuthorChipClick(View v){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(item.getCard().card.authorAccount));
|
||||
Nav.go(activity, ProfileFragment.class, args);
|
||||
}
|
||||
|
||||
public interface LinkCardProvider{
|
||||
CardViewModel getCard();
|
||||
}
|
||||
}
|
||||
@@ -158,6 +158,9 @@ public class FloatingHintEditTextLayout extends FrameLayout implements CustomVie
|
||||
}else{
|
||||
transY=edit.getHeight()/2f-edit.getLineHeight()/2f+(edit.getTop()-label.getTop())-(label.getHeight()/2f-label.getLineHeight()/2f);
|
||||
}
|
||||
int labelX=label.getLeft();
|
||||
int editX=edit.getLeft()+edit.getPaddingLeft();
|
||||
float xOffset=editX-labelX;
|
||||
|
||||
AnimatorSet anim=new AnimatorSet();
|
||||
if(hintVisible){
|
||||
@@ -166,6 +169,7 @@ public class FloatingHintEditTextLayout extends FrameLayout implements CustomVie
|
||||
ObjectAnimator.ofFloat(label, SCALE_X, scale),
|
||||
ObjectAnimator.ofFloat(label, SCALE_Y, scale),
|
||||
ObjectAnimator.ofFloat(label, TRANSLATION_Y, transY),
|
||||
ObjectAnimator.ofFloat(label, TRANSLATION_X, xOffset),
|
||||
ObjectAnimator.ofFloat(FloatingHintEditTextLayout.this, "animProgress", 0f)
|
||||
);
|
||||
edit.setHintTextColor(0);
|
||||
@@ -173,11 +177,13 @@ public class FloatingHintEditTextLayout extends FrameLayout implements CustomVie
|
||||
label.setScaleX(scale);
|
||||
label.setScaleY(scale);
|
||||
label.setTranslationY(transY);
|
||||
label.setTranslationX(xOffset);
|
||||
anim.playTogether(
|
||||
ObjectAnimator.ofFloat(edit, TRANSLATION_Y, offsetY),
|
||||
ObjectAnimator.ofFloat(label, SCALE_X, 1f),
|
||||
ObjectAnimator.ofFloat(label, SCALE_Y, 1f),
|
||||
ObjectAnimator.ofFloat(label, TRANSLATION_Y, 0f),
|
||||
ObjectAnimator.ofFloat(label, TRANSLATION_X, 0f),
|
||||
ObjectAnimator.ofFloat(FloatingHintEditTextLayout.this, "animProgress", 1f)
|
||||
);
|
||||
}
|
||||
|
||||
35
mastodon/src/main/res/animator/alt_badge.xml
Normal file
35
mastodon/src/main/res/animator/alt_badge.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true">
|
||||
<set>
|
||||
<objectAnimator
|
||||
android:duration="100"
|
||||
android:propertyName="alpha"
|
||||
android:valueTo="0.85"
|
||||
android:valueFrom="1.0"
|
||||
android:valueType="floatType"/>
|
||||
<objectAnimator
|
||||
android:duration="100"
|
||||
android:propertyName="textColor"
|
||||
android:valueTo="#80ffffff"
|
||||
android:valueFrom="#fff"
|
||||
android:valueType="colorType"/>
|
||||
</set>
|
||||
</item>
|
||||
<item>
|
||||
<set>
|
||||
<objectAnimator
|
||||
android:duration="200"
|
||||
android:propertyName="alpha"
|
||||
android:valueTo="1.0"
|
||||
android:valueFrom="0.85"
|
||||
android:valueType="floatType"/>
|
||||
<objectAnimator
|
||||
android:duration="200"
|
||||
android:propertyName="textColor"
|
||||
android:valueTo="#fff"
|
||||
android:valueFrom="#80ffffff"
|
||||
android:valueType="colorType"/>
|
||||
</set>
|
||||
</item>
|
||||
</selector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?colorM3OnSurfaceVariant" android:alpha="0.4"/>
|
||||
</selector>
|
||||
9
mastodon/src/main/res/drawable/bg_link_card_author.xml
Normal file
9
mastodon/src/main/res/drawable/bg_link_card_author.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:gravity="top">
|
||||
<shape>
|
||||
<solid android:color="?colorM3OutlineVariant"/>
|
||||
<size android:height="1dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/m3_on_surface_overlay">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="?colorM3SurfaceVariant"/>
|
||||
<corners android:radius="6dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
9
mastodon/src/main/res/drawable/bg_status_header.xml
Normal file
9
mastodon/src/main/res/drawable/bg_status_header.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/m3_primary_overlay">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape>
|
||||
<solid android:color="#000"/>
|
||||
<corners android:radius="14dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -2,14 +2,14 @@
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/m3_on_surface_overlay">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape>
|
||||
<corners android:radius="11dp"/>
|
||||
<corners android:radius="7dp"/>
|
||||
<solid android:color="#000"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<item android:left="-1dp" android:top="-1dp" android:right="-1dp" android:bottom="-1dp">
|
||||
<shape>
|
||||
<corners android:radius="11dp"/>
|
||||
<stroke android:color="?colorM3OutlineVariant" android:width="1dp"/>
|
||||
<corners android:radius="8dp"/>
|
||||
<stroke android:color="?colorM3OutlineVariant" android:width="2dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<stroke android:color="?colorM3OutlineVariant" android:width="0.5dp"/>
|
||||
<corners android:radius="2.5dp"/>
|
||||
</shape>
|
||||
9
mastodon/src/main/res/drawable/ic_clear_night_24px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_clear_night_24px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,20Q13.35,20 14.613,19.562Q15.875,19.125 16.925,18.3Q15.475,17.775 13.863,16.612Q12.25,15.45 11.025,13.7Q9.8,11.95 9.238,9.637Q8.675,7.325 9.275,4.5Q6.95,5.325 5.475,7.362Q4,9.4 4,12Q4,15.325 6.338,17.663Q8.675,20 12,20ZM12,22Q9.925,22 8.1,21.212Q6.275,20.425 4.925,19.075Q3.575,17.725 2.788,15.9Q2,14.075 2,12Q2,8.325 4.312,5.525Q6.625,2.725 10.5,2.125Q11.225,2 11.538,2.462Q11.85,2.925 11.575,3.675Q10.825,5.775 11.05,7.912Q11.275,10.05 12.275,11.85Q13.275,13.65 14.963,14.962Q16.65,16.275 18.825,16.75Q19.625,16.925 19.837,17.425Q20.05,17.925 19.575,18.475Q18.2,20.1 16.238,21.05Q14.275,22 12,22Z"/>
|
||||
</vector>
|
||||
7
mastodon/src/main/res/drawable/ic_explore_foreground.xml
Normal file
7
mastodon/src/main/res/drawable/ic_explore_foreground.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<vector android:height="108dp"
|
||||
android:viewportHeight="56" android:viewportWidth="56"
|
||||
android:width="108dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<group android:translateX="16" android:translateY="16">
|
||||
<path android:fillColor="@color/shortcut_icon_foreground" android:pathData="M19.6,21 L13.3,14.7Q12.55,15.3 11.575,15.65Q10.6,16 9.5,16Q6.775,16 4.888,14.113Q3,12.225 3,9.5Q3,6.775 4.888,4.887Q6.775,3 9.5,3Q12.225,3 14.113,4.887Q16,6.775 16,9.5Q16,10.6 15.65,11.575Q15.3,12.55 14.7,13.3L21,19.6ZM9.5,14Q11.375,14 12.688,12.688Q14,11.375 14,9.5Q14,7.625 12.688,6.312Q11.375,5 9.5,5Q7.625,5 6.312,6.312Q5,7.625 5,9.5Q5,11.375 6.312,12.688Q7.625,14 9.5,14Z"/>
|
||||
</group>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_lock_24px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_lock_24px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M6,22Q5.175,22 4.588,21.413Q4,20.825 4,20V10Q4,9.175 4.588,8.587Q5.175,8 6,8H7V6Q7,3.925 8.463,2.462Q9.925,1 12,1Q14.075,1 15.538,2.462Q17,3.925 17,6V8H18Q18.825,8 19.413,8.587Q20,9.175 20,10V20Q20,20.825 19.413,21.413Q18.825,22 18,22ZM9,8H15V6Q15,4.75 14.125,3.875Q13.25,3 12,3Q10.75,3 9.875,3.875Q9,4.75 9,6ZM6,20H18Q18,20 18,20Q18,20 18,20V10Q18,10 18,10Q18,10 18,10H6Q6,10 6,10Q6,10 6,10V20Q6,20 6,20Q6,20 6,20ZM12,17Q12.825,17 13.413,16.413Q14,15.825 14,15Q14,14.175 13.413,13.587Q12.825,13 12,13Q11.175,13 10.588,13.587Q10,14.175 10,15Q10,15.825 10.588,16.413Q11.175,17 12,17ZM12,15Q12,15 12,15Q12,15 12,15Q12,15 12,15Q12,15 12,15Q12,15 12,15Q12,15 12,15Q12,15 12,15Q12,15 12,15Z"/>
|
||||
</vector>
|
||||
@@ -39,14 +39,15 @@
|
||||
android:layout_height="56dp"
|
||||
android:background="@drawable/bg_m3_filled_text_field"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:textColorHint="?colorM3OnSurfaceVariant"
|
||||
android:textColorHint="@color/m3_on_surface_variant_alpha40"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:gravity="start|bottom"
|
||||
android:paddingBottom="8dp"
|
||||
android:singleLine="true"
|
||||
android:inputType="textUri"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:hint="example.social/invite/AbC123"/>
|
||||
android:hint="example.social/invite/AbC123"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label"
|
||||
|
||||
@@ -14,5 +14,6 @@
|
||||
android:includeFontPadding="false"
|
||||
android:background="@drawable/bg_image_alt_overlay"
|
||||
android:text="ALT"
|
||||
android:stateListAnimator="@animator/alt_badge"
|
||||
tools:ignore="HardcodedText"
|
||||
tools:showIn="@layout/display_item_photo" />
|
||||
@@ -4,8 +4,9 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingLeft="16dp">
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/more"
|
||||
@@ -21,6 +22,18 @@
|
||||
android:contentDescription="@string/more_options"
|
||||
android:src="@drawable/ic_more_vert_20px" />
|
||||
|
||||
<View
|
||||
android:id="@+id/clickable_thing"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="-4dp"
|
||||
android:layout_marginVertical="-4dp"
|
||||
android:layout_alignLeft="@id/avatar"
|
||||
android:layout_alignRight="@id/time_and_username"
|
||||
android:layout_alignTop="@id/avatar"
|
||||
android:layout_alignBottom="@id/avatar"
|
||||
android:background="@drawable/bg_status_header"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="40dp"
|
||||
@@ -28,7 +41,8 @@
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginEnd="8dp" />
|
||||
android:layout_marginEnd="8dp"
|
||||
android:importantForAccessibility="no"/>
|
||||
|
||||
<org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout
|
||||
android:id="@+id/name_wrap"
|
||||
@@ -70,6 +84,8 @@
|
||||
android:layout_height="20dp"
|
||||
android:layout_below="@id/name_wrap"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_toStartOf="@id/more"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:foreground="@drawable/fg_link_card"
|
||||
android:padding="1dp"
|
||||
android:paddingHorizontal="1dp"
|
||||
android:paddingTop="1dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:clipToPadding="false"
|
||||
android:maxWidth="400dp">
|
||||
<org.joinmastodon.android.ui.views.FixedAspectRatioImageView
|
||||
android:id="@+id/photo"
|
||||
@@ -24,14 +27,42 @@
|
||||
android:importantForAccessibility="no"
|
||||
app:aspectRatio="1.7777777778"
|
||||
tools:src="#0f0"/>
|
||||
|
||||
<org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="2dp">
|
||||
<TextView
|
||||
android:id="@+id/domain"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:textAppearance="@style/m3_body_small"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="example.com"/>
|
||||
<TextView
|
||||
android:id="@+id/timestamp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:textAppearance="@style/m3_body_small"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="example.com"/>
|
||||
</org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:maxLines="3"
|
||||
android:maxLines="4"
|
||||
android:ellipsize="end"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
tools:text="Link title"/>
|
||||
@@ -45,33 +76,13 @@
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
tools:text="Link description"/>
|
||||
<org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout
|
||||
<include
|
||||
layout="@layout/link_card_author_footer"
|
||||
android:id="@+id/author_footer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
<TextView
|
||||
android:id="@+id/domain"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="example.com"/>
|
||||
<TextView
|
||||
android:id="@+id/timestamp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="example.com"/>
|
||||
</org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout>
|
||||
android:layout_marginBottom="-16dp"/>
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -10,7 +10,7 @@
|
||||
<RelativeLayout
|
||||
android:id="@+id/inner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="128dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:foreground="@drawable/fg_link_card"
|
||||
@@ -18,40 +18,30 @@
|
||||
android:maxWidth="400dp">
|
||||
<ImageView
|
||||
android:id="@+id/photo"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:layout_width="96dp"
|
||||
android:layout_height="96dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:importantForAccessibility="no"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginStart="16dp"
|
||||
tools:src="#0f0"/>
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
<org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout
|
||||
android:id="@+id/domain_and_time"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_toStartOf="@id/photo"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:maxLines="3"
|
||||
android:ellipsize="end"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
tools:text="Link title"/>
|
||||
<org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_toStartOf="@id/photo"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="2dp">
|
||||
<TextView
|
||||
android:id="@+id/domain"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textAppearance="@style/m3_body_small"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
@@ -61,13 +51,32 @@
|
||||
android:id="@+id/timestamp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textAppearance="@style/m3_body_small"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="example.com"/>
|
||||
</org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout>
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@id/domain_and_time"
|
||||
android:layout_toStartOf="@id/photo"
|
||||
android:layout_marginStart="12dp"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:maxLines="3"
|
||||
android:ellipsize="end"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
tools:text="Link title"/>
|
||||
<include
|
||||
layout="@layout/link_card_author_footer"
|
||||
android:id="@+id/author_footer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:layout_below="@id/photo"/>
|
||||
</RelativeLayout>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -76,21 +76,50 @@
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
tools:text="\@Gargron" />
|
||||
|
||||
<Button
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/btn_visibility"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="28dp"
|
||||
android:layout_below="@id/name"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
android:background="@drawable/bg_filter_chip"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:drawablePadding="8dp"
|
||||
tools:text="@string/visibility_public"/>
|
||||
android:paddingHorizontal="8dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/visibility_text1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:drawablePadding="8dp"
|
||||
android:singleLine="true"
|
||||
android:gravity="center"
|
||||
tools:text="@string/visibility_public"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/visibility_text2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:drawablePadding="8dp"
|
||||
android:singleLine="true"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
tools:text="@string/visibility_public"/>
|
||||
|
||||
<View
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:background="@drawable/ic_baseline_arrow_drop_down_18"
|
||||
android:backgroundTint="?colorM3OnSurface"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
@@ -56,10 +56,12 @@
|
||||
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
||||
android:id="@+id/username_wrap"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="12dp"
|
||||
app:labelTextColor="@color/m3_outlined_text_field_label"
|
||||
android:foreground="@drawable/bg_m3_outlined_text_field">
|
||||
android:foreground="@drawable/bg_m3_outlined_text_field"
|
||||
tools:ignore="RtlHardcoded">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/username"
|
||||
@@ -91,6 +93,20 @@
|
||||
android:textColor="?colorM3OnSurface"
|
||||
tools:text="\@mastodon.social"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/at_sign"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="56dp"
|
||||
android:layout_gravity="left|top"
|
||||
android:layout_marginLeft="56dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="2dp"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:gravity="center_vertical"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:text="\@"/>
|
||||
|
||||
</org.joinmastodon.android.ui.views.FloatingHintEditTextLayout>
|
||||
|
||||
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
||||
@@ -191,19 +207,6 @@
|
||||
|
||||
</org.joinmastodon.android.ui.views.FloatingHintEditTextLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="56dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginTop="-8dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:textAppearance="@style/m3_body_small"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:text="@string/password_note"/>
|
||||
|
||||
|
||||
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
||||
android:id="@+id/reason_wrap"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:paddingStart="56dp"
|
||||
android:paddingStart="80dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:text="@string/login_subtitle"/>
|
||||
@@ -38,7 +38,7 @@
|
||||
android:textColorHint="?colorM3OnSurfaceVariant"
|
||||
android:inputType="textUri"
|
||||
android:importantForAutofill="no"
|
||||
android:paddingStart="48dp"
|
||||
android:paddingStart="64dp"
|
||||
android:layout_marginEnd="52dp"
|
||||
android:drawablePadding="16dp"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
@@ -47,6 +47,7 @@
|
||||
<ImageView
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:scaleType="center"
|
||||
android:importantForAccessibility="no"
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="12dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="24dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/photo"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:importantForAccessibility="no"
|
||||
tools:src="#0f0"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="16dp"
|
||||
android:layout_toEndOf="@id/photo"
|
||||
android:textAppearance="@style/m3_label_medium"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="Site Name"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/name"
|
||||
android:layout_toEndOf="@id/photo"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:paddingVertical="2.5dp"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
tools:text="Title title title"/>
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -1,44 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="280dp"
|
||||
android:layout_height="240dp"
|
||||
android:foreground="@drawable/bg_settings_banner">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/photo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="140dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:importantForAccessibility="no"
|
||||
tools:src="#0f0"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/photo"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:paddingVertical="2.5dp"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
tools:text="Title title title"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_below="@id/title"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="Site Name"/>
|
||||
|
||||
</RelativeLayout>
|
||||
63
mastodon/src/main/res/layout/link_card_author_footer.xml
Normal file
63
mastodon/src/main/res/layout/link_card_author_footer.xml
Normal file
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:gravity="center_vertical"
|
||||
android:background="@drawable/bg_link_card_author">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/author_before"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:drawablePadding="8dp"
|
||||
android:drawableTint="?colorM3Outline"
|
||||
tools:text="By"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/author_chip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="24dp"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:paddingHorizontal="6dp"
|
||||
android:gravity="center_vertical"
|
||||
android:background="@drawable/bg_link_card_author_chip">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/author_ava"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:foreground="@drawable/fg_link_card_author_chip_ava"
|
||||
tools:src="#0f0"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/author_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:textSize="14dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:layout_marginStart="4dp"
|
||||
tools:text="John Appleseed"/>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/author_after"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
tools:text="some languages might need this"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -141,6 +141,7 @@
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:textIsSelectable="true"
|
||||
tools:text="A cute black cat"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
5
mastodon/src/main/res/menu/boost_longtap.xml
Normal file
5
mastodon/src/main/res/menu/boost_longtap.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/boost" android:title="@string/button_reblog"/>
|
||||
<item android:id="@+id/view_boosts" android:title="@string/view_boosts"/>
|
||||
</menu>
|
||||
6
mastodon/src/main/res/menu/favorite_longtap.xml
Normal file
6
mastodon/src/main/res/menu/favorite_longtap.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/favorite" android:title="@string/button_favorite"/>
|
||||
<item android:id="@+id/bookmark" android:title="@string/add_bookmark"/>
|
||||
<item android:id="@+id/view_favorites" android:title="@string/view_favorites"/>
|
||||
</menu>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background>
|
||||
<shape>
|
||||
<solid android:color="@color/shortcut_icon_background"/>
|
||||
<size android:width="108dp" android:height="108dp"/>
|
||||
</shape>
|
||||
</background>
|
||||
<foreground android:drawable="@drawable/ic_explore_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -9,7 +9,6 @@
|
||||
<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">%s بَدَأ بِمُتابَعَتِك</string>
|
||||
<string name="user_sent_follow_request">%s أرسَلَ طَلَبًا لِمُتابَعَتِك</string>
|
||||
@@ -195,7 +194,6 @@
|
||||
<string name="email">البريد الإلكتروني</string>
|
||||
<string name="password">كلمة المرور</string>
|
||||
<string name="confirm_password">تأكيد كلمة المرور</string>
|
||||
<string name="password_note">ضمّن الأحرف الكبيرة والأحرف الخاصة والأرقام لزيادة قوة كلمة المرور.</string>
|
||||
<string name="category_general">عام</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="confirm_email_subtitle">اضغط على الرابط الذي أرسلناه إليك للتحقق من %s. سننتظر هنا.</string>
|
||||
@@ -208,8 +206,6 @@
|
||||
<string name="save">احفظ</string>
|
||||
<string name="add_alt_text">أضف نصًا بديلًا</string>
|
||||
<string name="visibility_public">علني</string>
|
||||
<string name="visibility_followers_only">للمُتابِعينَ فقط</string>
|
||||
<string name="visibility_private">للمشار إليهم فقط</string>
|
||||
<string name="recent_searches">الحديثة</string>
|
||||
<string name="skip">تخطى</string>
|
||||
<string name="notification_type_follow">متابعُون جُدُد</string>
|
||||
@@ -372,7 +368,6 @@
|
||||
<string name="instance_signup_closed">هذا الخادم لا يقبل التسجيلات الجديدة.</string>
|
||||
<string name="text_copied">نُسِخ إلى الحافظة</string>
|
||||
<string name="add_bookmark">إضافة إلى الفواصل المرجعية</string>
|
||||
<string name="remove_bookmark">إزالة الفاصلة المرجعية</string>
|
||||
<string name="bookmarks">الفواصل المرجعية</string>
|
||||
<string name="login_subtitle">قم بتسجيل الدخول باستخدام الخادم حيث قمتَ بإنشاء حسابك فيه.</string>
|
||||
<string name="server_url">رابط الخادم</string>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<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="in_reply_to">у адказ на %s</string>
|
||||
<string name="notifications">Апавяшчэнні</string>
|
||||
<string name="user_followed_you">%s падпісаўся на вас</string>
|
||||
<string name="user_sent_follow_request">%s адправіў вам запыт на падпіску</string>
|
||||
@@ -176,7 +176,6 @@
|
||||
<string name="email">Электронная пошта</string>
|
||||
<string name="password">Пароль</string>
|
||||
<string name="confirm_password">Пацвердзіць пароль</string>
|
||||
<string name="password_note">Выкарыстоўвайце вялікія літары, спецыяльныя сімвалы і лічбы, каб павялічыць надзейнасць пароля.</string>
|
||||
<string name="category_general">Асноўныя</string>
|
||||
<string name="confirm_email_title">Праверце вашу пошту</string>
|
||||
<!-- %s is the email address -->
|
||||
@@ -190,8 +189,8 @@
|
||||
<string name="save">Захаваць</string>
|
||||
<string name="add_alt_text">Дадаць альтэрнатыўны тэкст</string>
|
||||
<string name="visibility_public">Публічны</string>
|
||||
<string name="visibility_followers_only">Толькі для падпісчыкаў</string>
|
||||
<string name="visibility_private">Толькі людзі, якіх згадалі</string>
|
||||
<string name="visibility_followers_only">Падпісчыкаў</string>
|
||||
<string name="visibility_private">Канкрэтныя людзі</string>
|
||||
<string name="recent_searches">Нядаўняе</string>
|
||||
<string name="skip">Прапусціць</string>
|
||||
<string name="notification_type_follow">Новыя падпісчыкі</string>
|
||||
@@ -784,4 +783,48 @@
|
||||
<string name="new_post_notifications_disabled">Вы больш не будзеце атрымліваць апавяшчэнні аб новых допісах.</string>
|
||||
<string name="mute_conversation">Ігнараваць размову</string>
|
||||
<string name="unmute_conversation">Не ігнараваць размову</string>
|
||||
<string name="visibility_unlisted">Ціхі публічны</string>
|
||||
<string name="filtered_notifications">Адфільтраваныя апавяшчэнні</string>
|
||||
<string name="filter_notifications">Адфільтроўваць апавяшчэнні ад...</string>
|
||||
<string name="notification_filter_following">Людзі, на якіх вы не падпісаны</string>
|
||||
<string name="notification_filter_following_explanation">Пакуль вы не пацвердзіце іх уручную</string>
|
||||
<string name="notification_filter_followers">Людзі, якія не падпісаны на вас</string>
|
||||
<string name="notification_filter_followers_explanation">Уключаючы людзей, якія падпісаны на вас менш за 3 дні</string>
|
||||
<string name="notification_filter_new_accounts">Новыя ўліковыя запісы</string>
|
||||
<string name="notification_filter_new_accounts_explanation">Створана за апошнія 30 дзён</string>
|
||||
<string name="notification_filter_mentions">Непажаданыя асаблівыя згадванні</string>
|
||||
<string name="notification_filter_mentions_explanation">Фільтруецца за выключэннем адказу на вашае згадванне ці калі вы падпісаны на адпраўніка</string>
|
||||
<string name="allow_notifications">Дазволіць апавяшчэнні</string>
|
||||
<string name="mute_notifications">Адхіліць запыт на апавяшчэнне</string>
|
||||
<plurals name="x_people_you_may_know">
|
||||
<item quantity="one">%,d чалавек, якога вы можаце ведаць</item>
|
||||
<item quantity="few">%,d чалавека, якога вы можаце ведаць</item>
|
||||
<item quantity="many">%,d людзей, якіх вы можаце ведаць</item>
|
||||
<item quantity="other">%,d людзей, якіх вы можаце ведаць</item>
|
||||
</plurals>
|
||||
<string name="notifications_from_user">Апавяшчэнні ад %s</string>
|
||||
<string name="notifications_muted">Апавяшчэнні ад %s былі адключаныя.</string>
|
||||
<string name="notifications_allowed">Цяпер %s з\'явіцца ў вашым спісе апавяшчэнняў.</string>
|
||||
<string name="visibility_subtitle_public">Усе, хто ёсць і каго няма ў Mastodon</string>
|
||||
<string name="visibility_subtitle_unlisted">Менш фанфар ад алгарытмаў</string>
|
||||
<string name="visibility_subtitle_followers">Толькі вашыя падпісчыкі</string>
|
||||
<string name="visibility_subtitle_private">Усе згаданыя ў допісе</string>
|
||||
<string name="view_boosts">Праглядзець пашырэнні</string>
|
||||
<string name="view_favorites">Праглядзець абранае</string>
|
||||
<string name="undo_reblog">Адмяніць пашырэнне</string>
|
||||
<string name="undo_favorite">Адмяніць абранне</string>
|
||||
<string name="could_not_reach_server">Не ўдалося звязацца з серверам. Праверце злучэнне і паспрабуйце яшчэ раз.</string>
|
||||
<string name="connection_timed_out">Запыт быў перапынены. Праверце злучэнне і паспрабуйце яшчэ раз.</string>
|
||||
<string name="server_error">Нешта пайшло не так у сувязі з вашым серверам. Хутчэй за ўсё, гэта не ваша віна. Паспрабаваць яшчэ раз?</string>
|
||||
<string name="not_found">Ён мог быць выдалены, а можа, яго ўвогуле не існавала.</string>
|
||||
<string name="no_servers_found">Не знойдзена ніводнага сервера для “%s”</string>
|
||||
<string name="signup_username_taken">Гэтае імя карыстальніка занята. Паспрабуйце іншае ці <a>выберыце іншы сервер</a>.</string>
|
||||
<string name="signup_email_invalid">Гэта не падобна на сапраўдны адрас электроннай пошты.</string>
|
||||
<string name="signup_email_taken">Адрас электроннай пошты ўжо выкарыстоўваецца. Вы <a>забылі свой пароль</a>?</string>
|
||||
<plurals name="x_new_notifications">
|
||||
<item quantity="one">%,d новае апавяшчэнне</item>
|
||||
<item quantity="few">%,d новыя апавяшчэнні</item>
|
||||
<item quantity="many">%,d новыя апавяшчэнняў</item>
|
||||
<item quantity="other">%,d новыя апавяшчэнняў</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<string name="error">কোনো ত্রুটি ঘটেছে</string>
|
||||
<string name="ok">ঠিক আছে</string>
|
||||
<string name="preparing_auth">প্রমাণীকরণের জন্য প্রস্তুত হচ্ছে...</string>
|
||||
<string name="in_reply_to">%s কে উত্তর দিন</string>
|
||||
<string name="notifications">নোটিফিকেশন</string>
|
||||
<string name="user_followed_you">%s আপনাকে ফলো করেছেন</string>
|
||||
<string name="user_sent_follow_request">%s আপনাকে ফলো করার অনুরোধ পাঠিয়েছেন</string>
|
||||
@@ -115,7 +114,6 @@
|
||||
<!-- %s is the email address -->
|
||||
<string name="open_email_app">ই-মেইল অ্যাপ খুলুন</string>
|
||||
<string name="resent_email">নিশ্চিতকরনের ই-মেইল পাঠানো হয়েছে</string>
|
||||
<string name="visibility_followers_only">ফলোয়ারদের জন্য</string>
|
||||
<string name="notification_type_follow">নতুন ফলোয়াররা</string>
|
||||
<string name="err_not_logged_in">Mastodon-এ প্রথমে লগ ইন করুন</string>
|
||||
<plurals name="cant_add_more_than_x_attachments">
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<string name="ok">OK</string>
|
||||
<string name="preparing_auth">Pripremamo autorizaciju…</string>
|
||||
<string name="finishing_auth">Završavamo autorizaciju…</string>
|
||||
<string name="in_reply_to">Odgovor za %s</string>
|
||||
<string name="notifications">Obavijesti</string>
|
||||
<string name="share_toot_title">Podijeli</string>
|
||||
<string name="settings">Postavke</string>
|
||||
@@ -80,7 +79,6 @@
|
||||
<string name="unfollow_user">Prestani pratiti %s</string>
|
||||
<string name="unfollow">Prestani pratiti</string>
|
||||
<string name="back">Nazad</string>
|
||||
<string name="password_note">Koristite velika slova, brojeve i oznake da bi osigurali lozinku.</string>
|
||||
<string name="category_general">Generalno</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="resend">Ponovo poslato</string>
|
||||
@@ -90,7 +88,6 @@
|
||||
<string name="save">Sačuvaj</string>
|
||||
<string name="add_alt_text">Dodaj alt tekst</string>
|
||||
<string name="visibility_public">Javno</string>
|
||||
<string name="visibility_followers_only">Samo prijatelji</string>
|
||||
<string name="skip">Preskoči</string>
|
||||
<string name="notification_type_follow">Novi pratioci</string>
|
||||
<string name="notification_type_favorite">Favoriti</string>
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
<string name="next">Següent</string>
|
||||
<string name="loading_instance">S\'està recuperant la informació del servidor…</string>
|
||||
<string name="error">Error</string>
|
||||
<string name="not_a_mastodon_instance">Sembla que %s no és un servidor de Mastodon.</string>
|
||||
<string name="ok">D\'acord</string>
|
||||
<string name="preparing_auth">Preparant l\'autenticació…</string>
|
||||
<string name="finishing_auth">Finalitzant l\'autenticació…</string>
|
||||
<string name="in_reply_to">En resposta a %s</string>
|
||||
<string name="notifications">Notificacions</string>
|
||||
<string name="user_followed_you">%s et segueix</string>
|
||||
<string name="user_favorited">%s ha marcat com a favorita la teva publicació</string>
|
||||
<string name="poll_ended">Veieu els resultats d\'una enquesta on heu votat</string>
|
||||
<string name="share_toot_title">Comparteix</string>
|
||||
<string name="settings">Configuració</string>
|
||||
@@ -25,10 +27,13 @@
|
||||
<item quantity="other">seguint</item>
|
||||
</plurals>
|
||||
<string name="posts">Publicacions</string>
|
||||
<string name="posts_and_replies">Tuts i respostes</string>
|
||||
<string name="media">Multimèdia</string>
|
||||
<string name="profile_about">Quant a</string>
|
||||
<string name="button_follow">Segueix</string>
|
||||
<string name="button_following">Seguint</string>
|
||||
<string name="edit_profile">Edita el perfil</string>
|
||||
<string name="share_user">Comparteix el perfil via…</string>
|
||||
<string name="mute_user">Silencia %s</string>
|
||||
<string name="unmute_user">Deixa de silenciar %s</string>
|
||||
<string name="block_user">Bloca %s</string>
|
||||
@@ -123,7 +128,6 @@
|
||||
<string name="email">Correu electrònic</string>
|
||||
<string name="password">Contrasenya</string>
|
||||
<string name="confirm_password">Confirmar contrasenya</string>
|
||||
<string name="password_note">Inclou lletres majúscules, caràcters especials i números per augmentar la seguretat de la teva contrasenya.</string>
|
||||
<string name="category_general">General</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="resend">Reenvia</string>
|
||||
@@ -133,7 +137,6 @@
|
||||
<string name="save">Desa</string>
|
||||
<string name="add_alt_text">Afegeix text alternatiu</string>
|
||||
<string name="visibility_public">Públic</string>
|
||||
<string name="visibility_followers_only">Només seguidors</string>
|
||||
<string name="skip">Omet</string>
|
||||
<string name="notification_type_follow">Seguidors nous</string>
|
||||
<string name="notification_type_favorite">Favorits</string>
|
||||
@@ -156,6 +159,7 @@
|
||||
<string name="settings_clear_cache">Esborra la memòria cau multimèdia</string>
|
||||
<string name="settings_app_version">Mastodon per a Android v%1$s (%2$d)</string>
|
||||
<string name="media_cache_cleared">S\'ha esborrat la memòria cau multimèdia</string>
|
||||
<string name="avatar_description">Ves al perfil de: %s</string>
|
||||
<string name="more_options">Més opcions</string>
|
||||
<string name="new_post">Nova publicació</string>
|
||||
<string name="button_reply">Respon</string>
|
||||
@@ -244,7 +248,6 @@
|
||||
<string name="instance_signup_closed">Aquest servidor no accepta nous registres.</string>
|
||||
<string name="text_copied">Copiat al porta-retalls</string>
|
||||
<string name="add_bookmark">Marca</string>
|
||||
<string name="remove_bookmark">Elimina el marcador</string>
|
||||
<string name="bookmarks">Marcadors</string>
|
||||
<string name="login_subtitle">Inicia sessió amb el servidor on vas crear el compte.</string>
|
||||
<string name="server_url">URL del servidor</string>
|
||||
@@ -255,10 +258,15 @@
|
||||
<string name="server_filter_region_asia">Àsia</string>
|
||||
<string name="server_filter_region_oceania">Oceania</string>
|
||||
<string name="signup_passwords_dont_match">Les contrasenyes no coincideixen</string>
|
||||
<string name="profile_add_row">Afegeix una fila</string>
|
||||
<string name="profile_setup">Configuració del perfil</string>
|
||||
<!-- %s is server domain -->
|
||||
<string name="profile_bio">Biografia</string>
|
||||
<!-- Shown in a progress dialog when you tap "follow all" -->
|
||||
<!-- %1$s is server domain, %2$s is email domain. You can reorder these placeholders to fit your language better. -->
|
||||
<string name="profile_featured">Destacat</string>
|
||||
<string name="profile_timeline">Línia de temps</string>
|
||||
<string name="profile_endorsed_accounts">Comptes</string>
|
||||
<!-- %s is formatted file size ("467 KB image") -->
|
||||
<string name="poll_style">Estil</string>
|
||||
<!-- %s is the server domain -->
|
||||
@@ -270,14 +278,21 @@
|
||||
<!-- %s is the username -->
|
||||
<!-- %s is the timestamp ("tomorrow at 12:34") -->
|
||||
<string name="filter_context_public_timelines">Línies de temps públiques</string>
|
||||
<string name="filter_context_profiles">Perfils</string>
|
||||
<!-- Shown like a content warning, %s is the name of the filter -->
|
||||
<!-- Shown in the post header. Please keep it short -->
|
||||
<!-- %s is the name of the post language -->
|
||||
<!-- %1$s is the language, %2$s is the name of the translation service -->
|
||||
<string name="timeline_following">Inici</string>
|
||||
<!-- Screen reader description for the menu on the home timeline screen -->
|
||||
<!-- %s is the name of the list -->
|
||||
<!-- %s is a username -->
|
||||
<!-- %s is a time interval ("5 months") -->
|
||||
<!-- Shown on a button that saves a file, after it was successfully saved -->
|
||||
<string name="copy_profile_link">Copia l\'enllaç al perfil</string>
|
||||
<string name="tab_home">Inici</string>
|
||||
<string name="tab_search">Explora</string>
|
||||
<string name="tab_profile">Perfil</string>
|
||||
<string name="pin_post">Fixa en el perfil</string>
|
||||
<!-- %s is the username -->
|
||||
</resources>
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
<string name="preparing_auth">Příprava na autentizaci…</string>
|
||||
<string name="finishing_auth">Dokončení autentizace…</string>
|
||||
<string name="user_boosted">%s boostnul(a)</string>
|
||||
<string name="in_reply_to">V odpovědi na %s</string>
|
||||
<string name="notifications">Upozornění</string>
|
||||
<string name="user_followed_you">%s vás sleduje</string>
|
||||
<string name="user_sent_follow_request">%s vám poslal(a) žádost o sledování</string>
|
||||
@@ -176,7 +175,6 @@
|
||||
<string name="email">E-mail</string>
|
||||
<string name="password">Heslo</string>
|
||||
<string name="confirm_password">Potvrdit 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_general">Obecné</string>
|
||||
<string name="confirm_email_title">Zkontrolujte si příchozí poštu</string>
|
||||
<!-- %s is the email address -->
|
||||
@@ -190,8 +188,6 @@
|
||||
<string name="save">Uložit</string>
|
||||
<string name="add_alt_text">Přidat alternativní text</string>
|
||||
<string name="visibility_public">Veřejné</string>
|
||||
<string name="visibility_followers_only">Pouze sledující</string>
|
||||
<string name="visibility_private">Pouze lidé, které zmíním</string>
|
||||
<string name="recent_searches">Nedávné</string>
|
||||
<string name="skip">Přeskočit</string>
|
||||
<string name="notification_type_follow">Noví sledující</string>
|
||||
@@ -346,16 +342,13 @@
|
||||
<string name="instance_signup_closed">Tento server nepřijímá nové registrace.</string>
|
||||
<string name="text_copied">Zkopírováno do schránky</string>
|
||||
<string name="add_bookmark">Přidat do záložek</string>
|
||||
<string name="remove_bookmark">Odstranit ze záložek</string>
|
||||
<string name="bookmarks">Záložky</string>
|
||||
<string name="your_favorites">Vaše oblíbené</string>
|
||||
<string name="login_title">Vítej zpět</string>
|
||||
<string name="login_subtitle">Přihlaste se pomocí serveru, kde jste vytvořili svůj účet.</string>
|
||||
<string name="server_url">URL serveru</string>
|
||||
<string name="server_filter_any_language">Jakýkoliv jazyk</string>
|
||||
<string name="server_filter_instant_signup">Okamžitá registrace</string>
|
||||
<string name="server_filter_manual_review">S ruční kontrolou</string>
|
||||
<string name="server_filter_any_signup_speed">Jakákoliv rychlost registrace</string>
|
||||
<string name="server_filter_region_europe">Evropa</string>
|
||||
<string name="server_filter_region_north_america">Severní Amerika</string>
|
||||
<string name="server_filter_region_south_america">Jižní Amerika</string>
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
<string name="preparing_auth">Forbereder godkendelse…</string>
|
||||
<string name="finishing_auth">Afslutter godkendelse…</string>
|
||||
<string name="user_boosted">%s fremhævede</string>
|
||||
<string name="in_reply_to">Som svar til %s</string>
|
||||
<string name="notifications">Notifikationer</string>
|
||||
<string name="user_followed_you">%s begyndte at følge dig</string>
|
||||
<string name="user_sent_follow_request">%s har sendt dig en følgeanmodning</string>
|
||||
@@ -147,7 +146,6 @@
|
||||
<string name="email">E-mail</string>
|
||||
<string name="password">Adgangskode</string>
|
||||
<string name="confirm_password">Bekræft adgangskode</string>
|
||||
<string name="password_note">Benyt majuskler, specialtegn og tal for at øge adgangskodens styrke.</string>
|
||||
<string name="category_general">Generelt</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="confirm_email_subtitle">Tryk på det modtage link for at bekræfte %s. Vi venter her så længe.</string>
|
||||
@@ -160,8 +158,6 @@
|
||||
<string name="save">Gem</string>
|
||||
<string name="add_alt_text">Tilføj alternativ tekst</string>
|
||||
<string name="visibility_public">Offentlig</string>
|
||||
<string name="visibility_followers_only">Kun Følgere</string>
|
||||
<string name="visibility_private">Kun nævnte personer</string>
|
||||
<string name="recent_searches">Nylige</string>
|
||||
<string name="skip">Overspring</string>
|
||||
<string name="notification_type_follow">Nye Følgere</string>
|
||||
@@ -295,7 +291,6 @@
|
||||
<string name="instance_signup_closed">Denne server accepterer ikke nye tilmeldinger.</string>
|
||||
<string name="text_copied">Kopieret til udklipsholderen</string>
|
||||
<string name="add_bookmark">Bogmærk</string>
|
||||
<string name="remove_bookmark">Fjern bogmærke</string>
|
||||
<string name="bookmarks">Bogmærker</string>
|
||||
<string name="login_subtitle">Log ind med serveren, på hvilken din konto blev oprettet.</string>
|
||||
<string name="server_url">Server-URL</string>
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
<string name="preparing_auth">Authentifizierung wird vorbereitet …</string>
|
||||
<string name="finishing_auth">Authentisierung wird abgeschlossen …</string>
|
||||
<string name="user_boosted">%s teilte</string>
|
||||
<string name="in_reply_to">Als Antwort auf %s</string>
|
||||
<string name="notifications">Benachrichtigungen</string>
|
||||
<string name="user_followed_you">%s folgt dir jetzt</string>
|
||||
<string name="user_sent_follow_request">%s hat dir eine Folgeanfrage gesendet</string>
|
||||
@@ -152,7 +151,6 @@
|
||||
<string name="email">E-Mail</string>
|
||||
<string name="password">Passwort</string>
|
||||
<string name="confirm_password">Passwort bestätigen</string>
|
||||
<string name="password_note">Verwende Großbuchstaben, Sonderzeichen und Zahlen, um deine Passwortstärke zu erhöhen.</string>
|
||||
<string name="category_general">Allgemein</string>
|
||||
<string name="confirm_email_title">Überprüfe dein E-Mail-Postfach</string>
|
||||
<!-- %s is the email address -->
|
||||
@@ -166,8 +164,8 @@
|
||||
<string name="save">Speichern</string>
|
||||
<string name="add_alt_text">Bildbeschreibung hinzufügen</string>
|
||||
<string name="visibility_public">Öffentlich</string>
|
||||
<string name="visibility_followers_only">Nur Follower</string>
|
||||
<string name="visibility_private">Nur erwähnte Profile</string>
|
||||
<string name="visibility_followers_only">Folgende</string>
|
||||
<string name="visibility_private">Bestimmte Personen</string>
|
||||
<string name="recent_searches">Verlauf</string>
|
||||
<string name="skip">Überspringen</string>
|
||||
<string name="notification_type_follow">Neue Follower</string>
|
||||
@@ -189,6 +187,7 @@
|
||||
<string name="theme_dark">Dunkel</string>
|
||||
<string name="settings_behavior">Verhalten</string>
|
||||
<string name="settings_gif">Animierte GIFs, Avatare und Emojis abspielen</string>
|
||||
<string name="settings_custom_tabs">Link öffnen in</string>
|
||||
<string name="settings_notifications">Benachrichtigungen</string>
|
||||
<string name="settings_contribute">Zu Mastodon beitragen</string>
|
||||
<string name="settings_tos">Nutzungsbedingungen</string>
|
||||
@@ -217,6 +216,8 @@
|
||||
<string name="followed_user">Du folgst nun %s</string>
|
||||
<string name="following_user_requested">Deine Follower-Anfrage an %s wurde gesendet</string>
|
||||
<string name="open_in_browser">Im Browser öffnen</string>
|
||||
<string name="hide_boosts_from_user">Boosts ausblenden</string>
|
||||
<string name="show_boosts_from_user">Boosts anzeigen</string>
|
||||
<string name="signup_reason">Warum möchtest du beitreten?</string>
|
||||
<string name="signup_reason_note">Das erleichtert uns die Prüfung deiner Anmeldung.</string>
|
||||
<string name="clear">Leeren</string>
|
||||
@@ -298,7 +299,9 @@
|
||||
<!-- %s is file size -->
|
||||
<string name="download_update">(%s) herunterladen</string>
|
||||
<string name="install_update">Installieren</string>
|
||||
<string name="privacy_policy_title">Deine Privatsphäre</string>
|
||||
<string name="privacy_policy_subtitle">Auch wenn die Mastodon-App keine Daten sammelt, kann der Server, über den du dich anmeldest, eine andere Richtlinie haben.\n\nWenn du mit der Richtlinie für %s nicht einverstanden bist, kannst du zurückkehren und einen anderen Server wählen.</string>
|
||||
<string name="i_agree">Ich stimme zu</string>
|
||||
<string name="empty_list">Diese Liste ist leer</string>
|
||||
<string name="instance_signup_closed">Dieser Server akzeptiert keine neuen Registrierungen.</string>
|
||||
<string name="text_copied">In die Zwischenablage kopiert</string>
|
||||
@@ -309,6 +312,8 @@
|
||||
<string name="login_title">Willkommen zurück</string>
|
||||
<string name="login_subtitle">Melde dich mit dem Server an, auf dem du dein Account erstellt hast.</string>
|
||||
<string name="server_url">Serveradresse</string>
|
||||
<string name="server_filter_any_language">Beliebige Sprache</string>
|
||||
<string name="server_filter_manual_review">Manuelle Überprüfung</string>
|
||||
<string name="server_filter_region_europe">Europa</string>
|
||||
<string name="server_filter_region_north_america">Nordamerika</string>
|
||||
<string name="server_filter_region_south_america">Südamerika</string>
|
||||
@@ -316,6 +321,7 @@
|
||||
<string name="server_filter_region_asia">Asien</string>
|
||||
<string name="server_filter_region_oceania">Ozeanien</string>
|
||||
<string name="not_accepting_new_members">Keine Aufnahme neuer Mitglieder</string>
|
||||
<string name="category_special_interests">Besondere Interessen</string>
|
||||
<string name="signup_passwords_dont_match">Passwörter stimmen nicht überein</string>
|
||||
<string name="profile_add_row">Zeile hinzufügen</string>
|
||||
<string name="profile_setup">Profil einrichten</string>
|
||||
@@ -580,6 +586,7 @@
|
||||
<string name="remove">Entfernen</string>
|
||||
<string name="add_list_member">Mitglied hinzufügen</string>
|
||||
<string name="search_among_people_you_follow">Suche nach Leuten, denen du folgst</string>
|
||||
<string name="add_user_to_list">Zu Listen hinzufügen/entfernen…</string>
|
||||
<string name="add_user_to_list_title">Zur Liste hinzufügen</string>
|
||||
<!-- %s is a username -->
|
||||
<string name="manage_user_lists">Listen mit %s verwalten</string>
|
||||
@@ -596,23 +603,53 @@
|
||||
</plurals>
|
||||
<string name="create_list">Liste erstellen</string>
|
||||
<string name="step_x_of_y">Schritt %1$d von %2$d</string>
|
||||
<string name="create">Erstellen</string>
|
||||
<string name="manage_list_members">Listenmitglieder verwalten</string>
|
||||
<string name="list_no_members">Noch keine Mitglieder</string>
|
||||
<string name="list_find_users">Benutzer zum Hinzufügen finden</string>
|
||||
<string name="reply_to_user">Antwort an %s</string>
|
||||
<string name="posted_at">Veröffentlicht um %s Uhr</string>
|
||||
<string name="non_mutual_sheet_title">Hallo, neues Bindeglied!</string>
|
||||
<string name="non_mutual_sheet_text">Es wirkt so, als ob Sie jemandem antworten, der Ihnen noch nicht folgt. Lassen Sie uns einen ersten guten Eindruck machen.</string>
|
||||
<string name="got_it">Verstanden</string>
|
||||
<string name="dont_remind_again">Nicht erneut erinnern</string>
|
||||
<!-- %s is a time interval ("5 months") -->
|
||||
<string name="old_post_sheet_title">Dieser Beitrag ist %s alt</string>
|
||||
<string name="old_post_sheet_text">Sie können immer noch antworten, aber es ist möglicherweise nicht mehr relevant.</string>
|
||||
<plurals name="x_months">
|
||||
<item quantity="one">%,d Monat</item>
|
||||
<item quantity="other">%,d Monate</item>
|
||||
</plurals>
|
||||
<string name="more_than_two_years">mehr als 2 Jahre</string>
|
||||
<string name="non_mutual_title1">Bleiben Sie respektvoll & relevant</string>
|
||||
<string name="non_mutual_text1">Vergewissern Sie sich, dass Ihre Antwort höflich und relevant ist.</string>
|
||||
<string name="non_mutual_text2">Eine freundliche Ausdrucksweise wird immer gewürdigt.</string>
|
||||
<string name="non_mutual_title3">Sei offen</string>
|
||||
<string name="non_mutual_text3">Jedermanns Gesprächsstil ist einzigartig. Seien Sie bereit, sich anzupassen.</string>
|
||||
<string name="make_profile_discoverable">Mein Profil entdeckbar machen</string>
|
||||
<string name="discoverability">Auffindbarkeit</string>
|
||||
<string name="discoverability_help">Wenn Sie sich für die Auffindbarkeit auf Mastodon entscheiden, können Ihre Beiträge in Suchergebnissen und Trends erscheinen.\n\nIhr Profil kann Personen mit ähnlichen Interessen empfohlen werden.\n\nWidersprechen versteckt Ihr Profil nicht, wenn jemand nach Ihrem Namen sucht.</string>
|
||||
<string name="app_version_copied">Versionsnummer in die Zwischenablage kopiert</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="button_favorited">Favorisiert</string>
|
||||
<string name="bookmarked">Gemerkt</string>
|
||||
<string name="join_server_x_with_invite">%s mit Einladung beitreten</string>
|
||||
<string name="expired_invite_link">Abgelaufener Einladungslink</string>
|
||||
<string name="expired_clipboard_invite_link_alert">Der Einladungslink für %1$s in Ihrer Zwischenablage ist abgelaufen und kann nicht verwendet werden, um sich anzumelden.\n\nSie können einen neuen Link von einem existierenden Benutzer anfordern, Melden Sie sich über %2$s an, oder wählen Sie einen anderen Server, um sich anzumelden.</string>
|
||||
<string name="invalid_invite_link">Ungültiger Einladungslink</string>
|
||||
<string name="mute_user_confirm_title">Benutzer stummschalten?</string>
|
||||
<string name="block_user_confirm_title">Benutzer blockieren?</string>
|
||||
<string name="user_can_see_blocked">Es wird erkennbar sein, dass dieses Profil blockiert wurde.</string>
|
||||
<string name="handle_title">Ihr Handle</string>
|
||||
<string name="handle_title_own">Dein Handle</string>
|
||||
<string name="server">Server</string>
|
||||
<string name="qr_code">QR-Code</string>
|
||||
<string name="scan_qr_code">QR-Code scannen</string>
|
||||
<!-- Shown on a button that saves a file, after it was successfully saved -->
|
||||
<string name="saved">Gespeichert</string>
|
||||
<string name="image_saved">Bild gespeichert.</string>
|
||||
<string name="video_saved">Video gespeichert.</string>
|
||||
<string name="view_file">Ansehen</string>
|
||||
<string name="tab_search">Entdecken</string>
|
||||
<!-- %s is the username -->
|
||||
</resources>
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
<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">%s σε ακολούθησε</string>
|
||||
<string name="user_sent_follow_request">%s σού έστειλε ένα αίτημα ακολούθησης</string>
|
||||
@@ -152,7 +151,6 @@
|
||||
<string name="email">Email</string>
|
||||
<string name="password">Κωδικός πρόσβασης</string>
|
||||
<string name="confirm_password">Επιβεβαίωση κωδικού πρόσβασης</string>
|
||||
<string name="password_note">Να συμπεριλάβεις κεφαλαία γράμματα, ειδικούς χαρακτήρες και αριθμούς για να αυξήσεις την ισχύ του κωδικού πρόσβασης.</string>
|
||||
<string name="category_general">Γενικά</string>
|
||||
<string name="confirm_email_title">Έλεγξε τα εισερχόμενά σου</string>
|
||||
<!-- %s is the email address -->
|
||||
@@ -166,8 +164,6 @@
|
||||
<string name="save">Αποθήκευση</string>
|
||||
<string name="add_alt_text">Προσθήκη εναλλακτικού κειμένου</string>
|
||||
<string name="visibility_public">Δημόσιο</string>
|
||||
<string name="visibility_followers_only">Μόνο ακόλουθοι</string>
|
||||
<string name="visibility_private">Μόνο άτομα που επισημαίνω</string>
|
||||
<string name="recent_searches">Πρόσφατα</string>
|
||||
<string name="skip">Παράλειψη</string>
|
||||
<string name="notification_type_follow">Νέοι ακόλουθοι</string>
|
||||
@@ -308,16 +304,13 @@
|
||||
<string name="instance_signup_closed">Αυτός ο διακομιστής δεν δέχεται νέες εγγραφές.</string>
|
||||
<string name="text_copied">Αντιγράφηκε στο πρόχειρο</string>
|
||||
<string name="add_bookmark">Σελιδοδείκτης</string>
|
||||
<string name="remove_bookmark">Αφαίρεση σελιδοδείκτη</string>
|
||||
<string name="bookmarks">Σελιδοδείκτες</string>
|
||||
<string name="your_favorites">Τα αγαπημένα σου</string>
|
||||
<string name="login_title">Καλωσόρισες και πάλι</string>
|
||||
<string name="login_subtitle">Συνδέσου με τον διακομιστή όπου δημιούργησες τον λογαριασμό σου.</string>
|
||||
<string name="server_url">URL διακομιστή</string>
|
||||
<string name="server_filter_any_language">Οποιαδήποτε γλώσσα</string>
|
||||
<string name="server_filter_instant_signup">Άμεση εγγραφή</string>
|
||||
<string name="server_filter_manual_review">Χειροκίνητη αξιολόγηση</string>
|
||||
<string name="server_filter_any_signup_speed">Οποιαδήποτε ταχύτητα εγγραφής</string>
|
||||
<string name="server_filter_region_europe">Ευρώπη</string>
|
||||
<string name="server_filter_region_north_america">Βόρεια Αμερική</string>
|
||||
<string name="server_filter_region_south_america">Νότια Αμερική</string>
|
||||
@@ -686,7 +679,7 @@
|
||||
<string name="handle_server_explanation">Το ψηφιακό τους σπίτι, όπου ζουν όλες οι αναρτήσεις τους.</string>
|
||||
<string name="handle_explanation">Από τη στιγμή που τα handles λένε ποιος είναι κάποιος και πού είναι, μπορείς να αλληλεπιδράσεις με τους ανθρώπους απ\' όλο τον κοινωνικό ιστό των <a> πλατφορμών που στηρίζονται στο ActivityPub</a>.</string>
|
||||
<string name="handle_server_explanation_own">Το ψηφιακό σπίτι σου, όπου ζουν όλες οι αναρτήσεις σου. Δεν σ\' αρέσει αυτός; Μετακινήσου σε διακομιστές ανά πάσα στιγμή και φέρε και τους ακόλουθούς σου.</string>
|
||||
<string name="handle_explanation_own">Επειδή το handle σου λέει ποιος είσαι και πού βρίσκεσαι, οι άνθρωποι μπορούν να αλληλεπιδράσουν μαζί σou στον κοινωνικό ιστό των <a>πλατφορμών που στηρίζονται στο ActivityPub</a>.</string>
|
||||
<string name="handle_explanation_own">Επειδή το handle σου λέει ποιος είσαι και πού βρίσκεσαι, οι άνθρωποι μπορούν να αλληλεπιδράσουν μαζί σου στον κοινωνικό ιστό των <a>πλατφορμών που στηρίζονται στο ActivityPub</a>.</string>
|
||||
<string name="what_is_activitypub_title">Τί είναι το ActivityPub;</string>
|
||||
<string name="what_is_activitypub">Το ActivityPub είναι σαν τη γλώσσα που μιλάει το Mastodon με άλλα κοινωνικά δίκτυα.\n\nΣου επιτρέπει να συνδεθείς και να αλληλεπιδράσεις με ανθρώπους όχι μόνο στο Mastodon, αλλά και σε διαφορετικές κοινωνικές εφαρμογές.</string>
|
||||
<string name="handle_copied">Το handle αντιγράφηκε στο πρόχειρο.</string>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<string name="preparing_auth">Preparando para autenticación…</string>
|
||||
<string name="finishing_auth">Finalizando autenticación…</string>
|
||||
<string name="user_boosted">Impulsado por %s</string>
|
||||
<string name="in_reply_to">En respuesta a %s</string>
|
||||
<string name="in_reply_to">en respuesta a %s</string>
|
||||
<string name="notifications">Notificaciones</string>
|
||||
<string name="user_followed_you">%s te ha seguido</string>
|
||||
<string name="user_sent_follow_request">%s te envió una solicitud de seguimiento</string>
|
||||
@@ -31,10 +31,13 @@
|
||||
<item quantity="other">siguiendo</item>
|
||||
</plurals>
|
||||
<string name="posts">Publicaciones</string>
|
||||
<string name="posts_and_replies">Publicaciones y respuestas</string>
|
||||
<string name="media">Multimedia</string>
|
||||
<string name="profile_about">Acerca de</string>
|
||||
<string name="button_follow">Seguir</string>
|
||||
<string name="button_following">Siguiendo</string>
|
||||
<string name="edit_profile">Editar perfil</string>
|
||||
<string name="share_user">Compartir perfil a través de…</string>
|
||||
<string name="mute_user">Silenciar a %s</string>
|
||||
<string name="unmute_user">Dejar de silenciar a %s</string>
|
||||
<string name="block_user">Bloquear a %s</string>
|
||||
@@ -141,14 +144,16 @@
|
||||
<string name="report_personal_subtitle">Aquí están tus opciones para controlar lo que ves en Mastodon:</string>
|
||||
<string name="back">Atrás</string>
|
||||
<string name="search_communities">Nombre del servidor o URL</string>
|
||||
<string name="instance_rules_title">Reglas del servidor</string>
|
||||
<string name="instance_rules_subtitle">Al continuar, aceptas seguir las siguientes reglas establecidas y aplicadas por los %s moderadores.</string>
|
||||
<string name="signup_title">Crear cuenta</string>
|
||||
<string name="display_name">Nombre</string>
|
||||
<string name="username">Nombre de usuario</string>
|
||||
<string name="email">Correo electrónico</string>
|
||||
<string name="password">Contraseña</string>
|
||||
<string name="confirm_password">Confirmar contraseña</string>
|
||||
<string name="password_note">Incluye letras mayúsculas, caracteres especiales y números para aumentar la fuerza de tu contraseña.</string>
|
||||
<string name="category_general">General</string>
|
||||
<string name="confirm_email_title">Revisa tu bandeja de entrada</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="confirm_email_subtitle">Pulsa el enlace que te hemos enviado para verificar %s. Esperaremos aquí mismo.</string>
|
||||
<string name="confirm_email_didnt_get">¿No recibiste un enlace?</string>
|
||||
@@ -160,13 +165,11 @@
|
||||
<string name="save">Guardar</string>
|
||||
<string name="add_alt_text">Añadir texto alternativo</string>
|
||||
<string name="visibility_public">Público</string>
|
||||
<string name="visibility_followers_only">Sólo seguidores</string>
|
||||
<string name="visibility_private">Solo personas mencionadas</string>
|
||||
<string name="recent_searches">Recientes</string>
|
||||
<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">Retoots</string>
|
||||
<string name="notification_type_reblog">Impulsos</string>
|
||||
<string name="notification_type_mention">Menciones</string>
|
||||
<string name="notification_type_poll">Encuestas</string>
|
||||
<string name="choose_account">Elegir cuenta</string>
|
||||
@@ -183,6 +186,7 @@
|
||||
<string name="theme_dark">Oscuro</string>
|
||||
<string name="settings_behavior">Comportamiento</string>
|
||||
<string name="settings_gif">Reproducir avatares animados y emoji</string>
|
||||
<string name="settings_custom_tabs">Abrir enlaces en</string>
|
||||
<string name="settings_notifications">Notificaciones</string>
|
||||
<string name="settings_contribute">Contribuir a Mastodon</string>
|
||||
<string name="settings_tos">Términos del servicio</string>
|
||||
@@ -194,9 +198,9 @@
|
||||
<string name="sensitive_content_explain">El autor ha marcado este medio como sensible.</string>
|
||||
<string name="avatar_description">Ir al perfil de %s</string>
|
||||
<string name="more_options">Más opciones</string>
|
||||
<string name="new_post">Nuevo post</string>
|
||||
<string name="new_post">Nueva publicación</string>
|
||||
<string name="button_reply">Responder</string>
|
||||
<string name="button_reblog">Retootear</string>
|
||||
<string name="button_reblog">Impulsar</string>
|
||||
<string name="button_favorite">Favorito</string>
|
||||
<string name="button_share">Compartir</string>
|
||||
<string name="media_no_description">Medios sin descripción</string>
|
||||
@@ -211,11 +215,13 @@
|
||||
<string name="followed_user">Ahora estás siguiendo a %s</string>
|
||||
<string name="following_user_requested">%s solicitó seguirte</string>
|
||||
<string name="open_in_browser">Abrir en el navegador</string>
|
||||
<string name="hide_boosts_from_user">Ocultar impulsos</string>
|
||||
<string name="show_boosts_from_user">Mostrar impulsos</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="signup_reason_note">Esto nos ayudará a revisar tu solicitud.</string>
|
||||
<string name="clear">Borrar</string>
|
||||
<string name="profile_header">Imagen de cabecera</string>
|
||||
<string name="profile_picture">Imagen de perfil</string>
|
||||
<string name="profile_picture">Foto de perfil</string>
|
||||
<string name="reorder">Reordenar</string>
|
||||
<string name="download">Descargar</string>
|
||||
<string name="permission_required">Permiso requerido</string>
|
||||
@@ -233,6 +239,7 @@
|
||||
<string name="recommended_accounts_info_banner">Es posible que te gusten estas cuentas basadas en otras que sigues.</string>
|
||||
<string name="see_new_posts">Nuevas publicaciones</string>
|
||||
<string name="load_missing_posts">Cargar publicaciones faltantes</string>
|
||||
<string name="follow_back">Seguir también</string>
|
||||
<string name="button_follow_pending">Pendiente</string>
|
||||
<string name="follows_you">Te sigue</string>
|
||||
<string name="manually_approves_followers">Aprueba seguidores manualmente</string>
|
||||
@@ -250,8 +257,8 @@
|
||||
<item quantity="other">%,d favoritos</item>
|
||||
</plurals>
|
||||
<plurals name="x_reblogs">
|
||||
<item quantity="one">%,d retooteo</item>
|
||||
<item quantity="other">%,d retooteos</item>
|
||||
<item quantity="one">%,d impulso</item>
|
||||
<item quantity="other">%,d impulsos</item>
|
||||
</plurals>
|
||||
<string name="time_now">ahora</string>
|
||||
<string name="edit_history">Editar historial</string>
|
||||
@@ -291,15 +298,20 @@
|
||||
<!-- %s is file size -->
|
||||
<string name="download_update">Descargar (%s)</string>
|
||||
<string name="install_update">Instalar</string>
|
||||
<string name="privacy_policy_title">Tu privacidad</string>
|
||||
<string name="privacy_policy_subtitle">Aunque la aplicación Mastodon no recoge ningún dato, el servidor al que se registra puede tener una política diferente.\n\nSi no está de acuerdo con la política para %s, puede volver atrás y elegir un servidor diferente.</string>
|
||||
<string name="i_agree">De acuerdo</string>
|
||||
<string name="empty_list">Esta lista está vacía</string>
|
||||
<string name="instance_signup_closed">Este servidor no acepta altas nuevas.</string>
|
||||
<string name="text_copied">Copiado en el portapapeles</string>
|
||||
<string name="add_bookmark">Marcadores</string>
|
||||
<string name="remove_bookmark">Eliminar marcador</string>
|
||||
<string name="add_bookmark">Añadir marcador</string>
|
||||
<string name="bookmarks">Marcadores</string>
|
||||
<string name="login_subtitle">Inicie sesión con el servidor donde creó su cuenta.</string>
|
||||
<string name="your_favorites">Tus favoritos</string>
|
||||
<string name="login_title">Bienvenido/da de nuevo</string>
|
||||
<string name="login_subtitle">Inicia sesión con el servidor donde creaste tu cuenta.</string>
|
||||
<string name="server_url">URL del servidor</string>
|
||||
<string name="server_filter_any_language">Cualquier idioma</string>
|
||||
<string name="server_filter_manual_review">Revisión manual</string>
|
||||
<string name="server_filter_region_europe">Europa</string>
|
||||
<string name="server_filter_region_north_america">Norteamérica</string>
|
||||
<string name="server_filter_region_south_america">Suramérica</string>
|
||||
@@ -307,6 +319,7 @@
|
||||
<string name="server_filter_region_asia">Asia</string>
|
||||
<string name="server_filter_region_oceania">Oceanía</string>
|
||||
<string name="not_accepting_new_members">No se aceptan nuevos miembros</string>
|
||||
<string name="category_special_interests">Intereses especiales</string>
|
||||
<string name="signup_passwords_dont_match">Las contraseñas no coinciden</string>
|
||||
<string name="profile_add_row">Añadir fila</string>
|
||||
<string name="profile_setup">Configuración del perfil</string>
|
||||
@@ -431,7 +444,7 @@
|
||||
<string name="server_rules">Reglas</string>
|
||||
<string name="server_administrator">Administrador</string>
|
||||
<string name="send_email_to_server_admin">Contacta a un administrador</string>
|
||||
<string name="notifications_disabled_in_system">Active las notificaciones desde la configuración de su dispositivo para ver actualizaciones desde cualquier lugar.</string>
|
||||
<string name="notifications_disabled_in_system">Activa las notificaciones desde la configuración de tu dispositivo para ver actualizaciones desde cualquier lugar.</string>
|
||||
<string name="settings_even_more">Aún más ajustes</string>
|
||||
<string name="settings_show_cws">Mostrar advertencias de contenido</string>
|
||||
<string name="settings_hide_sensitive_media">Esconder contenidos marcados como explícitos</string>
|
||||
@@ -496,12 +509,12 @@
|
||||
<string name="filter_context_profiles">Profiles</string>
|
||||
<string name="settings_filter_title">Título</string>
|
||||
<string name="settings_delete_filter_title">¿Eliminar filtro “%s”?</string>
|
||||
<string name="settings_delete_filter_confirmation">Este filtro se eliminará de su cuenta en todos sus dispositivos.</string>
|
||||
<string name="settings_delete_filter_confirmation">Este filtro se eliminará de tu cuenta en todos tus dispositivos.</string>
|
||||
<string name="add_muted_word">Añadir palabra silenciada</string>
|
||||
<string name="edit_muted_word">Editar palabra silenciada</string>
|
||||
<string name="add">Añadir</string>
|
||||
<string name="filter_word_or_phrase">Palabra o frase</string>
|
||||
<string name="filter_add_word_help">Las palabras no distinguen letras mayúsculas y solo reconocen palabras completas.\n\nSi filtras la palabra \"Agua\", se ocultarán posteos que incluyan \"agua\" o \"aGua\", pero no los que contengan \"paraguas\".</string>
|
||||
<string name="filter_add_word_help">Las palabras no distinguen letras mayúsculas y solo reconocen palabras completas.\n\nSi filtras la palabra \"Agua\", se ocultarán publicaciones que incluyan \"agua\" o \"aGua\", pero no los que contengan \"paraguas\".</string>
|
||||
<string name="settings_delete_filter_word">¿Eliminar palabra “%s”?</string>
|
||||
<string name="enter_selection_mode">Seleccionar</string>
|
||||
<string name="select_all">Seleccionar todo</string>
|
||||
@@ -575,6 +588,7 @@
|
||||
<string name="remove">Eliminar</string>
|
||||
<string name="add_list_member">Añadir miembro</string>
|
||||
<string name="search_among_people_you_follow">Buscar entre las personas a las que sigues</string>
|
||||
<string name="add_user_to_list">Añadir/quitar de listas…</string>
|
||||
<string name="add_user_to_list_title">Añadir a lista</string>
|
||||
<!-- %s is a username -->
|
||||
<string name="manage_user_lists">Administrar las listas en las que %s aparece</string>
|
||||
@@ -641,6 +655,7 @@ Mientras más personas sigas, más activo e interesante será.</string>
|
||||
<string name="mute_user_confirm_title">¿Silenciar usuario?</string>
|
||||
<string name="user_wont_know_muted">No sabrán que han sido silenciados.</string>
|
||||
<string name="user_can_still_see_your_posts">Todavía pueden ver tus publicaciones, pero no verás las suyas.</string>
|
||||
<string name="you_wont_see_user_mentions">No verás publicaciones que lo mencionen.</string>
|
||||
<string name="user_can_mention_and_follow_you">Pueden mencionarte y seguirte, pero no los verás.</string>
|
||||
<string name="unmuted_user_x">%s ya no está silenciado</string>
|
||||
<string name="block_user_confirm_title">¿Bloquear usuario?</string>
|
||||
@@ -651,10 +666,70 @@ Mientras más personas sigas, más activo e interesante será.</string>
|
||||
<string name="block_domain_confirm_title">¿Bloquear dominio?</string>
|
||||
<string name="do_block_server">Bloquear servidor</string>
|
||||
<string name="block_user_x_instead">Bloquear a %s en su lugar</string>
|
||||
<string name="users_cant_see_blocked">No verás publicaciones ni notificaciones de usuarios en este servidor.</string>
|
||||
<string name="you_wont_see_server_posts">No verás ninguna publicación de usuarios en este servidor.</string>
|
||||
<string name="server_followers_will_be_removed">Tus seguidores de este servidor serán eliminados.</string>
|
||||
<string name="server_cant_mention_or_follow_you">Nadie de este servidor puede seguirte.</string>
|
||||
<string name="server_can_interact_with_older">Las personas de este servidor pueden interactuar con tus mensajes antiguos.</string>
|
||||
<string name="unblocked_domain_x">El dominio %s ya no está bloqueado</string>
|
||||
<string name="handle_help_title">¿En qué consiste el alias?</string>
|
||||
<string name="handle_title">Su alias</string>
|
||||
<string name="handle_username_explanation">Su identificador único en su servidor. Es posible encontrar usuarios con el mismo nombre de usuario en diferentes servidores.</string>
|
||||
<string name="handle_title_own">Tu alias</string>
|
||||
<string name="handle_username_explanation_own">Tu identificador único en este servidor. Es posible encontrar usuarios con el mismo nombre de usuario en diferentes servidores.</string>
|
||||
<string name="server">Servidor</string>
|
||||
<string name="handle_server_explanation">Su hogar digital, donde residen todas sus publicaciones.</string>
|
||||
<string name="handle_explanation">Los alias indican quiénes son y dónde se encuentran, y gracias a ellos puedes interactuar con personas a través de las redes sociales compatibles con <a>ActivityPub</a>.</string>
|
||||
<string name="handle_server_explanation_own">Tu hogar digital, donde residen todas tus publicaciones. ¿No te gusta este sitio? Muévete a otro servidor en cualquier momento y llévate a tus seguidores.</string>
|
||||
<string name="handle_explanation_own">Los alias indican quién eres y dónde te encuentras, y gracias a ellos puedes interactuar con personas a través de las redes sociales compatibles con <a>ActivityPub</a>.</string>
|
||||
<string name="what_is_activitypub_title">¿Qué es ActivityPub?</string>
|
||||
<string name="what_is_activitypub">ActivityPub es como el idioma que Mastodon habla con otras redes sociales.\n\nTe permite conectar e interactuar con personas no sólo en Mastodon, sino también a través de diferentes aplicaciones sociales.</string>
|
||||
<string name="handle_copied">Alias copiado al portapapeles.</string>
|
||||
<string name="qr_code">Código QR</string>
|
||||
<string name="scan_qr_code">Escanear código QR</string>
|
||||
<!-- Shown on a button that saves a file, after it was successfully saved -->
|
||||
<string name="saved">Guardado</string>
|
||||
<string name="image_saved">Imagen guardada.</string>
|
||||
<string name="video_saved">Vídeo guardado.</string>
|
||||
<string name="view_file">Ver</string>
|
||||
<string name="share_sheet_preview_profile">%s en Mastodon</string>
|
||||
<string name="share_sheet_preview_post">%1$s en Mastodon: “%2$s”</string>
|
||||
<string name="copy_profile_link">Copiar enlace de perfil</string>
|
||||
<string name="in_app_browser">Navegador en la app</string>
|
||||
<string name="system_browser">Navegador del sistema</string>
|
||||
<string name="add_muted_word_short">Añadir palabra</string>
|
||||
<string name="tab_home">Inicio</string>
|
||||
<string name="tab_search">Explorar</string>
|
||||
<string name="tab_profile">Perfil</string>
|
||||
<string name="pin_post">Anclar en el perfil</string>
|
||||
<string name="unpin_post">Desanclar del perfil</string>
|
||||
<string name="post_pinned">La publicación ha sido anclada</string>
|
||||
<string name="post_unpinned">La publicación ha sido desanclada</string>
|
||||
<!-- %s is the username -->
|
||||
<string name="enable_new_post_notifications">Notificarme cuando %s publique algo</string>
|
||||
<string name="disable_new_post_notifications">Dejar de notificarme cuando %s publique algo</string>
|
||||
<string name="new_post_notifications_enabled">Recibirás notificaciones de nuevas publicaciones.</string>
|
||||
<string name="new_post_notifications_disabled">Ya no recibirás notificaciones de nuevas publicaciones.</string>
|
||||
<string name="mute_conversation">Silenciar conversación</string>
|
||||
<string name="unmute_conversation">Dejar de silenciar conversación</string>
|
||||
<string name="visibility_unlisted">Público silencioso</string>
|
||||
<string name="filtered_notifications">Notificaciones filtradas</string>
|
||||
<string name="filter_notifications">Filtrar notificaciones de...</string>
|
||||
<string name="notification_filter_following">Personas que no sigues</string>
|
||||
<string name="notification_filter_following_explanation">Hasta que los apruebes manualmente</string>
|
||||
<string name="notification_filter_followers">Personas que no te siguen</string>
|
||||
<string name="notification_filter_followers_explanation">Incluyendo a las personas que te han seguido por menos de 3 días</string>
|
||||
<string name="notification_filter_new_accounts">Cuentas nuevas</string>
|
||||
<string name="notification_filter_new_accounts_explanation">Creadas en los últimos 30 días</string>
|
||||
<string name="notification_filter_mentions">Menciones privadas no solicitadas</string>
|
||||
<string name="notification_filter_mentions_explanation">Filtradas a menos que sea en respuesta a tu propia mención o sigas al remitente</string>
|
||||
<string name="allow_notifications">Permitir notificaciones</string>
|
||||
<string name="mute_notifications">Descartar solicitud de notificación</string>
|
||||
<plurals name="x_people_you_may_know">
|
||||
<item quantity="one">%,d persona a la que tal vez conoces</item>
|
||||
<item quantity="other">%,d personas a las que tal vez conoces</item>
|
||||
</plurals>
|
||||
<string name="notifications_from_user">Notificaciones de %s</string>
|
||||
<string name="notifications_muted">Las notificaciones de %s han sido descartadas.</string>
|
||||
<string name="notifications_allowed">Ahora aparecerá %s en tu lista de notificaciones.</string>
|
||||
</resources>
|
||||
|
||||
@@ -152,7 +152,6 @@
|
||||
<string name="email">Eposta</string>
|
||||
<string name="password">Pasahitza</string>
|
||||
<string name="confirm_password">Berretsi pasahitza</string>
|
||||
<string name="password_note">Sartu letra larriak, karaktere bereziak eta zenbakiak zure pasahitzaren segurtasuna areagotzeko.</string>
|
||||
<string name="category_general">Orokorra</string>
|
||||
<string name="confirm_email_title">Begiratu zure sarrera-ontzia</string>
|
||||
<!-- %s is the email address -->
|
||||
@@ -166,8 +165,8 @@
|
||||
<string name="save">Gorde</string>
|
||||
<string name="add_alt_text">Gehitu ordezko testua</string>
|
||||
<string name="visibility_public">Publikoa</string>
|
||||
<string name="visibility_followers_only">Jarraitzaileak soilik</string>
|
||||
<string name="visibility_private">Aipatzen dudan jendea soilik</string>
|
||||
<string name="visibility_followers_only">Jarraitzaileak</string>
|
||||
<string name="visibility_private">Jende jakina</string>
|
||||
<string name="recent_searches">Azkenaldikoak</string>
|
||||
<string name="skip">Saltatu</string>
|
||||
<string name="notification_type_follow">Jarraitzaile berriak</string>
|
||||
@@ -351,7 +350,7 @@
|
||||
<string name="verified_link">Lotura egiaztatua</string>
|
||||
<string name="show">Erakutsi</string>
|
||||
<string name="hide">Ezkutatu</string>
|
||||
<string name="join_default_server">Batu %s -ra</string>
|
||||
<string name="join_default_server">Batu %s-(e)ra</string>
|
||||
<string name="pick_server">Aukeratu beste zerbitzari bat</string>
|
||||
<string name="signup_or_login">edo</string>
|
||||
<string name="learn_more">Ikasi gehiago</string>
|
||||
@@ -738,4 +737,24 @@ Zenbat eta jende gehiago jarraitu, orduan eta aktiboagoa eta interesgarriagoa iz
|
||||
<string name="notifications_from_user">%s erabiltzailearen jakinarazpenak</string>
|
||||
<string name="notifications_muted">%s(r)en jakinarazpenak baztertu dira.</string>
|
||||
<string name="notifications_allowed">%s jakinarazpen-zerrendan agertuko da orain.</string>
|
||||
<string name="visibility_subtitle_public">Mastodonen dagoen edo ez dagoen edonor</string>
|
||||
<string name="visibility_subtitle_unlisted">Tontakeria algoritmiko gutxiago</string>
|
||||
<string name="visibility_subtitle_followers">Soilik jarraitzaileak</string>
|
||||
<string name="visibility_subtitle_private">Argitalpen honetan aipatutako denak</string>
|
||||
<string name="view_boosts">Ikusi bultzadak</string>
|
||||
<string name="view_favorites">Ikusi Gogokoak</string>
|
||||
<string name="undo_reblog">Desegin bultzada</string>
|
||||
<string name="undo_favorite">Desegin Gogokoa</string>
|
||||
<string name="could_not_reach_server">Ezin izan da zerbitzariarekin komunikatu. Zure konexioa egiaztatu eta saiatu berriro?</string>
|
||||
<string name="connection_timed_out">Eskaerak denbora-muga gainditu du. Zure konexioa egiaztatu eta saiatu berriro?</string>
|
||||
<string name="server_error">Zerbait gaizki atera da zure zerbitzariarekin hitz egiterakoan. Seguruenik, ez da zure errua. Berriro saiatu?</string>
|
||||
<string name="not_found">Ezaba zitekeen, edo agian ez zen inoiz existitu.</string>
|
||||
<string name="no_servers_found">Ez da aurkitu “%s”-(r)entzako zerbitzaririk</string>
|
||||
<string name="signup_username_taken">Erabiltzaile-izen hau hartuta dago. Probatu beste bat edo <a>aukeratu beste zerbitzari bat</a>.</string>
|
||||
<string name="signup_email_invalid">Ez dirudi baliozko helbide elektronikoa.</string>
|
||||
<string name="signup_email_taken">Helbide elektronikoa dagoeneko erabilita dago. <a>Pasahitza ahaztu duzu</a>?</string>
|
||||
<plurals name="x_new_notifications">
|
||||
<item quantity="one">Jakinarazpen berri %,d</item>
|
||||
<item quantity="other">%,d jakinarazpen berri</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
||||
@@ -152,7 +152,6 @@
|
||||
<string name="email">رایانامه</string>
|
||||
<string name="password">گذرواژه</string>
|
||||
<string name="confirm_password">تأیید گذرواژه</string>
|
||||
<string name="password_note">برای افزایش قدرت گذرواژه خود، حروف بزرگ، کاراکترهای خاص و اعداد را اضافه کنید.</string>
|
||||
<string name="category_general">عمومی</string>
|
||||
<string name="confirm_email_title">صندوق ورودیتان را بررسی کنید</string>
|
||||
<!-- %s is the email address -->
|
||||
@@ -166,8 +165,8 @@
|
||||
<string name="save">ذخیره</string>
|
||||
<string name="add_alt_text">اضافه کردن متن جایگزین</string>
|
||||
<string name="visibility_public">عمومی</string>
|
||||
<string name="visibility_followers_only">فقط پیگیران</string>
|
||||
<string name="visibility_private">فقط افرادی نامبرده</string>
|
||||
<string name="visibility_followers_only">پیگیرندگان</string>
|
||||
<string name="visibility_private">افراد مشخّص</string>
|
||||
<string name="recent_searches">اخیر</string>
|
||||
<string name="skip">بعدی</string>
|
||||
<string name="notification_type_follow">پیگیرندگان جدید</string>
|
||||
@@ -631,6 +630,11 @@
|
||||
<string name="non_mutual_text2">لحن مثبت همیشه قدردانی میشود.</string>
|
||||
<string name="non_mutual_title3">باز باش</string>
|
||||
<string name="make_profile_discoverable">نمایه من را قابل کشف کن</string>
|
||||
<string name="discoverability">قابلیت کشف</string>
|
||||
<string name="app_version_copied">شماره نسخه به تختهگیره رونوشت شد</string>
|
||||
<string name="onboarding_recommendations_intro">شما خوراک خانه خود را مدیریت میکنید.
|
||||
هرچه افراد بیشتری را پیگیری کنید، فعالتر و جالبتر خواهد بود.</string>
|
||||
<string name="onboarding_recommendations_title">خوراک خانه خود را شخصی سازی کنید</string>
|
||||
<string name="article_by_author">توسط %s</string>
|
||||
<string name="info">اطلاعات</string>
|
||||
<string name="button_reblogged">تقویت شد</string>
|
||||
@@ -655,9 +659,14 @@
|
||||
<string name="you_wont_see_server_posts">شما هیچ پستی از افراد این سرور نخواهید دید.</string>
|
||||
<string name="server_followers_will_be_removed">دنبال کننده های شما در این سرور حذف خواهند شد.</string>
|
||||
<string name="unblocked_domain_x">مسدودیت دامنه %s رفع شد</string>
|
||||
<string name="handle_help_title">شناسه چیست؟</string>
|
||||
<string name="handle_title">شناسه آنها</string>
|
||||
<string name="handle_title_own">شناسه شما</string>
|
||||
<string name="handle_username_explanation_own">شناسه منحصر به فرد شما در این کارساز. امکان یافتن کاربرانی با نام کاربری مشابه در کارسازهای مختلف وجود دارد.</string>
|
||||
<string name="server">کارساز</string>
|
||||
<string name="handle_server_explanation">خانه دیجیتالی آنها، جایی که همه فرستههای آنها در آن زندگی می کنند.</string>
|
||||
<string name="handle_explanation">از آنجایی که شناسهها میگوید کسی کیست و کجاست، میتوانید با افراد در سراسر وب اجتماعی <a>سکوهای قدرت گرفته از اکتیویتی پاپ</a> تعامل داشته باشید.</string>
|
||||
<string name="handle_explanation_own">از آنجایی که شناسه شما میگوید شما کی هستید و کجا هستید، افراد میتوانند در <a>سکوهای قدرت گرفته از اکتیویتی پاپ</a> از وب اجتماعی با شما تعامل داشته باشند.</string>
|
||||
<string name="what_is_activitypub_title">اکتیویتی پاپ چیست؟</string>
|
||||
<string name="qr_code">کد QR</string>
|
||||
<string name="scan_qr_code">اسکن کد QR</string>
|
||||
@@ -681,6 +690,8 @@
|
||||
<string name="post_unpinned">سنجاق فرسته برداشته شد</string>
|
||||
<!-- %s is the username -->
|
||||
<string name="enable_new_post_notifications">هنگامی که %s فرسته میفرستد مرا آگاه کن</string>
|
||||
<string name="new_post_notifications_enabled">برای فرستههای جدید آگاهی دریافت خواهید کرد.</string>
|
||||
<string name="new_post_notifications_disabled">دیگر برای فرستههای جدید آگاهی دریافت نخواهید کرد.</string>
|
||||
<string name="mute_conversation">خموشاندن گفتوگو</string>
|
||||
<string name="unmute_conversation">رفع خموشی گفتوگو</string>
|
||||
<string name="visibility_unlisted">عمومی ساکت</string>
|
||||
@@ -692,4 +703,16 @@
|
||||
<string name="allow_notifications">اجازه دادن به آگاهیها</string>
|
||||
<string name="mute_notifications">رد درخواست آگاهی</string>
|
||||
<string name="notifications_from_user">آگاهیها از %s</string>
|
||||
<string name="visibility_subtitle_public">هرکسی در و بیرون از ماستودون</string>
|
||||
<string name="visibility_subtitle_unlisted">سروصدای الگوریتمی کمتر</string>
|
||||
<string name="visibility_subtitle_followers">تنها پیگیرندگانتان</string>
|
||||
<string name="visibility_subtitle_private">هرکسی که در فرسته نام برده شده</string>
|
||||
<string name="view_boosts">مشاهده تقویتّها</string>
|
||||
<string name="view_favorites">دیدن برگزیدهها</string>
|
||||
<string name="undo_reblog">لغو تقویت</string>
|
||||
<string name="undo_favorite">لغو برگزیدگی</string>
|
||||
<plurals name="x_new_notifications">
|
||||
<item quantity="one">%,d اعلان جدید</item>
|
||||
<item quantity="other">%,d اعلان جدید</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
<string name="preparing_auth">Valmistellaan todennusta…</string>
|
||||
<string name="finishing_auth">Viimeistellään todennusta…</string>
|
||||
<string name="user_boosted">%s tehosti</string>
|
||||
<string name="in_reply_to">Vastaus käyttäjälle %s</string>
|
||||
<string name="notifications">Ilmoitukset</string>
|
||||
<string name="user_followed_you">%s seurasi sinua</string>
|
||||
<string name="user_sent_follow_request">%s lähetti sinulle seurauspyynnön</string>
|
||||
@@ -147,7 +146,6 @@
|
||||
<string name="email">Sähköposti</string>
|
||||
<string name="password">Salasana</string>
|
||||
<string name="confirm_password">Vahvista salasana</string>
|
||||
<string name="password_note">Sisällytä suuraakkoset, erikoismerkit, ja numerot, jotta voit lisätä salasanan voimaa.</string>
|
||||
<string name="category_general">Yleinen</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="confirm_email_subtitle">Napauta lähettämäämme linkkiä vahvistaaksesi tunnuksen %s. Odotamme täällä.</string>
|
||||
@@ -160,8 +158,6 @@
|
||||
<string name="save">Tallenna</string>
|
||||
<string name="add_alt_text">Lisää selitys</string>
|
||||
<string name="visibility_public">Julkinen</string>
|
||||
<string name="visibility_followers_only">Vain seuraajat</string>
|
||||
<string name="visibility_private">Vain mainitsemani tilit</string>
|
||||
<string name="recent_searches">Viimeisimmät</string>
|
||||
<string name="skip">Ohita</string>
|
||||
<string name="notification_type_follow">Uudet seuraajat</string>
|
||||
@@ -296,7 +292,6 @@
|
||||
<string name="instance_signup_closed">Tämä palvelin ei hyväksy uusia rekisteröintejä.</string>
|
||||
<string name="text_copied">Kopioitu leikepöydälle</string>
|
||||
<string name="add_bookmark">Kirjanmerkki</string>
|
||||
<string name="remove_bookmark">Poista kirjanmerkki</string>
|
||||
<string name="bookmarks">Kirjanmerkit</string>
|
||||
<string name="login_subtitle">Kirjaudu sisään palvelimella, jossa olet luonut tilisi.</string>
|
||||
<string name="server_url">Palvelimen URL-osoite</string>
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
<string name="ok">OK</string>
|
||||
<string name="preparing_auth">Paghahanda para sa pagpapatunay…</string>
|
||||
<string name="finishing_auth">Natapos na ang pagpapatunay…</string>
|
||||
<string name="in_reply_to">Bilang tugon kay %s</string>
|
||||
<string name="notifications">Mga abiso</string>
|
||||
<string name="share_toot_title">Ibahagi</string>
|
||||
<string name="settings">Mga setting</string>
|
||||
@@ -115,7 +114,6 @@
|
||||
<string name="unfollow_user">I-Unfollow %s</string>
|
||||
<string name="unfollow">I-unfollow</string>
|
||||
<string name="back">Bumalik</string>
|
||||
<string name="password_note">Isama ang mga malalaking titik, mga espesyal na character, at mga numero para madagdagan ang lakas ng iyong password.</string>
|
||||
<string name="category_general">Pangkalahatan</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="resend">Muling ipadala</string>
|
||||
@@ -125,7 +123,6 @@
|
||||
<string name="save">I-save</string>
|
||||
<string name="add_alt_text">Magdagdag ng teksto ng alt</string>
|
||||
<string name="visibility_public">Publiko</string>
|
||||
<string name="visibility_followers_only">Mga tagasunod lamang</string>
|
||||
<string name="skip">Laktawan</string>
|
||||
<string name="notification_type_follow">Mga Bagong Follower</string>
|
||||
<string name="notification_type_favorite">Mga Paborito</string>
|
||||
@@ -233,7 +230,6 @@
|
||||
<string name="instance_signup_closed">Ang server na ito ay hindi tumatanggap ng mga bagong pagrerehistro.</string>
|
||||
<string name="text_copied">Kinopya sa clipboard</string>
|
||||
<string name="add_bookmark">Bookmark</string>
|
||||
<string name="remove_bookmark">Alisin ang bookmark</string>
|
||||
<string name="bookmarks">Mga bookmark</string>
|
||||
<string name="login_subtitle">Mag-Log in gamit ang server kung saan mo nilikha ang iyong account.</string>
|
||||
<string name="server_url">Ang URL ng server</string>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user