Compare commits
270 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dffbff97c | ||
|
|
efb8cd565b | ||
|
|
1f5bdb975b | ||
|
|
22dfc33974 | ||
|
|
6915d19fb4 | ||
|
|
ad2ef39ace | ||
|
|
3cff655e6f | ||
|
|
ed86a5a3e8 | ||
|
|
f329435f51 | ||
|
|
6a6a80bcd7 | ||
|
|
62e4983f02 | ||
|
|
6dfd991e87 | ||
|
|
e205462bf4 | ||
|
|
03f341f6f8 | ||
|
|
b9b08c5ea7 | ||
|
|
2b5498ff5d | ||
|
|
84b058873d | ||
|
|
fcf5c0822e | ||
|
|
53c3da6a3d | ||
|
|
68371c9a0f | ||
|
|
e7295aac07 | ||
|
|
ae7f65954a | ||
|
|
350a73c3eb | ||
|
|
66d8ba9b5d | ||
|
|
f944b12f45 | ||
|
|
61928a1cf0 | ||
|
|
f06196802e | ||
|
|
e162833ad7 | ||
|
|
936ffdc793 | ||
|
|
0bbf6abc0c | ||
|
|
5552dc2ac6 | ||
|
|
a65d6fbeb3 | ||
|
|
43612ffbc1 | ||
|
|
971881bbd3 | ||
|
|
390cc6b65d | ||
|
|
ee31288769 | ||
|
|
401986af29 | ||
|
|
e41e89c5cd | ||
|
|
53de0cfc63 | ||
|
|
e68481395f | ||
|
|
9a361e0688 | ||
|
|
b8cce74824 | ||
|
|
f1ad6fc511 | ||
|
|
2aa4cc1a88 | ||
|
|
fb17ba4777 | ||
|
|
6e3c464c97 | ||
|
|
640e5163a8 | ||
|
|
fdd3f2f398 | ||
|
|
dfc55a13b8 | ||
|
|
5a83b79ac2 | ||
|
|
7d954ab3c2 | ||
|
|
ec0b830f4f | ||
|
|
26256b67d3 | ||
|
|
2f9d60b9c0 | ||
|
|
499a325bc8 | ||
|
|
97ca2634a0 | ||
|
|
6630f0f8da | ||
|
|
129b253176 | ||
|
|
c2382d065e | ||
|
|
085264755a | ||
|
|
baac955e52 | ||
|
|
4a0501209a | ||
|
|
e471b36d39 | ||
|
|
5c2961cf7c | ||
|
|
6e980f17c6 | ||
|
|
2860ce8755 | ||
|
|
ca25a868a0 | ||
|
|
74f3bd5905 | ||
|
|
e0a53b4296 | ||
|
|
c20f043f38 | ||
|
|
daf3005178 | ||
|
|
d17e24faae | ||
|
|
0cd17accf9 | ||
|
|
65f7b97e60 | ||
|
|
c7324285f3 | ||
|
|
6bc795ebea | ||
|
|
f2616cdd58 | ||
|
|
d50f65ffd8 | ||
|
|
b39b2d0544 | ||
|
|
cdaaa91bcc | ||
|
|
109dca0b8a | ||
|
|
ee87da564b | ||
|
|
b143559a0f | ||
|
|
9b89727c80 | ||
|
|
68a252c85c | ||
|
|
d99cb91e89 | ||
|
|
38879cd2fe | ||
|
|
af4d98a48b | ||
|
|
39bb93d650 | ||
|
|
0a3568f424 | ||
|
|
e0b45720f0 | ||
|
|
f5b7024bb5 | ||
|
|
f1bfa1f598 | ||
|
|
653304f9a4 | ||
|
|
3d416a038a | ||
|
|
e0eeb87182 | ||
|
|
2570a86da9 | ||
|
|
7b110f16b3 | ||
|
|
d170e87325 | ||
|
|
4a60f0c576 | ||
|
|
4b5e9d604c | ||
|
|
f9562d5087 | ||
|
|
786091c0a4 | ||
|
|
436b8240ef | ||
|
|
e7253dcf97 | ||
|
|
48f9aabaf7 | ||
|
|
14d353ae27 | ||
|
|
9a82846b84 | ||
|
|
a4c9bbadc4 | ||
|
|
fa70c55084 | ||
|
|
8d0a89fb06 | ||
|
|
3caf6cb94c | ||
|
|
f4854061ea | ||
|
|
bf7607674e | ||
|
|
137a8ca27b | ||
|
|
b9ed4e0ee2 | ||
|
|
bc04672d32 | ||
|
|
70c668ecf1 | ||
|
|
64bbe2c438 | ||
|
|
32209e766e | ||
|
|
99349cff0a | ||
|
|
74ca1961e0 | ||
|
|
ef6ba7fe0c | ||
|
|
9ace2b71cc | ||
|
|
0c54654b8b | ||
|
|
bf686309fb | ||
|
|
ce4f46537b | ||
|
|
4c43207f17 | ||
|
|
afe5bcd1f3 | ||
|
|
3bda81bd43 | ||
|
|
7339b2325f | ||
|
|
ee84a9ee7e | ||
|
|
fef594150a | ||
|
|
10371f69cb | ||
|
|
75cf3d76fb | ||
|
|
51a7d00c47 | ||
|
|
9ac8261cc4 | ||
|
|
1f4ad80b7d | ||
|
|
4b090f0d68 | ||
|
|
4002bcde26 | ||
|
|
ded3777b40 | ||
|
|
7236066003 | ||
|
|
033f07ea09 | ||
|
|
283c0cba4b | ||
|
|
e3a1fc2fbb | ||
|
|
95de9e2917 | ||
|
|
a82ebeed11 | ||
|
|
3a3aa0be1c | ||
|
|
e72491c2d1 | ||
|
|
36dede1f93 | ||
|
|
ed15daf9e9 | ||
|
|
c6052c841d | ||
|
|
ce39c7ca8f | ||
|
|
b7723dcb98 | ||
|
|
ad0774f8a5 | ||
|
|
9172feb72b | ||
|
|
a297bd3281 | ||
|
|
e713a9cfc3 | ||
|
|
195395a22d | ||
|
|
7b6a62b047 | ||
|
|
ada1c9ff6d | ||
|
|
5a0a14ed56 | ||
|
|
cad3879646 | ||
|
|
5d961991d4 | ||
|
|
e27536743f | ||
|
|
9f8d4a0f34 | ||
|
|
67b6a89fd9 | ||
|
|
dabc4058ba | ||
|
|
6c468602c6 | ||
|
|
9c5d29a860 | ||
|
|
da5e2a6b50 | ||
|
|
a194569fd4 | ||
|
|
78a4ace9b2 | ||
|
|
9a664088cd | ||
|
|
1d2e6f880b | ||
|
|
2cd2918d53 | ||
|
|
9b49db6677 | ||
|
|
9f6c61e5c0 | ||
|
|
b6a2bb7881 | ||
|
|
62262010b9 | ||
|
|
72fe9a04a6 | ||
|
|
d8cf55ae21 | ||
|
|
dfb393b934 | ||
|
|
cd27716f6a | ||
|
|
469553b34e | ||
|
|
5d7c37262e | ||
|
|
3f3867473f | ||
|
|
b08cd1eb4b | ||
|
|
1f9ff8d341 | ||
|
|
528b362f64 | ||
|
|
1db10c5047 | ||
|
|
f295f5f4e7 | ||
|
|
08924bd9b0 | ||
|
|
5d432435a1 | ||
|
|
8bd76aa833 | ||
|
|
2147cb87ac | ||
|
|
00ed0f5402 | ||
|
|
870f79f6cd | ||
|
|
da879213fc | ||
|
|
db66974bd6 | ||
|
|
e3d5ae1d65 | ||
|
|
b06bc5b3b7 | ||
|
|
a4c988012d | ||
|
|
a200701e4c | ||
|
|
e8f604792c | ||
|
|
c8b0666ef9 | ||
|
|
13aa72b150 | ||
|
|
6694074b18 | ||
|
|
63aa32c636 | ||
|
|
5fbab870c3 | ||
|
|
4a34e248e0 | ||
|
|
2c45165e53 | ||
|
|
3f029ac45b | ||
|
|
a4cf76d5ba | ||
|
|
3044000cf8 | ||
|
|
ab1ef5cfd8 | ||
|
|
16b91a283a | ||
|
|
e9fbdc21fa | ||
|
|
b429e662aa | ||
|
|
834ad1736e | ||
|
|
91021699d2 | ||
|
|
0f86aa12ab | ||
|
|
fb7bf6f308 | ||
|
|
5aa67aaa78 | ||
|
|
2e892e7305 | ||
|
|
6486a1689f | ||
|
|
5966535111 | ||
|
|
a2cf4bda99 | ||
|
|
7a93c8615d | ||
|
|
cf29f11cea | ||
|
|
23188a26d7 | ||
|
|
0480dc0140 | ||
|
|
cb14b29c78 | ||
|
|
bf68272de3 | ||
|
|
730f5f8cc9 | ||
|
|
4b6d328e3d | ||
|
|
cfde38be2d | ||
|
|
a2ea8e76fb | ||
|
|
e797d8a1c2 | ||
|
|
b58c157c87 | ||
|
|
58f746a285 | ||
|
|
a6bba42a49 | ||
|
|
519d6868b2 | ||
|
|
5322120097 | ||
|
|
2c88c86480 | ||
|
|
55f32fd45b | ||
|
|
f39f0b03d1 | ||
|
|
ff2f1a4955 | ||
|
|
b283e216a7 | ||
|
|
4328d568b3 | ||
|
|
8edc47703f | ||
|
|
92ce906163 | ||
|
|
6e141e360e | ||
|
|
d1aba87e13 | ||
|
|
723853079e | ||
|
|
cd0742c093 | ||
|
|
52d5de5aec | ||
|
|
4f8a5ae5db | ||
|
|
616f2463c7 | ||
|
|
d3b711a966 | ||
|
|
827fe34709 | ||
|
|
4b6c0242d5 | ||
|
|
c3cbc16084 | ||
|
|
36493bfc88 | ||
|
|
66ce93a3ff | ||
|
|
957bc76dbb | ||
|
|
1f5a28fb33 | ||
|
|
045c58ce66 | ||
|
|
e2dde7239f | ||
|
|
512ad93eea |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 844 KiB After Width: | Height: | Size: 596 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 776 KiB After Width: | Height: | Size: 748 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 790 KiB After Width: | Height: | Size: 722 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
@@ -1,16 +1,16 @@
|
||||
Mastodon is the largest decentralized social network on the internet. Instead of a single website, it’s a network of millions of users in independent communities that can all interact with one another, seamlessly. No matter what you’re into, you can meet passionate people posting about it on Mastodon!
|
||||
Mastodon इंटरनेट का सबसे बड़ा डिसेंट्रलाइज़्ड सोशल नेटवर्क है। एक सिंगल वेबसाइट के जगह, ये हज़ारों आज़ाद ग्रुपों के लाखों यूज़रों का एक नेटवर्क है जो एक दूसरे से आसानी से बात करते है। चाहे आपकी जो भी दिलचस्पी हो, आपको उसके ऊपर पोस्ट करनेवाले लोग Mastodon पे ज़रूर मिलेंगे!
|
||||
|
||||
Join a community and create your profile. Find and follow fascinating folks and read their posts in an ad-free, chronological timeline. Express yourself with custom emoji, images, GIFs, videos, and audio in 500-character posts. Reply to threads and reblog posts from anyone to share great stuff. Find new accounts to follow and trending hashtags to expand your network.
|
||||
कोई ग्रुप जॉइन करें और अपना प्रोफाइल बनाएं। दिलचस्प लोगों को ढूंढ़ें और फॉलो करें और उनके पोस्ट पढ़ें बिना किसी ऐड के। Express yourself with custom emoji, images, GIFs, videos, and audio in 500-character posts. Reply to threads and reblog posts from anyone to share great stuff. Find new accounts to follow and trending hashtags to expand your network.
|
||||
|
||||
Mastodon is built with a focus on privacy and safety. Decide whether your posts are shared with your followers, just the people you mention, or the whole world. Content warnings let you hide posts containing sensitive or triggering material until you're ready to engage with them. Each community has its own guidelines and moderators to keep its members safe, and robust blocking and reporting tools help prevent abuse.
|
||||
|
||||
More features:
|
||||
एक्स्ट्रा फीचर:
|
||||
|
||||
• Dark Mode: Read posts in light, dark, or true black mode
|
||||
• डार्क मोड: पोस्ट लाइट, डार्क, या प्योर ब्लैक मोड में पढ़ें
|
||||
• Polls: Ask followers for their opinion and tally the votes
|
||||
• Explore: Trending hashtags and accounts are a tap away
|
||||
• Notifications: Get notified about new follows, replies, and reblogs
|
||||
• Sharing: Post directly to Mastodon from any share sheet in any app
|
||||
• Cuteness: Our mascot is an adorable elephant, and you'll see them pop up from time to time
|
||||
• क्यूटपन: हमारा मैस्कॉट एक प्यारा हाथी है, और आप उसे समय-समय पे देखेंगे
|
||||
|
||||
Mastodon is a registered nonprofit and development is supported directly by your donations. There’s no advertising, no monetization, and no venture capital, and we plan to keep it that way.
|
||||
|
||||
@@ -1 +1 @@
|
||||
Decentralized social network
|
||||
डिसेंट्रलाइज़्ड सोशल नेटवर्क
|
||||
16
fastlane/metadata/android/ka-GE/full_description.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
Mastodon is the largest decentralized social network on the internet. Instead of a single website, it’s a network of millions of users in independent communities that can all interact with one another, seamlessly. No matter what you’re into, you can meet passionate people posting about it on Mastodon!
|
||||
|
||||
Join a community and create your profile. Find and follow fascinating folks and read their posts in an ad-free, chronological timeline. Express yourself with custom emoji, images, GIFs, videos, and audio in 500-character posts. Reply to threads and reblog posts from anyone to share great stuff. Find new accounts to follow and trending hashtags to expand your network.
|
||||
|
||||
Mastodon is built with a focus on privacy and safety. Decide whether your posts are shared with your followers, just the people you mention, or the whole world. Content warnings let you hide posts containing sensitive or triggering material until you're ready to engage with them. Each community has its own guidelines and moderators to keep its members safe, and robust blocking and reporting tools help prevent abuse.
|
||||
|
||||
More features:
|
||||
|
||||
• Dark Mode: Read posts in light, dark, or true black mode
|
||||
• Polls: Ask followers for their opinion and tally the votes
|
||||
• Explore: Trending hashtags and accounts are a tap away
|
||||
• Notifications: Get notified about new follows, replies, and reblogs
|
||||
• Sharing: Post directly to Mastodon from any share sheet in any app
|
||||
• Cuteness: Our mascot is an adorable elephant, and you'll see them pop up from time to time
|
||||
|
||||
Mastodon is a registered nonprofit and development is supported directly by your donations. There’s no advertising, no monetization, and no venture capital, and we plan to keep it that way.
|
||||
1
fastlane/metadata/android/ka-GE/short_description.txt
Normal file
@@ -0,0 +1 @@
|
||||
Decentralized social network
|
||||
1
fastlane/metadata/android/ka-GE/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Mastodon
|
||||
16
fastlane/metadata/android/lt-LT/full_description.txt
Normal file
@@ -0,0 +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!
|
||||
|
||||
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ą.
|
||||
|
||||
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 tinklaraščių perrašymus
|
||||
• Bendrinimas: skelbk tiesiogiai į Mastodon iš bet kurio bendrinimo lapo bet kurioje programėlėje
|
||||
• Mielumas: mūsų talismanas yra žavus drambliukas, kurį retkarčiais pamatysi
|
||||
|
||||
Mastodon yra 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.
|
||||
1
fastlane/metadata/android/lt-LT/short_description.txt
Normal file
@@ -0,0 +1 @@
|
||||
Decentralizuotas socialinis tinklas
|
||||
1
fastlane/metadata/android/lt-LT/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Mastodon
|
||||
@@ -9,10 +9,10 @@ android {
|
||||
applicationId "org.joinmastodon.android"
|
||||
minSdk 23
|
||||
targetSdk 33
|
||||
versionCode 79
|
||||
versionName "2.2.3"
|
||||
versionCode 84
|
||||
versionName "2.3.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "da-rDK", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fa-rIR", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "ig-rNG", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "my-rMM", "nl-rNL", "no-rNO", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "ur-rIN", "vi-rVN", "zh-rCN", "zh-rTW"
|
||||
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "da-rDK", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fa-rIR", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "ig-rNG", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "ka-rGE", "kab", "ko-rKR", "lt-rLT", "my-rMM", "nl-rNL", "no-rNO", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "ur-rIN", "vi-rVN", "zh-rCN", "zh-rTW"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -76,7 +76,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.15'
|
||||
implementation 'me.grishka.appkit:appkit:1.2.16'
|
||||
implementation 'com.google.code.gson:gson:2.8.9'
|
||||
implementation 'org.jsoup:jsoup:1.14.3'
|
||||
implementation 'com.squareup:otto:1.3.8'
|
||||
|
||||
@@ -102,11 +102,11 @@ public class MainActivity extends FragmentStackActivity{
|
||||
session=AccountSessionManager.get(accountID);
|
||||
if(session==null || !session.activated)
|
||||
return;
|
||||
openSearchQuery(uri.toString(), session.getID(), R.string.opening_link, false);
|
||||
openSearchQuery(uri.toString(), session.getID(), R.string.opening_link, false, null);
|
||||
}
|
||||
|
||||
public void openSearchQuery(String q, String accountID, int progressText, boolean fromSearch){
|
||||
new GetSearchResults(q, null, true, null, 0, 0)
|
||||
public void openSearchQuery(String q, String accountID, int progressText, boolean fromSearch, GetSearchResults.Type type){
|
||||
new GetSearchResults(q, type, true, null, 0, 0)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(SearchResults result){
|
||||
|
||||
@@ -45,7 +45,11 @@ public class MastodonAPIController{
|
||||
.registerTypeAdapter(LocalDate.class, new IsoLocalDateTypeAdapter())
|
||||
.create();
|
||||
private static WorkerThread thread=new WorkerThread("MastodonAPIController");
|
||||
private static OkHttpClient httpClient=new OkHttpClient.Builder().build();
|
||||
private static OkHttpClient httpClient=new OkHttpClient.Builder()
|
||||
.connectTimeout(60, TimeUnit.SECONDS)
|
||||
.writeTimeout(60, TimeUnit.SECONDS)
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
private AccountSession session;
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.api.RequiredField;
|
||||
import org.joinmastodon.android.model.BaseModel;
|
||||
|
||||
public class CheckInviteLink extends MastodonAPIRequest<CheckInviteLink.Response>{
|
||||
public CheckInviteLink(String path){
|
||||
super(HttpMethod.GET, path, Response.class);
|
||||
addHeader("Accept", "application/json");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPathPrefix(){
|
||||
return "";
|
||||
}
|
||||
|
||||
public static class Response extends BaseModel{
|
||||
@RequiredField
|
||||
public String inviteCode;
|
||||
}
|
||||
}
|
||||
@@ -4,22 +4,23 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Token;
|
||||
|
||||
public class RegisterAccount extends MastodonAPIRequest<Token>{
|
||||
public RegisterAccount(String username, String email, String password, String locale, String reason, String timezone){
|
||||
public RegisterAccount(String username, String email, String password, String locale, String reason, String timezone, String inviteCode){
|
||||
super(HttpMethod.POST, "/accounts", Token.class);
|
||||
setRequestBody(new Body(username, email, password, locale, reason, timezone));
|
||||
setRequestBody(new Body(username, email, password, locale, reason, timezone, inviteCode));
|
||||
}
|
||||
|
||||
private static class Body{
|
||||
public String username, email, password, locale, reason, timeZone;
|
||||
public String username, email, password, locale, reason, timeZone, inviteCode;
|
||||
public boolean agreement=true;
|
||||
|
||||
public Body(String username, String email, String password, String locale, String reason, String timeZone){
|
||||
public Body(String username, String email, String password, String locale, String reason, String timeZone, String inviteCode){
|
||||
this.username=username;
|
||||
this.email=email;
|
||||
this.password=password;
|
||||
this.locale=locale;
|
||||
this.reason=reason;
|
||||
this.timeZone=timeZone;
|
||||
this.inviteCode=inviteCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,10 +29,9 @@ import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.Translation;
|
||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.NonMutualPreReplySheet;
|
||||
import org.joinmastodon.android.ui.OldPostPreReplySheet;
|
||||
import org.joinmastodon.android.ui.sheets.NonMutualPreReplySheet;
|
||||
import org.joinmastodon.android.ui.sheets.OldPostPreReplySheet;
|
||||
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
||||
@@ -182,7 +181,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
@Override
|
||||
public void openPhotoViewer(String parentID, Status _status, int attachmentIndex, MediaGridStatusDisplayItem.Holder gridHolder){
|
||||
final Status status=_status.getContentStatus();
|
||||
currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, new PhotoViewer.Listener(){
|
||||
currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, status, accountID, new PhotoViewer.Listener(){
|
||||
private MediaAttachmentViewController transitioningHolder;
|
||||
|
||||
@Override
|
||||
@@ -661,7 +660,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
new NonMutualPreReplySheet(getActivity(), notAgain->{
|
||||
GlobalUserPreferences.optOutOfPreReplySheet(GlobalUserPreferences.PreReplySheetType.NON_MUTUAL, notAgain ? null : status.account, accountID);
|
||||
proceed.run();
|
||||
}, status.account).show();
|
||||
}, status.account, accountID).show();
|
||||
}else if(!GlobalUserPreferences.isOptedOutOfPreReplySheet(GlobalUserPreferences.PreReplySheetType.OLD_POST, null, null) &&
|
||||
status.createdAt.isBefore(Instant.now().minus(90, ChronoUnit.DAYS))){
|
||||
new OldPostPreReplySheet(getActivity(), notAgain->{
|
||||
|
||||
@@ -1077,6 +1077,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
Editable e=mainEditText.getText();
|
||||
int start=e.getSpanStart(currentAutocompleteSpan);
|
||||
int end=e.getSpanEnd(currentAutocompleteSpan);
|
||||
if(start==-1 || end==-1)
|
||||
return;
|
||||
e.replace(start, end, text+" ");
|
||||
finishAutocomplete();
|
||||
InputConnection conn=mainEditText.getCurrentInputConnection();
|
||||
|
||||
@@ -7,10 +7,7 @@ import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.media.MediaMetadataRetriever;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.style.BulletSpan;
|
||||
import android.util.Log;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -131,20 +128,9 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment imp
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item){
|
||||
if(item.getItemId()==R.id.help){
|
||||
SpannableStringBuilder msg=new SpannableStringBuilder(getText(R.string.alt_text_help));
|
||||
BulletSpan[] spans=msg.getSpans(0, msg.length(), BulletSpan.class);
|
||||
for(BulletSpan span:spans){
|
||||
BulletSpan betterSpan;
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.Q)
|
||||
betterSpan=new BulletSpan(V.dp(10), UiUtils.getThemeColor(themeWrapper, R.attr.colorM3OnSurface));
|
||||
else
|
||||
betterSpan=new BulletSpan(V.dp(10), UiUtils.getThemeColor(themeWrapper, R.attr.colorM3OnSurface), V.dp(1.5f));
|
||||
msg.setSpan(betterSpan, msg.getSpanStart(span), msg.getSpanEnd(span), msg.getSpanFlags(span));
|
||||
msg.removeSpan(span);
|
||||
}
|
||||
new M3AlertDialogBuilder(themeWrapper)
|
||||
.setTitle(R.string.what_is_alt_text)
|
||||
.setMessage(msg)
|
||||
.setMessage(UiUtils.fixBulletListInString(themeWrapper, R.string.alt_text_help))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
@@ -181,7 +167,7 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment imp
|
||||
fakeAttachment.meta.width=width;
|
||||
fakeAttachment.meta.height=height;
|
||||
|
||||
photoViewer=new PhotoViewer(getActivity(), Collections.singletonList(fakeAttachment), 0, new PhotoViewer.Listener(){
|
||||
photoViewer=new PhotoViewer(getActivity(), Collections.singletonList(fakeAttachment), 0, null, accountID, new PhotoViewer.Listener(){
|
||||
@Override
|
||||
public void setPhotoViewVisibility(int index, boolean visible){
|
||||
image.setAlpha(visible ? 1f : 0f);
|
||||
|
||||
@@ -30,7 +30,7 @@ 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.AccountSwitcherSheet;
|
||||
import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.TabBar;
|
||||
|
||||
@@ -178,6 +178,7 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
||||
if(refreshing)
|
||||
list.scrollToPosition(0);
|
||||
maxID=result.isEmpty() ? null : result.get(result.size()-1).id;
|
||||
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.PUBLIC);
|
||||
onDataLoaded(result, !result.isEmpty());
|
||||
}
|
||||
})
|
||||
@@ -191,6 +192,7 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
||||
if(refreshing)
|
||||
list.scrollToPosition(0);
|
||||
maxID=result.isEmpty() ? null : result.get(result.size()-1).id;
|
||||
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.HOME);
|
||||
onDataLoaded(result, !result.isEmpty());
|
||||
}
|
||||
})
|
||||
|
||||
@@ -40,7 +40,6 @@ import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
@@ -63,10 +62,12 @@ import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||
import org.joinmastodon.android.ui.SingleImagePhotoViewerListener;
|
||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
||||
import org.joinmastodon.android.ui.sheets.DecentralizationExplainerSheet;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
||||
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.text.ImageSpanThatDoesNotBreakShitForNoGoodReason;
|
||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.CoverImageView;
|
||||
@@ -108,7 +109,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
private ImageView avatar;
|
||||
private CoverImageView cover;
|
||||
private View avatarBorder;
|
||||
private TextView name, username, bio, followersCount, followersLabel, followingCount, followingLabel;
|
||||
private TextView name, username, usernameDomain, bio, followersCount, followersLabel, followingCount, followingLabel;
|
||||
private ProgressBarButton actionButton;
|
||||
private ViewPager2 pager;
|
||||
private NestedRecyclerScrollView scrollView;
|
||||
@@ -186,6 +187,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
avatarBorder=content.findViewById(R.id.avatar_border);
|
||||
name=content.findViewById(R.id.name);
|
||||
username=content.findViewById(R.id.username);
|
||||
usernameDomain=content.findViewById(R.id.username_domain);
|
||||
bio=content.findViewById(R.id.bio);
|
||||
followersCount=content.findViewById(R.id.followers_count);
|
||||
followersLabel=content.findViewById(R.id.followers_label);
|
||||
@@ -321,6 +323,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
nameEdit.addTextChangedListener(new SimpleTextWatcher(e->editDirty=true));
|
||||
bioEdit.addTextChangedListener(new SimpleTextWatcher(e->editDirty=true));
|
||||
|
||||
usernameDomain.setOnClickListener(v->new DecentralizationExplainerSheet(getActivity(), accountID, account).show());
|
||||
|
||||
return sizeWrapper;
|
||||
}
|
||||
|
||||
@@ -500,22 +504,21 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account);
|
||||
|
||||
if(account.locked){
|
||||
ssb=new SpannableStringBuilder("@");
|
||||
ssb.append(account.acct);
|
||||
if(isSelf){
|
||||
ssb.append('@');
|
||||
ssb.append(AccountSessionManager.getInstance().getAccount(accountID).domain);
|
||||
}
|
||||
ssb=new SpannableStringBuilder(account.username);
|
||||
ssb.append(" ");
|
||||
Drawable lock=username.getResources().getDrawable(R.drawable.ic_lock_fill1_20px, getActivity().getTheme()).mutate();
|
||||
lock.setBounds(0, 0, lock.getIntrinsicWidth(), lock.getIntrinsicHeight());
|
||||
lock.setTint(username.getCurrentTextColor());
|
||||
ssb.append(getString(R.string.manually_approves_followers), new ImageSpan(lock, ImageSpan.ALIGN_BOTTOM), 0);
|
||||
ssb.append(getString(R.string.manually_approves_followers), new ImageSpanThatDoesNotBreakShitForNoGoodReason(lock, ImageSpan.ALIGN_BOTTOM), 0);
|
||||
username.setText(ssb);
|
||||
}else{
|
||||
// noinspection SetTextI18n
|
||||
username.setText('@'+account.acct+(isSelf ? ('@'+AccountSessionManager.getInstance().getAccount(accountID).domain) : ""));
|
||||
username.setText(account.username);
|
||||
}
|
||||
String domain=account.getDomain();
|
||||
if(TextUtils.isEmpty(domain))
|
||||
domain=AccountSessionManager.get(accountID).domain;
|
||||
usernameDomain.setText(domain);
|
||||
|
||||
CharSequence parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID, account);
|
||||
if(TextUtils.isEmpty(parsedBio)){
|
||||
bio.setVisibility(View.GONE);
|
||||
@@ -631,10 +634,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
}else if(id==R.id.open_in_browser){
|
||||
UiUtils.launchWebBrowser(getActivity(), account.url);
|
||||
}else if(id==R.id.block_domain){
|
||||
UiUtils.confirmToggleBlockDomain(getActivity(), accountID, account.getDomain(), relationship.domainBlocking, ()->{
|
||||
UiUtils.confirmToggleBlockDomain(getActivity(), accountID, account, relationship.domainBlocking, ()->{
|
||||
relationship.domainBlocking=!relationship.domainBlocking;
|
||||
updateRelationship();
|
||||
});
|
||||
}, this::updateRelationship);
|
||||
}else if(id==R.id.hide_boosts){
|
||||
new SetAccountFollowed(account.id, true, !relationship.showingReblogs)
|
||||
.setCallback(new Callback<>(){
|
||||
@@ -977,7 +980,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
return;
|
||||
int radius=V.dp(25);
|
||||
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.avatar, ava), 0,
|
||||
new SingleImagePhotoViewerListener(avatar, avatarBorder, new int[]{radius, radius, radius, radius}, this, ()->currentPhotoViewer=null, ()->ava, null, null));
|
||||
null, accountID, new SingleImagePhotoViewerListener(avatar, avatarBorder, new int[]{radius, radius, radius, radius}, this, ()->currentPhotoViewer=null, ()->ava, null, null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -989,7 +992,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
if(drawable==null || drawable instanceof ColorDrawable)
|
||||
return;
|
||||
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.header, drawable), 0,
|
||||
new SingleImagePhotoViewerListener(cover, cover, null, this, ()->currentPhotoViewer=null, ()->drawable, ()->avatarBorder.setTranslationZ(2), ()->avatarBorder.setTranslationZ(0)));
|
||||
null, accountID, new SingleImagePhotoViewerListener(cover, cover, null, this, ()->currentPhotoViewer=null, ()->drawable, ()->avatarBorder.setTranslationZ(2), ()->avatarBorder.setTranslationZ(0)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -11,6 +16,8 @@ import android.widget.ProgressBar;
|
||||
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||
import org.joinmastodon.android.api.requests.accounts.CheckInviteLink;
|
||||
import org.joinmastodon.android.api.requests.catalog.GetCatalogDefaultInstances;
|
||||
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||
import org.joinmastodon.android.fragments.onboarding.InstanceCatalogSignupFragment;
|
||||
@@ -20,6 +27,7 @@ import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.catalog.CatalogDefaultInstance;
|
||||
import org.joinmastodon.android.ui.InterpolatingMotionEffect;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||
import org.joinmastodon.android.ui.views.SizeListenerFrameLayout;
|
||||
@@ -48,6 +56,9 @@ public class SplashFragment extends AppKitFragment{
|
||||
private ProgressBar defaultServerProgress;
|
||||
private String chosenDefaultServer=DEFAULT_SERVER;
|
||||
private boolean loadingDefaultServer, loadedDefaultServer;
|
||||
private Uri currentInviteLink;
|
||||
private ProgressDialog instanceLoadingProgress;
|
||||
private String inviteCode;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -110,19 +121,65 @@ public class SplashFragment extends AppKitFragment{
|
||||
Bundle extras=new Bundle();
|
||||
boolean isSignup=v.getId()==R.id.btn_get_started;
|
||||
extras.putBoolean("signup", isSignup);
|
||||
extras.putString("defaultServer", chosenDefaultServer);
|
||||
Nav.go(getActivity(), isSignup ? InstanceCatalogSignupFragment.class : InstanceChooserLoginFragment.class, extras);
|
||||
}
|
||||
|
||||
private void onJoinDefaultServerClick(View v){
|
||||
if(loadingDefaultServer)
|
||||
return;
|
||||
instanceLoadingProgress=new ProgressDialog(getActivity());
|
||||
instanceLoadingProgress.setCancelable(false);
|
||||
instanceLoadingProgress.setMessage(getString(R.string.loading_instance));
|
||||
instanceLoadingProgress.show();
|
||||
if(currentInviteLink!=null){
|
||||
new CheckInviteLink(currentInviteLink.getPath())
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(CheckInviteLink.Response result){
|
||||
inviteCode=result.inviteCode;
|
||||
proceedWithServerDomain(currentInviteLink.getHost());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
instanceLoadingProgress.dismiss();
|
||||
instanceLoadingProgress=null;
|
||||
if(error instanceof MastodonErrorResponse mer){
|
||||
switch(mer.httpStatus){
|
||||
case 401 -> new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.expired_invite_link)
|
||||
.setMessage(getString(R.string.expired_clipboard_invite_link_alert, currentInviteLink.getHost(), chosenDefaultServer))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
case 404 -> new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.invalid_invite_link)
|
||||
.setMessage(getString(R.string.invalid_clipboard_invite_link_alert, currentInviteLink.getHost(), chosenDefaultServer))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
default -> error.showToast(getActivity());
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.execNoAuth(currentInviteLink.getHost());
|
||||
return;
|
||||
}
|
||||
proceedWithServerDomain(chosenDefaultServer);
|
||||
}
|
||||
|
||||
private void proceedWithServerDomain(String domain){
|
||||
new GetInstance()
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Instance result){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
if(!result.registrations){
|
||||
instanceLoadingProgress.dismiss();
|
||||
instanceLoadingProgress=null;
|
||||
if(!result.registrations && TextUtils.isEmpty(inviteCode)){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(R.string.instance_signup_closed)
|
||||
@@ -132,6 +189,8 @@ public class SplashFragment extends AppKitFragment{
|
||||
}
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("instance", Parcels.wrap(result));
|
||||
if(inviteCode!=null)
|
||||
args.putString("inviteCode", inviteCode);
|
||||
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
||||
}
|
||||
|
||||
@@ -139,11 +198,12 @@ public class SplashFragment extends AppKitFragment{
|
||||
public void onError(ErrorResponse error){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
instanceLoadingProgress.dismiss();
|
||||
instanceLoadingProgress=null;
|
||||
error.showToast(getActivity());
|
||||
}
|
||||
})
|
||||
.wrapProgress(getActivity(), R.string.loading_instance, true)
|
||||
.execNoAuth(chosenDefaultServer);
|
||||
.execNoAuth(domain);
|
||||
}
|
||||
|
||||
private void onLearnMoreClick(View v){
|
||||
@@ -198,9 +258,18 @@ public class SplashFragment extends AppKitFragment{
|
||||
}
|
||||
|
||||
private void loadAndChooseDefaultServer(){
|
||||
loadingDefaultServer=true;
|
||||
defaultServerButton.setTextVisible(false);
|
||||
defaultServerProgress.setVisibility(View.VISIBLE);
|
||||
ClipData clipData=getActivity().getSystemService(ClipboardManager.class).getPrimaryClip();
|
||||
if(clipData!=null && clipData.getItemCount()>0){
|
||||
CharSequence clipText=clipData.getItemAt(0).coerceToText(getActivity());
|
||||
if(HtmlParser.INVITE_LINK_PATTERN.matcher(clipText).find()){
|
||||
currentInviteLink=Uri.parse(clipText.toString());
|
||||
defaultServerButton.setText(getString(R.string.join_server_x_with_invite, currentInviteLink.getHost()));
|
||||
}
|
||||
}else{
|
||||
loadingDefaultServer=true;
|
||||
defaultServerButton.setTextVisible(false);
|
||||
defaultServerProgress.setVisibility(View.VISIBLE);
|
||||
}
|
||||
new GetCatalogDefaultInstances()
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
@@ -243,7 +312,7 @@ public class SplashFragment extends AppKitFragment{
|
||||
chosenDefaultServer=domain;
|
||||
loadingDefaultServer=false;
|
||||
loadedDefaultServer=true;
|
||||
if(defaultServerButton!=null && getActivity()!=null){
|
||||
if(defaultServerButton!=null && getActivity()!=null && currentInviteLink==null){
|
||||
defaultServerButton.setTextVisible(true);
|
||||
defaultServerProgress.setVisibility(View.GONE);
|
||||
defaultServerButton.setText(getString(R.string.join_default_server, chosenDefaultServer));
|
||||
|
||||
@@ -38,6 +38,7 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<A
|
||||
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
||||
protected String accountID;
|
||||
protected ArrayList<APIRequest<?>> relationshipsRequests=new ArrayList<>();
|
||||
protected int itemLayoutRes=R.layout.item_account_list;
|
||||
|
||||
public BaseAccountListFragment(){
|
||||
super(40);
|
||||
@@ -151,7 +152,7 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<A
|
||||
@NonNull
|
||||
@Override
|
||||
public AccountViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
AccountViewHolder holder=new AccountViewHolder(BaseAccountListFragment.this, parent, relationships);
|
||||
AccountViewHolder holder=new AccountViewHolder(BaseAccountListFragment.this, parent, relationships, itemLayoutRes);
|
||||
onConfigureViewHolder(holder);
|
||||
return holder;
|
||||
}
|
||||
|
||||
@@ -408,7 +408,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
||||
if(q.lastIndexOf('@')==0){
|
||||
q+="@"+AccountSessionManager.get(accountID).domain;
|
||||
}
|
||||
((MainActivity)getActivity()).openSearchQuery(q, accountID, R.string.loading, true);
|
||||
((MainActivity)getActivity()).openSearchQuery(q, accountID, R.string.loading, true, GetSearchResults.Type.ACCOUNTS);
|
||||
}
|
||||
|
||||
private void onGoToStatusSearchClick(ListItem<?> item_){
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.settings.SettingsMainFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
||||
import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -137,6 +137,9 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
||||
protected void onButtonClick(){
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("instance", Parcels.wrap(instance));
|
||||
if(getArguments().containsKey("inviteCode")){
|
||||
args.putString("inviteCode", getArguments().getString("inviteCode"));
|
||||
}
|
||||
Nav.goForResult(getActivity(), SignupFragment.class, args, SIGNUP_REQUEST, this);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.joinmastodon.android.fragments.onboarding;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.KeyEvent;
|
||||
@@ -37,6 +36,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
@@ -48,7 +48,6 @@ import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
@@ -61,6 +60,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
protected EditText searchEdit;
|
||||
protected Runnable searchDebouncer=this::onSearchChangedDebounced;
|
||||
protected String currentSearchQuery;
|
||||
protected String currentSearchQueryButWithCasePreserved;
|
||||
protected String loadingInstanceDomain;
|
||||
protected HashMap<String, Instance> instancesCache=new HashMap<>();
|
||||
protected View buttonBar;
|
||||
@@ -91,6 +91,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
if(event!=null && event.getAction()!=KeyEvent.ACTION_DOWN)
|
||||
return true;
|
||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
|
||||
currentSearchQueryButWithCasePreserved=searchEdit.getText().toString().trim();
|
||||
updateFilteredList();
|
||||
searchEdit.removeCallbacks(searchDebouncer);
|
||||
Instance instance=instancesCache.get(normalizeInstanceDomain(currentSearchQuery));
|
||||
@@ -105,6 +106,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
|
||||
protected void onSearchChangedDebounced(){
|
||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
|
||||
currentSearchQueryButWithCasePreserved=searchEdit.getText().toString().trim();
|
||||
updateFilteredList();
|
||||
loadInstanceInfo(currentSearchQuery, false);
|
||||
}
|
||||
@@ -149,6 +151,10 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
}
|
||||
|
||||
protected void loadInstanceInfo(String _domain, boolean isFromRedirect){
|
||||
loadInstanceInfo(_domain, isFromRedirect, null);
|
||||
}
|
||||
|
||||
protected void loadInstanceInfo(String _domain, boolean isFromRedirect, Consumer<Object> onError){
|
||||
if(TextUtils.isEmpty(_domain))
|
||||
return;
|
||||
String domain=normalizeInstanceDomain(_domain);
|
||||
@@ -173,7 +179,10 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
try{
|
||||
new URI("https://"+domain+"/api/v1/instance"); // Validate the host by trying to parse the URI
|
||||
}catch(URISyntaxException x){
|
||||
showInstanceInfoLoadError(domain, x);
|
||||
if(onError!=null)
|
||||
onError.accept(x);
|
||||
else
|
||||
showInstanceInfoLoadError(domain, x);
|
||||
if(fakeInstance!=null){
|
||||
fakeInstance.description=getString(R.string.error);
|
||||
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
|
||||
@@ -193,10 +202,11 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
loadingInstanceDomain=null;
|
||||
result.uri=domain; // needed for instances that use domain redirection
|
||||
instancesCache.put(domain, result);
|
||||
if(instanceProgressDialog!=null || onError!=null)
|
||||
proceedWithAuthOrSignup(result);
|
||||
if(instanceProgressDialog!=null){
|
||||
instanceProgressDialog.dismiss();
|
||||
instanceProgressDialog=null;
|
||||
proceedWithAuthOrSignup(result);
|
||||
}
|
||||
if(Objects.equals(domain, currentSearchQuery) || Objects.equals(currentSearchQuery, redirects.get(domain)) || Objects.equals(currentSearchQuery, redirectsInverse.get(domain))){
|
||||
boolean found=false;
|
||||
@@ -223,11 +233,14 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
public void onError(ErrorResponse error){
|
||||
loadingInstanceRequest=null;
|
||||
if(!isFromRedirect && error instanceof MastodonErrorResponse me && me.httpStatus==404){
|
||||
fetchDomainFromHostMetaAndMaybeRetry(domain, error);
|
||||
fetchDomainFromHostMetaAndMaybeRetry(domain, error, onError);
|
||||
return;
|
||||
}
|
||||
loadingInstanceDomain=null;
|
||||
showInstanceInfoLoadError(domain, error);
|
||||
if(onError!=null)
|
||||
onError.accept(error);
|
||||
else
|
||||
showInstanceInfoLoadError(domain, error);
|
||||
if(fakeInstance!=null && getActivity()!=null){
|
||||
fakeInstance.description=getString(R.string.error);
|
||||
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
|
||||
@@ -276,7 +289,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchDomainFromHostMetaAndMaybeRetry(String domain, Object origError){
|
||||
private void fetchDomainFromHostMetaAndMaybeRetry(String domain, Object origError, Consumer<Object> onError){
|
||||
String url="https://"+domain+"/.well-known/host-meta";
|
||||
Request req=new Request.Builder()
|
||||
.url(url)
|
||||
@@ -290,7 +303,12 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
Activity a=getActivity();
|
||||
if(a==null)
|
||||
return;
|
||||
a.runOnUiThread(()->showInstanceInfoLoadError(domain, e));
|
||||
a.runOnUiThread(()->{
|
||||
if(onError!=null)
|
||||
onError.accept(e);
|
||||
else
|
||||
showInstanceInfoLoadError(domain, e);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -302,7 +320,13 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
return;
|
||||
try(response){
|
||||
if(!response.isSuccessful()){
|
||||
a.runOnUiThread(()->showInstanceInfoLoadError(domain, response.code()+" "+response.message()));
|
||||
a.runOnUiThread(()->{
|
||||
String err=response.code()+" "+response.message();
|
||||
if(onError!=null)
|
||||
onError.accept(err);
|
||||
else
|
||||
showInstanceInfoLoadError(domain, err);
|
||||
});
|
||||
return;
|
||||
}
|
||||
InputSource source=new InputSource(response.body().charStream());
|
||||
@@ -321,9 +345,19 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
}
|
||||
}
|
||||
}
|
||||
a.runOnUiThread(()->showInstanceInfoLoadError(domain, origError));
|
||||
a.runOnUiThread(()->{
|
||||
if(onError!=null)
|
||||
onError.accept(origError);
|
||||
else
|
||||
showInstanceInfoLoadError(domain, origError);
|
||||
});
|
||||
}catch(Exception x){
|
||||
a.runOnUiThread(()->showInstanceInfoLoadError(domain, x));
|
||||
a.runOnUiThread(()->{
|
||||
if(onError!=null)
|
||||
onError.accept(x);
|
||||
else
|
||||
showInstanceInfoLoadError(domain, x);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
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.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
@@ -12,6 +17,8 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
@@ -19,9 +26,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;
|
||||
@@ -29,6 +39,8 @@ 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;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.FilterChipView;
|
||||
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||
@@ -40,7 +52,9 @@ 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;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -77,6 +91,9 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
private CatalogInstance.Region chosenRegion;
|
||||
private CategoryChoice categoryChoice=CategoryChoice.GENERAL;
|
||||
|
||||
private String inviteCode, inviteCodeHost;
|
||||
private AlertDialog currentInviteLinkAlert;
|
||||
|
||||
public InstanceCatalogSignupFragment(){
|
||||
super(R.layout.fragment_onboarding_common, 10);
|
||||
}
|
||||
@@ -317,7 +334,7 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
focusThing=view.findViewById(R.id.focus_thing);
|
||||
focusThing.requestFocus();
|
||||
|
||||
view.findViewById(R.id.btn_random_instance).setOnClickListener(this::onPickRandomInstanceClick);
|
||||
view.findViewById(R.id.btn_use_invite).setOnClickListener(this::onUseInviteClick);
|
||||
nextButton.setEnabled(chosenInstance!=null);
|
||||
}
|
||||
|
||||
@@ -351,34 +368,191 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
|
||||
@Override
|
||||
protected void proceedWithAuthOrSignup(Instance instance){
|
||||
if(currentInviteLinkAlert!=null){
|
||||
currentInviteLinkAlert.dismiss();
|
||||
}else if(!TextUtils.isEmpty(currentSearchQuery) && HtmlParser.INVITE_LINK_PATTERN.matcher(currentSearchQueryButWithCasePreserved).find()){
|
||||
if(TextUtils.isEmpty(inviteCode) || !Objects.equals(instance.uri, inviteCodeHost)){
|
||||
Uri inviteLink=Uri.parse(currentSearchQueryButWithCasePreserved);
|
||||
new CheckInviteLink(inviteLink.getPath())
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(CheckInviteLink.Response result){
|
||||
inviteCodeHost=inviteLink.getHost();
|
||||
inviteCode=result.inviteCode;
|
||||
proceedWithAuthOrSignup(instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
if(error instanceof MastodonErrorResponse mer){
|
||||
switch(mer.httpStatus){
|
||||
case 401 -> new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.expired_invite_link)
|
||||
.setMessage(getString(R.string.expired_clipboard_invite_link_alert, inviteLink.getHost(), getArguments().getString("defaultServer")))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
case 404 -> new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.invalid_invite_link)
|
||||
.setMessage(getString(R.string.invalid_clipboard_invite_link_alert, inviteLink.getHost(), getArguments().getString("defaultServer")))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
default -> error.showToast(getActivity());
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.wrapProgress(getActivity(), R.string.loading_instance, true)
|
||||
.execNoAuth(inviteLink.getHost());
|
||||
return;
|
||||
}
|
||||
}
|
||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0);
|
||||
if(!instance.registrations){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(R.string.instance_signup_closed)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
if(!instance.registrations && (TextUtils.isEmpty(inviteCode) || !Objects.equals(instance.uri, inviteCodeHost))){
|
||||
if(instance.invitesEnabled){
|
||||
showInviteLinkAlert(instance.uri);
|
||||
}else{
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(R.string.instance_signup_closed)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("instance", Parcels.wrap(instance));
|
||||
if(!TextUtils.isEmpty(inviteCode) && Objects.equals(instance.uri, inviteCodeHost))
|
||||
args.putString("inviteCode", inviteCode);
|
||||
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
||||
}
|
||||
|
||||
private void onPickRandomInstanceClick(View v){
|
||||
String lang=Locale.getDefault().getLanguage();
|
||||
List<CatalogInstance> instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general"))) && (lang.equals(ci.language) || (ci.languages!=null && ci.languages.contains(lang)))).collect(Collectors.toList());
|
||||
if(instances.isEmpty()){
|
||||
instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general")))).collect(Collectors.toList());
|
||||
private void onUseInviteClick(View v){
|
||||
showInviteLinkAlert(null);
|
||||
}
|
||||
|
||||
private void showInviteLinkAlert(String domain){
|
||||
AlertDialog alert=new M3AlertDialogBuilder(getActivity())
|
||||
.setView(R.layout.alert_invite_link)
|
||||
.setPositiveButton(R.string.next, null)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.create();
|
||||
|
||||
Button next=alert.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
EditText edit=alert.findViewById(R.id.edit);
|
||||
TextView supportingText=alert.findViewById(R.id.supporting_text);
|
||||
TextView label=alert.findViewById(R.id.label);
|
||||
TextView subtitle=alert.findViewById(R.id.subtitle);
|
||||
ImageButton clear=alert.findViewById(R.id.clear);
|
||||
clear.setVisibility(View.GONE);
|
||||
|
||||
if(TextUtils.isEmpty(domain)){
|
||||
subtitle.setVisibility(View.GONE);
|
||||
}else{
|
||||
subtitle.setText(getString(R.string.need_invite_to_join_server, domain));
|
||||
}
|
||||
if(instances.isEmpty()){
|
||||
instances=data.stream().filter(ci->("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general")))).collect(Collectors.toList());
|
||||
|
||||
Consumer<String> errorSetter=err->{
|
||||
supportingText.setText(err);
|
||||
int errorColor=UiUtils.getThemeColor(getActivity(), R.attr.colorM3Error);
|
||||
supportingText.setTextColor(errorColor);
|
||||
label.setTextColor(errorColor);
|
||||
edit.setBackgroundResource(R.drawable.bg_m3_filled_text_field_error);
|
||||
};
|
||||
|
||||
next.setOnClickListener(_v->{
|
||||
Uri inviteLink=Uri.parse(edit.getText().toString());
|
||||
if(TextUtils.isEmpty(inviteLink.getHost()) || TextUtils.isEmpty(inviteLink.getPath())){
|
||||
errorSetter.accept(getString(R.string.this_invite_is_invalid));
|
||||
return;
|
||||
}
|
||||
UiUtils.showProgressForAlertButton(next, true);
|
||||
new CheckInviteLink(inviteLink.getPath())
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(CheckInviteLink.Response result){
|
||||
if(getActivity()==null || !alert.isShowing())
|
||||
return;
|
||||
|
||||
String host=inviteLink.getHost();
|
||||
inviteCode=result.inviteCode;
|
||||
inviteCodeHost=host;
|
||||
|
||||
Instance instance=instancesCache.get(normalizeInstanceDomain(host));
|
||||
if(instance==null){
|
||||
loadInstanceInfo(host, false, err->{
|
||||
String errorStr;
|
||||
if(err instanceof String str){
|
||||
errorStr=str;
|
||||
}else if(err instanceof Throwable x){
|
||||
errorStr=x.getMessage();
|
||||
}else if(err instanceof MastodonErrorResponse mer){
|
||||
errorStr=mer.error;
|
||||
}else{
|
||||
errorStr=getString(R.string.error);
|
||||
}
|
||||
errorSetter.accept(errorStr);
|
||||
UiUtils.showProgressForAlertButton(next, false);
|
||||
});
|
||||
}else{
|
||||
proceedWithAuthOrSignup(instance);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
if(getActivity()==null || !alert.isShowing())
|
||||
return;
|
||||
UiUtils.showProgressForAlertButton(next, false);
|
||||
if(error instanceof MastodonErrorResponse mer){
|
||||
errorSetter.accept(switch(mer.httpStatus){
|
||||
case 404 -> getString(R.string.this_invite_is_invalid);
|
||||
case 401 -> getString(R.string.this_invite_has_expired);
|
||||
default -> mer.error;
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.execNoAuth(inviteLink.getHost());
|
||||
});
|
||||
next.setEnabled(false);
|
||||
edit.addTextChangedListener(new SimpleTextWatcher(e->{
|
||||
boolean wasEmpty=!next.isEnabled();
|
||||
next.setEnabled(e.length()>0);
|
||||
if(supportingText.length()>0){
|
||||
supportingText.setText("");
|
||||
int regularColor=UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant);
|
||||
supportingText.setTextColor(regularColor);
|
||||
label.setTextColor(regularColor);
|
||||
edit.setBackgroundResource(R.drawable.bg_m3_filled_text_field);
|
||||
}
|
||||
if(wasEmpty!=(e.length()==0)){
|
||||
int padEnd;
|
||||
if(e.length()==0){
|
||||
clear.setVisibility(View.GONE);
|
||||
padEnd=V.dp(16);
|
||||
}else{
|
||||
clear.setVisibility(View.VISIBLE);
|
||||
padEnd=V.dp(48);
|
||||
}
|
||||
edit.setPaddingRelative(edit.getPaddingStart(), edit.getPaddingTop(), padEnd, edit.getPaddingBottom());
|
||||
}
|
||||
}));
|
||||
clear.setOnClickListener(_v->edit.setText(""));
|
||||
|
||||
ClipData clipData=getActivity().getSystemService(ClipboardManager.class).getPrimaryClip();
|
||||
if(clipData!=null && clipData.getItemCount()>0){
|
||||
CharSequence clipText=clipData.getItemAt(0).coerceToText(getActivity());
|
||||
if(HtmlParser.INVITE_LINK_PATTERN.matcher(clipText).find()){
|
||||
edit.setText(clipText);
|
||||
supportingText.setText(R.string.invite_link_pasted);
|
||||
}
|
||||
}
|
||||
if(instances.isEmpty()){
|
||||
return;
|
||||
}
|
||||
chosenInstance=instances.get(new Random().nextInt(instances.size()));
|
||||
onNextClick(v);
|
||||
|
||||
currentInviteLinkAlert=alert;
|
||||
alert.setOnDismissListener(dialog->currentInviteLinkAlert=null);
|
||||
alert.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -387,8 +561,14 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
filteredData.clear();
|
||||
if(searchQueryMode){
|
||||
if(!TextUtils.isEmpty(currentSearchQuery)){
|
||||
String actualQuery;
|
||||
if(currentSearchQuery.startsWith("https:")){
|
||||
actualQuery=Uri.parse(currentSearchQuery).getHost();
|
||||
}else{
|
||||
actualQuery=currentSearchQuery;
|
||||
}
|
||||
for(CatalogInstance instance:data){
|
||||
if(instance.domain.contains(currentSearchQuery)){
|
||||
if(instance.domain.contains(actualQuery)){
|
||||
filteredData.add(instance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,9 @@ public class InstanceRulesFragment extends ToolbarFragment{
|
||||
protected void onButtonClick(){
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("instance", Parcels.wrap(instance));
|
||||
if(getArguments().containsKey("inviteCode")){
|
||||
args.putString("inviteCode", getArguments().getString("inviteCode"));
|
||||
}
|
||||
Nav.goForResult(getActivity(), GoogleMadeMeAddThisFragment.class, args, RULES_REQUEST, this);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.app.ProgressDialog;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
|
||||
@@ -12,35 +13,38 @@ import org.joinmastodon.android.fragments.account_list.BaseAccountListFragment;
|
||||
import org.joinmastodon.android.model.FollowSuggestion;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
||||
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment{
|
||||
private String accountID;
|
||||
private View buttonBar;
|
||||
private ElevationOnScrollListener onScrollListener;
|
||||
private int numRunningFollowRequests=0;
|
||||
|
||||
public OnboardingFollowSuggestionsFragment(){
|
||||
super(R.layout.fragment_onboarding_follow_suggestions, 40);
|
||||
itemLayoutRes=R.layout.item_account_list_onboarding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
setTitle(R.string.popular_on_mastodon);
|
||||
setTitle(R.string.onboarding_recommendations_title);
|
||||
accountID=getArguments().getString("account");
|
||||
loadData();
|
||||
}
|
||||
@@ -49,7 +53,6 @@ public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
buttonBar=view.findViewById(R.id.button_bar);
|
||||
list.addOnScrollListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
|
||||
|
||||
view.findViewById(R.id.btn_next).setOnClickListener(UiUtils.rateLimitedClickListener(this::onFollowAllClick));
|
||||
view.findViewById(R.id.btn_skip).setOnClickListener(UiUtils.rateLimitedClickListener(v->proceed()));
|
||||
@@ -58,9 +61,7 @@ public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment
|
||||
@Override
|
||||
protected void onUpdateToolbar(){
|
||||
super.onUpdateToolbar();
|
||||
if(onScrollListener!=null){
|
||||
onScrollListener.setViews(buttonBar, getToolbar());
|
||||
}
|
||||
getToolbar().setContentInsetsRelative(V.dp(56), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,7 +70,7 @@ public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<FollowSuggestion> result){
|
||||
onDataLoaded(result.stream().map(fs->new AccountViewModel(fs.account, accountID)).collect(Collectors.toList()), false);
|
||||
onDataLoaded(result.stream().map(fs->new AccountViewModel(fs.account, accountID).stripLinksFromBio()).collect(Collectors.toList()), false);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
@@ -80,6 +81,19 @@ public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment
|
||||
super.onApplyWindowInsets(UiUtils.applyBottomInsetToFixedView(buttonBar, insets));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter<?> getAdapter(){
|
||||
TextView introText=new TextView(getActivity());
|
||||
introText.setTextAppearance(R.style.m3_body_large);
|
||||
introText.setTextColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurface));
|
||||
introText.setPaddingRelative(V.dp(56), 0, V.dp(24), V.dp(8));
|
||||
introText.setText(R.string.onboarding_recommendations_intro);
|
||||
MergeRecyclerAdapter mergeAdapter=new MergeRecyclerAdapter();
|
||||
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(introText));
|
||||
mergeAdapter.addAdapter(super.getAdapter());
|
||||
return mergeAdapter;
|
||||
}
|
||||
|
||||
private void onFollowAllClick(View v){
|
||||
if(!loaded || relationships.isEmpty())
|
||||
return;
|
||||
@@ -155,5 +169,6 @@ public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment
|
||||
protected void onConfigureViewHolder(AccountViewHolder holder){
|
||||
super.onConfigureViewHolder(holder);
|
||||
holder.setStyle(AccountViewHolder.AccessoryType.BUTTON, true);
|
||||
holder.avatar.setOutlineProvider(OutlineProviders.roundedRect(8));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +219,9 @@ public class SignupFragment extends ToolbarFragment{
|
||||
if(!serverSupportedTimezones.contains(timezone))
|
||||
timezone=null;
|
||||
|
||||
new RegisterAccount(username, email, password.getText().toString(), locale, reason.getText().toString(), timezone)
|
||||
String inviteCode=getArguments().getString("inviteCode");
|
||||
|
||||
new RegisterAccount(username, email, password.getText().toString(), locale, reason.getText().toString(), timezone, inviteCode)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Token result){
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.joinmastodon.android.fragments.settings;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
@@ -17,7 +16,7 @@ import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
||||
import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.joinmastodon.android.ui.utils.BlurHashDecoder;
|
||||
import org.joinmastodon.android.ui.utils.BlurHashDrawable;
|
||||
import org.parceler.Parcel;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
@Parcel
|
||||
@@ -34,11 +35,14 @@ public class Card extends BaseModel{
|
||||
public String embedUrl;
|
||||
public String blurhash;
|
||||
public List<History> history;
|
||||
public Instant publishedAt;
|
||||
|
||||
public transient Drawable blurhashPlaceholder;
|
||||
|
||||
@Override
|
||||
public void postprocess() throws ObjectValidationException{
|
||||
if(type==null)
|
||||
type=Type.LINK;
|
||||
super.postprocess();
|
||||
if(blurhash!=null){
|
||||
Bitmap placeholder=BlurHashDecoder.decode(blurhash, 16, 16);
|
||||
@@ -64,6 +68,7 @@ public class Card extends BaseModel{
|
||||
", embedUrl='"+embedUrl+'\''+
|
||||
", blurhash='"+blurhash+'\''+
|
||||
", history="+history+
|
||||
", publishedAt="+publishedAt+
|
||||
'}';
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.model.viewmodel;
|
||||
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
@@ -7,6 +8,7 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.AccountField;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.text.LinkSpan;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -43,4 +45,13 @@ public class AccountViewModel{
|
||||
}
|
||||
this.verifiedLink=verifiedLink;
|
||||
}
|
||||
|
||||
public AccountViewModel stripLinksFromBio(){
|
||||
if(parsedBio instanceof Spannable spannable){
|
||||
for(LinkSpan span:spannable.getSpans(0, spannable.length(), LinkSpan.class)){
|
||||
spannable.removeSpan(span);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ public class Snackbar{
|
||||
if(current!=null)
|
||||
current.dismiss();
|
||||
current=this;
|
||||
WindowManager.LayoutParams lp=new WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_APPLICATION_PANEL, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, PixelFormat.TRANSLUCENT);
|
||||
WindowManager.LayoutParams lp=new WindowManager.LayoutParams(WindowManager.LayoutParams.LAST_APPLICATION_WINDOW, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, PixelFormat.TRANSLUCENT);
|
||||
lp.width=ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
lp.height=ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
lp.gravity=Gravity.BOTTOM;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
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;
|
||||
@@ -13,6 +15,7 @@ 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.utils.UiUtils;
|
||||
|
||||
@@ -35,7 +38,7 @@ public class LinkCardStatusDisplayItem extends StatusDisplayItem{
|
||||
|
||||
@Override
|
||||
public Type getType(){
|
||||
return Type.CARD;
|
||||
return status.card.type==Card.Type.VIDEO || (status.card.image!=null && status.card.width>status.card.height) ? Type.CARD_LARGE : Type.CARD_COMPACT;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,36 +52,65 @@ public class LinkCardStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
public static class Holder extends StatusDisplayItem.Holder<LinkCardStatusDisplayItem> implements ImageLoaderViewHolder{
|
||||
private final TextView title, description, domain;
|
||||
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;
|
||||
|
||||
public Holder(Context context, ViewGroup parent){
|
||||
super(context, R.layout.display_item_link_card, parent);
|
||||
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);
|
||||
findViewById(R.id.inner).setOnClickListener(this::onClick);
|
||||
inner=findViewById(R.id.inner);
|
||||
inner.setOnClickListener(this::onClick);
|
||||
inner.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||
inner.setClipToOutline(true);
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@Override
|
||||
public void onBind(LinkCardStatusDisplayItem item){
|
||||
Card card=item.status.card;
|
||||
title.setText(card.title);
|
||||
description.setText(card.description);
|
||||
description.setVisibility(TextUtils.isEmpty(card.description) ? View.GONE : View.VISIBLE);
|
||||
domain.setText(Uri.parse(card.url).getHost());
|
||||
if(description!=null){
|
||||
description.setText(card.description);
|
||||
description.setVisibility(TextUtils.isEmpty(card.description) ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
String cardDomain=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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.joinmastodon.android.model.Translation;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||
import org.joinmastodon.android.ui.drawables.SpoilerStripesDrawable;
|
||||
import org.joinmastodon.android.ui.photoviewer.AltTextSheet;
|
||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
|
||||
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
|
||||
import org.joinmastodon.android.ui.views.FrameLayoutThatOnlyMeasuresFirstChild;
|
||||
@@ -103,11 +104,6 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
private final ArrayList<MediaAttachmentViewController> controllers=new ArrayList<>();
|
||||
|
||||
private final MaxWidthFrameLayout overlays;
|
||||
private final FrameLayout altTextWrapper;
|
||||
private final TextView altTextButton;
|
||||
private final View altTextScroller;
|
||||
private final ImageButton altTextClose;
|
||||
private final TextView altText;
|
||||
|
||||
private final View sensitiveOverlay;
|
||||
private final LayerDrawable sensitiveOverlayBG;
|
||||
@@ -115,9 +111,6 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
private final TextView hideSensitiveButton;
|
||||
private final TextView sensitiveText;
|
||||
|
||||
private int altTextIndex=-1;
|
||||
private Animator altTextAnimator;
|
||||
|
||||
public Holder(Activity activity, ViewGroup parent){
|
||||
super(new FrameLayoutThatOnlyMeasuresFirstChild(activity));
|
||||
wrapper=(FrameLayout)itemView;
|
||||
@@ -129,14 +122,6 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
overlays.setMaxWidth(V.dp(MediaGridLayout.MAX_WIDTH));
|
||||
wrapper.addView(overlays, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER_HORIZONTAL));
|
||||
|
||||
activity.getLayoutInflater().inflate(R.layout.overlay_image_alt_text, overlays);
|
||||
altTextWrapper=findViewById(R.id.alt_text_wrapper);
|
||||
altTextButton=findViewById(R.id.alt_button);
|
||||
altTextScroller=findViewById(R.id.alt_text_scroller);
|
||||
altTextClose=findViewById(R.id.alt_text_close);
|
||||
altText=findViewById(R.id.alt_text);
|
||||
altTextClose.setOnClickListener(this::onAltTextCloseClick);
|
||||
|
||||
hideSensitiveButton=(TextView) activity.getLayoutInflater().inflate(R.layout.alt_text_badge, overlays, false);
|
||||
hideSensitiveButton.setText(R.string.hide);
|
||||
FrameLayout.LayoutParams lp;
|
||||
@@ -160,9 +145,6 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
public void onBind(MediaGridStatusDisplayItem item){
|
||||
wrapper.setPadding(0, 0, 0, item.inset ? 0 : V.dp(8));
|
||||
|
||||
if(altTextAnimator!=null)
|
||||
altTextAnimator.cancel();
|
||||
|
||||
layout.setTiledLayout(item.tiledLayout);
|
||||
for(MediaAttachmentViewController c:controllers){
|
||||
item.viewPool.reuse(c.type, c);
|
||||
@@ -212,8 +194,6 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
c.bind(att, item.status);
|
||||
i++;
|
||||
}
|
||||
altTextWrapper.setVisibility(View.GONE);
|
||||
altTextIndex=-1;
|
||||
|
||||
if(!item.sensitiveRevealed){
|
||||
sensitiveOverlay.setVisibility(View.VISIBLE);
|
||||
@@ -246,115 +226,9 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
private void onAltTextClick(View v){
|
||||
if(altTextAnimator!=null)
|
||||
altTextAnimator.cancel();
|
||||
v.setVisibility(View.INVISIBLE);
|
||||
int index=(Integer)v.getTag();
|
||||
altTextIndex=index;
|
||||
Attachment att=item.attachments.get(index);
|
||||
altText.setText(att.description);
|
||||
altTextWrapper.setVisibility(View.VISIBLE);
|
||||
altTextWrapper.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||
@Override
|
||||
public boolean onPreDraw(){
|
||||
altTextWrapper.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
|
||||
int[] loc={0, 0};
|
||||
v.getLocationInWindow(loc);
|
||||
int btnL=loc[0], btnT=loc[1];
|
||||
overlays.getLocationInWindow(loc);
|
||||
btnL-=loc[0];
|
||||
btnT-=loc[1];
|
||||
|
||||
ArrayList<Animator> anims=new ArrayList<>();
|
||||
anims.add(ObjectAnimator.ofFloat(altTextButton, View.ALPHA, 1, 0));
|
||||
anims.add(ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, 0, 1));
|
||||
anims.add(ObjectAnimator.ofFloat(altTextClose, View.ALPHA, 0, 1));
|
||||
anims.add(ObjectAnimator.ofInt(altTextWrapper, "left", btnL, altTextWrapper.getLeft()));
|
||||
anims.add(ObjectAnimator.ofInt(altTextWrapper, "top", btnT, altTextWrapper.getTop()));
|
||||
anims.add(ObjectAnimator.ofInt(altTextWrapper, "right", btnL+v.getWidth(), altTextWrapper.getRight()));
|
||||
anims.add(ObjectAnimator.ofInt(altTextWrapper, "bottom", btnT+v.getHeight(), altTextWrapper.getBottom()));
|
||||
for(Animator a:anims)
|
||||
a.setDuration(300);
|
||||
|
||||
for(MediaAttachmentViewController c:controllers){
|
||||
if(c.altButton!=null && c.altButton!=v){
|
||||
anims.add(ObjectAnimator.ofFloat(c.altButton, View.ALPHA, 1, 0).setDuration(150));
|
||||
}
|
||||
}
|
||||
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(anims);
|
||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
altTextAnimator=null;
|
||||
for(MediaAttachmentViewController c:controllers){
|
||||
if(c.altButton!=null){
|
||||
c.altButton.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
altTextAnimator=set;
|
||||
set.start();
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onAltTextCloseClick(View v){
|
||||
if(altTextAnimator!=null)
|
||||
altTextAnimator.cancel();
|
||||
|
||||
View btn=controllers.get(altTextIndex).altButton;
|
||||
int i=0;
|
||||
for(MediaAttachmentViewController c:controllers){
|
||||
if(c.altButton!=null && c.altButton!=btn && !TextUtils.isEmpty(item.attachments.get(i).description))
|
||||
c.altButton.setVisibility(View.VISIBLE);
|
||||
i++;
|
||||
}
|
||||
|
||||
int[] loc={0, 0};
|
||||
btn.getLocationInWindow(loc);
|
||||
int btnL=loc[0], btnT=loc[1];
|
||||
overlays.getLocationInWindow(loc);
|
||||
btnL-=loc[0];
|
||||
btnT-=loc[1];
|
||||
|
||||
ArrayList<Animator> anims=new ArrayList<>();
|
||||
anims.add(ObjectAnimator.ofFloat(altTextButton, View.ALPHA, 1));
|
||||
anims.add(ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, 0));
|
||||
anims.add(ObjectAnimator.ofFloat(altTextClose, View.ALPHA, 0));
|
||||
anims.add(ObjectAnimator.ofInt(altTextWrapper, "left", btnL));
|
||||
anims.add(ObjectAnimator.ofInt(altTextWrapper, "top", btnT));
|
||||
anims.add(ObjectAnimator.ofInt(altTextWrapper, "right", btnL+btn.getWidth()));
|
||||
anims.add(ObjectAnimator.ofInt(altTextWrapper, "bottom", btnT+btn.getHeight()));
|
||||
for(Animator a:anims)
|
||||
a.setDuration(300);
|
||||
|
||||
for(MediaAttachmentViewController c:controllers){
|
||||
if(c.altButton!=null && c.altButton!=btn){
|
||||
anims.add(ObjectAnimator.ofFloat(c.altButton, View.ALPHA, 1).setDuration(150));
|
||||
}
|
||||
}
|
||||
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(anims);
|
||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
altTextAnimator=null;
|
||||
altTextWrapper.setVisibility(View.GONE);
|
||||
btn.setVisibility(View.VISIBLE);
|
||||
btn.setAlpha(1);
|
||||
}
|
||||
});
|
||||
altTextAnimator=set;
|
||||
set.start();
|
||||
new AltTextSheet(v.getContext(), att).show();
|
||||
}
|
||||
|
||||
public MediaAttachmentViewController getViewController(int index){
|
||||
|
||||
@@ -68,7 +68,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 -> new LinkCardStatusDisplayItem.Holder(activity, parent);
|
||||
case CARD_LARGE -> new LinkCardStatusDisplayItem.Holder(activity, parent, true);
|
||||
case CARD_COMPACT -> new LinkCardStatusDisplayItem.Holder(activity, parent, false);
|
||||
case FOOTER -> new FooterStatusDisplayItem.Holder(activity, parent);
|
||||
case ACCOUNT -> new AccountStatusDisplayItem.Holder(new AccountViewHolder(parentFragment, parent, null));
|
||||
case HASHTAG -> new HashtagStatusDisplayItem.Holder(activity, parent);
|
||||
@@ -208,7 +209,8 @@ public abstract class StatusDisplayItem{
|
||||
AUDIO,
|
||||
POLL_OPTION,
|
||||
POLL_FOOTER,
|
||||
CARD,
|
||||
CARD_LARGE,
|
||||
CARD_COMPACT,
|
||||
FOOTER,
|
||||
ACCOUNT,
|
||||
HASHTAG,
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.joinmastodon.android.ui.photoviewer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import me.grishka.appkit.views.BottomSheet;
|
||||
|
||||
public class AltTextSheet extends BottomSheet{
|
||||
public AltTextSheet(@NonNull Context context, Attachment attachment){
|
||||
super(context);
|
||||
|
||||
View content=context.getSystemService(LayoutInflater.class).inflate(R.layout.sheet_alt_text, null);
|
||||
setContentView(content);
|
||||
TextView altText=findViewById(R.id.alt_text);
|
||||
altText.setText(attachment.description);
|
||||
findViewById(R.id.alt_text_help).setOnClickListener(v->showAltTextHelp());
|
||||
setNavigationBarBackground(new ColorDrawable(UiUtils.alphaBlendColors(UiUtils.getThemeColor(context, R.attr.colorM3Surface),
|
||||
UiUtils.getThemeColor(context, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme());
|
||||
}
|
||||
|
||||
private void showAltTextHelp(){
|
||||
new M3AlertDialogBuilder(getContext())
|
||||
.setTitle(R.string.what_is_alt_text)
|
||||
.setMessage(UiUtils.fixBulletListInString(getContext(), R.string.alt_text_help))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
package org.joinmastodon.android.ui.photoviewer;
|
||||
|
||||
import android.Manifest;
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.DownloadManager;
|
||||
@@ -26,6 +30,8 @@ import android.os.SystemClock;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.util.Property;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.DisplayCutout;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
@@ -48,8 +54,12 @@ import android.widget.Toolbar;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
@@ -85,6 +95,8 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
private int currentIndex;
|
||||
private WindowManager wm;
|
||||
private Listener listener;
|
||||
private Status status;
|
||||
private String accountID;
|
||||
|
||||
private FrameLayout windowView;
|
||||
private FragmentRootLinearLayout uiOverlay;
|
||||
@@ -104,17 +116,32 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
if(uiVisible)
|
||||
toggleUI();
|
||||
};
|
||||
private Animator currentSheetRelatedToolbarAnimation;
|
||||
|
||||
private boolean videoPositionNeedsUpdating;
|
||||
private Runnable videoPositionUpdater=this::updateVideoPosition;
|
||||
private int videoDuration, videoInitialPosition, videoLastTimeUpdatePosition;
|
||||
private long videoInitialPositionTime;
|
||||
|
||||
public PhotoViewer(Activity activity, List<Attachment> attachments, int index, Listener listener){
|
||||
private static final Property<FragmentRootLinearLayout, Integer> STATUS_BAR_COLOR_PROPERTY=new Property<>(Integer.class, "Fdsafdsa"){
|
||||
@Override
|
||||
public Integer get(FragmentRootLinearLayout object){
|
||||
return object.getStatusBarColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(FragmentRootLinearLayout object, Integer value){
|
||||
object.setStatusBarColor(value);
|
||||
}
|
||||
};
|
||||
|
||||
public PhotoViewer(Activity activity, List<Attachment> attachments, int index, Status status, String accountID, Listener listener){
|
||||
this.activity=activity;
|
||||
this.attachments=attachments.stream().filter(a->a.type==Attachment.Type.IMAGE || a.type==Attachment.Type.GIFV || a.type==Attachment.Type.VIDEO).collect(Collectors.toList());
|
||||
currentIndex=index;
|
||||
this.listener=listener;
|
||||
this.status=status;
|
||||
this.accountID=accountID;
|
||||
|
||||
wm=activity.getWindowManager();
|
||||
|
||||
@@ -175,9 +202,15 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
toolbarWrap=uiOverlay.findViewById(R.id.toolbar_wrap);
|
||||
toolbar=uiOverlay.findViewById(R.id.toolbar);
|
||||
toolbar.setNavigationOnClickListener(v->onStartSwipeToDismissTransition(0));
|
||||
toolbar.getMenu().add(R.string.download).setIcon(R.drawable.ic_download_24px).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
if(status!=null)
|
||||
toolbar.getMenu().add(R.string.info).setIcon(R.drawable.ic_info_24px).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
else
|
||||
toolbar.getMenu().add(R.string.download).setIcon(R.drawable.ic_download_24px).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
toolbar.setOnMenuItemClickListener(item->{
|
||||
saveCurrentFile();
|
||||
if(status!=null)
|
||||
showInfoSheet();
|
||||
else
|
||||
saveCurrentFile();
|
||||
return true;
|
||||
});
|
||||
uiOverlay.setAlpha(0f);
|
||||
@@ -610,6 +643,93 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
}
|
||||
}
|
||||
|
||||
private void showInfoSheet(){
|
||||
pauseVideo();
|
||||
PhotoViewerInfoSheet sheet=new PhotoViewerInfoSheet(new ContextThemeWrapper(activity, R.style.Theme_Mastodon_Dark), attachments.get(currentIndex), toolbar.getHeight(), new PhotoViewerInfoSheet.Listener(){
|
||||
private boolean ignoreBeforeDismiss;
|
||||
|
||||
@Override
|
||||
public void onBeforeDismiss(int duration){
|
||||
if(ignoreBeforeDismiss)
|
||||
return;
|
||||
if(currentSheetRelatedToolbarAnimation!=null)
|
||||
currentSheetRelatedToolbarAnimation.cancel();
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(
|
||||
ObjectAnimator.ofFloat(pager, View.TRANSLATION_Y, 0),
|
||||
ObjectAnimator.ofFloat(toolbarWrap, View.ALPHA, 1f),
|
||||
ObjectAnimator.ofArgb(uiOverlay, STATUS_BAR_COLOR_PROPERTY, 0x80000000)
|
||||
);
|
||||
set.setDuration(duration);
|
||||
set.setInterpolator(CubicBezierInterpolator.EASE_OUT);
|
||||
currentSheetRelatedToolbarAnimation=set;
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
currentSheetRelatedToolbarAnimation=null;
|
||||
}
|
||||
});
|
||||
set.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismissEntireViewer(){
|
||||
ignoreBeforeDismiss=true;
|
||||
onStartSwipeToDismissTransition(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onButtonClick(int id){
|
||||
if(id==R.id.btn_boost){
|
||||
if(status!=null){
|
||||
AccountSessionManager.get(accountID).getStatusInteractionController().setReblogged(status, !status.reblogged);
|
||||
}
|
||||
}else if(id==R.id.btn_favorite){
|
||||
if(status!=null){
|
||||
AccountSessionManager.get(accountID).getStatusInteractionController().setFavorited(status, !status.favourited);
|
||||
}
|
||||
}else if(id==R.id.btn_share){
|
||||
if(status!=null){
|
||||
UiUtils.openSystemShareSheet(activity, status.url);
|
||||
}
|
||||
}else if(id==R.id.btn_bookmark){
|
||||
if(status!=null){
|
||||
AccountSessionManager.get(accountID).getStatusInteractionController().setBookmarked(status, !status.bookmarked);
|
||||
}
|
||||
}else if(id==R.id.btn_download){
|
||||
saveCurrentFile();
|
||||
}
|
||||
}
|
||||
});
|
||||
sheet.setStatus(status);
|
||||
sheet.show();
|
||||
if(currentSheetRelatedToolbarAnimation!=null)
|
||||
currentSheetRelatedToolbarAnimation.cancel();
|
||||
sheet.getWindow().getDecorView().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||
@Override
|
||||
public boolean onPreDraw(){
|
||||
sheet.getWindow().getDecorView().getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(
|
||||
ObjectAnimator.ofFloat(pager, View.TRANSLATION_Y, -pager.getHeight()*0.2f),
|
||||
ObjectAnimator.ofFloat(toolbarWrap, View.ALPHA, 0f),
|
||||
ObjectAnimator.ofArgb(uiOverlay, STATUS_BAR_COLOR_PROPERTY, 0)
|
||||
);
|
||||
set.setDuration(300);
|
||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
currentSheetRelatedToolbarAnimation=set;
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
currentSheetRelatedToolbarAnimation=null;
|
||||
}
|
||||
});
|
||||
set.start();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public interface Listener{
|
||||
void setPhotoViewVisibility(int index, boolean visible);
|
||||
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
package org.joinmastodon.android.ui.photoviewer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.BottomSheet;
|
||||
|
||||
public class PhotoViewerInfoSheet extends BottomSheet{
|
||||
private final Attachment attachment;
|
||||
private final View buttonsContainer;
|
||||
private final TextView altText;
|
||||
private final ImageButton backButton, infoButton;
|
||||
private final Button boostBtn, favoriteBtn, bookmarkBtn;
|
||||
private final Listener listener;
|
||||
private String statusID;
|
||||
|
||||
public PhotoViewerInfoSheet(@NonNull Context context, Attachment attachment, int toolbarHeight, Listener listener){
|
||||
super(context);
|
||||
this.attachment=attachment;
|
||||
this.listener=listener;
|
||||
|
||||
dimAmount=0;
|
||||
View content=context.getSystemService(LayoutInflater.class).inflate(R.layout.sheet_photo_viewer_info, null);
|
||||
setContentView(content);
|
||||
setNavigationBarBackground(new ColorDrawable(UiUtils.alphaBlendColors(UiUtils.getThemeColor(context, R.attr.colorM3Surface),
|
||||
UiUtils.getThemeColor(context, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme());
|
||||
|
||||
buttonsContainer=findViewById(R.id.buttons_container);
|
||||
altText=findViewById(R.id.alt_text);
|
||||
|
||||
if(TextUtils.isEmpty(attachment.description)){
|
||||
findViewById(R.id.alt_text).setVisibility(View.GONE);
|
||||
findViewById(R.id.alt_text_title).setVisibility(View.GONE);
|
||||
findViewById(R.id.divider).setVisibility(View.GONE);
|
||||
}else{
|
||||
altText.setText(attachment.description);
|
||||
findViewById(R.id.alt_text_help).setOnClickListener(v->showAltTextHelp());
|
||||
}
|
||||
|
||||
backButton=new ImageButton(context);
|
||||
backButton.setImageResource(R.drawable.ic_arrow_back);
|
||||
backButton.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(context, R.attr.colorM3OnSurfaceVariant)));
|
||||
backButton.setBackgroundResource(R.drawable.bg_button_m3_tonal_icon);
|
||||
backButton.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
|
||||
backButton.setElevation(V.dp(2));
|
||||
backButton.setAlpha(0f);
|
||||
backButton.setOnClickListener(v->{
|
||||
listener.onDismissEntireViewer();
|
||||
dismiss();
|
||||
});
|
||||
|
||||
infoButton=new ImageButton(context);
|
||||
infoButton.setImageResource(R.drawable.ic_info_fill1_24px);
|
||||
infoButton.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(context, R.attr.colorM3OnPrimary)));
|
||||
infoButton.setBackgroundResource(R.drawable.bg_button_m3_filled_icon);
|
||||
infoButton.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
|
||||
infoButton.setElevation(V.dp(2));
|
||||
infoButton.setAlpha(0f);
|
||||
infoButton.setSelected(true);
|
||||
infoButton.setOnClickListener(v->dismiss());
|
||||
|
||||
FrameLayout.LayoutParams lp=new FrameLayout.LayoutParams(V.dp(48), V.dp(48));
|
||||
lp.topMargin=toolbarHeight/2-V.dp(24);
|
||||
lp.leftMargin=lp.rightMargin=V.dp(4);
|
||||
lp.gravity=Gravity.START | Gravity.TOP;
|
||||
container.addView(backButton, lp);
|
||||
|
||||
lp=new FrameLayout.LayoutParams(lp);
|
||||
lp.leftMargin=lp.rightMargin=0;
|
||||
lp.gravity=Gravity.END | Gravity.TOP;
|
||||
container.addView(infoButton, lp);
|
||||
|
||||
boostBtn=findViewById(R.id.btn_boost);
|
||||
favoriteBtn=findViewById(R.id.btn_favorite);
|
||||
bookmarkBtn=findViewById(R.id.btn_bookmark);
|
||||
View.OnClickListener clickListener=v->listener.onButtonClick(v.getId());
|
||||
|
||||
boostBtn.setOnClickListener(clickListener);
|
||||
favoriteBtn.setOnClickListener(clickListener);
|
||||
findViewById(R.id.btn_share).setOnClickListener(clickListener);
|
||||
bookmarkBtn.setOnClickListener(clickListener);
|
||||
findViewById(R.id.btn_download).setOnClickListener(clickListener);
|
||||
}
|
||||
|
||||
private void showAltTextHelp(){
|
||||
new M3AlertDialogBuilder(getContext())
|
||||
.setTitle(R.string.what_is_alt_text)
|
||||
.setMessage(UiUtils.fixBulletListInString(getContext(), R.string.alt_text_help))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dismiss(){
|
||||
if(dismissed)
|
||||
return;
|
||||
int height=content.getHeight();
|
||||
int duration=Math.max(60, (int) (180 * (height - content.getTranslationY()) / (float) height));
|
||||
listener.onBeforeDismiss(duration);
|
||||
backButton.animate().alpha(0).setDuration(duration).setInterpolator(CubicBezierInterpolator.EASE_OUT).start();
|
||||
infoButton.animate().alpha(0).setDuration(duration).setInterpolator(CubicBezierInterpolator.EASE_OUT).start();
|
||||
super.dismiss();
|
||||
E.unregister(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show(){
|
||||
super.show();
|
||||
E.register(this);
|
||||
content.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||
@Override
|
||||
public boolean onPreDraw(){
|
||||
content.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
backButton.animate().alpha(1).setDuration(300).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
||||
infoButton.animate().alpha(1).setDuration(300).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setStatus(Status status){
|
||||
statusID=status.id;
|
||||
boostBtn.setCompoundDrawablesWithIntrinsicBounds(0, switch(status.visibility){
|
||||
case DIRECT -> R.drawable.ic_boost_disabled_24px;
|
||||
case PUBLIC, UNLISTED -> R.drawable.ic_boost;
|
||||
case PRIVATE -> R.drawable.ic_boost_private;
|
||||
}, 0, 0);
|
||||
boostBtn.setEnabled(status.visibility!=StatusPrivacy.DIRECT);
|
||||
setButtonStates(status.reblogged, status.favourited, status.bookmarked);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onCountersUpdated(StatusCountersUpdatedEvent ev){
|
||||
if(ev.id.equals(statusID)){
|
||||
setButtonStates(ev.reblogged, ev.favorited, ev.bookmarked);
|
||||
}
|
||||
}
|
||||
|
||||
private void setButtonStates(boolean reblogged, boolean favorited, boolean bookmarked){
|
||||
boostBtn.setText(reblogged ? R.string.button_reblogged : R.string.button_reblog);
|
||||
boostBtn.setSelected(reblogged);
|
||||
|
||||
favoriteBtn.setText(favorited ? R.string.button_favorited : R.string.button_favorite);
|
||||
favoriteBtn.setSelected(favorited);
|
||||
|
||||
bookmarkBtn.setText(bookmarked ? R.string.bookmarked : R.string.add_bookmark);
|
||||
bookmarkBtn.setSelected(bookmarked);
|
||||
}
|
||||
|
||||
public interface Listener{
|
||||
void onBeforeDismiss(int duration);
|
||||
void onDismissEntireViewer();
|
||||
void onButtonClick(int id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package org.joinmastodon.android.ui.sheets;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.InsetDrawable;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.ui.drawables.EmptyDrawable;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.BottomSheet;
|
||||
|
||||
public abstract class AccountRestrictionConfirmationSheet extends BottomSheet{
|
||||
private LinearLayout contentWrap;
|
||||
protected Button cancelBtn;
|
||||
protected ProgressBarButton confirmBtn, secondaryBtn;
|
||||
protected TextView titleView, subtitleView;
|
||||
protected ImageView icon;
|
||||
protected boolean loading;
|
||||
|
||||
public AccountRestrictionConfirmationSheet(@NonNull Context context, Account user, ConfirmCallback confirmCallback){
|
||||
super(context);
|
||||
View content=context.getSystemService(LayoutInflater.class).inflate(R.layout.sheet_restrict_account, null);
|
||||
setContentView(content);
|
||||
setNavigationBarBackground(new ColorDrawable(UiUtils.alphaBlendColors(UiUtils.getThemeColor(context, R.attr.colorM3Surface),
|
||||
UiUtils.getThemeColor(context, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme());
|
||||
|
||||
contentWrap=findViewById(R.id.content_wrap);
|
||||
titleView=findViewById(R.id.title);
|
||||
subtitleView=findViewById(R.id.text);
|
||||
cancelBtn=findViewById(R.id.btn_cancel);
|
||||
confirmBtn=findViewById(R.id.btn_confirm);
|
||||
secondaryBtn=findViewById(R.id.btn_secondary);
|
||||
icon=findViewById(R.id.icon);
|
||||
|
||||
contentWrap.setDividerDrawable(new EmptyDrawable(1, V.dp(8)));
|
||||
contentWrap.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);
|
||||
confirmBtn.setOnClickListener(v->{
|
||||
if(loading)
|
||||
return;
|
||||
loading=true;
|
||||
confirmBtn.setProgressBarVisible(true);
|
||||
confirmCallback.onConfirmed(this::dismiss, ()->{
|
||||
confirmBtn.setProgressBarVisible(false);
|
||||
loading=false;
|
||||
});
|
||||
});
|
||||
cancelBtn.setOnClickListener(v->{
|
||||
if(!loading)
|
||||
dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
protected void addRow(@DrawableRes int icon, CharSequence text){
|
||||
TextView tv=new TextView(getContext());
|
||||
tv.setTextAppearance(R.style.m3_body_large);
|
||||
tv.setTextColor(UiUtils.getThemeColor(getContext(), R.attr.colorM3OnSurfaceVariant));
|
||||
tv.setCompoundDrawableTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getContext(), R.attr.colorM3Primary)));
|
||||
tv.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
|
||||
tv.setText(text);
|
||||
InsetDrawable drawable=new InsetDrawable(getContext().getResources().getDrawable(icon, getContext().getTheme()), V.dp(8));
|
||||
drawable.setBounds(0, 0, V.dp(40), V.dp(40));
|
||||
tv.setCompoundDrawablesRelative(drawable, null, null, null);
|
||||
tv.setCompoundDrawablePadding(V.dp(16));
|
||||
contentWrap.addView(tv, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
}
|
||||
|
||||
protected void addRow(@DrawableRes int icon, @StringRes int text){
|
||||
addRow(icon, getContext().getString(text));
|
||||
}
|
||||
|
||||
public interface ConfirmCallback{
|
||||
void onConfirmed(Runnable onSuccess, Runnable onError);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
package org.joinmastodon.android.ui;
|
||||
package org.joinmastodon.android.ui.sheets;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
@@ -25,6 +23,9 @@ import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.HomeFragment;
|
||||
import org.joinmastodon.android.fragments.SplashFragment;
|
||||
import org.joinmastodon.android.ui.ClickableSingleViewRecyclerAdapter;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.CheckableRelativeLayout;
|
||||
|
||||
@@ -37,7 +38,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import me.grishka.appkit.FragmentStackActivity;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.joinmastodon.android.ui.sheets;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class BlockAccountConfirmationSheet extends AccountRestrictionConfirmationSheet{
|
||||
public BlockAccountConfirmationSheet(@NonNull Context context, Account user, ConfirmCallback confirmCallback){
|
||||
super(context, user, confirmCallback);
|
||||
titleView.setText(R.string.block_user_confirm_title);
|
||||
confirmBtn.setText(R.string.do_block);
|
||||
secondaryBtn.setVisibility(View.GONE);
|
||||
icon.setImageResource(R.drawable.ic_block_24px);
|
||||
subtitleView.setText(user.getDisplayUsername());
|
||||
addRow(R.drawable.ic_campaign_24px, R.string.user_can_see_blocked);
|
||||
addRow(R.drawable.ic_visibility_off_24px, R.string.user_cant_see_each_other_posts);
|
||||
addRow(R.drawable.ic_alternate_email_24px, R.string.you_wont_see_user_mentions);
|
||||
addRow(R.drawable.ic_reply_24px, R.string.user_cant_mention_or_follow_you);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.joinmastodon.android.ui.sheets;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class BlockDomainConfirmationSheet extends AccountRestrictionConfirmationSheet{
|
||||
public BlockDomainConfirmationSheet(@NonNull Context context, Account user, ConfirmCallback confirmCallback, ConfirmCallback blockUserConfirmCallback){
|
||||
super(context, user, confirmCallback);
|
||||
titleView.setText(R.string.block_domain_confirm_title);
|
||||
confirmBtn.setText(R.string.do_block_server);
|
||||
secondaryBtn.setText(context.getString(R.string.block_user_x_instead, user.getDisplayUsername()));
|
||||
icon.setImageResource(R.drawable.ic_domain_disabled_24px);
|
||||
subtitleView.setText(user.getDomain());
|
||||
addRow(R.drawable.ic_campaign_24px, R.string.users_cant_see_blocked);
|
||||
addRow(R.drawable.ic_visibility_off_24px, R.string.you_wont_see_server_posts);
|
||||
addRow(R.drawable.ic_person_remove_24px, R.string.server_followers_will_be_removed);
|
||||
addRow(R.drawable.ic_reply_24px, R.string.server_cant_mention_or_follow_you);
|
||||
addRow(R.drawable.ic_history_24px, R.string.server_can_interact_with_older);
|
||||
|
||||
secondaryBtn.setOnClickListener(v->{
|
||||
if(loading)
|
||||
return;
|
||||
loading=true;
|
||||
secondaryBtn.setProgressBarVisible(true);
|
||||
blockUserConfirmCallback.onConfirmed(this::dismiss, ()->{
|
||||
secondaryBtn.setProgressBarVisible(false);
|
||||
loading=false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package org.joinmastodon.android.ui.sheets;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.Snackbar;
|
||||
import org.joinmastodon.android.ui.text.LinkSpan;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.RippleAnimationTextView;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.nodes.Node;
|
||||
import org.jsoup.nodes.TextNode;
|
||||
import org.jsoup.select.NodeVisitor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import me.grishka.appkit.views.BottomSheet;
|
||||
|
||||
public class DecentralizationExplainerSheet extends BottomSheet{
|
||||
private final String handleStr;
|
||||
|
||||
public DecentralizationExplainerSheet(@NonNull Context context, String accountID, Account account){
|
||||
super(context);
|
||||
View content=context.getSystemService(LayoutInflater.class).inflate(R.layout.sheet_decentralization_info, null);
|
||||
setContentView(content);
|
||||
setNavigationBarBackground(new ColorDrawable(UiUtils.alphaBlendColors(UiUtils.getThemeColor(context, R.attr.colorM3Surface),
|
||||
UiUtils.getThemeColor(context, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme());
|
||||
|
||||
TextView handleTitle=findViewById(R.id.handle_title);
|
||||
RippleAnimationTextView handle=findViewById(R.id.handle);
|
||||
TextView usernameExplanation=findViewById(R.id.username_text);
|
||||
TextView serverExplanation=findViewById(R.id.server_text);
|
||||
TextView handleExplanation=findViewById(R.id.handle_explanation);
|
||||
findViewById(R.id.btn_cancel).setOnClickListener(v->dismiss());
|
||||
|
||||
String domain=account.getDomain();
|
||||
if(TextUtils.isEmpty(domain))
|
||||
domain=AccountSessionManager.get(accountID).domain;
|
||||
handleStr="@"+account.username+"@"+domain;
|
||||
boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account);
|
||||
|
||||
handleTitle.setText(isSelf ? R.string.handle_title_own : R.string.handle_title);
|
||||
handle.setText(handleStr);
|
||||
usernameExplanation.setText(isSelf ? R.string.handle_username_explanation_own : R.string.handle_username_explanation);
|
||||
serverExplanation.setText(isSelf ? R.string.handle_server_explanation_own : R.string.handle_server_explanation);
|
||||
|
||||
String explanation=context.getString(isSelf ? R.string.handle_explanation_own : R.string.handle_explanation);
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder();
|
||||
Jsoup.parseBodyFragment(explanation).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("", DecentralizationExplainerSheet.this::showActivityPubAlert, LinkSpan.Type.CUSTOM, null, null, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
});
|
||||
handleExplanation.setText(ssb);
|
||||
|
||||
findViewById(R.id.handle_wrap).setOnClickListener(v->{
|
||||
context.getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText(null, handleStr));
|
||||
if(UiUtils.needShowClipboardToast()){
|
||||
new Snackbar.Builder(context)
|
||||
.setText(R.string.handle_copied)
|
||||
.show();
|
||||
}
|
||||
});
|
||||
String _domain=domain;
|
||||
findViewById(R.id.username_row).setOnClickListener(v->handle.animate(1, account.username.length()+1));
|
||||
findViewById(R.id.server_row).setOnClickListener(v->handle.animate(handleStr.length()-_domain.length(), handleStr.length()));
|
||||
}
|
||||
|
||||
private void showActivityPubAlert(LinkSpan s){
|
||||
new M3AlertDialogBuilder(getContext())
|
||||
.setTitle(R.string.what_is_activitypub_title)
|
||||
.setMessage(R.string.what_is_activitypub)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.joinmastodon.android.ui.sheets;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class MuteAccountConfirmationSheet extends AccountRestrictionConfirmationSheet{
|
||||
public MuteAccountConfirmationSheet(@NonNull Context context, Account user, ConfirmCallback confirmCallback){
|
||||
super(context, user, confirmCallback);
|
||||
titleView.setText(R.string.mute_user_confirm_title);
|
||||
confirmBtn.setText(R.string.do_mute);
|
||||
secondaryBtn.setVisibility(View.GONE);
|
||||
icon.setImageResource(R.drawable.ic_volume_off_24px);
|
||||
subtitleView.setText(user.getDisplayUsername());
|
||||
addRow(R.drawable.ic_campaign_24px, R.string.user_wont_know_muted);
|
||||
addRow(R.drawable.ic_visibility_off_24px, R.string.user_can_still_see_your_posts);
|
||||
addRow(R.drawable.ic_alternate_email_24px, R.string.you_wont_see_user_mentions);
|
||||
addRow(R.drawable.ic_reply_24px, R.string.user_can_mention_and_follow_you);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.joinmastodon.android.ui;
|
||||
package org.joinmastodon.android.ui.sheets;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
@@ -12,7 +12,9 @@ import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
@@ -25,7 +27,7 @@ public class NonMutualPreReplySheet extends PreReplySheet{
|
||||
private boolean fullBioShown=false;
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
public NonMutualPreReplySheet(@NonNull Context context, ResultListener resultListener, Account account){
|
||||
public NonMutualPreReplySheet(@NonNull Context context, ResultListener resultListener, Account account, String accountID){
|
||||
super(context, resultListener);
|
||||
icon.setImageResource(R.drawable.ic_waving_hand_24px);
|
||||
title.setText(R.string.non_mutual_sheet_title);
|
||||
@@ -55,11 +57,16 @@ public class NonMutualPreReplySheet extends PreReplySheet{
|
||||
name.setEllipsize(TextUtils.TruncateAt.END);
|
||||
name.setTextAppearance(R.style.m3_title_medium);
|
||||
name.setTextColor(UiUtils.getThemeColor(context, R.attr.colorM3OnSurface));
|
||||
name.setText(account.displayName);
|
||||
if(AccountSessionManager.get(accountID).getLocalPreferences().customEmojiInNames){
|
||||
name.setText(HtmlParser.parseCustomEmoji(account.displayName, account.emojis));
|
||||
UiUtils.loadCustomEmojiInTextView(name);
|
||||
}else{
|
||||
name.setText(account.displayName);
|
||||
}
|
||||
name.setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
|
||||
nameAndFields.addView(name, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(24)));
|
||||
if(!TextUtils.isEmpty(account.note)){
|
||||
String strippedBio=HtmlParser.stripAndRemoveInvisibleSpans(account.note);
|
||||
CharSequence strippedBio=HtmlParser.parseCustomEmoji(HtmlParser.stripAndRemoveInvisibleSpans(account.note), account.emojis);
|
||||
TextView bioShort=new TextView(context);
|
||||
bioShort.setTextAppearance(R.style.m3_body_medium);
|
||||
bioShort.setTextColor(UiUtils.getThemeColor(context, R.attr.colorM3Secondary));
|
||||
@@ -85,6 +92,8 @@ public class NonMutualPreReplySheet extends PreReplySheet{
|
||||
bioShort.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
UiUtils.loadCustomEmojiInTextView(bioShort);
|
||||
UiUtils.loadCustomEmojiInTextView(bioFull);
|
||||
}else{
|
||||
TextView username=new TextView(context);
|
||||
username.setTextAppearance(R.style.m3_body_medium);
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.joinmastodon.android.ui;
|
||||
package org.joinmastodon.android.ui.sheets;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.joinmastodon.android.ui;
|
||||
package org.joinmastodon.android.ui.sheets;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
@@ -52,6 +52,7 @@ public class HtmlParser{
|
||||
")" +
|
||||
")";
|
||||
public static final Pattern URL_PATTERN=Pattern.compile(VALID_URL_PATTERN_STRING, Pattern.CASE_INSENSITIVE);
|
||||
public static final Pattern INVITE_LINK_PATTERN=Pattern.compile("^https://"+Regex.URL_VALID_DOMAIN+"/invite/[a-z\\d]+$", Pattern.CASE_INSENSITIVE);
|
||||
private static Pattern EMOJI_CODE_PATTERN=Pattern.compile(":([\\w]+):");
|
||||
|
||||
private HtmlParser(){}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.joinmastodon.android.ui.text;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.text.style.ImageSpan;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class ImageSpanThatDoesNotBreakShitForNoGoodReason extends ImageSpan{
|
||||
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Bitmap b){
|
||||
super(b);
|
||||
}
|
||||
|
||||
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Bitmap b, int verticalAlignment){
|
||||
super(b, verticalAlignment);
|
||||
}
|
||||
|
||||
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Context context, @NonNull Bitmap bitmap){
|
||||
super(context, bitmap);
|
||||
}
|
||||
|
||||
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Context context, @NonNull Bitmap bitmap, int verticalAlignment){
|
||||
super(context, bitmap, verticalAlignment);
|
||||
}
|
||||
|
||||
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Drawable drawable){
|
||||
super(drawable);
|
||||
}
|
||||
|
||||
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Drawable drawable, int verticalAlignment){
|
||||
super(drawable, verticalAlignment);
|
||||
}
|
||||
|
||||
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Drawable drawable, @NonNull String source){
|
||||
super(drawable, source);
|
||||
}
|
||||
|
||||
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Drawable drawable, @NonNull String source, int verticalAlignment){
|
||||
super(drawable, source, verticalAlignment);
|
||||
}
|
||||
|
||||
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Context context, @NonNull Uri uri){
|
||||
super(context, uri);
|
||||
}
|
||||
|
||||
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Context context, @NonNull Uri uri, int verticalAlignment){
|
||||
super(context, uri, verticalAlignment);
|
||||
}
|
||||
|
||||
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Context context, int resourceId){
|
||||
super(context, resourceId);
|
||||
}
|
||||
|
||||
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Context context, int resourceId, int verticalAlignment){
|
||||
super(context, resourceId, verticalAlignment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm){
|
||||
// Purposefully not touching the font metrics
|
||||
return getDrawable().getBounds().right;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{
|
||||
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();
|
||||
if(type==StatusDisplayItem.Type.CARD || type==StatusDisplayItem.Type.MEDIA_GRID)
|
||||
if(type==StatusDisplayItem.Type.CARD_LARGE || type==StatusDisplayItem.Type.MEDIA_GRID)
|
||||
outRect.left=outRect.right=V.dp(16);
|
||||
else
|
||||
outRect.left=outRect.right=V.dp(8);
|
||||
|
||||
@@ -11,9 +11,11 @@ import android.content.res.TypedArray;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.InsetDrawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@@ -26,11 +28,14 @@ import android.provider.OpenableColumns;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.BulletSpan;
|
||||
import android.transition.ChangeBounds;
|
||||
import android.transition.ChangeScroll;
|
||||
import android.transition.Fade;
|
||||
import android.transition.TransitionManager;
|
||||
import android.transition.TransitionSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@@ -38,7 +43,9 @@ import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.Toolbar;
|
||||
@@ -68,6 +75,10 @@ import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.model.SearchResults;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.Snackbar;
|
||||
import org.joinmastodon.android.ui.sheets.BlockAccountConfirmationSheet;
|
||||
import org.joinmastodon.android.ui.sheets.BlockDomainConfirmationSheet;
|
||||
import org.joinmastodon.android.ui.sheets.MuteAccountConfirmationSheet;
|
||||
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
|
||||
import org.joinmastodon.android.ui.text.SpacerSpan;
|
||||
import org.parceler.Parcels;
|
||||
@@ -375,72 +386,142 @@ public class UiUtils{
|
||||
}
|
||||
|
||||
public static void confirmToggleBlockUser(Activity activity, String accountID, Account account, boolean currentlyBlocked, Consumer<Relationship> resultCallback){
|
||||
showConfirmationAlert(activity, activity.getString(currentlyBlocked ? R.string.confirm_unblock_title : R.string.confirm_block_title),
|
||||
activity.getString(currentlyBlocked ? R.string.confirm_unblock : R.string.confirm_block, account.displayName),
|
||||
activity.getString(currentlyBlocked ? R.string.do_unblock : R.string.do_block), ()->{
|
||||
new SetAccountBlocked(account.id, !currentlyBlocked)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
resultCallback.accept(result);
|
||||
if(!currentlyBlocked){
|
||||
E.post(new RemoveAccountPostsEvent(accountID, account.id, false));
|
||||
}
|
||||
}
|
||||
if(!currentlyBlocked){
|
||||
new BlockAccountConfirmationSheet(activity, account, (onSuccess, onError)->{
|
||||
new SetAccountBlocked(account.id, true)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
resultCallback.accept(result);
|
||||
onSuccess.run();
|
||||
E.post(new RemoveAccountPostsEvent(accountID, account.id, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(activity);
|
||||
}
|
||||
})
|
||||
.wrapProgress(activity, R.string.loading, false)
|
||||
.exec(accountID);
|
||||
});
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(activity);
|
||||
onError.run();
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}).show();
|
||||
}else{
|
||||
new SetAccountBlocked(account.id, false)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
resultCallback.accept(result);
|
||||
new Snackbar.Builder(activity)
|
||||
.setText(activity.getString(R.string.unblocked_user_x, account.getDisplayUsername()))
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(activity);
|
||||
}
|
||||
})
|
||||
.wrapProgress(activity, R.string.loading, false)
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
|
||||
public static void confirmToggleBlockDomain(Activity activity, String accountID, String domain, boolean currentlyBlocked, Runnable resultCallback){
|
||||
showConfirmationAlert(activity, activity.getString(currentlyBlocked ? R.string.confirm_unblock_domain_title : R.string.confirm_block_domain_title),
|
||||
activity.getString(currentlyBlocked ? R.string.confirm_unblock : R.string.confirm_block, domain),
|
||||
activity.getString(currentlyBlocked ? R.string.do_unblock : R.string.do_block), ()->{
|
||||
new SetDomainBlocked(domain, !currentlyBlocked)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Object result){
|
||||
resultCallback.run();
|
||||
}
|
||||
public static void confirmToggleBlockDomain(Activity activity, String accountID, Account account, boolean currentlyBlocked, Runnable resultCallback, Consumer<Relationship> callbackInCaseUserWasBlockedInstead){
|
||||
if(!currentlyBlocked){
|
||||
new BlockDomainConfirmationSheet(activity, account, (onSuccess, onError)->{
|
||||
new SetDomainBlocked(account.getDomain(), true)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Object result){
|
||||
resultCallback.run();
|
||||
onSuccess.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(activity);
|
||||
}
|
||||
})
|
||||
.wrapProgress(activity, R.string.loading, false)
|
||||
.exec(accountID);
|
||||
});
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(activity);
|
||||
onError.run();
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}, (onSuccess, onError)->{
|
||||
new SetAccountBlocked(account.id, true)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
callbackInCaseUserWasBlockedInstead.accept(result);
|
||||
onSuccess.run();
|
||||
E.post(new RemoveAccountPostsEvent(accountID, account.id, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(activity);
|
||||
onError.run();
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}).show();
|
||||
}else{
|
||||
new SetDomainBlocked(account.getDomain(), false)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Object result){
|
||||
resultCallback.run();
|
||||
new Snackbar.Builder(activity)
|
||||
.setText(activity.getString(R.string.unblocked_domain_x, account.getDomain()))
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(activity);
|
||||
}
|
||||
})
|
||||
.wrapProgress(activity, R.string.loading, false)
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
|
||||
public static void confirmToggleMuteUser(Activity activity, String accountID, Account account, boolean currentlyMuted, Consumer<Relationship> resultCallback){
|
||||
showConfirmationAlert(activity, activity.getString(currentlyMuted ? R.string.confirm_unmute_title : R.string.confirm_mute_title),
|
||||
activity.getString(currentlyMuted ? R.string.confirm_unmute : R.string.confirm_mute, account.displayName),
|
||||
activity.getString(currentlyMuted ? R.string.do_unmute : R.string.do_mute), ()->{
|
||||
new SetAccountMuted(account.id, !currentlyMuted)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
resultCallback.accept(result);
|
||||
if(!currentlyMuted){
|
||||
E.post(new RemoveAccountPostsEvent(accountID, account.id, false));
|
||||
}
|
||||
}
|
||||
if(!currentlyMuted){
|
||||
new MuteAccountConfirmationSheet(activity, account, (onSuccess, onError)->{
|
||||
new SetAccountMuted(account.id, true)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
resultCallback.accept(result);
|
||||
onSuccess.run();
|
||||
E.post(new RemoveAccountPostsEvent(accountID, account.id, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(activity);
|
||||
}
|
||||
})
|
||||
.wrapProgress(activity, R.string.loading, false)
|
||||
.exec(accountID);
|
||||
});
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(activity);
|
||||
onError.run();
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}).show();
|
||||
}else{
|
||||
new SetAccountMuted(account.id, false)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
resultCallback.accept(result);
|
||||
new Snackbar.Builder(activity)
|
||||
.setText(activity.getString(R.string.unmuted_user_x, account.getDisplayUsername()))
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(activity);
|
||||
}
|
||||
})
|
||||
.wrapProgress(activity, R.string.loading, false)
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
|
||||
public static void confirmDeletePost(Activity activity, String accountID, Status status, Consumer<Status> resultCallback){
|
||||
@@ -848,11 +929,15 @@ public class UiUtils{
|
||||
public static void maybeShowTextCopiedToast(Context context){
|
||||
//show toast, android from S_V2 on has built-in popup, as documented in
|
||||
//https://developer.android.com/develop/ui/views/touch-and-input/copy-paste#duplicate-notifications
|
||||
if(Build.VERSION.SDK_INT<=Build.VERSION_CODES.S_V2){
|
||||
if(needShowClipboardToast()){
|
||||
Toast.makeText(context, R.string.text_copied, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean needShowClipboardToast(){
|
||||
return Build.VERSION.SDK_INT<=Build.VERSION_CODES.S_V2;
|
||||
}
|
||||
|
||||
public static void setAllPaddings(View view, int paddingDp){
|
||||
int pad=V.dp(paddingDp);
|
||||
view.setPadding(pad, pad, pad, pad);
|
||||
@@ -866,4 +951,46 @@ public class UiUtils{
|
||||
lp.setMarginEnd(V.dp(marginEnd));
|
||||
return lp;
|
||||
}
|
||||
|
||||
public static CharSequence fixBulletListInString(Context context, @StringRes int res){
|
||||
SpannableStringBuilder msg=new SpannableStringBuilder(context.getText(res));
|
||||
BulletSpan[] spans=msg.getSpans(0, msg.length(), BulletSpan.class);
|
||||
for(BulletSpan span:spans){
|
||||
BulletSpan betterSpan;
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.Q)
|
||||
betterSpan=new BulletSpan(V.dp(10), UiUtils.getThemeColor(context, R.attr.colorM3OnSurface));
|
||||
else
|
||||
betterSpan=new BulletSpan(V.dp(10), UiUtils.getThemeColor(context, R.attr.colorM3OnSurface), V.dp(1.5f));
|
||||
msg.setSpan(betterSpan, msg.getSpanStart(span), msg.getSpanEnd(span), msg.getSpanFlags(span));
|
||||
msg.removeSpan(span);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static void showProgressForAlertButton(Button button, boolean show){
|
||||
boolean shown=button.getTag(R.id.button_progress_orig_color)!=null;
|
||||
if(shown==show)
|
||||
return;
|
||||
button.setEnabled(!show);
|
||||
if(show){
|
||||
ColorStateList origColor=button.getTextColors();
|
||||
button.setTag(R.id.button_progress_orig_color, origColor);
|
||||
button.setTextColor(0);
|
||||
ProgressBar progressBar=(ProgressBar) LayoutInflater.from(button.getContext()).inflate(R.layout.progress_bar, null);
|
||||
Drawable progress=progressBar.getIndeterminateDrawable().mutate();
|
||||
progress.setTint(getThemeColor(button.getContext(), R.attr.colorM3OnSurface) & 0x60ffffff);
|
||||
if(progress instanceof Animatable a)
|
||||
a.start();
|
||||
LayerDrawable layerList=new LayerDrawable(new Drawable[]{progress});
|
||||
layerList.setLayerGravity(0, Gravity.CENTER);
|
||||
layerList.setLayerSize(0, V.dp(24), V.dp(24));
|
||||
layerList.setBounds(0, 0, button.getWidth(), button.getHeight());
|
||||
button.getOverlay().add(layerList);
|
||||
}else{
|
||||
button.getOverlay().clear();
|
||||
ColorStateList origColor=(ColorStateList) button.getTag(R.id.button_progress_orig_color);
|
||||
button.setTag(R.id.button_progress_orig_color, null);
|
||||
button.setTextColor(origColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,7 +278,7 @@ public class ComposeAutocompleteViewController{
|
||||
@Override
|
||||
public void onSuccess(SearchResults result){
|
||||
currentRequest=null;
|
||||
if(result.hashtags.isEmpty() || (result.hashtags.size()==1 && result.hashtags.get(0).name.equals(lastText.substring(1))))
|
||||
if(result.hashtags.isEmpty() || (result.hashtags.size()==1 && result.hashtags.get(0).name.equals(lastText.substring(1))) || mode!=Mode.HASHTAGS)
|
||||
return;
|
||||
List<Hashtag> oldList=hashtags;
|
||||
hashtags=result.hashtags;
|
||||
|
||||
@@ -28,7 +28,6 @@ import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -90,14 +89,14 @@ public class ComposeLanguageAlertViewController{
|
||||
|
||||
if(previouslySelected!=null){
|
||||
if(previouslySelected.index!=-1 && ((previouslySelected.index<specialLocales.size() && Objects.equals(previouslySelected.locale, specialLocales.get(previouslySelected.index).locale)) ||
|
||||
(previouslySelected.index<specialLocales.size()+allLocales.size() && Objects.equals(previouslySelected.locale, allLocales.get(previouslySelected.index-specialLocales.size()).locale)))){
|
||||
(previouslySelected.index<specialLocales.size()+allLocales.size() && previouslySelected.index>-1 && Objects.equals(previouslySelected.locale, allLocales.get(previouslySelected.index-specialLocales.size()).locale)))){
|
||||
selectedIndex=previouslySelected.index;
|
||||
selectedLocale=previouslySelected.locale;
|
||||
}else{
|
||||
int i=0;
|
||||
boolean found=false;
|
||||
for(SpecialLocaleInfo li:specialLocales){
|
||||
if(li.locale.equals(previouslySelected.locale)){
|
||||
if(null!=li.locale&&li.locale.equals(previouslySelected.locale)){
|
||||
selectedLocale=li.locale;
|
||||
selectedIndex=i;
|
||||
found=true;
|
||||
|
||||
@@ -44,6 +44,7 @@ import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
@@ -53,7 +54,7 @@ import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class AccountViewHolder extends BindableViewHolder<AccountViewModel> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable, UsableRecyclerView.LongClickable{
|
||||
private final TextView name, username, followers, verifiedLink, bio;
|
||||
private final ImageView avatar;
|
||||
public final ImageView avatar;
|
||||
private final ProgressBarButton button;
|
||||
private final PopupMenu contextMenu;
|
||||
private final View menuAnchor;
|
||||
@@ -75,7 +76,11 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
|
||||
private boolean checked;
|
||||
|
||||
public AccountViewHolder(Fragment fragment, ViewGroup list, HashMap<String, Relationship> relationships){
|
||||
super(fragment.getActivity(), R.layout.item_account_list, list);
|
||||
this(fragment, list, relationships, R.layout.item_account_list);
|
||||
}
|
||||
|
||||
public AccountViewHolder(Fragment fragment, ViewGroup list, HashMap<String, Relationship> relationships, @LayoutRes int layout){
|
||||
super(fragment.getActivity(), layout, list);
|
||||
this.fragment=fragment;
|
||||
this.accountID=Objects.requireNonNull(fragment.getArguments().getString("account"));
|
||||
this.relationships=relationships;
|
||||
@@ -111,24 +116,28 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
|
||||
public void onBind(AccountViewModel item){
|
||||
name.setText(item.parsedName);
|
||||
username.setText("@"+item.account.acct);
|
||||
String followersStr=fragment.getResources().getQuantityString(R.plurals.x_followers, item.account.followersCount>1000 ? 999 : (int)item.account.followersCount);
|
||||
String followersNum=UiUtils.abbreviateNumber(item.account.followersCount);
|
||||
int index=followersStr.indexOf("%,d");
|
||||
followersStr=followersStr.replace("%,d", followersNum);
|
||||
SpannableStringBuilder followersFormatted=new SpannableStringBuilder(followersStr);
|
||||
if(index!=-1){
|
||||
followersFormatted.setSpan(mediumSpan, index, index+followersNum.length(), 0);
|
||||
if(followers!=null){
|
||||
String followersStr=fragment.getResources().getQuantityString(R.plurals.x_followers, item.account.followersCount>1000 ? 999 : (int)item.account.followersCount);
|
||||
String followersNum=UiUtils.abbreviateNumber(item.account.followersCount);
|
||||
int index=followersStr.indexOf("%,d");
|
||||
followersStr=followersStr.replace("%,d", followersNum);
|
||||
SpannableStringBuilder followersFormatted=new SpannableStringBuilder(followersStr);
|
||||
if(index!=-1){
|
||||
followersFormatted.setSpan(mediumSpan, index, index+followersNum.length(), 0);
|
||||
}
|
||||
followers.setText(followersFormatted);
|
||||
}
|
||||
if(verifiedLink!=null){
|
||||
boolean hasVerifiedLink=item.verifiedLink!=null;
|
||||
if(!hasVerifiedLink)
|
||||
verifiedLink.setText(R.string.no_verified_link);
|
||||
else
|
||||
verifiedLink.setText(item.verifiedLink);
|
||||
verifiedLink.setCompoundDrawablesRelativeWithIntrinsicBounds(hasVerifiedLink ? R.drawable.ic_check_small_16px : R.drawable.ic_help_16px, 0, 0, 0);
|
||||
int tintColor=UiUtils.getThemeColor(fragment.getActivity(), hasVerifiedLink ? R.attr.colorM3Primary : R.attr.colorM3Secondary);
|
||||
verifiedLink.setTextColor(tintColor);
|
||||
verifiedLink.setCompoundDrawableTintList(ColorStateList.valueOf(tintColor));
|
||||
}
|
||||
followers.setText(followersFormatted);
|
||||
boolean hasVerifiedLink=item.verifiedLink!=null;
|
||||
if(!hasVerifiedLink)
|
||||
verifiedLink.setText(R.string.no_verified_link);
|
||||
else
|
||||
verifiedLink.setText(item.verifiedLink);
|
||||
verifiedLink.setCompoundDrawablesRelativeWithIntrinsicBounds(hasVerifiedLink ? R.drawable.ic_check_small_16px : R.drawable.ic_help_16px, 0, 0, 0);
|
||||
int tintColor=UiUtils.getThemeColor(fragment.getActivity(), hasVerifiedLink ? R.attr.colorM3Primary : R.attr.colorM3Secondary);
|
||||
verifiedLink.setTextColor(tintColor);
|
||||
verifiedLink.setCompoundDrawableTintList(ColorStateList.valueOf(tintColor));
|
||||
bindRelationship();
|
||||
if(showBio){
|
||||
bio.setText(item.parsedBio);
|
||||
@@ -243,10 +252,10 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
|
||||
}else if(id==R.id.open_in_browser){
|
||||
UiUtils.launchWebBrowser(fragment.getActivity(), account.url);
|
||||
}else if(id==R.id.block_domain){
|
||||
UiUtils.confirmToggleBlockDomain(fragment.getActivity(), accountID, account.getDomain(), relationship.domainBlocking, ()->{
|
||||
UiUtils.confirmToggleBlockDomain(fragment.getActivity(), accountID, account, relationship.domainBlocking, ()->{
|
||||
relationship.domainBlocking=!relationship.domainBlocking;
|
||||
bindRelationship();
|
||||
});
|
||||
}, this::updateRelationship);
|
||||
}else if(id==R.id.hide_boosts){
|
||||
new SetAccountFollowed(account.id, true, !relationship.showingReblogs)
|
||||
.setCallback(new Callback<>(){
|
||||
@@ -338,7 +347,7 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
|
||||
Menu menu=contextMenu.getMenu();
|
||||
Account account=item.account;
|
||||
|
||||
menu.findItem(R.id.share).setTitle(fragment.getString(R.string.share_user, account.getDisplayUsername()));
|
||||
menu.findItem(R.id.share).setTitle(R.string.share_user);
|
||||
menu.findItem(R.id.mute).setTitle(fragment.getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername()));
|
||||
menu.findItem(R.id.block).setTitle(fragment.getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername()));
|
||||
menu.findItem(R.id.report).setTitle(fragment.getString(R.string.report_user, account.getDisplayUsername()));
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
|
||||
public class FixedAspectRatioImageView extends ImageView{
|
||||
private float aspectRatio=1;
|
||||
private float aspectRatio;
|
||||
private boolean useHeight;
|
||||
|
||||
public FixedAspectRatioImageView(Context context){
|
||||
@@ -18,6 +21,10 @@ public class FixedAspectRatioImageView extends ImageView{
|
||||
|
||||
public FixedAspectRatioImageView(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.FixedAspectRatioImageView);
|
||||
aspectRatio=ta.getFloat(R.styleable.FixedAspectRatioImageView_aspectRatio, 1);
|
||||
useHeight=ta.getBoolean(R.styleable.FixedAspectRatioImageView_useHeight, false);
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,23 +1,42 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
|
||||
public class ProgressBarButton extends Button{
|
||||
private boolean textVisible=true;
|
||||
private ProgressBar progressBar;
|
||||
private int progressBarID;
|
||||
|
||||
public ProgressBarButton(Context context){
|
||||
super(context);
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public ProgressBarButton(Context context, AttributeSet attrs){
|
||||
super(context, attrs);
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public ProgressBarButton(Context context, AttributeSet attrs, int defStyleAttr){
|
||||
super(context, attrs, defStyleAttr);
|
||||
public ProgressBarButton(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.ProgressBarButton);
|
||||
progressBarID=ta.getResourceId(R.styleable.ProgressBarButton_progressBar, 0);
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow(){
|
||||
super.onAttachedToWindow();
|
||||
if(progressBarID!=0){
|
||||
progressBar=((ViewGroup)getParent()).findViewById(progressBarID);
|
||||
}
|
||||
}
|
||||
|
||||
public void setTextVisible(boolean textVisible){
|
||||
@@ -29,6 +48,19 @@ public class ProgressBarButton extends Button{
|
||||
return textVisible;
|
||||
}
|
||||
|
||||
public void setProgressBarVisible(boolean visible){
|
||||
if(progressBar==null)
|
||||
throw new IllegalStateException("progressBar is not set");
|
||||
if(visible){
|
||||
setTextVisible(false);
|
||||
progressBar.setIndeterminateTintList(getTextColors());
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
}else{
|
||||
setTextVisible(true);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas){
|
||||
if(textVisible){
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.text.Layout;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.dynamicanimation.animation.FloatValueHolder;
|
||||
import androidx.dynamicanimation.animation.SpringAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringForce;
|
||||
import me.grishka.appkit.utils.CustomViewHelper;
|
||||
|
||||
public class RippleAnimationTextView extends TextView implements CustomViewHelper{
|
||||
private final Paint animationPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private CharacterAnimationState[] charStates;
|
||||
private final ArgbEvaluator colorEvaluator=new ArgbEvaluator();
|
||||
private int runningAnimCount=0;
|
||||
private Runnable[] delayedAnimations1, delayedAnimations2;
|
||||
|
||||
public RippleAnimationTextView(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public RippleAnimationTextView(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public RippleAnimationTextView(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter){
|
||||
super.onTextChanged(text, start, lengthBefore, lengthAfter);
|
||||
if(charStates!=null){
|
||||
for(CharacterAnimationState state:charStates){
|
||||
state.colorAnimation.cancel();
|
||||
state.shadowAnimation.cancel();
|
||||
state.scaleAnimation.cancel();
|
||||
}
|
||||
for(Runnable r:delayedAnimations1){
|
||||
if(r!=null)
|
||||
removeCallbacks(r);
|
||||
}
|
||||
for(Runnable r:delayedAnimations2){
|
||||
if(r!=null)
|
||||
removeCallbacks(r);
|
||||
}
|
||||
}
|
||||
charStates=new CharacterAnimationState[lengthAfter];
|
||||
delayedAnimations1=new Runnable[lengthAfter];
|
||||
delayedAnimations2=new Runnable[lengthAfter];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas){
|
||||
if(runningAnimCount==0 && !areThereDelayedAnimations()){
|
||||
super.onDraw(canvas);
|
||||
return;
|
||||
}
|
||||
Layout layout=getLayout();
|
||||
animationPaint.set(getPaint());
|
||||
CharSequence text=layout.getText();
|
||||
for(int i=0;i<layout.getLineCount();i++){
|
||||
int baseline=layout.getLineBaseline(i);
|
||||
for(int offset=layout.getLineStart(i); offset<layout.getLineEnd(i); offset++){
|
||||
float x=layout.getPrimaryHorizontal(offset);
|
||||
CharacterAnimationState state=charStates[offset];
|
||||
if(state==null || state.scaleAnimation==null){
|
||||
animationPaint.setColor(getCurrentTextColor());
|
||||
animationPaint.clearShadowLayer();
|
||||
canvas.drawText(text, offset, offset+1, x, baseline, animationPaint);
|
||||
}else{
|
||||
animationPaint.setColor((int)colorEvaluator.evaluate(Math.max(0, Math.min(1, state.color.getValue())), getCurrentTextColor(), getLinkTextColors().getDefaultColor()));
|
||||
float scale=state.scale.getValue();
|
||||
int shadowAlpha=Math.round(255*Math.max(0, Math.min(1, state.shadowAlpha.getValue())));
|
||||
animationPaint.setShadowLayer(dp(4), 0, dp(3), (getPaint().linkColor & 0xFFFFFF) | (shadowAlpha << 24));
|
||||
canvas.save();
|
||||
canvas.scale(scale, scale, x, baseline);
|
||||
canvas.drawText(text, offset, offset+1, x, baseline, animationPaint);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void animate(int startIndex, int endIndex){
|
||||
for(int i=startIndex;i<endIndex;i++){
|
||||
CharacterAnimationState _state=charStates[i];
|
||||
if(_state==null){
|
||||
_state=charStates[i]=new CharacterAnimationState();
|
||||
}
|
||||
CharacterAnimationState state=_state;
|
||||
int finalI=i;
|
||||
postOnAnimationDelayed(()->{
|
||||
if(!state.colorAnimation.isRunning())
|
||||
runningAnimCount++;
|
||||
state.colorAnimation.animateToFinalPosition(1f);
|
||||
if(!state.shadowAnimation.isRunning())
|
||||
runningAnimCount++;
|
||||
state.shadowAnimation.animateToFinalPosition(0.3f);
|
||||
if(!state.scaleAnimation.isRunning())
|
||||
runningAnimCount++;
|
||||
state.scaleAnimation.animateToFinalPosition(1.2f);
|
||||
invalidate();
|
||||
|
||||
if(delayedAnimations1[finalI]!=null)
|
||||
removeCallbacks(delayedAnimations1[finalI]);
|
||||
if(delayedAnimations2[finalI]!=null)
|
||||
removeCallbacks(delayedAnimations2[finalI]);
|
||||
Runnable delay1=()->{
|
||||
if(!state.colorAnimation.isRunning())
|
||||
runningAnimCount++;
|
||||
state.colorAnimation.animateToFinalPosition(0f);
|
||||
if(!state.shadowAnimation.isRunning())
|
||||
runningAnimCount++;
|
||||
state.shadowAnimation.animateToFinalPosition(0f);
|
||||
invalidate();
|
||||
delayedAnimations1[finalI]=null;
|
||||
};
|
||||
Runnable delay2=()->{
|
||||
if(!state.scaleAnimation.isRunning())
|
||||
runningAnimCount++;
|
||||
state.scaleAnimation.animateToFinalPosition(1f);
|
||||
delayedAnimations2[finalI]=null;
|
||||
};
|
||||
delayedAnimations1[finalI]=delay1;
|
||||
delayedAnimations2[finalI]=delay2;
|
||||
postOnAnimationDelayed(delay1, 2000);
|
||||
postOnAnimationDelayed(delay2, 100);
|
||||
}, 20L*(i-startIndex));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean areThereDelayedAnimations(){
|
||||
for(Runnable r:delayedAnimations1){
|
||||
if(r!=null)
|
||||
return true;
|
||||
}
|
||||
for(Runnable r:delayedAnimations2){
|
||||
if(r!=null)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private class CharacterAnimationState extends FloatValueHolder{
|
||||
private final SpringAnimation scaleAnimation, colorAnimation, shadowAnimation;
|
||||
private final FloatValueHolder scale=new FloatValueHolder(1), color=new FloatValueHolder(), shadowAlpha=new FloatValueHolder();
|
||||
|
||||
public CharacterAnimationState(){
|
||||
scaleAnimation=new SpringAnimation(scale);
|
||||
colorAnimation=new SpringAnimation(color);
|
||||
shadowAnimation=new SpringAnimation(shadowAlpha);
|
||||
setupSpring(scaleAnimation);
|
||||
setupSpring(colorAnimation);
|
||||
setupSpring(shadowAnimation);
|
||||
}
|
||||
|
||||
private void setupSpring(SpringAnimation anim){
|
||||
anim.setMinimumVisibleChange(0.01f);
|
||||
anim.setSpring(new SpringForce().setStiffness(500f).setDampingRatio(0.175f));
|
||||
anim.addEndListener((animation, canceled, value, velocity)->runningAnimCount--);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<item android:bottom="-28dp">
|
||||
<shape android:tint="@color/m3_primary_alpha5" android:tintMode="src_over">
|
||||
<solid android:color="?colorM3Surface"/>
|
||||
<corners android:topLeftRadius="28dp" android:topRightRadius="28dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:tint="?colorM3Primary">
|
||||
<solid android:color="#0D000000"/>
|
||||
<corners android:topLeftRadius="28dp" android:topRightRadius="28dp"/>
|
||||
<corners android:radius="28dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
9
mastodon/src/main/res/drawable/bg_button_m3_elevated.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple android:color="@color/m3_primary_overlay" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:tint="@color/m3_primary_alpha5" android:tintMode="src_over">
|
||||
<solid android:color="?colorM3Surface"/>
|
||||
<corners android:radius="20dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -5,7 +5,7 @@
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="?colorM3Primary"/>
|
||||
<corners android:radius="20dp"/>
|
||||
<corners android:radius="100dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -13,7 +13,7 @@
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="?colorM3DisabledBackground"/>
|
||||
<corners android:radius="20dp"/>
|
||||
<corners android:radius="100dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/bg_button_m3_filled" android:inset="4dp">
|
||||
</inset>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple android:color="@color/m3_primary_overlay" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape>
|
||||
<solid android:color="#000"/>
|
||||
<corners android:radius="8dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -5,7 +5,7 @@
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="?colorM3SecondaryContainer"/>
|
||||
<corners android:radius="20dp"/>
|
||||
<corners android:radius="100dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -13,7 +13,7 @@
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="?colorM3DisabledBackground"/>
|
||||
<corners android:radius="20dp"/>
|
||||
<corners android:radius="100dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/bg_button_m3_tonal" android:inset="4dp">
|
||||
</inset>
|
||||
17
mastodon/src/main/res/drawable/bg_handle_help.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape
|
||||
android:tint="@color/m3_primary_alpha11"
|
||||
android:tintMode="src_over">
|
||||
<solid android:color="?colorM3Surface" />
|
||||
<corners android:radius="8dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<stroke android:width="2dp" android:color="?colorM3OutlineVariant" android:dashWidth="5dp" android:dashGap="5dp"/>
|
||||
<corners android:radius="8dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
33
mastodon/src/main/res/drawable/bg_m3_filled_text_field.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<selector>
|
||||
<item android:state_enabled="true">
|
||||
<shape>
|
||||
<corners android:topLeftRadius="4dp" android:topRightRadius="4dp"/>
|
||||
<solid android:color="?colorM3SurfaceVariant"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:tint="?colorM3OnSurface">
|
||||
<corners android:topLeftRadius="4dp" android:topRightRadius="4dp"/>
|
||||
<solid android:color="#0a000000"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
</item>
|
||||
<item android:left="-3dp" android:top="-3dp" android:right="-3dp">
|
||||
<selector>
|
||||
<item android:state_focused="true">
|
||||
<shape>
|
||||
<stroke android:color="?colorM3Primary" android:width="2dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<stroke android:color="?colorM3OnSurfaceVariant" android:width="1dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<corners android:topLeftRadius="4dp" android:topRightRadius="4dp" />
|
||||
<solid android:color="?colorM3SurfaceVariant" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:left="-3dp" android:right="-3dp" android:top="-3dp">
|
||||
<shape>
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="?colorM3Error" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
15
mastodon/src/main/res/drawable/fg_link_card.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?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 android:id="@android:id/mask">
|
||||
<shape>
|
||||
<corners android:radius="11dp"/>
|
||||
<solid android:color="#000"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<corners android:radius="11dp"/>
|
||||
<stroke android:color="?colorM3OutlineVariant" android:width="1dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
5
mastodon/src/main/res/drawable/fg_onboarding_ava.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<stroke android:width="1dp" android:color="?colorM3OutlineVariant"/>
|
||||
<corners android:radius="7dp"/>
|
||||
</shape>
|
||||
@@ -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,22Q9.95,22 8.125,21.212Q6.3,20.425 4.938,19.062Q3.575,17.7 2.788,15.875Q2,14.05 2,12Q2,9.925 2.788,8.113Q3.575,6.3 4.938,4.938Q6.3,3.575 8.125,2.787Q9.95,2 12,2Q14.075,2 15.887,2.787Q17.7,3.575 19.062,4.938Q20.425,6.3 21.212,8.113Q22,9.925 22,12V13.45Q22,14.925 20.988,15.962Q19.975,17 18.5,17Q17.6,17 16.825,16.6Q16.05,16.2 15.55,15.55Q14.875,16.225 13.963,16.613Q13.05,17 12,17Q9.925,17 8.463,15.537Q7,14.075 7,12Q7,9.925 8.463,8.462Q9.925,7 12,7Q14.075,7 15.538,8.462Q17,9.925 17,12V13.45Q17,14.175 17.45,14.587Q17.9,15 18.5,15Q19.1,15 19.55,14.587Q20,14.175 20,13.45V12Q20,8.725 17.637,6.362Q15.275,4 12,4Q8.725,4 6.362,6.362Q4,8.725 4,12Q4,15.275 6.362,17.637Q8.725,20 12,20H17V22ZM12,15Q13.25,15 14.125,14.125Q15,13.25 15,12Q15,10.75 14.125,9.875Q13.25,9 12,9Q10.75,9 9.875,9.875Q9,10.75 9,12Q9,13.25 9.875,14.125Q10.75,15 12,15Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_badge_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="M4,22Q3.175,22 2.588,21.413Q2,20.825 2,20V9Q2,8.175 2.588,7.587Q3.175,7 4,7H9V4Q9,3.175 9.588,2.587Q10.175,2 11,2H13Q13.825,2 14.413,2.587Q15,3.175 15,4V7H20Q20.825,7 21.413,7.587Q22,8.175 22,9V20Q22,20.825 21.413,21.413Q20.825,22 20,22ZM4,20H20Q20,20 20,20Q20,20 20,20V9Q20,9 20,9Q20,9 20,9H15Q15,9.825 14.413,10.412Q13.825,11 13,11H11Q10.175,11 9.588,10.412Q9,9.825 9,9H4Q4,9 4,9Q4,9 4,9V20Q4,20 4,20Q4,20 4,20ZM6,18H12V17.55Q12,17.125 11.762,16.762Q11.525,16.4 11.1,16.2Q10.6,15.975 10.088,15.863Q9.575,15.75 9,15.75Q8.425,15.75 7.913,15.863Q7.4,15.975 6.9,16.2Q6.475,16.4 6.238,16.762Q6,17.125 6,17.55ZM14,16.5H18V15H14ZM9,15Q9.625,15 10.062,14.562Q10.5,14.125 10.5,13.5Q10.5,12.875 10.062,12.438Q9.625,12 9,12Q8.375,12 7.938,12.438Q7.5,12.875 7.5,13.5Q7.5,14.125 7.938,14.562Q8.375,15 9,15ZM14,13.5H18V12H14ZM11,9H13V4H11ZM12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_block_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,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,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22ZM12,20Q15.35,20 17.675,17.675Q20,15.35 20,12Q20,10.65 19.562,9.4Q19.125,8.15 18.3,7.1L7.1,18.3Q8.15,19.125 9.4,19.562Q10.65,20 12,20ZM5.7,16.9 L16.9,5.7Q15.85,4.875 14.6,4.438Q13.35,4 12,4Q8.65,4 6.325,6.325Q4,8.65 4,12Q4,13.35 4.438,14.6Q4.875,15.85 5.7,16.9Z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/ic_bookmark_fill1_24px" android:state_selected="true"/>
|
||||
<item android:drawable="@drawable/ic_bookmark_24px"/>
|
||||
</selector>
|
||||
@@ -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="M5,21V5Q5,4.175 5.588,3.587Q6.175,3 7,3H17Q17.825,3 18.413,3.587Q19,4.175 19,5V21L12,18Z"/>
|
||||
</vector>
|
||||
@@ -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="M4,20Q3.175,20 2.588,19.413Q2,18.825 2,18V14Q2.825,14 3.413,13.412Q4,12.825 4,12Q4,11.175 3.413,10.587Q2.825,10 2,10V6Q2,5.175 2.588,4.588Q3.175,4 4,4H20Q20.825,4 21.413,4.588Q22,5.175 22,6V10Q21.175,10 20.587,10.587Q20,11.175 20,12Q20,12.825 20.587,13.412Q21.175,14 22,14V18Q22,18.825 21.413,19.413Q20.825,20 20,20ZM4,18H20V15.45Q19.075,14.9 18.538,13.988Q18,13.075 18,12Q18,10.925 18.538,10.012Q19.075,9.1 20,8.55V6H4V8.55Q4.925,9.1 5.463,10.012Q6,10.925 6,12Q6,13.075 5.463,13.988Q4.925,14.9 4,15.45ZM12,17Q12.425,17 12.713,16.712Q13,16.425 13,16Q13,15.575 12.713,15.287Q12.425,15 12,15Q11.575,15 11.288,15.287Q11,15.575 11,16Q11,16.425 11.288,16.712Q11.575,17 12,17ZM12,13Q12.425,13 12.713,12.712Q13,12.425 13,12Q13,11.575 12.713,11.287Q12.425,11 12,11Q11.575,11 11.288,11.287Q11,11.575 11,12Q11,12.425 11.288,12.712Q11.575,13 12,13ZM12,9Q12.425,9 12.713,8.712Q13,8.425 13,8Q13,7.575 12.713,7.287Q12.425,7 12,7Q11.575,7 11.288,7.287Q11,7.575 11,8Q11,8.425 11.288,8.712Q11.575,9 12,9ZM12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Z"/>
|
||||
</vector>
|
||||
@@ -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="M22,19.15 L20,17.15V9H11.85L10,7.15V5H7.85L5.85,3H12V7H22ZM20.5,23.3 L18.15,21H2V4.8L0.7,3.5L2.1,2.1L21.9,21.9ZM4,19H6V17H4ZM4,15H6V13H4ZM4,11H6V9H4ZM8,19H10V17H8ZM8,15H10V13H8ZM12,19H16.15L14.15,17H12ZM18,13H16V11H18Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_feed_48px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M9,42Q7.8,42 6.9,41.1Q6,40.2 6,39V9Q6,7.8 6.9,6.9Q7.8,6 9,6H32.1L42,15.9V39Q42,40.2 41.1,41.1Q40.2,42 39,42ZM9,39H39Q39,39 39,39Q39,39 39,39V17.55H30.45V9H9Q9,9 9,9Q9,9 9,9V39Q9,39 9,39Q9,39 9,39ZM13.95,33.45H34.05V30.45H13.95ZM13.95,17.55H24V14.55H13.95ZM13.95,25.5H34.05V22.5H13.95ZM9,9V17.55V9V17.55V39Q9,39 9,39Q9,39 9,39Q9,39 9,39Q9,39 9,39V9Q9,9 9,9Q9,9 9,9Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_info_fill1_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="M11,17H13V11H11ZM12,9Q12.425,9 12.713,8.712Q13,8.425 13,8Q13,7.575 12.713,7.287Q12.425,7 12,7Q11.575,7 11.288,7.287Q11,7.575 11,8Q11,8.425 11.288,8.712Q11.575,9 12,9ZM12,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,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_public_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,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,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22ZM11,19.95V18Q10.175,18 9.588,17.413Q9,16.825 9,16V15L4.2,10.2Q4.125,10.65 4.062,11.1Q4,11.55 4,12Q4,15.025 5.988,17.3Q7.975,19.575 11,19.95ZM17.9,17.4Q18.925,16.275 19.462,14.887Q20,13.5 20,12Q20,9.55 18.638,7.525Q17.275,5.5 15,4.6V5Q15,5.825 14.413,6.412Q13.825,7 13,7H11V9Q11,9.425 10.713,9.712Q10.425,10 10,10H8V12H14Q14.425,12 14.713,12.287Q15,12.575 15,13V16H16Q16.65,16 17.175,16.387Q17.7,16.775 17.9,17.4Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_reply_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="M19,19V15Q19,13.75 18.125,12.875Q17.25,12 16,12H6.8L10.4,15.6L9,17L3,11L9,5L10.4,6.4L6.8,10H16Q18.075,10 19.538,11.462Q21,12.925 21,15V19Z"/>
|
||||
</vector>
|
||||
5
mastodon/src/main/res/drawable/ic_star_24px_selector.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/ic_star_fill1_24px" android:state_selected="true"/>
|
||||
<item android:drawable="@drawable/ic_star_24px"/>
|
||||
</selector>
|
||||
9
mastodon/src/main/res/drawable/ic_volume_off_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="M19.8,22.6 L16.775,19.575Q16.15,19.975 15.45,20.263Q14.75,20.55 14,20.725V18.675Q14.35,18.55 14.688,18.425Q15.025,18.3 15.325,18.125L12,14.8V20L7,15H3V9H6.2L1.4,4.2L2.8,2.8L21.2,21.2ZM19.6,16.8 L18.15,15.35Q18.575,14.575 18.788,13.725Q19,12.875 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,13.3 20.638,14.525Q20.275,15.75 19.6,16.8ZM9.1,11.9ZM16.25,13.45 L14,11.2V7.95Q15.175,8.5 15.838,9.6Q16.5,10.7 16.5,12Q16.5,12.375 16.438,12.738Q16.375,13.1 16.25,13.45ZM12,9.2 L9.4,6.6 12,4ZM10,15.15V12.8L8.2,11H5V13H7.85Z"/>
|
||||
</vector>
|
||||
88
mastodon/src/main/res/layout/alert_invite_link.xml
Normal file
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:paddingTop="24dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/m3_headline_small"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:drawableTop="@drawable/ic_confirmation_number_24px"
|
||||
android:drawableTint="?colorM3Secondary"
|
||||
android:drawablePadding="16dp"
|
||||
android:text="@string/enter_invite_link"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
tools:text="@string/need_invite_to_join_server"/>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="76dp"
|
||||
android:layout_marginTop="16dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@drawable/bg_m3_filled_text_field"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:textColorHint="?colorM3OnSurfaceVariant"
|
||||
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"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_gravity="top"
|
||||
android:paddingEnd="23dp"
|
||||
android:textAppearance="@style/m3_body_small"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:text="@string/server_url"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/supporting_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:textAppearance="@style/m3_body_small"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:text=""/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/clear"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_gravity="end|top"
|
||||
android:src="@drawable/ic_m3_cancel"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:contentDescription="@string/clear"/>
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -3,54 +3,75 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<org.joinmastodon.android.ui.views.MaxWidthFrameLayout
|
||||
<LinearLayout
|
||||
android:id="@+id/inner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="250dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:foreground="?android:selectableItemBackground"
|
||||
android:orientation="vertical"
|
||||
android:foreground="@drawable/fg_link_card"
|
||||
android:padding="1dp"
|
||||
android:maxWidth="400dp">
|
||||
<ImageView
|
||||
<org.joinmastodon.android.ui.views.FixedAspectRatioImageView
|
||||
android:id="@+id/photo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:scaleType="centerCrop"
|
||||
android:importantForAccessibility="no"
|
||||
app:aspectRatio="1.7777777778"
|
||||
tools:src="#0f0"/>
|
||||
<LinearLayout
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:padding="8dp"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/window_bg_alpha95">
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
tools:text="Link title"/>
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
tools:text="Link description"/>
|
||||
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"/>
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
tools:text="Link description"/>
|
||||
<org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
<TextView
|
||||
android:id="@+id/domain"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
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"/>
|
||||
</LinearLayout>
|
||||
</org.joinmastodon.android.ui.views.MaxWidthFrameLayout>
|
||||
<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>
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/inner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="128dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:foreground="@drawable/fg_link_card"
|
||||
android:padding="1dp"
|
||||
android:maxWidth="400dp">
|
||||
<ImageView
|
||||
android:id="@+id/photo"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:layout_alignParentEnd="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_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">
|
||||
<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>
|
||||
</RelativeLayout>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -125,25 +125,15 @@
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/bg_onboarding_panel">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textAppearance="@style/m3_body_small"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:text="@string/signup_random_server_explain"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_random_instance"
|
||||
android:id="@+id/btn_use_invite"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Text"
|
||||
android:text="@string/pick_server_for_me"/>
|
||||
android:text="@string/use_invite_link"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_next"
|
||||
|
||||
@@ -37,16 +37,16 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:background="@drawable/bg_onboarding_panel">
|
||||
android:background="@drawable/bg_m3_surface1">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_next"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Tonal"
|
||||
style="@style/Widget.Mastodon.M3.Button.Elevated"
|
||||
android:text="@string/follow_all"/>
|
||||
|
||||
<Button
|
||||
@@ -55,10 +55,10 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||
android:text="@string/skip"/>
|
||||
android:text="@string/next"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
android:layout_below="@id/cover"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="-44dp"
|
||||
android:layout_marginTop="-36dp"
|
||||
android:background="@drawable/profile_ava_bg"
|
||||
android:outlineProvider="@null">
|
||||
|
||||
@@ -76,64 +76,63 @@
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/profile_action_btn_wrap"
|
||||
android:layout_width="wrap_content"
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/cover"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:paddingStart="120dp">
|
||||
android:layout_toEndOf="@id/avatar_border"
|
||||
android:layout_marginTop="14dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:fontFamily="sans-serif"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/m3_title_large"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
tools:text="Eugen" />
|
||||
|
||||
<org.joinmastodon.android.ui.views.ProgressBarButton
|
||||
android:id="@+id/profile_action_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||
android:minWidth="156dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
tools:text="@string/save_changes" />
|
||||
<org.joinmastodon.android.ui.views.WrappingLinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/name"
|
||||
android:layout_toEndOf="@id/avatar_border"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:horizontalGap="4dp"
|
||||
android:verticalGap="0dp">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/action_progress"
|
||||
style="?android:progressBarStyleSmall"
|
||||
<TextView
|
||||
android:id="@+id/username"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:elevation="10dp"
|
||||
android:indeterminate="true"
|
||||
android:outlineProvider="none"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
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="Gargron" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/username_domain"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:textAppearance="@style/m3_label_small"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingHorizontal="4dp"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:background="@drawable/rect_4dp"
|
||||
android:backgroundTint="?colorM3SurfaceVariant"
|
||||
tools:text="mastodon.social"/>
|
||||
|
||||
</org.joinmastodon.android.ui.views.WrappingLinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:fontFamily="sans-serif"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/m3_title_large"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
tools:text="Eugen" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
tools:text="\@Gargron" />
|
||||
|
||||
<org.joinmastodon.android.ui.views.LinkedTextView
|
||||
android:id="@+id/bio"
|
||||
android:layout_width="match_parent"
|
||||
@@ -192,18 +191,11 @@
|
||||
tools:text="followers" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="28dp"
|
||||
android:gravity="center"
|
||||
android:text="·"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
android:textColor="?colorM3OnSurfaceVariant" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/following_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="28dp"
|
||||
android:layout_marginStart="-4dp"
|
||||
android:background="@drawable/bg_button_borderless_rounded"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="horizontal"
|
||||
@@ -289,6 +281,41 @@
|
||||
android:padding="16dp"
|
||||
tools:text="Founder, CEO and lead developer @Mastodon, Germany." />
|
||||
</org.joinmastodon.android.ui.views.FloatingHintEditTextLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/profile_action_btn_wrap"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<org.joinmastodon.android.ui.views.ProgressBarButton
|
||||
android:id="@+id/profile_action_btn"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||
android:minWidth="156dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
tools:text="@string/save_changes" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/action_progress"
|
||||
style="?android:progressBarStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:elevation="10dp"
|
||||
android:indeterminate="true"
|
||||
android:outlineProvider="none"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<org.joinmastodon.android.ui.tabs.TabLayout
|
||||
android:id="@+id/tabbar"
|
||||
|
||||
123
mastodon/src/main/res/layout/item_account_list_onboarding.xml
Normal file
@@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.joinmastodon.android.ui.views.CheckableRelativeLayout 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:paddingHorizontal="12dp"
|
||||
android:paddingVertical="8dp"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="38dp"
|
||||
android:layout_height="38dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:foreground="@drawable/fg_onboarding_ava"
|
||||
android:padding="1dp"
|
||||
tools:src="#0f0"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_toStartOf="@id/accessory"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
tools:text="User"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_below="@id/name"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_toStartOf="@id/accessory"
|
||||
android:layout_marginTop="-2dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical|start"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?colorM3Secondary"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="\@user@server"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/accessory"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="38dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginStart="8dp"
|
||||
android:duplicateParentState="true">
|
||||
|
||||
<org.joinmastodon.android.ui.views.ProgressBarButton
|
||||
android:id="@+id/button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:minWidth="96dp"
|
||||
android:maxWidth="150dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||
tools:text="Follow back"/>
|
||||
<ProgressBar
|
||||
android:id="@+id/action_progress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
style="?android:progressBarStyleSmall"
|
||||
android:elevation="10dp"
|
||||
android:outlineProvider="none"
|
||||
android:indeterminateTint="?colorM3OnPrimary"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/checkbox"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginHorizontal="4dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:duplicateParentState="true"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/options_btn"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_gravity="top"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:tint="?colorM3OnSurfaceVariant"
|
||||
android:contentDescription="@string/more_options"
|
||||
android:src="@drawable/ic_more_vert_24px"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bio"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_below="@id/username"
|
||||
android:layout_marginTop="2dp"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:maxLines="2"
|
||||
android:paddingVertical="2dp"
|
||||
tools:text="bla bla bla bla bla bla"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/menu_anchor"
|
||||
android:layout_width="1px"
|
||||
android:layout_height="1px"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_marginLeft="-16dp"/>
|
||||
|
||||
</org.joinmastodon.android.ui.views.CheckableRelativeLayout>
|
||||
@@ -1,63 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/alt_text_wrapper"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|bottom"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:importantForAccessibility="noHideDescendants"
|
||||
android:background="@drawable/bg_image_alt_overlay">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/alt_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="24dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
android:textColor="#FFF"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:text="ALT"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/alt_text_close"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="end|top"
|
||||
android:src="@drawable/ic_baseline_close_24"
|
||||
android:tint="#FFF"
|
||||
android:background="?android:selectableItemBackgroundBorderless"/>
|
||||
|
||||
<org.joinmastodon.android.ui.views.NestableScrollView
|
||||
android:id="@+id/alt_text_scroller"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="40dp"
|
||||
android:requiresFadingEdge="vertical"
|
||||
android:fadingEdgeLength="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/alt_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="#FFF"
|
||||
tools:text="Alt text goes here"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</org.joinmastodon.android.ui.views.NestableScrollView>
|
||||
|
||||
</FrameLayout>
|
||||
6
mastodon/src/main/res/layout/progress_bar.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
</ProgressBar>
|
||||