Compare commits
239 Commits
revert-804
...
upstream/f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdff407514 | ||
|
|
422c3c3809 | ||
|
|
1988849b26 | ||
|
|
fc10fbffb0 | ||
|
|
a21a74a8e7 | ||
|
|
20e5d2a545 | ||
|
|
7da363fb87 | ||
|
|
c46f78272d | ||
|
|
95685d4de8 | ||
|
|
cbee0fe72e | ||
|
|
6d085ae6f0 | ||
|
|
4de7211523 | ||
|
|
05f7a44bd5 | ||
|
|
1079f600bc | ||
|
|
72580dadd0 | ||
|
|
98a02e874b | ||
|
|
d219d7aa4b | ||
|
|
68a9fe8376 | ||
|
|
f2f8620312 | ||
|
|
4ee229ea79 | ||
|
|
c261214e49 | ||
|
|
b06df8c3d0 | ||
|
|
a00afd5d7f | ||
|
|
9a41a2d6fb | ||
|
|
2cd98a6620 | ||
|
|
283b56be5b | ||
|
|
6d56771aba | ||
|
|
1724d8a532 | ||
|
|
52030b3b2d | ||
|
|
b4cdf35d36 | ||
|
|
cad0ad7a59 | ||
|
|
ca60003c39 | ||
|
|
0f030e0bac | ||
|
|
6d4f212a18 | ||
|
|
183b39bc24 | ||
|
|
27ad0c6fcf | ||
|
|
b5f661f1af | ||
|
|
0015f3f0bf | ||
|
|
c5d0fdd645 | ||
|
|
2d09ad44fb | ||
|
|
667fffd124 | ||
|
|
699233d8c7 | ||
|
|
56aabdc4a6 | ||
|
|
443e2c7a6f | ||
|
|
985b0f6e63 | ||
|
|
cc86edf276 | ||
|
|
4071b9342d | ||
|
|
f71d1bc5d3 | ||
|
|
2b926ffa46 | ||
|
|
6bcdbaba34 | ||
|
|
a2beead3a5 | ||
|
|
e7a25e353d | ||
|
|
af04a01130 | ||
|
|
fe1cfa1d7b | ||
|
|
b248797bb0 | ||
|
|
f24eba08d3 | ||
|
|
0e89559a47 | ||
|
|
858657799f | ||
|
|
d02a72e079 | ||
|
|
3be57d1b0b | ||
|
|
bed550e97c | ||
|
|
7e2619ea75 | ||
|
|
4b22f1d3a7 | ||
|
|
9dcc7e293f | ||
|
|
6a68cf5e41 | ||
|
|
29297be4a3 | ||
|
|
90b87529e0 | ||
|
|
39af05524d | ||
|
|
e3fb2cd03c | ||
|
|
90f84d628a | ||
|
|
b89e0b5c5a | ||
|
|
f724644d84 | ||
|
|
aac89c354c | ||
|
|
a032f9af10 | ||
|
|
642aaec6da | ||
|
|
ff667d6aed | ||
|
|
5e98496ea6 | ||
|
|
972fe1d15b | ||
|
|
26eaa36faa | ||
|
|
c517f41595 | ||
|
|
56a6d7243f | ||
|
|
18e43dfc22 | ||
|
|
816f6370ef | ||
|
|
30866a5292 | ||
|
|
3e1403d18a | ||
|
|
ebc2b2e59d | ||
|
|
c9a796dbfe | ||
|
|
1ba185ea9c | ||
|
|
1a50c3ff5f | ||
|
|
35a85c3247 | ||
|
|
6a729fa97f | ||
|
|
923639a329 | ||
|
|
a78be8bc1d | ||
|
|
abfb497577 | ||
|
|
a10b184508 | ||
|
|
f0ea6660e6 | ||
|
|
a829f25d56 | ||
|
|
deff3dd8e0 | ||
|
|
dab596f527 | ||
|
|
0c18ab2319 | ||
|
|
6c5fb5ea09 | ||
|
|
afe0c9e0db | ||
|
|
1f2213042f | ||
|
|
5edd2466f9 | ||
|
|
f3b3a1a577 | ||
|
|
068619b815 | ||
|
|
f121e94979 | ||
|
|
b5b52529d4 | ||
|
|
876bf73454 | ||
|
|
522dbf6e4a | ||
|
|
ae685095ba | ||
|
|
30d5fe2f12 | ||
|
|
2bf27c561c | ||
|
|
bbdc72323d | ||
|
|
6e335930f3 | ||
|
|
9b309939da | ||
|
|
faf2e5115d | ||
|
|
dc5d9412c8 | ||
|
|
fc0680d66f | ||
|
|
56c9a5433f | ||
|
|
60e473ee55 | ||
|
|
ae34ecd5c3 | ||
|
|
fd1caa8729 | ||
|
|
1182e5c60c | ||
|
|
d99d515dfa | ||
|
|
70a15e7d9c | ||
|
|
1691382369 | ||
|
|
b7da9c6d51 | ||
|
|
3426538dca | ||
|
|
63de2b200b | ||
|
|
ff1ee766dc | ||
|
|
f033411adf | ||
|
|
a738eaf8c0 | ||
|
|
5074aadd6e | ||
|
|
0854961470 | ||
|
|
227b077935 | ||
|
|
1e4358290a | ||
|
|
925169eb31 | ||
|
|
e1abeb9252 | ||
|
|
cbe0add211 | ||
|
|
299b524d62 | ||
|
|
31c094e696 | ||
|
|
a8038a2863 | ||
|
|
29933bb916 | ||
|
|
5ec0c078d8 | ||
|
|
e6287f1ff2 | ||
|
|
be9caf8905 | ||
|
|
f375142084 | ||
|
|
fd3668d520 | ||
|
|
d5e03e9d9e | ||
|
|
d62f094919 | ||
|
|
6d84f28600 | ||
|
|
209e603f2c | ||
|
|
1b4dc01c74 | ||
|
|
6aab8f6578 | ||
|
|
645af12c3f | ||
|
|
fadc42d72b | ||
|
|
fc831e7d42 | ||
|
|
2998ee9145 | ||
|
|
971c4e5879 | ||
|
|
48c53ee88b | ||
|
|
acf1fa15da | ||
|
|
1c3b28f9d7 | ||
|
|
b396ee7987 | ||
|
|
90856a414a | ||
|
|
ea19925be6 | ||
|
|
03b3775843 | ||
|
|
38b39751ae | ||
|
|
54a4b0fe41 | ||
|
|
3bf591c944 | ||
|
|
584a6bbfa3 | ||
|
|
0f803cd4fa | ||
|
|
167a14b8db | ||
|
|
81cbc2d10c | ||
|
|
9bd8aff99b | ||
|
|
a770828165 | ||
|
|
ab457035ff | ||
|
|
f886e4c1d2 | ||
|
|
380e4ff77e | ||
|
|
58f0c07357 | ||
|
|
77dee59b9c | ||
|
|
464dc93d99 | ||
|
|
dcdfd3e5d3 | ||
|
|
646f83ff0a | ||
|
|
fdf0414698 | ||
|
|
cc699a3f5e | ||
|
|
12eaa8d5f1 | ||
|
|
70680e39c6 | ||
|
|
8bd8f90d58 | ||
|
|
54b53a266e | ||
|
|
66921e3b5a | ||
|
|
9d7af3964b | ||
|
|
ec73687e9b | ||
|
|
c8af800b88 | ||
|
|
e74ac5da56 | ||
|
|
efa003a9a5 | ||
|
|
de5165434d | ||
|
|
be648cc5ab | ||
|
|
90f1f464dc | ||
|
|
734aa52816 | ||
|
|
068d42175e | ||
|
|
2314871246 | ||
|
|
e4f13c900b | ||
|
|
4ddfa483d4 | ||
|
|
20f41ce7c9 | ||
|
|
c8df9e085e | ||
|
|
0238aa4375 | ||
|
|
79d7873790 | ||
|
|
996842489d | ||
|
|
23c2c2b5e7 | ||
|
|
9dd694ce2e | ||
|
|
f51f2a1197 | ||
|
|
3020cab243 | ||
|
|
be73c9e81c | ||
|
|
1c2183bf1a | ||
|
|
1789d90dc3 | ||
|
|
57306ff7fe | ||
|
|
2aba90f353 | ||
|
|
5065c7e7e2 | ||
|
|
a10e661b21 | ||
|
|
4975bde76f | ||
|
|
ebbd56e3bc | ||
|
|
454ec6b4c0 | ||
|
|
10a8b195b1 | ||
|
|
6f273df060 | ||
|
|
7cfade62d3 | ||
|
|
0a338ad607 | ||
|
|
0cb3e1863e | ||
|
|
209081f1f0 | ||
|
|
f0eb6573f4 | ||
|
|
e7f5dd3357 | ||
|
|
8101bb9ea1 | ||
|
|
54d48253d5 | ||
|
|
3373b2bb04 | ||
|
|
a7e23aa228 | ||
|
|
228fdc8ffe | ||
|
|
2d24e50ff2 | ||
|
|
e9df125cde | ||
|
|
d76e823489 |
@@ -15,8 +15,8 @@ android {
|
|||||||
applicationId "org.joinmastodon.android.sk"
|
applicationId "org.joinmastodon.android.sk"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 98
|
versionCode 100
|
||||||
versionName "2.0.3+fork.98"
|
versionName "2.1.4+fork.100"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
resourceConfigurations += ['ar-rSA', 'ar-rDZ', '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']
|
resourceConfigurations += ['ar-rSA', 'ar-rDZ', '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']
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ import java.util.HashSet;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
|
||||||
public class GlobalUserPreferences{
|
public class GlobalUserPreferences{
|
||||||
private static final String TAG="GlobalUserPreferences";
|
private static final String TAG="GlobalUserPreferences";
|
||||||
|
|
||||||
@@ -60,6 +62,7 @@ public class GlobalUserPreferences{
|
|||||||
public static boolean showNavigationLabels;
|
public static boolean showNavigationLabels;
|
||||||
public static boolean displayPronounsInTimelines, displayPronounsInThreads, displayPronounsInUserListings;
|
public static boolean displayPronounsInTimelines, displayPronounsInThreads, displayPronounsInUserListings;
|
||||||
public static boolean overlayMedia;
|
public static boolean overlayMedia;
|
||||||
|
public static boolean showSuicideHelp;
|
||||||
|
|
||||||
private static SharedPreferences getPrefs(){
|
private static SharedPreferences getPrefs(){
|
||||||
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
||||||
@@ -119,6 +122,7 @@ public class GlobalUserPreferences{
|
|||||||
displayPronounsInThreads=prefs.getBoolean("displayPronounsInThreads", true);
|
displayPronounsInThreads=prefs.getBoolean("displayPronounsInThreads", true);
|
||||||
displayPronounsInUserListings=prefs.getBoolean("displayPronounsInUserListings", true);
|
displayPronounsInUserListings=prefs.getBoolean("displayPronounsInUserListings", true);
|
||||||
overlayMedia=prefs.getBoolean("overlayMedia", false);
|
overlayMedia=prefs.getBoolean("overlayMedia", false);
|
||||||
|
showSuicideHelp=prefs.getBoolean("showSuicideHelp", true);
|
||||||
|
|
||||||
if (prefs.contains("prefixRepliesWithRe")) {
|
if (prefs.contains("prefixRepliesWithRe")) {
|
||||||
prefixReplies = prefs.getBoolean("prefixRepliesWithRe", false)
|
prefixReplies = prefs.getBoolean("prefixRepliesWithRe", false)
|
||||||
@@ -177,6 +181,7 @@ public class GlobalUserPreferences{
|
|||||||
.putBoolean("displayPronounsInThreads", displayPronounsInThreads)
|
.putBoolean("displayPronounsInThreads", displayPronounsInThreads)
|
||||||
.putBoolean("displayPronounsInUserListings", displayPronounsInUserListings)
|
.putBoolean("displayPronounsInUserListings", displayPronounsInUserListings)
|
||||||
.putBoolean("overlayMedia", overlayMedia)
|
.putBoolean("overlayMedia", overlayMedia)
|
||||||
|
.putBoolean("showSuicideHelp", showSuicideHelp)
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,50 +43,7 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
if(savedInstanceState==null){
|
if(savedInstanceState==null){
|
||||||
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
|
restartHomeFragment();
|
||||||
showFragmentClearingBackStack(new CustomWelcomeFragment());
|
|
||||||
}else{
|
|
||||||
AccountSession session;
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
Intent intent=getIntent();
|
|
||||||
if(intent.hasExtra("fromExternalShare")) {
|
|
||||||
AccountSessionManager.getInstance()
|
|
||||||
.setLastActiveAccountID(intent.getStringExtra("account"));
|
|
||||||
AccountSessionManager.getInstance().maybeUpdateLocalInfo(
|
|
||||||
AccountSessionManager.getInstance().getLastActiveAccount());
|
|
||||||
showFragmentForExternalShare(intent.getExtras());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
|
|
||||||
boolean hasNotification = intent.hasExtra("notification");
|
|
||||||
if(fromNotification){
|
|
||||||
String accountID=intent.getStringExtra("accountID");
|
|
||||||
try{
|
|
||||||
session=AccountSessionManager.getInstance().getAccount(accountID);
|
|
||||||
if(!hasNotification) args.putString("tab", "notifications");
|
|
||||||
}catch(IllegalStateException x){
|
|
||||||
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
|
||||||
}
|
|
||||||
AccountSessionManager.getInstance().maybeUpdateLocalInfo(session);
|
|
||||||
args.putString("account", session.getID());
|
|
||||||
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
|
|
||||||
fragment.setArguments(args);
|
|
||||||
if(fromNotification && hasNotification){
|
|
||||||
Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
|
|
||||||
showFragmentForNotification(notification, session.getID());
|
|
||||||
} else if (intent.getBooleanExtra("compose", false)){
|
|
||||||
showCompose();
|
|
||||||
} else if (Intent.ACTION_VIEW.equals(intent.getAction())){
|
|
||||||
handleURL(intent.getData(), null);
|
|
||||||
} else {
|
|
||||||
showFragmentClearingBackStack(fragment);
|
|
||||||
maybeRequestNotificationsPermission();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(GithubSelfUpdater.needSelfUpdating()){
|
if(GithubSelfUpdater.needSelfUpdating()){
|
||||||
@@ -143,7 +100,7 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void openSearchQuery(String q, String accountID, int progressText, boolean fromSearch){
|
public void openSearchQuery(String q, String accountID, int progressText, boolean fromSearch){
|
||||||
new GetSearchResults(q, null, true)
|
new GetSearchResults(q, null, true, null, 0, 0)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(SearchResults result){
|
public void onSuccess(SearchResults result){
|
||||||
@@ -259,4 +216,51 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
|
|||||||
Fragment fragment = getCurrentFragment();
|
Fragment fragment = getCurrentFragment();
|
||||||
if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent);
|
if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void restartHomeFragment(){
|
||||||
|
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
|
||||||
|
showFragmentClearingBackStack(new CustomWelcomeFragment());
|
||||||
|
}else{
|
||||||
|
AccountSession session;
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
Intent intent=getIntent();
|
||||||
|
if(intent.hasExtra("fromExternalShare")) {
|
||||||
|
AccountSessionManager.getInstance()
|
||||||
|
.setLastActiveAccountID(intent.getStringExtra("account"));
|
||||||
|
AccountSessionManager.getInstance().maybeUpdateLocalInfo(
|
||||||
|
AccountSessionManager.getInstance().getLastActiveAccount());
|
||||||
|
showFragmentForExternalShare(intent.getExtras());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
|
||||||
|
boolean hasNotification = intent.hasExtra("notification");
|
||||||
|
if(fromNotification){
|
||||||
|
String accountID=intent.getStringExtra("accountID");
|
||||||
|
try{
|
||||||
|
session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
|
if(!hasNotification) args.putString("tab", "notifications");
|
||||||
|
}catch(IllegalStateException x){
|
||||||
|
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
||||||
|
}
|
||||||
|
AccountSessionManager.getInstance().maybeUpdateLocalInfo(session);
|
||||||
|
args.putString("account", session.getID());
|
||||||
|
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
|
||||||
|
fragment.setArguments(args);
|
||||||
|
if(fromNotification && hasNotification){
|
||||||
|
Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
|
||||||
|
showFragmentForNotification(notification, session.getID());
|
||||||
|
} else if (intent.getBooleanExtra("compose", false)){
|
||||||
|
showCompose();
|
||||||
|
} else if (Intent.ACTION_VIEW.equals(intent.getAction())){
|
||||||
|
handleURL(intent.getData(), null);
|
||||||
|
} else {
|
||||||
|
showFragmentClearingBackStack(fragment);
|
||||||
|
maybeRequestNotificationsPermission();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,6 +176,8 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
List<NotificationChannel> channels=Arrays.stream(PushNotification.Type.values())
|
List<NotificationChannel> channels=Arrays.stream(PushNotification.Type.values())
|
||||||
.map(type->{
|
.map(type->{
|
||||||
NotificationChannel channel=new NotificationChannel(accountID+"_"+type, context.getString(type.localizedName), NotificationManager.IMPORTANCE_DEFAULT);
|
NotificationChannel channel=new NotificationChannel(accountID+"_"+type, context.getString(type.localizedName), NotificationManager.IMPORTANCE_DEFAULT);
|
||||||
|
channel.setLightColor(context.getColor(R.color.primary_700));
|
||||||
|
channel.enableLights(true);
|
||||||
channel.setGroup(accountID);
|
channel.setGroup(accountID);
|
||||||
return channel;
|
return channel;
|
||||||
})
|
})
|
||||||
@@ -205,6 +207,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
.setShowWhen(true)
|
.setShowWhen(true)
|
||||||
.setCategory(Notification.CATEGORY_SOCIAL)
|
.setCategory(Notification.CATEGORY_SOCIAL)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
|
.setLights(UiUtils.getThemeColor(context, android.R.attr.colorAccent), 500, 1000)
|
||||||
.setColor(UiUtils.getThemeColor(context, android.R.attr.colorAccent));
|
.setColor(UiUtils.getThemeColor(context, android.R.attr.colorAccent));
|
||||||
|
|
||||||
if (!GlobalUserPreferences.uniformNotificationIcon) {
|
if (!GlobalUserPreferences.uniformNotificationIcon) {
|
||||||
@@ -321,8 +324,8 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
|
|
||||||
CreateStatus.Request req=new CreateStatus.Request();
|
CreateStatus.Request req=new CreateStatus.Request();
|
||||||
req.status = initialText + input.toString();
|
req.status = initialText + input.toString();
|
||||||
req.language = preferences.postingDefaultLanguage;
|
req.language = notification.status.language;
|
||||||
req.visibility = preferences.postingDefaultVisibility;
|
req.visibility = notification.status.visibility;
|
||||||
req.inReplyToId = notification.status.id;
|
req.inReplyToId = notification.status.id;
|
||||||
|
|
||||||
if (notification.status.hasSpoiler() &&
|
if (notification.status.hasSpoiler() &&
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ public class PushSubscriptionManager{
|
|||||||
deviceToken=getPrefs().getString("deviceToken", null);
|
deviceToken=getPrefs().getString("deviceToken", null);
|
||||||
int tokenVersion=getPrefs().getInt("version", 0);
|
int tokenVersion=getPrefs().getInt("version", 0);
|
||||||
if(!TextUtils.isEmpty(deviceToken) && tokenVersion==BuildConfig.VERSION_CODE){
|
if(!TextUtils.isEmpty(deviceToken) && tokenVersion==BuildConfig.VERSION_CODE){
|
||||||
registerAllAccountsForPush(false);
|
registerAllAccountsForPush(true); // TODO: revert this before release
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.i(TAG, "tryRegisterFCM: no token found or app was updated. Trying to get push token...");
|
Log.i(TAG, "tryRegisterFCM: no token found or app was updated. Trying to get push token...");
|
||||||
@@ -130,12 +130,11 @@ public class PushSubscriptionManager{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String endpoint = "https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/"+accountID;
|
String endpoint = "https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/";
|
||||||
registerAccountForPush(subscription, endpoint);
|
registerAccountForPush(subscription, endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerAccountForPush(PushSubscription subscription, String endpoint){
|
public void registerAccountForPush(PushSubscription subscription, String endpoint){
|
||||||
|
|
||||||
MastodonAPIController.runInBackground(()->{
|
MastodonAPIController.runInBackground(()->{
|
||||||
Log.d(TAG, "registerAccountForPush: started for "+accountID);
|
Log.d(TAG, "registerAccountForPush: started for "+accountID);
|
||||||
String encodedPublicKey, encodedAuthKey, pushAccountID;
|
String encodedPublicKey, encodedAuthKey, pushAccountID;
|
||||||
@@ -164,7 +163,13 @@ public class PushSubscriptionManager{
|
|||||||
Log.e(TAG, "registerAccountForPush: error generating encryption key", e);
|
Log.e(TAG, "registerAccountForPush: error generating encryption key", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
new RegisterForPushNotifications(endpoint,
|
|
||||||
|
//work-around for adding the randomAccountId
|
||||||
|
String newEndpoint = endpoint;
|
||||||
|
if (endpoint.startsWith("https://app.joinmastodon.org/relay-to/fcm/"))
|
||||||
|
newEndpoint += pushAccountID;
|
||||||
|
|
||||||
|
new RegisterForPushNotifications(newEndpoint,
|
||||||
encodedPublicKey,
|
encodedPublicKey,
|
||||||
encodedAuthKey,
|
encodedAuthKey,
|
||||||
subscription==null ? PushSubscription.Alerts.ofAll() : subscription.alerts,
|
subscription==null ? PushSubscription.Alerts.ofAll() : subscription.alerts,
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.accounts;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
|
|
||||||
|
public class GetAccountBlocks extends HeaderPaginationRequest<Account>{
|
||||||
|
public GetAccountBlocks(String maxID, int limit){
|
||||||
|
super(HttpMethod.GET, "/blocks", new TypeToken<>(){});
|
||||||
|
if(maxID!=null)
|
||||||
|
addQueryParameter("max_id", maxID);
|
||||||
|
if(limit>0)
|
||||||
|
addQueryParameter("limit", limit+"");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.accounts;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
|
|
||||||
|
public class GetAccountMutes extends HeaderPaginationRequest<Account>{
|
||||||
|
public GetAccountMutes(String maxID, int limit){
|
||||||
|
super(HttpMethod.GET, "/mutes/", new TypeToken<>(){});
|
||||||
|
if(maxID!=null)
|
||||||
|
addQueryParameter("max_id", maxID);
|
||||||
|
if(limit>0)
|
||||||
|
addQueryParameter("limit", limit+"");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,18 +6,19 @@ import org.joinmastodon.android.model.Preferences;
|
|||||||
import org.joinmastodon.android.model.StatusPrivacy;
|
import org.joinmastodon.android.model.StatusPrivacy;
|
||||||
|
|
||||||
public class UpdateAccountCredentialsPreferences extends MastodonAPIRequest<Account>{
|
public class UpdateAccountCredentialsPreferences extends MastodonAPIRequest<Account>{
|
||||||
public UpdateAccountCredentialsPreferences(Preferences preferences, Boolean locked, Boolean discoverable){
|
public UpdateAccountCredentialsPreferences(Preferences preferences, Boolean locked, Boolean discoverable, Boolean indexable){
|
||||||
super(HttpMethod.PATCH, "/accounts/update_credentials", Account.class);
|
super(HttpMethod.PATCH, "/accounts/update_credentials", Account.class);
|
||||||
setRequestBody(new Request(locked, discoverable, new RequestSource(preferences.postingDefaultVisibility, preferences.postingDefaultLanguage)));
|
setRequestBody(new Request(locked, discoverable, indexable, new RequestSource(preferences.postingDefaultVisibility, preferences.postingDefaultLanguage)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Request{
|
private static class Request{
|
||||||
public Boolean locked, discoverable;
|
public Boolean locked, discoverable, indexable;
|
||||||
public RequestSource source;
|
public RequestSource source;
|
||||||
|
|
||||||
public Request(Boolean locked, Boolean discoverable, RequestSource source){
|
public Request(Boolean locked, Boolean discoverable, Boolean indexable, RequestSource source){
|
||||||
this.locked=locked;
|
this.locked=locked;
|
||||||
this.discoverable=discoverable;
|
this.discoverable=discoverable;
|
||||||
|
this.indexable=indexable;
|
||||||
this.source=source;
|
this.source=source;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import org.joinmastodon.android.model.FilterContext;
|
|||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import androidx.annotation.Keep;
|
||||||
|
|
||||||
|
@Keep
|
||||||
class FilterRequest{
|
class FilterRequest{
|
||||||
public String title;
|
public String title;
|
||||||
public EnumSet<FilterContext> context;
|
public EnumSet<FilterContext> context;
|
||||||
|
|||||||
@@ -4,13 +4,19 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
|
|||||||
import org.joinmastodon.android.model.SearchResults;
|
import org.joinmastodon.android.model.SearchResults;
|
||||||
|
|
||||||
public class GetSearchResults extends MastodonAPIRequest<SearchResults>{
|
public class GetSearchResults extends MastodonAPIRequest<SearchResults>{
|
||||||
public GetSearchResults(String query, Type type, boolean resolve){
|
public GetSearchResults(String query, Type type, boolean resolve, String maxID, int offset, int count){
|
||||||
super(HttpMethod.GET, "/search", SearchResults.class);
|
super(HttpMethod.GET, "/search", SearchResults.class);
|
||||||
addQueryParameter("q", query);
|
addQueryParameter("q", query);
|
||||||
if(type!=null)
|
if(type!=null)
|
||||||
addQueryParameter("type", type.name().toLowerCase());
|
addQueryParameter("type", type.name().toLowerCase());
|
||||||
if(resolve)
|
if(resolve)
|
||||||
addQueryParameter("resolve", "true");
|
addQueryParameter("resolve", "true");
|
||||||
|
if(maxID!=null)
|
||||||
|
addQueryParameter("max_id", maxID);
|
||||||
|
if(offset>0)
|
||||||
|
addQueryParameter("offset", String.valueOf(offset));
|
||||||
|
if(count>0)
|
||||||
|
addQueryParameter("limit", String.valueOf(count));
|
||||||
}
|
}
|
||||||
|
|
||||||
public GetSearchResults limit(int limit){
|
public GetSearchResults limit(int limit){
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package org.joinmastodon.android.api.requests.statuses;
|
package org.joinmastodon.android.api.requests.statuses;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.model.TranslatedStatus;
|
import org.joinmastodon.android.model.Translation;
|
||||||
|
|
||||||
public class TranslateStatus extends MastodonAPIRequest<TranslatedStatus> {
|
import java.util.Map;
|
||||||
public TranslateStatus(String id) {
|
|
||||||
super(HttpMethod.POST, "/statuses/"+id+"/translate", TranslatedStatus.class);
|
public class TranslateStatus extends MastodonAPIRequest<Translation>{
|
||||||
setRequestBody(new Object());
|
public TranslateStatus(String id, String lang){
|
||||||
|
super(HttpMethod.POST, "/statuses/"+id+"/translate", Translation.class);
|
||||||
|
setRequestBody(Map.of("lang", lang));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.tags;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
|
|
||||||
|
public class GetTag extends MastodonAPIRequest<Hashtag>{
|
||||||
|
public GetTag(String tag){
|
||||||
|
super(HttpMethod.GET, "/tags/"+tag, Hashtag.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.tags;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
|
|
||||||
|
public class SetTagFollowed extends MastodonAPIRequest<Hashtag>{
|
||||||
|
public SetTagFollowed(String tag, boolean followed){
|
||||||
|
super(HttpMethod.POST, "/tags/"+tag+(followed ? "/follow" : "/unfollow"), Hashtag.class);
|
||||||
|
setRequestBody(new Object());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -219,7 +219,7 @@ public class AccountSession{
|
|||||||
|
|
||||||
public void savePreferencesIfPending(){
|
public void savePreferencesIfPending(){
|
||||||
if(preferencesNeedSaving){
|
if(preferencesNeedSaving){
|
||||||
new UpdateAccountCredentialsPreferences(preferences, null, null)
|
new UpdateAccountCredentialsPreferences(preferences, null, self.discoverable, self.source.indexable)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Account result){
|
public void onSuccess(Account result){
|
||||||
@@ -303,6 +303,10 @@ public class AccountSession{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateAccountInfo(){
|
||||||
|
AccountSessionManager.getInstance().updateSessionLocalInfo(this);
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<Instance> getInstance() {
|
public Optional<Instance> getInstance() {
|
||||||
return Optional.ofNullable(AccountSessionManager.getInstance().getInstanceInfo(domain));
|
return Optional.ofNullable(AccountSessionManager.getInstance().getInstanceInfo(domain));
|
||||||
}
|
}
|
||||||
@@ -313,4 +317,10 @@ public class AccountSession{
|
|||||||
.authority(getInstance().map(i -> i.normalizedUri).orElse(domain))
|
.authority(getInstance().map(i -> i.normalizedUri).orElse(domain))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDefaultAvatarUrl() {
|
||||||
|
return getInstance()
|
||||||
|
.map(instance->"https://"+domain+(instance.isAkkoma() ? "/images/avi.png" : "/avatars/original/missing.png"))
|
||||||
|
.orElse("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -303,8 +303,7 @@ public class AccountSessionManager{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*package*/ void updateSessionLocalInfo(AccountSession session){
|
||||||
private void updateSessionLocalInfo(AccountSession session){
|
|
||||||
new GetOwnAccount()
|
new GetOwnAccount()
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import org.joinmastodon.android.GlobalUserPreferences;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||||
import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
|
import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
|
||||||
|
import org.joinmastodon.android.api.requests.statuses.TranslateStatus;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
@@ -29,7 +30,9 @@ import org.joinmastodon.android.model.DisplayItemsParent;
|
|||||||
import org.joinmastodon.android.model.Poll;
|
import org.joinmastodon.android.model.Poll;
|
||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.model.Translation;
|
||||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||||
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||||
@@ -55,6 +58,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -617,7 +621,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onGapClick(GapStatusDisplayItem.Holder item){}
|
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){}
|
||||||
|
|
||||||
public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){
|
public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){
|
||||||
int startPos = warning.getAbsoluteAdapterPosition();
|
int startPos = warning.getAbsoluteAdapterPosition();
|
||||||
@@ -772,6 +776,61 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
|
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void togglePostTranslation(Status status, String itemID){
|
||||||
|
switch(status.translationState){
|
||||||
|
case LOADING -> {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case SHOWN -> {
|
||||||
|
status.translationState=Status.TranslationState.HIDDEN;
|
||||||
|
}
|
||||||
|
case HIDDEN -> {
|
||||||
|
if(status.translation!=null){
|
||||||
|
status.translationState=Status.TranslationState.SHOWN;
|
||||||
|
}else{
|
||||||
|
status.translationState=Status.TranslationState.LOADING;
|
||||||
|
new TranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage())
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Translation result){
|
||||||
|
if(getActivity()==null)
|
||||||
|
return;
|
||||||
|
status.translation=result;
|
||||||
|
status.translationState=Status.TranslationState.SHOWN;
|
||||||
|
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
|
||||||
|
if(text!=null){
|
||||||
|
text.updateTranslation(true);
|
||||||
|
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
if(getActivity()==null)
|
||||||
|
return;
|
||||||
|
status.translationState=Status.TranslationState.HIDDEN;
|
||||||
|
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
|
||||||
|
if(text!=null){
|
||||||
|
text.updateTranslation(true);
|
||||||
|
}
|
||||||
|
new M3AlertDialogBuilder(getActivity())
|
||||||
|
.setTitle(R.string.error)
|
||||||
|
.setMessage(R.string.translation_failed)
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
|
||||||
|
if(text!=null){
|
||||||
|
text.updateTranslation(true);
|
||||||
|
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void rebuildAllDisplayItems(){
|
public void rebuildAllDisplayItems(){
|
||||||
displayItems.clear();
|
displayItems.clear();
|
||||||
for(T item:data){
|
for(T item:data){
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import android.text.TextWatcher;
|
|||||||
import android.text.format.DateFormat;
|
import android.text.format.DateFormat;
|
||||||
import android.text.style.BackgroundColorSpan;
|
import android.text.style.BackgroundColorSpan;
|
||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@@ -826,6 +827,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
publishButton = wrap.findViewById(R.id.publish_btn);
|
publishButton = wrap.findViewById(R.id.publish_btn);
|
||||||
languageButton = wrap.findViewById(R.id.language_btn);
|
languageButton = wrap.findViewById(R.id.language_btn);
|
||||||
languageButton.setOnClickListener(v->showLanguageAlert());
|
languageButton.setOnClickListener(v->showLanguageAlert());
|
||||||
|
languageButton.setOnLongClickListener(v->{
|
||||||
|
languageButton.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||||
|
if(!getLocalPrefs().bottomEncoding){
|
||||||
|
getLocalPrefs().bottomEncoding=true;
|
||||||
|
getLocalPrefs().save();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
publishButton.setOnClickListener(v -> {
|
publishButton.setOnClickListener(v -> {
|
||||||
if(GlobalUserPreferences.altTextReminders && editingStatus==null)
|
if(GlobalUserPreferences.altTextReminders && editingStatus==null)
|
||||||
@@ -1308,6 +1317,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
boolean usePhotoPicker=photoPicker && UiUtils.isPhotoPickerAvailable();
|
boolean usePhotoPicker=photoPicker && UiUtils.isPhotoPickerAvailable();
|
||||||
if(usePhotoPicker){
|
if(usePhotoPicker){
|
||||||
intent=new Intent(MediaStore.ACTION_PICK_IMAGES);
|
intent=new Intent(MediaStore.ACTION_PICK_IMAGES);
|
||||||
|
if(mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount()>1)
|
||||||
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount());
|
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount());
|
||||||
}else{
|
}else{
|
||||||
intent=new Intent(Intent.ACTION_GET_CONTENT);
|
intent=new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
|
|||||||
@@ -239,16 +239,21 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
|
|||||||
|
|
||||||
private boolean setTagListContent(NachoTextView editText, @Nullable List<String> tags) {
|
private boolean setTagListContent(NachoTextView editText, @Nullable List<String> tags) {
|
||||||
if (tags == null || tags.isEmpty()) return false;
|
if (tags == null || tags.isEmpty()) return false;
|
||||||
editText.setText(String.join(",", tags));
|
editText.setText(tags);
|
||||||
editText.chipifyAllUnterminatedTokens();
|
editText.chipifyAllUnterminatedTokens();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private NachoTextView prepareChipTextView(NachoTextView nacho) {
|
private NachoTextView prepareChipTextView(NachoTextView nacho) {
|
||||||
nacho.addChipTerminator(',', BEHAVIOR_CHIPIFY_ALL);
|
//I’ll Be Back
|
||||||
nacho.addChipTerminator('\n', BEHAVIOR_CHIPIFY_ALL);
|
nacho.setChipTerminators(
|
||||||
nacho.addChipTerminator(' ', BEHAVIOR_CHIPIFY_ALL);
|
Map.of(
|
||||||
nacho.addChipTerminator(';', BEHAVIOR_CHIPIFY_ALL);
|
',', BEHAVIOR_CHIPIFY_ALL,
|
||||||
|
'\n', BEHAVIOR_CHIPIFY_ALL,
|
||||||
|
' ', BEHAVIOR_CHIPIFY_ALL,
|
||||||
|
';', BEHAVIOR_CHIPIFY_ALL
|
||||||
|
)
|
||||||
|
);
|
||||||
nacho.enableEditChipOnTouch(true, true);
|
nacho.enableEditChipOnTouch(true, true);
|
||||||
nacho.setOnFocusChangeListener((v, hasFocus) -> nacho.chipifyAllUnterminatedTokens());
|
nacho.setOnFocusChangeListener((v, hasFocus) -> nacho.chipifyAllUnterminatedTokens());
|
||||||
return nacho;
|
return nacho;
|
||||||
@@ -344,7 +349,7 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
|
|||||||
mainHashtag = name;
|
mainHashtag = name;
|
||||||
name = null;
|
name = null;
|
||||||
}
|
}
|
||||||
if (TextUtils.isEmpty(mainHashtag)) {
|
if (TextUtils.isEmpty(mainHashtag) && (item != null && item.getType() == TimelineDefinition.TimelineType.HASHTAG)) {
|
||||||
Toast.makeText(ctx, R.string.sk_add_timeline_tag_error_empty, Toast.LENGTH_SHORT).show();
|
Toast.makeText(ctx, R.string.sk_add_timeline_tag_error_empty, Toast.LENGTH_SHORT).show();
|
||||||
onSave.accept(null);
|
onSave.accept(null);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ public class FeaturedHashtagsListFragment extends BaseStatusListFragment<Hashtag
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(String hashtag){
|
public void onItemClick(String id){
|
||||||
UiUtils.openHashtagTimeline(getActivity(), accountID, hashtag, data.stream().filter(h -> Objects.equals(h.name, hashtag)).findAny().map(h -> h.following).orElse(null));
|
UiUtils.openHashtagTimeline(getActivity(), accountID, Objects.requireNonNull(findItemOfType(id, HashtagStatusDisplayItem.class)).tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -17,8 +17,10 @@ import android.widget.TextView;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
|
import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
import org.joinmastodon.android.ui.OutlineProviders;
|
import org.joinmastodon.android.ui.OutlineProviders;
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
@@ -357,8 +359,9 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
|
|||||||
|
|
||||||
public AccountWrapper(Account account){
|
public AccountWrapper(Account account){
|
||||||
this.account=account;
|
this.account=account;
|
||||||
if(!TextUtils.isEmpty(account.avatar))
|
avaRequest=new UrlImageLoaderRequest(
|
||||||
avaRequest=new UrlImageLoaderRequest(account.avatar, V.dp(50), V.dp(50));
|
TextUtils.isEmpty(account.avatar) ? AccountSessionManager.get(getAccountID()).getDefaultAvatarUrl() : account.avatar,
|
||||||
|
V.dp(50), V.dp(50));
|
||||||
if(!TextUtils.isEmpty(account.header))
|
if(!TextUtils.isEmpty(account.header))
|
||||||
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
|
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
|
||||||
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ public class FollowedHashtagsFragment extends MastodonRecyclerFragment<Hashtag>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick() {
|
public void onClick() {
|
||||||
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name, item.following);
|
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,64 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
import android.view.HapticFeedbackConstants;
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.tags.GetHashtag;
|
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||||
import org.joinmastodon.android.api.requests.tags.SetHashtagFollowed;
|
import org.joinmastodon.android.api.requests.tags.GetTag;
|
||||||
|
import org.joinmastodon.android.api.requests.tags.SetTagFollowed;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
|
||||||
import org.joinmastodon.android.events.HashtagUpdatedEvent;
|
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.TimelineDefinition;
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
|
import org.joinmastodon.android.ui.text.SpacerSpan;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||||
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
|
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class HashtagTimelineFragment extends PinnableStatusListFragment{
|
public class HashtagTimelineFragment extends PinnableStatusListFragment{
|
||||||
private String hashtag;
|
private Hashtag hashtag;
|
||||||
|
private String hashtagName;
|
||||||
|
private TextView headerTitle, headerSubtitle;
|
||||||
|
private ProgressBarButton followButton;
|
||||||
|
private ProgressBar followProgress;
|
||||||
|
private MenuItem followMenuItem, pinMenuItem;
|
||||||
|
private boolean followRequestRunning;
|
||||||
|
private boolean toolbarContentVisible;
|
||||||
|
|
||||||
private List<String> any;
|
private List<String> any;
|
||||||
private List<String> all;
|
private List<String> all;
|
||||||
private List<String> none;
|
private List<String> none;
|
||||||
private boolean following;
|
private boolean following;
|
||||||
private boolean localOnly;
|
private boolean localOnly;
|
||||||
private MenuItem followButton;
|
private Menu optionsMenu;
|
||||||
|
private MenuInflater optionsMenuInflater;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean wantsComposeButton() {
|
protected boolean wantsComposeButton() {
|
||||||
@@ -50,75 +68,21 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity){
|
public void onAttach(Activity activity){
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
updateTitle(getArguments().getString("hashtag"));
|
|
||||||
following=getArguments().getBoolean("following", false);
|
following=getArguments().getBoolean("following", false);
|
||||||
localOnly=getArguments().getBoolean("localOnly", false);
|
localOnly=getArguments().getBoolean("localOnly", false);
|
||||||
any=getArguments().getStringArrayList("any");
|
any=getArguments().getStringArrayList("any");
|
||||||
all=getArguments().getStringArrayList("all");
|
all=getArguments().getStringArrayList("all");
|
||||||
none=getArguments().getStringArrayList("none");
|
none=getArguments().getStringArrayList("none");
|
||||||
|
if(getArguments().containsKey("hashtag")){
|
||||||
|
hashtag=Parcels.unwrap(getArguments().getParcelable("hashtag"));
|
||||||
|
hashtagName=hashtag.name;
|
||||||
|
}else{
|
||||||
|
hashtagName=getArguments().getString("hashtagName");
|
||||||
|
}
|
||||||
|
setTitle('#'+hashtagName);
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTitle(String hashtagName) {
|
|
||||||
hashtag = hashtagName;
|
|
||||||
setTitle('#'+hashtag);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateFollowingState(boolean newFollowing) {
|
|
||||||
this.following = newFollowing;
|
|
||||||
followButton.setTitle(getString(newFollowing ? R.string.unfollow_user : R.string.follow_user, "#" + hashtag));
|
|
||||||
followButton.setIcon(newFollowing ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
|
|
||||||
E.post(new HashtagUpdatedEvent(hashtag, following));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
|
||||||
inflater.inflate(R.menu.hashtag_timeline, menu);
|
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
|
||||||
followButton = menu.findItem(R.id.follow_hashtag);
|
|
||||||
updateFollowingState(following);
|
|
||||||
|
|
||||||
new GetHashtag(hashtag).setCallback(new Callback<>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Hashtag hashtag) {
|
|
||||||
if (getActivity() == null) return;
|
|
||||||
updateTitle(hashtag.name);
|
|
||||||
updateFollowingState(hashtag.following);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error) {
|
|
||||||
error.showToast(getActivity());
|
|
||||||
}
|
|
||||||
}).exec(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
if (super.onOptionsItemSelected(item)) return true;
|
|
||||||
if (item.getItemId() == R.id.follow_hashtag) {
|
|
||||||
updateFollowingState(!following);
|
|
||||||
getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
|
|
||||||
new SetHashtagFollowed(hashtag, following).setCallback(new Callback<>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Hashtag i) {
|
|
||||||
if (getActivity() == null) return;
|
|
||||||
if (i.following == following) Toast.makeText(getActivity(), getString(i.following ? R.string.followed_user : R.string.unfollowed_user, "#" + i.name), Toast.LENGTH_SHORT).show();
|
|
||||||
updateFollowingState(i.following);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error) {
|
|
||||||
error.showToast(getActivity());
|
|
||||||
updateFollowingState(!following);
|
|
||||||
}
|
|
||||||
}).exec(accountID);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected TimelineDefinition makeTimelineDefinition() {
|
protected TimelineDefinition makeTimelineDefinition() {
|
||||||
return TimelineDefinition.ofHashtag(hashtag);
|
return TimelineDefinition.ofHashtag(hashtag);
|
||||||
@@ -126,12 +90,10 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
currentRequest=new GetHashtagTimeline(hashtag, offset==0 ? null : getMaxID(), null, count, any, all, none, localOnly, getLocalPrefs().timelineReplyVisibility)
|
currentRequest=new GetHashtagTimeline(hashtagName, offset==0 ? null : getMaxID(), null, count, any, all, none, localOnly, getLocalPrefs().timelineReplyVisibility)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
if (getActivity() == null) return;
|
|
||||||
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
|
|
||||||
onDataLoaded(result, !result.isEmpty());
|
onDataLoaded(result, !result.isEmpty());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -146,15 +108,40 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onFabLongClick(View v) {
|
public void loadData(){
|
||||||
return UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtag+' ');
|
reloadTag();
|
||||||
|
super.loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
fab=view.findViewById(R.id.fab);
|
||||||
|
fab.setOnClickListener(this::onFabClick);
|
||||||
|
|
||||||
|
if(getParentFragment() instanceof HomeTabFragment) return;
|
||||||
|
|
||||||
|
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
||||||
|
@Override
|
||||||
|
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
|
||||||
|
View topChild=recyclerView.getChildAt(0);
|
||||||
|
int firstChildPos=recyclerView.getChildAdapterPosition(topChild);
|
||||||
|
float newAlpha=firstChildPos>0 ? 1f : Math.min(1f, -topChild.getTop()/(float)headerTitle.getHeight());
|
||||||
|
toolbarTitleView.setAlpha(newAlpha);
|
||||||
|
boolean newToolbarVisibility=newAlpha>0.5f;
|
||||||
|
if(newToolbarVisibility!=toolbarContentVisible){
|
||||||
|
toolbarContentVisible=newToolbarVisibility;
|
||||||
|
createOptionsMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFabClick(View v){
|
public void onFabClick(View v){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putString("prefilledText", '#'+hashtag+' ');
|
args.putString("prefilledText", '#'+hashtagName+' ');
|
||||||
Nav.go(getActivity(), ComposeFragment.class, args);
|
Nav.go(getActivity(), ComposeFragment.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +157,204 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Uri getWebUri(Uri.Builder base) {
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
return base.path((isInstanceAkkoma() ? "/tag/" : "/tags") + hashtag).build();
|
return base.path((isInstanceAkkoma() ? "/tag/" : "/tags/") + hashtag).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RecyclerView.Adapter getAdapter(){
|
||||||
|
View header=getActivity().getLayoutInflater().inflate(R.layout.header_hashtag_timeline, list, false);
|
||||||
|
headerTitle=header.findViewById(R.id.title);
|
||||||
|
headerSubtitle=header.findViewById(R.id.subtitle);
|
||||||
|
followButton=header.findViewById(R.id.profile_action_btn);
|
||||||
|
followProgress=header.findViewById(R.id.action_progress);
|
||||||
|
|
||||||
|
headerTitle.setText("#"+hashtagName);
|
||||||
|
followButton.setVisibility(View.GONE);
|
||||||
|
followButton.setOnClickListener(v->{
|
||||||
|
if(hashtag==null)
|
||||||
|
return;
|
||||||
|
setFollowed(!hashtag.following);
|
||||||
|
});
|
||||||
|
followButton.setOnLongClickListener(v->{
|
||||||
|
if(hashtag==null) return false;
|
||||||
|
UiUtils.pickAccount(getActivity(), accountID, R.string.sk_follow_as, R.drawable.ic_fluent_person_add_28_regular, session -> {
|
||||||
|
new SetTagFollowed(hashtagName, true).setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Hashtag hashtag) {
|
||||||
|
Toast.makeText(
|
||||||
|
getActivity(),
|
||||||
|
getString(R.string.sk_followed_as, session.self.getShortUsername()),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
error.showToast(getActivity());
|
||||||
|
}
|
||||||
|
}).exec(session.getID());
|
||||||
|
}, null);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
updateHeader();
|
||||||
|
|
||||||
|
MergeRecyclerAdapter mergeAdapter=new MergeRecyclerAdapter();
|
||||||
|
if(!(getParentFragment() instanceof HomeTabFragment)){
|
||||||
|
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(header));
|
||||||
|
}
|
||||||
|
mergeAdapter.addAdapter(super.getAdapter());
|
||||||
|
return mergeAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getMainAdapterOffset(){
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createOptionsMenu(){
|
||||||
|
optionsMenu.clear();
|
||||||
|
optionsMenuInflater.inflate(R.menu.hashtag_timeline, optionsMenu);
|
||||||
|
followMenuItem=optionsMenu.findItem(R.id.follow_hashtag);
|
||||||
|
pinMenuItem=optionsMenu.findItem(R.id.pin);
|
||||||
|
followMenuItem.setVisible(toolbarContentVisible);
|
||||||
|
followMenuItem.setTitle(getString(hashtag.following ? R.string.unfollow_user : R.string.follow_user, "#"+hashtagName));
|
||||||
|
followMenuItem.setIcon(hashtag.following ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
|
||||||
|
pinMenuItem.setShowAsAction(toolbarContentVisible ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
|
super.updatePinButton(pinMenuItem);
|
||||||
|
if(toolbarContentVisible){
|
||||||
|
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu);
|
||||||
|
}else{
|
||||||
|
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu, R.id.pin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updatePinButton(MenuItem pin){
|
||||||
|
super.updatePinButton(pin);
|
||||||
|
if(toolbarContentVisible) UiUtils.insetPopupMenuIcon(getContext(), pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
|
inflater.inflate(R.menu.hashtag_timeline, menu);
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
optionsMenu=menu;
|
||||||
|
optionsMenuInflater=inflater;
|
||||||
|
createOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item){
|
||||||
|
if (super.onOptionsItemSelected(item)) return true;
|
||||||
|
if (item.getItemId() == R.id.follow_hashtag && hashtag!=null) {
|
||||||
|
setFollowed(!hashtag.following);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onUpdateToolbar(){
|
||||||
|
super.onUpdateToolbar();
|
||||||
|
toolbarTitleView.setAlpha(toolbarContentVisible ? 1f : 0f);
|
||||||
|
createOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateHeader(){
|
||||||
|
if(hashtag==null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(hashtag.history!=null && !hashtag.history.isEmpty()){
|
||||||
|
int weekPosts=hashtag.history.stream().mapToInt(h->h.uses).sum();
|
||||||
|
int todayPosts=hashtag.history.get(0).uses;
|
||||||
|
int numAccounts=hashtag.history.stream().mapToInt(h->h.accounts).sum();
|
||||||
|
int hSpace=V.dp(8);
|
||||||
|
SpannableStringBuilder ssb=new SpannableStringBuilder();
|
||||||
|
ssb.append(getResources().getQuantityString(R.plurals.x_posts, weekPosts, weekPosts));
|
||||||
|
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
|
||||||
|
ssb.append('·');
|
||||||
|
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
|
||||||
|
ssb.append(getResources().getQuantityString(R.plurals.x_participants, numAccounts, numAccounts));
|
||||||
|
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
|
||||||
|
ssb.append('·');
|
||||||
|
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
|
||||||
|
ssb.append(getResources().getQuantityString(R.plurals.x_posts_today, todayPosts, todayPosts));
|
||||||
|
headerSubtitle.setText(ssb);
|
||||||
|
}
|
||||||
|
|
||||||
|
int styleRes;
|
||||||
|
followButton.setVisibility(View.VISIBLE);
|
||||||
|
if(hashtag.following){
|
||||||
|
followButton.setText(R.string.button_following);
|
||||||
|
styleRes=R.style.Widget_Mastodon_M3_Button_Tonal;
|
||||||
|
}else{
|
||||||
|
followButton.setText(R.string.button_follow);
|
||||||
|
styleRes=R.style.Widget_Mastodon_M3_Button_Filled;
|
||||||
|
}
|
||||||
|
TypedArray ta=followButton.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background});
|
||||||
|
followButton.setBackground(ta.getDrawable(0));
|
||||||
|
ta.recycle();
|
||||||
|
ta=followButton.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.textColor});
|
||||||
|
followButton.setTextColor(ta.getColorStateList(0));
|
||||||
|
followProgress.setIndeterminateTintList(ta.getColorStateList(0));
|
||||||
|
ta.recycle();
|
||||||
|
|
||||||
|
followButton.setTextVisible(true);
|
||||||
|
followProgress.setVisibility(View.GONE);
|
||||||
|
if(followMenuItem!=null){
|
||||||
|
followMenuItem.setTitle(getString(hashtag.following ? R.string.unfollow_user : R.string.follow_user, "#"+hashtagName));
|
||||||
|
followMenuItem.setIcon(hashtag.following ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
|
||||||
|
UiUtils.insetPopupMenuIcon(getContext(), followMenuItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reloadTag(){
|
||||||
|
new GetTag(hashtagName)
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Hashtag result){
|
||||||
|
hashtag=result;
|
||||||
|
updateHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFollowed(boolean followed){
|
||||||
|
if(followRequestRunning)
|
||||||
|
return;
|
||||||
|
getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
|
||||||
|
followButton.setTextVisible(false);
|
||||||
|
followProgress.setVisibility(View.VISIBLE);
|
||||||
|
followRequestRunning=true;
|
||||||
|
new SetTagFollowed(hashtagName, followed)
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Hashtag result){
|
||||||
|
if(getActivity()==null)
|
||||||
|
return;
|
||||||
|
hashtag=result;
|
||||||
|
updateHeader();
|
||||||
|
followRequestRunning=false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
if(getActivity()==null)
|
||||||
|
return;
|
||||||
|
if(error instanceof MastodonErrorResponse er && "Duplicate record".equals(er.error)){
|
||||||
|
hashtag.following=true;
|
||||||
|
}else{
|
||||||
|
error.showToast(getActivity());
|
||||||
|
}
|
||||||
|
updateHeader();
|
||||||
|
followRequestRunning=false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tabBar.selectTab(currentTab);
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -524,9 +524,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
if (list.repliesPolicy != null) args.putInt("repliesPolicy", list.repliesPolicy.ordinal());
|
if (list.repliesPolicy != null) args.putInt("repliesPolicy", list.repliesPolicy.ordinal());
|
||||||
Nav.go(getActivity(), ListTimelineFragment.class, args);
|
Nav.go(getActivity(), ListTimelineFragment.class, args);
|
||||||
} else if ((hashtag = hashtagsItems.get(id)) != null) {
|
} else if ((hashtag = hashtagsItems.get(id)) != null) {
|
||||||
args.putString("hashtag", hashtag.name);
|
UiUtils.openHashtagTimeline(getContext(), accountID, hashtag);
|
||||||
args.putBoolean("following", hashtag.following);
|
|
||||||
Nav.go(getActivity(), HashtagTimelineFragment.class, args);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,25 +13,24 @@ import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
|||||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||||
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
|
||||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.TimelineMarkers;
|
import org.joinmastodon.android.model.TimelineMarkers;
|
||||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
|
|
||||||
public class HomeTimelineFragment extends StatusListFragment {
|
public class HomeTimelineFragment extends StatusListFragment {
|
||||||
private HomeTabFragment parent;
|
private HomeTabFragment parent;
|
||||||
@@ -176,15 +175,23 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onGapClick(GapStatusDisplayItem.Holder item){
|
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){
|
||||||
if(dataLoading)
|
if(dataLoading)
|
||||||
return;
|
return;
|
||||||
item.getItem().loading=true;
|
|
||||||
V.setVisibilityAnimated(item.progress, View.VISIBLE);
|
|
||||||
V.setVisibilityAnimated(item.text, View.GONE);
|
|
||||||
GapStatusDisplayItem gap=item.getItem();
|
GapStatusDisplayItem gap=item.getItem();
|
||||||
|
gap.loading=true;
|
||||||
dataLoading=true;
|
dataLoading=true;
|
||||||
currentRequest=new GetHomeTimeline(item.getItemID(), null, 20, null, getLocalPrefs().timelineReplyVisibility)
|
|
||||||
|
String maxID = null;
|
||||||
|
String minID = null;
|
||||||
|
if (downwards) {
|
||||||
|
maxID = item.getItemID();
|
||||||
|
} else {
|
||||||
|
int gapPos=displayItems.indexOf(gap);
|
||||||
|
StatusDisplayItem nextItem=displayItems.get(gapPos + 1);
|
||||||
|
minID=nextItem.parentID;
|
||||||
|
}
|
||||||
|
currentRequest=new GetHomeTimeline(maxID, minID, 20, null, getLocalPrefs().timelineReplyVisibility)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
@@ -204,6 +211,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
|
if(downwards) {
|
||||||
Set<String> idsBelowGap=new HashSet<>();
|
Set<String> idsBelowGap=new HashSet<>();
|
||||||
boolean belowGap=false;
|
boolean belowGap=false;
|
||||||
int gapPostIndex=0;
|
int gapPostIndex=0;
|
||||||
@@ -229,19 +237,16 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
}else{
|
}else{
|
||||||
result=result.subList(0, endIndex);
|
result=result.subList(0, endIndex);
|
||||||
}
|
}
|
||||||
|
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.HOME);
|
||||||
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
||||||
targetList.clear();
|
targetList.clear();
|
||||||
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
||||||
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, getFilterContext());
|
|
||||||
for(Status s:result){
|
for(Status s:result){
|
||||||
if(idsBelowGap.contains(s.id))
|
if(idsBelowGap.contains(s.id))
|
||||||
break;
|
break;
|
||||||
if(typeFilterPredicate(s) && filterPredicate.test(s)){
|
|
||||||
targetList.addAll(buildDisplayItems(s));
|
targetList.addAll(buildDisplayItems(s));
|
||||||
insertedPosts.add(s);
|
insertedPosts.add(s);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
AccountSessionManager.get(accountID).filterStatuses(insertedPosts, getFilterContext());
|
|
||||||
if(targetList.isEmpty()){
|
if(targetList.isEmpty()){
|
||||||
// oops. We didn't add new posts, but at least we know there are none.
|
// oops. We didn't add new posts, but at least we know there are none.
|
||||||
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
|
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
|
||||||
@@ -250,6 +255,52 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
|
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
|
||||||
}
|
}
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
|
||||||
|
} else {
|
||||||
|
String aboveGapID = gap.parentID;
|
||||||
|
int gapPostIndex = 0;
|
||||||
|
for (;gapPostIndex<data.size();gapPostIndex++){
|
||||||
|
if (Objects.equals(aboveGapID, data.get(gapPostIndex).id)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// find if there's an overlap between the new data and the current data
|
||||||
|
int indexOfGapInResponse = 0;
|
||||||
|
for (;indexOfGapInResponse<result.size();indexOfGapInResponse++){
|
||||||
|
if (Objects.equals(aboveGapID, result.get(indexOfGapInResponse).id)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// there is an overlap between new and current data
|
||||||
|
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
||||||
|
if(indexOfGapInResponse<result.size()){
|
||||||
|
result=result.subList(indexOfGapInResponse+1,result.size());
|
||||||
|
Optional<Status> gapStatus=data.stream()
|
||||||
|
.filter(s->Objects.equals(s.id, gap.parentID))
|
||||||
|
.findFirst();
|
||||||
|
if (gapStatus.isPresent()) {
|
||||||
|
gapStatus.get().hasGapAfter=false;
|
||||||
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus.get()), false);
|
||||||
|
}
|
||||||
|
targetList.clear();
|
||||||
|
} else {
|
||||||
|
gap.loading=false;
|
||||||
|
}
|
||||||
|
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
||||||
|
for(Status s:result){
|
||||||
|
targetList.addAll(buildDisplayItems(s));
|
||||||
|
insertedPosts.add(s);
|
||||||
|
}
|
||||||
|
AccountSessionManager.get(accountID).filterStatuses(insertedPosts, FilterContext.HOME);
|
||||||
|
if(targetList.isEmpty()){
|
||||||
|
// oops. We didn't add new posts, but at least we know there are none.
|
||||||
|
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
|
||||||
|
}else{
|
||||||
|
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
|
||||||
|
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
|
||||||
|
}
|
||||||
|
list.scrollToPosition(getMainAdapterOffset()+gapPos+targetList.size());
|
||||||
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
if (n.type == Notification.Type.FOLLOW_REQUEST) {
|
if (n.type == Notification.Type.FOLLOW_REQUEST) {
|
||||||
ArrayList<StatusDisplayItem> items = new ArrayList<>();
|
ArrayList<StatusDisplayItem> items = new ArrayList<>();
|
||||||
items.add(titleItem);
|
items.add(titleItem);
|
||||||
items.add(new AccountCardStatusDisplayItem(n.id, this, n.account, n));
|
items.add(new AccountCardStatusDisplayItem(n.id, this, accountID, n.account, n));
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
if(n.status!=null){
|
if(n.status!=null){
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import android.os.Build;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.style.ImageSpan;
|
|
||||||
import android.transition.ChangeBounds;
|
import android.transition.ChangeBounds;
|
||||||
import android.transition.Fade;
|
import android.transition.Fade;
|
||||||
import android.transition.TransitionManager;
|
import android.transition.TransitionManager;
|
||||||
@@ -60,8 +59,10 @@ import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
|||||||
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
|
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
|
||||||
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.fragments.account_list.BlockedAccountsListFragment;
|
||||||
import org.joinmastodon.android.fragments.account_list.FollowerListFragment;
|
import org.joinmastodon.android.fragments.account_list.FollowerListFragment;
|
||||||
import org.joinmastodon.android.fragments.account_list.FollowingListFragment;
|
import org.joinmastodon.android.fragments.account_list.FollowingListFragment;
|
||||||
|
import org.joinmastodon.android.fragments.account_list.MutedAccountsListFragment;
|
||||||
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
||||||
import org.joinmastodon.android.fragments.settings.SettingsServerFragment;
|
import org.joinmastodon.android.fragments.settings.SettingsServerFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
@@ -304,6 +305,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
|
|
||||||
tabbar.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant), UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary));
|
tabbar.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant), UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary));
|
||||||
tabbar.setTabTextSize(V.dp(14));
|
tabbar.setTabTextSize(V.dp(14));
|
||||||
|
tabLayoutMediator=new TabLayoutMediator(tabbar, pager, (tab, position)->tab.setText(switch(position){
|
||||||
|
case 0 -> R.string.profile_featured;
|
||||||
|
case 1 -> R.string.profile_timeline;
|
||||||
|
case 2 -> R.string.profile_about;
|
||||||
|
default -> throw new IllegalStateException();
|
||||||
|
}));
|
||||||
tabLayoutMediator=new TabLayoutMediator(tabbar, pager, new TabLayoutMediator.TabConfigurationStrategy(){
|
tabLayoutMediator=new TabLayoutMediator(tabbar, pager, new TabLayoutMediator.TabConfigurationStrategy(){
|
||||||
@Override
|
@Override
|
||||||
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
|
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
|
||||||
@@ -317,6 +324,19 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
if (position == 4) tab.view.setVisibility(View.GONE);
|
if (position == 4) tab.view.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
tabbar.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
|
||||||
|
@Override
|
||||||
|
public void onTabSelected(TabLayout.Tab tab){}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTabUnselected(TabLayout.Tab tab){}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTabReselected(TabLayout.Tab tab){
|
||||||
|
if(getFragmentForPage(tab.getPosition()) instanceof ScrollableToTop stt)
|
||||||
|
stt.scrollToTop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
cover.setOutlineProvider(new ViewOutlineProvider(){
|
cover.setOutlineProvider(new ViewOutlineProvider(){
|
||||||
@Override
|
@Override
|
||||||
@@ -377,7 +397,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
username.setOnLongClickListener(v->{
|
content.findViewById(R.id.username_wrap).setOnLongClickListener(v->{
|
||||||
String usernameString=account.acct;
|
String usernameString=account.acct;
|
||||||
if(!usernameString.contains("@")){
|
if(!usernameString.contains("@")){
|
||||||
usernameString+="@"+domain;
|
usernameString+="@"+domain;
|
||||||
@@ -601,7 +621,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
private void bindHeaderView(){
|
private void bindHeaderView(){
|
||||||
setTitle(account.displayName);
|
setTitle(account.displayName);
|
||||||
setSubtitle(getResources().getQuantityString(R.plurals.x_posts, (int)(account.statusesCount%1000), account.statusesCount));
|
setSubtitle(getResources().getQuantityString(R.plurals.x_posts, (int)(account.statusesCount%1000), account.statusesCount));
|
||||||
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(100), V.dp(100)));
|
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(
|
||||||
|
TextUtils.isEmpty(account.avatar) ? getSession().getDefaultAvatarUrl() :
|
||||||
|
GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic,
|
||||||
|
V.dp(100), V.dp(100)));
|
||||||
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.header : account.headerStatic, 1000, 1000));
|
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.header : account.headerStatic, 1000, 1000));
|
||||||
SpannableStringBuilder ssb=new SpannableStringBuilder(account.displayName);
|
SpannableStringBuilder ssb=new SpannableStringBuilder(account.displayName);
|
||||||
if(AccountSessionManager.get(accountID).getLocalPreferences().customEmojiInNames)
|
if(AccountSessionManager.get(accountID).getLocalPreferences().customEmojiInNames)
|
||||||
@@ -824,6 +847,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
||||||
}
|
}
|
||||||
Nav.go(getActivity(), ListsFragment.class, args);
|
Nav.go(getActivity(), ListsFragment.class, args);
|
||||||
|
}else if(id==R.id.muted_accounts){
|
||||||
|
final Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
args.putParcelable("targetAccount", Parcels.wrap(account));
|
||||||
|
Nav.go(getActivity(), MutedAccountsListFragment.class, args);
|
||||||
|
}else if(id==R.id.blocked_accounts){
|
||||||
|
final Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
args.putParcelable("targetAccount", Parcels.wrap(account));
|
||||||
|
Nav.go(getActivity(), BlockedAccountsListFragment.class, args);
|
||||||
}else if(id==R.id.followed_hashtags){
|
}else if(id==R.id.followed_hashtags){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
@@ -1229,7 +1262,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
if(ava==null)
|
if(ava==null)
|
||||||
return;
|
return;
|
||||||
int radius=V.dp(25);
|
int radius=V.dp(25);
|
||||||
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.avatar, ava), 0,
|
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(TextUtils.isEmpty(account.avatar) ? getSession().getDefaultAvatarUrl() : account.avatar, ava), 0,
|
||||||
new SingleImagePhotoViewerListener(avatar, avatarBorder, new int[]{radius, radius, radius, radius}, this, ()->currentPhotoViewer=null, ()->ava, null, null));
|
new SingleImagePhotoViewerListener(avatar, avatarBorder, new int[]{radius, radius, radius, radius}, this, ()->currentPhotoViewer=null, ()->ava, null, null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1301,9 +1334,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
FrameLayout view=tabViews[viewType];
|
FrameLayout view=new FrameLayout(parent.getContext());
|
||||||
if (view.getParent() != null) ((ViewGroup)view.getParent()).removeView(view);
|
|
||||||
view.setVisibility(View.VISIBLE);
|
|
||||||
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
return new SimpleViewHolder(view);
|
return new SimpleViewHolder(view);
|
||||||
}
|
}
|
||||||
@@ -1311,8 +1342,13 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
|
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
|
||||||
Fragment fragment=getFragmentForPage(position);
|
Fragment fragment=getFragmentForPage(position);
|
||||||
|
FrameLayout fragmentView=tabViews[position];
|
||||||
|
fragmentView.setVisibility(View.VISIBLE);
|
||||||
|
if(fragmentView.getParent() instanceof ViewGroup parent)
|
||||||
|
parent.removeView(fragmentView);
|
||||||
|
((FrameLayout)holder.itemView).addView(fragmentView);
|
||||||
if(!fragment.isAdded()){
|
if(!fragment.isAdded()){
|
||||||
getChildFragmentManager().beginTransaction().add(holder.itemView.getId(), fragment).commit();
|
getChildFragmentManager().beginTransaction().add(fragmentView.getId(), fragment).commit();
|
||||||
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreDraw(){
|
public boolean onPreDraw(){
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ package org.joinmastodon.android.fragments;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowInsets;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
import com.squareup.otto.Subscribe;
|
||||||
@@ -26,6 +29,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class ScheduledStatusListFragment extends BaseStatusListFragment<ScheduledStatus> {
|
public class ScheduledStatusListFragment extends BaseStatusListFragment<ScheduledStatus> {
|
||||||
private String nextMaxID;
|
private String nextMaxID;
|
||||||
@@ -186,6 +190,21 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplyWindowInsets(WindowInsets insets){
|
||||||
|
if(contentView!=null){
|
||||||
|
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0){
|
||||||
|
int insetBottom=insets.getSystemWindowInsetBottom();
|
||||||
|
((ViewGroup.MarginLayoutParams) list.getLayoutParams()).bottomMargin=insetBottom;
|
||||||
|
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(16)+insetBottom;
|
||||||
|
insets=insets.inset(0, 0, 0, insetBottom);
|
||||||
|
}else{
|
||||||
|
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onApplyWindowInsets(insets);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Uri getWebUri(Uri.Builder base) {
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
// TODO: adapt when frontends finally implement a scheduled posts list
|
// TODO: adapt when frontends finally implement a scheduled posts list
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import org.joinmastodon.android.R;
|
|||||||
import org.joinmastodon.android.api.requests.statuses.GetStatusEditHistory;
|
import org.joinmastodon.android.api.requests.statuses.GetStatusEditHistory;
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.DummyStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
||||||
@@ -143,7 +144,8 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
String sep = getString(R.string.sk_separator);
|
String sep = getString(R.string.sk_separator);
|
||||||
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" "+sep+" "+date, Collections.emptyList(), 0, null, null));
|
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" "+sep+" "+date, Collections.emptyList(), 0, null, null, s));
|
||||||
|
items.add(1, new DummyStatusDisplayItem(s.id, this));
|
||||||
}
|
}
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import org.joinmastodon.android.ui.BetterItemAnimator;
|
|||||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.SpoilerStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
|
||||||
@@ -105,6 +106,12 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
|||||||
text.textSelectable=true;
|
text.textSelectable=true;
|
||||||
else if(item instanceof FooterStatusDisplayItem footer)
|
else if(item instanceof FooterStatusDisplayItem footer)
|
||||||
footer.hideCounts=true;
|
footer.hideCounts=true;
|
||||||
|
else if(item instanceof SpoilerStatusDisplayItem spoiler){
|
||||||
|
for(StatusDisplayItem subItem:spoiler.contentItems){
|
||||||
|
if(subItem instanceof TextStatusDisplayItem text)
|
||||||
|
text.textSelectable=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.joinmastodon.android.fragments.account_list;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||||
|
import org.joinmastodon.android.api.requests.accounts.GetAccountBlocks;
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
||||||
|
|
||||||
|
public class BlockedAccountsListFragment extends AccountRelatedAccountListFragment{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setTitle(R.string.sk_blocked_accounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
||||||
|
return new GetAccountBlocks(maxID, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onConfigureViewHolder(AccountViewHolder holder){
|
||||||
|
super.onConfigureViewHolder(holder);
|
||||||
|
holder.setStyle(AccountViewHolder.AccessoryType.NONE, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return super.getWebUri(base).buildUpon()
|
||||||
|
.appendPath("/blocks").build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,7 +48,7 @@ public class ComposeAccountSearchFragment extends BaseAccountListFragment{
|
|||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
refreshing=true;
|
refreshing=true;
|
||||||
currentRequest=new GetSearchResults(currentQuery, GetSearchResults.Type.ACCOUNTS, false)
|
currentRequest=new GetSearchResults(currentQuery, GetSearchResults.Type.ACCOUNTS, false, null, 0, 0)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(SearchResults result){
|
public void onSuccess(SearchResults result){
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.joinmastodon.android.fragments.account_list;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||||
|
import org.joinmastodon.android.api.requests.accounts.GetAccountMutes;
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
||||||
|
|
||||||
|
public class MutedAccountsListFragment extends AccountRelatedAccountListFragment{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setTitle(R.string.sk_muted_accounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
||||||
|
return new GetAccountMutes(maxID, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onConfigureViewHolder(AccountViewHolder holder){
|
||||||
|
super.onConfigureViewHolder(holder);
|
||||||
|
holder.setStyle(AccountViewHolder.AccessoryType.NONE, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return super.getWebUri(base).buildUpon()
|
||||||
|
.appendPath("/mutes").build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ import android.widget.TextView;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
|
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.IsOnTop;
|
import org.joinmastodon.android.fragments.IsOnTop;
|
||||||
import org.joinmastodon.android.fragments.MastodonRecyclerFragment;
|
import org.joinmastodon.android.fragments.MastodonRecyclerFragment;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
@@ -319,8 +320,9 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
|
|||||||
|
|
||||||
public AccountWrapper(Account account){
|
public AccountWrapper(Account account){
|
||||||
this.account=account;
|
this.account=account;
|
||||||
if(!TextUtils.isEmpty(account.avatar))
|
avaRequest=new UrlImageLoaderRequest(
|
||||||
avaRequest=new UrlImageLoaderRequest(account.avatar, V.dp(50), V.dp(50));
|
TextUtils.isEmpty(account.avatar) ? AccountSessionManager.getInstance().getAccount(accountID).getDefaultAvatarUrl() : account.avatar,
|
||||||
|
V.dp(50), V.dp(50));
|
||||||
if(!TextUtils.isEmpty(account.header))
|
if(!TextUtils.isEmpty(account.header))
|
||||||
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
|
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
|
||||||
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||||
|
|||||||
@@ -289,15 +289,19 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
FrameLayout view=tabViews[viewType];
|
FrameLayout view=new FrameLayout(parent.getContext());
|
||||||
((ViewGroup)view.getParent()).removeView(view);
|
|
||||||
view.setVisibility(View.VISIBLE);
|
|
||||||
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
return new SimpleViewHolder(view);
|
return new SimpleViewHolder(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){}
|
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
|
||||||
|
FrameLayout view=tabViews[position];
|
||||||
|
if(view.getParent() instanceof ViewGroup parent)
|
||||||
|
parent.removeView(view);
|
||||||
|
view.setVisibility(View.VISIBLE);
|
||||||
|
((FrameLayout)holder.itemView).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount(){
|
public int getItemCount(){
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import org.joinmastodon.android.model.Status;
|
|||||||
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
@@ -31,7 +30,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
@@ -97,7 +96,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||||||
args.putParcelable("profileAccount", Parcels.wrap(res.account));
|
args.putParcelable("profileAccount", Parcels.wrap(res.account));
|
||||||
Nav.go(getActivity(), ProfileFragment.class, args);
|
Nav.go(getActivity(), ProfileFragment.class, args);
|
||||||
}
|
}
|
||||||
case HASHTAG -> UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name, res.hashtag.following);
|
case HASHTAG -> UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag);
|
||||||
case STATUS -> {
|
case STATUS -> {
|
||||||
Status status=res.status.getContentStatus();
|
Status status=res.status.getContentStatus();
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
@@ -113,7 +112,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int _offset, int count){
|
||||||
GetSearchResults.Type type;
|
GetSearchResults.Type type;
|
||||||
if(currentFilter.size()==1){
|
if(currentFilter.size()==1){
|
||||||
type=switch(currentFilter.iterator().next()){
|
type=switch(currentFilter.iterator().next()){
|
||||||
@@ -128,7 +127,21 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||||||
dataLoaded();
|
dataLoaded();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentRequest=new GetSearchResults(currentQuery, type, true)
|
String maxID=null;
|
||||||
|
// TODO server-side bug
|
||||||
|
/*int offset=0;
|
||||||
|
if(_offset>0){
|
||||||
|
if(type==GetSearchResults.Type.STATUSES){
|
||||||
|
if(!preloadedData.isEmpty())
|
||||||
|
maxID=preloadedData.get(preloadedData.size()-1).status.id;
|
||||||
|
else if(!data.isEmpty())
|
||||||
|
maxID=data.get(data.size()-1).status.id;
|
||||||
|
}else{
|
||||||
|
offset=_offset;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
int offset=_offset;
|
||||||
|
currentRequest=new GetSearchResults(currentQuery, type, type==null, maxID, offset, type==null ? 0 : count)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(SearchResults result){
|
public void onSuccess(SearchResults result){
|
||||||
@@ -142,12 +155,15 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||||||
results.add(new SearchResult(tag));
|
results.add(new SearchResult(tag));
|
||||||
}
|
}
|
||||||
if(result.statuses!=null){
|
if(result.statuses!=null){
|
||||||
for(Status status:result.statuses)
|
Set<String> alreadyLoadedStatuses=data.stream().filter(r->r.type==SearchResult.Type.STATUS).map(r->r.status.id).collect(Collectors.toSet());
|
||||||
|
for(Status status:result.statuses){
|
||||||
|
if(!alreadyLoadedStatuses.contains(status.id))
|
||||||
results.add(new SearchResult(status));
|
results.add(new SearchResult(status));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
prevDisplayItems=new ArrayList<>(displayItems);
|
prevDisplayItems=new ArrayList<>(displayItems);
|
||||||
unfilteredResults=results;
|
unfilteredResults=results;
|
||||||
onDataLoaded(filterSearchResults(results), false);
|
onDataLoaded(filterSearchResults(results), type!=null && !results.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import android.graphics.Outline;
|
|||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.LayerDrawable;
|
import android.graphics.drawable.LayerDrawable;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -26,7 +25,7 @@ import android.widget.TextView;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
import org.joinmastodon.android.MainActivity;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
@@ -38,19 +37,16 @@ import org.joinmastodon.android.model.SearchResults;
|
|||||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||||
import org.joinmastodon.android.model.viewmodel.SearchResultViewModel;
|
import org.joinmastodon.android.model.viewmodel.SearchResultViewModel;
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.SearchViewHelper;
|
import org.joinmastodon.android.ui.SearchViewHelper;
|
||||||
import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter;
|
import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter;
|
||||||
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
|
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
||||||
import org.joinmastodon.android.ui.viewholders.SimpleListItemViewHolder;
|
import org.joinmastodon.android.ui.viewholders.SimpleListItemViewHolder;
|
||||||
import org.parceler.Parcels;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@@ -126,7 +122,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
|||||||
recentsHeader.setVisible(!data.isEmpty());
|
recentsHeader.setVisible(!data.isEmpty());
|
||||||
});
|
});
|
||||||
}else{
|
}else{
|
||||||
currentRequest=new GetSearchResults(currentQuery, null, false)
|
currentRequest=new GetSearchResults(currentQuery, null, false, null, 0, 0)
|
||||||
.limit(2)
|
.limit(2)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
@@ -381,16 +377,56 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void openHashtag(SearchResult res){
|
private void openHashtag(SearchResult res){
|
||||||
UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name, res.hashtag.following);
|
wrapSuicideDialog(()->{
|
||||||
|
UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag);
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putRecentSearch(res);
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putRecentSearch(res);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isInRecentMode(){
|
private boolean isInRecentMode(){
|
||||||
return TextUtils.isEmpty(currentQuery);
|
return TextUtils.isEmpty(currentQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void wrapSuicideDialog(Runnable r){
|
||||||
|
if(!GlobalUserPreferences.showSuicideHelp || currentQuery==null){
|
||||||
|
r.run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] terms=getContext().getString(R.string.sk_suicide_search_terms).toLowerCase().split(",");
|
||||||
|
String query=currentQuery.trim().toLowerCase();
|
||||||
|
boolean termMatches=false;
|
||||||
|
for(String term : terms){
|
||||||
|
if(query.contains(term)){
|
||||||
|
termMatches=true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!termMatches){
|
||||||
|
r.run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String url=getContext().getString(R.string.sk_suicide_helplines_url);
|
||||||
|
new M3AlertDialogBuilder(getActivity())
|
||||||
|
.setTitle(R.string.sk_search_suicide_title)
|
||||||
|
.setMessage(R.string.sk_search_suicide_message)
|
||||||
|
.setNegativeButton(R.string.sk_do_not_show_again, (dialog, which)->{
|
||||||
|
GlobalUserPreferences.showSuicideHelp = false;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
r.run();
|
||||||
|
})
|
||||||
|
.setNeutralButton(R.string.sk_search_suicide_hotlines, (dialog, which)->UiUtils.launchWebBrowser(getContext(), url))
|
||||||
|
.setPositiveButton(R.string.ok, (dialog, which)->r.run())
|
||||||
|
.setOnDismissListener((dialog)->{})
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
private void onSearchViewEnter(){
|
private void onSearchViewEnter(){
|
||||||
deliverResult(currentQuery, null);
|
if(TextUtils.isEmpty(currentQuery) || currentQuery.trim().isEmpty())
|
||||||
|
return;
|
||||||
|
wrapSuicideDialog(()->deliverResult(currentQuery, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onOpenURLClick(){
|
private void onOpenURLClick(){
|
||||||
@@ -398,10 +434,12 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onGoToHashtagClick(){
|
private void onGoToHashtagClick(){
|
||||||
|
wrapSuicideDialog(()->{
|
||||||
String q=searchViewHelper.getQuery();
|
String q=searchViewHelper.getQuery();
|
||||||
if(q.startsWith("#"))
|
if(q.startsWith("#"))
|
||||||
q=q.substring(1);
|
q=q.substring(1);
|
||||||
UiUtils.openHashtagTimeline(getActivity(), accountID, q, null);
|
UiUtils.openHashtagTimeline(getActivity(), accountID, q);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onGoToAccountClick(){
|
private void onGoToAccountClick(){
|
||||||
@@ -422,11 +460,11 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onGoToStatusSearchClick(){
|
private void onGoToStatusSearchClick(){
|
||||||
deliverResult(searchViewHelper.getQuery(), SearchResult.Type.STATUS);
|
wrapSuicideDialog(()->deliverResult(searchViewHelper.getQuery(), SearchResult.Type.STATUS));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onGoToAccountSearchClick(){
|
private void onGoToAccountSearchClick(){
|
||||||
deliverResult(searchViewHelper.getQuery(), SearchResult.Type.ACCOUNT);
|
wrapSuicideDialog(()->deliverResult(searchViewHelper.getQuery(), SearchResult.Type.ACCOUNT));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onClearRecentClick(){
|
private void onClearRecentClick(){
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(){
|
public void onClick(){
|
||||||
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name, item.following);
|
UiUtils.openHashtagTimeline(getActivity(), accountID, item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,9 +165,7 @@ public class AccountActivationFragment extends ToolbarFragment{
|
|||||||
private void tryGetAccount(){
|
private void tryGetAccount(){
|
||||||
if(AccountSessionManager.getInstance().tryGetAccount(accountID)==null){
|
if(AccountSessionManager.getInstance().tryGetAccount(accountID)==null){
|
||||||
uiHandler.removeCallbacks(pollRunnable);
|
uiHandler.removeCallbacks(pollRunnable);
|
||||||
getActivity().finish();
|
((MainActivity)getActivity()).restartHomeFragment();
|
||||||
Intent intent=new Intent(getActivity(), MainActivity.class);
|
|
||||||
startActivity(intent);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentRequest=new GetOwnAccount()
|
currentRequest=new GetOwnAccount()
|
||||||
|
|||||||
@@ -271,7 +271,7 @@ public class SignupFragment extends ToolbarFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void tail(Node node, int depth){
|
public void tail(Node node, int depth){
|
||||||
if(node instanceof Element){
|
if(node instanceof Element){
|
||||||
ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null, null, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
ssb.setSpan(new TypefaceSpan("sans-serif-medium"), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
ssb.setSpan(new TypefaceSpan("sans-serif-medium"), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
|
|||||||
reportStatus=Parcels.unwrap(getArguments().getParcelable("status"));
|
reportStatus=Parcels.unwrap(getArguments().getParcelable("status"));
|
||||||
if(reportStatus!=null){
|
if(reportStatus!=null){
|
||||||
Status hiddenStatus=reportStatus.clone();
|
Status hiddenStatus=reportStatus.clone();
|
||||||
hiddenStatus.spoilerText=getString(R.string.post_hidden);
|
if(hiddenStatus.spoilerText==null) hiddenStatus.spoilerText=getString(R.string.post_hidden);
|
||||||
onDataLoaded(Collections.singletonList(hiddenStatus));
|
onDataLoaded(Collections.singletonList(hiddenStatus));
|
||||||
setTitle(R.string.report_title_post);
|
setTitle(R.string.report_title_post);
|
||||||
}else{
|
}else{
|
||||||
@@ -168,17 +168,6 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
|
|||||||
((UsableRecyclerView)list).setIncludeMarginsInItemHitbox(false);
|
((UsableRecyclerView)list).setIncludeMarginsInItemHitbox(false);
|
||||||
|
|
||||||
if(reportStatus!=null){
|
if(reportStatus!=null){
|
||||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
|
||||||
@Override
|
|
||||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
|
||||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
|
|
||||||
if(holder instanceof LinkCardStatusDisplayItem.Holder || holder instanceof MediaGridStatusDisplayItem.Holder){
|
|
||||||
outRect.left=V.dp(16);
|
|
||||||
outRect.right=V.dp(16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||||
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
{
|
{
|
||||||
@@ -222,10 +211,6 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
|
|||||||
if(holder instanceof StatusDisplayItem.Holder<?>){
|
if(holder instanceof StatusDisplayItem.Holder<?>){
|
||||||
outRect.left=outRect.right=V.dp(16);
|
outRect.left=outRect.right=V.dp(16);
|
||||||
}
|
}
|
||||||
int index=holder.getAbsoluteAdapterPosition()-mergeAdapter.getPositionForAdapter(adapter);
|
|
||||||
if(index==displayItems.size()){
|
|
||||||
outRect.top=V.dp(32);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -251,18 +236,6 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onModifyItemViewHolder(BindableViewHolder<StatusDisplayItem> holder){
|
|
||||||
if((Object)holder instanceof MediaGridStatusDisplayItem.Holder h){
|
|
||||||
View layout=h.getLayout();
|
|
||||||
layout.setOutlineProvider(OutlineProviders.roundedRect(8));
|
|
||||||
layout.setClipToOutline(true);
|
|
||||||
View overlay=h.getSensitiveOverlay();
|
|
||||||
overlay.setOutlineProvider(OutlineProviders.roundedRect(8));
|
|
||||||
overlay.setClipToOutline(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void putRelationship(String id, Relationship rel){
|
public void putRelationship(String id, Relationship rel){
|
||||||
super.putRelationship(id, rel);
|
super.putRelationship(id, rel);
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{
|
|||||||
setTitle(getString(R.string.about_app, getString(R.string.sk_app_name)));
|
setTitle(getString(R.string.about_app, getString(R.string.sk_app_name)));
|
||||||
AccountSession s=AccountSessionManager.get(accountID);
|
AccountSession s=AccountSessionManager.get(accountID);
|
||||||
onDataLoaded(List.of(
|
onDataLoaded(List.of(
|
||||||
new ListItem<>(R.string.sk_settings_donate, 0, R.drawable.ic_fluent_heart_24_regular, ()->UiUtils.openHashtagTimeline(getActivity(), accountID, getString(R.string.donate_hashtag), null)),
|
new ListItem<>(R.string.sk_settings_donate, 0, R.drawable.ic_fluent_heart_24_regular, ()->UiUtils.openHashtagTimeline(getActivity(), accountID, getString(R.string.donate_hashtag))),
|
||||||
new ListItem<>(R.string.sk_settings_contribute, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), getString(R.string.repo_url))),
|
new ListItem<>(R.string.sk_settings_contribute, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), getString(R.string.repo_url))),
|
||||||
new ListItem<>(R.string.settings_tos, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/terms")),
|
new ListItem<>(R.string.settings_tos, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/terms")),
|
||||||
new ListItem<>(R.string.settings_privacy_policy, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), getString(R.string.privacy_policy_url)), 0, true),
|
new ListItem<>(R.string.settings_privacy_policy, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), getString(R.string.privacy_policy_url)), 0, true),
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import org.joinmastodon.android.utils.MastodonLanguage;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> implements HasAccountID{
|
public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> implements HasAccountID{
|
||||||
private ListItem<Void> languageItem;
|
private ListItem<Void> languageItem;
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
|
|||||||
onDataLoaded(List.of(
|
onDataLoaded(List.of(
|
||||||
new ListItem<>(R.string.settings_behavior, 0, R.drawable.ic_fluent_settings_24_regular, this::onBehaviorClick),
|
new ListItem<>(R.string.settings_behavior, 0, R.drawable.ic_fluent_settings_24_regular, this::onBehaviorClick),
|
||||||
new ListItem<>(R.string.settings_display, 0, R.drawable.ic_fluent_color_24_regular, this::onDisplayClick),
|
new ListItem<>(R.string.settings_display, 0, R.drawable.ic_fluent_color_24_regular, this::onDisplayClick),
|
||||||
|
new ListItem<>(R.string.settings_privacy, 0, R.drawable.ic_privacy_tip_24px, this::onPrivacyClick),
|
||||||
|
new ListItem<>(R.string.settings_filters, 0, R.drawable.ic_filter_alt_24px, this::onFiltersClick),
|
||||||
new ListItem<>(R.string.settings_notifications, 0, R.drawable.ic_fluent_alert_24_regular, this::onNotificationsClick),
|
new ListItem<>(R.string.settings_notifications, 0, R.drawable.ic_fluent_alert_24_regular, this::onNotificationsClick),
|
||||||
new ListItem<>(R.string.sk_settings_instance, 0, R.drawable.ic_fluent_server_24_regular, this::onInstanceClick),
|
new ListItem<>(R.string.sk_settings_instance, 0, R.drawable.ic_fluent_server_24_regular, this::onInstanceClick),
|
||||||
new ListItem<>(getString(R.string.about_app, getString(R.string.sk_app_name)), null, R.drawable.ic_fluent_info_24_regular, this::onAboutClick, null, 0, true),
|
new ListItem<>(getString(R.string.about_app, getString(R.string.sk_app_name)), null, R.drawable.ic_fluent_info_24_regular, this::onAboutClick, null, 0, true),
|
||||||
@@ -69,7 +71,9 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
|
|||||||
data.add(0, new ListItem<>("Debug settings", null, R.drawable.ic_fluent_wrench_screwdriver_24_regular, ()->Nav.go(getActivity(), SettingsDebugFragment.class, makeFragmentArgs()), null, 0, true));
|
data.add(0, new ListItem<>("Debug settings", null, R.drawable.ic_fluent_wrench_screwdriver_24_regular, ()->Nav.go(getActivity(), SettingsDebugFragment.class, makeFragmentArgs()), null, 0, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
account.reloadPreferences(null);
|
AccountSession session=AccountSessionManager.get(accountID);
|
||||||
|
session.reloadPreferences(null);
|
||||||
|
session.updateAccountInfo();
|
||||||
E.register(this);
|
E.register(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +137,10 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
|
|||||||
Nav.go(getActivity(), SettingsDisplayFragment.class, makeFragmentArgs());
|
Nav.go(getActivity(), SettingsDisplayFragment.class, makeFragmentArgs());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onPrivacyClick(){
|
||||||
|
Nav.go(getActivity(), SettingsPrivacyFragment.class, makeFragmentArgs());
|
||||||
|
}
|
||||||
|
|
||||||
private void onFiltersClick(){
|
private void onFiltersClick(){
|
||||||
Nav.go(getActivity(), SettingsFiltersFragment.class, makeFragmentArgs());
|
Nav.go(getActivity(), SettingsFiltersFragment.class, makeFragmentArgs());
|
||||||
}
|
}
|
||||||
@@ -155,9 +163,7 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
|
|||||||
.setMessage(getString(R.string.confirm_log_out, session.getFullUsername()))
|
.setMessage(getString(R.string.confirm_log_out, session.getFullUsername()))
|
||||||
.setPositiveButton(R.string.log_out, (dialog, which)->account.logOut(getActivity(), ()->{
|
.setPositiveButton(R.string.log_out, (dialog, which)->account.logOut(getActivity(), ()->{
|
||||||
loggedOut=true;
|
loggedOut=true;
|
||||||
getActivity().finish();
|
((MainActivity)getActivity()).restartHomeFragment();
|
||||||
Intent intent=new Intent(getActivity(), MainActivity.class);
|
|
||||||
startActivity(intent);
|
|
||||||
}))
|
}))
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.show();
|
.show();
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package org.joinmastodon.android.fragments.settings;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SettingsPrivacyFragment extends BaseSettingsFragment<Void>{
|
||||||
|
private CheckableListItem<Void> discoverableItem, indexableItem;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setTitle(R.string.settings_privacy);
|
||||||
|
Account self=AccountSessionManager.get(accountID).self;
|
||||||
|
onDataLoaded(List.of(
|
||||||
|
discoverableItem=new CheckableListItem<>(R.string.settings_discoverable, 0, CheckableListItem.Style.SWITCH, self.discoverable, R.drawable.ic_thumbs_up_down_24px, ()->toggleCheckableItem(discoverableItem)),
|
||||||
|
indexableItem=new CheckableListItem<>(R.string.settings_indexable, 0, CheckableListItem.Style.SWITCH, self.source.indexable!=null ? self.source.indexable : true, R.drawable.ic_search_24px, ()->toggleCheckableItem(indexableItem))
|
||||||
|
));
|
||||||
|
if(self.source.indexable==null)
|
||||||
|
indexableItem.isEnabled=false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doLoadData(int offset, int count){}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause(){
|
||||||
|
super.onPause();
|
||||||
|
Account self=AccountSessionManager.get(accountID).self;
|
||||||
|
if(self.discoverable!=discoverableItem.checked || (self.source.indexable!=null && self.source.indexable!=indexableItem.checked)){
|
||||||
|
self.discoverable=discoverableItem.checked;
|
||||||
|
self.source.indexable=indexableItem.checked;
|
||||||
|
AccountSessionManager.get(accountID).savePreferencesLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -153,18 +153,21 @@ public class SettingsServerFragment extends AppKitFragment{
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
FrameLayout view=tabViews[viewType];
|
FrameLayout view=new FrameLayout(parent.getContext());
|
||||||
((ViewGroup)view.getParent()).removeView(view);
|
|
||||||
view.setVisibility(View.VISIBLE);
|
|
||||||
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
return new SimpleViewHolder(view);
|
return new SimpleViewHolder(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
|
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
|
||||||
|
FrameLayout view=tabViews[position];
|
||||||
|
if(view.getParent() instanceof ViewGroup parent)
|
||||||
|
parent.removeView(view);
|
||||||
|
view.setVisibility(View.VISIBLE);
|
||||||
|
((FrameLayout)holder.itemView).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
Fragment fragment=getFragmentForPage(position);
|
Fragment fragment=getFragmentForPage(position);
|
||||||
if(!fragment.isAdded()){
|
if(!fragment.isAdded()){
|
||||||
getChildFragmentManager().beginTransaction().add(holder.itemView.getId(), fragment).commit();
|
getChildFragmentManager().beginTransaction().add(view.getId(), fragment).commit();
|
||||||
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreDraw(){
|
public boolean onPreDraw(){
|
||||||
|
|||||||
@@ -7,12 +7,17 @@ import androidx.annotation.Nullable;
|
|||||||
|
|
||||||
import org.joinmastodon.android.api.ObjectValidationException;
|
import org.joinmastodon.android.api.ObjectValidationException;
|
||||||
import org.joinmastodon.android.api.RequiredField;
|
import org.joinmastodon.android.api.RequiredField;
|
||||||
|
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||||
import org.parceler.Parcel;
|
import org.parceler.Parcel;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a user of Mastodon and their associated profile.
|
* Represents a user of Mastodon and their associated profile.
|
||||||
*/
|
*/
|
||||||
@@ -23,22 +28,22 @@ public class Account extends BaseModel implements Searchable{
|
|||||||
/**
|
/**
|
||||||
* The account id
|
* The account id
|
||||||
*/
|
*/
|
||||||
@RequiredField
|
// @RequiredField
|
||||||
public String id;
|
public String id;
|
||||||
/**
|
/**
|
||||||
* The username of the account, not including domain.
|
* The username of the account, not including domain.
|
||||||
*/
|
*/
|
||||||
@RequiredField
|
// @RequiredField
|
||||||
public String username;
|
public String username;
|
||||||
/**
|
/**
|
||||||
* The Webfinger account URI. Equal to username for local users, or username@domain for remote users.
|
* The Webfinger account URI. Equal to username for local users, or username@domain for remote users.
|
||||||
*/
|
*/
|
||||||
@RequiredField
|
// @RequiredField
|
||||||
public String acct;
|
public String acct;
|
||||||
/**
|
/**
|
||||||
* The location of the user's profile page.
|
* The location of the user's profile page.
|
||||||
*/
|
*/
|
||||||
@RequiredField
|
// @RequiredField
|
||||||
public String url;
|
public String url;
|
||||||
|
|
||||||
// Display attributes
|
// Display attributes
|
||||||
@@ -51,12 +56,12 @@ public class Account extends BaseModel implements Searchable{
|
|||||||
/**
|
/**
|
||||||
* The profile's bio / description.
|
* The profile's bio / description.
|
||||||
*/
|
*/
|
||||||
@RequiredField
|
// @RequiredField
|
||||||
public String note;
|
public String note;
|
||||||
/**
|
/**
|
||||||
* An image icon that is shown next to statuses and in the profile.
|
* An image icon that is shown next to statuses and in the profile.
|
||||||
*/
|
*/
|
||||||
@RequiredField
|
// @RequiredField
|
||||||
public String avatar;
|
public String avatar;
|
||||||
/**
|
/**
|
||||||
* A static version of the avatar. Equal to avatar if its value is a static image; different if avatar is an animated GIF.
|
* A static version of the avatar. Equal to avatar if its value is a static image; different if avatar is an animated GIF.
|
||||||
@@ -134,6 +139,7 @@ public class Account extends BaseModel implements Searchable{
|
|||||||
* When a timed mute will expire, if applicable.
|
* When a timed mute will expire, if applicable.
|
||||||
*/
|
*/
|
||||||
public Instant muteExpiresAt;
|
public Instant muteExpiresAt;
|
||||||
|
public boolean noindex;
|
||||||
|
|
||||||
public List<Role> roles;
|
public List<Role> roles;
|
||||||
|
|
||||||
@@ -157,16 +163,26 @@ public class Account extends BaseModel implements Searchable{
|
|||||||
if(fields!=null){
|
if(fields!=null){
|
||||||
for(AccountField f:fields)
|
for(AccountField f:fields)
|
||||||
f.postprocess();
|
f.postprocess();
|
||||||
|
} else {
|
||||||
|
fields = Collections.emptyList();
|
||||||
}
|
}
|
||||||
if(emojis!=null){
|
if(emojis!=null){
|
||||||
for(Emoji e:emojis)
|
for(Emoji e:emojis)
|
||||||
e.postprocess();
|
e.postprocess();
|
||||||
|
} else {
|
||||||
|
emojis = Collections.emptyList();
|
||||||
}
|
}
|
||||||
if(moved!=null)
|
if(moved!=null)
|
||||||
moved.postprocess();
|
moved.postprocess();
|
||||||
if(TextUtils.isEmpty(displayName))
|
|
||||||
displayName=username;
|
|
||||||
if(fqn == null) fqn = getFullyQualifiedName();
|
if(fqn == null) fqn = getFullyQualifiedName();
|
||||||
|
if(id == null) id = "";
|
||||||
|
if(username == null) username = "";
|
||||||
|
if(TextUtils.isEmpty(displayName))
|
||||||
|
displayName = !TextUtils.isEmpty(username) ? username : "";
|
||||||
|
if(acct == null) acct = "";
|
||||||
|
if(url == null) url = "";
|
||||||
|
if(note == null) note = "";
|
||||||
|
if(avatar == null) avatar = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLocal(){
|
public boolean isLocal(){
|
||||||
@@ -191,6 +207,8 @@ public class Account extends BaseModel implements Searchable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getFullyQualifiedName() {
|
public String getFullyQualifiedName() {
|
||||||
|
if (TextUtils.isEmpty(acct))
|
||||||
|
return "";
|
||||||
return fqn != null ? fqn : acct.split("@")[0] + "@" + getDomainFromURL();
|
return fqn != null ? fqn : acct.split("@")[0] + "@" + getDomainFromURL();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,6 +239,7 @@ public class Account extends BaseModel implements Searchable{
|
|||||||
", source="+source+
|
", source="+source+
|
||||||
", suspended="+suspended+
|
", suspended="+suspended+
|
||||||
", muteExpiresAt="+muteExpiresAt+
|
", muteExpiresAt="+muteExpiresAt+
|
||||||
|
", noindex="+noindex+
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,26 +46,26 @@ public class Attachment extends BaseModel{
|
|||||||
|
|
||||||
public int getWidth(){
|
public int getWidth(){
|
||||||
if(meta==null)
|
if(meta==null)
|
||||||
return 0;
|
return 1920;
|
||||||
if(meta.width>0)
|
if(meta.width>0)
|
||||||
return meta.width;
|
return meta.width;
|
||||||
if(meta.original!=null && meta.original.width>0)
|
if(meta.original!=null && meta.original.width>0)
|
||||||
return meta.original.width;
|
return meta.original.width;
|
||||||
if(meta.small!=null && meta.small.width>0)
|
if(meta.small!=null && meta.small.width>0)
|
||||||
return meta.small.width;
|
return meta.small.width;
|
||||||
return 0;
|
return 1920;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getHeight(){
|
public int getHeight(){
|
||||||
if(meta==null)
|
if(meta==null)
|
||||||
return 0;
|
return 1080;
|
||||||
if(meta.height>0)
|
if(meta.height>0)
|
||||||
return meta.height;
|
return meta.height;
|
||||||
if(meta.original!=null && meta.original.height>0)
|
if(meta.original!=null && meta.original.height>0)
|
||||||
return meta.original.height;
|
return meta.original.height;
|
||||||
if(meta.small!=null && meta.small.height>0)
|
if(meta.small!=null && meta.small.height>0)
|
||||||
return meta.small.height;
|
return meta.small.height;
|
||||||
return 0;
|
return 1080;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getDuration(){
|
public double getDuration(){
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.joinmastodon.android.model;
|
package org.joinmastodon.android.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class EmojiCategory{
|
public class EmojiCategory{
|
||||||
@@ -10,4 +11,8 @@ public class EmojiCategory{
|
|||||||
this.title=title;
|
this.title=title;
|
||||||
this.emojis=emojis;
|
this.emojis=emojis;
|
||||||
}
|
}
|
||||||
|
public EmojiCategory(EmojiCategory category){
|
||||||
|
this.title = category.title;
|
||||||
|
this.emojis = new ArrayList<>(category.emojis);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public class Hashtag extends BaseModel implements DisplayItemsParent{
|
|||||||
", following="+following+
|
", following="+following+
|
||||||
", history="+history+
|
", history="+history+
|
||||||
", statusesCount="+statusesCount+
|
", statusesCount="+statusesCount+
|
||||||
|
", following="+following+
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,4 +31,19 @@ public class Hashtag extends BaseModel implements DisplayItemsParent{
|
|||||||
public String getID(){
|
public String getID(){
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o){
|
||||||
|
if(this==o) return true;
|
||||||
|
if(o==null || getClass()!=o.getClass()) return false;
|
||||||
|
|
||||||
|
Hashtag hashtag=(Hashtag) o;
|
||||||
|
|
||||||
|
return name.equals(hashtag.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode(){
|
||||||
|
return name.hashCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,4 +20,22 @@ public class Mention extends BaseModel{
|
|||||||
", url='"+url+'\''+
|
", url='"+url+'\''+
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o){
|
||||||
|
if(this==o) return true;
|
||||||
|
if(o==null || getClass()!=o.getClass()) return false;
|
||||||
|
|
||||||
|
Mention mention=(Mention) o;
|
||||||
|
|
||||||
|
if(!id.equals(mention.id)) return false;
|
||||||
|
return url.equals(mention.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode(){
|
||||||
|
int result=id.hashCode();
|
||||||
|
result=31*result+url.hashCode();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ public class Source extends BaseModel{
|
|||||||
* The number of pending follow requests.
|
* The number of pending follow requests.
|
||||||
*/
|
*/
|
||||||
public int followRequestCount;
|
public int followRequestCount;
|
||||||
|
public Boolean indexable;
|
||||||
|
public boolean hideCollections;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postprocess() throws ObjectValidationException{
|
public void postprocess() throws ObjectValidationException{
|
||||||
@@ -54,6 +56,8 @@ public class Source extends BaseModel{
|
|||||||
", sensitive="+sensitive+
|
", sensitive="+sensitive+
|
||||||
", language='"+language+'\''+
|
", language='"+language+'\''+
|
||||||
", followRequestCount="+followRequestCount+
|
", followRequestCount="+followRequestCount+
|
||||||
|
", indexable="+indexable+
|
||||||
|
", hideCollections="+hideCollections+
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,21 @@ import static org.joinmastodon.android.api.MastodonAPIController.gsonWithoutDese
|
|||||||
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.ObjectValidationException;
|
||||||
|
import org.joinmastodon.android.api.RequiredField;
|
||||||
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
|
import org.parceler.Parcel;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.github.bottomSoftwareFoundation.bottom.Bottom;
|
||||||
|
import com.github.bottomSoftwareFoundation.bottom.TranslationError;
|
||||||
import com.google.gson.JsonDeserializationContext;
|
import com.google.gson.JsonDeserializationContext;
|
||||||
import com.google.gson.JsonDeserializer;
|
import com.google.gson.JsonDeserializer;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
@@ -15,16 +28,21 @@ import com.google.gson.JsonParseException;
|
|||||||
|
|
||||||
import org.joinmastodon.android.api.ObjectValidationException;
|
import org.joinmastodon.android.api.ObjectValidationException;
|
||||||
import org.joinmastodon.android.api.RequiredField;
|
import org.joinmastodon.android.api.RequiredField;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
|
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
|
import org.joinmastodon.android.utils.StatusTextEncoder;
|
||||||
import org.parceler.Parcel;
|
import org.parceler.Parcel;
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@Parcel
|
@Parcel
|
||||||
public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
||||||
@@ -83,9 +101,9 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
|||||||
public transient boolean sensitiveRevealed;
|
public transient boolean sensitiveRevealed;
|
||||||
public transient boolean textExpanded, textExpandable;
|
public transient boolean textExpanded, textExpandable;
|
||||||
public transient boolean hasGapAfter;
|
public transient boolean hasGapAfter;
|
||||||
public transient TranslatedStatus translation;
|
|
||||||
public transient boolean translationShown;
|
|
||||||
private transient String strippedText;
|
private transient String strippedText;
|
||||||
|
public transient TranslationState translationState=TranslationState.HIDDEN;
|
||||||
|
public transient Translation translation;
|
||||||
|
|
||||||
public Status(){}
|
public Status(){}
|
||||||
|
|
||||||
@@ -201,6 +219,38 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
|||||||
return (Status) super.clone();
|
return (Status) super.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final Pattern BOTTOM_TEXT_PATTERN = Pattern.compile("(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️)(?:\uD83D\uDC49\uD83D\uDC48(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️))*\uD83D\uDC49\uD83D\uDC48");
|
||||||
|
public boolean isEligibleForTranslation(AccountSession session){
|
||||||
|
Instance instanceInfo = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
|
||||||
|
boolean translateEnabled = instanceInfo != null &&
|
||||||
|
instanceInfo.v2 != null && instanceInfo.v2.configuration.translation != null &&
|
||||||
|
instanceInfo.v2.configuration.translation.enabled;
|
||||||
|
|
||||||
|
try {
|
||||||
|
String bottomText = BOTTOM_TEXT_PATTERN.matcher(getStrippedText()).find()
|
||||||
|
? new StatusTextEncoder(Bottom::decode).decode(getStrippedText(), BOTTOM_TEXT_PATTERN)
|
||||||
|
: null;
|
||||||
|
if(bottomText==null || bottomText.length()==0 || bottomText.equals("\u0005")) bottomText=null;
|
||||||
|
if(bottomText!=null){
|
||||||
|
translation=new Translation();
|
||||||
|
translation.content=bottomText;
|
||||||
|
translation.detectedSourceLanguage="\uD83E\uDD7A\uD83D\uDC49\uD83D\uDC48";
|
||||||
|
translation.provider="bottom-java";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (TranslationError ignored) {}
|
||||||
|
|
||||||
|
return translateEnabled && !TextUtils.isEmpty(content) && !TextUtils.isEmpty(language)
|
||||||
|
&& !Objects.equals(Locale.getDefault().getLanguage(), language)
|
||||||
|
&& (visibility==StatusPrivacy.PUBLIC || visibility==StatusPrivacy.UNLISTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TranslationState{
|
||||||
|
HIDDEN,
|
||||||
|
SHOWN,
|
||||||
|
LOADING
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isReblogPermitted(String accountID){
|
public boolean isReblogPermitted(String accountID){
|
||||||
return visibility.isReblogPermitted(account.id.equals(
|
return visibility.isReblogPermitted(account.id.equals(
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).self.id
|
AccountSessionManager.getInstance().getAccount(accountID).self.id
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ public class TimelineDefinition {
|
|||||||
args.putString("listID", listId);
|
args.putString("listID", listId);
|
||||||
args.putBoolean("listIsExclusive", listIsExclusive);
|
args.putBoolean("listIsExclusive", listIsExclusive);
|
||||||
} else if (type == TimelineType.HASHTAG) {
|
} else if (type == TimelineType.HASHTAG) {
|
||||||
args.putString("hashtag", hashtagName);
|
args.putString("hashtagName", hashtagName);
|
||||||
args.putBoolean("localOnly", hashtagLocalOnly);
|
args.putBoolean("localOnly", hashtagLocalOnly);
|
||||||
args.putStringArrayList("any", hashtagAny == null ? new ArrayList<>() : new ArrayList<>(hashtagAny));
|
args.putStringArrayList("any", hashtagAny == null ? new ArrayList<>() : new ArrayList<>(hashtagAny));
|
||||||
args.putStringArrayList("all", hashtagAll == null ? new ArrayList<>() : new ArrayList<>(hashtagAll));
|
args.putStringArrayList("all", hashtagAll == null ? new ArrayList<>() : new ArrayList<>(hashtagAll));
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
package org.joinmastodon.android.model;
|
|
||||||
|
|
||||||
public class TranslatedStatus extends BaseModel {
|
|
||||||
public String content;
|
|
||||||
public String detectedSourceLanguage;
|
|
||||||
public String provider;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.joinmastodon.android.model;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
||||||
|
|
||||||
|
@AllFieldsAreRequired
|
||||||
|
public class Translation extends BaseModel{
|
||||||
|
public String content;
|
||||||
|
public String detectedSourceLanguage;
|
||||||
|
public String provider;
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
package org.joinmastodon.android.model.viewmodel;
|
package org.joinmastodon.android.model.viewmodel;
|
||||||
|
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.AccountField;
|
import org.joinmastodon.android.model.AccountField;
|
||||||
@@ -24,9 +26,13 @@ public class AccountViewModel{
|
|||||||
|
|
||||||
public AccountViewModel(Account account, String accountID){
|
public AccountViewModel(Account account, String accountID){
|
||||||
this.account=account;
|
this.account=account;
|
||||||
avaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(50), V.dp(50));
|
AccountSession session = AccountSessionManager.get(accountID);
|
||||||
|
avaRequest=new UrlImageLoaderRequest(
|
||||||
|
TextUtils.isEmpty(account.avatar) ? session.getDefaultAvatarUrl() :
|
||||||
|
GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic,
|
||||||
|
V.dp(50), V.dp(50));
|
||||||
emojiHelper=new CustomEmojiHelper();
|
emojiHelper=new CustomEmojiHelper();
|
||||||
if(AccountSessionManager.get(accountID).getLocalPreferences().customEmojiInNames)
|
if(session.getLocalPreferences().customEmojiInNames)
|
||||||
parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
|
parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
|
||||||
else
|
else
|
||||||
parsedName=account.displayName;
|
parsedName=account.displayName;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.graphics.drawable.Animatable;
|
|||||||
import android.graphics.drawable.ColorDrawable;
|
import android.graphics.drawable.ColorDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
@@ -39,6 +40,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import me.grishka.appkit.FragmentStackActivity;
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
@@ -144,21 +146,10 @@ public class AccountSwitcherSheet extends BottomSheet{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void logOut(String accountID){
|
private void logOut(String accountID){
|
||||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
AccountSessionManager.get(accountID).logOut(activity, ()->{
|
||||||
new RevokeOauthToken(session.app.clientId, session.app.clientSecret, session.token.accessToken)
|
dismiss();
|
||||||
.setCallback(new Callback<>(){
|
((MainActivity)activity).restartHomeFragment();
|
||||||
@Override
|
});
|
||||||
public void onSuccess(Object result){
|
|
||||||
onLoggedOut(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
onLoggedOut(accountID);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.wrapProgress(activity, R.string.loading, false)
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void logOutAll(){
|
private void logOutAll(){
|
||||||
@@ -326,15 +317,15 @@ public class AccountSwitcherSheet extends BottomSheet{
|
|||||||
@Override
|
@Override
|
||||||
public void onClick(){
|
public void onClick(){
|
||||||
setOnDismissListener(null);
|
setOnDismissListener(null);
|
||||||
if (onClick != null) {
|
|
||||||
dismiss();
|
dismiss();
|
||||||
|
if (onClick != null) {
|
||||||
onClick.accept(item.getID(), false);
|
onClick.accept(item.getID(), false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(AccountSessionManager.getInstance().tryGetAccount(item.getID())!=null)
|
if(AccountSessionManager.getInstance().tryGetAccount(item.getID())!=null){
|
||||||
AccountSessionManager.getInstance().setLastActiveAccountID(item.getID());
|
AccountSessionManager.getInstance().setLastActiveAccountID(item.getID());
|
||||||
activity.finish();
|
((MainActivity)activity).restartHomeFragment();
|
||||||
activity.startActivity(new Intent(activity, MainActivity.class));
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
import android.widget.Filter;
|
||||||
|
import android.widget.Filterable;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
@@ -34,6 +36,7 @@ import org.joinmastodon.android.model.Emoji;
|
|||||||
import org.joinmastodon.android.model.EmojiCategory;
|
import org.joinmastodon.android.model.EmojiCategory;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -139,43 +142,52 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
|
|||||||
|
|
||||||
ll.addView(list, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
|
ll.addView(list, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
|
||||||
|
|
||||||
FrameLayout bottomPanel=new FrameLayout(activity);
|
|
||||||
bottomPanel.setPadding(V.dp(16), V.dp(8), V.dp(16), V.dp(8));
|
|
||||||
bottomPanel.setBackgroundResource(R.drawable.bg_m3_surface2);
|
|
||||||
ll.addView(bottomPanel, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
|
||||||
|
|
||||||
if(forReaction){
|
if(forReaction){
|
||||||
InputMethodManager imm=(InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
FrameLayout topPanel=new FrameLayout(activity);
|
||||||
|
topPanel.setPadding(V.dp(16), V.dp(12), V.dp(16), V.dp(12));
|
||||||
|
topPanel.setBackgroundResource(R.drawable.bg_m3_surface2);
|
||||||
|
ll.addView(topPanel, 0, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||||
|
|
||||||
// TODO: support filtering custom emoji
|
InputMethodManager imm=(InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
EditText input=new EditText(activity);
|
EditText input=new EditText(activity);
|
||||||
input.setHint(R.string.sk_enter_emoji_hint);
|
input.setHint(R.string.sk_enter_emoji_hint);
|
||||||
input.addTextChangedListener(new TextWatcher() {
|
input.addTextChangedListener(new TextWatcher() {
|
||||||
@Override
|
@Override
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count){
|
public void onTextChanged(CharSequence s, int start, int before, int count){
|
||||||
if (!s.toString().isEmpty()) {
|
// Only check the emoji regex if the text field was empty before
|
||||||
|
if(start == 0){
|
||||||
if(emojiRegex.matcher(s.toString()).find()){
|
if(emojiRegex.matcher(s.toString()).find()){
|
||||||
imm.hideSoftInputFromWindow(input.getWindowToken(), 0);
|
imm.hideSoftInputFromWindow(input.getWindowToken(), 0);
|
||||||
listener.onEmojiSelected(s.toString().substring(before));
|
listener.onEmojiSelected(s.toString().substring(before));
|
||||||
input.getText().clear();
|
input.getText().clear();
|
||||||
} else {
|
|
||||||
Toast.makeText(activity, R.string.sk_enter_emoji_toast, Toast.LENGTH_SHORT).show();
|
|
||||||
input.getText().clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for(int i=0; i<adapter.getAdapterCount(); i++){
|
||||||
|
SingleCategoryAdapter currentAdapter=(SingleCategoryAdapter) adapter.getAdapterAt(i);
|
||||||
|
currentAdapter.getFilter().filter(s.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||||
@Override public void afterTextChanged(Editable s) {}
|
@Override public void afterTextChanged(Editable s) {}
|
||||||
});
|
});
|
||||||
|
input.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener(){
|
||||||
|
@Override
|
||||||
|
public void onViewAttachedToWindow(@NonNull View view){}
|
||||||
|
|
||||||
FrameLayout.LayoutParams params=new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START);
|
@Override
|
||||||
int pad=forReaction ? 0 : V.dp(36 + 16);
|
public void onViewDetachedFromWindow(@NonNull View view){
|
||||||
params.setMargins(pad, V.dp(8), pad, V.dp(8));
|
input.getText().clear();
|
||||||
bottomPanel.addView(input, params);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
topPanel.addView(input);
|
||||||
|
|
||||||
|
}else{ // in compose fragment
|
||||||
|
FrameLayout bottomPanel=new FrameLayout(activity);
|
||||||
|
bottomPanel.setPadding(V.dp(16), V.dp(8), V.dp(16), V.dp(8));
|
||||||
|
bottomPanel.setBackgroundResource(R.drawable.bg_m3_surface2);
|
||||||
|
ll.addView(bottomPanel, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||||
|
|
||||||
if(!forReaction){
|
|
||||||
ImageButton hideKeyboard=new ImageButton(activity);
|
ImageButton hideKeyboard=new ImageButton(activity);
|
||||||
hideKeyboard.setImageResource(R.drawable.ic_fluent_keyboard_dock_24_regular);
|
hideKeyboard.setImageResource(R.drawable.ic_fluent_keyboard_dock_24_regular);
|
||||||
hideKeyboard.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(activity, R.attr.colorM3OnSurfaceVariant)));
|
hideKeyboard.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(activity, R.attr.colorM3OnSurfaceVariant)));
|
||||||
@@ -207,13 +219,16 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SingleCategoryAdapter extends UsableRecyclerView.Adapter<RecyclerView.ViewHolder> implements ImageLoaderRecyclerAdapter{
|
private class SingleCategoryAdapter extends UsableRecyclerView.Adapter<RecyclerView.ViewHolder> implements ImageLoaderRecyclerAdapter, Filterable{
|
||||||
private final EmojiCategory category;
|
private EmojiCategory category;
|
||||||
private final List<ImageLoaderRequest> requests;
|
|
||||||
|
private final EmojiCategory originalCategory;
|
||||||
|
private List<ImageLoaderRequest> requests;
|
||||||
|
|
||||||
public SingleCategoryAdapter(EmojiCategory category){
|
public SingleCategoryAdapter(EmojiCategory category){
|
||||||
super(imgLoader);
|
super(imgLoader);
|
||||||
this.category=category;
|
this.category=category;
|
||||||
|
this.originalCategory=new EmojiCategory(category);
|
||||||
requests=category.emojis.stream().map(e->new UrlImageLoaderRequest(e.getUrl(playGifs), V.dp(24), V.dp(24))).collect(Collectors.toList());
|
requests=category.emojis.stream().map(e->new UrlImageLoaderRequest(e.getUrl(playGifs), V.dp(24), V.dp(24))).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,17 +240,22 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position){
|
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position){
|
||||||
|
if(category.emojis.size() == 0) {
|
||||||
|
holder.itemView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
if(holder instanceof EmojiViewHolder evh){
|
if(holder instanceof EmojiViewHolder evh){
|
||||||
evh.bind(category.emojis.get(position-1));
|
evh.bind(category.emojis.get(position-1));
|
||||||
evh.positionWithinCategory=position-1;
|
evh.positionWithinCategory=position-1;
|
||||||
}else if(holder instanceof SectionHeaderViewHolder shvh){
|
}else if(holder instanceof SectionHeaderViewHolder shvh){
|
||||||
shvh.bind(TextUtils.isEmpty(category.title) ? domain : category.title);
|
shvh.bind(TextUtils.isEmpty(category.title) ? domain : category.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
super.onBindViewHolder(holder, position);
|
super.onBindViewHolder(holder, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount(){
|
public int getItemCount(){
|
||||||
|
if(category.emojis.size() == 0) return 0;
|
||||||
return category.emojis.size()+1;
|
return category.emojis.size()+1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,6 +273,39 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
|
|||||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||||
return requests.get(position-1);
|
return requests.get(position-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Filter getFilter(){
|
||||||
|
return emojiFilter;
|
||||||
|
}
|
||||||
|
private final Filter emojiFilter = new Filter(){
|
||||||
|
@Override
|
||||||
|
protected FilterResults performFiltering(CharSequence charSequence){
|
||||||
|
List<Emoji> filteredEmoji=new ArrayList<>();
|
||||||
|
String search=charSequence.toString().toLowerCase().trim();
|
||||||
|
|
||||||
|
if(charSequence==null || charSequence.length()==0){
|
||||||
|
filteredEmoji.addAll(originalCategory.emojis);
|
||||||
|
}else{
|
||||||
|
for(Emoji emoji : originalCategory.emojis){
|
||||||
|
if(emoji.shortcode.toLowerCase().contains(search)){
|
||||||
|
filteredEmoji.add(emoji);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
FilterResults results=new FilterResults();
|
||||||
|
results.values=filteredEmoji;
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected void publishResults(CharSequence charSequence, FilterResults filterResults){
|
||||||
|
category.emojis.clear();
|
||||||
|
category.emojis.addAll((List) filterResults.values);
|
||||||
|
requests=category.emojis.stream().map(e->new UrlImageLoaderRequest(e.getUrl(playGifs), V.dp(24), V.dp(24))).collect(Collectors.toList());
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SectionHeaderViewHolder extends BindableViewHolder<String> implements StickyHeadersOverlay.HeaderViewHolder{
|
private class SectionHeaderViewHolder extends BindableViewHolder<String> implements StickyHeadersOverlay.HeaderViewHolder{
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import android.widget.TextView;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
@@ -41,12 +42,13 @@ public class AccountCardStatusDisplayItem extends StatusDisplayItem{
|
|||||||
public CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
|
public CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
|
||||||
public CharSequence parsedName, parsedBio;
|
public CharSequence parsedName, parsedBio;
|
||||||
|
|
||||||
public AccountCardStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Account account, Notification notification){
|
public AccountCardStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, String accountID, Account account, Notification notification){
|
||||||
super(parentID, parentFragment);
|
super(parentID, parentFragment);
|
||||||
this.account=account;
|
this.account=account;
|
||||||
this.notification=notification;
|
this.notification=notification;
|
||||||
if(!TextUtils.isEmpty(account.avatar))
|
avaRequest=new UrlImageLoaderRequest(
|
||||||
avaRequest=new UrlImageLoaderRequest(account.avatar, V.dp(50), V.dp(50));
|
TextUtils.isEmpty(account.avatar) ? AccountSessionManager.get(accountID).getDefaultAvatarUrl() : account.avatar,
|
||||||
|
V.dp(50), V.dp(50));
|
||||||
if(!TextUtils.isEmpty(account.header))
|
if(!TextUtils.isEmpty(account.header))
|
||||||
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
|
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
|
||||||
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), parentFragment.getAccountID());
|
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), parentFragment.getAccountID());
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package org.joinmastodon.android.ui.displayitems;
|
package org.joinmastodon.android.ui.displayitems;
|
||||||
|
|
||||||
|
import static org.joinmastodon.android.ui.utils.UiUtils.opacityIn;
|
||||||
|
import static org.joinmastodon.android.ui.utils.UiUtils.opacityOut;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -12,8 +15,6 @@ import android.view.View;
|
|||||||
import android.view.ViewConfiguration;
|
import android.view.ViewConfiguration;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.accessibility.AccessibilityNodeInfo;
|
import android.view.accessibility.AccessibilityNodeInfo;
|
||||||
import android.view.animation.AlphaAnimation;
|
|
||||||
import android.view.animation.Animation;
|
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -56,7 +57,6 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||||||
public static class Holder extends StatusDisplayItem.Holder<FooterStatusDisplayItem>{
|
public static class Holder extends StatusDisplayItem.Holder<FooterStatusDisplayItem>{
|
||||||
private final TextView replies, boosts, favorites;
|
private final TextView replies, boosts, favorites;
|
||||||
private final View reply, boost, favorite, share, bookmark;
|
private final View reply, boost, favorite, share, bookmark;
|
||||||
private static final Animation opacityOut, opacityIn;
|
|
||||||
|
|
||||||
private View touchingView = null;
|
private View touchingView = null;
|
||||||
private boolean longClickPerformed = false;
|
private boolean longClickPerformed = false;
|
||||||
@@ -77,18 +77,6 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final float ALPHA_PRESSED=0.55f;
|
|
||||||
|
|
||||||
static {
|
|
||||||
opacityOut = new AlphaAnimation(1, ALPHA_PRESSED);
|
|
||||||
opacityOut.setDuration(300);
|
|
||||||
opacityOut.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
|
||||||
opacityOut.setFillAfter(true);
|
|
||||||
opacityIn = new AlphaAnimation(ALPHA_PRESSED, 1);
|
|
||||||
opacityIn.setDuration(400);
|
|
||||||
opacityIn.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Holder(Activity activity, ViewGroup parent){
|
public Holder(Activity activity, ViewGroup parent){
|
||||||
super(activity, R.layout.display_item_footer, parent);
|
super(activity, R.layout.display_item_footer, parent);
|
||||||
|
|
||||||
|
|||||||
@@ -8,14 +8,21 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.drawables.SawtoothTearDrawable;
|
import org.joinmastodon.android.ui.drawables.SawtoothTearDrawable;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
// Mind the gap!
|
|
||||||
public class GapStatusDisplayItem extends StatusDisplayItem{
|
public class GapStatusDisplayItem extends StatusDisplayItem{
|
||||||
public boolean loading;
|
public boolean loading;
|
||||||
|
private Status status;
|
||||||
|
|
||||||
public GapStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment){
|
public GapStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, Status status){
|
||||||
super(parentID, parentFragment);
|
super(parentID, parentFragment);
|
||||||
|
this.status=status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -24,25 +31,53 @@ public class GapStatusDisplayItem extends StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class Holder extends StatusDisplayItem.Holder<GapStatusDisplayItem>{
|
public static class Holder extends StatusDisplayItem.Holder<GapStatusDisplayItem>{
|
||||||
public final ProgressBar progress;
|
public final ProgressBar progressTop, progressBottom;
|
||||||
public final TextView text;
|
public final TextView textTop, gap, textBottom;
|
||||||
|
public final View top, bottom;
|
||||||
|
|
||||||
public Holder(Context context, ViewGroup parent){
|
public Holder(Context context, ViewGroup parent){
|
||||||
super(context, R.layout.display_item_gap, parent);
|
super(context, R.layout.display_item_gap, parent);
|
||||||
progress=findViewById(R.id.progress);
|
progressTop=findViewById(R.id.progress_top);
|
||||||
text=findViewById(R.id.text);
|
progressBottom=findViewById(R.id.progress_bottom);
|
||||||
itemView.setForeground(new SawtoothTearDrawable(context));
|
textTop=findViewById(R.id.text_top);
|
||||||
|
textBottom=findViewById(R.id.text_bottom);
|
||||||
|
top=findViewById(R.id.top);
|
||||||
|
top.setOnClickListener(this::onViewClick);
|
||||||
|
bottom=findViewById(R.id.bottom);
|
||||||
|
bottom.setOnClickListener(this::onViewClick);
|
||||||
|
gap=findViewById(R.id.gap);
|
||||||
|
gap.setForeground(new SawtoothTearDrawable(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(GapStatusDisplayItem item){
|
public void onBind(GapStatusDisplayItem item){
|
||||||
text.setVisibility(item.loading ? View.GONE : View.VISIBLE);
|
if(!item.loading){
|
||||||
progress.setVisibility(item.loading ? View.VISIBLE : View.GONE);
|
progressBottom.setVisibility(View.GONE);
|
||||||
|
progressTop.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
top.setClickable(!item.loading);
|
||||||
|
bottom.setClickable(!item.loading);
|
||||||
|
StatusDisplayItem next=getNextVisibleDisplayItem().orElse(null);
|
||||||
|
Instant dateBelow=next instanceof HeaderStatusDisplayItem h ? h.status.createdAt
|
||||||
|
: next instanceof ReblogOrReplyLineStatusDisplayItem l ? l.status.createdAt
|
||||||
|
: null;
|
||||||
|
String text=dateBelow!=null && item.status.createdAt!=null && dateBelow.isBefore(item.status.createdAt)
|
||||||
|
? UiUtils.formatPeriodBetween(item.parentFragment.getContext(), dateBelow, item.status.createdAt)
|
||||||
|
: null;
|
||||||
|
gap.setText(text);
|
||||||
|
int p=text==null ? V.dp(6) : V.dp(20);
|
||||||
|
gap.setPadding(p, p, p, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onViewClick(View v){
|
||||||
|
if(item.loading) return;
|
||||||
|
boolean isTop=v==top;
|
||||||
|
(isTop ? textTop : textBottom).startAnimation(UiUtils.opacityOut);
|
||||||
|
V.setVisibilityAnimated((isTop ? progressTop : progressBottom), View.VISIBLE);
|
||||||
|
item.parentFragment.onGapClick(this, isTop);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(){
|
public void onClick(){}
|
||||||
item.parentFragment.onGapClick(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,10 +86,14 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
|
|
||||||
public HeaderStatusDisplayItem(String parentID, Account user, Instant createdAt, BaseStatusListFragment parentFragment, String accountID, Status status, CharSequence extraText, Notification notification, ScheduledStatus scheduledStatus){
|
public HeaderStatusDisplayItem(String parentID, Account user, Instant createdAt, BaseStatusListFragment parentFragment, String accountID, Status status, CharSequence extraText, Notification notification, ScheduledStatus scheduledStatus){
|
||||||
super(parentID, parentFragment);
|
super(parentID, parentFragment);
|
||||||
user=scheduledStatus != null ? AccountSessionManager.getInstance().getAccount(accountID).self : user;
|
AccountSession session = AccountSessionManager.get(accountID);
|
||||||
|
user=scheduledStatus != null ? session.self : user;
|
||||||
this.user=user;
|
this.user=user;
|
||||||
this.createdAt=createdAt;
|
this.createdAt=createdAt;
|
||||||
avaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? user.avatar : user.avatarStatic, V.dp(50), V.dp(50));
|
avaRequest=new UrlImageLoaderRequest(
|
||||||
|
TextUtils.isEmpty(user.avatar) ? session.getDefaultAvatarUrl() :
|
||||||
|
GlobalUserPreferences.playGifs ? user.avatar : user.avatarStatic,
|
||||||
|
V.dp(50), V.dp(50));
|
||||||
this.accountID=accountID;
|
this.accountID=accountID;
|
||||||
parsedName=new SpannableStringBuilder(user.displayName);
|
parsedName=new SpannableStringBuilder(user.displayName);
|
||||||
this.status=status;
|
this.status=status;
|
||||||
@@ -430,6 +434,8 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onAvaClick(View v){
|
private void onAvaClick(View v){
|
||||||
|
if (TextUtils.isEmpty(item.user.url))
|
||||||
|
return;
|
||||||
if (item.announcement != null) {
|
if (item.announcement != null) {
|
||||||
UiUtils.openURL(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.user.url);
|
UiUtils.openURL(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.user.url);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
import org.joinmastodon.android.model.Emoji;
|
import org.joinmastodon.android.model.Emoji;
|
||||||
@@ -53,7 +55,11 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
if(notification.type==Notification.Type.POLL){
|
if(notification.type==Notification.Type.POLL){
|
||||||
text=parentFragment.getString(R.string.poll_ended);
|
text=parentFragment.getString(R.string.poll_ended);
|
||||||
}else{
|
}else{
|
||||||
avaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? notification.account.avatar : notification.account.avatarStatic, V.dp(50), V.dp(50));
|
AccountSession session = AccountSessionManager.get(accountID);
|
||||||
|
avaRequest=new UrlImageLoaderRequest(
|
||||||
|
TextUtils.isEmpty(notification.account.avatar) ? session.getDefaultAvatarUrl() :
|
||||||
|
GlobalUserPreferences.playGifs ? notification.account.avatar : notification.account.avatarStatic,
|
||||||
|
V.dp(50), V.dp(50));
|
||||||
SpannableStringBuilder parsedName=new SpannableStringBuilder(notification.account.displayName);
|
SpannableStringBuilder parsedName=new SpannableStringBuilder(notification.account.displayName);
|
||||||
HtmlParser.parseCustomEmoji(parsedName, notification.account.emojis);
|
HtmlParser.parseCustomEmoji(parsedName, notification.account.emojis);
|
||||||
String str = parentFragment.getString(switch(notification.type){
|
String str = parentFragment.getString(switch(notification.type){
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
|
|||||||
progressBg=activity.getResources().getDrawable(R.drawable.bg_poll_option_voted, activity.getTheme()).mutate();
|
progressBg=activity.getResources().getDrawable(R.drawable.bg_poll_option_voted, activity.getTheme()).mutate();
|
||||||
progressBgInset=activity.getResources().getDrawable(R.drawable.bg_poll_option_voted_inset, activity.getTheme()).mutate();
|
progressBgInset=activity.getResources().getDrawable(R.drawable.bg_poll_option_voted_inset, activity.getTheme()).mutate();
|
||||||
itemView.setOnClickListener(this::onButtonClick);
|
itemView.setOnClickListener(this::onButtonClick);
|
||||||
button.setOutlineProvider(OutlineProviders.M3_BUTTON);
|
button.setOutlineProvider(OutlineProviders.roundedRect(20));
|
||||||
button.setClipToOutline(true);
|
button.setClipToOutline(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
import org.joinmastodon.android.model.Emoji;
|
import org.joinmastodon.android.model.Emoji;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.StatusPrivacy;
|
import org.joinmastodon.android.model.StatusPrivacy;
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||||
@@ -43,18 +45,21 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
|||||||
public boolean needBottomPadding;
|
public boolean needBottomPadding;
|
||||||
ReblogOrReplyLineStatusDisplayItem extra;
|
ReblogOrReplyLineStatusDisplayItem extra;
|
||||||
CharSequence fullText;
|
CharSequence fullText;
|
||||||
|
Status status;
|
||||||
|
|
||||||
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick) {
|
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, Status status) {
|
||||||
this(parentID, parentFragment, text, emojis, icon, visibility, handleClick, text);
|
this(parentID, parentFragment, text, emojis, icon, visibility, handleClick, text, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, CharSequence fullText) {
|
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, CharSequence fullText, Status status) {
|
||||||
super(parentID, parentFragment);
|
super(parentID, parentFragment);
|
||||||
SpannableStringBuilder ssb=new SpannableStringBuilder(text);
|
SpannableStringBuilder ssb=new SpannableStringBuilder(text);
|
||||||
|
if(AccountSessionManager.get(parentFragment.getAccountID()).getLocalPreferences().customEmojiInNames)
|
||||||
HtmlParser.parseCustomEmoji(ssb, emojis);
|
HtmlParser.parseCustomEmoji(ssb, emojis);
|
||||||
this.text=ssb;
|
this.text=ssb;
|
||||||
emojiHelper.setText(ssb);
|
emojiHelper.setText(ssb);
|
||||||
this.icon=icon;
|
this.icon=icon;
|
||||||
|
this.status=status;
|
||||||
this.handleClick=handleClick;
|
this.handleClick=handleClick;
|
||||||
TypedValue outValue = new TypedValue();
|
TypedValue outValue = new TypedValue();
|
||||||
context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
|
context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import android.graphics.drawable.LayerDrawable;
|
|||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
@@ -29,11 +29,13 @@ public class SpoilerStatusDisplayItem extends StatusDisplayItem{
|
|||||||
private final CharSequence parsedTitle;
|
private final CharSequence parsedTitle;
|
||||||
private final CustomEmojiHelper emojiHelper;
|
private final CustomEmojiHelper emojiHelper;
|
||||||
private final Type type;
|
private final Type type;
|
||||||
|
private final int attachmentCount;
|
||||||
|
|
||||||
public SpoilerStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, String title, Status statusForContent, Type type){
|
public SpoilerStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, String title, Status statusForContent, Type type){
|
||||||
super(parentID, parentFragment);
|
super(parentID, parentFragment);
|
||||||
this.status=statusForContent;
|
this.status=statusForContent;
|
||||||
this.type=type;
|
this.type=type;
|
||||||
|
this.attachmentCount=statusForContent.mediaAttachments.size();
|
||||||
if(TextUtils.isEmpty(title)){
|
if(TextUtils.isEmpty(title)){
|
||||||
parsedTitle=HtmlParser.parseCustomEmoji(statusForContent.spoilerText, statusForContent.emojis);
|
parsedTitle=HtmlParser.parseCustomEmoji(statusForContent.spoilerText, statusForContent.emojis);
|
||||||
emojiHelper=new CustomEmojiHelper();
|
emojiHelper=new CustomEmojiHelper();
|
||||||
@@ -62,12 +64,14 @@ public class SpoilerStatusDisplayItem extends StatusDisplayItem{
|
|||||||
public static class Holder extends StatusDisplayItem.Holder<SpoilerStatusDisplayItem> implements ImageLoaderViewHolder{
|
public static class Holder extends StatusDisplayItem.Holder<SpoilerStatusDisplayItem> implements ImageLoaderViewHolder{
|
||||||
private final TextView title, action;
|
private final TextView title, action;
|
||||||
private final View button;
|
private final View button;
|
||||||
|
private final ImageView mediaIcon;
|
||||||
|
|
||||||
public Holder(Context context, ViewGroup parent, Type type){
|
public Holder(Context context, ViewGroup parent, Type type){
|
||||||
super(context, R.layout.display_item_spoiler, parent);
|
super(context, R.layout.display_item_spoiler, parent);
|
||||||
title=findViewById(R.id.spoiler_title);
|
title=findViewById(R.id.spoiler_title);
|
||||||
action=findViewById(R.id.spoiler_action);
|
action=findViewById(R.id.spoiler_action);
|
||||||
button=findViewById(R.id.spoiler_button);
|
button=findViewById(R.id.spoiler_button);
|
||||||
|
mediaIcon=findViewById(R.id.media_icon);
|
||||||
|
|
||||||
button.setOutlineProvider(OutlineProviders.roundedRect(8));
|
button.setOutlineProvider(OutlineProviders.roundedRect(8));
|
||||||
button.setClipToOutline(true);
|
button.setClipToOutline(true);
|
||||||
@@ -94,6 +98,10 @@ public class SpoilerStatusDisplayItem extends StatusDisplayItem{
|
|||||||
itemView.getPaddingRight(),
|
itemView.getPaddingRight(),
|
||||||
item.inset ? itemView.getPaddingTop() : 0
|
item.inset ? itemView.getPaddingTop() : 0
|
||||||
);
|
);
|
||||||
|
mediaIcon.setVisibility(item.attachmentCount > 0 ? View.VISIBLE : View.GONE);
|
||||||
|
mediaIcon.setImageResource(item.attachmentCount > 1
|
||||||
|
? R.drawable.ic_fluent_image_multiple_24_regular
|
||||||
|
: R.drawable.ic_fluent_image_24_regular);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ import me.grishka.appkit.views.UsableRecyclerView;
|
|||||||
public abstract class StatusDisplayItem{
|
public abstract class StatusDisplayItem{
|
||||||
public final String parentID;
|
public final String parentID;
|
||||||
public final BaseStatusListFragment<?> parentFragment;
|
public final BaseStatusListFragment<?> parentFragment;
|
||||||
public boolean inset, insetPadding=true;
|
public boolean inset;
|
||||||
public int index;
|
public int index;
|
||||||
public boolean
|
public boolean
|
||||||
hasDescendantNeighbor = false,
|
hasDescendantNeighbor = false,
|
||||||
@@ -135,7 +135,7 @@ public abstract class StatusDisplayItem{
|
|||||||
: fragment.getString(R.string.in_reply_to, account.displayName);
|
: fragment.getString(R.string.in_reply_to, account.displayName);
|
||||||
return new ReblogOrReplyLineStatusDisplayItem(
|
return new ReblogOrReplyLineStatusDisplayItem(
|
||||||
parentID, fragment, text, account == null ? List.of() : account.emojis,
|
parentID, fragment, text, account == null ? List.of() : account.emojis,
|
||||||
R.drawable.ic_fluent_arrow_reply_20sp_filled, null, null, fullText
|
R.drawable.ic_fluent_arrow_reply_20sp_filled, null, null, fullText, status
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ public abstract class StatusDisplayItem{
|
|||||||
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, text, status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20sp_filled, isOwnPost ? status.visibility : null, i->{
|
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, text, status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20sp_filled, isOwnPost ? status.visibility : null, i->{
|
||||||
args.putParcelable("profileAccount", Parcels.wrap(status.account));
|
args.putParcelable("profileAccount", Parcels.wrap(status.account));
|
||||||
Nav.go(fragment.getActivity(), ProfileFragment.class, args);
|
Nav.go(fragment.getActivity(), ProfileFragment.class, args);
|
||||||
}, fullText));
|
}, fullText, status));
|
||||||
} else if (!(status.tags.isEmpty() ||
|
} else if (!(status.tags.isEmpty() ||
|
||||||
fragment instanceof HashtagTimelineFragment ||
|
fragment instanceof HashtagTimelineFragment ||
|
||||||
fragment instanceof ListTimelineFragment
|
fragment instanceof ListTimelineFragment
|
||||||
@@ -180,10 +180,8 @@ public abstract class StatusDisplayItem{
|
|||||||
.ifPresent(hashtag -> items.add(new ReblogOrReplyLineStatusDisplayItem(
|
.ifPresent(hashtag -> items.add(new ReblogOrReplyLineStatusDisplayItem(
|
||||||
parentID, fragment, hashtag.name, List.of(),
|
parentID, fragment, hashtag.name, List.of(),
|
||||||
R.drawable.ic_fluent_number_symbol_20sp_filled, null,
|
R.drawable.ic_fluent_number_symbol_20sp_filled, null,
|
||||||
i -> {
|
i->UiUtils.openHashtagTimeline(fragment.getActivity(), accountID, hashtag),
|
||||||
args.putString("hashtag", hashtag.name);
|
status
|
||||||
Nav.go(fragment.getActivity(), HashtagTimelineFragment.class, args);
|
|
||||||
}
|
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,7 +292,7 @@ public abstract class StatusDisplayItem{
|
|||||||
footer.hideCounts=hideCounts;
|
footer.hideCounts=hideCounts;
|
||||||
items.add(footer);
|
items.add(footer);
|
||||||
if(status.hasGapAfter && !(fragment instanceof ThreadFragment))
|
if(status.hasGapAfter && !(fragment instanceof ThreadFragment))
|
||||||
items.add(new GapStatusDisplayItem(parentID, fragment));
|
items.add(new GapStatusDisplayItem(parentID, fragment, status));
|
||||||
}
|
}
|
||||||
int i=1;
|
int i=1;
|
||||||
boolean inset=(flags & FLAG_INSET)!=0;
|
boolean inset=(flags & FLAG_INSET)!=0;
|
||||||
@@ -397,10 +395,11 @@ public abstract class StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Optional<StatusDisplayItem> getDisplayItemOffset(int offset){
|
public Optional<StatusDisplayItem> getDisplayItemOffset(int offset){
|
||||||
int nextPos=getAbsoluteAdapterPosition() + offset;
|
|
||||||
List<StatusDisplayItem> displayItems=item.parentFragment.getDisplayItems();
|
List<StatusDisplayItem> displayItems=item.parentFragment.getDisplayItems();
|
||||||
return displayItems.size() > nextPos
|
int thisPos=displayItems.indexOf(item);
|
||||||
? Optional.of(displayItems.get(nextPos))
|
int offsetPos=thisPos + offset;
|
||||||
|
return displayItems.size() > offsetPos && thisPos >= 0 && offsetPos >= 0
|
||||||
|
? Optional.of(displayItems.get(offsetPos))
|
||||||
: Optional.empty();
|
: Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,71 +1,53 @@
|
|||||||
package org.joinmastodon.android.ui.displayitems;
|
package org.joinmastodon.android.ui.displayitems;
|
||||||
|
|
||||||
|
import static org.joinmastodon.android.ui.utils.UiUtils.opacityIn;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.graphics.drawable.Animatable;
|
import android.graphics.drawable.Animatable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewStub;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
import android.widget.ScrollView;
|
import android.widget.ScrollView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.github.bottomSoftwareFoundation.bottom.Bottom;
|
|
||||||
import com.github.bottomSoftwareFoundation.bottom.TranslationError;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.statuses.TranslateStatus;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
|
||||||
import org.joinmastodon.android.model.Instance;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.StatusPrivacy;
|
import org.joinmastodon.android.model.Translation;
|
||||||
import org.joinmastodon.android.model.TranslatedStatus;
|
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.LinkedTextView;
|
import org.joinmastodon.android.ui.views.LinkedTextView;
|
||||||
import org.joinmastodon.android.utils.StatusTextEncoder;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import me.grishka.appkit.api.Callback;
|
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
import me.grishka.appkit.imageloader.MovieDrawable;
|
import me.grishka.appkit.imageloader.MovieDrawable;
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class TextStatusDisplayItem extends StatusDisplayItem{
|
public class TextStatusDisplayItem extends StatusDisplayItem{
|
||||||
private CharSequence text;
|
private CharSequence text;
|
||||||
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
|
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
|
||||||
|
private CharSequence translatedText;
|
||||||
|
private CustomEmojiHelper translationEmojiHelper=new CustomEmojiHelper();
|
||||||
public boolean textSelectable;
|
public boolean textSelectable;
|
||||||
public boolean reduceTopPadding;
|
public boolean reduceTopPadding;
|
||||||
|
public boolean disableTranslate;
|
||||||
public final Status status;
|
public final Status status;
|
||||||
public boolean disableTranslate, translationShown;
|
|
||||||
private AccountSession session;
|
|
||||||
public static final Pattern BOTTOM_TEXT_PATTERN = Pattern.compile("(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️)(?:\uD83D\uDC49\uD83D\uDC48(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️))*\uD83D\uDC49\uD83D\uDC48");
|
|
||||||
|
|
||||||
public TextStatusDisplayItem(String parentID, CharSequence text, BaseStatusListFragment parentFragment, Status status, boolean disableTranslate){
|
public TextStatusDisplayItem(String parentID, CharSequence text, BaseStatusListFragment parentFragment, Status status, boolean disableTranslate){
|
||||||
super(parentID, parentFragment);
|
super(parentID, parentFragment);
|
||||||
this.text=text;
|
this.text=text;
|
||||||
this.status=status;
|
this.status=status;
|
||||||
this.disableTranslate=disableTranslate;
|
this.disableTranslate=disableTranslate;
|
||||||
this.translationShown=status.translationShown;
|
|
||||||
emojiHelper.setText(text);
|
emojiHelper.setText(text);
|
||||||
session = AccountSessionManager.getInstance().getAccount(parentFragment.getAccountID());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTranslationShown(boolean translationShown) {
|
|
||||||
this.translationShown = translationShown;
|
|
||||||
status.translationShown = translationShown;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -75,38 +57,47 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getImageCount(){
|
public int getImageCount(){
|
||||||
return emojiHelper.getImageCount();
|
return getCurrentEmojiHelper().getImageCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ImageLoaderRequest getImageRequest(int index){
|
public ImageLoaderRequest getImageRequest(int index){
|
||||||
return emojiHelper.getImageRequest(index);
|
return getCurrentEmojiHelper().getImageRequest(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTranslatedText(String text){
|
||||||
|
Status statusForContent=status.getContentStatus();
|
||||||
|
translatedText=HtmlParser.parse(text, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, parentFragment.getAccountID());
|
||||||
|
translationEmojiHelper.setText(translatedText);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CustomEmojiHelper getCurrentEmojiHelper(){
|
||||||
|
return status.translationState==Status.TranslationState.SHOWN ? translationEmojiHelper : emojiHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Holder extends StatusDisplayItem.Holder<TextStatusDisplayItem> implements ImageLoaderViewHolder{
|
public static class Holder extends StatusDisplayItem.Holder<TextStatusDisplayItem> implements ImageLoaderViewHolder{
|
||||||
private final LinkedTextView text;
|
private final LinkedTextView text;
|
||||||
private final TextView translateInfo, readMore;
|
private final ViewStub translationFooterStub;
|
||||||
private final View textWrap, translateWrap, translateProgress;
|
private View translationFooter, translationButtonWrap;
|
||||||
private final Button translateButton;
|
private TextView translationInfo;
|
||||||
private final ScrollView textScrollView;
|
private Button translationButton;
|
||||||
|
private ProgressBar translationProgress;
|
||||||
|
|
||||||
private final float textMaxHeight, textCollapsedHeight;
|
private final float textMaxHeight;
|
||||||
private final LinearLayout.LayoutParams collapseParams, wrapParams;
|
private final LinearLayout.LayoutParams collapseParams, wrapParams;
|
||||||
private final ViewGroup parent;
|
private final ViewGroup parent;
|
||||||
|
private final TextView readMore;
|
||||||
|
private final ScrollView textScrollView;
|
||||||
|
|
||||||
public Holder(Activity activity, ViewGroup parent){
|
public Holder(Activity activity, ViewGroup parent){
|
||||||
super(activity, R.layout.display_item_text, parent);
|
super(activity, R.layout.display_item_text, parent);
|
||||||
this.parent=parent;
|
this.parent=parent;
|
||||||
text=findViewById(R.id.text);
|
text=findViewById(R.id.text);
|
||||||
textWrap = (LinearLayout) itemView;
|
translationFooterStub=findViewById(R.id.translation_info);
|
||||||
translateWrap=findViewById(R.id.translate_wrap);
|
|
||||||
translateButton=findViewById(R.id.translate_btn);
|
|
||||||
translateInfo=findViewById(R.id.translate_info);
|
|
||||||
translateProgress=findViewById(R.id.translate_progress);
|
|
||||||
textScrollView=findViewById(R.id.text_scroll_view);
|
textScrollView=findViewById(R.id.text_scroll_view);
|
||||||
readMore=findViewById(R.id.read_more);
|
readMore=findViewById(R.id.read_more);
|
||||||
textMaxHeight=activity.getResources().getDimension(R.dimen.text_max_height);
|
textMaxHeight=activity.getResources().getDimension(R.dimen.text_max_height);
|
||||||
textCollapsedHeight=activity.getResources().getDimension(R.dimen.text_collapsed_height);
|
float textCollapsedHeight=activity.getResources().getDimension(R.dimen.text_collapsed_height);
|
||||||
collapseParams=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) textCollapsedHeight);
|
collapseParams=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) textCollapsedHeight);
|
||||||
wrapParams=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
wrapParams=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
readMore.setOnClickListener(v -> item.parentFragment.onToggleExpanded(item.status, getItemID()));
|
readMore.setOnClickListener(v -> item.parentFragment.onToggleExpanded(item.status, getItemID()));
|
||||||
@@ -114,83 +105,20 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(TextStatusDisplayItem item){
|
public void onBind(TextStatusDisplayItem item){
|
||||||
boolean hasSpoiler = !TextUtils.isEmpty(item.status.spoilerText);
|
if(item.status.translationState==Status.TranslationState.SHOWN){
|
||||||
text.setText(item.translationShown
|
if(item.translatedText==null){
|
||||||
? HtmlParser.parse(item.status.translation.content, item.status.emojis, item.status.mentions, item.status.tags, item.parentFragment.getAccountID())
|
item.setTranslatedText(item.status.translation.content);
|
||||||
: item.text);
|
|
||||||
text.setTextIsSelectable(item.textSelectable);
|
|
||||||
if (item.textSelectable) {
|
|
||||||
textScrollView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
|
|
||||||
}
|
}
|
||||||
|
text.setText(item.translatedText);
|
||||||
|
}else{
|
||||||
|
text.setText(item.text);
|
||||||
|
}
|
||||||
|
text.setTextIsSelectable(item.textSelectable);
|
||||||
text.setInvalidateOnEveryFrame(false);
|
text.setInvalidateOnEveryFrame(false);
|
||||||
itemView.setClickable(false);
|
itemView.setClickable(false);
|
||||||
itemView.setPadding(itemView.getPaddingLeft(), item.reduceTopPadding ? V.dp(6) : V.dp(12), itemView.getPaddingRight(), itemView.getPaddingBottom());
|
itemView.setPadding(itemView.getPaddingLeft(), item.reduceTopPadding ? V.dp(6) : V.dp(12), itemView.getPaddingRight(), itemView.getPaddingBottom());
|
||||||
text.setTextColor(UiUtils.getThemeColor(text.getContext(), item.inset ? R.attr.colorM3OnSurfaceVariant : R.attr.colorM3OnSurface));
|
text.setTextColor(UiUtils.getThemeColor(text.getContext(), item.inset ? R.attr.colorM3OnSurfaceVariant : R.attr.colorM3OnSurface));
|
||||||
|
updateTranslation(false);
|
||||||
Instance instanceInfo = AccountSessionManager.getInstance().getInstanceInfo(item.session.domain);
|
|
||||||
boolean translateEnabled = !item.disableTranslate && instanceInfo != null &&
|
|
||||||
instanceInfo.v2 != null && instanceInfo.v2.configuration.translation != null &&
|
|
||||||
instanceInfo.v2.configuration.translation.enabled;
|
|
||||||
String bottomText = null;
|
|
||||||
try {
|
|
||||||
bottomText = BOTTOM_TEXT_PATTERN.matcher(item.status.getStrippedText()).find()
|
|
||||||
? new StatusTextEncoder(Bottom::decode).decode(item.status.getStrippedText(), BOTTOM_TEXT_PATTERN)
|
|
||||||
: null;
|
|
||||||
} catch (TranslationError ignored) {}
|
|
||||||
|
|
||||||
boolean translateVisible = (bottomText != null || (
|
|
||||||
translateEnabled &&
|
|
||||||
!item.status.visibility.isLessVisibleThan(StatusPrivacy.UNLISTED) &&
|
|
||||||
item.status.language != null &&
|
|
||||||
// todo: compare to mastodon locale instead (how do i query that?!)
|
|
||||||
!item.status.language.equalsIgnoreCase(Locale.getDefault().getLanguage())))
|
|
||||||
&& (!GlobalUserPreferences.translateButtonOpenedOnly || item.textSelectable);
|
|
||||||
translateWrap.setVisibility(translateVisible ? View.VISIBLE : View.GONE);
|
|
||||||
translateButton.setText(item.translationShown ? R.string.sk_translate_show_original : R.string.sk_translate_post);
|
|
||||||
translateInfo.setText(item.translationShown ? itemView.getResources().getString(R.string.sk_translated_using, bottomText != null ? "bottom-java" : item.status.translation.provider) : "");
|
|
||||||
String finalBottomText = bottomText;
|
|
||||||
translateButton.setOnClickListener(v->{
|
|
||||||
if (item.status.translation == null) {
|
|
||||||
if (finalBottomText != null) {
|
|
||||||
try {
|
|
||||||
item.status.translation = new TranslatedStatus();
|
|
||||||
item.status.translation.content = finalBottomText;
|
|
||||||
item.setTranslationShown(true);
|
|
||||||
} catch (TranslationError err) {
|
|
||||||
item.status.translation = null;
|
|
||||||
Toast.makeText(itemView.getContext(), err.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
rebind();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
translateProgress.setVisibility(View.VISIBLE);
|
|
||||||
translateButton.setClickable(false);
|
|
||||||
translateButton.animate().alpha(0.5f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start();
|
|
||||||
new TranslateStatus(item.status.id).setCallback(new Callback<>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(TranslatedStatus translatedStatus) {
|
|
||||||
item.status.translation = translatedStatus;
|
|
||||||
item.setTranslationShown(true);
|
|
||||||
if (item.parentFragment.getActivity() == null) return;
|
|
||||||
translateProgress.setVisibility(View.GONE);
|
|
||||||
translateButton.setClickable(true);
|
|
||||||
translateButton.animate().alpha(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(50).start();
|
|
||||||
rebind();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error) {
|
|
||||||
translateProgress.setVisibility(View.GONE);
|
|
||||||
translateButton.setClickable(true);
|
|
||||||
translateButton.animate().alpha(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(50).start();
|
|
||||||
error.showToast(itemView.getContext());
|
|
||||||
}
|
|
||||||
}).exec(item.parentFragment.getAccountID());
|
|
||||||
} else {
|
|
||||||
item.setTranslationShown(!item.translationShown);
|
|
||||||
rebind();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
readMore.setText(item.status.textExpanded ? R.string.sk_collapse : R.string.sk_expand);
|
readMore.setText(item.status.textExpanded ? R.string.sk_collapse : R.string.sk_expand);
|
||||||
|
|
||||||
@@ -224,18 +152,19 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
|
|
||||||
if (GlobalUserPreferences.collapseLongPosts && !item.status.textExpandable) {
|
if (GlobalUserPreferences.collapseLongPosts && !item.status.textExpandable) {
|
||||||
boolean tooBig = text.getMeasuredHeight() > textMaxHeight;
|
boolean tooBig = text.getMeasuredHeight() > textMaxHeight;
|
||||||
boolean expandable = tooBig && !hasSpoiler;
|
boolean expandable = tooBig && !item.status.hasSpoiler();
|
||||||
item.parentFragment.onEnableExpandable(Holder.this, expandable);
|
item.parentFragment.onEnableExpandable(Holder.this, expandable);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean expandButtonShown=item.status.textExpandable && !item.status.textExpanded;
|
boolean expandButtonShown=item.status.textExpandable && !item.status.textExpanded;
|
||||||
translateWrap.setPadding(0, V.dp(expandButtonShown ? 0 : 4), 0, 0);
|
if(translationFooter!=null)
|
||||||
|
translationFooter.setPadding(0, V.dp(expandButtonShown ? 0 : 4), 0, 0);
|
||||||
readMore.setVisibility(expandButtonShown ? View.VISIBLE : View.GONE);
|
readMore.setVisibility(expandButtonShown ? View.VISIBLE : View.GONE);
|
||||||
textScrollView.setLayoutParams(item.status.textExpandable && !item.status.textExpanded ? collapseParams : wrapParams);
|
textScrollView.setLayoutParams(item.status.textExpandable && !item.status.textExpanded ? collapseParams : wrapParams);
|
||||||
|
|
||||||
// compensate for spoiler's bottom margin
|
// compensate for spoiler's bottom margin
|
||||||
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) itemView.getLayoutParams();
|
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) itemView.getLayoutParams();
|
||||||
params.setMargins(params.leftMargin, item.inset && hasSpoiler ? V.dp(-16) : 0,
|
params.setMargins(params.leftMargin, item.inset && item.status.hasSpoiler() ? V.dp(-16) : 0,
|
||||||
params.rightMargin, params.bottomMargin);
|
params.rightMargin, params.bottomMargin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,5 +188,60 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
private CustomEmojiHelper getEmojiHelper(){
|
private CustomEmojiHelper getEmojiHelper(){
|
||||||
return item.emojiHelper;
|
return item.emojiHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateTranslation(boolean updateText){
|
||||||
|
if(item.status==null)
|
||||||
|
return;
|
||||||
|
boolean translateEnabled=!item.disableTranslate && item.status.isEligibleForTranslation(item.parentFragment.getSession());
|
||||||
|
if(translationFooter==null && translateEnabled){
|
||||||
|
translationFooter=translationFooterStub.inflate();
|
||||||
|
translationInfo=findViewById(R.id.translation_info_text);
|
||||||
|
translationButton=findViewById(R.id.translation_btn);
|
||||||
|
translationButtonWrap=findViewById(R.id.translation_btn_wrap);
|
||||||
|
translationProgress=findViewById(R.id.translation_progress);
|
||||||
|
translationButton.setOnClickListener(v->item.parentFragment.togglePostTranslation(item.status, item.parentID));
|
||||||
|
}
|
||||||
|
if(item.status.translationState==Status.TranslationState.HIDDEN){
|
||||||
|
if(updateText) text.setText(item.text);
|
||||||
|
if(translationFooter==null) return;
|
||||||
|
translationFooter.setVisibility(translateEnabled ? View.VISIBLE : View.GONE);
|
||||||
|
translationProgress.setVisibility(View.GONE);
|
||||||
|
Translation existingTrans=item.status.getContentStatus().translation;
|
||||||
|
String existingTransLang=existingTrans!=null ? existingTrans.detectedSourceLanguage : null;
|
||||||
|
String lang=existingTransLang!=null ? existingTransLang : item.status.getContentStatus().language;
|
||||||
|
String displayLang=Locale.forLanguageTag(lang != null ? lang
|
||||||
|
: AccountSessionManager.get(item.parentFragment.getAccountID()).preferences.postingDefaultLanguage).getDisplayLanguage();
|
||||||
|
translationButton.setText(item.parentFragment.getString(R.string.translate_post, !displayLang.isBlank() ? displayLang : lang));
|
||||||
|
translationButton.setEnabled(true);
|
||||||
|
translationButton.setAlpha(1);
|
||||||
|
translationInfo.setVisibility(View.GONE);
|
||||||
|
UiUtils.beginLayoutTransition((ViewGroup) translationButtonWrap);
|
||||||
|
}else{
|
||||||
|
translationFooter.setVisibility(View.VISIBLE);
|
||||||
|
if(item.status.translationState==Status.TranslationState.SHOWN){
|
||||||
|
translationProgress.setVisibility(View.GONE);
|
||||||
|
translationButton.setText(R.string.translation_show_original);
|
||||||
|
translationButton.setEnabled(true);
|
||||||
|
translationButton.setAlpha(1);
|
||||||
|
translationInfo.setVisibility(View.VISIBLE);
|
||||||
|
translationButton.setVisibility(View.VISIBLE);
|
||||||
|
String displayLang=Locale.forLanguageTag(item.status.translation.detectedSourceLanguage).getDisplayLanguage();
|
||||||
|
translationInfo.setText(translationInfo.getContext().getString(R.string.post_translated, !displayLang.isBlank() ? displayLang : item.status.translation.detectedSourceLanguage, item.status.translation.provider));
|
||||||
|
UiUtils.beginLayoutTransition((ViewGroup) translationButtonWrap);
|
||||||
|
if(updateText){
|
||||||
|
if(item.translatedText==null){
|
||||||
|
item.setTranslatedText(item.status.translation.content);
|
||||||
|
}
|
||||||
|
text.setText(item.translatedText);
|
||||||
|
}
|
||||||
|
}else{ // LOADING
|
||||||
|
translationProgress.setVisibility(View.VISIBLE);
|
||||||
|
translationButton.setEnabled(false);
|
||||||
|
translationButton.startAnimation(opacityIn);
|
||||||
|
translationInfo.setVisibility(View.INVISIBLE);
|
||||||
|
UiUtils.beginLayoutTransition((ViewGroup) translationButton.getParent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,15 +99,11 @@ public class BlurhashCrossfadeDrawable extends Drawable{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getIntrinsicWidth(){
|
public int getIntrinsicWidth(){
|
||||||
if(width==0)
|
|
||||||
return imageDrawable==null ? 1920 : imageDrawable.getIntrinsicWidth();
|
|
||||||
return width;
|
return width;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getIntrinsicHeight(){
|
public int getIntrinsicHeight(){
|
||||||
if(height==0)
|
|
||||||
return imageDrawable==null ? 1080 : imageDrawable.getIntrinsicHeight();
|
|
||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ import me.grishka.appkit.utils.V;
|
|||||||
public class SawtoothTearDrawable extends Drawable{
|
public class SawtoothTearDrawable extends Drawable{
|
||||||
private final Paint topPaint, bottomPaint;
|
private final Paint topPaint, bottomPaint;
|
||||||
|
|
||||||
private static final int TOP_SAWTOOTH_HEIGHT=5;
|
private static final int TOP_SAWTOOTH_HEIGHT=4;
|
||||||
private static final int BOTTOM_SAWTOOTH_HEIGHT=3;
|
private static final int BOTTOM_SAWTOOTH_HEIGHT=4;
|
||||||
private static final int STROKE_WIDTH=2;
|
private static final int STROKE_WIDTH=1;
|
||||||
private static final int SAWTOOTH_PERIOD=14;
|
private static final int SAWTOOTH_PERIOD=14;
|
||||||
|
|
||||||
public SawtoothTearDrawable(Context context){
|
public SawtoothTearDrawable(Context context){
|
||||||
|
|||||||
@@ -878,6 +878,8 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onVideoSizeChanged(MediaPlayer mp, int width, int height){
|
public void onVideoSizeChanged(MediaPlayer mp, int width, int height){
|
||||||
|
if(width<=0 || height<=0)
|
||||||
|
return;
|
||||||
FrameLayout.LayoutParams params=(FrameLayout.LayoutParams) wrap.getLayoutParams();
|
FrameLayout.LayoutParams params=(FrameLayout.LayoutParams) wrap.getLayoutParams();
|
||||||
params.width=width;
|
params.width=width;
|
||||||
params.height=height;
|
params.height=height;
|
||||||
|
|||||||
@@ -119,6 +119,9 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
|
|||||||
|
|
||||||
int width=right-left;
|
int width=right-left;
|
||||||
int height=bottom-top;
|
int height=bottom-top;
|
||||||
|
if(width==0 || height==0)
|
||||||
|
return;
|
||||||
|
|
||||||
float scale=Math.min(width/(float)child.getWidth(), height/(float)child.getHeight());
|
float scale=Math.min(width/(float)child.getWidth(), height/(float)child.getHeight());
|
||||||
minScale=scale;
|
minScale=scale;
|
||||||
maxScale=Math.max(3f, height/(float)child.getHeight());
|
maxScale=Math.max(3f, height/(float)child.getHeight());
|
||||||
@@ -306,8 +309,6 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
|
|||||||
}, 1f).setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_ALPHA));
|
}, 1f).setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_ALPHA));
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
if(animatingTransition)
|
|
||||||
Log.w(TAG, "updateViewTransform: ", new Throwable().fillInStackTrace());
|
|
||||||
child.setScaleX(matrixValues[Matrix.MSCALE_X]);
|
child.setScaleX(matrixValues[Matrix.MSCALE_X]);
|
||||||
child.setScaleY(matrixValues[Matrix.MSCALE_Y]);
|
child.setScaleY(matrixValues[Matrix.MSCALE_Y]);
|
||||||
child.setTranslationX(matrixValues[Matrix.MTRANS_X]);
|
child.setTranslationX(matrixValues[Matrix.MTRANS_X]);
|
||||||
|
|||||||
@@ -1715,7 +1715,9 @@ public class TabLayout extends HorizontalScrollView implements CustomViewHelper{
|
|||||||
child.getLayoutParams().height);
|
child.getLayoutParams().height);
|
||||||
|
|
||||||
int childWidthMeasureSpec =
|
int childWidthMeasureSpec =
|
||||||
MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
|
MeasureSpec.makeMeasureSpec(
|
||||||
|
getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
|
||||||
|
MeasureSpec.EXACTLY);
|
||||||
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
|
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,9 +100,10 @@ public class HtmlParser{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> idsByUrl=mentions.stream().collect(Collectors.toMap(m->m.url, m->m.id));
|
Map<String, String> idsByUrl=mentions.stream().distinct().collect(Collectors.toMap(m->m.url, m->m.id));
|
||||||
// Hashtags in remote posts have remote URLs, these have local URLs so they don't match.
|
// Hashtags in remote posts have remote URLs, these have local URLs so they don't match.
|
||||||
// Map<String, String> tagsByUrl=tags.stream().collect(Collectors.toMap(t->t.url, t->t.name));
|
// Map<String, String> tagsByUrl=tags.stream().collect(Collectors.toMap(t->t.url, t->t.name));
|
||||||
|
Map<String, Hashtag> tagsByTag=tags.stream().distinct().collect(Collectors.toMap(t->t.name.toLowerCase(), Function.identity()));
|
||||||
|
|
||||||
final SpannableStringBuilder ssb=new SpannableStringBuilder();
|
final SpannableStringBuilder ssb=new SpannableStringBuilder();
|
||||||
Jsoup.parseBodyFragment(source).body().traverse(new NodeVisitor(){
|
Jsoup.parseBodyFragment(source).body().traverse(new NodeVisitor(){
|
||||||
@@ -115,6 +116,7 @@ public class HtmlParser{
|
|||||||
}else if(node instanceof Element el){
|
}else if(node instanceof Element el){
|
||||||
switch(el.nodeName()){
|
switch(el.nodeName()){
|
||||||
case "a" -> {
|
case "a" -> {
|
||||||
|
Object linkObject=null;
|
||||||
String href=el.attr("href");
|
String href=el.attr("href");
|
||||||
LinkSpan.Type linkType;
|
LinkSpan.Type linkType;
|
||||||
String text=el.text();
|
String text=el.text();
|
||||||
@@ -122,6 +124,7 @@ public class HtmlParser{
|
|||||||
if(text.startsWith("#")){
|
if(text.startsWith("#")){
|
||||||
linkType=LinkSpan.Type.HASHTAG;
|
linkType=LinkSpan.Type.HASHTAG;
|
||||||
href=text.substring(1);
|
href=text.substring(1);
|
||||||
|
linkObject=tagsByTag.get(text.substring(1).toLowerCase());
|
||||||
}else{
|
}else{
|
||||||
linkType=LinkSpan.Type.URL;
|
linkType=LinkSpan.Type.URL;
|
||||||
}
|
}
|
||||||
@@ -136,7 +139,7 @@ public class HtmlParser{
|
|||||||
}else{
|
}else{
|
||||||
linkType=LinkSpan.Type.URL;
|
linkType=LinkSpan.Type.URL;
|
||||||
}
|
}
|
||||||
openSpans.add(new SpanInfo(new LinkSpan(href, null, linkType, accountID), ssb.length(), el));
|
openSpans.add(new SpanInfo(new LinkSpan(href, null, linkType, accountID, linkObject, text), ssb.length(), el));
|
||||||
}
|
}
|
||||||
case "br" -> ssb.append('\n');
|
case "br" -> ssb.append('\n');
|
||||||
case "span" -> {
|
case "span" -> {
|
||||||
@@ -271,7 +274,7 @@ public class HtmlParser{
|
|||||||
String url=matcher.group(3);
|
String url=matcher.group(3);
|
||||||
if(TextUtils.isEmpty(matcher.group(4)))
|
if(TextUtils.isEmpty(matcher.group(4)))
|
||||||
url="http://"+url;
|
url="http://"+url;
|
||||||
ssb.setSpan(new LinkSpan(url, null, LinkSpan.Type.URL, null), matcher.start(3), matcher.end(3), 0);
|
ssb.setSpan(new LinkSpan(url, null, LinkSpan.Type.URL, null, null, null), matcher.start(3), matcher.end(3), 0);
|
||||||
}while(matcher.find()); // Find more URLs
|
}while(matcher.find()); // Find more URLs
|
||||||
return ssb;
|
return ssb;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.text.TextPaint;
|
|||||||
import android.text.style.CharacterStyle;
|
import android.text.style.CharacterStyle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
public class LinkSpan extends CharacterStyle {
|
public class LinkSpan extends CharacterStyle {
|
||||||
@@ -14,17 +15,15 @@ public class LinkSpan extends CharacterStyle {
|
|||||||
private String link;
|
private String link;
|
||||||
private Type type;
|
private Type type;
|
||||||
private String accountID;
|
private String accountID;
|
||||||
|
private Object linkObject;
|
||||||
private String text;
|
private String text;
|
||||||
|
|
||||||
public LinkSpan(String link, OnLinkClickListener listener, Type type, String accountID){
|
public LinkSpan(String link, OnLinkClickListener listener, Type type, String accountID, Object linkObject, String text){
|
||||||
this(link, listener, type, accountID, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LinkSpan(String link, OnLinkClickListener listener, Type type, String accountID, String text){
|
|
||||||
this.listener=listener;
|
this.listener=listener;
|
||||||
this.link=link;
|
this.link=link;
|
||||||
this.type=type;
|
this.type=type;
|
||||||
this.accountID=accountID;
|
this.accountID=accountID;
|
||||||
|
this.linkObject=linkObject;
|
||||||
this.text=text;
|
this.text=text;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +41,12 @@ public class LinkSpan extends CharacterStyle {
|
|||||||
switch(getType()){
|
switch(getType()){
|
||||||
case URL -> UiUtils.openURL(context, accountID, link);
|
case URL -> UiUtils.openURL(context, accountID, link);
|
||||||
case MENTION -> UiUtils.openProfileByID(context, accountID, link);
|
case MENTION -> UiUtils.openProfileByID(context, accountID, link);
|
||||||
case HASHTAG -> UiUtils.openHashtagTimeline(context, accountID, link, null);
|
case HASHTAG -> {
|
||||||
|
if(linkObject instanceof Hashtag ht)
|
||||||
|
UiUtils.openHashtagTimeline(context, accountID, ht);
|
||||||
|
else
|
||||||
|
UiUtils.openHashtagTimeline(context, accountID, text);
|
||||||
|
}
|
||||||
case CUSTOM -> listener.onLinkClick(this);
|
case CUSTOM -> listener.onLinkClick(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import android.widget.TextView;
|
|||||||
import org.joinmastodon.android.MastodonApp;
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
|
||||||
@@ -44,8 +45,8 @@ public class DiscoverInfoBannerHelper{
|
|||||||
banner=((Activity)list.getContext()).getLayoutInflater().inflate(R.layout.discover_info_banner, list, false);
|
banner=((Activity)list.getContext()).getLayoutInflater().inflate(R.layout.discover_info_banner, list, false);
|
||||||
TextView text=banner.findViewById(R.id.banner_text);
|
TextView text=banner.findViewById(R.id.banner_text);
|
||||||
text.setText(switch(type){
|
text.setText(switch(type){
|
||||||
case TRENDING_POSTS -> list.getResources().getString(R.string.trending_posts_info_banner);
|
case TRENDING_POSTS -> list.getResources().getString(R.string.sk_trending_posts_info_banner);
|
||||||
case TRENDING_LINKS -> list.getResources().getString(R.string.trending_links_info_banner);
|
case TRENDING_LINKS -> list.getResources().getString(R.string.sk_trending_links_info_banner);
|
||||||
case FEDERATED_TIMELINE -> list.getResources().getString(R.string.sk_federated_timeline_info_banner);
|
case FEDERATED_TIMELINE -> list.getResources().getString(R.string.sk_federated_timeline_info_banner);
|
||||||
case POST_NOTIFICATIONS -> list.getResources().getString(R.string.sk_notify_posts_info_banner);
|
case POST_NOTIFICATIONS -> list.getResources().getString(R.string.sk_notify_posts_info_banner);
|
||||||
case BUBBLE_TIMELINE -> list.getResources().getString(R.string.sk_bubble_timeline_info_banner);
|
case BUBBLE_TIMELINE -> list.getResources().getString(R.string.sk_bubble_timeline_info_banner);
|
||||||
@@ -57,8 +58,10 @@ public class DiscoverInfoBannerHelper{
|
|||||||
case TRENDING_POSTS -> R.drawable.ic_fluent_arrow_trending_24_regular;
|
case TRENDING_POSTS -> R.drawable.ic_fluent_arrow_trending_24_regular;
|
||||||
case TRENDING_LINKS -> R.drawable.ic_fluent_news_24_regular;
|
case TRENDING_LINKS -> R.drawable.ic_fluent_news_24_regular;
|
||||||
case ACCOUNTS -> R.drawable.ic_fluent_people_add_24_regular;
|
case ACCOUNTS -> R.drawable.ic_fluent_people_add_24_regular;
|
||||||
// no icon because those are displayed as timelines - with icon in top left
|
case LOCAL_TIMELINE -> TimelineDefinition.LOCAL_TIMELINE.getDefaultIcon().iconRes;
|
||||||
case LOCAL_TIMELINE, FEDERATED_TIMELINE, BUBBLE_TIMELINE, POST_NOTIFICATIONS -> 0;
|
case FEDERATED_TIMELINE -> TimelineDefinition.FEDERATED_TIMELINE.getDefaultIcon().iconRes;
|
||||||
|
case BUBBLE_TIMELINE -> TimelineDefinition.BUBBLE_TIMELINE.getDefaultIcon().iconRes;
|
||||||
|
case POST_NOTIFICATIONS -> TimelineDefinition.POSTS_TIMELINE.getDefaultIcon().iconRes;
|
||||||
});
|
});
|
||||||
adapter.addAdapter(new SingleViewRecyclerAdapter(banner));
|
adapter.addAdapter(new SingleViewRecyclerAdapter(banner));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,23 +74,18 @@ public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||||
List<StatusDisplayItem> displayItems=listFragment.getDisplayItems();
|
// List<StatusDisplayItem> displayItems=listFragment.getDisplayItems();
|
||||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
|
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
|
||||||
if(holder instanceof StatusDisplayItem.Holder<?> sdi){
|
if(holder instanceof StatusDisplayItem.Holder<?> sdi){
|
||||||
boolean inset=sdi.getItem().inset;
|
boolean inset=sdi.getItem().inset;
|
||||||
int pos=holder.getAbsoluteAdapterPosition();
|
// int pos=holder.getAbsoluteAdapterPosition();
|
||||||
if(inset){
|
if(inset){
|
||||||
boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
|
// boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
|
||||||
boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
|
// boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
|
||||||
int pad;
|
|
||||||
// if(holder instanceof MediaGridStatusDisplayItem.Holder || holder instanceof LinkCardStatusDisplayItem.Holder)
|
// if(holder instanceof MediaGridStatusDisplayItem.Holder || holder instanceof LinkCardStatusDisplayItem.Holder)
|
||||||
pad=V.dp(16);
|
int pad=V.dp(16);
|
||||||
// else
|
// else pad=V.dp(12);
|
||||||
// pad=V.dp(12);
|
|
||||||
boolean insetPadding=((StatusDisplayItem.Holder<?>) holder).getItem().insetPadding;
|
|
||||||
if(insetPadding)
|
|
||||||
outRect.left=pad;
|
outRect.left=pad;
|
||||||
if(insetPadding)
|
|
||||||
outRect.right=pad;
|
outRect.right=pad;
|
||||||
|
|
||||||
// had to comment this out because animations with offsets aren't handled properly.
|
// had to comment this out because animations with offsets aren't handled properly.
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ import android.view.SubMenu;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
|
import android.view.animation.AlphaAnimation;
|
||||||
|
import android.view.animation.Animation;
|
||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
@@ -96,13 +98,13 @@ import org.joinmastodon.android.fragments.ComposeFragment;
|
|||||||
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
|
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||||
import org.joinmastodon.android.fragments.settings.SettingsServerAboutFragment;
|
|
||||||
import org.joinmastodon.android.fragments.settings.SettingsServerFragment;
|
import org.joinmastodon.android.fragments.settings.SettingsServerFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.AccountField;
|
import org.joinmastodon.android.model.AccountField;
|
||||||
import org.joinmastodon.android.model.Emoji;
|
import org.joinmastodon.android.model.Emoji;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
import org.joinmastodon.android.model.ScheduledStatus;
|
import org.joinmastodon.android.model.ScheduledStatus;
|
||||||
import org.joinmastodon.android.model.SearchResults;
|
import org.joinmastodon.android.model.SearchResults;
|
||||||
@@ -173,6 +175,19 @@ public class UiUtils {
|
|||||||
private static final DateTimeFormatter TIME_FORMATTER=DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT);
|
private static final DateTimeFormatter TIME_FORMATTER=DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT);
|
||||||
public static int MAX_WIDTH, SCROLL_TO_TOP_DELTA;
|
public static int MAX_WIDTH, SCROLL_TO_TOP_DELTA;
|
||||||
|
|
||||||
|
public static final float ALPHA_PRESSED=0.55f;
|
||||||
|
public static final Animation opacityOut, opacityIn;
|
||||||
|
|
||||||
|
static {
|
||||||
|
opacityOut = new AlphaAnimation(1, ALPHA_PRESSED);
|
||||||
|
opacityOut.setDuration(300);
|
||||||
|
opacityOut.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||||
|
opacityOut.setFillAfter(true);
|
||||||
|
opacityIn = new AlphaAnimation(ALPHA_PRESSED, 1);
|
||||||
|
opacityIn.setDuration(400);
|
||||||
|
opacityIn.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
private UiUtils() {
|
private UiUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,28 +207,42 @@ public class UiUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String formatRelativeTimestamp(Context context, Instant instant) {
|
public static String formatRelativeTimestamp(Context context, Instant instant) {
|
||||||
long t = instant.toEpochMilli();
|
return formatPeriodBetween(context, instant, null);
|
||||||
long now = System.currentTimeMillis();
|
}
|
||||||
|
|
||||||
|
public static String formatPeriodBetween(Context context, Instant since, Instant until) {
|
||||||
|
boolean ago = until == null;
|
||||||
|
long t = since.toEpochMilli();
|
||||||
|
long now = ago ? System.currentTimeMillis() : until.toEpochMilli();
|
||||||
long diff = now - t;
|
long diff = now - t;
|
||||||
if(diff<1000L){
|
if(diff<1000L){
|
||||||
return context.getString(R.string.time_now);
|
return context.getString(R.string.time_now);
|
||||||
}else if(diff<60_000L){
|
}else if(diff<60_000L){
|
||||||
return context.getString(R.string.time_seconds_ago_short, diff/1000L);
|
long time = diff/1000L;
|
||||||
|
return ago ?
|
||||||
|
context.getString(R.string.time_seconds_ago_short, time) :
|
||||||
|
context.getResources().getQuantityString(R.plurals.sk_time_seconds, (int) time, time);
|
||||||
}else if(diff<3600_000L){
|
}else if(diff<3600_000L){
|
||||||
return context.getString(R.string.time_minutes_ago_short, diff/60_000L);
|
long time = diff/60_000L;
|
||||||
|
return ago ?
|
||||||
|
context.getString(R.string.time_minutes_ago_short, time) :
|
||||||
|
context.getResources().getQuantityString(R.plurals.sk_time_minutes, (int) time, time);
|
||||||
}else if(diff<3600_000L*24L){
|
}else if(diff<3600_000L*24L){
|
||||||
return context.getString(R.string.time_hours_ago_short, diff/3600_000L);
|
long time = diff/3600_000L;
|
||||||
|
return ago ?
|
||||||
|
context.getString(R.string.time_hours_ago_short, time) :
|
||||||
|
context.getResources().getQuantityString(R.plurals.sk_time_hours, (int) time, time);
|
||||||
} else {
|
} else {
|
||||||
int days = (int) (diff / (3600_000L * 24L));
|
int days = (int) (diff / (3600_000L * 24L));
|
||||||
if (days > 30) {
|
if (ago && days > 30) {
|
||||||
ZonedDateTime dt = instant.atZone(ZoneId.systemDefault());
|
ZonedDateTime dt = since.atZone(ZoneId.systemDefault());
|
||||||
if (dt.getYear() == ZonedDateTime.now().getYear()) {
|
if (dt.getYear() == ZonedDateTime.now().getYear()) {
|
||||||
return DATE_FORMATTER_SHORT.format(dt);
|
return DATE_FORMATTER_SHORT.format(dt);
|
||||||
} else {
|
} else {
|
||||||
return DATE_FORMATTER_SHORT_WITH_YEAR.format(dt);
|
return DATE_FORMATTER_SHORT_WITH_YEAR.format(dt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return context.getString(R.string.time_days_ago_short, days);
|
return ago ? context.getString(R.string.time_days_ago_short, days) : context.getResources().getQuantityString(R.plurals.sk_time_days, days, days);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,11 +455,17 @@ public class UiUtils {
|
|||||||
Nav.go((Activity) context, ProfileFragment.class, args);
|
Nav.go((Activity) context, ProfileFragment.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void openHashtagTimeline(Context context, String accountID, String hashtag, @Nullable Boolean following) {
|
public static void openHashtagTimeline(Context context, String accountID, Hashtag hashtag){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putString("hashtag", hashtag);
|
args.putParcelable("hashtag", Parcels.wrap(hashtag));
|
||||||
if (following != null) args.putBoolean("following", following);
|
Nav.go((Activity)context, HashtagTimelineFragment.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openHashtagTimeline(Context context, String accountID, String hashtag){
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
args.putString("hashtagName", hashtag);
|
||||||
Nav.go((Activity)context, HashtagTimelineFragment.class, args);
|
Nav.go((Activity)context, HashtagTimelineFragment.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1138,7 +1173,7 @@ public class UiUtils {
|
|||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Optional.of(new GetSearchResults(query.getQuery(), type, true).setCallback(new Callback<>() {
|
return Optional.of(new GetSearchResults(query.getQuery(), type, true, null, 0, 0).setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(SearchResults results) {
|
public void onSuccess(SearchResults results) {
|
||||||
Optional<T> result = extractResult.apply(results);
|
Optional<T> result = extractResult.apply(results);
|
||||||
@@ -1235,7 +1270,7 @@ public class UiUtils {
|
|||||||
}
|
}
|
||||||
public static MastodonAPIRequest<SearchResults> lookupAccountHandle(Context context, String accountID, Pair<String, Optional<String>> queryHandle, BiConsumer<Class<? extends Fragment>, Bundle> go) {
|
public static MastodonAPIRequest<SearchResults> lookupAccountHandle(Context context, String accountID, Pair<String, Optional<String>> queryHandle, BiConsumer<Class<? extends Fragment>, Bundle> go) {
|
||||||
String fullHandle = ("@" + queryHandle.first) + (queryHandle.second.map(domain -> "@" + domain).orElse(""));
|
String fullHandle = ("@" + queryHandle.first) + (queryHandle.second.map(domain -> "@" + domain).orElse(""));
|
||||||
return new GetSearchResults(fullHandle, GetSearchResults.Type.ACCOUNTS, true)
|
return new GetSearchResults(fullHandle, GetSearchResults.Type.ACCOUNTS, true, null, 0, 0)
|
||||||
.setCallback(new Callback<>() {
|
.setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(SearchResults results) {
|
public void onSuccess(SearchResults results) {
|
||||||
@@ -1298,7 +1333,7 @@ public class UiUtils {
|
|||||||
})
|
})
|
||||||
.execNoAuth(uri.getHost()));
|
.execNoAuth(uri.getHost()));
|
||||||
} else if (looksLikeFediverseUrl(url)) {
|
} else if (looksLikeFediverseUrl(url)) {
|
||||||
return Optional.of(new GetSearchResults(url, null, true)
|
return Optional.of(new GetSearchResults(url, null, true, null, 0, 0)
|
||||||
.setCallback(new Callback<>() {
|
.setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(SearchResults results) {
|
public void onSuccess(SearchResults results) {
|
||||||
|
|||||||
@@ -15,15 +15,12 @@ import android.widget.TextView;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.Account;
|
|
||||||
import org.joinmastodon.android.model.Emoji;
|
import org.joinmastodon.android.model.Emoji;
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.model.SearchResults;
|
import org.joinmastodon.android.model.SearchResults;
|
||||||
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
|
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
|
||||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||||
import org.joinmastodon.android.ui.OutlineProviders;
|
import org.joinmastodon.android.ui.OutlineProviders;
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
|
||||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
|
||||||
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
|
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.FilterChipView;
|
import org.joinmastodon.android.ui.views.FilterChipView;
|
||||||
@@ -96,6 +93,24 @@ public class ComposeAutocompleteViewController{
|
|||||||
outRect.right=V.dp(8);
|
outRect.right=V.dp(8);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// Set empty adapter to prevent NPEs
|
||||||
|
list.setAdapter(new RecyclerView.Adapter<>(){
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount(){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
contentView.addView(list, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
contentView.addView(list, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
|
|
||||||
emptyButton=new FilterChipView(activity);
|
emptyButton=new FilterChipView(activity);
|
||||||
@@ -222,11 +237,13 @@ public class ComposeAutocompleteViewController{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void doSearchUsers(){
|
private void doSearchUsers(){
|
||||||
currentRequest=new GetSearchResults(lastText, GetSearchResults.Type.ACCOUNTS, false)
|
currentRequest=new GetSearchResults(lastText, GetSearchResults.Type.ACCOUNTS, false, null, 0, 0)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(SearchResults result){
|
public void onSuccess(SearchResults result){
|
||||||
currentRequest=null;
|
currentRequest=null;
|
||||||
|
if(mode!=Mode.USERS)
|
||||||
|
return;
|
||||||
List<AccountViewModel> oldList=users;
|
List<AccountViewModel> oldList=users;
|
||||||
users=result.accounts.stream().map(a->new AccountViewModel(a, accountID)).collect(Collectors.toList());
|
users=result.accounts.stream().map(a->new AccountViewModel(a, accountID)).collect(Collectors.toList());
|
||||||
if(isLoading){
|
if(isLoading){
|
||||||
@@ -256,7 +273,7 @@ public class ComposeAutocompleteViewController{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void doSearchHashtags(){
|
private void doSearchHashtags(){
|
||||||
currentRequest=new GetSearchResults(lastText, GetSearchResults.Type.HASHTAGS, false)
|
currentRequest=new GetSearchResults(lastText, GetSearchResults.Type.HASHTAGS, false, null, 0, 0)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(SearchResults result){
|
public void onSuccess(SearchResults result){
|
||||||
|
|||||||
@@ -607,7 +607,7 @@ public class ComposeMediaViewController{
|
|||||||
public void saveAltTextsBeforePublishing(Runnable onSuccess, Consumer<ErrorResponse> onError){
|
public void saveAltTextsBeforePublishing(Runnable onSuccess, Consumer<ErrorResponse> onError){
|
||||||
ArrayList<UpdateAttachment> updateAltTextRequests=new ArrayList<>();
|
ArrayList<UpdateAttachment> updateAltTextRequests=new ArrayList<>();
|
||||||
for(DraftMediaAttachment att:attachments){
|
for(DraftMediaAttachment att:attachments){
|
||||||
if(!att.descriptionSaved && att.serverAttachment.description == null){
|
if(!att.descriptionSaved && (fragment.editingStatus==null || !fragment.editingStatus.mediaAttachments.contains(att.serverAttachment))){
|
||||||
UpdateAttachment req=new UpdateAttachment(att.serverAttachment.id, att.description);
|
UpdateAttachment req=new UpdateAttachment(att.serverAttachment.id, att.description);
|
||||||
req.setCallback(new Callback<>(){
|
req.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -75,14 +75,14 @@ public class ComposePollViewController{
|
|||||||
|
|
||||||
Instance instance=fragment.instance;
|
Instance instance=fragment.instance;
|
||||||
if (!instance.isAkkoma()) {
|
if (!instance.isAkkoma()) {
|
||||||
if(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxOptions>0)
|
if(instance!=null && instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxOptions>0)
|
||||||
maxPollOptions=instance.configuration.polls.maxOptions;
|
maxPollOptions=instance.configuration.polls.maxOptions;
|
||||||
if(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxCharactersPerOption>0)
|
if(instance!=null && instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxCharactersPerOption>0)
|
||||||
maxPollOptionLength=instance.configuration.polls.maxCharactersPerOption;
|
maxPollOptionLength=instance.configuration.polls.maxCharactersPerOption;
|
||||||
} else {
|
} else {
|
||||||
if (instance.pollLimits!=null && instance.pollLimits.maxOptions>0)
|
if(instance!=null && instance.pollLimits!=null && instance.pollLimits.maxOptions>0)
|
||||||
maxPollOptions=instance.pollLimits.maxOptions;
|
maxPollOptions=instance.pollLimits.maxOptions;
|
||||||
if(instance.pollLimits!=null && instance.pollLimits.maxOptionChars>0)
|
if(instance!=null && instance.pollLimits!=null && instance.pollLimits.maxOptionChars>0)
|
||||||
maxPollOptionLength=instance.pollLimits.maxOptionChars;
|
maxPollOptionLength=instance.pollLimits.maxOptionChars;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public class MediaGridLayout extends ViewGroup{
|
|||||||
|
|
||||||
private static final int GAP=2; // dp
|
private static final int GAP=2; // dp
|
||||||
private PhotoLayoutHelper.TiledLayoutResult tiledLayout;
|
private PhotoLayoutHelper.TiledLayoutResult tiledLayout;
|
||||||
private int[] columnStarts=new int[10], columnEnds=new int[10], rowStarts=new int[10], rowEnds=new int[10];
|
private int[] columnStarts, columnEnds, rowStarts, rowEnds;
|
||||||
|
|
||||||
public MediaGridLayout(Context context){
|
public MediaGridLayout(Context context){
|
||||||
this(context, null);
|
this(context, null);
|
||||||
@@ -41,6 +41,14 @@ public class MediaGridLayout extends ViewGroup{
|
|||||||
width=Math.round(width*(tiledLayout.width/(float)PhotoLayoutHelper.MAX_WIDTH));
|
width=Math.round(width*(tiledLayout.width/(float)PhotoLayoutHelper.MAX_WIDTH));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(rowStarts==null || rowStarts.length<tiledLayout.rowSizes.length){
|
||||||
|
rowStarts=new int[tiledLayout.rowSizes.length];
|
||||||
|
rowEnds=new int[tiledLayout.rowSizes.length];
|
||||||
|
}
|
||||||
|
if(columnStarts==null || columnStarts.length<tiledLayout.columnSizes.length){
|
||||||
|
columnStarts=new int[tiledLayout.columnSizes.length];
|
||||||
|
columnEnds=new int[tiledLayout.columnSizes.length];
|
||||||
|
}
|
||||||
int offset=0;
|
int offset=0;
|
||||||
for(int i=0;i<tiledLayout.columnSizes.length;i++){
|
for(int i=0;i<tiledLayout.columnSizes.length;i++){
|
||||||
columnStarts[i]=offset;
|
columnStarts[i]=offset;
|
||||||
@@ -73,7 +81,7 @@ public class MediaGridLayout extends ViewGroup{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onLayout(boolean changed, int l, int t, int r, int b){
|
protected void onLayout(boolean changed, int l, int t, int r, int b){
|
||||||
if(tiledLayout==null)
|
if(tiledLayout==null || rowStarts==null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int maxWidth=UiUtils.MAX_WIDTH;
|
int maxWidth=UiUtils.MAX_WIDTH;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight">
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight">
|
||||||
<item android:gravity="center_vertical" android:height="40dp">
|
<item>
|
||||||
<shape>
|
<shape>
|
||||||
<stroke android:width="1dp" android:color="?colorM3Outline"/>
|
<stroke android:width="1dp" android:color="?colorM3Outline"/>
|
||||||
<corners android:radius="20dp"/>
|
<corners android:radius="20dp"/>
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
<item android:id="@android:id/mask" android:gravity="center_vertical" android:height="40dp">
|
<item android:id="@android:id/mask">
|
||||||
<shape>
|
<shape>
|
||||||
<solid android:color="#000"/>
|
<solid android:color="#000"/>
|
||||||
<corners android:radius="20dp"/>
|
<corners android:radius="20dp"/>
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight">
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight">
|
||||||
<item android:gravity="center_vertical" android:height="40dp">
|
<item>
|
||||||
<shape>
|
<shape>
|
||||||
<stroke android:width="1dp" android:color="?colorM3Outline"/>
|
<stroke android:width="1dp" android:color="?colorM3Outline"/>
|
||||||
<solid android:color="?colorM3Surface"/>
|
<solid android:color="?colorM3Surface"/>
|
||||||
<corners android:radius="20dp"/>
|
<corners android:radius="20dp"/>
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
<item android:id="@android:id/mask" android:gravity="center_vertical" android:height="40dp">
|
<item android:id="@android:id/mask">
|
||||||
<shape>
|
<shape>
|
||||||
<solid android:color="#000"/>
|
<solid android:color="#000"/>
|
||||||
<corners android:radius="20dp"/>
|
<corners android:radius="20dp"/>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:gravity="center_vertical" android:height="40dp">
|
<item>
|
||||||
<scale android:scaleGravity="start|fill_vertical" android:scaleWidth="100%">
|
<scale android:scaleGravity="start|fill_vertical" android:scaleWidth="100%">
|
||||||
<shape>
|
<shape>
|
||||||
<solid android:color="?colorM3SecondaryContainer"/>
|
<solid android:color="?colorM3SecondaryContainer"/>
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
</shape>
|
</shape>
|
||||||
</scale>
|
</scale>
|
||||||
</item>
|
</item>
|
||||||
<item android:gravity="center_vertical" android:height="40dp">
|
<item>
|
||||||
<shape>
|
<shape>
|
||||||
<stroke android:width="1dp" android:color="?colorM3OutlineVariant"/>
|
<stroke android:width="1dp" android:color="?colorM3OutlineVariant"/>
|
||||||
<corners android:radius="20dp"/>
|
<corners android:radius="20dp"/>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:gravity="center_vertical" android:height="40dp">
|
<item>
|
||||||
<shape>
|
<shape>
|
||||||
<solid android:color="?colorM3Surface"/>
|
<solid android:color="?colorM3Surface"/>
|
||||||
<corners android:radius="20dp"/>
|
<corners android:radius="20dp"/>
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
<item android:gravity="center_vertical" android:height="40dp">
|
<item>
|
||||||
<scale android:scaleGravity="start|fill_vertical" android:scaleWidth="100%">
|
<scale android:scaleGravity="start|fill_vertical" android:scaleWidth="100%">
|
||||||
<shape>
|
<shape>
|
||||||
<solid android:color="@color/poll_option_progress_inset"/>
|
<solid android:color="@color/poll_option_progress_inset"/>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
</shape>
|
</shape>
|
||||||
</scale>
|
</scale>
|
||||||
</item>
|
</item>
|
||||||
<item android:gravity="center_vertical" android:height="40dp">
|
<item>
|
||||||
<shape>
|
<shape>
|
||||||
<stroke android:width="1dp" android:color="?colorM3Outline"/>
|
<stroke android:width="1dp" android:color="?colorM3Outline"/>
|
||||||
<corners android:radius="20dp"/>
|
<corners android:radius="20dp"/>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item>
|
<item>
|
||||||
<color android:color="?colorM3SurfaceVariant"/>
|
<color android:color="?colorM3SecondaryContainer"/>
|
||||||
</item>
|
</item>
|
||||||
<item android:drawable="?android:selectableItemBackground"/>
|
|
||||||
</layer-list>
|
</layer-list>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:left="-1dp" android:right="-1dp">
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="?colorM3Surface" />
|
||||||
|
<stroke android:color="?colorM3OutlineVariant" android:width="1dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M13.72 5.78c0.293 0.293 0.767 0.293 1.06 0 0.293-0.293 0.293-0.767 0-1.06l-2.5-2.5c-0.293-0.293-0.767-0.293-1.06 0l-2.5 2.5c-0.293 0.293-0.293 0.767 0 1.06 0.293 0.293 0.767 0.293 1.06 0L11 4.56v4.19c0 0.414 0.336 0.75 0.75 0.75s0.75-0.336 0.75-0.75V4.56l1.22 1.22zM4 11.75C4 11.336 4.336 11 4.75 11h14.5c0.414 0 0.75 0.336 0.75 0.75s-0.336 0.75-0.75 0.75H4.75C4.336 12.5 4 12.164 4 11.75zm8.5 3c0-0.414-0.336-0.75-0.75-0.75S11 14.336 11 14.75v4.69l-1.22-1.22c-0.293-0.293-0.767-0.293-1.06 0-0.293 0.293-0.293 0.767 0 1.06l2.5 2.5c0.293 0.293 0.767 0.293 1.06 0l2.5-2.5c0.293-0.293 0.293-0.767 0-1.06-0.293-0.293-0.767-0.293-1.06 0l-1.22 1.22v-4.69z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
|
||||||
|
<path android:pathData="M15.794 8.733c0.286 0.3 0.274 0.774-0.026 1.06l-5.25 5.001c-0.29 0.276-0.745 0.276-1.035 0l-5.25-5c-0.3-0.287-0.312-0.761-0.026-1.061 0.286-0.3 0.76-0.312 1.06-0.026l4.734 4.508 4.733-4.508c0.3-0.286 0.774-0.274 1.06 0.026zm0-4c0.286 0.3 0.274 0.774-0.026 1.06l-5.25 5.001c-0.29 0.276-0.745 0.276-1.035 0l-5.25-5c-0.3-0.287-0.312-0.761-0.026-1.061 0.286-0.3 0.76-0.312 1.06-0.026l4.734 4.509 4.733-4.51c0.3-0.285 0.774-0.273 1.06 0.027z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
|
||||||
|
<path android:pathData="M4.207 15.267c-0.286-0.3-0.274-0.774 0.026-1.06l5.25-5.002c0.29-0.275 0.745-0.275 1.034 0l5.25 5.002c0.3 0.286 0.312 0.76 0.026 1.06-0.286 0.3-0.76 0.312-1.06 0.026L10 10.784l-4.733 4.51c-0.3 0.285-0.774 0.273-1.06-0.027zm0-4.998c-0.286-0.3-0.274-0.775 0.026-1.06l5.25-5.002c0.29-0.276 0.745-0.276 1.034 0l5.25 5.001c0.3 0.286 0.312 0.76 0.026 1.06-0.286 0.3-0.76 0.312-1.06 0.026L10 5.786l-4.733 4.508c-0.3 0.286-0.774 0.275-1.06-0.025z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M13.748 8.996c0.69 0 1.248-0.559 1.248-1.248 0-0.69-0.559-1.248-1.248-1.248-0.69 0-1.248 0.559-1.248 1.248 0 0.69 0.559 1.248 1.248 1.248zM6.25 3C4.455 3 3 4.455 3 6.25v9c0 1.795 1.455 3.25 3.25 3.25h9c1.795 0 3.25-1.455 3.25-3.25v-9C18.5 4.455 17.045 3 15.25 3h-9zM4.5 6.25c0-0.966 0.784-1.75 1.75-1.75h9C16.216 4.5 17 5.284 17 6.25v9c0 0.231-0.045 0.452-0.126 0.654l-4.587-4.291c-0.865-0.81-2.21-0.81-3.075 0l-4.586 4.29C4.545 15.701 4.5 15.481 4.5 15.25v-9zm6.762 6.458l4.505 4.214C15.604 16.972 15.43 17 15.25 17h-9c-0.18 0-0.354-0.027-0.518-0.078l4.505-4.214c0.289-0.27 0.737-0.27 1.025 0zM8.75 21c-1.15 0-2.162-0.598-2.74-1.5h9.74c2.071 0 3.75-1.679 3.75-3.75V6.011C20.402 6.59 21 7.6 21 8.751v7C21 18.65 18.65 21 15.75 21h-7z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
9
mastodon/src/main/res/drawable/ic_privacy_tip_24px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_privacy_tip_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,22Q8.525,21.125 6.263,18.012Q4,14.9 4,11.1V5L12,2L20,5V11.1Q20,14.9 17.738,18.012Q15.475,21.125 12,22ZM12,19.9Q14.6,19.075 16.3,16.6Q18,14.125 18,11.1V6.375L12,4.125L6,6.375V11.1Q6,14.125 7.7,16.6Q9.4,19.075 12,19.9ZM12,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="M2,14Q1.175,14 0.588,13.412Q0,12.825 0,12V6Q0,5.7 0.125,5.425Q0.25,5.15 0.45,4.95L5.4,0L6.15,0.75Q6.3,0.9 6.4,1.137Q6.5,1.375 6.5,1.6V1.8L5.8,5H11Q11.425,5 11.713,5.287Q12,5.575 12,6V7.25Q12,7.4 11.975,7.537Q11.95,7.675 11.9,7.8L9.65,13.1Q9.475,13.525 9.088,13.762Q8.7,14 8.25,14ZM7.95,12 L10,7.15V7Q10,7 10,7Q10,7 10,7H3.35L3.95,4.3L2,6.2V12Q2,12 2,12Q2,12 2,12ZM18.6,24 L17.85,23.25Q17.7,23.1 17.6,22.863Q17.5,22.625 17.5,22.4V22.2L18.2,19H13Q12.575,19 12.288,18.712Q12,18.425 12,18V16.75Q12,16.6 12.025,16.462Q12.05,16.325 12.1,16.2L14.35,10.9Q14.55,10.475 14.925,10.238Q15.3,10 15.75,10H22Q22.825,10 23.413,10.587Q24,11.175 24,12V18Q24,18.3 23.888,18.562Q23.775,18.825 23.55,19.05ZM16.05,12 L14,16.85V17Q14,17 14,17Q14,17 14,17H20.65L20.05,19.7L22,17.8V12Q22,12 22,12Q22,12 22,12ZM2,12V6.2V7Q2,7 2,7Q2,7 2,7V7.15V12Q2,12 2,12Q2,12 2,12ZM22,12V17.8V17Q22,17 22,17Q22,17 22,17V16.85V12Q22,12 22,12Q22,12 22,12Z"/>
|
||||||
|
</vector>
|
||||||
@@ -5,7 +5,9 @@
|
|||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
android:padding="16dp"
|
android:padding="16dp"
|
||||||
android:background="@drawable/rect_12dp"
|
android:background="@drawable/rect_12dp"
|
||||||
android:backgroundTint="?colorM3SurfaceVariant">
|
android:backgroundTint="?colorM3SurfaceVariant">
|
||||||
@@ -15,7 +17,7 @@
|
|||||||
android:layout_width="32dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="32dp"
|
android:layout_height="32dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:tint="?colorM3OnPrimaryContainer"
|
android:tint="?colorM3Primary"
|
||||||
android:scaleType="center"
|
android:scaleType="center"
|
||||||
android:importantForAccessibility="no"
|
android:importantForAccessibility="no"
|
||||||
tools:src="@drawable/ic_fluent_arrow_trending_24_regular"/>
|
tools:src="@drawable/ic_fluent_arrow_trending_24_regular"/>
|
||||||
|
|||||||
@@ -1,24 +1,79 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="75dp"
|
|
||||||
android:background="@drawable/bg_timeline_gap">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/text"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_vertical"
|
android:orientation="vertical"
|
||||||
android:gravity="center_horizontal"
|
android:background="@drawable/bg_timeline_gap_border">
|
||||||
android:textAppearance="@style/m3_body_large"
|
|
||||||
android:textColor="?android:textColorSecondary"
|
<FrameLayout
|
||||||
android:text="@string/load_missing_posts"/>
|
android:id="@+id/top"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:background="?android:selectableItemBackground">
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/progress"
|
android:id="@+id/progress_top"
|
||||||
android:layout_width="32dp"
|
android:layout_width="24dp"
|
||||||
android:layout_height="32dp"
|
android:layout_height="24dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:visibility="gone"/>
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_top"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:paddingTop="24dp"
|
||||||
|
android:paddingBottom="20dp"
|
||||||
|
android:drawablePadding="16dp"
|
||||||
|
android:drawableEnd="@drawable/ic_fluent_chevron_double_down_20_filled"
|
||||||
|
android:textAppearance="@style/m3_title_medium"
|
||||||
|
android:textColor="?android:textColorSecondary"
|
||||||
|
android:text="@string/sk_load_missing_posts_below"/>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/gap"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="20dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textStyle="italic"
|
||||||
|
android:textColor="?colorM3Primary"
|
||||||
|
android:textAppearance="@style/m3_label_large"
|
||||||
|
android:background="@drawable/bg_timeline_gap"/>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/bottom"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:background="?android:selectableItemBackground">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress_bottom"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_bottom"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:paddingTop="20dp"
|
||||||
|
android:paddingBottom="24dp"
|
||||||
|
android:drawablePadding="16dp"
|
||||||
|
android:drawableEnd="@drawable/ic_fluent_chevron_double_up_20_filled"
|
||||||
|
android:textAppearance="@style/m3_title_medium"
|
||||||
|
android:textColor="?android:textColorSecondary"
|
||||||
|
android:text="@string/sk_load_missing_posts_above"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -5,28 +5,30 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingLeft="16dp"
|
android:paddingLeft="16dp"
|
||||||
android:paddingRight="16dp"
|
android:paddingRight="16dp"
|
||||||
android:paddingBottom="4dp"
|
android:paddingVertical="4dp"
|
||||||
android:clipToPadding="false">
|
android:clipToPadding="false">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/button"
|
android:id="@+id/button"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="48dp"
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="40dp"
|
||||||
android:background="@drawable/bg_poll_option_clickable"
|
android:background="@drawable/bg_poll_option_clickable"
|
||||||
android:duplicateParentState="true"
|
android:duplicateParentState="true"
|
||||||
android:layoutDirection="locale">
|
android:layoutDirection="locale">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/icon"
|
android:id="@+id/icon"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="12dp"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:tint="?colorM3OnSecondaryContainer"
|
android:tint="?colorM3OnSecondaryContainer"
|
||||||
android:scaleType="center"
|
android:scaleType="center"
|
||||||
@@ -41,7 +43,6 @@
|
|||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:textAppearance="@style/m3_label_large"
|
android:textAppearance="@style/m3_label_large"
|
||||||
android:textColor="?colorM3Primary"
|
android:textColor="?colorM3Primary"
|
||||||
android:singleLine="true"
|
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:paddingEnd="26dp"
|
android:paddingEnd="26dp"
|
||||||
tools:text="scream into void jsfdklfjdalskfjdsalkfjdsalkfjdsalkfdjsalkfdsajlk"/>
|
tools:text="scream into void jsfdklfjdalskfjdsalkfjdsalkfjdsalkfdjsalkfdsajlk"/>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
android:paddingTop="16dp"
|
android:paddingTop="16dp"
|
||||||
android:paddingRight="16dp">
|
android:paddingRight="16dp">
|
||||||
|
|
||||||
<LinearLayout
|
<RelativeLayout
|
||||||
android:id="@+id/spoiler_button"
|
android:id="@+id/spoiler_button"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -19,11 +19,26 @@
|
|||||||
android:paddingRight="12dp"
|
android:paddingRight="12dp"
|
||||||
android:paddingBottom="8dp">
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/media_icon"
|
||||||
|
android:layout_width="28dp"
|
||||||
|
android:layout_height="28dp"
|
||||||
|
android:layout_marginHorizontal="8dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:contentDescription="@string/sk_post_contains_media"
|
||||||
|
android:src="@drawable/ic_fluent_image_24_regular"
|
||||||
|
android:tint="?colorM3OnSecondaryContainer" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/spoiler_title"
|
android:id="@+id/spoiler_title"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="4dp"
|
android:layout_marginBottom="4dp"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_toStartOf="@id/media_icon"
|
||||||
android:textAppearance="@style/m3_body_large"
|
android:textAppearance="@style/m3_body_large"
|
||||||
android:textColor="?colorM3OnSecondaryContainer"
|
android:textColor="?colorM3OnSecondaryContainer"
|
||||||
tools:text="Spoilery stuff"/>
|
tools:text="Spoilery stuff"/>
|
||||||
@@ -32,21 +47,14 @@
|
|||||||
android:id="@+id/spoiler_action"
|
android:id="@+id/spoiler_action"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="20dp"
|
android:layout_height="20dp"
|
||||||
|
android:layout_below="@id/spoiler_title"
|
||||||
|
android:layout_toStartOf="@id/media_icon"
|
||||||
android:textAppearance="@style/m3_label_large"
|
android:textAppearance="@style/m3_label_large"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:textColor="?colorM3Primary"
|
android:textColor="?colorM3Primary"
|
||||||
tools:text="Re-hide"/>
|
tools:text="Re-hide"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
<ImageView
|
</FrameLayout>
|
||||||
android:id="@+id/media_icon"
|
|
||||||
android:layout_width="36dp"
|
|
||||||
android:layout_height="36dp"
|
|
||||||
android:background="?android:actionBarItemBackground"
|
|
||||||
android:scaleType="center"
|
|
||||||
android:src="@drawable/ic_fluent_image_24_regular"
|
|
||||||
android:tint="?android:textColorSecondary" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -45,56 +45,10 @@
|
|||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:importantForAccessibility="no"/>
|
android:importantForAccessibility="no"/>
|
||||||
|
|
||||||
<org.joinmastodon.android.ui.views.AutoOrientationLinearLayout
|
<ViewStub
|
||||||
android:id="@+id/translate_wrap"
|
android:id="@+id/translation_info"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingTop="6dp"
|
android:layout="@layout/footer_text_translation"/>
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/action_btn_wrap"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="6dp"
|
|
||||||
android:clipToPadding="false">
|
|
||||||
<org.joinmastodon.android.ui.views.ProgressBarButton
|
|
||||||
android:id="@+id/translate_btn"
|
|
||||||
style="@style/Widget.Mastodon.M3.Button.Text"
|
|
||||||
android:textColor="?colorM3OnSurfaceVariant"
|
|
||||||
android:paddingStart="10dp"
|
|
||||||
android:paddingEnd="10dp"
|
|
||||||
android:minWidth="0dp"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
tools:text="@string/sk_translate_post"/>
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/translate_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:visibility="gone"/>
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<Space
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/translate_info"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="16dp"
|
|
||||||
android:textColor="?colorM3OnSurfaceVariant"
|
|
||||||
android:textAlignment="textEnd"
|
|
||||||
tools:text="Translated using TranslateEngine" />
|
|
||||||
|
|
||||||
</org.joinmastodon.android.ui.views.AutoOrientationLinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user