diff --git a/mastodon/src/androidTest/java/org/joinmastodon/android/fragments/ThreadFragmentTest.java b/mastodon/src/androidTest/java/org/joinmastodon/android/fragments/ThreadFragmentTest.java
new file mode 100644
index 000000000..524aaed83
--- /dev/null
+++ b/mastodon/src/androidTest/java/org/joinmastodon/android/fragments/ThreadFragmentTest.java
@@ -0,0 +1,89 @@
+package org.joinmastodon.android.fragments;
+
+import static org.junit.Assert.*;
+
+import android.util.Pair;
+
+import org.joinmastodon.android.model.Status;
+import org.joinmastodon.android.model.StatusContext;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ThreadFragmentTest {
+
+ private Status fakeStatus(String id, String inReplyTo) {
+ Status status = Status.ofFake(id, null, null);
+ status.inReplyToId = inReplyTo;
+ return status;
+ }
+
+ private ThreadFragment.NeighborAncestryInfo fakeInfo(Status s, Status d, Status a) {
+ ThreadFragment.NeighborAncestryInfo info = new ThreadFragment.NeighborAncestryInfo(s);
+ info.descendantNeighbor = d;
+ info.ancestoringNeighbor = a;
+ return info;
+ }
+
+ @Test
+ public void mapNeighborhoodAncestry() {
+ StatusContext context = new StatusContext();
+ context.ancestors = List.of(
+ fakeStatus("oldest ancestor", null),
+ fakeStatus("younger ancestor", "oldest ancestor")
+ );
+ Status mainStatus = fakeStatus("main status", "younger ancestor");
+ context.descendants = List.of(
+ fakeStatus("first reply", "main status"),
+ fakeStatus("reply to first reply", "first reply"),
+ fakeStatus("third level reply", "reply to first reply"),
+ fakeStatus("another reply", "main status")
+ );
+
+ List neighbors =
+ ThreadFragment.mapNeighborhoodAncestry(mainStatus, context);
+
+ assertEquals(List.of(
+ fakeInfo(context.ancestors.get(0), context.ancestors.get(1), null),
+ fakeInfo(context.ancestors.get(1), mainStatus, context.ancestors.get(0)),
+ fakeInfo(mainStatus, context.descendants.get(0), context.ancestors.get(1)),
+ fakeInfo(context.descendants.get(0), context.descendants.get(1), mainStatus),
+ fakeInfo(context.descendants.get(1), context.descendants.get(2), context.descendants.get(0)),
+ fakeInfo(context.descendants.get(2), null, context.descendants.get(1)),
+ fakeInfo(context.descendants.get(3), null, null)
+ ), neighbors);
+ }
+
+ @Test
+ public void sortStatusContext() {
+ StatusContext context = new StatusContext();
+ context.ancestors = List.of(
+ fakeStatus("younger ancestor", "oldest ancestor"),
+ fakeStatus("oldest ancestor", null)
+ );
+ context.descendants = List.of(
+ fakeStatus("reply to first reply", "first reply"),
+ fakeStatus("third level reply", "reply to first reply"),
+ fakeStatus("first reply", "main status"),
+ fakeStatus("another reply", "main status")
+ );
+
+ ThreadFragment.sortStatusContext(
+ fakeStatus("main status", "younger ancestor"),
+ context
+ );
+ List expectedAncestors = List.of(
+ fakeStatus("oldest ancestor", null),
+ fakeStatus("younger ancestor", "oldest ancestor")
+ );
+ List expectedDescendants = List.of(
+ fakeStatus("first reply", "main status"),
+ fakeStatus("reply to first reply", "first reply"),
+ fakeStatus("third level reply", "reply to first reply"),
+ fakeStatus("another reply", "main status")
+ );
+
+ // TODO: ??? i have no idea how this code works. it certainly doesn't return what i'd expect
+ }
+}
\ No newline at end of file
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ExternalShareActivity.java b/mastodon/src/main/java/org/joinmastodon/android/ExternalShareActivity.java
index d99c200fe..be1657660 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ExternalShareActivity.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ExternalShareActivity.java
@@ -1,7 +1,6 @@
package org.joinmastodon.android;
import android.app.Fragment;
-import android.app.assist.AssistContent;
import android.content.ClipData;
import android.content.Intent;
import android.net.Uri;
@@ -19,6 +18,7 @@ import org.jsoup.internal.StringUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Optional;
import androidx.annotation.Nullable;
import me.grishka.appkit.FragmentStackActivity;
@@ -30,8 +30,8 @@ public class ExternalShareActivity extends FragmentStackActivity{
super.onCreate(savedInstanceState);
if(savedInstanceState==null){
- String text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
- boolean isMastodonURL = UiUtils.looksLikeMastodonUrl(text);
+ Optional text = Optional.ofNullable(getIntent().getStringExtra(Intent.EXTRA_TEXT));
+ boolean isMastodonURL = text.map(UiUtils::looksLikeMastodonUrl).orElse(false);
List sessions=AccountSessionManager.getInstance().getLoggedInAccounts();
if(sessions.isEmpty()){
@@ -40,11 +40,22 @@ public class ExternalShareActivity extends FragmentStackActivity{
}else if(sessions.size()==1 && !isMastodonURL){
openComposeFragment(sessions.get(0).getID());
}else{
- new AccountSwitcherSheet(this, false, false, isMastodonURL, accountSession -> {
- if(accountSession!=null)
- openComposeFragment(accountSession.getID());
- else
- UiUtils.openURL(this, AccountSessionManager.getInstance().getLastActiveAccountID(), text);
+ new AccountSwitcherSheet(this, null, true, isMastodonURL, (accountId, open) -> {
+ if (open && text.isPresent()) {
+ UiUtils.lookupURL(this, accountId, text.get(), false, (clazz, args) -> {
+ if (clazz == null) {
+ finish();
+ return;
+ }
+ args.putString("fromExternalShare", clazz.getSimpleName());
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.putExtras(args);
+ finish();
+ startActivity(intent);
+ });
+ } else {
+ openComposeFragment(accountId);
+ }
}).show();
}
}
@@ -108,11 +119,4 @@ public class ExternalShareActivity extends FragmentStackActivity{
return null;
return new ArrayList<>(l);
}
-
- @Override
- public void onProvideAssistContent(AssistContent outContent) {
- super.onProvideAssistContent(outContent);
-
- outContent.setWebUri(Uri.parse(DomainManager.getInstance().getCurrentDomain()));
- }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java
index 7d3a45c2c..156f4a0a3 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java
@@ -88,6 +88,16 @@ public class GlobalUserPreferences{
catch (JsonSyntaxException ignored) { return orElse; }
}
+ public static void removeAccount(String accountId) {
+ recentLanguages.remove(accountId);
+ pinnedTimelines.remove(accountId);
+ accountsInGlitchMode.remove(accountId);
+ accountsWithLocalOnlySupport.remove(accountId);
+ accountsWithContentTypesEnabled.remove(accountId);
+ accountsDefaultContentTypes.remove(accountId);
+ save();
+ }
+
public static void load(){
SharedPreferences prefs=getPrefs();
playGifs=prefs.getBoolean("playGifs", true);
@@ -218,4 +228,3 @@ public class GlobalUserPreferences{
DARK
}
}
-
diff --git a/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java b/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java
index 8371fbe95..f2aed9cdf 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java
@@ -9,6 +9,8 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
+import android.view.View;
+import android.widget.FrameLayout;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.session.AccountSession;
@@ -22,13 +24,13 @@ import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.updater.GithubSelfUpdater;
+import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.parceler.Parcels;
import androidx.annotation.Nullable;
import me.grishka.appkit.FragmentStackActivity;
-public class MainActivity extends FragmentStackActivity{
-
+public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState){
UiUtils.setUserPreferredTheme(this);
@@ -38,10 +40,18 @@ public class MainActivity extends FragmentStackActivity{
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
showFragmentClearingBackStack(new CustomWelcomeFragment());
}else{
- AccountSessionManager.getInstance().maybeUpdateLocalInfo();
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){
@@ -55,6 +65,7 @@ public class MainActivity extends FragmentStackActivity{
}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);
@@ -78,12 +89,12 @@ public class MainActivity extends FragmentStackActivity{
@Override
protected void onNewIntent(Intent intent){
super.onNewIntent(intent);
- if(intent.getBooleanExtra("fromNotification", false)){
+ AccountSessionManager.getInstance().maybeUpdateLocalInfo();
+ if (intent.hasExtra("fromExternalShare")) showFragmentForExternalShare(intent.getExtras());
+ else if (intent.getBooleanExtra("fromNotification", false)) {
String accountID=intent.getStringExtra("accountID");
- AccountSession accountSession;
try{
- accountSession=AccountSessionManager.getInstance().getAccount(accountID);
- DomainManager.getInstance().setCurrentDomain(accountSession.domain);
+ AccountSessionManager.getInstance().getAccount(accountID);
}catch(IllegalStateException x){
return;
}
@@ -128,6 +139,19 @@ public class MainActivity extends FragmentStackActivity{
showFragment(fragment);
}
+ private void showFragmentForExternalShare(Bundle args) {
+ String clazz = args.getString("fromExternalShare");
+ Fragment fragment = switch (clazz) {
+ case "ThreadFragment" -> new ThreadFragment();
+ case "ProfileFragment" -> new ProfileFragment();
+ default -> null;
+ };
+ if (fragment == null) return;
+ args.putBoolean("_can_go_back", true);
+ fragment.setArguments(args);
+ showFragment(fragment);
+ }
+
private void showCompose(){
AccountSession session=AccountSessionManager.getInstance().getLastActiveAccount();
if(session==null || !session.activated)
@@ -157,25 +181,40 @@ public class MainActivity extends FragmentStackActivity{
(fragmentContainers.get(fragmentContainers.size() - 1)).getId()
);
Bundle currentArgs = currentFragment.getArguments();
- if (this.fragmentContainers.size() == 1
- && currentArgs != null
- && currentArgs.getBoolean("_can_go_back", false)
- && currentArgs.containsKey("account")) {
+ if (fragmentContainers.size() != 1
+ || currentArgs == null
+ || !currentArgs.getBoolean("_can_go_back", false)) {
+ super.onBackPressed();
+ return;
+ }
+ if (currentArgs.getBoolean("_finish_on_back", false)) {
+ finish();
+ } else if (currentArgs.containsKey("account")) {
Bundle args = new Bundle();
args.putString("account", currentArgs.getString("account"));
- args.putString("tab", "notifications");
+ if (getIntent().getBooleanExtra("fromNotification", false)) {
+ args.putString("tab", "notifications");
+ }
Fragment fragment=new HomeFragment();
fragment.setArguments(args);
showFragmentClearingBackStack(fragment);
- } else {
- super.onBackPressed();
}
}
- @Override
- public void onProvideAssistContent(AssistContent outContent) {
- super.onProvideAssistContent(outContent);
- outContent.setWebUri(Uri.parse(DomainManager.getInstance().getCurrentDomain()));
+ public Fragment getCurrentFragment() {
+ for (int i = fragmentContainers.size() - 1; i >= 0; i--) {
+ FrameLayout fl = fragmentContainers.get(i);
+ if (fl.getVisibility() == View.VISIBLE) {
+ return getFragmentManager().findFragmentById(fl.getId());
+ }
+ }
+ return null;
}
+ @Override
+ public void onProvideAssistContent(AssistContent assistContent) {
+ super.onProvideAssistContent(assistContent);
+ Fragment fragment = getCurrentFragment();
+ if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent);
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java b/mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java
index 616b0eb05..564a266bb 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java
@@ -29,6 +29,7 @@ import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.NotificationReceivedEvent;
import org.joinmastodon.android.model.Account;
+import org.joinmastodon.android.model.Mention;
import org.joinmastodon.android.model.NotificationAction;
import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.PushNotification;
@@ -38,6 +39,7 @@ import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
@@ -57,7 +59,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
private static final String ACTION_KEY_TEXT_REPLY = "ACTION_KEY_TEXT_REPLY";
private static final int SUMMARY_ID = 791;
- private static int notificationId;
+ private static int notificationId = 0;
@Override
public void onReceive(Context context, Intent intent){
@@ -298,27 +300,60 @@ public class PushNotificationReceiver extends BroadcastReceiver{
}
CharSequence input = remoteInput.getCharSequence(ACTION_KEY_TEXT_REPLY);
+ // copied from ComposeFragment - TODO: generalize?
+ ArrayList mentions=new ArrayList<>();
+ Status status = notification.status;
+ String ownID=AccountSessionManager.getInstance().getAccount(accountID).self.id;
+ if(!status.account.id.equals(ownID))
+ mentions.add('@'+status.account.acct);
+ for(Mention mention:status.mentions){
+ if(mention.id.equals(ownID))
+ continue;
+ String m='@'+mention.acct;
+ if(!mentions.contains(m))
+ mentions.add(m);
+ }
+ String initialText=mentions.isEmpty() ? "" : TextUtils.join(" ", mentions)+" ";
+
CreateStatus.Request req=new CreateStatus.Request();
- req.status = input.toString() + "\n\n" + "@" + notification.status.account.acct;
- req.language = notification.status.language;
- req.visibility = (notification.status.visibility == StatusPrivacy.PUBLIC && GlobalUserPreferences.defaultToUnlistedReplies ? StatusPrivacy.UNLISTED : notification.status.visibility);
+ req.status = initialText + input.toString();
+ req.language = preferences.postingDefaultLanguage;
+ req.visibility = preferences.postingDefaultVisibility;
req.inReplyToId = notification.status.id;
if(!notification.status.spoilerText.isEmpty() && GlobalUserPreferences.prefixRepliesWithRe && !notification.status.spoilerText.startsWith("re: ")){
req.spoilerText = "re: " + notification.status.spoilerText;
}
- new CreateStatus(req, UUID.randomUUID().toString()).exec(accountID);
+ new CreateStatus(req, UUID.randomUUID().toString()).setCallback(new Callback<>() {
+ @Override
+ public void onSuccess(Status status) {
+ NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ Notification.Builder builder = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O ?
+ new Notification.Builder(context, accountID+"_"+notification.type) :
+ new Notification.Builder(context)
+ .setPriority(Notification.PRIORITY_DEFAULT)
+ .setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE);
- NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- Notification.Builder builder = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O ?
- new Notification.Builder(context, accountID+"_"+notification.type) :
- new Notification.Builder(context)
- .setPriority(Notification.PRIORITY_DEFAULT)
- .setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE);
+ notification.status = status;
+ Intent contentIntent=new Intent(context, MainActivity.class);
+ contentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ contentIntent.putExtra("fromNotification", true);
+ contentIntent.putExtra("accountID", accountID);
+ contentIntent.putExtra("notification", Parcels.wrap(notification));
- Notification repliedNotification = builder.setSmallIcon(R.drawable.ic_ntf_logo)
- .setContentText(context.getString(R.string.mo_notification_action_replied, notification.status.account.getDisplayUsername()))
- .build();
- notificationManager.notify(accountID, notificationId, repliedNotification);
+ Notification repliedNotification = builder.setSmallIcon(R.drawable.ic_ntf_logo)
+ .setContentTitle(context.getString(R.string.sk_notification_action_replied, notification.status.account.displayName))
+ .setContentText(status.getStrippedText())
+ .setCategory(Notification.CATEGORY_SOCIAL)
+ .setContentIntent(PendingIntent.getActivity(context, notificationId, contentIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT))
+ .build();
+ notificationManager.notify(accountID, notificationId, repliedNotification);
+ }
+
+ @Override
+ public void onError(ErrorResponse errorResponse) {
+
+ }
+ }).exec(accountID);
}
-}
\ No newline at end of file
+}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/CacheController.java b/mastodon/src/main/java/org/joinmastodon/android/api/CacheController.java
index e4e54b451..335d00914 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/api/CacheController.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/api/CacheController.java
@@ -19,7 +19,6 @@ import org.joinmastodon.android.model.CacheablePaginatedResponse;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Notification;
-import org.joinmastodon.android.model.PaginatedResponse;
import org.joinmastodon.android.model.SearchResult;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.utils.StatusFilterPredicate;
@@ -160,7 +159,7 @@ public class CacheController{
}
}
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(accountSession.domain);
- new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class), instance.pleroma != null)
+ new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class), instance.isAkkoma())
.setCallback(new Callback<>(){
@Override
public void onSuccess(List result){
diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/notifications/PleromaMarkNotificationsRead.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/notifications/PleromaMarkNotificationsRead.java
new file mode 100644
index 000000000..6e9fe23e7
--- /dev/null
+++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/notifications/PleromaMarkNotificationsRead.java
@@ -0,0 +1,30 @@
+package org.joinmastodon.android.api.requests.notifications;
+
+import android.text.TextUtils;
+
+import com.google.gson.reflect.TypeToken;
+
+import org.joinmastodon.android.api.MastodonAPIRequest;
+import org.joinmastodon.android.model.Notification;
+
+import java.util.List;
+
+import okhttp3.MultipartBody;
+import okhttp3.RequestBody;
+
+public class PleromaMarkNotificationsRead extends MastodonAPIRequest> {
+ private String maxID;
+ public PleromaMarkNotificationsRead(String maxID) {
+ super(HttpMethod.POST, "/pleroma/notifications/read", new TypeToken<>(){});
+ this.maxID = maxID;
+ }
+
+ @Override
+ public RequestBody getRequestBody() {
+ MultipartBody.Builder builder=new MultipartBody.Builder()
+ .setType(MultipartBody.FORM);
+ if(!TextUtils.isEmpty(maxID))
+ builder.addFormDataPart("max_id", maxID);
+ return builder.build();
+ }
+}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetBubbleTimeline.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetBubbleTimeline.java
new file mode 100644
index 000000000..9b54d1895
--- /dev/null
+++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetBubbleTimeline.java
@@ -0,0 +1,23 @@
+package org.joinmastodon.android.api.requests.timelines;
+
+import android.text.TextUtils;
+
+import com.google.gson.reflect.TypeToken;
+
+import org.joinmastodon.android.GlobalUserPreferences;
+import org.joinmastodon.android.api.MastodonAPIRequest;
+import org.joinmastodon.android.model.Status;
+
+import java.util.List;
+
+public class GetBubbleTimeline extends MastodonAPIRequest> {
+ public GetBubbleTimeline(String maxID, int limit) {
+ super(HttpMethod.GET, "/timelines/bubble", new TypeToken<>(){});
+ if(!TextUtils.isEmpty(maxID))
+ addQueryParameter("max_id", maxID);
+ if(limit>0)
+ addQueryParameter("limit", limit+"");
+ if(GlobalUserPreferences.replyVisibility != null)
+ addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
+ }
+}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetHashtagTimeline.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetHashtagTimeline.java
index 4a3c831df..1670c38c2 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetHashtagTimeline.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetHashtagTimeline.java
@@ -2,6 +2,7 @@ package org.joinmastodon.android.api.requests.timelines;
import com.google.gson.reflect.TypeToken;
+import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
@@ -16,5 +17,7 @@ public class GetHashtagTimeline extends MastodonAPIRequest>{
addQueryParameter("min_id", minID);
if(limit>0)
addQueryParameter("limit", ""+limit);
+ if(GlobalUserPreferences.replyVisibility != null)
+ addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
}
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetListTimeline.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetListTimeline.java
index 145a740bc..82d537971 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetListTimeline.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetListTimeline.java
@@ -2,6 +2,7 @@ package org.joinmastodon.android.api.requests.timelines;
import com.google.gson.reflect.TypeToken;
+import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
@@ -18,5 +19,7 @@ public class GetListTimeline extends MastodonAPIRequest> {
addQueryParameter("limit", ""+limit);
if(sinceID!=null)
addQueryParameter("since_id", sinceID);
+ if(GlobalUserPreferences.replyVisibility != null)
+ addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
}
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetPublicTimeline.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetPublicTimeline.java
index 6723c18b9..7ec562704 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetPublicTimeline.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetPublicTimeline.java
@@ -4,6 +4,7 @@ import android.text.TextUtils;
import com.google.gson.reflect.TypeToken;
+import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
@@ -20,5 +21,7 @@ public class GetPublicTimeline extends MastodonAPIRequest>{
addQueryParameter("max_id", maxID);
if(limit>0)
addQueryParameter("limit", limit+"");
+ if(GlobalUserPreferences.replyVisibility != null)
+ addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
}
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java
index da32d9d1a..30acb30d6 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java
@@ -1,5 +1,7 @@
package org.joinmastodon.android.api.session;
+import android.net.Uri;
+
import org.joinmastodon.android.api.CacheController;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.PushSubscriptionManager;
@@ -7,6 +9,7 @@ import org.joinmastodon.android.api.StatusInteractionController;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Application;
import org.joinmastodon.android.model.Filter;
+import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Markers;
import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.PushSubscription;
@@ -14,6 +17,7 @@ import org.joinmastodon.android.model.Token;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
public class AccountSession{
public Token token;
@@ -87,4 +91,15 @@ public class AccountSession{
pushSubscriptionManager=new PushSubscriptionManager(getID());
return pushSubscriptionManager;
}
+
+ public Optional getInstance() {
+ return Optional.ofNullable(AccountSessionManager.getInstance().getInstanceInfo(domain));
+ }
+
+ public Uri getInstanceUri() {
+ return new Uri.Builder()
+ .scheme("https")
+ .authority(getInstance().map(i -> i.normalizedUri).orElse(domain))
+ .build();
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java
index 00ed9cb0c..6f90a5c73 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java
@@ -15,6 +15,7 @@ import android.util.Log;
import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.E;
+import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
@@ -121,6 +122,12 @@ public class AccountSessionManager{
sessions.put(session.getID(), session);
lastActiveAccountID=session.getID();
writeAccountsFile();
+
+ // write initial instance info to file immediately to avoid sessions without instance info
+ InstanceInfoStorageWrapper wrapper = new InstanceInfoStorageWrapper();
+ wrapper.instance = instance;
+ MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, instance.uri));
+
updateMoreInstanceInfo(instance, instance.uri);
if(PushSubscriptionManager.arePushNotificationsAvailable()){
session.getPushSubscriptionManager().registerAccountForPush(null);
@@ -129,14 +136,16 @@ public class AccountSessionManager{
}
public synchronized void writeAccountsFile(){
- File file=new File(MastodonApp.context.getFilesDir(), "accounts.json");
+ File tmpFile = new File(MastodonApp.context.getFilesDir(), "accounts.json~");
+ File file = new File(MastodonApp.context.getFilesDir(), "accounts.json");
try{
- try(FileOutputStream out=new FileOutputStream(file)){
+ try(FileOutputStream out=new FileOutputStream(tmpFile)){
SessionsStorageWrapper w=new SessionsStorageWrapper();
w.accounts=new ArrayList<>(sessions.values());
OutputStreamWriter writer=new OutputStreamWriter(out, StandardCharsets.UTF_8);
MastodonAPIController.gson.toJson(w, writer);
writer.flush();
+ if (!tmpFile.renameTo(file)) Log.e(TAG, "Error renaming " + tmpFile.getPath() + " to " + file.getPath());
}
}catch(IOException x){
Log.e(TAG, "Error writing accounts file", x);
@@ -189,6 +198,7 @@ public class AccountSessionManager{
AccountSession session=getAccount(id);
session.getCacheController().closeDatabase();
MastodonApp.context.deleteDatabase(id+".db");
+ GlobalUserPreferences.removeAccount(id);
sessions.remove(id);
if(lastActiveAccountID.equals(id)){
if(sessions.isEmpty())
@@ -259,31 +269,35 @@ public class AccountSessionManager{
}
public void maybeUpdateLocalInfo(){
+ maybeUpdateLocalInfo(null);
+ }
+
+ public void maybeUpdateLocalInfo(AccountSession activeSession){
long now=System.currentTimeMillis();
HashSet domains=new HashSet<>();
for(AccountSession session:sessions.values()){
domains.add(session.domain.toLowerCase());
-// if(now-session.infoLastUpdated>24L*3600_000L){
- updateSessionPreferences(session);
- updateSessionLocalInfo(session);
-// }
-// if(now-session.filtersLastUpdated>3600_000L){
- updateSessionWordFilters(session);
-// }
+ if(now-session.infoLastUpdated>24L*3600_000L || session == activeSession){
+ updateSessionPreferences(session);
+ updateSessionLocalInfo(session);
+ }
+ if(now-session.filtersLastUpdated>3600_000L || session == activeSession){
+ updateSessionWordFilters(session);
+ }
updateSessionMarkers(session);
}
if(loadedInstances){
- maybeUpdateCustomEmojis(domains);
+ maybeUpdateCustomEmojis(domains, activeSession != null ? activeSession.domain : null);
}
}
- private void maybeUpdateCustomEmojis(Set domains){
+ private void maybeUpdateCustomEmojis(Set domains, String activeDomain){
long now=System.currentTimeMillis();
for(String domain:domains){
-// Long lastUpdated=instancesLastUpdated.get(domain);
-// if(lastUpdated==null || now-lastUpdated>24L*3600_000L){
- updateInstanceInfo(domain);
-// }
+ Long lastUpdated=instancesLastUpdated.get(domain);
+ if(lastUpdated==null || now-lastUpdated>24L*3600_000L || domain.equals(activeDomain)){
+ updateInstanceInfo(domain);
+ }
}
}
@@ -411,7 +425,9 @@ public class AccountSessionManager{
@Override
public void onError(ErrorResponse error){
-
+ InstanceInfoStorageWrapper wrapper=new InstanceInfoStorageWrapper();
+ wrapper.instance = instance;
+ MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, domain));
}
})
.execNoAuth(domain);
@@ -422,10 +438,13 @@ public class AccountSessionManager{
}
private void writeInstanceInfoFile(InstanceInfoStorageWrapper emojis, String domain){
- try(FileOutputStream out=new FileOutputStream(getInstanceInfoFile(domain))){
+ File file = getInstanceInfoFile(domain);
+ File tmpFile = new File(file.getPath() + "~");
+ try(FileOutputStream out=new FileOutputStream(tmpFile)){
OutputStreamWriter writer=new OutputStreamWriter(out, StandardCharsets.UTF_8);
MastodonAPIController.gson.toJson(emojis, writer);
writer.flush();
+ if (!tmpFile.renameTo(file)) Log.e(TAG, "Error renaming " + tmpFile.getPath() + " to " + file.getPath());
}catch(IOException x){
Log.w(TAG, "Error writing instance info file for "+domain, x);
}
@@ -445,7 +464,7 @@ public class AccountSessionManager{
}
if(!loadedInstances){
loadedInstances=true;
- maybeUpdateCustomEmojis(domains);
+ maybeUpdateCustomEmojis(domains, null);
}
}
@@ -469,10 +488,6 @@ public class AccountSessionManager{
return instances.get(domain);
}
- public Instance getInstanceInfoForAccount(String account) {
- return AccountSessionManager.getInstance().getInstanceInfo(instance.getAccount(account).domain);
- }
-
public void updateAccountInfo(String id, Account account){
AccountSession session=getAccount(id);
session.self=account;
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/AccountTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/AccountTimelineFragment.java
index 424cea88c..9ddf968b6 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/AccountTimelineFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/AccountTimelineFragment.java
@@ -1,6 +1,7 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
+import android.net.Uri;
import android.os.Bundle;
import android.view.View;
@@ -131,4 +132,13 @@ public class AccountTimelineFragment extends StatusListFragment{
protected Filter.FilterContext getFilterContext() {
return Filter.FilterContext.ACCOUNT;
}
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ // could return different uris based on filter (e.g. media -> "/media"), but i want to
+ // return the remote url to the user, and i don't know whether i'd need to append
+ // '#media' (akkoma/pleroma) or '/media' (glitch/mastodon) since i don't know anything
+ // about the remote instance. so, just returning the base url to the user instead
+ return Uri.parse(user.url);
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/AnnouncementsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/AnnouncementsFragment.java
index a87b8c465..1b6b8ce2a 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/AnnouncementsFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/AnnouncementsFragment.java
@@ -3,6 +3,7 @@ package org.joinmastodon.android.fragments;
import static java.util.stream.Collectors.toList;
import android.app.Activity;
+import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
@@ -103,4 +104,9 @@ public class AnnouncementsFragment extends BaseStatusListFragment
})
.exec(accountID);
}
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ return isInstanceAkkoma() ? base.path("/announcements").build() : null;
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java
index 0d25daa52..3c6288693 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java
@@ -1,6 +1,7 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
+import android.app.assist.AssistContent;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Paint;
@@ -15,6 +16,7 @@ import android.text.TextPaint;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.animation.TranslateAnimation;
import android.widget.ImageButton;
@@ -49,6 +51,7 @@ import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
import org.joinmastodon.android.ui.utils.UiUtils;
+import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.joinmastodon.android.utils.TypedObjectPool;
import java.util.ArrayList;
@@ -69,7 +72,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
-public abstract class BaseStatusListFragment extends BaseRecyclerFragment implements PhotoViewerHost, ScrollableToTop, HasFab, DomainDisplay{
+public abstract class BaseStatusListFragment extends RecyclerFragment implements PhotoViewerHost, ScrollableToTop, HasFab, ProvidesAssistContent.ProvidesWebUri, DomainDisplay {
protected ArrayList displayItems=new ArrayList<>();
protected DisplayItemsAdapter adapter;
protected String accountID;
@@ -132,7 +135,7 @@ public abstract class BaseStatusListFragment exten
displayItems.clear();
}
- protected void prependItems(List items, boolean notify){
+ protected int prependItems(List items, boolean notify){
data.addAll(0, items);
int offset=0;
for(T s:items){
@@ -145,6 +148,7 @@ public abstract class BaseStatusListFragment exten
}
if(notify)
adapter.notifyItemRangeInserted(0, offset);
+ return offset;
}
protected String getMaxID(){
@@ -205,7 +209,7 @@ public abstract class BaseStatusListFragment exten
@Override
public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){
MediaAttachmentViewController holder=findPhotoViewHolder(index);
- if(holder!=null){
+ if(holder!=null && list!=null){
transitioningHolder=holder;
View view=transitioningHolder.photo;
int[] pos={0, 0};
@@ -337,6 +341,8 @@ public abstract class BaseStatusListFragment exten
private Rect tmpRect=new Rect();
@Override
public void getSelectorBounds(View view, Rect outRect){
+ boolean hasDescendant = false, hasAncestor = false, isWarning = false;
+ int lastIndex = -1, firstIndex = -1;
list.getDecoratedBoundsWithMargins(view, outRect);
RecyclerView.ViewHolder holder=list.getChildViewHolder(view);
if(holder instanceof StatusDisplayItem.Holder){
@@ -348,18 +354,40 @@ public abstract class BaseStatusListFragment exten
for(int i=0;i h){
String otherID=((StatusDisplayItem.Holder>) holder).getItemID();
if(otherID.equals(id)){
+ if (firstIndex < 0) firstIndex = i;
+ lastIndex = i;
+ StatusDisplayItem item = h.getItem();
+ hasDescendant = item.hasDescendantNeighbor;
+ // no for direct descendants because main status (right above) is
+ // being displayed with an extended footer - no connected layout
+ hasAncestor = item.hasAncestoringNeighbor && !item.isDirectDescendant;
list.getDecoratedBoundsWithMargins(child, tmpRect);
outRect.left=Math.min(outRect.left, tmpRect.left);
outRect.top=Math.min(outRect.top, tmpRect.top);
outRect.right=Math.max(outRect.right, tmpRect.right);
outRect.bottom=Math.max(outRect.bottom, tmpRect.bottom);
+ if (holder instanceof WarningFilteredStatusDisplayItem.Holder) {
+ isWarning = true;
+ }
}
}
}
}
+ // shifting the selection box down
+ // see also: FooterStatusDisplayItem#onBind (setMargins)
+ if (isWarning || firstIndex < 0 || lastIndex < 0) return;
+ int prevIndex = firstIndex - 1, nextIndex = lastIndex + 1;
+ boolean prevIsWarning = prevIndex > 0 && prevIndex < list.getChildCount() &&
+ list.getChildViewHolder(list.getChildAt(prevIndex))
+ instanceof WarningFilteredStatusDisplayItem.Holder;
+ boolean nextIsWarning = nextIndex > 0 && nextIndex < list.getChildCount() &&
+ list.getChildViewHolder(list.getChildAt(nextIndex))
+ instanceof WarningFilteredStatusDisplayItem.Holder;
+ if (!prevIsWarning && hasAncestor) outRect.top += V.dp(4);
+ if (!nextIsWarning && hasDescendant) outRect.bottom += V.dp(4);
}
});
list.setItemAnimator(new BetterItemAnimator());
@@ -568,6 +596,14 @@ public abstract class BaseStatusListFragment exten
}
}
+ public void onImageUpdated(MediaGridStatusDisplayItem.Holder holder, int index) {
+ holder.rebind();
+ MediaGridStatusDisplayItem.Holder mediaGrid = findHolderOfType(holder.getItemID(), MediaGridStatusDisplayItem.Holder.class);
+ if(mediaGrid!=null){
+ adapter.notifyItemChanged(mediaGrid.getAbsoluteAdapterPosition());
+ }
+ }
+
public void onGapClick(GapStatusDisplayItem.Holder item){}
public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){
@@ -579,6 +615,7 @@ public abstract class BaseStatusListFragment exten
warning.getItem().status.filterRevealed = true;
}
+ @Override
public String getAccountID(){
return accountID;
}
@@ -717,6 +754,10 @@ public abstract class BaseStatusListFragment exten
return attachmentViewsPool;
}
+ @Override
+ public void onProvideAssistContent(AssistContent assistContent) {
+ assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
+ }
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter> implements ImageLoaderRecyclerAdapter{
@@ -778,6 +819,7 @@ public abstract class BaseStatusListFragment exten
RecyclerView.ViewHolder siblingHolder=parent.getChildViewHolder(bottomSibling);
if(holder instanceof StatusDisplayItem.Holder> ih && siblingHolder instanceof StatusDisplayItem.Holder> sh
&& (!ih.getItemID().equals(sh.getItemID()) || sh instanceof ExtendedFooterStatusDisplayItem.Holder) && ih.getItem().getType()!=StatusDisplayItem.Type.GAP){
+ if (!ih.getItem().isMainStatus && ih.getItem().hasDescendantNeighbor) continue;
drawDivider(child, bottomSibling, holder, siblingHolder, parent, c, dividerPaint);
}
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/BookmarkedStatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/BookmarkedStatusListFragment.java
index 6f863bdd2..c69967435 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/BookmarkedStatusListFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/BookmarkedStatusListFragment.java
@@ -1,6 +1,7 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
+import android.net.Uri;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.GetBookmarkedStatuses;
@@ -41,4 +42,9 @@ public class BookmarkedStatusListFragment extends StatusListFragment{
protected Filter.FilterContext getFilterContext() {
return Filter.FilterContext.ACCOUNT;
}
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ return base.path("/bookmarks").build();
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
index 7275b412e..db75e9835 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
@@ -252,10 +252,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
accountID=getArguments().getString("account");
contentType = GlobalUserPreferences.accountsDefaultContentTypes.get(accountID);
- if (contentType == null && GlobalUserPreferences.accountsWithContentTypesEnabled.contains(accountID)) {
- // if formatting is enabled, use plain to avoid confusing unspecified default setting
- contentType = ContentType.PLAIN;
- }
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
self=session.self;
@@ -274,9 +270,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
Nav.finish(this);
return;
}
- if(customEmojis.isEmpty()){
- AccountSessionManager.getInstance().updateInstanceInfo(instanceDomain);
- }
Bundle bundle = savedInstanceState != null ? savedInstanceState : getArguments();
if (bundle.containsKey("scheduledStatus")) scheduledStatus=Parcels.unwrap(bundle.getParcelable("scheduledStatus"));
@@ -1146,7 +1139,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
req.status=text;
req.localOnly=localOnly;
- req.visibility=localOnly && instance.pleroma != null ? StatusPrivacy.LOCAL : statusVisibility;
+ req.visibility=localOnly && instance.isAkkoma() ? StatusPrivacy.LOCAL : statusVisibility;
req.sensitive=sensitive;
req.language=language;
req.contentType=contentType;
@@ -1800,11 +1793,24 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
pollChanged=true;
updatePublishButtonState();
}));
- option.edit.setFilters(new InputFilter[]{new InputFilter.LengthFilter(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxCharactersPerOption>0 ? instance.configuration.polls.maxCharactersPerOption : 50)});
+
+ int maxCharactersPerOption = 50;
+ if(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxCharactersPerOption>0)
+ maxCharactersPerOption = instance.configuration.polls.maxCharactersPerOption;
+ else if(instance.pollLimits!=null && instance.pollLimits.maxOptionChars>0)
+ maxCharactersPerOption = instance.pollLimits.maxOptionChars;
+ option.edit.setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxCharactersPerOption)});
pollOptionsView.addView(option.view);
pollOptions.add(option);
- if(pollOptions.size()==(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxOptions>0 ? instance.configuration.polls.maxOptions : 4))
+
+ int maxPollOptions = 4;
+ if(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxOptions>0)
+ maxPollOptions = instance.configuration.polls.maxOptions;
+ else if (instance.pollLimits!=null && instance.pollLimits.maxOptions>0)
+ maxPollOptions = instance.pollLimits.maxOptions;
+
+ if(pollOptions.size()==maxPollOptions)
addPollOptionBtn.setVisibility(View.GONE);
return option;
}
@@ -1961,7 +1967,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
Menu m=visibilityPopup.getMenu();
MenuItem localOnlyItem = visibilityPopup.getMenu().findItem(R.id.local_only);
boolean prefsSaysSupported = GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID);
- if (instance.pleroma != null) {
+ if (instance.isAkkoma()) {
m.findItem(R.id.vis_local).setVisible(true);
} else if (localOnly || prefsSaysSupported) {
localOnlyItem.setVisible(true);
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/EditTimelinesFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/EditTimelinesFragment.java
index 8d986b95e..ee751c3ce 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/EditTimelinesFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/EditTimelinesFragment.java
@@ -32,9 +32,12 @@ import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.lists.GetLists;
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
+import org.joinmastodon.android.api.session.AccountSession;
+import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.CustomLocalTimeline;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.HeaderPaginationList;
+import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.ListTimeline;
import org.joinmastodon.android.model.TimelineDefinition;
import org.joinmastodon.android.ui.DividerItemDecoration;
@@ -196,7 +199,7 @@ public class EditTimelinesFragment extends RecyclerFragment
makeBackItem(listsMenu);
makeBackItem(hashtagsMenu);
- TimelineDefinition.ALL_TIMELINES.forEach(tl -> addTimelineToOptions(tl, timelinesMenu));
+ TimelineDefinition.getAllTimelines(accountID).forEach(tl -> addTimelineToOptions(tl, timelinesMenu));
listTimelines.stream().map(TimelineDefinition::ofList).forEach(tl -> addTimelineToOptions(tl, listsMenu));
hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl -> addTimelineToOptions(tl, hashtagsMenu));
@@ -222,7 +225,7 @@ public class EditTimelinesFragment extends RecyclerFragment
@Override
protected void doLoadData(int offset, int count){
- onDataLoaded(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES), false);
+ onDataLoaded(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.getDefaultTimelines(accountID)), false);
updateOptionsMenu();
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/FavoritedStatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/FavoritedStatusListFragment.java
index 41e6e0c7f..e649bc7ba 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/FavoritedStatusListFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/FavoritedStatusListFragment.java
@@ -1,6 +1,7 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
+import android.net.Uri;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.GetFavoritedStatuses;
@@ -41,4 +42,11 @@ public class FavoritedStatusListFragment extends StatusListFragment{
protected Filter.FilterContext getFilterContext() {
return Filter.FilterContext.ACCOUNT;
}
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ return base.encodedPath(isInstanceAkkoma()
+ ? '/' + getSession().self.username + "#favorites"
+ : "/favourites").build();
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/FollowRequestsListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/FollowRequestsListFragment.java
index 9cd86da83..9f144634d 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/FollowRequestsListFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/FollowRequestsListFragment.java
@@ -4,6 +4,7 @@ import android.app.Activity;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
@@ -24,6 +25,7 @@ import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ProgressBarButton;
+import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.parceler.Parcels;
import java.util.Collections;
@@ -46,7 +48,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
-public class FollowRequestsListFragment extends RecyclerFragment implements ScrollableToTop{
+public class FollowRequestsListFragment extends RecyclerFragment implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri {
private String accountID;
private Map relationships=Collections.emptyMap();
private GetAccountRelationships relationshipsRequest;
@@ -149,8 +151,13 @@ public class FollowRequestsListFragment extends RecyclerFragment implements ImageLoaderRecyclerAdapter{
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/FollowedHashtagsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/FollowedHashtagsFragment.java
index ded699f99..e2e6ae59c 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/FollowedHashtagsFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/FollowedHashtagsFragment.java
@@ -1,5 +1,6 @@
package org.joinmastodon.android.fragments;
+import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
@@ -14,14 +15,15 @@ import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.utils.UiUtils;
+import org.joinmastodon.android.utils.ProvidesAssistContent;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.views.UsableRecyclerView;
-public class FollowedHashtagsFragment extends RecyclerFragment implements ScrollableToTop {
+public class FollowedHashtagsFragment extends RecyclerFragment implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri {
private String nextMaxID;
- private String accountId;
+ private String accountID;
public FollowedHashtagsFragment() {
super(20);
@@ -31,7 +33,7 @@ public class FollowedHashtagsFragment extends RecyclerFragment implemen
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args=getArguments();
- accountId=args.getString("account");
+ accountID=args.getString("account");
setTitle(R.string.sk_hashtags_you_follow);
}
@@ -62,7 +64,7 @@ public class FollowedHashtagsFragment extends RecyclerFragment implemen
onDataLoaded(result, nextMaxID!=null);
}
})
- .exec(accountId);
+ .exec(accountID);
}
@Override
@@ -76,8 +78,13 @@ public class FollowedHashtagsFragment extends RecyclerFragment implemen
}
@Override
- public boolean isScrolledToTop() {
- return list.getChildAt(0).getTop() == 0;
+ public String getAccountID() {
+ return accountID;
+ }
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ return isInstanceAkkoma() ? null : base.path("/followed_tags").build();
}
private class HashtagsAdapter extends RecyclerView.Adapter{
@@ -114,7 +121,7 @@ public class FollowedHashtagsFragment extends RecyclerFragment implemen
@Override
public void onClick() {
- UiUtils.openHashtagTimeline(getActivity(), accountId, item.name, item.following);
+ UiUtils.openHashtagTimeline(getActivity(), accountID, item.name, item.following);
}
}
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HasAccountID.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HasAccountID.java
new file mode 100644
index 000000000..67a3bbfac
--- /dev/null
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HasAccountID.java
@@ -0,0 +1,23 @@
+package org.joinmastodon.android.fragments;
+
+import org.joinmastodon.android.api.session.AccountSession;
+import org.joinmastodon.android.api.session.AccountSessionManager;
+import org.joinmastodon.android.model.Instance;
+
+import java.util.Optional;
+
+public interface HasAccountID {
+ String getAccountID();
+
+ default AccountSession getSession() {
+ return AccountSessionManager.getInstance().getAccount(getAccountID());
+ }
+
+ default boolean isInstanceAkkoma() {
+ return getInstance().map(Instance::isAkkoma).orElse(false);
+ }
+
+ default Optional getInstance() {
+ return getSession().getInstance();
+ }
+}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HashtagTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HashtagTimelineFragment.java
index c7ce498ae..5fec68deb 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HashtagTimelineFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HashtagTimelineFragment.java
@@ -1,6 +1,7 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
+import android.net.Uri;
import android.os.Bundle;
import android.view.HapticFeedbackConstants;
import android.view.Menu;
@@ -8,7 +9,6 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageButton;
import android.widget.Toast;
import org.joinmastodon.android.DomainManager;
@@ -167,4 +167,9 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
protected Filter.FilterContext getFilterContext() {
return Filter.FilterContext.PUBLIC;
}
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ return base.path((isInstanceAkkoma() ? "/tag/" : "/tags") + hashtag).build();
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java
index 2424a8abc..8e19ad402 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java
@@ -2,6 +2,7 @@ package org.joinmastodon.android.fragments;
import android.app.Fragment;
import android.app.NotificationManager;
+import android.app.assist.AssistContent;
import android.content.Intent;
import android.graphics.Outline;
import android.os.Build;
@@ -40,16 +41,13 @@ import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.ui.AccountSwitcherSheet;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.TabBar;
+import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
-
-import androidx.annotation.IdRes;
-import androidx.annotation.Nullable;
-
-import com.squareup.otto.Subscribe;
+import java.util.Optional;
import me.grishka.appkit.FragmentStackActivity;
import me.grishka.appkit.Nav;
@@ -63,11 +61,9 @@ import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.FragmentRootLinearLayout;
-public class HomeFragment extends AppKitFragment implements OnBackPressedListener{
+public class HomeFragment extends AppKitFragment implements OnBackPressedListener, ProvidesAssistContent, HasAccountID {
private FragmentRootLinearLayout content;
-
private HomeTabFragment homeTabFragment;
-
private NotificationsFragment notificationsFragment;
private DiscoverFragment searchFragment;
private ProfileFragment profileFragment;
@@ -79,6 +75,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
private int currentTab=R.id.tab_home;
private String accountID;
+ private boolean isPleroma;
@Override
public void onCreate(Bundle savedInstanceState){
@@ -86,18 +83,21 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
E.register(this);
accountID=getArguments().getString("account");
setTitle(R.string.mo_app_name);
+ isPleroma = AccountSessionManager.getInstance().getAccount(accountID).getInstance()
+ .map(Instance::isAkkoma)
+ .orElse(false);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
setRetainInstance(true);
+ // TODO: clean up
if(savedInstanceState==null){
Bundle args=new Bundle();
args.putString("account", accountID);
-
homeTabFragment=new HomeTabFragment();
homeTabFragment.setArguments(args);
-
args=new Bundle(args);
+ args.putBoolean("disableDiscover", isPleroma);
args.putBoolean("noAutoLoad", true);
searchFragment=new DiscoverFragment();
searchFragment.setArguments(args);
@@ -149,7 +149,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
.add(me.grishka.appkit.R.id.fragment_wrap, profileFragment).hide(profileFragment)
.commit();
-
String defaultTab=getArguments().getString("tab");
if("notifications".equals(defaultTab)){
tabBar.selectTab(R.id.tab_notifications);
@@ -170,19 +169,14 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
@Override
public void onViewStateRestored(Bundle savedInstanceState){
super.onViewStateRestored(savedInstanceState);
-
if(savedInstanceState==null) return;
-
-
homeTabFragment=(HomeTabFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTabFragment");
-
searchFragment=(DiscoverFragment) getChildFragmentManager().getFragment(savedInstanceState, "searchFragment");
notificationsFragment=(NotificationsFragment) getChildFragmentManager().getFragment(savedInstanceState, "notificationsFragment");
profileFragment=(ProfileFragment) getChildFragmentManager().getFragment(savedInstanceState, "profileFragment");
currentTab=savedInstanceState.getInt("selectedTab");
tabBar.selectTab(currentTab);
Fragment current=fragmentForTab(currentTab);
-
getChildFragmentManager().beginTransaction()
.hide(homeTabFragment)
.hide(searchFragment)
@@ -190,15 +184,12 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
.hide(profileFragment)
.show(current)
.commit();
-
maybeTriggerLoading(current);
}
@Override
public void onHiddenChanged(boolean hidden){
super.onHiddenChanged(hidden);
- if (!hidden && fragmentForTab(currentTab) instanceof DomainDisplay display)
- DomainManager.getInstance().setCurrentDomain(display.getDomain());
fragmentForTab(currentTab).onHiddenChanged(hidden);
}
@@ -222,9 +213,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
}
WindowInsets topOnlyInsets=insets.replaceSystemWindowInsets(0, insets.getSystemWindowInsetTop(), 0, 0);
-
homeTabFragment.onApplyWindowInsets(topOnlyInsets);
-
searchFragment.onApplyWindowInsets(topOnlyInsets);
notificationsFragment.onApplyWindowInsets(topOnlyInsets);
profileFragment.onApplyWindowInsets(topOnlyInsets);
@@ -243,34 +232,28 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
throw new IllegalArgumentException();
}
+ public void setCurrentTab(@IdRes int tab){
+ if(tab==currentTab)
+ return;
+ tabBar.selectTab(tab);
+ onTabSelected(tab);
+ }
+
private void onTabSelected(@IdRes int tab){
Fragment newFragment=fragmentForTab(tab);
if(tab==currentTab){
- if(tab == R.id.tab_search){
- if(newFragment instanceof ScrollableToTop scrollable)
- scrollable.scrollToTop();
- searchFragment.selectSearch();
- return;
- }
- if(newFragment instanceof ScrollableToTop scrollable)
+ if (tab == R.id.tab_search)
+ searchFragment.onSelect();
+ else if(newFragment instanceof ScrollableToTop scrollable)
scrollable.scrollToTop();
return;
}
- if(tab==currentTab && tab == R.id.tab_search){
- if(newFragment instanceof ScrollableToTop scrollable)
- scrollable.scrollToTop();
- return;
- }
-
- if (newFragment instanceof DomainDisplay display) {
- DomainManager.getInstance().setCurrentDomain(display.getDomain());
- }
-
getChildFragmentManager().beginTransaction().hide(fragmentForTab(currentTab)).show(newFragment).commit();
maybeTriggerLoading(newFragment);
if (newFragment instanceof HasFab fabulous) fabulous.showFab();
currentTab=tab;
((FragmentStackActivity)getActivity()).invalidateSystemBarColors(this);
+ if (tab == R.id.tab_search && isPleroma) searchFragment.selectSearch();
}
private void maybeTriggerLoading(Fragment newFragment){
@@ -297,10 +280,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
options.add(session.self.displayName+"\n("+session.self.username+"@"+session.domain+")");
}
- new AccountSwitcherSheet(getActivity(), true, true, false, accountSession -> {
- getActivity().finish();
- getActivity().startActivity(new Intent(getActivity(), MainActivity.class));
- }).show();
+ new AccountSwitcherSheet(getActivity(), this).show();
return true;
}
if(tab==R.id.tab_search){
@@ -336,7 +316,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
outState.putInt("selectedTab", currentTab);
-
if (homeTabFragment.isAdded()) getChildFragmentManager().putFragment(outState, "homeTabFragment", homeTabFragment);
if (searchFragment.isAdded()) getChildFragmentManager().putFragment(outState, "searchFragment", searchFragment);
if (notificationsFragment.isAdded()) getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment);
@@ -345,10 +324,10 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
public void updateNotificationBadge() {
AccountSession session = AccountSessionManager.getInstance().getAccount(accountID);
- Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
- if (instance == null) return;
+ Optional instance = session.getInstance();
+ if (instance.isEmpty()) return; // avoiding incompatibility with akkoma
- new GetNotifications(null, 1, EnumSet.allOf(Notification.Type.class), instance != null && instance.pleroma != null)
+ new GetNotifications(null, 1, EnumSet.allOf(Notification.Type.class), instance.get().isAkkoma())
.setCallback(new Callback<>() {
@Override
public void onSuccess(List notifications) {
@@ -356,9 +335,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
try {
long newestId = Long.parseLong(notifications.get(0).id);
long lastSeenId = Long.parseLong(session.markers.notifications.lastReadId);
- System.out.println("NEWEST: " + newestId);
- System.out.println("LAST SEEN: " + lastSeenId);
-
setNotificationBadge(newestId > lastSeenId);
} catch (Exception ignored) {
setNotificationBadge(false);
@@ -372,6 +348,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
}
}).exec(accountID);
}
+
public void setNotificationBadge(boolean badge) {
notificationTabIcon.setImageResource(badge
? R.drawable.ic_fluent_alert_28_selector_badged
@@ -387,4 +364,14 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
public void onAllNotificationsSeen(AllNotificationsSeenEvent allNotificationsSeenEvent) {
setNotificationBadge(false);
}
+
+ @Override
+ public String getAccountID() {
+ return accountID;
+ }
+
+ @Override
+ public void onProvideAssistContent(AssistContent assistContent) {
+ callFragmentToProvideAssistContent(fragmentForTab(currentTab), assistContent);
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java
index 6994b4d84..57df424da 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java
@@ -10,6 +10,7 @@ import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
+import android.app.assist.AssistContent;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
@@ -56,6 +57,7 @@ import org.joinmastodon.android.model.TimelineDefinition;
import org.joinmastodon.android.ui.SimpleViewHolder;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.updater.GithubSelfUpdater;
+import org.joinmastodon.android.utils.ProvidesAssistContent;
import java.util.Collection;
import java.util.HashMap;
@@ -73,7 +75,7 @@ import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
-public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener, DomainDisplay, HasFab {
+public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener, HasFab, ProvidesAssistContent {
private static final int ANNOUNCEMENTS_RESULT = 654;
private String accountID;
@@ -108,7 +110,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
super.onCreate(savedInstanceState);
E.register(this);
accountID = getArguments().getString("account");
- timelineDefinitions = GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES);
+ timelineDefinitions = GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.getDefaultTimelines(accountID));
assert timelineDefinitions != null;
if (timelineDefinitions.size() == 0) timelineDefinitions = List.of(TimelineDefinition.HOME_TIMELINE);
count = timelineDefinitions.size();
@@ -209,10 +211,6 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
if (fragments[position] instanceof BaseRecyclerFragment> page){
if(!page.loaded && !page.isDataLoading()) page.loadData();
}
-
- //update recent app list url
- if (fragments[position] instanceof DomainDisplay page)
- DomainManager.getInstance().setCurrentDomain(page.getDomain());
}
});
@@ -297,14 +295,6 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
}).exec(accountID);
}
- @Override
- public String getDomain() {
- if (fragments[pager.getCurrentItem()] instanceof DomainDisplay page) {
- return page.getDomain();
- }
- return DomainDisplay.super.getDomain();
- }
-
private void onFabClick(View v){
if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment> l) {
l.onFabClick(v);
@@ -722,6 +712,11 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
return fab;
}
+ @Override
+ public void onProvideAssistContent(AssistContent assistContent) {
+ callFragmentToProvideAssistContent(fragments[pager.getCurrentItem()], assistContent);
+ }
+
private class HomePagerAdapter extends RecyclerView.Adapter {
@NonNull
@Override
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java
index 5fb3a9cc9..afb70884d 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java
@@ -1,6 +1,7 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
+import android.net.Uri;
import android.os.Bundle;
import android.view.View;
@@ -291,4 +292,9 @@ public class HomeTimelineFragment extends StatusListFragment {
protected Filter.FilterContext getFilterContext() {
return Filter.FilterContext.HOME;
}
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ return base.path("/").build();
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelineFragment.java
index da6322a85..0e03a19e6 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelineFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelineFragment.java
@@ -1,13 +1,13 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
+import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageButton;
import androidx.annotation.Nullable;
@@ -168,4 +168,9 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
protected Filter.FilterContext getFilterContext() {
return Filter.FilterContext.HOME;
}
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ return base.path("/lists/" + listID).build();
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelinesFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelinesFragment.java
deleted file mode 100644
index 3655bd3ee..000000000
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelinesFragment.java
+++ /dev/null
@@ -1,263 +0,0 @@
-package org.joinmastodon.android.fragments;
-
-import android.os.Bundle;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CheckBox;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.squareup.otto.Subscribe;
-
-import org.joinmastodon.android.E;
-import org.joinmastodon.android.R;
-import org.joinmastodon.android.api.MastodonAPIRequest;
-import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
-import org.joinmastodon.android.api.requests.lists.CreateList;
-import org.joinmastodon.android.api.requests.lists.GetLists;
-import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
-import org.joinmastodon.android.events.ListDeletedEvent;
-import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
-import org.joinmastodon.android.model.ListTimeline;
-import org.joinmastodon.android.ui.DividerItemDecoration;
-import org.joinmastodon.android.ui.M3AlertDialogBuilder;
-import org.joinmastodon.android.ui.views.ListTimelineEditor;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-
-import me.grishka.appkit.Nav;
-import me.grishka.appkit.api.Callback;
-import me.grishka.appkit.api.ErrorResponse;
-import me.grishka.appkit.api.SimpleCallback;
-import me.grishka.appkit.utils.BindableViewHolder;
-import me.grishka.appkit.views.UsableRecyclerView;
-
-public class ListTimelinesFragment extends RecyclerFragment implements ScrollableToTop {
- private String accountId;
- private String profileAccountId;
- private final HashMap userInListBefore = new HashMap<>();
- private final HashMap userInList = new HashMap<>();
- private ListsAdapter adapter;
-
- public ListTimelinesFragment() {
- super(10);
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Bundle args=getArguments();
- accountId=args.getString("account");
- setHasOptionsMenu(true);
- E.register(this);
-
- if(args.containsKey("profileAccount")){
- profileAccountId=args.getString("profileAccount");
- String profileDisplayUsername = args.getString("profileDisplayUsername");
- setTitle(getString(R.string.sk_lists_with_user, profileDisplayUsername));
- } else {
- setTitle(R.string.sk_your_lists);
- }
- }
-
- @Override
- protected void onShown(){
- super.onShown();
- if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
- loadData();
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- inflater.inflate(R.menu.menu_list, menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (item.getItemId() == R.id.create) {
- ListTimelineEditor editor = new ListTimelineEditor(getContext());
- new M3AlertDialogBuilder(getActivity())
- .setTitle(R.string.sk_create_list_title)
- .setIcon(R.drawable.ic_fluent_people_add_28_regular)
- .setView(editor)
- .setPositiveButton(R.string.sk_create, (d, which) ->
- new CreateList(editor.getTitle(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
- @Override
- public void onSuccess(ListTimeline list) {
- data.add(0, list);
- adapter.notifyItemRangeInserted(0, 1);
- E.post(new ListUpdatedCreatedEvent(list.id, list.title, list.repliesPolicy));
- }
-
- @Override
- public void onError(ErrorResponse error) {
- error.showToast(getContext());
- }
- }).exec(accountId)
- )
- .setNegativeButton(R.string.cancel, (d, which) -> {})
- .show();
- }
- return true;
- }
-
- private void saveListMembership(String listId, boolean isMember) {
- userInList.put(listId, isMember);
- List accountIdList = Collections.singletonList(profileAccountId);
- MastodonAPIRequest req = isMember ? new AddAccountsToList(listId, accountIdList) : new RemoveAccountsFromList(listId, accountIdList);
- req.setCallback(new Callback<>() {
- @Override
- public void onSuccess(Object o) {}
-
- @Override
- public void onError(ErrorResponse error) {
- error.showToast(getContext());
- }
- }).exec(accountId);
- }
-
- @Override
- protected void doLoadData(int offset, int count){
- userInListBefore.clear();
- userInList.clear();
- currentRequest=(profileAccountId != null ? new GetLists(profileAccountId) : new GetLists())
- .setCallback(new SimpleCallback<>(this) {
- @Override
- public void onSuccess(List lists) {
- if (getActivity() == null) return;
- for (ListTimeline l : lists) userInListBefore.put(l.id, true);
- userInList.putAll(userInListBefore);
- if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false);
- if (profileAccountId == null) return;
-
- currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListTimelinesFragment.this) {
- @Override
- public void onSuccess(List allLists) {
- if (getActivity() == null) return;
- List newLists = new ArrayList<>();
- for (ListTimeline l : allLists) {
- if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l);
- if (!userInListBefore.containsKey(l.id)) {
- userInListBefore.put(l.id, false);
- }
- }
- userInList.putAll(userInListBefore);
- onDataLoaded(newLists, false);
- }
- }).exec(accountId);
- }
- })
- .exec(accountId);
- }
-
- @Subscribe
- public void onListDeletedEvent(ListDeletedEvent event) {
- for (int i = 0; i < data.size(); i++) {
- ListTimeline item = data.get(i);
- if (item.id.equals(event.id)) {
- data.remove(i);
- adapter.notifyItemRemoved(i);
- break;
- }
- }
- }
-
- @Subscribe
- public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) {
- for (int i = 0; i < data.size(); i++) {
- ListTimeline item = data.get(i);
- if (item.id.equals(event.id)) {
- item.title = event.title;
- item.repliesPolicy = event.repliesPolicy;
- adapter.notifyItemChanged(i);
- break;
- }
- }
- }
-
- @Override
- protected RecyclerView.Adapter getAdapter() {
- return adapter = new ListsAdapter();
- }
-
- @Override
- public void scrollToTop() {
- smoothScrollRecyclerViewToTop(list);
- }
-
- @Override
- public boolean isScrolledToTop() {
- return list.getChildAt(0).getTop() == 0;
- }
-
- private class ListsAdapter extends RecyclerView.Adapter{
- @NonNull
- @Override
- public ListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
- return new ListViewHolder();
- }
-
- @Override
- public void onBindViewHolder(@NonNull ListViewHolder holder, int position) {
- holder.bind(data.get(position));
- }
-
- @Override
- public int getItemCount() {
- return data.size();
- }
- }
-
- private class ListViewHolder extends BindableViewHolder implements UsableRecyclerView.Clickable{
- private final TextView title;
- private final CheckBox listToggle;
-
- public ListViewHolder(){
- super(getActivity(), R.layout.item_text, list);
- title=findViewById(R.id.title);
- listToggle=findViewById(R.id.list_toggle);
- }
-
- @Override
- public void onBind(ListTimeline item) {
- title.setText(item.title);
- title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(R.drawable.ic_fluent_people_24_regular), null, null, null);
- if (profileAccountId != null) {
- Boolean checked = userInList.get(item.id);
- listToggle.setVisibility(View.VISIBLE);
- listToggle.setChecked(userInList.containsKey(item.id) && checked != null && checked);
- listToggle.setOnClickListener(this::onClickToggle);
- } else {
- listToggle.setVisibility(View.GONE);
- }
- }
-
- private void onClickToggle(View view) {
- saveListMembership(item.id, listToggle.isChecked());
- }
-
- @Override
- public void onClick() {
- Bundle args=new Bundle();
- args.putString("account", accountId);
- args.putString("listID", item.id);
- args.putString("listTitle", item.title);
- if (item.repliesPolicy != null) args.putInt("repliesPolicy", item.repliesPolicy.ordinal());
- Nav.go(getActivity(), ListTimelineFragment.class, args);
- }
- }
-}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ListsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ListsFragment.java
new file mode 100644
index 000000000..243a104c6
--- /dev/null
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ListsFragment.java
@@ -0,0 +1,270 @@
+package org.joinmastodon.android.fragments;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.squareup.otto.Subscribe;
+
+import org.joinmastodon.android.E;
+import org.joinmastodon.android.R;
+import org.joinmastodon.android.api.MastodonAPIRequest;
+import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
+import org.joinmastodon.android.api.requests.lists.CreateList;
+import org.joinmastodon.android.api.requests.lists.GetLists;
+import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
+import org.joinmastodon.android.events.ListDeletedEvent;
+import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
+import org.joinmastodon.android.model.ListTimeline;
+import org.joinmastodon.android.ui.DividerItemDecoration;
+import org.joinmastodon.android.ui.M3AlertDialogBuilder;
+import org.joinmastodon.android.ui.views.ListTimelineEditor;
+import org.joinmastodon.android.utils.ProvidesAssistContent;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+import me.grishka.appkit.Nav;
+import me.grishka.appkit.api.Callback;
+import me.grishka.appkit.api.ErrorResponse;
+import me.grishka.appkit.api.SimpleCallback;
+import me.grishka.appkit.utils.BindableViewHolder;
+import me.grishka.appkit.views.UsableRecyclerView;
+
+public class ListsFragment extends RecyclerFragment implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri {
+ private String accountID;
+ private String profileAccountId;
+ private final HashMap userInListBefore = new HashMap<>();
+ private final HashMap userInList = new HashMap<>();
+ private ListsAdapter adapter;
+
+ public ListsFragment() {
+ super(10);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Bundle args = getArguments();
+ accountID = args.getString("account");
+ setHasOptionsMenu(true);
+ E.register(this);
+
+ if(args.containsKey("profileAccount")){
+ profileAccountId=args.getString("profileAccount");
+ String profileDisplayUsername = args.getString("profileDisplayUsername");
+ setTitle(getString(R.string.sk_lists_with_user, profileDisplayUsername));
+ } else {
+ setTitle(R.string.sk_your_lists);
+ }
+ }
+
+ @Override
+ protected void onShown(){
+ super.onShown();
+ if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
+ loadData();
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.menu_list, menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == R.id.create) {
+ ListTimelineEditor editor = new ListTimelineEditor(getContext());
+ new M3AlertDialogBuilder(getActivity())
+ .setTitle(R.string.sk_create_list_title)
+ .setIcon(R.drawable.ic_fluent_people_add_28_regular)
+ .setView(editor)
+ .setPositiveButton(R.string.sk_create, (d, which) ->
+ new CreateList(editor.getTitle(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
+ @Override
+ public void onSuccess(ListTimeline list) {
+ data.add(0, list);
+ adapter.notifyItemRangeInserted(0, 1);
+ E.post(new ListUpdatedCreatedEvent(list.id, list.title, list.repliesPolicy));
+ }
+
+ @Override
+ public void onError(ErrorResponse error) {
+ error.showToast(getContext());
+ }
+ }).exec(accountID)
+ )
+ .setNegativeButton(R.string.cancel, (d, which) -> {})
+ .show();
+ }
+ return true;
+ }
+
+ private void saveListMembership(String listId, boolean isMember) {
+ userInList.put(listId, isMember);
+ List accountIdList = Collections.singletonList(profileAccountId);
+ MastodonAPIRequest req = isMember ? new AddAccountsToList(listId, accountIdList) : new RemoveAccountsFromList(listId, accountIdList);
+ req.setCallback(new Callback<>() {
+ @Override
+ public void onSuccess(Object o) {}
+
+ @Override
+ public void onError(ErrorResponse error) {
+ error.showToast(getContext());
+ }
+ }).exec(accountID);
+ }
+
+ @Override
+ protected void doLoadData(int offset, int count){
+ userInListBefore.clear();
+ userInList.clear();
+ currentRequest=(profileAccountId != null ? new GetLists(profileAccountId) : new GetLists())
+ .setCallback(new SimpleCallback<>(this) {
+ @Override
+ public void onSuccess(List lists) {
+ if (getActivity() == null) return;
+ for (ListTimeline l : lists) userInListBefore.put(l.id, true);
+ userInList.putAll(userInListBefore);
+ if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false);
+ if (profileAccountId == null) return;
+
+ currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListsFragment.this) {
+ @Override
+ public void onSuccess(List allLists) {
+ if (getActivity() == null) return;
+ List newLists = new ArrayList<>();
+ for (ListTimeline l : allLists) {
+ if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l);
+ if (!userInListBefore.containsKey(l.id)) {
+ userInListBefore.put(l.id, false);
+ }
+ }
+ userInList.putAll(userInListBefore);
+ onDataLoaded(newLists, false);
+ }
+ }).exec(accountID);
+ }
+ })
+ .exec(accountID);
+ }
+
+ @Subscribe
+ public void onListDeletedEvent(ListDeletedEvent event) {
+ for (int i = 0; i < data.size(); i++) {
+ ListTimeline item = data.get(i);
+ if (item.id.equals(event.id)) {
+ data.remove(i);
+ adapter.notifyItemRemoved(i);
+ break;
+ }
+ }
+ }
+
+ @Subscribe
+ public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) {
+ for (int i = 0; i < data.size(); i++) {
+ ListTimeline item = data.get(i);
+ if (item.id.equals(event.id)) {
+ item.title = event.title;
+ item.repliesPolicy = event.repliesPolicy;
+ adapter.notifyItemChanged(i);
+ break;
+ }
+ }
+ }
+
+ @Override
+ protected RecyclerView.Adapter getAdapter() {
+ return adapter = new ListsAdapter();
+ }
+
+ @Override
+ public void scrollToTop() {
+ smoothScrollRecyclerViewToTop(list);
+ }
+
+ @Override
+ public String getAccountID() {
+ return accountID;
+ }
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ return base.path("/lists").build();
+ }
+
+ private class ListsAdapter extends RecyclerView.Adapter{
+ @NonNull
+ @Override
+ public ListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
+ return new ListViewHolder();
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ListViewHolder holder, int position) {
+ holder.bind(data.get(position));
+ }
+
+ @Override
+ public int getItemCount() {
+ return data.size();
+ }
+ }
+
+ private class ListViewHolder extends BindableViewHolder implements UsableRecyclerView.Clickable{
+ private final TextView title;
+ private final CheckBox listToggle;
+
+ public ListViewHolder(){
+ super(getActivity(), R.layout.item_text, list);
+ title=findViewById(R.id.title);
+ listToggle=findViewById(R.id.list_toggle);
+ }
+
+ @Override
+ public void onBind(ListTimeline item) {
+ title.setText(item.title);
+ title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(R.drawable.ic_fluent_people_24_regular), null, null, null);
+ if (profileAccountId != null) {
+ Boolean checked = userInList.get(item.id);
+ listToggle.setVisibility(View.VISIBLE);
+ listToggle.setChecked(userInList.containsKey(item.id) && checked != null && checked);
+ listToggle.setOnClickListener(this::onClickToggle);
+ } else {
+ listToggle.setVisibility(View.GONE);
+ }
+ }
+
+ private void onClickToggle(View view) {
+ saveListMembership(item.id, listToggle.isChecked());
+ }
+
+ @Override
+ public void onClick() {
+ Bundle args=new Bundle();
+ args.putString("account", accountID);
+ args.putString("listID", item.id);
+ args.putString("listTitle", item.title);
+ if (item.repliesPolicy != null) args.putInt("repliesPolicy", item.repliesPolicy.ordinal());
+ Nav.go(getActivity(), ListTimelineFragment.class, args);
+ }
+ }
+}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsFragment.java
index 06719c24d..a80c9d192 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsFragment.java
@@ -2,6 +2,7 @@ package org.joinmastodon.android.fragments;
import android.app.Activity;
import android.app.Fragment;
+import android.app.assist.AssistContent;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -13,6 +14,12 @@ import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.viewpager2.widget.ViewPager2;
+
+import com.squareup.otto.Subscribe;
+
import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
@@ -24,12 +31,7 @@ import org.joinmastodon.android.ui.SimpleViewHolder;
import org.joinmastodon.android.ui.tabs.TabLayout;
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
import org.joinmastodon.android.ui.utils.UiUtils;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.viewpager2.widget.ViewPager2;
-
-import com.squareup.otto.Subscribe;
+import org.joinmastodon.android.utils.ProvidesAssistContent;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
@@ -37,7 +39,7 @@ import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.utils.V;
-public class NotificationsFragment extends MastodonToolbarFragment implements ScrollableToTop, DomainDisplay{
+public class NotificationsFragment extends MastodonToolbarFragment implements ScrollableToTop, ProvidesAssistContent {
private TabLayout tabLayout;
private ViewPager2 pager;
@@ -47,12 +49,6 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
private NotificationsListFragment allNotificationsFragment, mentionsFragment, postsFragment;
private String accountID;
-
- @Override
- public String getDomain() {
- return DomainDisplay.super.getDomain() + "/notifications";
- }
-
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
@@ -107,6 +103,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
tabLayout=view.findViewById(R.id.tabbar);
pager=view.findViewById(R.id.pager);
+ UiUtils.reduceSwipeSensitivity(pager);
tabViews=new FrameLayout[3];
for(int i=0;i() {
@Override
public void onSuccess(HeaderPaginationList accounts) {
+ if (getActivity() == null) return;
getToolbar().getMenu().findItem(R.id.follow_requests).setVisible(!accounts.isEmpty());
}
@@ -228,6 +235,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
protected void updateToolbar(){
super.updateToolbar();
getToolbar().setOutlineProvider(null);
+ getToolbar().setOnClickListener(v->scrollToTop());
}
private NotificationsListFragment getFragmentForPage(int page){
@@ -239,6 +247,11 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
};
}
+ @Override
+ public void onProvideAssistContent(AssistContent assistContent) {
+ callFragmentToProvideAssistContent(getFragmentForPage(pager.getCurrentItem()), assistContent);
+ }
+
private class DiscoverPagerAdapter extends RecyclerView.Adapter{
@NonNull
@Override
@@ -263,4 +276,4 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
return position;
}
}
-}
\ No newline at end of file
+}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java
index c9c409b3f..176adcfe1 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java
@@ -1,6 +1,7 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
+import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
@@ -10,6 +11,7 @@ import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
+import org.joinmastodon.android.api.requests.notifications.PleromaMarkNotificationsRead;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.AllNotificationsSeenEvent;
import org.joinmastodon.android.events.PollUpdatedEvent;
@@ -18,6 +20,8 @@ import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.CacheablePaginatedResponse;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.Filter;
+import org.joinmastodon.android.model.Instance;
+import org.joinmastodon.android.model.Markers;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
@@ -53,11 +57,6 @@ public class NotificationsListFragment extends BaseStatusListFragment(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES));
+ pinnedTimelines = new ArrayList<>(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.getDefaultTimelines(accountID)));
}
@Override
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java
index 2bc81efa1..7e35672e1 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java
@@ -6,6 +6,7 @@ import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.app.Fragment;
+import android.app.assist.AssistContent;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Color;
@@ -67,6 +68,7 @@ import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.AccountField;
import org.joinmastodon.android.model.Attachment;
+import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.SimpleViewHolder;
@@ -83,6 +85,7 @@ import org.joinmastodon.android.ui.views.CoverImageView;
import org.joinmastodon.android.ui.views.LinkedTextView;
import org.joinmastodon.android.ui.views.NestedRecyclerScrollView;
import org.joinmastodon.android.ui.views.ProgressBarButton;
+import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.parceler.Parcels;
import java.time.LocalDateTime;
@@ -93,9 +96,13 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+import androidx.viewpager2.widget.ViewPager2;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
@@ -116,7 +123,7 @@ import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
-public class ProfileFragment extends LoaderFragment implements OnBackPressedListener, ScrollableToTop, HasFab{
+public class ProfileFragment extends LoaderFragment implements OnBackPressedListener, ScrollableToTop, HasFab, ProvidesAssistContent.ProvidesWebUri {
private static final int AVATAR_RESULT=722;
private static final int COVER_RESULT=343;
@@ -146,6 +153,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private String note;
private Account account;
private String accountID;
+ private String domain;
private Relationship relationship;
private int statusBarHeight;
private boolean isOwnProfile;
@@ -160,7 +168,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private PhotoViewer currentPhotoViewer;
private boolean editModeLoading;
- private static final int MAX_FIELDS=4;
+ private int maxFields = 4;
// from ProfileAboutFragment
public UsableRecyclerView list;
@@ -181,6 +189,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
setRetainInstance(true);
accountID=getArguments().getString("account");
+ domain=AccountSessionManager.getInstance().getAccount(accountID).domain;
if(getArguments().containsKey("profileAccount")){
account=Parcels.unwrap(getArguments().getParcelable("profileAccount"));
profileAccountID=account.id;
@@ -188,6 +197,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
loaded=true;
if(!isOwnProfile)
loadRelationship();
+ else if (isInstanceAkkoma() && getInstance().isPresent())
+ maxFields = getInstance().get().pleroma.metadata.fieldsLimits.maxFields;
}else{
profileAccountID=getArguments().getString("profileAccountID");
if(!getArguments().getBoolean("noAutoLoad", false))
@@ -206,14 +217,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
setHasOptionsMenu(true);
}
- @Override
- public void onHiddenChanged(boolean hidden) {
- super.onHiddenChanged(hidden);
- if (!hidden) {
- DomainManager.getInstance().setCurrentDomain(account.url);
- }
- }
-
@Override
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View content=inflater.inflate(R.layout.fragment_profile, container, false);
@@ -396,7 +399,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
username.setOnLongClickListener(v->{
String usernameString=account.acct;
if(!usernameString.contains("@")){
- usernameString+="@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
+ usernameString+="@"+domain;
}
UiUtils.copyText(username, '@'+usernameString);
return true;
@@ -469,11 +472,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@Override
public void onRefresh(){
- if(isInEditMode){
- refreshing=false;
- refreshLayout.setRefreshing(false);
- return;
- }
if(refreshing)
return;
refreshing=true;
@@ -577,12 +575,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private void bindHeaderView(){
setTitle(account.displayName);
setSubtitle(getResources().getQuantityString(R.plurals.x_posts, (int)(account.statusesCount%1000), account.statusesCount));
- if((GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic) != null){
- ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(100), V.dp(100)));
- }
- if((GlobalUserPreferences.playGifs ? account.header : account.headerStatic) != null) {
- ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.header : account.headerStatic, 1000, 1000));
- }
+ ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(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));
SpannableStringBuilder ssb=new SpannableStringBuilder(account.displayName);
HtmlParser.parseCustomEmoji(ssb, account.emojis);
name.setText(ssb);
@@ -610,7 +604,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
ssb.append(account.acct);
if(isSelf){
ssb.append('@');
- ssb.append(AccountSessionManager.getInstance().getAccount(accountID).domain);
+ ssb.append(domain);
}
ssb.append(" ");
Drawable lock=username.getResources().getDrawable(R.drawable.ic_lock, getActivity().getTheme()).mutate();
@@ -633,7 +627,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
username.setText(ssb);
}else{
// noinspection SetTextI18n
- username.setText('@'+account.acct+(isSelf ? ('@'+AccountSessionManager.getInstance().getAccount(accountID).domain) : ""));
+ username.setText('@'+account.acct+(isSelf ? ('@'+domain) : ""));
}
CharSequence parsedBio = null;
if(account.note != null){
@@ -645,8 +639,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
bio.setVisibility(View.VISIBLE);
bio.setText(parsedBio);
}
-
-
followersCount.setText(UiUtils.abbreviateNumber(account.followersCount));
followingCount.setText(UiUtils.abbreviateNumber(account.followingCount));
postsCount.setText(UiUtils.abbreviateNumber(account.statusesCount));
@@ -825,7 +817,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
args.putString("profileAccount", profileAccountID);
args.putString("profileDisplayUsername", account.getDisplayUsername());
}
- Nav.go(getActivity(), ListTimelinesFragment.class, args);
+ Nav.go(getActivity(), ListsFragment.class, args);
}else if(id==R.id.followed_hashtags){
Bundle args=new Bundle();
args.putString("account", accountID);
@@ -878,7 +870,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
// aboutFragment.setNote(relationship.note, accountID, profileAccountID);
}
notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username));
- DomainManager.getInstance().setCurrentDomain(account.url);
}
public ImageButton getFab() {
@@ -1215,11 +1206,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
scrollView.smoothScrollTo(0, 0);
}
- @Override
- public boolean isScrolledToTop() {
- return list.getChildAt(0).getTop() == 0;
- }
-
private void onFollowersOrFollowingClick(View v){
Bundle args=new Bundle();
args.putString("account", accountID);
@@ -1284,6 +1270,21 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if (adapter != null) adapter.notifyDataSetChanged();
}
+ @Override
+ public void onProvideAssistContent(AssistContent assistContent) {
+ callFragmentToProvideAssistContent(getFragmentForPage(pager.getCurrentItem()), assistContent);
+ }
+
+ @Override
+ public String getAccountID() {
+ return accountID;
+ }
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ return Uri.parse(account.url);
+ }
+
private class MetadataAdapter extends UsableRecyclerView.Adapter implements ImageLoaderRecyclerAdapter {
public MetadataAdapter(){
super(imgLoader);
@@ -1314,7 +1315,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
public int getItemCount(){
if(isInEditMode){
int size=metadataListData.size();
- if(size items=new ArrayList<>();
+ private ThemeItem themeItem;
+ private NotificationPolicyItem notificationPolicyItem;
+ private SwitchItem showNewPostsItem, glitchModeItem, compactReblogReplyLineItem;
+ private ButtonItem defaultContentTypeButtonItem;
+ private String accountID;
+ private boolean needUpdateNotificationSettings;
+ private boolean needAppRestart;
+ private PushSubscription pushSubscription;
+
+ private ImageView themeTransitionWindowView;
+ private TextItem checkForUpdateItem, clearImageCacheItem;
+ private ImageCache imageCache;
+ private Menu contentTypeMenu;
+
+ @SuppressLint("ClickableViewAccessibility")
+ @Override
+ public void onCreate(Bundle savedInstanceState){
+ super.onCreate(savedInstanceState);
+ if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
+ setRetainInstance(true);
+ setTitle(R.string.settings);
+ imageCache = ImageCache.getInstance(getActivity());
+ accountID=getArguments().getString("account");
+ AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
+ Optional instance = session.getInstance();
+ String instanceName = UiUtils.getInstanceName(accountID);
+
+ if(GithubSelfUpdater.needSelfUpdating()){
+ GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
+ GithubSelfUpdater.UpdateState state=updater.getState();
+ if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING){
+ items.add(new UpdateItem());
+ }
+ }
+
+ items.add(new HeaderItem(R.string.settings_theme));
+ items.add(themeItem=new ThemeItem());
+ items.add(new SwitchItem(R.string.theme_true_black, R.drawable.ic_fluent_dark_theme_24_regular, GlobalUserPreferences.trueBlackTheme, this::onTrueBlackThemeChanged));
+ items.add(new ButtonItem(R.string.sk_settings_color_palette, R.drawable.ic_fluent_color_24_regular, b->{
+ PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
+ popupMenu.inflate(R.menu.color_palettes);
+ popupMenu.getMenu().findItem(R.id.m3_color).setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S);
+ popupMenu.setOnMenuItemClickListener(SettingsFragment.this::onColorPreferenceClick);
+ b.setOnTouchListener(popupMenu.getDragToOpenListener());
+ b.setOnClickListener(v->popupMenu.show());
+ b.setText(switch(GlobalUserPreferences.color){
+ case MATERIAL3 -> R.string.sk_color_palette_material3;
+ case PINK -> R.string.sk_color_palette_pink;
+ case PURPLE -> R.string.sk_color_palette_purple;
+ case GREEN -> R.string.sk_color_palette_green;
+ case BLUE -> R.string.sk_color_palette_blue;
+ case BROWN -> R.string.sk_color_palette_brown;
+ case RED -> R.string.sk_color_palette_red;
+ case YELLOW -> R.string.sk_color_palette_yellow;
+ });
+ }));
+ items.add(new ButtonItem(R.string.sk_settings_publish_button_text, R.drawable.ic_fluent_send_24_regular, b->{
+ updatePublishText(b);
+
+ b.setOnClickListener(l->{
+ TextInputFrameLayout input = new TextInputFrameLayout(
+ getContext(),
+ getString(R.string.publish),
+ GlobalUserPreferences.publishButtonText.trim()
+ );
+ new M3AlertDialogBuilder(getContext()).setTitle(R.string.sk_settings_publish_button_text_title).setView(input)
+ .setPositiveButton(R.string.save, (d, which) -> {
+ GlobalUserPreferences.publishButtonText = input.getEditText().getText().toString().trim();
+ GlobalUserPreferences.save();
+ updatePublishText(b);
+ })
+ .setNeutralButton(R.string.clear, (d, which) -> {
+ GlobalUserPreferences.publishButtonText = "";
+ GlobalUserPreferences.save();
+ updatePublishText(b);
+ })
+ .setNegativeButton(R.string.cancel, (d, which) -> {})
+ .show();
+ });
+ }));
+ items.add(new SwitchItem(R.string.sk_settings_uniform_icon_for_notifications, R.drawable.ic_ntf_logo, GlobalUserPreferences.uniformNotificationIcon, i->{
+ GlobalUserPreferences.uniformNotificationIcon=i.checked;
+ GlobalUserPreferences.save();
+ }));
+ items.add(new SwitchItem(R.string.sk_disable_marquee, R.drawable.ic_fluent_text_more_24_regular, GlobalUserPreferences.disableMarquee, i->{
+ GlobalUserPreferences.disableMarquee=i.checked;
+ GlobalUserPreferences.save();
+ }));
+ items.add(new SwitchItem(R.string.sk_settings_reduce_motion, R.drawable.ic_fluent_star_emphasis_24_regular, GlobalUserPreferences.reduceMotion, i->{
+ GlobalUserPreferences.reduceMotion=i.checked;
+ GlobalUserPreferences.save();
+ }));
+
+ items.add(new HeaderItem(R.string.settings_behavior));
+ items.add(new SwitchItem(R.string.settings_gif, R.drawable.ic_fluent_gif_24_regular, GlobalUserPreferences.playGifs, i->{
+ GlobalUserPreferences.playGifs=i.checked;
+ GlobalUserPreferences.save();
+ }));
+ items.add(new SwitchItem(R.string.settings_custom_tabs, R.drawable.ic_fluent_link_24_regular, GlobalUserPreferences.useCustomTabs, i->{
+ GlobalUserPreferences.useCustomTabs=i.checked;
+ GlobalUserPreferences.save();
+ }));
+ items.add(new SwitchItem(R.string.sk_settings_show_interaction_counts, R.drawable.ic_fluent_number_row_24_regular, GlobalUserPreferences.showInteractionCounts, i->{
+ GlobalUserPreferences.showInteractionCounts=i.checked;
+ GlobalUserPreferences.save();
+ }));
+ items.add(new SwitchItem(R.string.sk_settings_always_reveal_content_warnings, R.drawable.ic_fluent_chat_warning_24_regular, GlobalUserPreferences.alwaysExpandContentWarnings, i->{
+ GlobalUserPreferences.alwaysExpandContentWarnings=i.checked;
+ GlobalUserPreferences.save();
+ }));
+ items.add(new SwitchItem(R.string.sk_tabs_disable_swipe, R.drawable.ic_fluent_swipe_right_24_regular, GlobalUserPreferences.disableSwipe, i->{
+ GlobalUserPreferences.disableSwipe=i.checked;
+ GlobalUserPreferences.save();
+ needAppRestart=true;
+ }));
+ items.add(new SwitchItem(R.string.sk_enable_delete_notifications, R.drawable.ic_fluent_mail_inbox_dismiss_24_regular, GlobalUserPreferences.enableDeleteNotifications, i->{
+ GlobalUserPreferences.enableDeleteNotifications=i.checked;
+ GlobalUserPreferences.save();
+ needAppRestart=true;
+ }));
+ items.add(new SwitchItem(R.string.sk_settings_disable_alt_text_reminder, R.drawable.ic_fluent_image_alt_text_24_regular, GlobalUserPreferences.disableAltTextReminder, i->{
+ GlobalUserPreferences.disableAltTextReminder=i.checked;
+ GlobalUserPreferences.save();
+ }));
+ items.add(new SwitchItem(R.string.sk_settings_single_notification, R.drawable.ic_fluent_convert_range_24_regular, GlobalUserPreferences.keepOnlyLatestNotification, i->{
+ GlobalUserPreferences.keepOnlyLatestNotification=i.checked;
+ GlobalUserPreferences.save();
+ }));
+ items.add(new SwitchItem(R.string.sk_settings_prefix_reply_cw_with_re, R.drawable.ic_fluent_arrow_reply_24_regular, GlobalUserPreferences.prefixRepliesWithRe, i->{
+ GlobalUserPreferences.prefixRepliesWithRe=i.checked;
+ GlobalUserPreferences.save();
+ }));
+ items.add(new SwitchItem(R.string.sk_settings_confirm_before_reblog, R.drawable.ic_fluent_checkmark_circle_24_regular, GlobalUserPreferences.confirmBeforeReblog, i->{
+ GlobalUserPreferences.confirmBeforeReblog=i.checked;
+ GlobalUserPreferences.save();
+ }));
+
+ items.add(new HeaderItem(R.string.sk_timelines));
+ items.add(new SwitchItem(R.string.sk_settings_show_replies, R.drawable.ic_fluent_chat_multiple_24_regular, GlobalUserPreferences.showReplies, i->{
+ GlobalUserPreferences.showReplies=i.checked;
+ GlobalUserPreferences.save();
+ }));
+ if (isInstanceAkkoma()) {
+ items.add(new ButtonItem(R.string.sk_settings_reply_visibility, R.drawable.ic_fluent_chat_24_regular, b->{
+ PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
+ popupMenu.inflate(R.menu.reply_visibility);
+ popupMenu.setOnMenuItemClickListener(item -> this.onReplyVisibilityChanged(item, b));
+ b.setOnTouchListener(popupMenu.getDragToOpenListener());
+ b.setOnClickListener(v->popupMenu.show());
+ b.setText(GlobalUserPreferences.replyVisibility == null ?
+ R.string.sk_settings_reply_visibility_all :
+ switch(GlobalUserPreferences.replyVisibility){
+ case "following" -> R.string.sk_settings_reply_visibility_following;
+ case "self" -> R.string.sk_settings_reply_visibility_self;
+ default -> R.string.sk_settings_reply_visibility_all;
+ });
+ }));
+ }
+ items.add(new SwitchItem(R.string.sk_settings_show_boosts, R.drawable.ic_fluent_arrow_repeat_all_24_regular, GlobalUserPreferences.showBoosts, i->{
+ GlobalUserPreferences.showBoosts=i.checked;
+ GlobalUserPreferences.save();
+ }));
+ items.add(new SwitchItem(R.string.sk_settings_load_new_posts, R.drawable.ic_fluent_arrow_sync_24_regular, GlobalUserPreferences.loadNewPosts, i->{
+ GlobalUserPreferences.loadNewPosts=i.checked;
+ showNewPostsItem.enabled = i.checked;
+ if (!i.checked) {
+ GlobalUserPreferences.showNewPostsButton = false;
+ showNewPostsItem.checked = false;
+ }
+ if (list.findViewHolderForAdapterPosition(items.indexOf(showNewPostsItem)) instanceof SwitchViewHolder svh) svh.rebind();
+ GlobalUserPreferences.save();
+ }));
+ items.add(showNewPostsItem = new SwitchItem(R.string.sk_settings_see_new_posts_button, R.drawable.ic_fluent_arrow_up_24_regular, GlobalUserPreferences.showNewPostsButton, i->{
+ GlobalUserPreferences.showNewPostsButton=i.checked;
+ GlobalUserPreferences.save();
+ }));
+ items.add(new SwitchItem(R.string.sk_settings_show_alt_indicator, R.drawable.ic_fluent_scan_text_24_regular, GlobalUserPreferences.showAltIndicator, i->{
+ GlobalUserPreferences.showAltIndicator=i.checked;
+ GlobalUserPreferences.save();
+ }));
+ items.add(new SwitchItem(R.string.sk_settings_show_no_alt_indicator, R.drawable.ic_fluent_important_24_regular, GlobalUserPreferences.showNoAltIndicator, i->{
+ GlobalUserPreferences.showNoAltIndicator=i.checked;
+ GlobalUserPreferences.save();
+ }));
+ items.add(new SwitchItem(R.string.sk_settings_collapse_long_posts, R.drawable.ic_fluent_chevron_down_24_regular, GlobalUserPreferences.collapseLongPosts, i->{
+ GlobalUserPreferences.collapseLongPosts=i.checked;
+ GlobalUserPreferences.save();
+ }));
+ items.add(new SwitchItem(R.string.sk_settings_hide_interaction, R.drawable.ic_fluent_eye_24_regular, GlobalUserPreferences.spectatorMode, i->{
+ GlobalUserPreferences.spectatorMode=i.checked;
+ GlobalUserPreferences.save();
+ needAppRestart=true;
+ }));
+ items.add(new SwitchItem(R.string.sk_settings_hide_fab, R.drawable.ic_fluent_edit_24_regular, GlobalUserPreferences.autoHideFab, i->{
+ GlobalUserPreferences.autoHideFab=i.checked;
+ GlobalUserPreferences.save();
+ needAppRestart=true;
+ }));
+ items.add(new SwitchItem(R.string.sk_reply_line_above_avatar, R.drawable.ic_fluent_arrow_reply_24_regular, GlobalUserPreferences.replyLineAboveHeader, i->{
+ GlobalUserPreferences.replyLineAboveHeader=i.checked;
+ GlobalUserPreferences.compactReblogReplyLine=i.checked;
+ compactReblogReplyLineItem.enabled=i.checked;
+ compactReblogReplyLineItem.checked= GlobalUserPreferences.replyLineAboveHeader;
+ if (list.findViewHolderForAdapterPosition(items.indexOf(compactReblogReplyLineItem)) instanceof SwitchViewHolder svh) svh.rebind();
+ GlobalUserPreferences.save();
+ needAppRestart=true;
+ }));
+ items.add(compactReblogReplyLineItem=new SwitchItem(R.string.sk_compact_reblog_reply_line, R.drawable.ic_fluent_re_order_24_regular, GlobalUserPreferences.compactReblogReplyLine, i->{
+ GlobalUserPreferences.compactReblogReplyLine=i.checked;
+ GlobalUserPreferences.save();
+ needAppRestart=true;
+ }));
+ compactReblogReplyLineItem.enabled=GlobalUserPreferences.replyLineAboveHeader;
+ items.add(new SwitchItem(R.string.sk_settings_translate_only_opened, R.drawable.ic_fluent_translate_24_regular, GlobalUserPreferences.translateButtonOpenedOnly, i->{
+ GlobalUserPreferences.translateButtonOpenedOnly=i.checked;
+ GlobalUserPreferences.save();
+ needAppRestart=true;
+ }));
+ boolean translationAvailable = instance
+ .map(i -> i.v2 != null && i.v2.configuration.translation != null && i.v2.configuration.translation.enabled)
+ .orElse(false);
+ items.add(new SmallTextItem(getString(translationAvailable ?
+ R.string.sk_settings_translation_availability_note_available :
+ R.string.sk_settings_translation_availability_note_unavailable, instanceName)));
+
+ items.add(new HeaderItem(R.string.settings_notifications));
+ items.add(notificationPolicyItem=new NotificationPolicyItem());
+ PushSubscription pushSubscription=getPushSubscription();
+ boolean switchEnabled=pushSubscription.policy!=PushSubscription.Policy.NONE;
+
+ items.add(new SwitchItem(R.string.notify_favorites, R.drawable.ic_fluent_star_24_regular, pushSubscription.alerts.favourite, i->onNotificationsChanged(PushNotification.Type.FAVORITE, i.checked), switchEnabled));
+ items.add(new SwitchItem(R.string.notify_follow, R.drawable.ic_fluent_person_add_24_regular, pushSubscription.alerts.follow, i->onNotificationsChanged(PushNotification.Type.FOLLOW, i.checked), switchEnabled));
+ items.add(new SwitchItem(R.string.notify_reblog, R.drawable.ic_fluent_arrow_repeat_all_24_regular, pushSubscription.alerts.reblog, i->onNotificationsChanged(PushNotification.Type.REBLOG, i.checked), switchEnabled));
+ items.add(new SwitchItem(R.string.notify_mention, R.drawable.ic_fluent_mention_24_regular, pushSubscription.alerts.mention, i->onNotificationsChanged(PushNotification.Type.MENTION, i.checked), switchEnabled));
+ items.add(new SwitchItem(R.string.sk_notify_posts, R.drawable.ic_fluent_chat_24_regular, pushSubscription.alerts.status, i->onNotificationsChanged(PushNotification.Type.STATUS, i.checked), switchEnabled));
+ items.add(new SwitchItem(R.string.sk_notify_update, R.drawable.ic_fluent_history_24_regular, pushSubscription.alerts.update, i->onNotificationsChanged(PushNotification.Type.UPDATE, i.checked), switchEnabled));
+ items.add(new SwitchItem(R.string.sk_notify_poll_results, R.drawable.ic_fluent_poll_24_regular, pushSubscription.alerts.poll, i->onNotificationsChanged(PushNotification.Type.POLL, i.checked), switchEnabled));
+
+ items.add(new HeaderItem(R.string.settings_account));
+ items.add(new TextItem(R.string.sk_settings_profile, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/settings/profile"), R.drawable.ic_fluent_open_24_regular));
+ items.add(new TextItem(R.string.sk_settings_posting, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/settings/preferences/other"), R.drawable.ic_fluent_open_24_regular));
+ items.add(new TextItem(R.string.sk_settings_filters, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/filters"), R.drawable.ic_fluent_open_24_regular));
+ items.add(new TextItem(R.string.sk_settings_auth, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/auth/edit"), R.drawable.ic_fluent_open_24_regular));
+
+ items.add(new HeaderItem(instanceName));
+ items.add(new TextItem(R.string.sk_settings_rules, instance.map(i -> () -> {
+ Bundle args = new Bundle();
+ args.putParcelable("instance", Parcels.wrap(i));
+ Nav.go(getActivity(), InstanceRulesFragment.class, args);
+ }).orElse(null), R.drawable.ic_fluent_task_list_ltr_24_regular));
+ items.add(new TextItem(R.string.sk_settings_about_instance , ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/about"), R.drawable.ic_fluent_info_24_regular));
+ items.add(new TextItem(R.string.settings_tos, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular));
+ items.add(new TextItem(R.string.settings_privacy_policy, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular));
+ items.add(new TextItem(R.string.log_out, this::confirmLogOut, R.drawable.ic_fluent_sign_out_24_regular));
+ items.add(new SmallTextItem(instance
+ .map(i -> getString(R.string.sk_settings_server_version, i.version))
+ .orElse(getString(R.string.sk_instance_info_unavailable))));
+
+ items.add(new HeaderItem(R.string.sk_instance_features));
+ items.add(new SwitchItem(R.string.sk_settings_content_types, 0, GlobalUserPreferences.accountsWithContentTypesEnabled.contains(accountID), (i)->{
+ if (i.checked) {
+ GlobalUserPreferences.accountsWithContentTypesEnabled.add(accountID);
+ if (GlobalUserPreferences.accountsDefaultContentTypes.get(accountID) == null) {
+ GlobalUserPreferences.accountsDefaultContentTypes.put(accountID, ContentType.PLAIN);
+ }
+ } else {
+ GlobalUserPreferences.accountsWithContentTypesEnabled.remove(accountID);
+ GlobalUserPreferences.accountsDefaultContentTypes.remove(accountID);
+ }
+ if (list.findViewHolderForAdapterPosition(items.indexOf(defaultContentTypeButtonItem))
+ instanceof ButtonViewHolder bvh) bvh.rebind();
+ GlobalUserPreferences.save();
+ }));
+ items.add(new SmallTextItem(getString(R.string.sk_settings_content_types_explanation)));
+ items.add(defaultContentTypeButtonItem = new ButtonItem(R.string.sk_settings_default_content_type, 0, b->{
+ PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
+ popupMenu.inflate(R.menu.compose_content_type);
+ popupMenu.setOnMenuItemClickListener(item -> this.onContentTypeChanged(item, b));
+ b.setOnTouchListener(popupMenu.getDragToOpenListener());
+ b.setOnClickListener(v->popupMenu.show());
+ ContentType contentType = GlobalUserPreferences.accountsDefaultContentTypes.get(accountID);
+ b.setText(getContentTypeString(contentType));
+ contentTypeMenu = popupMenu.getMenu();
+ contentTypeMenu.findItem(ContentType.getContentTypeRes(contentType)).setChecked(true);
+ instance.ifPresent(i -> ContentType.adaptMenuToInstance(contentTypeMenu, i));
+ }));
+ items.add(new SmallTextItem(getString(R.string.sk_settings_default_content_type_explanation)));
+ items.add(new SwitchItem(R.string.sk_settings_support_local_only, 0, GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID), i->{
+ glitchModeItem.enabled = i.checked;
+ if (i.checked) {
+ GlobalUserPreferences.accountsWithLocalOnlySupport.add(accountID);
+ if (!isInstanceAkkoma()) {
+ GlobalUserPreferences.accountsInGlitchMode.add(accountID);
+ }
+ } else {
+ GlobalUserPreferences.accountsWithLocalOnlySupport.remove(accountID);
+ GlobalUserPreferences.accountsInGlitchMode.remove(accountID);
+ }
+ glitchModeItem.checked = GlobalUserPreferences.accountsInGlitchMode.contains(accountID);
+ if (list.findViewHolderForAdapterPosition(items.indexOf(glitchModeItem)) instanceof SwitchViewHolder svh) svh.rebind();
+ GlobalUserPreferences.save();
+ }));
+ items.add(new SmallTextItem(getString(R.string.sk_settings_local_only_explanation)));
+ items.add(glitchModeItem = new SwitchItem(R.string.sk_settings_glitch_instance, 0, GlobalUserPreferences.accountsInGlitchMode.contains(accountID), i->{
+ if (i.checked) {
+ GlobalUserPreferences.accountsInGlitchMode.add(accountID);
+ } else {
+ GlobalUserPreferences.accountsInGlitchMode.remove(accountID);
+ }
+ GlobalUserPreferences.save();
+ }));
+ glitchModeItem.enabled = GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID);
+ items.add(new SmallTextItem(getString(R.string.sk_settings_glitch_mode_explanation)));
+
+ items.add(new HeaderItem(R.string.sk_settings_about));
+ items.add(new TextItem(R.string.sk_settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/sk22/megalodon"), R.drawable.ic_fluent_open_24_regular));
+ items.add(new TextItem(R.string.sk_settings_donate, ()->UiUtils.launchWebBrowser(getActivity(), "https://ko-fi.com/xsk22"), R.drawable.ic_fluent_heart_24_regular));
+ LruCache, ?> cache = imageCache == null ? null : imageCache.getLruCache();
+ clearImageCacheItem = new TextItem(R.string.settings_clear_cache, UiUtils.formatFileSize(getContext(), cache != null ? cache.size() : 0, true), this::clearImageCache, 0);
+ items.add(clearImageCacheItem);
+ items.add(new TextItem(R.string.sk_clear_recent_languages, ()->UiUtils.showConfirmationAlert(getActivity(), R.string.sk_clear_recent_languages, R.string.sk_confirm_clear_recent_languages, R.string.clear, ()->{
+ GlobalUserPreferences.recentLanguages.remove(accountID);
+ GlobalUserPreferences.save();
+ })));
+ if (GithubSelfUpdater.needSelfUpdating()) {
+ items.add(new SwitchItem(R.string.sk_updater_enable_pre_releases, 0, GlobalUserPreferences.enablePreReleases, i->{
+ GlobalUserPreferences.enablePreReleases=i.checked;
+ GlobalUserPreferences.save();
+ }));
+ checkForUpdateItem = new TextItem(R.string.sk_check_for_update, GithubSelfUpdater.getInstance()::checkForUpdates);
+ items.add(checkForUpdateItem);
+ }
+
+ if(BuildConfig.DEBUG){
+ items.add(new RedHeaderItem("Debug options"));
+ items.add(new TextItem("Test e-mail confirmation flow", ()->{
+ AccountSession sess=AccountSessionManager.getInstance().getAccount(accountID);
+ sess.activated=false;
+ sess.activationInfo=new AccountActivationInfo("test@email", System.currentTimeMillis());
+ Bundle args=new Bundle();
+ args.putString("account", accountID);
+ args.putBoolean("debug", true);
+ Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args);
+ }));
+ }
+
+ items.add(new FooterItem(getString(R.string.sk_settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)));
+ }
+
+ private void updatePublishText(Button btn) {
+ if (GlobalUserPreferences.publishButtonText.isBlank()) btn.setText(R.string.publish);
+ else btn.setText(GlobalUserPreferences.publishButtonText);
+ }
+
+ @Override
+ public void onAttach(Activity activity){
+ super.onAttach(activity);
+ if(themeTransitionWindowView!=null){
+ // Activity has finished recreating. Remove the overlay.
+ MastodonApp.context.getSystemService(WindowManager.class).removeView(themeTransitionWindowView);
+ themeTransitionWindowView=null;
+ }
+ }
+
+ @Override
+ public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
+ list=new UsableRecyclerView(getActivity());
+ list.setLayoutManager(new LinearLayoutManager(getActivity()));
+ list.setAdapter(new SettingsAdapter());
+ list.setBackgroundColor(UiUtils.getThemeColor(getActivity(), android.R.attr.colorBackground));
+ list.setPadding(0, V.dp(16), 0, V.dp(12));
+ list.setClipToPadding(false);
+ list.addItemDecoration(new RecyclerView.ItemDecoration(){
+ @Override
+ public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
+ // Add 32dp gaps between sections
+ RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
+ if((holder instanceof HeaderViewHolder || holder instanceof FooterViewHolder) && holder.getAbsoluteAdapterPosition()>1)
+ outRect.top=V.dp(32);
+ }
+ });
+ return list;
+ }
+
+ @Override
+ public void onApplyWindowInsets(WindowInsets insets){
+ if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0){
+ list.setPadding(0, V.dp(16), 0, V.dp(12)+insets.getSystemWindowInsetBottom());
+ insets=insets.inset(0, 0, 0, insets.getSystemWindowInsetBottom());
+ }
+ super.onApplyWindowInsets(insets);
+ }
+
+ @Override
+ public void onDestroy(){
+ super.onDestroy();
+ if(needUpdateNotificationSettings && PushSubscriptionManager.arePushNotificationsAvailable()){
+ AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().updatePushSettings(pushSubscription);
+ }
+ if(needAppRestart) UiUtils.restartApp();
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState){
+ super.onViewCreated(view, savedInstanceState);
+ if(GithubSelfUpdater.needSelfUpdating())
+ E.register(this);
+ }
+
+ @Override
+ public void onDestroyView(){
+ super.onDestroyView();
+ if(GithubSelfUpdater.needSelfUpdating())
+ E.unregister(this);
+ }
+
+ private void onThemePreferenceClick(GlobalUserPreferences.ThemePreference theme){
+ GlobalUserPreferences.theme=theme;
+ GlobalUserPreferences.save();
+ restartActivityToApplyNewTheme();
+ }
+
+ private boolean onColorPreferenceClick(MenuItem item){
+ ColorPreference pref = null;
+ int id = item.getItemId();
+
+ if (id == R.id.m3_color) pref = ColorPreference.MATERIAL3;
+ else if (id == R.id.pink_color) pref = ColorPreference.PINK;
+ else if (id == R.id.purple_color) pref = ColorPreference.PURPLE;
+ else if (id == R.id.green_color) pref = ColorPreference.GREEN;
+ else if (id == R.id.blue_color) pref = ColorPreference.BLUE;
+ else if (id == R.id.brown_color) pref = ColorPreference.BROWN;
+ else if (id == R.id.red_color) pref = ColorPreference.RED;
+ else if (id == R.id.yellow_color) pref = ColorPreference.YELLOW;
+
+ if (pref == null) return false;
+
+ GlobalUserPreferences.color=pref;
+ GlobalUserPreferences.save();
+ restartActivityToApplyNewTheme();
+ return true;
+ }
+
+ private void onTrueBlackThemeChanged(SwitchItem item){
+ GlobalUserPreferences.trueBlackTheme=item.checked;
+ GlobalUserPreferences.save();
+
+ RecyclerView.ViewHolder themeHolder=list.findViewHolderForAdapterPosition(items.indexOf(themeItem));
+ if(themeHolder!=null){
+ ((ThemeViewHolder)themeHolder).bindSubitems();
+ }else{
+ list.getAdapter().notifyItemChanged(items.indexOf(themeItem));
+ }
+
+ if(UiUtils.isDarkTheme()){
+ restartActivityToApplyNewTheme();
+ }
+ }
+
+ private @StringRes int getContentTypeString(@Nullable ContentType contentType) {
+ if (contentType == null) return R.string.sk_content_type_unspecified;
+ return switch (contentType) {
+ case PLAIN -> R.string.sk_content_type_plain;
+ case HTML -> R.string.sk_content_type_html;
+ case MARKDOWN -> R.string.sk_content_type_markdown;
+ case BBCODE -> R.string.sk_content_type_bbcode;
+ case MISSKEY_MARKDOWN -> R.string.sk_content_type_mfm;
+ };
+ }
+
+ private boolean onContentTypeChanged(MenuItem item, Button btn){
+ int id = item.getItemId();
+ ContentType contentType = switch (id) {
+ case R.id.content_type_plain -> ContentType.PLAIN;
+ case R.id.content_type_html -> ContentType.HTML;
+ case R.id.content_type_markdown -> ContentType.MARKDOWN;
+ case R.id.content_type_bbcode -> ContentType.BBCODE;
+ case R.id.content_type_misskey_markdown -> ContentType.MISSKEY_MARKDOWN;
+ default -> null;
+ };
+ GlobalUserPreferences.accountsDefaultContentTypes.put(accountID, contentType);
+ GlobalUserPreferences.save();
+ btn.setText(getContentTypeString(contentType));
+ item.setChecked(true);
+ return true;
+ }
+
+ private boolean onReplyVisibilityChanged(MenuItem item, Button btn){
+ String pref = null;
+ int id = item.getItemId();
+
+ if (id == R.id.reply_visibility_following) pref = "following";
+ else if (id == R.id.reply_visibility_self) pref = "self";
+
+ GlobalUserPreferences.replyVisibility=pref;
+ GlobalUserPreferences.save();
+ btn.setText(GlobalUserPreferences.replyVisibility == null ?
+ R.string.sk_settings_reply_visibility_all :
+ switch(GlobalUserPreferences.replyVisibility){
+ case "following" -> R.string.sk_settings_reply_visibility_following;
+ case "self" -> R.string.sk_settings_reply_visibility_self;
+ default -> R.string.sk_settings_reply_visibility_all;
+ });
+ return true;
+ }
+
+ private void restartActivityToApplyNewTheme(){
+ // Calling activity.recreate() causes a black screen for like half a second.
+ // So, let's take a screenshot and overlay it on top to create the illusion of a smoother transition.
+ // As a bonus, we can fade it out to make it even smoother.
+ if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
+ View activityDecorView=getActivity().getWindow().getDecorView();
+ Bitmap bitmap=Bitmap.createBitmap(activityDecorView.getWidth(), activityDecorView.getHeight(), Bitmap.Config.ARGB_8888);
+ activityDecorView.draw(new Canvas(bitmap));
+ themeTransitionWindowView=new ImageView(MastodonApp.context);
+ themeTransitionWindowView.setImageBitmap(bitmap);
+ WindowManager.LayoutParams lp=new WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_APPLICATION);
+ lp.flags=WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
+ WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ lp.systemUiVisibility=View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
+ lp.systemUiVisibility|=(activityDecorView.getWindowSystemUiVisibility() & (View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR));
+ lp.width=lp.height=WindowManager.LayoutParams.MATCH_PARENT;
+ lp.token=getActivity().getWindow().getAttributes().token;
+ lp.windowAnimations=R.style.window_fade_out;
+ MastodonApp.context.getSystemService(WindowManager.class).addView(themeTransitionWindowView, lp);
+ }
+ getActivity().recreate();
+ }
+
+ private PushSubscription getPushSubscription(){
+ if(pushSubscription!=null)
+ return pushSubscription;
+ AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
+ if(session.pushSubscription==null){
+ pushSubscription=new PushSubscription();
+ pushSubscription.alerts=PushSubscription.Alerts.ofAll();
+ }else{
+ pushSubscription=session.pushSubscription.clone();
+ }
+ return pushSubscription;
+ }
+
+ private void onNotificationsChanged(PushNotification.Type type, boolean enabled){
+ PushSubscription subscription=getPushSubscription();
+ switch(type){
+ case FAVORITE -> subscription.alerts.favourite=enabled;
+ case FOLLOW -> subscription.alerts.follow=enabled;
+ case REBLOG -> subscription.alerts.reblog=enabled;
+ case MENTION -> subscription.alerts.mention=enabled;
+ case POLL -> subscription.alerts.poll=enabled;
+ case STATUS -> subscription.alerts.status=enabled;
+ case UPDATE -> subscription.alerts.update=enabled;
+ }
+ needUpdateNotificationSettings=true;
+ }
+
+ private void onNotificationsPolicyChanged(PushSubscription.Policy policy){
+ PushSubscription subscription=getPushSubscription();
+ PushSubscription.Policy prevPolicy=subscription.policy;
+ if(prevPolicy==policy)
+ return;
+ subscription.policy=policy;
+ int index=items.indexOf(notificationPolicyItem);
+ RecyclerView.ViewHolder policyHolder=list.findViewHolderForAdapterPosition(index);
+ if(policyHolder!=null){
+ ((NotificationPolicyViewHolder)policyHolder).rebind();
+ }else{
+ list.getAdapter().notifyItemChanged(index);
+ }
+ if((prevPolicy==PushSubscription.Policy.NONE)!=(policy==PushSubscription.Policy.NONE)){
+ boolean newState=policy!=PushSubscription.Policy.NONE;
+ for(PushNotification.Type value : PushNotification.Type.values()){
+ onNotificationsChanged(value, newState);
+ }
+ index++;
+ while(items.get(index) instanceof SwitchItem si){
+ si.enabled=si.checked=newState;
+ RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(index);
+ if(holder!=null)
+ ((BindableViewHolder>)holder).rebind();
+ else
+ list.getAdapter().notifyItemChanged(index);
+ index++;
+ }
+ }
+ needUpdateNotificationSettings=true;
+ }
+
+ private void confirmLogOut(){
+ new M3AlertDialogBuilder(getActivity())
+ .setTitle(R.string.log_out)
+ .setMessage(R.string.confirm_log_out)
+ .setPositiveButton(R.string.log_out, (dialog, which) -> logOut())
+ .setNegativeButton(R.string.cancel, null)
+ .show();
+ }
+
+ private void logOut(){
+ AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
+ new RevokeOauthToken(session.app.clientId, session.app.clientSecret, session.token.accessToken)
+ .setCallback(new Callback<>(){
+ @Override
+ public void onSuccess(Object result){
+ onLoggedOut();
+ }
+
+ @Override
+ public void onError(ErrorResponse error){
+ onLoggedOut();
+ }
+ })
+ .wrapProgress(getActivity(), R.string.loading, false)
+ .exec(accountID);
+ }
+
+ private void onLoggedOut(){
+ if (getActivity() == null) return;
+ AccountSessionManager.getInstance().removeAccount(accountID);
+ getActivity().finish();
+ Intent intent=new Intent(getActivity(), MainActivity.class);
+ startActivity(intent);
+ }
+
+ private void clearImageCache(){
+ MastodonAPIController.runInBackground(()->{
+ Activity activity=getActivity();
+ imageCache.clear();
+ Toast.makeText(activity, R.string.media_cache_cleared, Toast.LENGTH_SHORT).show();
+ });
+ if (list.findViewHolderForAdapterPosition(items.indexOf(clearImageCacheItem)) instanceof TextViewHolder tvh) {
+ clearImageCacheItem.secondaryText = UiUtils.formatFileSize(getContext(), 0, true);
+ tvh.rebind();
+ }
+ }
+
+ @Subscribe
+ public void onSelfUpdateStateChanged(SelfUpdateStateChangedEvent ev){
+ checkForUpdateItem.loading = ev.state == GithubSelfUpdater.UpdateState.CHECKING;
+ if (list.findViewHolderForAdapterPosition(items.indexOf(checkForUpdateItem)) instanceof TextViewHolder tvh) tvh.rebind();
+
+ UpdateItem updateItem = null;
+ if(items.get(0) instanceof UpdateItem item0) {
+ updateItem = item0;
+ } else if (ev.state != GithubSelfUpdater.UpdateState.CHECKING
+ && ev.state != GithubSelfUpdater.UpdateState.NO_UPDATE) {
+ updateItem = new UpdateItem();
+ items.add(0, updateItem);
+ list.setAdapter(new SettingsAdapter());
+ }
+
+ if(updateItem != null && list.findViewHolderForAdapterPosition(0) instanceof UpdateViewHolder uvh){
+ uvh.bind(updateItem);
+ }
+
+ if (ev.state == GithubSelfUpdater.UpdateState.NO_UPDATE) {
+ Toast.makeText(getActivity(), R.string.sk_no_update_available, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ return base.path(isInstanceAkkoma() ? "/about" : "/settings").build();
+ }
+
+ @Override
+ public String getAccountID() {
+ return accountID;
+ }
+
+ private static abstract class Item{
+ public abstract int getViewType();
+ }
+
+ private class HeaderItem extends Item{
+ private String text;
+
+ public HeaderItem(@StringRes int text){
+ this.text=getString(text);
+ }
+
+ public HeaderItem(String text){
+ this.text=text;
+ }
+
+ @Override
+ public int getViewType(){
+ return 0;
+ }
+ }
+
+ private class SwitchItem extends Item{
+ private String text;
+ private int icon;
+ private boolean checked;
+ private Consumer onChanged;
+ private boolean enabled=true;
+
+ public SwitchItem(@StringRes int text, @DrawableRes int icon, boolean checked, Consumer onChanged){
+ this.text=getString(text);
+ this.icon=icon;
+ this.checked=checked;
+ this.onChanged=onChanged;
+ }
+
+ public SwitchItem(@StringRes int text, @DrawableRes int icon, boolean checked, Consumer onChanged, boolean enabled){
+ this.text=getString(text);
+ this.icon=icon;
+ this.checked=checked;
+ this.onChanged=onChanged;
+ this.enabled=enabled;
+ }
+
+ @Override
+ public int getViewType(){
+ return 1;
+ }
+ }
+
+ public class ButtonItem extends Item{
+ private int text;
+ private int icon;
+ private Consumer buttonConsumer;
+
+ public ButtonItem(@StringRes int text, @DrawableRes int icon, Consumer buttonConsumer) {
+ this.text = text;
+ this.icon = icon;
+ this.buttonConsumer = buttonConsumer;
+ }
+
+ @Override
+ public int getViewType(){
+ return 8;
+ }
+ }
+
+ private static class ThemeItem extends Item{
+
+ @Override
+ public int getViewType(){
+ return 2;
+ }
+ }
+
+ private static class NotificationPolicyItem extends Item{
+
+ @Override
+ public int getViewType(){
+ return 3;
+ }
+ }
+
+ private class SmallTextItem extends Item {
+ private String text;
+
+ public SmallTextItem(String text) {
+ this.text = text;
+ }
+
+ @Override
+ public int getViewType() {
+ return 9;
+ }
+ }
+
+ private class TextItem extends Item{
+ private String text;
+ private String secondaryText;
+ private Runnable onClick;
+ private boolean loading;
+ private int icon;
+
+ public TextItem(@StringRes int text, Runnable onClick) {
+ this(text, null, onClick, false, 0);
+ }
+
+ public TextItem(@StringRes int text, Runnable onClick, @DrawableRes int icon) {
+ this(text, null, onClick, false, icon);
+ }
+
+ public TextItem(@StringRes int text, String secondaryText, Runnable onClick, @DrawableRes int icon) {
+ this(text, secondaryText, onClick, false, icon);
+ }
+
+ public TextItem(@StringRes int text, String secondaryText, Runnable onClick, boolean loading, @DrawableRes int icon){
+ this.text=getString(text);
+ this.onClick=onClick;
+ this.loading=loading;
+ this.icon=icon;
+ this.secondaryText = secondaryText;
+ }
+
+ public TextItem(String text, Runnable onClick){
+ this.text=text;
+ this.onClick=onClick;
+ }
+
+ @Override
+ public int getViewType(){
+ return 4;
+ }
+ }
+
+ private class RedHeaderItem extends HeaderItem{
+
+ public RedHeaderItem(int text){
+ super(text);
+ }
+
+ public RedHeaderItem(String text){
+ super(text);
+ }
+
+ @Override
+ public int getViewType(){
+ return 5;
+ }
+ }
+
+ private class FooterItem extends Item{
+ private String text;
+
+ public FooterItem(String text){
+ this.text=text;
+ }
+
+ @Override
+ public int getViewType(){
+ return 6;
+ }
+ }
+
+ private class UpdateItem extends Item{
+
+ @Override
+ public int getViewType(){
+ return 7;
+ }
+ }
+
+ private class SettingsAdapter extends RecyclerView.Adapter>{
+ @NonNull
+ @Override
+ public BindableViewHolder- onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
+ //noinspection unchecked
+ return (BindableViewHolder
- ) switch(viewType){
+ case 0 -> new HeaderViewHolder(false);
+ case 1 -> new SwitchViewHolder();
+ case 2 -> new ThemeViewHolder();
+ case 3 -> new NotificationPolicyViewHolder();
+ case 4 -> new TextViewHolder();
+ case 5 -> new HeaderViewHolder(true);
+ case 6 -> new FooterViewHolder();
+ case 7 -> new UpdateViewHolder();
+ case 8 -> new ButtonViewHolder();
+ case 9 -> new SmallTextViewHolder();
+ default -> throw new IllegalStateException("Unexpected value: "+viewType);
+ };
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull BindableViewHolder
- holder, int position){
+ holder.bind(items.get(position));
+ }
+
+ @Override
+ public int getItemCount(){
+ return items.size();
+ }
+
+ @Override
+ public int getItemViewType(int position){
+ return items.get(position).getViewType();
+ }
+ }
+
+ private class HeaderViewHolder extends BindableViewHolder
{
+ private final TextView text;
+ public HeaderViewHolder(boolean red){
+ super(getActivity(), R.layout.item_settings_header, list);
+ text=(TextView) itemView;
+ if(red)
+ text.setTextColor(getResources().getColor(UiUtils.isDarkTheme() ? R.color.error_400 : R.color.error_700));
+ }
+
+ @Override
+ public void onBind(HeaderItem item){
+ text.setText(item.text);
+ }
+ }
+
+ private class SwitchViewHolder extends BindableViewHolder implements UsableRecyclerView.DisableableClickable{
+ private final TextView text;
+ private final ImageView icon;
+ private final Switch checkbox;
+
+ public SwitchViewHolder(){
+ super(getActivity(), R.layout.item_settings_switch, list);
+ text=findViewById(R.id.text);
+ icon=findViewById(R.id.icon);
+ checkbox=findViewById(R.id.checkbox);
+ }
+
+ @Override
+ public void onBind(SwitchItem item){
+ text.setText(item.text);
+ if (item.icon == 0) {
+ icon.setVisibility(View.GONE);
+ } else {
+ icon.setVisibility(View.VISIBLE);
+ icon.setImageResource(item.icon);
+ }
+ checkbox.setChecked(item.checked && item.enabled);
+ checkbox.setEnabled(item.enabled);
+ }
+
+ @Override
+ public void onClick(){
+ item.checked=!item.checked;
+ checkbox.setChecked(item.checked);
+ item.onChanged.accept(item);
+ }
+
+ @Override
+ public boolean isEnabled(){
+ return item.enabled;
+ }
+ }
+
+ private class ThemeViewHolder extends BindableViewHolder{
+ private SubitemHolder autoHolder, lightHolder, darkHolder;
+
+ public ThemeViewHolder(){
+ super(getActivity(), R.layout.item_settings_theme, list);
+ autoHolder=new SubitemHolder(findViewById(R.id.theme_auto));
+ lightHolder=new SubitemHolder(findViewById(R.id.theme_light));
+ darkHolder=new SubitemHolder(findViewById(R.id.theme_dark));
+ }
+
+ @Override
+ public void onBind(ThemeItem item){
+ bindSubitems();
+ }
+
+ public void bindSubitems(){
+ autoHolder.bind(R.string.theme_auto, GlobalUserPreferences.trueBlackTheme ? R.drawable.theme_auto_trueblack : R.drawable.theme_auto, GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.AUTO);
+ lightHolder.bind(R.string.theme_light, R.drawable.theme_light, GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.LIGHT);
+ darkHolder.bind(R.string.theme_dark, GlobalUserPreferences.trueBlackTheme ? R.drawable.theme_dark_trueblack : R.drawable.theme_dark, GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.DARK);
+ }
+
+ private void onSubitemClick(View v){
+ GlobalUserPreferences.ThemePreference pref;
+ if(v.getId()==R.id.theme_auto)
+ pref=GlobalUserPreferences.ThemePreference.AUTO;
+ else if(v.getId()==R.id.theme_light)
+ pref=GlobalUserPreferences.ThemePreference.LIGHT;
+ else if(v.getId()==R.id.theme_dark)
+ pref=GlobalUserPreferences.ThemePreference.DARK;
+ else
+ return;
+ onThemePreferenceClick(pref);
+ }
+
+ private class SubitemHolder{
+ public TextView text;
+ public ImageView icon;
+ public RadioButton checkbox;
+
+ public SubitemHolder(View view){
+ text=view.findViewById(R.id.text);
+ icon=view.findViewById(R.id.icon);
+ checkbox=view.findViewById(R.id.checkbox);
+ view.setOnClickListener(ThemeViewHolder.this::onSubitemClick);
+
+ icon.setClipToOutline(true);
+ icon.setOutlineProvider(OutlineProviders.roundedRect(4));
+ }
+
+ public void bind(int text, int icon, boolean checked){
+ this.text.setText(text);
+ this.icon.setImageResource(icon);
+ checkbox.setChecked(checked);
+ }
+
+ public void setChecked(boolean checked){
+ checkbox.setChecked(checked);
+ }
+ }
+ }
+ private class ButtonViewHolder extends BindableViewHolder{
+ private final Button button;
+ private final ImageView icon;
+ private final TextView text;
+
+ @SuppressLint("ClickableViewAccessibility")
+ public ButtonViewHolder(){
+ super(getActivity(), R.layout.item_settings_button, list);
+ text=findViewById(R.id.text);
+ icon=findViewById(R.id.icon);
+ button=findViewById(R.id.button);
+ }
+
+ @Override
+ public void onBind(ButtonItem item){
+ text.setText(item.text);
+ if (item.icon == 0) {
+ icon.setVisibility(View.GONE);
+ } else {
+ icon.setImageResource(item.icon);
+ }
+ item.buttonConsumer.accept(button);
+ }
+ }
+
+
+ private class NotificationPolicyViewHolder extends BindableViewHolder{
+ private final Button button;
+ private final PopupMenu popupMenu;
+
+ @SuppressLint("ClickableViewAccessibility")
+ public NotificationPolicyViewHolder(){
+ super(getActivity(), R.layout.item_settings_notification_policy, list);
+ button=findViewById(R.id.button);
+ popupMenu=new PopupMenu(getActivity(), button, Gravity.CENTER_HORIZONTAL);
+ popupMenu.inflate(R.menu.notification_policy);
+ popupMenu.setOnMenuItemClickListener(item->{
+ PushSubscription.Policy policy;
+ int id=item.getItemId();
+ if(id==R.id.notify_anyone)
+ policy=PushSubscription.Policy.ALL;
+ else if(id==R.id.notify_followed)
+ policy=PushSubscription.Policy.FOLLOWED;
+ else if(id==R.id.notify_follower)
+ policy=PushSubscription.Policy.FOLLOWER;
+ else if(id==R.id.notify_none)
+ policy=PushSubscription.Policy.NONE;
+ else
+ return false;
+ onNotificationsPolicyChanged(policy);
+ return true;
+ });
+ UiUtils.enablePopupMenuIcons(getActivity(), popupMenu);
+ button.setOnTouchListener(popupMenu.getDragToOpenListener());
+ button.setOnClickListener(v->popupMenu.show());
+ }
+
+ @Override
+ public void onBind(NotificationPolicyItem item){
+ button.setText(switch(getPushSubscription().policy){
+ case ALL -> R.string.notify_anyone;
+ case FOLLOWED -> R.string.notify_followed;
+ case FOLLOWER -> R.string.notify_follower;
+ case NONE -> R.string.notify_none;
+ });
+ }
+ }
+
+ private class TextViewHolder extends BindableViewHolder implements UsableRecyclerView.Clickable{
+ private final TextView text, secondaryText;
+ private final ProgressBar progress;
+ private final ImageView icon;
+
+ public TextViewHolder(){
+ super(getActivity(), R.layout.item_settings_text, list);
+ text = itemView.findViewById(R.id.text);
+ secondaryText = itemView.findViewById(R.id.secondary_text);
+ progress = itemView.findViewById(R.id.progress);
+ icon = itemView.findViewById(R.id.icon);
+ }
+
+ @Override
+ public void onBind(TextItem item){
+ icon.setVisibility(item.icon != 0 ? View.VISIBLE : View.GONE);
+ secondaryText.setVisibility(item.secondaryText != null ? View.VISIBLE : View.GONE);
+
+ text.setText(item.text);
+ progress.animate().alpha(item.loading ? 1 : 0);
+ icon.setImageResource(item.icon);
+ secondaryText.setText(item.secondaryText);
+ }
+
+ @Override
+ public void onClick(){
+ item.onClick.run();
+ }
+ }
+
+ private class SmallTextViewHolder extends BindableViewHolder {
+ private final TextView text;
+
+ public SmallTextViewHolder(){
+ super(getActivity(), R.layout.item_settings_text, list);
+ text = itemView.findViewById(R.id.text);
+ text.setTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorSecondary));
+ text.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
+ text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
+ text.setPaddingRelative(text.getPaddingStart(), 0, text.getPaddingEnd(), text.getPaddingBottom());
+ }
+
+ @Override
+ public void onBind(SmallTextItem item){
+ text.setText(item.text);
+ }
+ }
+
+ private class FooterViewHolder extends BindableViewHolder{
+ private final TextView text;
+ public FooterViewHolder(){
+ super(getActivity(), R.layout.item_settings_footer, list);
+ text=(TextView) itemView;
+ }
+
+ @Override
+ public void onBind(FooterItem item){
+ text.setText(item.text);
+ }
+ }
+
+ private class UpdateViewHolder extends BindableViewHolder{
+
+ private final TextView text, changelog;
+ private final Button button;
+ private final ImageButton cancelBtn;
+ private final ProgressBar progress;
+
+ private ObjectAnimator rotationAnimator;
+ private Runnable progressUpdater=this::updateProgress;
+
+ public UpdateViewHolder(){
+ super(getActivity(), R.layout.item_settings_update, list);
+ text=findViewById(R.id.text);
+ changelog=findViewById(R.id.changelog);
+ button=findViewById(R.id.button);
+ cancelBtn=findViewById(R.id.cancel_btn);
+ progress=findViewById(R.id.progress);
+ button.setOnClickListener(v->{
+ GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
+ switch(updater.getState()){
+ case UPDATE_AVAILABLE -> updater.downloadUpdate();
+ case DOWNLOADED -> updater.installUpdate(getActivity());
+ }
+ });
+ cancelBtn.setOnClickListener(v->GithubSelfUpdater.getInstance().cancelDownload());
+ rotationAnimator=ObjectAnimator.ofFloat(progress, View.ROTATION, 0f, 360f);
+ rotationAnimator.setInterpolator(new LinearInterpolator());
+ rotationAnimator.setDuration(1500);
+ rotationAnimator.setRepeatCount(ObjectAnimator.INFINITE);
+ }
+
+ @Override
+ public void onBind(UpdateItem item){
+ GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
+ GithubSelfUpdater.UpdateState state=updater.getState();
+ if (state == GithubSelfUpdater.UpdateState.CHECKING) return;
+ GithubSelfUpdater.UpdateInfo info=updater.getUpdateInfo();
+ if(state!=GithubSelfUpdater.UpdateState.DOWNLOADED){
+ text.setText(getString(R.string.sk_update_available, info.version));
+ button.setText(getString(R.string.download_update, UiUtils.formatFileSize(getActivity(), info.size, false)));
+ }else{
+ text.setText(getString(R.string.sk_update_ready, info.version));
+ button.setText(R.string.install_update);
+ }
+ if(state==GithubSelfUpdater.UpdateState.DOWNLOADING){
+ rotationAnimator.start();
+ button.setVisibility(View.INVISIBLE);
+ cancelBtn.setVisibility(View.VISIBLE);
+ progress.setVisibility(View.VISIBLE);
+ updateProgress();
+ }else{
+ rotationAnimator.cancel();
+ button.setVisibility(View.VISIBLE);
+ cancelBtn.setVisibility(View.GONE);
+ progress.setVisibility(View.GONE);
+ progress.removeCallbacks(progressUpdater);
+ }
+ changelog.setText(info.changelog);
+ }
+
+ private void updateProgress(){
+ GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
+ if(updater.getState()!=GithubSelfUpdater.UpdateState.DOWNLOADING)
+ return;
+ int value=Math.round(progress.getMax()*updater.getDownloadProgress());
+ if(Build.VERSION.SDK_INT>=24)
+ progress.setProgress(value, true);
+ else
+ progress.setProgress(value);
+ progress.postDelayed(progressUpdater, 1000);
+ }
+ }
+}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusEditHistoryFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusEditHistoryFragment.java
index 4cf0e0639..5d592f7fd 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusEditHistoryFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusEditHistoryFragment.java
@@ -1,6 +1,7 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
+import android.net.Uri;
import android.os.Bundle;
import android.view.View;
@@ -24,13 +25,13 @@ import java.util.stream.Collectors;
import me.grishka.appkit.api.SimpleCallback;
public class StatusEditHistoryFragment extends StatusListFragment{
- private String id;
-
+ private String id, url;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
id=getArguments().getString("id");
+ url=getArguments().getString("url");
loadData();
}
@@ -162,4 +163,9 @@ public class StatusEditHistoryFragment extends StatusListFragment{
protected Filter.FilterContext getFilterContext() {
return null;
}
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ return Uri.parse(url);
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java
index 90a346024..0e400fd6e 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java
@@ -1,5 +1,6 @@
package org.joinmastodon.android.fragments;
+import android.app.assist.AssistContent;
import android.content.res.Configuration;
import android.os.Bundle;
@@ -196,7 +197,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment
}
@Override
- public void onConfigurationChanged(Configuration newConfig){
+ public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (getParentFragment() instanceof HomeTabFragment home) home.updateToolbarLogo();
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java
index 504c72e67..623f6b94b 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java
@@ -1,9 +1,13 @@
package org.joinmastodon.android.fragments;
+import android.net.Uri;
import android.os.Bundle;
+import android.util.Pair;
import android.view.View;
import org.joinmastodon.android.DomainManager;
+import androidx.annotation.NonNull;
+
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
import org.joinmastodon.android.api.session.AccountSession;
@@ -16,27 +20,51 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusContext;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
+import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
+import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.UiUtils;
+import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import org.parceler.Parcels;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import me.grishka.appkit.api.SimpleCallback;
-public class ThreadFragment extends StatusListFragment implements DomainDisplay{
+public class ThreadFragment extends StatusListFragment implements ProvidesAssistContent {
protected Status mainStatus;
- @Override
- public String getDomain() {
- return mainStatus.url;
- }
+ /**
+ * lists the hierarchy of ancestors and descendants in a thread. level 0 = the main status.
+ * e.g.
+ *
+ * [0] ancestor: -2 ↰
+ * [1] ancestor: -1 ↰
+ * [2] main status: 0 ↰
+ * [3] descendant: 1 ↰
+ * [4] descendant: 2 ↰
+ * [5] descendant: 3
+ * [6] descendant: 1
+ * [7] descendant: 1 ↰
+ * [8] descendant: 2
+ *
+ * confused? good. /j
+ */
+ private final List> levels = new ArrayList<>();
+ private final HashMap ancestryMap = new HashMap<>();
@Override
public void onCreate(Bundle savedInstanceState){
@@ -48,21 +76,50 @@ public class ThreadFragment extends StatusListFragment implements DomainDisplay{
data.add(mainStatus);
onAppendItems(Collections.singletonList(mainStatus));
setTitle(HtmlParser.parseCustomEmoji(getString(R.string.post_from_user, mainStatus.account.displayName), mainStatus.account.emojis));
-
- DomainManager.getInstance().setCurrentDomain(getDomain());
}
@Override
protected List buildDisplayItems(Status s){
List items=super.buildDisplayItems(s);
- if(s.id.equals(mainStatus.id)){
- for(StatusDisplayItem item:items){
+ // "what the fuck is a deque"? yes
+ // (it's just so the last-added item automatically comes first when looping over it)
+ Deque deleteTheseItems = new ArrayDeque<>();
+
+ // modifying hidden filtered items if status is displayed as a warning
+ List itemsToModify =
+ (items.get(0) instanceof WarningFilteredStatusDisplayItem warning)
+ ? warning.filteredItems
+ : items;
+
+ for(int i = 0; i < itemsToModify.size(); i++){
+ StatusDisplayItem item = itemsToModify.get(i);
+ NeighborAncestryInfo ancestryInfo = ancestryMap.get(s.id);
+ if (ancestryInfo != null) {
+ item.setAncestryInfo(
+ ancestryInfo.hasDescendantNeighbor(),
+ ancestryInfo.hasAncestoringNeighbor(),
+ s.id.equals(mainStatus.id),
+ ancestryInfo.getAncestoringNeighbor()
+ .map(ancestor -> ancestor.id.equals(mainStatus.id))
+ .orElse(false)
+ );
+ }
+
+ if (item instanceof ReblogOrReplyLineStatusDisplayItem &&
+ (!item.isDirectDescendant && item.hasAncestoringNeighbor)) {
+ deleteTheseItems.add(i);
+ }
+
+ if(s.id.equals(mainStatus.id)){
if(item instanceof TextStatusDisplayItem text)
text.textSelectable=true;
else if(item instanceof FooterStatusDisplayItem footer)
footer.hideCounts=true;
}
- items.add(new ExtendedFooterStatusDisplayItem(s.id, this, accountID, s.getContentStatus()));
+ }
+ for (int deleteThisItem : deleteTheseItems) itemsToModify.remove(deleteThisItem);
+ if(s.id.equals(mainStatus.id)) {
+ items.add(new ExtendedFooterStatusDisplayItem(s.id, this, s.getContentStatus()));
}
return items;
}
@@ -76,36 +133,22 @@ public class ThreadFragment extends StatusListFragment implements DomainDisplay{
if (getActivity() == null) return;
if(refreshing){
data.clear();
+ ancestryMap.clear();
displayItems.clear();
data.add(mainStatus);
onAppendItems(Collections.singletonList(mainStatus));
}
- AccountSession account=AccountSessionManager.getInstance().getAccount(accountID);
- Instance instance=AccountSessionManager.getInstance().getInstanceInfo(account.domain);
- if(instance.pleroma != null){
- List threadIds=new ArrayList<>();
- threadIds.add(mainStatus.id);
- for(Status s:result.descendants){
- if(threadIds.contains(s.inReplyToId)){
- threadIds.add(s.id);
- }
- }
- threadIds.add(mainStatus.inReplyToId);
- for(int i=result.ancestors.size()-1; i >= 0; i--){
- Status s=result.ancestors.get(i);
- if(s.inReplyToId != null && threadIds.contains(s.id)){
- threadIds.add(s.inReplyToId);
- }
- }
- result.ancestors=result.ancestors.stream().filter(s -> threadIds.contains(s.id)).collect(Collectors.toList());
- result.descendants=getDescendantsOrdered(mainStatus.id,
- result.descendants.stream()
- .filter(s -> threadIds.contains(s.id))
- .collect(Collectors.toList()));
- }
+ // TODO: figure out how this code works
+ if(isInstanceAkkoma()) sortStatusContext(mainStatus, result);
+
result.descendants=filterStatuses(result.descendants);
result.ancestors=filterStatuses(result.ancestors);
+
+ for (NeighborAncestryInfo i : mapNeighborhoodAncestry(mainStatus, result)) {
+ ancestryMap.put(i.status.id, i);
+ }
+
if(footerProgress!=null)
footerProgress.setVisibility(View.GONE);
data.addAll(result.descendants);
@@ -114,7 +157,12 @@ public class ThreadFragment extends StatusListFragment implements DomainDisplay{
int count=displayItems.size();
if(!refreshing)
adapter.notifyItemRangeInserted(prevCount, count-prevCount);
- prependItems(result.ancestors, !refreshing);
+ int prependedCount = prependItems(result.ancestors, !refreshing);
+ if (prependedCount > 0 && displayItems.get(prependedCount) instanceof ReblogOrReplyLineStatusDisplayItem) {
+ displayItems.remove(prependedCount);
+ adapter.notifyItemRemoved(prependedCount);
+ count--;
+ }
dataLoaded();
if(refreshing){
refreshDone();
@@ -126,7 +174,61 @@ public class ThreadFragment extends StatusListFragment implements DomainDisplay{
.exec(accountID);
}
- private List getDescendantsOrdered(String id, List statuses){
+ public static List mapNeighborhoodAncestry(Status mainStatus, StatusContext context) {
+ List ancestry = new ArrayList<>();
+
+ List statuses = new ArrayList<>(context.ancestors);
+ statuses.add(mainStatus);
+ statuses.addAll(context.descendants);
+
+ int count = statuses.size();
+ for (int index = 0; index < count; index++) {
+ Status current = statuses.get(index);
+ NeighborAncestryInfo item = new NeighborAncestryInfo(current);
+
+ item.descendantNeighbor = Optional
+ .ofNullable(count > index + 1 ? statuses.get(index + 1) : null)
+ .filter(s -> s.inReplyToId.equals(current.id))
+ .orElse(null);
+
+ item.ancestoringNeighbor = Optional.ofNullable(index > 0 ? ancestry.get(index - 1) : null)
+ .filter(ancestor -> ancestor
+ .getDescendantNeighbor()
+ .map(ancestorsDescendant -> ancestorsDescendant.id.equals(current.id))
+ .orElse(false))
+ .flatMap(NeighborAncestryInfo::getStatus)
+ .orElse(null);
+
+ ancestry.add(item);
+ }
+
+ return ancestry;
+ }
+
+ public static void sortStatusContext(Status mainStatus, StatusContext context) {
+ List threadIds=new ArrayList<>();
+ threadIds.add(mainStatus.id);
+ for(Status s:context.descendants){
+ if(threadIds.contains(s.inReplyToId)){
+ threadIds.add(s.id);
+ }
+ }
+ threadIds.add(mainStatus.inReplyToId);
+ for(int i=context.ancestors.size()-1; i >= 0; i--){
+ Status s=context.ancestors.get(i);
+ if(s.inReplyToId != null && threadIds.contains(s.id)){
+ threadIds.add(s.inReplyToId);
+ }
+ }
+
+ context.ancestors=context.ancestors.stream().filter(s -> threadIds.contains(s.id)).collect(Collectors.toList());
+ context.descendants=getDescendantsOrdered(mainStatus.id,
+ context.descendants.stream()
+ .filter(s -> threadIds.contains(s.id))
+ .collect(Collectors.toList()));
+ }
+
+ private static List getDescendantsOrdered(String id, List statuses){
List out=new ArrayList<>();
for(Status s:getDirectDescendants(id, statuses)){
out.add(s);
@@ -138,7 +240,7 @@ public class ThreadFragment extends StatusListFragment implements DomainDisplay{
return out;
}
- private List getDirectDescendants(String id, List statuses){
+ private static List getDirectDescendants(String id, List statuses){
return statuses.stream()
.filter(s -> s.inReplyToId.equals(id))
.collect(Collectors.toList());
@@ -195,4 +297,52 @@ public class ThreadFragment extends StatusListFragment implements DomainDisplay{
protected Filter.FilterContext getFilterContext() {
return Filter.FilterContext.THREAD;
}
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ return Uri.parse(mainStatus.url);
+ }
+
+ public static class NeighborAncestryInfo {
+ protected Status status, descendantNeighbor, ancestoringNeighbor;
+
+ public NeighborAncestryInfo(@NonNull Status status) {
+ this.status = status;
+ }
+
+ public Optional getStatus() {
+ return Optional.ofNullable(status);
+ }
+
+ public Optional getDescendantNeighbor() {
+ return Optional.ofNullable(descendantNeighbor);
+ }
+
+ public Optional getAncestoringNeighbor() {
+ return Optional.ofNullable(ancestoringNeighbor);
+ }
+
+ public boolean hasDescendantNeighbor() {
+ return getDescendantNeighbor().isPresent();
+ }
+
+ public boolean hasAncestoringNeighbor() {
+ return getAncestoringNeighbor().isPresent();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ NeighborAncestryInfo that = (NeighborAncestryInfo) o;
+ return status.equals(that.status)
+ && Objects.equals(descendantNeighbor, that.descendantNeighbor)
+ && Objects.equals(ancestoringNeighbor, that.ancestoringNeighbor);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(status, descendantNeighbor, ancestoringNeighbor);
+ }
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/AccountRelatedAccountListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/AccountRelatedAccountListFragment.java
index 8277a5b41..3a27bca56 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/AccountRelatedAccountListFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/AccountRelatedAccountListFragment.java
@@ -1,5 +1,6 @@
package org.joinmastodon.android.fragments.account_list;
+import android.net.Uri;
import android.os.Bundle;
import org.joinmastodon.android.model.Account;
@@ -14,4 +15,11 @@ public abstract class AccountRelatedAccountListFragment extends PaginatedAccount
account=Parcels.unwrap(getArguments().getParcelable("targetAccount"));
setTitle("@"+account.acct);
}
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ return base.path(isInstanceAkkoma()
+ ? "/users/" + account.id
+ : '@' + account.acct).build();
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/BaseAccountListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/BaseAccountListFragment.java
index 36f9913d2..cdf0e9625 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/BaseAccountListFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/BaseAccountListFragment.java
@@ -1,6 +1,7 @@
package org.joinmastodon.android.fragments.account_list;
import android.app.ProgressDialog;
+import android.app.assist.AssistContent;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.drawable.Animatable;
@@ -23,7 +24,8 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
import org.joinmastodon.android.api.session.AccountSessionManager;
-import org.joinmastodon.android.fragments.ListTimelinesFragment;
+import org.joinmastodon.android.fragments.HasAccountID;
+import org.joinmastodon.android.fragments.ListsFragment;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.RecyclerFragment;
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
@@ -34,6 +36,7 @@ import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import org.joinmastodon.android.ui.utils.UiUtils;
+import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.parceler.Parcels;
import java.util.ArrayList;
@@ -57,7 +60,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
-public abstract class BaseAccountListFragment extends RecyclerFragment {
+public abstract class BaseAccountListFragment extends RecyclerFragment implements ProvidesAssistContent.ProvidesWebUri {
protected HashMap relationships=new HashMap<>();
protected String accountID;
protected ArrayList> relationshipsRequests=new ArrayList<>();
@@ -175,6 +178,16 @@ public abstract class BaseAccountListFragment extends RecyclerFragment implements ImageLoaderRecyclerAdapter{
public AccountsAdapter(){
super(imgLoader);
@@ -406,7 +419,7 @@ public abstract class BaseAccountListFragment extends RecyclerFragment onCreateRemoteRequest(String id, String maxID, int count){
- return new GetAccountFollowers(id, maxID, count);
+ public Uri getWebUri(Uri.Builder base) {
+ return super.getWebUri(base).buildUpon()
+ .appendPath(isInstanceAkkoma() ? "#followers" : "/followers").build();
}
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/FollowingListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/FollowingListFragment.java
index 33ca43734..a9b73e921 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/FollowingListFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/FollowingListFragment.java
@@ -1,5 +1,6 @@
package org.joinmastodon.android.fragments.account_list;
+import android.net.Uri;
import android.os.Bundle;
import org.joinmastodon.android.R;
@@ -12,7 +13,6 @@ public class FollowingListFragment extends AccountRelatedAccountListFragment{
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
- targetAccount = account;
setSubtitle(getResources().getQuantityString(R.plurals.x_following, (int)(account.followingCount%1000), account.followingCount));
}
@@ -22,7 +22,8 @@ public class FollowingListFragment extends AccountRelatedAccountListFragment{
}
@Override
- public HeaderPaginationRequest onCreateRemoteRequest(String id, String maxID, int count){
- return new GetAccountFollowing(id, maxID, count);
+ public Uri getWebUri(Uri.Builder base) {
+ return super.getWebUri(base).buildUpon()
+ .appendPath(isInstanceAkkoma() ? "#followees" : "/following").build();
}
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/StatusFavoritesListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/StatusFavoritesListFragment.java
index 9e8f16430..55629e8cf 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/StatusFavoritesListFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/StatusFavoritesListFragment.java
@@ -1,5 +1,6 @@
package org.joinmastodon.android.fragments.account_list;
+import android.net.Uri;
import android.os.Bundle;
import org.joinmastodon.android.R;
@@ -19,6 +20,14 @@ public class StatusFavoritesListFragment extends StatusRelatedAccountListFragmen
return new GetStatusFavorites(status.id, maxID, count);
}
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ Uri statusUri = super.getWebUri(base);
+ return isInstanceAkkoma()
+ ? statusUri
+ : statusUri.buildUpon().appendPath("favourites").build();
+ }
+
@Override
public HeaderPaginationRequest onCreateRemoteRequest(String id, String maxID, int count) {
return null;
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/StatusReblogsListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/StatusReblogsListFragment.java
index 229e64a13..3505df53a 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/StatusReblogsListFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/StatusReblogsListFragment.java
@@ -1,5 +1,6 @@
package org.joinmastodon.android.fragments.account_list;
+import android.net.Uri;
import android.os.Bundle;
import org.joinmastodon.android.R;
@@ -19,6 +20,14 @@ public class StatusReblogsListFragment extends StatusRelatedAccountListFragment{
return new GetStatusReblogs(status.id, maxID, count);
}
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ Uri statusUri = super.getWebUri(base);
+ return isInstanceAkkoma()
+ ? statusUri
+ : statusUri.buildUpon().appendPath("reblogs").build();
+ }
+
@Override
public HeaderPaginationRequest onCreateRemoteRequest(String id, String maxID, int count) {
return null;
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/StatusRelatedAccountListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/StatusRelatedAccountListFragment.java
index db54c4850..d0499afa6 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/StatusRelatedAccountListFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/StatusRelatedAccountListFragment.java
@@ -1,5 +1,6 @@
package org.joinmastodon.android.fragments.account_list;
+import android.net.Uri;
import android.os.Bundle;
import org.joinmastodon.android.model.Status;
@@ -18,4 +19,13 @@ public abstract class StatusRelatedAccountListFragment extends PaginatedAccountL
protected boolean hasSubtitle(){
return false;
}
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ return base
+ .encodedPath(isInstanceAkkoma()
+ ? "/notice/" + status.id
+ : '@' + status.account.acct + '/' + status.id)
+ .build();
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/BubbleTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/BubbleTimelineFragment.java
new file mode 100644
index 000000000..3dee85c25
--- /dev/null
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/BubbleTimelineFragment.java
@@ -0,0 +1,61 @@
+package org.joinmastodon.android.fragments.discover;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+
+import org.joinmastodon.android.api.requests.timelines.GetBubbleTimeline;
+import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
+import org.joinmastodon.android.fragments.StatusListFragment;
+import org.joinmastodon.android.model.Filter;
+import org.joinmastodon.android.model.Status;
+import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
+import org.joinmastodon.android.utils.StatusFilterPredicate;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import me.grishka.appkit.api.SimpleCallback;
+
+public class BubbleTimelineFragment extends StatusListFragment {
+ private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.BUBBLE_TIMELINE);
+ private String maxID;
+
+ @Override
+ protected boolean wantsComposeButton() {
+ return true;
+ }
+
+
+ @Override
+ protected void doLoadData(int offset, int count){
+ currentRequest=new GetBubbleTimeline(refreshing ? null : maxID, count)
+ .setCallback(new SimpleCallback<>(this){
+ @Override
+ public void onSuccess(List result){
+ if(!result.isEmpty())
+ maxID=result.get(result.size()-1).id;
+ if (getActivity() == null) return;
+ result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
+ onDataLoaded(result, !result.isEmpty());
+ }
+ })
+ .exec(accountID);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState){
+ super.onViewCreated(view, savedInstanceState);
+ bannerHelper.maybeAddBanner(contentWrap);
+ }
+
+ @Override
+ protected Filter.FilterContext getFilterContext() {
+ return Filter.FilterContext.PUBLIC;
+ }
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ return isInstanceAkkoma() ? base.path("/main/bubble").build() : null;
+ }
+}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverAccountsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverAccountsFragment.java
index 73e7e99d5..0a68f22c8 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverAccountsFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverAccountsFragment.java
@@ -3,6 +3,7 @@ package org.joinmastodon.android.fragments.discover;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
@@ -15,7 +16,6 @@ import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
-import org.joinmastodon.android.fragments.DomainDisplay;
import org.joinmastodon.android.fragments.IsOnTop;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.RecyclerFragment;
@@ -28,6 +28,7 @@ import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ProgressBarButton;
+import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.parceler.Parcels;
import java.util.Collections;
@@ -51,7 +52,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
-public class DiscoverAccountsFragment extends BaseRecyclerFragment implements ScrollableToTop, IsOnTop, DomainDisplay {
+public class DiscoverAccountsFragment extends RecyclerFragment implements ScrollableToTop, IsOnTop, ProvidesAssistContent.ProvidesWebUri {
private String accountID;
private Map relationships=Collections.emptyMap();
private GetAccountRelationships relationshipsRequest;
@@ -60,11 +61,6 @@ public class DiscoverAccountsFragment extends BaseRecyclerFragment implements ImageLoaderRecyclerAdapter{
public AccountsAdapter(){
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java
index 21354eefe..66dbdcc19 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java
@@ -1,6 +1,7 @@
package org.joinmastodon.android.fragments.discover;
import android.app.Fragment;
+import android.app.assist.AssistContent;
import android.app.FragmentTransaction;
import android.os.Build;
import android.os.Bundle;
@@ -22,13 +23,15 @@ import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.DomainManager;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
-import org.joinmastodon.android.fragments.DomainDisplay;
+import org.joinmastodon.android.fragments.HomeFragment;
+import org.joinmastodon.android.fragments.IsOnTop;
import org.joinmastodon.android.fragments.ScrollableToTop;
import org.joinmastodon.android.fragments.ListTimelinesFragment;
import org.joinmastodon.android.ui.SimpleViewHolder;
import org.joinmastodon.android.ui.tabs.TabLayout;
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
import org.joinmastodon.android.ui.utils.UiUtils;
+import org.joinmastodon.android.utils.ProvidesAssistContent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -40,7 +43,7 @@ import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.utils.V;
-public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, OnBackPressedListener, DomainDisplay {
+public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, OnBackPressedListener, IsOnTop, ProvidesAssistContent {
private TabLayout tabLayout;
private ViewPager2 pager;
@@ -53,30 +56,14 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
private ProgressBar searchProgress;
private DiscoverPostsFragment postsFragment;
- private TrendingHashtagsFragment hashtagsFragment;
+ private DiscoverHashtagsFragment hashtagsFragment;
private DiscoverNewsFragment newsFragment;
private DiscoverAccountsFragment accountsFragment;
private SearchFragment searchFragment;
- private LocalTimelineFragment localTimelineFragment;
- private FederatedTimelineFragment federatedTimelineFragment;
- private ListTimelinesFragment listTimelinesFragment;
private String accountID;
private Runnable searchDebouncer=this::onSearchChangedDebounced;
-// private final boolean noFederated = !GlobalUserPreferences.showFederatedTimeline;
-
- @Override
- public String getDomain() {
- if (searchActive) {
- return searchFragment.getDomain();
- }
- if (tabViews[tabLayout.getSelectedTabPosition()] instanceof DomainDisplay page) {
- return page.getDomain();
- }
- return DomainDisplay.super.getDomain() + "/explore";
- }
-
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
@@ -94,22 +81,9 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
tabLayout=view.findViewById(R.id.tabbar);
pager=view.findViewById(R.id.pager);
-// tabViews=new FrameLayout[noFederated ? 5 : 6];
tabViews=new FrameLayout[4];
for(int i=0;i 0 ? i + 1 : i;
-// tabView.setId(switch(switchIndex){
-// case 0 -> R.id.discover_local_timeline;
-// case 1 -> R.id.discover_federated_timeline;
-// case 2 -> R.id.discover_hashtags;
-// case 3 -> R.id.discover_posts;
-// case 4 -> R.id.discover_news;
-// case 5 -> R.id.discover_users;
-// default -> throw new IllegalStateException("Unexpected value: "+switchIndex);
-// });
-
tabView.setId(switch(i){
case 0 -> R.id.discover_hashtags;
case 1 -> R.id.discover_posts;
@@ -117,7 +91,6 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
case 3 -> R.id.discover_users;
default -> throw new IllegalStateException("Unexpected value: "+i);
});
-
tabView.setVisibility(View.GONE);
view.addView(tabView); // needed so the fragment manager will have somewhere to restore the tab fragment
tabViews[i]=tabView;
@@ -140,13 +113,10 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
if(!page.loaded && !page.isDataLoading())
page.loadData();
}
-
- if (_page instanceof DomainDisplay display)
- DomainManager.getInstance().setCurrentDomain(display.getDomain());
}
});
- if(localTimelineFragment==null || hashtagsFragment==null){
+ if(hashtagsFragment==null){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putBoolean("__is_tab", true);
@@ -154,7 +124,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
postsFragment=new DiscoverPostsFragment();
postsFragment.setArguments(args);
- hashtagsFragment=new TrendingHashtagsFragment();
+ hashtagsFragment=new DiscoverHashtagsFragment();
hashtagsFragment.setArguments(args);
newsFragment=new DiscoverNewsFragment();
@@ -163,27 +133,6 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
accountsFragment=new DiscoverAccountsFragment();
accountsFragment.setArguments(args);
- localTimelineFragment=new LocalTimelineFragment();
- localTimelineFragment.setArguments(args);
-
-// listTimelinesFragment=new ListTimelinesFragment();
-// listTimelinesFragment.setArguments(args);
-//
-// FragmentTransaction transaction = getChildFragmentManager().beginTransaction()
-// .add(R.id.discover_posts, postsFragment)
-// .add(R.id.discover_local_timeline, localTimelineFragment)
-// .add(R.id.discover_hashtags, hashtagsFragment)
-// .add(R.id.discover_news, newsFragment)
-// .add(R.id.discover_users, accountsFragment)
-// .add(R.id.discover_lists, listTimelinesFragment);
-//
-// if (!noFederated) {
-// federatedTimelineFragment=new FederatedTimelineFragment();
-// federatedTimelineFragment.setArguments(args);
-// transaction.add(R.id.discover_federated_timeline, federatedTimelineFragment);
-// }
-//
-// transaction.commit();
getChildFragmentManager().beginTransaction()
.add(R.id.discover_posts, postsFragment)
@@ -196,21 +145,6 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
tabLayoutMediator=new TabLayoutMediator(tabLayout, pager, new TabLayoutMediator.TabConfigurationStrategy(){
@Override
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
-
-// if (noFederated && position > 0) position++;
-
-// tab.setText(switch(position){
-// case 0 -> R.string.local_timeline;
-// case 1 -> R.string.sk_federated_timeline;
-// case 2 -> R.string.sk_list_timelines;
-// case 3 -> R.string.hashtags;
-// case 4 -> R.string.posts;
-// case 5 -> R.string.news;
-// case 6 -> R.string.for_you;
-//
-// default -> throw new IllegalStateException("Unexpected value: "+position);
-// });
-
tab.setText(switch(position){
case 0 -> R.string.hashtags;
case 1 -> R.string.posts;
@@ -224,9 +158,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
tabLayoutMediator.attach();
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
@Override
- public void onTabSelected(TabLayout.Tab tab){
- DomainManager.getInstance().setCurrentDomain(getDomain());
- }
+ public void onTabSelected(TabLayout.Tab tab){}
@Override
public void onTabUnselected(TabLayout.Tab tab){}
@@ -303,20 +235,25 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
}
@Override
- public boolean isScrolledToTop() {
- if(!searchActive){
- return ((ScrollableToTop)getFragmentForPage(pager.getCurrentItem())).isScrolledToTop();
- }else{
- return searchFragment.isScrolledToTop();
- }
+ public boolean isOnTop() {
+ return searchActive ? searchFragment.isOnTop()
+ : ((IsOnTop)getFragmentForPage(pager.getCurrentItem())).isOnTop();
+ }
+
+ public void onSelect() {
+ if (isOnTop()) selectSearch();
+ else scrollToTop();
+ }
+
+ public void selectSearch() {
+ searchEdit.requestFocus();
+ onSearchEditFocusChanged(searchEdit, true);
+ getActivity().getSystemService(InputMethodManager.class).showSoftInput(searchEdit, 0);
}
public void loadData(){
if(hashtagsFragment!=null && !hashtagsFragment.loaded && !hashtagsFragment.dataLoading)
hashtagsFragment.loadData();
-
-// if(localTimelineFragment!=null && !localTimelineFragment.loaded && !localTimelineFragment.dataLoading)
-// localTimelineFragment.loadData();
}
private void onSearchEditFocusChanged(View v, boolean hasFocus){
@@ -342,6 +279,8 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
searchBack.setEnabled(false);
searchBack.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(searchEdit.getWindowToken(), 0);
+ if (getArguments().getBoolean("disableDiscover"))
+ ((HomeFragment) getParentFragment()).onBackPressed();
}
@Override
@@ -351,19 +290,6 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
}
private Fragment getFragmentForPage(int page){
-// if (noFederated && page > 0) page++;
-
-// return switch(page){
-// case 0 -> localTimelineFragment;
-// case 1 -> federatedTimelineFragment;
-// case 2 -> hashtagsFragment;
-// case 3 -> postsFragment;
-// case 4 -> newsFragment;
-// case 5 -> accountsFragment;
-// case 6 -> listTimelinesFragment;
-// default -> throw new IllegalStateException("Unexpected value: "+page);
-// };
-
return switch(page){
case 0 -> hashtagsFragment;
case 1 -> postsFragment;
@@ -401,6 +327,13 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
V.setVisibilityAnimated(searchClear, visible ? View.INVISIBLE : View.VISIBLE);
}
+ @Override
+ public void onProvideAssistContent(AssistContent assistContent) {
+ callFragmentToProvideAssistContent(searchActive
+ ? searchFragment
+ : getFragmentForPage(pager.getCurrentItem()), assistContent);
+ }
+
private class DiscoverPagerAdapter extends RecyclerView.Adapter{
@NonNull
@Override
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/TrendingHashtagsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverHashtagsFragment.java
similarity index 75%
rename from mastodon/src/main/java/org/joinmastodon/android/fragments/discover/TrendingHashtagsFragment.java
rename to mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverHashtagsFragment.java
index 31628640a..aedd41e6e 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/TrendingHashtagsFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverHashtagsFragment.java
@@ -1,5 +1,9 @@
package org.joinmastodon.android.fragments.discover;
+import static org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem.Holder.withHistoryParams;
+import static org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem.Holder.withoutHistoryParams;
+
+import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
@@ -7,7 +11,6 @@ import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.trends.GetTrendingHashtags;
-import org.joinmastodon.android.fragments.DomainDisplay;
import org.joinmastodon.android.fragments.IsOnTop;
import org.joinmastodon.android.fragments.RecyclerFragment;
import org.joinmastodon.android.fragments.ScrollableToTop;
@@ -16,29 +19,24 @@ import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.HashtagChartView;
+import org.joinmastodon.android.utils.ProvidesAssistContent;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.api.SimpleCallback;
-import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.views.UsableRecyclerView;
-public class TrendingHashtagsFragment extends BaseRecyclerFragment implements ScrollableToTop, IsOnTop, DomainDisplay {
+public class DiscoverHashtagsFragment extends RecyclerFragment implements ScrollableToTop, IsOnTop, ProvidesAssistContent.ProvidesWebUri {
private String accountID;
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_HASHTAGS);
- public TrendingHashtagsFragment(){
+ public DiscoverHashtagsFragment(){
super(10);
}
- @Override
- public String getDomain() {
- return DomainDisplay.super.getDomain() + "/explore/tags";
- }
-
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
@@ -76,13 +74,18 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment impl
}
@Override
- public boolean isScrolledToTop() {
- return list.getChildAt(0).getTop() == 0;
+ public boolean isOnTop() {
+ return isRecyclerViewOnTop(list);
}
@Override
- public boolean isOnTop() {
- return isRecyclerViewOnTop(list);
+ public String getAccountID() {
+ return accountID;
+ }
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ return isInstanceAkkoma() ? null : base.path("/explore/tags").build();
}
private class HashtagsAdapter extends RecyclerView.Adapter{
@@ -117,14 +120,19 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment impl
@Override
public void onBind(Hashtag item){
title.setText('#'+item.name);
- int numPeople = 0;
- if(item.history != null){
- numPeople=item.history.get(0).accounts;
- if(item.history.size()>1)
- numPeople+=item.history.get(1).accounts;
- chart.setData(item.history);
+ if (item.history == null || item.history.isEmpty()) {
+ subtitle.setText(null);
+ chart.setVisibility(View.GONE);
+ title.setLayoutParams(withoutHistoryParams);
+ return;
}
+ chart.setVisibility(View.VISIBLE);
+ title.setLayoutParams(withHistoryParams);
+ int numPeople=item.history.get(0).accounts;
+ if(item.history.size()>1)
+ numPeople+=item.history.get(1).accounts;
subtitle.setText(getResources().getQuantityString(R.plurals.x_people_talking, numPeople, numPeople));
+ chart.setData(item.history);
}
@Override
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverNewsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverNewsFragment.java
index be4c5931f..67e030514 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverNewsFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverNewsFragment.java
@@ -1,6 +1,7 @@
package org.joinmastodon.android.fragments.discover;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
@@ -10,7 +11,6 @@ import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.trends.GetTrendingLinks;
-import org.joinmastodon.android.fragments.DomainDisplay;
import org.joinmastodon.android.fragments.IsOnTop;
import org.joinmastodon.android.fragments.RecyclerFragment;
import org.joinmastodon.android.fragments.ScrollableToTop;
@@ -20,6 +20,7 @@ import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
import org.joinmastodon.android.ui.utils.UiUtils;
+import org.joinmastodon.android.utils.ProvidesAssistContent;
import java.util.Collections;
import java.util.List;
@@ -28,7 +29,6 @@ import java.util.stream.Collectors;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.api.SimpleCallback;
-import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
@@ -37,7 +37,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
-public class DiscoverNewsFragment extends BaseRecyclerFragment implements ScrollableToTop, IsOnTop, DomainDisplay {
+public class DiscoverNewsFragment extends RecyclerFragment implements ScrollableToTop, IsOnTop, ProvidesAssistContent.ProvidesWebUri {
private String accountID;
private List imageRequests=Collections.emptyList();
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS);
@@ -46,11 +46,6 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment implements
super(10);
}
- @Override
- public String getDomain() {
- return DomainDisplay.super.getDomain() + "/explore/links";
- }
-
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
@@ -91,13 +86,18 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment implements
}
@Override
- public boolean isScrolledToTop() {
- return list.getChildAt(0).getTop() == 0;
+ public boolean isOnTop() {
+ return isRecyclerViewOnTop(list);
}
@Override
- public boolean isOnTop() {
- return isRecyclerViewOnTop(list);
+ public String getAccountID() {
+ return accountID;
+ }
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ return isInstanceAkkoma() ? null : base.path("/explore/links").build();
}
private class LinksAdapter extends UsableRecyclerView.Adapter implements ImageLoaderRecyclerAdapter{
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverPostsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverPostsFragment.java
index 1af3735f4..67596593a 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverPostsFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverPostsFragment.java
@@ -1,5 +1,6 @@
package org.joinmastodon.android.fragments.discover;
+import android.net.Uri;
import android.os.Bundle;
import android.view.View;
@@ -48,9 +49,13 @@ public class DiscoverPostsFragment extends StatusListFragment implements IsOnTop
return isRecyclerViewOnTop(list);
}
-
@Override
protected Filter.FilterContext getFilterContext() {
return Filter.FilterContext.PUBLIC;
}
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ return isInstanceAkkoma() ? null : base.path("/explore/posts").build();
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/FederatedTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/FederatedTimelineFragment.java
index f5d2bb61c..5fb7da53f 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/FederatedTimelineFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/FederatedTimelineFragment.java
@@ -1,5 +1,6 @@
package org.joinmastodon.android.fragments.discover;
+import android.net.Uri;
import android.os.Bundle;
import android.view.View;
@@ -25,12 +26,6 @@ public class FederatedTimelineFragment extends StatusListFragment {
return true;
}
- @Override
- public String getDomain() {
- return super.getDomain() + "/public";
- }
-
-
@Override
protected void doLoadData(int offset, int count){
currentRequest=new GetPublicTimeline(false, false, refreshing ? null : maxID, count)
@@ -50,11 +45,16 @@ public class FederatedTimelineFragment extends StatusListFragment {
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
-// bannerHelper.maybeAddBanner(contentWrap);
+ bannerHelper.maybeAddBanner(contentWrap);
}
@Override
protected Filter.FilterContext getFilterContext() {
return Filter.FilterContext.PUBLIC;
}
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ return base.path(isInstanceAkkoma() ? "/main/all" : "/public").build();
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/LocalTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/LocalTimelineFragment.java
index 631a8d64f..8cde7b7df 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/LocalTimelineFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/LocalTimelineFragment.java
@@ -1,9 +1,11 @@
package org.joinmastodon.android.fragments.discover;
+import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
+import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.StatusListFragment;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Status;
@@ -24,11 +26,6 @@ public class LocalTimelineFragment extends StatusListFragment {
return true;
}
- @Override
- public String getDomain() {
- return super.getDomain() + "/public/local";
- }
-
@Override
protected void doLoadData(int offset, int count){
currentRequest=new GetPublicTimeline(true, false, refreshing ? null : maxID, count)
@@ -55,4 +52,9 @@ public class LocalTimelineFragment extends StatusListFragment {
protected Filter.FilterContext getFilterContext() {
return Filter.FilterContext.PUBLIC;
}
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ return base.path(isInstanceAkkoma() ? "/main/public" : "/public/local").build();
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/SearchFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/SearchFragment.java
index 3a2e6cce8..aa4b030b5 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/SearchFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/SearchFragment.java
@@ -1,6 +1,7 @@
package org.joinmastodon.android.fragments.discover;
import android.app.Activity;
+import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
@@ -11,6 +12,7 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.search.GetSearchResults;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
+import org.joinmastodon.android.fragments.IsOnTop;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.ThreadFragment;
import org.joinmastodon.android.model.Account;
@@ -42,7 +44,7 @@ import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.V;
-public class SearchFragment extends BaseStatusListFragment{
+public class SearchFragment extends BaseStatusListFragment implements IsOnTop {
private String currentQuery;
private List prevDisplayItems;
private EnumSet currentFilter=EnumSet.allOf(SearchResult.Type.class);
@@ -57,11 +59,6 @@ public class SearchFragment extends BaseStatusListFragment{
setLayout(R.layout.fragment_search);
}
- @Override
- public String getDomain() {
- return super.getDomain() + "/search";
- }
-
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
@@ -190,7 +187,7 @@ public class SearchFragment extends BaseStatusListFragment{
return;
}
UiUtils.updateList(prevDisplayItems, displayItems, list, adapter, (i1, i2)->i1.parentID.equals(i2.parentID) && i1.index==i2.index && i1.getType()==i2.getType());
- boolean recent=isInRecentMode();
+ boolean recent=isInRecentMode() && !displayItems.isEmpty();
if(recent!=headerAdapter.isVisible())
headerAdapter.setVisible(recent);
imgLoader.forceUpdateImages();
@@ -316,6 +313,19 @@ public class SearchFragment extends BaseStatusListFragment{
}
}
+ @Override
+ public boolean isOnTop() {
+ return isRecyclerViewOnTop(list);
+ }
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ Uri.Builder searchUri = base.path("/search");
+ return isInstanceAkkoma()
+ ? searchUri.appendQueryParameter("query", currentQuery).build()
+ : searchUri.build();
+ }
+
@FunctionalInterface
public interface ProgressVisibilityListener{
void onProgressVisibilityChanged(boolean visible);
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/AccountActivationFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/AccountActivationFragment.java
index a4cd41554..b1a77e9c0 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/AccountActivationFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/AccountActivationFragment.java
@@ -23,7 +23,8 @@ import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
import org.joinmastodon.android.api.session.AccountActivationInfo;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
-import org.joinmastodon.android.fragments.settings.SettingsMainFragment;
+import org.joinmastodon.android.fragments.HomeFragment;
+import org.joinmastodon.android.fragments.SettingsFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.ui.AccountSwitcherSheet;
import org.joinmastodon.android.ui.utils.UiUtils;
@@ -69,7 +70,7 @@ public class AccountActivationFragment extends ToolbarFragment{
openEmailBtn.setOnLongClickListener(v->{
Bundle args=new Bundle();
args.putString("account", accountID);
- Nav.go(getActivity(), SettingsMainFragment.class, args);
+ Nav.go(getActivity(), SettingsFragment.class, args);
return true;
});
resendBtn=view.findViewById(R.id.btn_resend);
@@ -89,15 +90,8 @@ public class AccountActivationFragment extends ToolbarFragment{
}
@Override
- public void onViewCreated(View view, Bundle savedInstanceState){
- super.onViewCreated(view, savedInstanceState);
- setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
- view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
- }
-
-// @Override
protected void onUpdateToolbar(){
-// super.onUpdateToolbar();
+ super.onUpdateToolbar();
getToolbar().setBackground(null);
getToolbar().setElevation(0);
}
@@ -109,10 +103,7 @@ public class AccountActivationFragment extends ToolbarFragment{
@Override
public void onToolbarNavigationClick(){
- new AccountSwitcherSheet(getActivity(), true, true, false, accountSession -> {
- getActivity().finish();
- getActivity().startActivity(new Intent(getActivity(), MainActivity.class));
- }).show();
+ new AccountSwitcherSheet(getActivity(), null).show();
}
@Override
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceRulesFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceRulesFragment.java
index aa748cbc2..45fe25cb8 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceRulesFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceRulesFragment.java
@@ -2,7 +2,9 @@ package org.joinmastodon.android.fragments.onboarding;
import android.annotation.SuppressLint;
import android.app.Activity;
+import android.app.assist.AssistContent;
import android.graphics.Typeface;
+import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.Html;
@@ -24,6 +26,7 @@ import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.ElevationOnScrollListener;
+import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.parceler.Parcels;
import androidx.annotation.NonNull;
@@ -38,7 +41,7 @@ import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.FragmentRootLinearLayout;
import me.grishka.appkit.views.UsableRecyclerView;
-public class InstanceRulesFragment extends ToolbarFragment{
+public class InstanceRulesFragment extends ToolbarFragment implements ProvidesAssistContent {
private UsableRecyclerView list;
private MergeRecyclerAdapter adapter;
private Button btn;
@@ -130,6 +133,15 @@ public class InstanceRulesFragment extends ToolbarFragment{
}
}
+ @Override
+ public void onProvideAssistContent(AssistContent assistContent) {
+ assistContent.setWebUri(new Uri.Builder()
+ .scheme("https")
+ .authority(instance.normalizedUri)
+ .path("/about")
+ .build());
+ }
+
private class ItemsAdapter extends RecyclerView.Adapter{
@NonNull
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java
index a04d0a9a8..1a9ff0605 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java
@@ -5,6 +5,7 @@ import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.SparseIntArray;
@@ -267,4 +268,11 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
protected Filter.FilterContext getFilterContext() {
return null;
}
+
+ @Override
+ public Uri getWebUri(Uri.Builder base) {
+ if (reportStatus != null) return Uri.parse(reportStatus.url);
+ if (reportAccount != null) return Uri.parse(reportAccount.url);
+ return null;
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Instance.java b/mastodon/src/main/java/org/joinmastodon/android/model/Instance.java
index 09aae3adc..12707952f 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/model/Instance.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/model/Instance.java
@@ -11,6 +11,7 @@ import java.net.IDN;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
@Parcel
public class Instance extends BaseModel{
@@ -86,6 +87,11 @@ public class Instance extends BaseModel{
public Pleroma pleroma;
+ public PleromaPollLimits pollLimits;
+
+ /** like uri, but always without scheme and trailing slash */
+ public transient String normalizedUri;
+
@Override
public void postprocess() throws ObjectValidationException{
super.postprocess();
@@ -95,6 +101,10 @@ public class Instance extends BaseModel{
rules=Collections.emptyList();
if(shortDescription==null)
shortDescription="";
+ // akkoma says uri is "https://example.social" while just "example.social" on mastodon
+ normalizedUri = uri
+ .replaceFirst("^https://", "")
+ .replaceFirst("/$", "");
}
@Override
@@ -134,6 +144,26 @@ public class Instance extends BaseModel{
return ci;
}
+ public boolean isAkkoma() {
+ return pleroma != null;
+ }
+
+ public boolean hasFeature(Feature feature) {
+ Optional> pleromaFeatures = Optional.ofNullable(pleroma)
+ .map(p -> p.metadata)
+ .map(m -> m.features);
+
+ return switch (feature) {
+ case BUBBLE_TIMELINE -> pleromaFeatures
+ .map(f -> f.contains("bubble_timeline"))
+ .orElse(false);
+ };
+ }
+
+ public enum Feature {
+ BUBBLE_TIMELINE
+ }
+
@Parcel
public static class Rule{
public String id;
@@ -198,6 +228,28 @@ public class Instance extends BaseModel{
@Parcel
public static class Pleroma extends BaseModel {
- // metadata etc
+ public Pleroma.Metadata metadata;
+
+ @Parcel
+ public static class Metadata {
+ public List features;
+ public Pleroma.Metadata.FieldsLimits fieldsLimits;
+
+ @Parcel
+ public static class FieldsLimits {
+ public int maxFields;
+ public int maxRemoteFields;
+ public int nameLength;
+ public int valueLength;
+ }
+ }
+ }
+
+ @Parcel
+ public static class PleromaPollLimits {
+ public int maxExpiration;
+ public int maxOptionChars;
+ public int maxOptions;
+ public int minExpiration;
}
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java b/mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java
index 7a30d3508..9ea7e9054 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java
@@ -11,15 +11,19 @@ import androidx.annotation.StringRes;
import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.CustomLocalTimelineFragment;
+import org.joinmastodon.android.api.session.AccountSession;
+import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
import org.joinmastodon.android.fragments.HomeTimelineFragment;
import org.joinmastodon.android.fragments.ListTimelineFragment;
import org.joinmastodon.android.fragments.NotificationsListFragment;
+import org.joinmastodon.android.fragments.discover.BubbleTimelineFragment;
import org.joinmastodon.android.fragments.discover.FederatedTimelineFragment;
import org.joinmastodon.android.fragments.discover.LocalTimelineFragment;
import java.util.List;
import java.util.Objects;
+import java.util.stream.Collectors;
public class TimelineDefinition {
private TimelineType type;
@@ -65,6 +69,14 @@ public class TimelineDefinition {
this.type = type;
}
+ public boolean isCompatible(AccountSession session) {
+ return true;
+ }
+
+ public boolean wantsDefault(AccountSession session) {
+ return true;
+ }
+
public String getTitle(Context ctx) {
return title != null ? title : getDefaultTitle(ctx);
}
@@ -85,6 +97,7 @@ public class TimelineDefinition {
case POST_NOTIFICATIONS -> ctx.getString(R.string.sk_timeline_posts);
case LIST -> listTitle;
case HASHTAG -> hashtagName;
+ case BUBBLE -> ctx.getString(R.string.sk_timeline_bubble);
case CUSTOM_LOCAL_TIMELINE -> domain;
};
}
@@ -98,6 +111,7 @@ public class TimelineDefinition {
case LIST -> Icon.LIST;
case HASHTAG -> Icon.HASHTAG;
case CUSTOM_LOCAL_TIMELINE -> Icon.CUSTOM_LOCAL_TIMELINE;
+ case BUBBLE -> Icon.BUBBLE;
};
}
@@ -109,6 +123,7 @@ public class TimelineDefinition {
case LIST -> new ListTimelineFragment();
case HASHTAG -> new HashtagTimelineFragment();
case POST_NOTIFICATIONS -> new NotificationsListFragment();
+ case BUBBLE -> new BubbleTimelineFragment();
case CUSTOM_LOCAL_TIMELINE -> new CustomLocalTimelineFragment();
};
}
@@ -172,7 +187,7 @@ public class TimelineDefinition {
return args;
}
- public enum TimelineType { HOME, LOCAL, FEDERATED, POST_NOTIFICATIONS, LIST, HASHTAG, CUSTOM_LOCAL_TIMELINE }
+ public enum TimelineType { HOME, LOCAL, FEDERATED, POST_NOTIFICATIONS, LIST, HASHTAG, CUSTOM_LOCAL_TIMELINE, BUBBLE }
public enum Icon {
HEART(R.drawable.ic_fluent_heart_24_regular, R.string.sk_icon_heart),
@@ -236,7 +251,8 @@ public class TimelineDefinition {
POST_NOTIFICATIONS(R.drawable.ic_fluent_chat_24_regular, R.string.sk_timeline_posts, true),
LIST(R.drawable.ic_fluent_people_24_regular, R.string.sk_list, true),
HASHTAG(R.drawable.ic_fluent_number_symbol_24_regular, R.string.sk_hashtag, true),
- CUSTOM_LOCAL_TIMELINE(R.drawable.ic_fluent_people_community_24_regular, R.string.sk_timeline_local, true);
+ CUSTOM_LOCAL_TIMELINE(R.drawable.ic_fluent_people_community_24_regular, R.string.sk_timeline_local, true),
+ BUBBLE(R.drawable.ic_fluent_circle_24_regular, R.string.sk_timeline_bubble, true);
public final int iconRes, nameRes;
public final boolean hidden;
@@ -256,14 +272,50 @@ public class TimelineDefinition {
public static final TimelineDefinition LOCAL_TIMELINE = new TimelineDefinition(TimelineType.LOCAL);
public static final TimelineDefinition FEDERATED_TIMELINE = new TimelineDefinition(TimelineType.FEDERATED);
public static final TimelineDefinition POSTS_TIMELINE = new TimelineDefinition(TimelineType.POST_NOTIFICATIONS);
+ public static final TimelineDefinition BUBBLE_TIMELINE = new TimelineDefinition(TimelineType.BUBBLE) {
+ @Override
+ public boolean isCompatible(AccountSession session) {
+ // still enabling the bubble timeline for all pleroma/akkoma instances since i know of
+ // at least one instance that supports it, but doesn't list "bubble_timeline"
+ return session.getInstance().map(Instance::isAkkoma).orElse(false);
+ }
- public static final List DEFAULT_TIMELINES = BuildConfig.BUILD_TYPE.equals("playRelease")
- ? List.of(HOME_TIMELINE.copy(), LOCAL_TIMELINE.copy())
- : List.of(HOME_TIMELINE.copy(), LOCAL_TIMELINE.copy(), FEDERATED_TIMELINE.copy());
- public static final List ALL_TIMELINES = List.of(
- HOME_TIMELINE.copy(),
- LOCAL_TIMELINE.copy(),
- FEDERATED_TIMELINE.copy(),
- POSTS_TIMELINE.copy()
+ @Override
+ public boolean wantsDefault(AccountSession session) {
+ return session.getInstance()
+ .map(i -> i.hasFeature(Instance.Feature.BUBBLE_TIMELINE))
+ .orElse(false);
+ }
+ };
+
+ public static List getDefaultTimelines(String accountId) {
+ AccountSession session = AccountSessionManager.getInstance().getAccount(accountId);
+ return DEFAULT_TIMELINES.stream()
+ .filter(tl -> tl.isCompatible(session) && tl.wantsDefault(session))
+ .map(TimelineDefinition::copy)
+ .collect(Collectors.toList());
+ }
+
+ public static List getAllTimelines(String accountId) {
+ AccountSession session = AccountSessionManager.getInstance().getAccount(accountId);
+ return ALL_TIMELINES.stream()
+ .filter(tl -> tl.isCompatible(session))
+ .map(TimelineDefinition::copy)
+ .collect(Collectors.toList());
+ }
+
+ private static final List DEFAULT_TIMELINES = List.of(
+ HOME_TIMELINE,
+ LOCAL_TIMELINE,
+ BUBBLE_TIMELINE,
+ FEDERATED_TIMELINE
+ );
+
+ private static final List ALL_TIMELINES = List.of(
+ HOME_TIMELINE,
+ LOCAL_TIMELINE,
+ FEDERATED_TIMELINE,
+ POSTS_TIMELINE,
+ BUBBLE_TIMELINE
);
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/AccountSwitcherSheet.java b/mastodon/src/main/java/org/joinmastodon/android/ui/AccountSwitcherSheet.java
index 49ff38b21..0eb9f205d 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/AccountSwitcherSheet.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/AccountSwitcherSheet.java
@@ -2,7 +2,8 @@ package org.joinmastodon.android.ui;
import android.annotation.SuppressLint;
import android.app.Activity;
-import android.content.res.ColorStateList;
+import android.app.ProgressDialog;
+import android.content.Intent;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -13,24 +14,31 @@ import android.view.WindowInsets;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
-import android.widget.PopupMenu;
+import android.widget.RadioButton;
import android.widget.TextView;
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.LinearLayoutManager;
-
import org.joinmastodon.android.GlobalUserPreferences;
+import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
+import org.joinmastodon.android.fragments.HomeFragment;
+import org.joinmastodon.android.fragments.SplashFragment;
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
import org.joinmastodon.android.ui.utils.UiUtils;
+import org.joinmastodon.android.ui.views.CheckableRelativeLayout;
+import java.util.ArrayList;
import java.util.List;
-import java.util.function.Consumer;
+import java.util.function.BiConsumer;
import java.util.stream.Collectors;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.recyclerview.widget.LinearLayoutManager;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
@@ -49,18 +57,26 @@ import me.grishka.appkit.views.UsableRecyclerView;
public class AccountSwitcherSheet extends BottomSheet{
private final Activity activity;
+ private final HomeFragment fragment;
+ private final BiConsumer onClick;
+ private final boolean externalShare, openInApp;
private UsableRecyclerView list;
private List accounts;
private ListImageLoaderWrapper imgLoader;
- private final boolean logOutEnabled;
- private final Consumer onClick;
+ private AccountsAdapter accountsAdapter;
- public AccountSwitcherSheet(@NonNull Activity activity, boolean logOutEnabled, boolean addAccountEnabled, boolean showOpenURL, Consumer onClick){
+ public AccountSwitcherSheet(@NonNull Activity activity, @Nullable HomeFragment fragment){
+ this(activity, fragment, false, false, null);
+ }
+
+ public AccountSwitcherSheet(@NonNull Activity activity, @Nullable HomeFragment fragment, boolean externalShare, boolean openInApp, BiConsumer onClick){
super(activity);
this.activity=activity;
- this.logOutEnabled=logOutEnabled;
- this.onClick=onClick;
-
+ this.fragment=fragment;
+ this.externalShare = externalShare;
+ this.openInApp = openInApp;
+ this.onClick = onClick;
+
accounts=AccountSessionManager.getInstance().getLoggedInAccounts().stream().map(WrappedAccount::new).collect(Collectors.toList());
list=new UsableRecyclerView(activity);
@@ -71,61 +87,59 @@ public class AccountSwitcherSheet extends BottomSheet{
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
View handle=new View(activity);
handle.setBackgroundResource(R.drawable.bg_bottom_sheet_handle);
- adapter.addAdapter(new SingleViewRecyclerAdapter(handle));
- adapter.addAdapter(new AccountsAdapter());
+ handle.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(36)));
- if(addAccountEnabled){
- AccountViewHolder holder = new AccountViewHolder();
- holder.more.setVisibility(View.GONE);
- holder.currentIcon.setVisibility(View.GONE);
- holder.display_name.setVisibility(View.GONE);
- holder.display_add_account.setVisibility(View.VISIBLE);
- holder.avatar.setScaleType(ImageView.ScaleType.CENTER);
- holder.avatar.setImageResource(R.drawable.ic_fluent_add_circle_24_filled);
- holder.avatar.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(activity, android.R.attr.textColorPrimary)));
- adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(holder.itemView, () -> {
+ adapter.addAdapter(new SingleViewRecyclerAdapter(handle));
+
+ if (externalShare) {
+ FrameLayout shareHeading = new FrameLayout(activity);
+ activity.getLayoutInflater().inflate(R.layout.item_external_share_heading, shareHeading);
+ ((TextView) shareHeading.findViewById(R.id.title)).setText(openInApp
+ ? R.string.sk_external_share_or_open_title
+ : R.string.sk_external_share_title);
+ adapter.addAdapter(new SingleViewRecyclerAdapter(shareHeading));
+
+ setOnDismissListener((d) -> activity.finish());
+ }
+
+ adapter.addAdapter(accountsAdapter = new AccountsAdapter());
+
+ if (!externalShare) {
+ adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(makeSimpleListItem(R.string.add_account, R.drawable.ic_fluent_add_24_regular), () -> {
Nav.go(activity, CustomWelcomeFragment.class, null);
dismiss();
}));
- }
-
- if(showOpenURL) {
- AccountViewHolder holder = new AccountViewHolder();
- holder.more.setVisibility(View.GONE);
- holder.currentIcon.setVisibility(View.GONE);
- holder.display_name.setVisibility(View.VISIBLE);
- holder.display_add_account.setVisibility(View.VISIBLE);
- holder.display_add_account.setText(R.string.mo_share_open_url);
- holder.avatar.setScaleType(ImageView.ScaleType.CENTER);
- holder.avatar.setImageResource(R.drawable.ic_fluent_open_24_regular);
- holder.avatar.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(activity, android.R.attr.textColorPrimary)));
- adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(holder.itemView, () -> {
- onClick.accept(null);
- dismiss();
- }));
+ // disabled in megalodon
+// adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(makeSimpleListItem(R.string.log_out_all_accounts, R.drawable.ic_fluent_person_arrow_right_24_filled), this::confirmLogOutAll));
}
list.setAdapter(adapter);
- DividerItemDecoration divider=new DividerItemDecoration(activity, R.attr.colorPollVoted, .5f, 72, 16, DividerItemDecoration.NOT_FIRST);
- divider.setDrawBelowLastItem(true);
- list.addItemDecoration(divider);
FrameLayout content=new FrameLayout(activity);
content.setBackgroundResource(R.drawable.bg_bottom_sheet);
content.addView(list);
setContentView(content);
- setNavigationBarBackground(new ColorDrawable(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground)), !UiUtils.isDarkTheme());
+ setNavigationBarBackground(new ColorDrawable(UiUtils.alphaBlendColors(UiUtils.getThemeColor(activity, R.attr.colorM3Surface),
+ UiUtils.getThemeColor(activity, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme());
}
private void confirmLogOut(String accountID){
+ AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
new M3AlertDialogBuilder(activity)
- .setTitle(R.string.log_out)
- .setMessage(R.string.confirm_log_out)
+ .setMessage(activity.getString(R.string.confirm_log_out, session.getFullUsername()))
.setPositiveButton(R.string.log_out, (dialog, which) -> logOut(accountID))
.setNegativeButton(R.string.cancel, null)
.show();
}
+ private void confirmLogOutAll(){
+ new M3AlertDialogBuilder(activity)
+ .setMessage(R.string.confirm_log_out_all_accounts)
+ .setPositiveButton(R.string.log_out, (dialog, which) -> logOutAll())
+ .setNegativeButton(R.string.cancel, null)
+ .show();
+ }
+
private void logOut(String accountID){
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
new RevokeOauthToken(session.app.clientId, session.app.clientSecret, session.token.accessToken)
@@ -144,9 +158,55 @@ public class AccountSwitcherSheet extends BottomSheet{
.exec(accountID);
}
+ private void logOutAll(){
+ final ProgressDialog progress=new ProgressDialog(activity);
+ progress.setMessage(activity.getString(R.string.loading));
+ progress.setCancelable(false);
+ progress.show();
+ ArrayList sessions=new ArrayList<>(AccountSessionManager.getInstance().getLoggedInAccounts());
+ for(AccountSession session:sessions){
+ new RevokeOauthToken(session.app.clientId, session.app.clientSecret, session.token.accessToken)
+ .setCallback(new Callback<>(){
+ @Override
+ public void onSuccess(Object result){
+ AccountSessionManager.getInstance().removeAccount(session.getID());
+ sessions.remove(session);
+ if(sessions.isEmpty()){
+ progress.dismiss();
+ Nav.goClearingStack(activity, SplashFragment.class, null);
+ dismiss();
+ }
+ }
+
+ @Override
+ public void onError(ErrorResponse error){
+ AccountSessionManager.getInstance().removeAccount(session.getID());
+ sessions.remove(session);
+ if(sessions.isEmpty()){
+ progress.dismiss();
+ Nav.goClearingStack(activity, SplashFragment.class, null);
+ dismiss();
+ }
+ }
+ })
+ .exec(session.getID());
+ }
+ }
+
private void onLoggedOut(String accountID){
AccountSessionManager.getInstance().removeAccount(accountID);
- dismiss();
+ String activeAccountID = fragment != null
+ ? fragment.getAccountID()
+ : AccountSessionManager.getInstance().getLastActiveAccountID();
+ if (accountID.equals(activeAccountID)) {
+ activity.finish();
+ activity.startActivity(new Intent(activity, MainActivity.class));
+ } else {
+ accounts.stream().filter(w -> accountID.equals(w.session.getID())).findAny().ifPresent(w -> {
+ accountsAdapter.notifyItemRemoved(accounts.indexOf(w));
+ accounts.remove(w);
+ });
+ }
}
@Override
@@ -164,6 +224,13 @@ public class AccountSwitcherSheet extends BottomSheet{
}
}
+ private View makeSimpleListItem(@StringRes int title, @DrawableRes int icon){
+ TextView tv=(TextView) activity.getLayoutInflater().inflate(R.layout.item_text_with_icon, list, false);
+ tv.setText(title);
+ tv.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, 0, 0, 0);
+ return tv;
+ }
+
private class AccountsAdapter extends UsableRecyclerView.Adapter implements ImageLoaderRecyclerAdapter{
public AccountsAdapter(){
super(imgLoader);
@@ -197,55 +264,42 @@ public class AccountSwitcherSheet extends BottomSheet{
}
}
- private class AccountViewHolder extends BindableViewHolder implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
- private final TextView name;
- private final TextView display_name;
- private final TextView display_add_account;
+ private class AccountViewHolder extends BindableViewHolder implements ImageLoaderViewHolder, UsableRecyclerView.Clickable, UsableRecyclerView.LongClickable{
+ private final TextView name, username;
private final ImageView avatar;
- private final ImageButton more;
- private final View currentIcon;
- private final PopupMenu menu;
+ private final CheckableRelativeLayout view;
+ private final View radioButton, extraBtnWrap;
+ private final ImageButton extraBtn;
public AccountViewHolder(){
super(activity, R.layout.item_account_switcher, list);
name=findViewById(R.id.name);
- display_name=findViewById(R.id.display_name);
- display_add_account=findViewById(R.id.add_account);
+ username=findViewById(R.id.username);
+ radioButton=findViewById(R.id.radiobtn);
+ radioButton.setBackground(new RadioButton(activity).getButtonDrawable());
avatar=findViewById(R.id.avatar);
- more=findViewById(R.id.more);
- currentIcon=findViewById(R.id.current);
-
- avatar.setOutlineProvider(OutlineProviders.roundedRect(12));
+ avatar.setOutlineProvider(OutlineProviders.roundedRect(OutlineProviders.RADIUS_MEDIUM));
avatar.setClipToOutline(true);
-
- menu=new PopupMenu(activity, more);
- menu.inflate(R.menu.account_switcher);
- menu.setOnMenuItemClickListener(item1 -> {
- confirmLogOut(item.getID());
- return true;
- });
- more.setOnClickListener(v->menu.show());
+ view=(CheckableRelativeLayout) itemView;
+ extraBtnWrap = findViewById(R.id.extra_btn_wrap);
+ extraBtn = findViewById(R.id.extra_btn);
+ extraBtn.setOnClickListener(this::onExtraBtnClick);
}
@SuppressLint("SetTextI18n")
@Override
public void onBind(AccountSession item){
- display_name.setText(item.self.displayName);
- name.setText("@"+item.self.username+"@"+item.domain);
- if(AccountSessionManager.getInstance().getLastActiveAccountID().equals(item.getID())){
- more.setVisibility(View.GONE);
- currentIcon.setVisibility(View.VISIBLE);
- }else{
- more.setVisibility(View.VISIBLE);
- currentIcon.setVisibility(View.GONE);
+ name.setText(item.self.displayName);
+ username.setText(item.getFullUsername());
+ radioButton.setVisibility(externalShare ? View.GONE : View.VISIBLE);
+ extraBtnWrap.setVisibility(externalShare && openInApp ? View.VISIBLE : View.GONE);
+ if (externalShare) view.setCheckable(false);
+ else {
+ String accountId = fragment != null
+ ? fragment.getAccountID()
+ : AccountSessionManager.getInstance().getLastActiveAccountID();
+ view.setChecked(accountId.equals(item.getID()));
}
-
- if(!logOutEnabled){
- more.setVisibility(View.GONE);
- currentIcon.setVisibility(View.GONE);
- }
- menu.getMenu().findItem(R.id.log_out).setTitle(activity.getString(R.string.log_out_account, "@"+item.self.username));
- UiUtils.enablePopupMenuIcons(activity, menu);
}
@Override
@@ -260,11 +314,31 @@ public class AccountSwitcherSheet extends BottomSheet{
setImage(index, null);
}
+ private void onExtraBtnClick(View view) {
+ setOnDismissListener(null);
+ dismiss();
+ onClick.accept(item.getID(), true);
+ }
+
@Override
public void onClick(){
+ setOnDismissListener(null);
+ if (onClick != null) {
+ dismiss();
+ onClick.accept(item.getID(), false);
+ return;
+ }
+
AccountSessionManager.getInstance().setLastActiveAccountID(item.getID());
- dismiss();
- onClick.accept(AccountSessionManager.getInstance().getAccount(item.getID()));
+ activity.finish();
+ activity.startActivity(new Intent(activity, MainActivity.class));
+ }
+
+ @Override
+ public boolean onLongClick(){
+ if (externalShare) return false;
+ confirmLogOut(item.getID());
+ return true;
}
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/BetterItemAnimator.java b/mastodon/src/main/java/org/joinmastodon/android/ui/BetterItemAnimator.java
index cbdf7d8a3..0bd6afa1f 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/BetterItemAnimator.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/BetterItemAnimator.java
@@ -18,13 +18,15 @@ package org.joinmastodon.android.ui;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
import android.view.View;
import android.view.ViewPropertyAnimator;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
+
+import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
+
import me.grishka.appkit.utils.CubicBezierInterpolator;
import java.util.ArrayList;
@@ -358,7 +360,14 @@ public class BetterItemAnimator extends SimpleItemAnimator{
mChangeAnimations.add(changeInfo.oldHolder);
oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
- oldViewAnim.alpha(0).setListener(new AnimatorListenerAdapter() {
+ float alpha = 0;
+ if (holder instanceof MediaGridStatusDisplayItem.Holder mediaItemHolder) {
+ if (mediaItemHolder.isSizeUpdating()) {
+ alpha = 1; // Image will flicker out and then in if alpha is 0
+ mediaItemHolder.sizeUpdated();
+ }
+ }
+ oldViewAnim.alpha(alpha).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
dispatchChangeStarting(changeInfo.oldHolder, true);
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/OutlineProviders.java b/mastodon/src/main/java/org/joinmastodon/android/ui/OutlineProviders.java
index 921598225..81f05df36 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/OutlineProviders.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/OutlineProviders.java
@@ -8,7 +8,15 @@ import android.view.ViewOutlineProvider;
import me.grishka.appkit.utils.V;
public class OutlineProviders{
- private static SparseArray roundedRects=new SparseArray<>();
+ private static final SparseArray roundedRects=new SparseArray<>();
+ private static final SparseArray topRoundedRects=new SparseArray<>();
+ private static final SparseArray endRoundedRects=new SparseArray<>();
+
+ public static final int RADIUS_XSMALL=4;
+ public static final int RADIUS_SMALL=8;
+ public static final int RADIUS_MEDIUM=12;
+ public static final int RADIUS_LARGE=16;
+ public static final int RADIUS_XLARGE=28;
private OutlineProviders(){
//no instance
@@ -21,6 +29,12 @@ public class OutlineProviders{
outline.setAlpha(view.getAlpha());
}
};
+ public static final ViewOutlineProvider OVAL=new ViewOutlineProvider(){
+ @Override
+ public void getOutline(View view, Outline outline){
+ outline.setOval(0, 0, view.getWidth(), view.getHeight());
+ }
+ };
public static ViewOutlineProvider roundedRect(int dp){
ViewOutlineProvider provider=roundedRects.get(dp);
@@ -31,6 +45,24 @@ public class OutlineProviders{
return provider;
}
+ public static ViewOutlineProvider topRoundedRect(int dp){
+ ViewOutlineProvider provider=topRoundedRects.get(dp);
+ if(provider!=null)
+ return provider;
+ provider=new TopRoundRectOutlineProvider(V.dp(dp));
+ topRoundedRects.put(dp, provider);
+ return provider;
+ }
+
+ public static ViewOutlineProvider endRoundedRect(int dp){
+ ViewOutlineProvider provider=endRoundedRects.get(dp);
+ if(provider!=null)
+ return provider;
+ provider=new EndRoundRectOutlineProvider(V.dp(dp));
+ endRoundedRects.put(dp, provider);
+ return provider;
+ }
+
private static class RoundRectOutlineProvider extends ViewOutlineProvider{
private final int radius;
@@ -43,4 +75,34 @@ public class OutlineProviders{
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), radius);
}
}
+
+ private static class TopRoundRectOutlineProvider extends ViewOutlineProvider{
+ private final int radius;
+
+ private TopRoundRectOutlineProvider(int radius){
+ this.radius=radius;
+ }
+
+ @Override
+ public void getOutline(View view, Outline outline){
+ outline.setRoundRect(0, 0, view.getWidth(), view.getHeight()+radius, radius);
+ }
+ }
+
+ private static class EndRoundRectOutlineProvider extends ViewOutlineProvider{
+ private final int radius;
+
+ private EndRoundRectOutlineProvider(int radius){
+ this.radius=radius;
+ }
+
+ @Override
+ public void getOutline(View view, Outline outline){
+ if(view.getLayoutDirection()==View.LAYOUT_DIRECTION_RTL){
+ outline.setRoundRect(-radius, 0, view.getWidth(), view.getHeight(), radius);
+ }else{
+ outline.setRoundRect(0, 0, view.getWidth()+radius, view.getHeight(), radius);
+ }
+ }
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ExtendedFooterStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ExtendedFooterStatusDisplayItem.java
index 3afc8a134..b87602c29 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ExtendedFooterStatusDisplayItem.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ExtendedFooterStatusDisplayItem.java
@@ -139,6 +139,7 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
Bundle args=new Bundle();
args.putString("account", item.parentFragment.getAccountID());
args.putString("id", item.status.id);
+ args.putString("url", item.status.url);
Nav.go(item.parentFragment.getActivity(), StatusEditHistoryFragment.class, args);
}
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java
index 1bbcb1713..1027d1b31 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java
@@ -148,12 +148,27 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
bindButton(reply, item.status.repliesCount);
bindButton(boost, item.status.reblogsCount);
bindButton(favorite, item.status.favouritesCount);
- reply.setSelected(item.status.repliesCount > 0);
+ // in thread view, direct descendant posts display one direct reply to themselves,
+ // hence in that case displaying whether there is another reply
+ int compareTo = item.isMainStatus || !item.hasDescendantNeighbor ? 0 : 1;
+ reply.setSelected(item.status.repliesCount > compareTo);
boost.setSelected(item.status.reblogged);
favorite.setSelected(item.status.favourited);
bookmark.setSelected(item.status.bookmarked);
- boost.setEnabled(item.status.canBeBoosted(item.accountID));
+ boost.setEnabled(item.status.visibility==StatusPrivacy.PUBLIC || item.status.visibility==StatusPrivacy.UNLISTED || item.status.visibility==StatusPrivacy.LOCAL
+ || (item.status.visibility==StatusPrivacy.PRIVATE && item.status.account.id.equals(AccountSessionManager.getInstance().getAccount(item.accountID).self.id)));
+ int nextPos = getAbsoluteAdapterPosition() + 1;
+ boolean nextIsWarning = item.parentFragment.getDisplayItems().size() > nextPos &&
+ item.parentFragment.getDisplayItems().get(nextPos) instanceof WarningFilteredStatusDisplayItem;
+ boolean condenseBottom = !item.isMainStatus && item.hasDescendantNeighbor &&
+ !nextIsWarning;
+
+ ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) itemView.getLayoutParams();
+ params.setMargins(params.leftMargin, params.topMargin, params.rightMargin,
+ condenseBottom ? V.dp(-8) : 0);
+
+ itemView.requestLayout();
}
private void bindButton(TextView btn, long count){
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HashtagStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HashtagStatusDisplayItem.java
index e0f8a3394..e337083b0 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HashtagStatusDisplayItem.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HashtagStatusDisplayItem.java
@@ -1,7 +1,9 @@
package org.joinmastodon.android.ui.displayitems;
import android.content.Context;
+import android.view.View;
import android.view.ViewGroup;
+import android.widget.RelativeLayout;
import android.widget.TextView;
import org.joinmastodon.android.R;
@@ -25,6 +27,13 @@ public class HashtagStatusDisplayItem extends StatusDisplayItem{
public static class Holder extends StatusDisplayItem.Holder{
private final TextView title, subtitle;
private final HashtagChartView chart;
+ public static final RelativeLayout.LayoutParams
+ withHistoryParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT),
+ withoutHistoryParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+ static {
+ withoutHistoryParams.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
+ }
public Holder(Context context, ViewGroup parent){
super(context, R.layout.item_trending_hashtag, parent);
@@ -37,14 +46,20 @@ public class HashtagStatusDisplayItem extends StatusDisplayItem{
public void onBind(HashtagStatusDisplayItem _item){
Hashtag item=_item.tag;
title.setText('#'+item.name);
- int numPeople = 0;
- if(item.history != null){
- numPeople=item.history.get(0).accounts;
- if(item.history.size()>1)
- numPeople+=item.history.get(1).accounts;
- chart.setData(item.history);
+ if (item.history == null || item.history.isEmpty()) {
+ subtitle.setText(null);
+ chart.setVisibility(View.GONE);
+ title.setLayoutParams(withoutHistoryParams);
+ return;
}
+ chart.setVisibility(View.VISIBLE);
+ title.setLayoutParams(withHistoryParams);
+ int numPeople=item.history.get(0).accounts;
+ if(item.history.size()>1)
+ numPeople+=item.history.get(1).accounts;
subtitle.setText(_item.parentFragment.getResources().getQuantityString(R.plurals.x_people_talking, numPeople, numPeople));
+ chart.setData(item.history);
+
}
}
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java
index 3b29eb45d..5a67bd195 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java
@@ -1,11 +1,9 @@
package org.joinmastodon.android.ui.displayitems;
-import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Outline;
import android.graphics.Paint;
-import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
@@ -27,8 +25,6 @@ import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
-import androidx.annotation.StringRes;
-
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
@@ -39,7 +35,7 @@ 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.ComposeFragment;
-import org.joinmastodon.android.fragments.ListTimelinesFragment;
+import org.joinmastodon.android.fragments.ListsFragment;
import org.joinmastodon.android.fragments.NotificationsListFragment;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.ThreadFragment;
@@ -47,12 +43,10 @@ import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Announcement;
import org.joinmastodon.android.model.Attachment;
-import org.joinmastodon.android.model.ContentType;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.ScheduledStatus;
import org.joinmastodon.android.model.Status;
-import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import org.joinmastodon.android.ui.utils.UiUtils;
@@ -62,7 +56,6 @@ import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
@@ -290,7 +283,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
args.putString("account", item.parentFragment.getAccountID());
args.putString("profileAccount", account.id);
args.putString("profileDisplayUsername", account.getDisplayUsername());
- Nav.go(item.parentFragment.getActivity(), ListTimelinesFragment.class, args);
+ Nav.go(item.parentFragment.getActivity(), ListsFragment.class, args);
}
if(!item.status.filterRevealed){
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/MediaGridStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/MediaGridStatusDisplayItem.java
index 9e92dc8b6..984efddb4 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/MediaGridStatusDisplayItem.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/MediaGridStatusDisplayItem.java
@@ -7,6 +7,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Activity;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.View;
@@ -17,7 +18,6 @@ import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
-import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Attachment;
@@ -40,7 +40,7 @@ import me.grishka.appkit.utils.CubicBezierInterpolator;
public class MediaGridStatusDisplayItem extends StatusDisplayItem{
private static final String TAG="MediaGridDisplayItem";
- private final PhotoLayoutHelper.TiledLayoutResult tiledLayout;
+ private PhotoLayoutHelper.TiledLayoutResult tiledLayout;
private final TypedObjectPool viewPool;
private final List attachments;
private final ArrayList requests=new ArrayList<>();
@@ -98,6 +98,8 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
private int altTextIndex=-1;
private Animator altTextAnimator;
+ private boolean sizeUpdating = false;
+
public Holder(Activity activity, ViewGroup parent){
super(new FrameLayoutThatOnlyMeasuresFirstChild(activity));
wrapper=(FrameLayout)itemView;
@@ -126,6 +128,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
}
layout.removeAllViews();
controllers.clear();
+
int i=0;
for(Attachment att:item.attachments){
MediaAttachmentViewController c=item.viewPool.obtain(switch(att.type){
@@ -158,6 +161,19 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
@Override
public void setImage(int index, Drawable drawable){
+ Rect bounds=drawable.getBounds();
+ drawable.setBounds(bounds.left, bounds.top, bounds.left+drawable.getIntrinsicWidth(), bounds.top+drawable.getIntrinsicHeight());
+ if(item.attachments.get(index).meta==null){
+ Attachment.Metadata metadata = new Attachment.Metadata();
+ metadata.width=drawable.getIntrinsicWidth();
+ metadata.height=drawable.getIntrinsicHeight();
+ item.attachments.get(index).meta=metadata;
+
+ item.tiledLayout=PhotoLayoutHelper.processThumbs(item.attachments);
+ sizeUpdating = true;
+ item.parentFragment.onImageUpdated(this, index);
+ }
+
controllers.get(index).setImage(drawable);
}
@@ -314,5 +330,13 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
layout.setClipChildren(clip);
wrapper.setClipChildren(clip);
}
+
+ public boolean isSizeUpdating() {
+ return sizeUpdating;
+ }
+
+ public void sizeUpdated() {
+ sizeUpdating = false;
+ }
}
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java
index 5ebb49209..874629811 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java
@@ -2,6 +2,7 @@ package org.joinmastodon.android.ui.displayitems;
import android.app.Activity;
import android.content.Context;
+import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
@@ -26,6 +27,7 @@ import org.joinmastodon.android.model.ScheduledStatus;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.PhotoLayoutHelper;
import org.joinmastodon.android.ui.text.HtmlParser;
+import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import org.parceler.Parcels;
@@ -45,6 +47,23 @@ public abstract class StatusDisplayItem{
public final BaseStatusListFragment parentFragment;
public boolean inset;
public int index;
+ public boolean
+ hasDescendantNeighbor = false,
+ hasAncestoringNeighbor = false,
+ isMainStatus = true,
+ isDirectDescendant = false;
+
+ public void setAncestryInfo(
+ boolean hasDescendantNeighbor,
+ boolean hasAncestoringNeighbor,
+ boolean isMainStatus,
+ boolean isDirectDescendant
+ ) {
+ this.hasDescendantNeighbor = hasDescendantNeighbor;
+ this.hasAncestoringNeighbor = hasAncestoringNeighbor;
+ this.isMainStatus = isMainStatus;
+ this.isDirectDescendant = isDirectDescendant;
+ }
public StatusDisplayItem(String parentID, BaseStatusListFragment parentFragment){
this.parentID=parentID;
@@ -163,6 +182,15 @@ public abstract class StatusDisplayItem{
items.add(replyLine);
}
+ if (statusForContent.quote != null) {
+ boolean hasQuoteInlineTag = statusForContent.content.contains("");
+ if (!hasQuoteInlineTag) {
+ String quoteUrl = statusForContent.quote.url;
+ String quoteInline = String.format("%sRE: %s ",
+ statusForContent.content.endsWith("
") ? "" : " ", quoteUrl, quoteUrl);
+ statusForContent.content += quoteInline;
+ }
+ }
if(!TextUtils.isEmpty(statusForContent.content))
items.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent, disableTranslate));
else if (!GlobalUserPreferences.replyLineAboveHeader && replyLine != null)
@@ -174,6 +202,12 @@ public abstract class StatusDisplayItem{
.filter(att->att.type.isImage() && !att.type.equals(Attachment.Type.UNKNOWN))
.collect(Collectors.toList());
if(!imageAttachments.isEmpty()){
+ int color = UiUtils.getThemeColor(fragment.getContext(), R.attr.colorAccentLightest);
+ for (Attachment att : imageAttachments) {
+ if (att.blurhashPlaceholder == null) {
+ att.blurhashPlaceholder = new ColorDrawable(color);
+ }
+ }
PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(imageAttachments);
items.add(new MediaGridStatusDisplayItem(parentID, fragment, layout, imageAttachments, statusForContent));
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/TextStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/TextStatusDisplayItem.java
index cee067fc5..59d026924 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/TextStatusDisplayItem.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/TextStatusDisplayItem.java
@@ -65,6 +65,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
spoilerEmojiHelper.setText(parsedSpoilerText);
}
session = AccountSessionManager.getInstance().getAccount(parentFragment.getAccountID());
+ UiUtils.loadMaxWidth(parentFragment.getContext());
}
public void setTranslationShown(boolean translationShown) {
@@ -225,13 +226,33 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
readMore.setText(item.status.textExpanded ? R.string.sk_collapse : R.string.sk_expand);
spaceBelowText.setVisibility(translateVisible ? View.VISIBLE : View.GONE);
+ // remove additional padding when (transparently padded) translate button is visible
+ int pos = getAbsoluteAdapterPosition();
+ itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(),
+ (translateVisible &&
+ item.parentFragment.getDisplayItems().size() >= pos + 1 &&
+ item.parentFragment.getDisplayItems().get(pos + 1) instanceof FooterStatusDisplayItem)
+ ? 0 : V.dp(12)
+ );
+
if (!GlobalUserPreferences.collapseLongPosts) {
textScrollView.setLayoutParams(wrapParams);
readMore.setVisibility(View.GONE);
}
+ // incredibly ugly workaround for https://github.com/sk22/megalodon/issues/520
+ // i am so, so sorry. FIXME
+ // attempts to use OnPreDrawListener, OnGlobalLayoutListener and .post have failed -
+ // the view didn't want to reliably update after calling .setVisibility etc :(
+ int width = parent.getWidth() != 0 ? parent.getWidth()
+ : item.parentFragment.getView().getWidth() != 0
+ ? item.parentFragment.getView().getWidth()
+ : item.parentFragment.getParentFragment() != null && item.parentFragment.getParentFragment().getView().getWidth() != 0
+ ? item.parentFragment.getParentFragment().getView().getWidth() // YIKES
+ : UiUtils.MAX_WIDTH;
+
text.measure(
- View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
if (GlobalUserPreferences.collapseLongPosts && !item.status.textExpandable) {
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/DiscoverInfoBannerHelper.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/DiscoverInfoBannerHelper.java
index c357378b3..9a6f530ac 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/DiscoverInfoBannerHelper.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/DiscoverInfoBannerHelper.java
@@ -38,6 +38,7 @@ public class DiscoverInfoBannerHelper{
case LOCAL_TIMELINE -> R.string.local_timeline_info_banner;
case FEDERATED_TIMELINE -> R.string.sk_federated_timeline_info_banner;
case POST_NOTIFICATIONS -> R.string.sk_notify_posts_info_banner;
+ case BUBBLE_TIMELINE -> R.string.sk_bubble_timeline_info_banner;
});
}
}
@@ -63,6 +64,7 @@ public class DiscoverInfoBannerHelper{
LOCAL_TIMELINE,
FEDERATED_TIMELINE,
POST_NOTIFICATIONS,
-// ACCOUNTS
+// ACCOUNTS,
+ BUBBLE_TIMELINE
}
}
\ No newline at end of file
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
index 0459e269e..3aa65d630 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
@@ -7,6 +7,7 @@ import static org.joinmastodon.android.GlobalUserPreferences.trueBlackTheme;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
+import android.app.Fragment;
import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
@@ -102,6 +103,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
+import java.net.URL;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
@@ -114,6 +116,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -141,12 +144,13 @@ import me.grishka.appkit.utils.V;
import okhttp3.MediaType;
public class UiUtils {
- private static final Handler mainHandler = new Handler(Looper.getMainLooper());
+ private static Handler mainHandler = new Handler(Looper.getMainLooper());
private static final DateTimeFormatter DATE_FORMATTER_SHORT_WITH_YEAR = DateTimeFormatter.ofPattern("d MMM uuuu"), DATE_FORMATTER_SHORT = DateTimeFormatter.ofPattern("d MMM");
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT);
public static int MAX_WIDTH;
- private UiUtils(){}
+ private UiUtils() {
+ }
public static void loadMaxWidth(Context ctx) {
if (MAX_WIDTH == 0) MAX_WIDTH = (int) ctx.getResources().getDimension(R.dimen.layout_max_width);
@@ -159,33 +163,33 @@ public class UiUtils {
.setShowTitle(true)
.build()
.launchUrl(context, Uri.parse(url));
- }else{
+ } else {
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
}
- }catch(ActivityNotFoundException x){
+ } catch (ActivityNotFoundException x) {
Toast.makeText(context, R.string.no_app_to_handle_action, Toast.LENGTH_SHORT).show();
}
}
- public static String formatRelativeTimestamp(Context context, Instant instant){
- long t=instant.toEpochMilli();
- long now=System.currentTimeMillis();
- long diff=now-t;
- if(diff<1000L){
+ public static String formatRelativeTimestamp(Context context, Instant instant) {
+ long t = instant.toEpochMilli();
+ long now = System.currentTimeMillis();
+ long diff = now - t;
+ if (diff < 1000L) {
return context.getString(R.string.time_now);
- }else if(diff<60_000L){
- return context.getString(R.string.time_seconds, diff/1000L);
- }else if(diff<3600_000L){
- return context.getString(R.string.time_minutes, diff/60_000L);
- }else if(diff<3600_000L*24L){
- return context.getString(R.string.time_hours, diff/3600_000L);
- }else{
- int days=(int)(diff/(3600_000L*24L));
- if(days>30){
- ZonedDateTime dt=instant.atZone(ZoneId.systemDefault());
- if(dt.getYear()==ZonedDateTime.now().getYear()){
+ } else if (diff < 60_000L) {
+ return context.getString(R.string.time_seconds, diff / 1000L);
+ } else if (diff < 3600_000L) {
+ return context.getString(R.string.time_minutes, diff / 60_000L);
+ } else if (diff < 3600_000L * 24L) {
+ return context.getString(R.string.time_hours, diff / 3600_000L);
+ } else {
+ int days = (int) (diff / (3600_000L * 24L));
+ if (days > 30) {
+ ZonedDateTime dt = instant.atZone(ZoneId.systemDefault());
+ if (dt.getYear() == ZonedDateTime.now().getYear()) {
return DATE_FORMATTER_SHORT.format(dt);
- }else{
+ } else {
return DATE_FORMATTER_SHORT_WITH_YEAR.format(dt);
}
}
@@ -207,76 +211,77 @@ public class UiUtils {
long diff=now-t;
if(diff<1000L){
return context.getString(R.string.time_just_now);
- }else if(diff<60_000L){
- int secs=(int)(diff/1000L);
+ } else if (diff < 60_000L) {
+ int secs = (int) (diff / 1000L);
return context.getResources().getQuantityString(R.plurals.x_seconds_ago, secs, secs);
- }else if(diff<3600_000L){
- int mins=(int)(diff/60_000L);
+ } else if (diff < 3600_000L) {
+ int mins = (int) (diff / 60_000L);
return context.getResources().getQuantityString(R.plurals.x_minutes_ago, mins, mins);
- }else{
+ } else {
return DATE_TIME_FORMATTER.format(instant.atZone(ZoneId.systemDefault()));
}
}
- public static String formatTimeLeft(Context context, Instant instant){
- long t=instant.toEpochMilli();
- long now=System.currentTimeMillis();
- long diff=t-now;
- if(diff<60_000L){
- int secs=(int)(diff/1000L);
+ public static String formatTimeLeft(Context context, Instant instant) {
+ long t = instant.toEpochMilli();
+ long now = System.currentTimeMillis();
+ long diff = t - now;
+ if (diff < 60_000L) {
+ int secs = (int) (diff / 1000L);
return context.getResources().getQuantityString(R.plurals.x_seconds_left, secs, secs);
- }else if(diff<3600_000L){
- int mins=(int)(diff/60_000L);
+ } else if (diff < 3600_000L) {
+ int mins = (int) (diff / 60_000L);
return context.getResources().getQuantityString(R.plurals.x_minutes_left, mins, mins);
- }else if(diff<3600_000L*24L){
- int hours=(int)(diff/3600_000L);
+ } else if (diff < 3600_000L * 24L) {
+ int hours = (int) (diff / 3600_000L);
return context.getResources().getQuantityString(R.plurals.x_hours_left, hours, hours);
- }else{
- int days=(int)(diff/(3600_000L*24L));
+ } else {
+ int days = (int) (diff / (3600_000L * 24L));
return context.getResources().getQuantityString(R.plurals.x_days_left, days, days);
}
}
@SuppressLint("DefaultLocale")
- public static String abbreviateNumber(int n){
- if(n<1000){
+ public static String abbreviateNumber(int n) {
+ if (n < 1000) {
return String.format("%,d", n);
- }else if(n<1_000_000){
- float a=n/1000f;
- return a>99f ? String.format("%,dK", (int)Math.floor(a)) : String.format("%,.1fK", a);
- }else{
- float a=n/1_000_000f;
- return a>99f ? String.format("%,dM", (int)Math.floor(a)) : String.format("%,.1fM", n/1_000_000f);
+ } else if (n < 1_000_000) {
+ float a = n / 1000f;
+ return a > 99f ? String.format("%,dK", (int) Math.floor(a)) : String.format("%,.1fK", a);
+ } else {
+ float a = n / 1_000_000f;
+ return a > 99f ? String.format("%,dM", (int) Math.floor(a)) : String.format("%,.1fM", n / 1_000_000f);
}
}
@SuppressLint("DefaultLocale")
- public static String abbreviateNumber(long n){
- if(n<1_000_000_000L)
- return abbreviateNumber((int)n);
+ public static String abbreviateNumber(long n) {
+ if (n < 1_000_000_000L)
+ return abbreviateNumber((int) n);
- double a=n/1_000_000_000.0;
- return a>99f ? String.format("%,dB", (int)Math.floor(a)) : String.format("%,.1fB", n/1_000_000_000.0);
+ double a = n / 1_000_000_000.0;
+ return a > 99f ? String.format("%,dB", (int) Math.floor(a)) : String.format("%,.1fB", n / 1_000_000_000.0);
}
/**
* Android 6.0 has a bug where start and end compound drawables don't get tinted.
* This works around it by setting the tint colors directly to the drawables.
+ *
* @param textView
*/
- public static void fixCompoundDrawableTintOnAndroid6(TextView textView){
- Drawable[] drawables=textView.getCompoundDrawablesRelative();
- for(int i=0;i> spansByEmoji=Arrays.stream(spans).collect(Collectors.groupingBy(s->s.emoji));
- for(Map.Entry> emoji:spansByEmoji.entrySet()){
- ViewImageLoader.load(new ViewImageLoader.Target(){
+ int emojiSize = V.dp(20);
+ Map> spansByEmoji = Arrays.stream(spans).collect(Collectors.groupingBy(s -> s.emoji));
+ for (Map.Entry> emoji : spansByEmoji.entrySet()) {
+ ViewImageLoader.load(new ViewImageLoader.Target() {
@Override
- public void setImageDrawable(Drawable d){
- if(d==null)
+ public void setImageDrawable(Drawable d) {
+ if (d == null)
return;
- for(CustomEmojiSpan span:emoji.getValue()){
+ for (CustomEmojiSpan span : emoji.getValue()) {
span.setDrawable(d);
}
view.invalidate();
}
@Override
- public View getView(){
+ public View getView() {
return view;
}
}, null, new UrlImageLoaderRequest(emoji.getKey().url, emojiSize, emojiSize), null, false, true);
@@ -371,22 +380,22 @@ public class UiUtils {
Bundle args = new Bundle();
args.putString("account", selfID);
args.putString("profileAccountID", id);
- 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){
- Bundle args=new Bundle();
+ public static void openHashtagTimeline(Context context, String accountID, String hashtag, @Nullable Boolean following) {
+ Bundle args = new Bundle();
args.putString("account", accountID);
args.putString("hashtag", hashtag);
if (following != null) args.putBoolean("following", following);
- Nav.go((Activity)context, HashtagTimelineFragment.class, args);
+ Nav.go((Activity) context, HashtagTimelineFragment.class, args);
}
- public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, Runnable onConfirmed){
+ public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, Runnable onConfirmed) {
showConfirmationAlert(context, title, message, confirmButton, 0, onConfirmed);
}
- public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, @DrawableRes int icon, Runnable onConfirmed){
+ public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, @DrawableRes int icon, Runnable onConfirmed) {
showConfirmationAlert(context, context.getString(title), context.getString(message), context.getString(confirmButton), icon, onConfirmed);
}
@@ -404,25 +413,25 @@ public class UiUtils {
.show();
}
- public static void confirmToggleBlockUser(Activity activity, String accountID, Account account, boolean currentlyBlocked, Consumer resultCallback){
+ public static void confirmToggleBlockUser(Activity activity, String accountID, Account account, boolean currentlyBlocked, Consumer resultCallback) {
showConfirmationAlert(activity, activity.getString(currentlyBlocked ? R.string.confirm_unblock_title : R.string.confirm_block_title),
activity.getString(currentlyBlocked ? R.string.confirm_unblock : R.string.confirm_block, account.displayName),
activity.getString(currentlyBlocked ? R.string.do_unblock : R.string.do_block),
R.drawable.ic_fluent_person_prohibited_28_regular,
- ()->{
+ () -> {
new SetAccountBlocked(account.id, !currentlyBlocked)
- .setCallback(new Callback<>(){
+ .setCallback(new Callback<>() {
@Override
- public void onSuccess(Relationship result){
+ public void onSuccess(Relationship result) {
if (activity == null) return;
resultCallback.accept(result);
- if(!currentlyBlocked){
+ if (!currentlyBlocked) {
E.post(new RemoveAccountPostsEvent(accountID, account.id, false));
}
}
@Override
- public void onError(ErrorResponse error){
+ public void onError(ErrorResponse error) {
error.showToast(activity);
}
})
@@ -431,54 +440,54 @@ public class UiUtils {
});
}
- public static void confirmSoftBlockUser(Activity activity, String accountID, Account account, Consumer resultCallback){
+ public static void confirmSoftBlockUser(Activity activity, String accountID, Account account, Consumer resultCallback) {
showConfirmationAlert(activity,
activity.getString(R.string.sk_remove_follower),
activity.getString(R.string.sk_remove_follower_confirm, account.displayName),
activity.getString(R.string.sk_do_remove_follower),
R.drawable.ic_fluent_person_delete_24_regular,
() -> new SetAccountBlocked(account.id, true).setCallback(new Callback<>() {
- @Override
- public void onSuccess(Relationship relationship) {
- new SetAccountBlocked(account.id, false).setCallback(new Callback<>() {
- @Override
- public void onSuccess(Relationship relationship) {
- if (activity == null) return;
- Toast.makeText(activity, R.string.sk_remove_follower_success, Toast.LENGTH_SHORT).show();
- resultCallback.accept(relationship);
- }
+ @Override
+ public void onSuccess(Relationship relationship) {
+ new SetAccountBlocked(account.id, false).setCallback(new Callback<>() {
+ @Override
+ public void onSuccess(Relationship relationship) {
+ if (activity == null) return;
+ Toast.makeText(activity, R.string.sk_remove_follower_success, Toast.LENGTH_SHORT).show();
+ resultCallback.accept(relationship);
+ }
- @Override
- public void onError(ErrorResponse error) {
- error.showToast(activity);
- resultCallback.accept(relationship);
- }
- }).exec(accountID);
- }
+ @Override
+ public void onError(ErrorResponse error) {
+ error.showToast(activity);
+ resultCallback.accept(relationship);
+ }
+ }).exec(accountID);
+ }
- @Override
- public void onError(ErrorResponse error) {
- error.showToast(activity);
- }
- }).exec(accountID)
+ @Override
+ public void onError(ErrorResponse error) {
+ error.showToast(activity);
+ }
+ }).exec(accountID)
);
}
- public static void confirmToggleBlockDomain(Activity activity, String accountID, String domain, boolean currentlyBlocked, Runnable resultCallback){
+ public static void confirmToggleBlockDomain(Activity activity, String accountID, String domain, boolean currentlyBlocked, Runnable resultCallback) {
showConfirmationAlert(activity, activity.getString(currentlyBlocked ? R.string.confirm_unblock_domain_title : R.string.confirm_block_domain_title),
activity.getString(currentlyBlocked ? R.string.confirm_unblock : R.string.confirm_block, domain),
activity.getString(currentlyBlocked ? R.string.do_unblock : R.string.do_block),
R.drawable.ic_fluent_shield_28_regular,
- ()->{
+ () -> {
new SetDomainBlocked(domain, !currentlyBlocked)
- .setCallback(new Callback<>(){
+ .setCallback(new Callback<>() {
@Override
- public void onSuccess(Object result){
+ public void onSuccess(Object result) {
resultCallback.run();
}
@Override
- public void onError(ErrorResponse error){
+ public void onError(ErrorResponse error) {
error.showToast(activity);
}
})
@@ -585,9 +594,9 @@ public class UiUtils {
R.string.delete,
R.drawable.ic_fluent_delete_28_regular,
() -> new DeleteStatus.Scheduled(status.id)
- .setCallback(new Callback<>(){
+ .setCallback(new Callback<>() {
@Override
- public void onSuccess(Object o){
+ public void onSuccess(Object o) {
resultCallback.run();
E.post(new ScheduledStatusDeletedEvent(status.id, accountID));
}
@@ -675,38 +684,39 @@ public class UiUtils {
setRelationshipToActionButton(relationship, button, false);
}
- public static void setRelationshipToActionButton(Relationship relationship, Button button, boolean keepText){
+ public static void setRelationshipToActionButton(Relationship relationship, Button button, boolean keepText) {
CharSequence textBefore = keepText ? button.getText() : null;
boolean secondaryStyle;
- if(relationship.blocking){
+ if (relationship.blocking) {
button.setText(R.string.button_blocked);
- secondaryStyle=true;
- }else if(relationship.blockedBy){
- button.setText(R.string.button_follow);
- secondaryStyle=false;
- }else if(relationship.requested){
+ secondaryStyle = true;
+// } else if (relationship.blockedBy) {
+// button.setText(R.string.button_follow);
+// secondaryStyle = false;
+ } else if (relationship.requested) {
button.setText(R.string.button_follow_pending);
- secondaryStyle=true;
- }else if(!relationship.following){
+ secondaryStyle = true;
+ } else if (!relationship.following) {
button.setText(relationship.followedBy ? R.string.follow_back : R.string.button_follow);
- secondaryStyle=false;
- }else{
+ secondaryStyle = false;
+ } else {
button.setText(R.string.button_following);
- secondaryStyle=true;
+ secondaryStyle = true;
}
if (keepText) button.setText(textBefore);
- button.setEnabled(!relationship.blockedBy);
- int attr=secondaryStyle ? R.attr.secondaryButtonStyle : android.R.attr.buttonStyle;
- TypedArray ta=button.getContext().obtainStyledAttributes(new int[]{attr});
- int styleRes=ta.getResourceId(0, 0);
+// https://github.com/sk22/megalodon/issues/526
+// button.setEnabled(!relationship.blockedBy);
+ int attr = secondaryStyle ? R.attr.secondaryButtonStyle : android.R.attr.buttonStyle;
+ TypedArray ta = button.getContext().obtainStyledAttributes(new int[]{attr});
+ int styleRes = ta.getResourceId(0, 0);
ta.recycle();
- ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background});
+ ta = button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background});
button.setBackground(ta.getDrawable(0));
ta.recycle();
- ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.textColor});
- if(relationship.blocking)
+ ta = button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.textColor});
+ if (relationship.blocking)
button.setTextColor(button.getResources().getColorStateList(R.color.error_600));
else
button.setTextColor(ta.getColorStateList(0));
@@ -721,7 +731,7 @@ public class UiUtils {
public void onSuccess(Relationship result) {
resultCallback.accept(result);
progressCallback.accept(false);
- Toast.makeText(activity, activity.getString(result.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username), Toast.LENGTH_SHORT).show();
+ Toast.makeText(activity, activity.getString(result.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@' + account.username), Toast.LENGTH_SHORT).show();
}
@Override
@@ -839,7 +849,8 @@ public class UiUtils {
@Override
public void onSuccess(Relationship rel) {
E.post(new FollowRequestHandledEvent(accountID, false, account, rel));
- if (notificationID != null) E.post(new NotificationDeletedEvent(notificationID));
+ if (notificationID != null)
+ E.post(new NotificationDeletedEvent(notificationID));
resultCallback.accept(rel);
}
@@ -852,34 +863,34 @@ public class UiUtils {
}
}
- public static void updateList(List oldList, List newList, RecyclerView list, RecyclerView.Adapter> adapter, BiPredicate areItemsSame){
+ public static void updateList(List oldList, List newList, RecyclerView list, RecyclerView.Adapter> adapter, BiPredicate areItemsSame) {
// Save topmost item position and offset because for some reason RecyclerView would scroll the list to weird places when you insert items at the top
int topItem, topItemOffset;
- if(list.getChildCount()==0){
- topItem=topItemOffset=0;
- }else{
- View child=list.getChildAt(0);
- topItem=list.getChildAdapterPosition(child);
- topItemOffset=child.getTop();
+ if (list.getChildCount() == 0) {
+ topItem = topItemOffset = 0;
+ } else {
+ View child = list.getChildAt(0);
+ topItem = list.getChildAdapterPosition(child);
+ topItemOffset = child.getTop();
}
- DiffUtil.calculateDiff(new DiffUtil.Callback(){
+ DiffUtil.calculateDiff(new DiffUtil.Callback() {
@Override
- public int getOldListSize(){
+ public int getOldListSize() {
return oldList.size();
}
@Override
- public int getNewListSize(){
+ public int getNewListSize() {
return newList.size();
}
@Override
- public boolean areItemsTheSame(int oldItemPosition, int newItemPosition){
+ public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return areItemsSame.test(oldList.get(oldItemPosition), newList.get(newItemPosition));
}
@Override
- public boolean areContentsTheSame(int oldItemPosition, int newItemPosition){
+ public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return true;
}
}).dispatchUpdatesTo(adapter);
@@ -887,34 +898,35 @@ public class UiUtils {
list.scrollBy(0, topItemOffset);
}
- public static Bitmap getBitmapFromDrawable(Drawable d){
- if(d instanceof BitmapDrawable)
+ public static Bitmap getBitmapFromDrawable(Drawable d) {
+ if (d instanceof BitmapDrawable)
return ((BitmapDrawable) d).getBitmap();
- Bitmap bitmap=Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+ Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
d.draw(new Canvas(bitmap));
return bitmap;
}
public static void insetPopupMenuIcon(Context context, MenuItem item) {
- ColorStateList iconTint=ColorStateList.valueOf(UiUtils.getThemeColor(context, android.R.attr.textColorSecondary));
+ ColorStateList iconTint = ColorStateList.valueOf(UiUtils.getThemeColor(context, android.R.attr.textColorSecondary));
insetPopupMenuIcon(item, iconTint);
}
+
public static void insetPopupMenuIcon(MenuItem item, ColorStateList iconTint) {
- Drawable icon=item.getIcon().mutate();
- if(Build.VERSION.SDK_INT>=26) item.setIconTintList(iconTint);
+ Drawable icon = item.getIcon().mutate();
+ if (Build.VERSION.SDK_INT >= 26) item.setIconTintList(iconTint);
else icon.setTintList(iconTint);
- icon=new InsetDrawable(icon, V.dp(8), 0, V.dp(8), 0);
+ icon = new InsetDrawable(icon, V.dp(8), 0, V.dp(8), 0);
item.setIcon(icon);
- SpannableStringBuilder ssb=new SpannableStringBuilder(item.getTitle());
+ SpannableStringBuilder ssb = new SpannableStringBuilder(item.getTitle());
item.setTitle(ssb);
}
public static void resetPopupItemTint(MenuItem item) {
- if(Build.VERSION.SDK_INT>=26) {
+ if (Build.VERSION.SDK_INT >= 26) {
item.setIconTintList(null);
} else {
- Drawable icon=item.getIcon().mutate();
+ Drawable icon = item.getIcon().mutate();
icon.setTintList(null);
item.setIcon(icon);
}
@@ -923,7 +935,7 @@ public class UiUtils {
/// Add icons to the menu.
/// Passing in items will be colored to be visible on the background.
public static void enableOptionsMenuIcons(Context context, Menu menu, @IdRes int... asAction) {
- if(menu.getClass().getSimpleName().equals("MenuBuilder")){
+ if (menu.getClass().getSimpleName().equals("MenuBuilder")) {
try {
Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE);
m.setAccessible(true);
@@ -935,31 +947,33 @@ public class UiUtils {
}
public static void enableMenuIcons(Context context, Menu m, @IdRes int... exclude) {
- ColorStateList iconTint=ColorStateList.valueOf(UiUtils.getThemeColor(context, android.R.attr.textColorSecondary));
- for(int i=0;i id == item.getItemId())) continue;
+ if (item.getIcon() == null || Arrays.stream(exclude).anyMatch(id -> id == item.getItemId()))
+ continue;
insetPopupMenuIcon(item, iconTint);
}
}
- public static void enablePopupMenuIcons(Context context, PopupMenu menu){
- Menu m=menu.getMenu();
- if(Build.VERSION.SDK_INT>=29){
+ public static void enablePopupMenuIcons(Context context, PopupMenu menu) {
+ Menu m = menu.getMenu();
+ if (Build.VERSION.SDK_INT >= 29) {
menu.setForceShowIcon(true);
- }else{
- try{
- Method setOptionalIconsVisible=m.getClass().getDeclaredMethod("setOptionalIconsVisible", boolean.class);
+ } else {
+ try {
+ Method setOptionalIconsVisible = m.getClass().getDeclaredMethod("setOptionalIconsVisible", boolean.class);
setOptionalIconsVisible.setAccessible(true);
setOptionalIconsVisible.invoke(m, true);
- }catch(Exception ignore){}
+ } catch (Exception ignore) {
+ }
}
enableMenuIcons(context, m);
}
- public static void setUserPreferredTheme(Context context){
+ public static void setUserPreferredTheme(Context context) {
context.setTheme(switch (theme) {
case LIGHT -> R.style.Theme_Mastodon_Light;
case DARK -> trueBlackTheme ? R.style.Theme_Mastodon_Dark_TrueBlack : R.style.Theme_Mastodon_Dark;
@@ -969,10 +983,11 @@ public class UiUtils {
ColorPalette palette = ColorPalette.palettes.get(GlobalUserPreferences.color);
if (palette != null) palette.apply(context);
}
- public static boolean isDarkTheme(){
- if(theme==GlobalUserPreferences.ThemePreference.AUTO)
- return (MastodonApp.context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)==Configuration.UI_MODE_NIGHT_YES;
- return theme==GlobalUserPreferences.ThemePreference.DARK;
+
+ public static boolean isDarkTheme() {
+ if (theme == GlobalUserPreferences.ThemePreference.AUTO)
+ return (MastodonApp.context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
+ return theme == GlobalUserPreferences.ThemePreference.DARK;
}
// https://mastodon.foo.bar/@User
@@ -1001,7 +1016,8 @@ public class UiUtils {
return false;
}
- if (uri.getQuery() != null || uri.getFragment() != null || uri.getPath() == null) return false;
+ if (uri.getQuery() != null || uri.getFragment() != null || uri.getPath() == null)
+ return false;
String it = uri.getPath();
return it.matches("^/@[^/]+$") ||
@@ -1021,13 +1037,13 @@ public class UiUtils {
public static String getInstanceName(String accountID) {
AccountSession session = AccountSessionManager.getInstance().getAccount(accountID);
- Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
- return instance != null && !instance.title.isBlank() ? instance.title : session.domain;
+ Optional instance = session.getInstance();
+ return instance.isPresent() && !instance.get().title.isBlank() ? instance.get().title : session.domain;
}
public static void pickAccount(Context context, String exceptFor, @StringRes int titleRes, @DrawableRes int iconRes, Consumer sessionConsumer, Consumer transformDialog) {
- List sessions=AccountSessionManager.getInstance().getLoggedInAccounts()
- .stream().filter(s->!s.getID().equals(exceptFor)).collect(Collectors.toList());
+ List sessions = AccountSessionManager.getInstance().getLoggedInAccounts()
+ .stream().filter(s -> !s.getID().equals(exceptFor)).collect(Collectors.toList());
AlertDialog.Builder builder = new M3AlertDialogBuilder(context)
.setItems(
@@ -1140,7 +1156,7 @@ public class UiUtils {
error.showToast(context);
}
})
- .wrapProgress((Activity)context, R.string.loading, true,
+ .wrapProgress((Activity) context, R.string.loading, true,
d -> transformDialogForLookup(context, targetAccountID, null, d))
.exec(targetAccountID);
}
@@ -1250,28 +1266,36 @@ public class UiUtils {
}
}
- public static void openURL(Context context, String accountID, String url, boolean launchBrowser){
- Uri uri=Uri.parse(url);
- List path=uri.getPathSegments();
- if(accountID!=null && "https".equals(uri.getScheme())){
- if(path.size()==2 && path.get(0).matches("^@[a-zA-Z0-9_]+$") && path.get(1).matches("^[0-9]+$") && AccountSessionManager.getInstance().getAccount(accountID).domain.equalsIgnoreCase(uri.getAuthority())){
+ public static void openURL(Context context, String accountID, String url, boolean launchBrowser) {
+ lookupURL(context, accountID, url, launchBrowser, (clazz, args) -> {
+ if (clazz == null) return;
+ Nav.go((Activity) context, clazz, args);
+ });
+ }
+
+ public static void lookupURL(Context context, String accountID, String url, boolean launchBrowser, BiConsumer, Bundle> go) {
+ Uri uri = Uri.parse(url);
+ List path = uri.getPathSegments();
+ if (accountID != null && "https".equals(uri.getScheme())) {
+ if (path.size() == 2 && path.get(0).matches("^@[a-zA-Z0-9_]+$") && path.get(1).matches("^[0-9]+$") && AccountSessionManager.getInstance().getAccount(accountID).domain.equalsIgnoreCase(uri.getAuthority())) {
new GetStatusByID(path.get(1))
- .setCallback(new Callback<>(){
+ .setCallback(new Callback<>() {
@Override
- public void onSuccess(Status result){
- Bundle args=new Bundle();
+ public void onSuccess(Status result) {
+ Bundle args = new Bundle();
args.putString("account", accountID);
args.putParcelable("status", Parcels.wrap(result));
- Nav.go((Activity) context, ThreadFragment.class, args);
+ go.accept(ThreadFragment.class, args);
}
@Override
- public void onError(ErrorResponse error){
+ public void onError(ErrorResponse error) {
error.showToast(context);
if (launchBrowser) launchWebBrowser(context, url);
+ go.accept(null, null);
}
})
- .wrapProgress((Activity)context, R.string.loading, true,
+ .wrapProgress((Activity) context, R.string.loading, true,
d -> transformDialogForLookup(context, accountID, url, d))
.exec(accountID);
return;
@@ -1280,54 +1304,62 @@ public class UiUtils {
.setCallback(new Callback<>() {
@Override
public void onSuccess(SearchResults results) {
- Bundle args=new Bundle();
+ Bundle args = new Bundle();
args.putString("account", accountID);
if (!results.statuses.isEmpty()) {
args.putParcelable("status", Parcels.wrap(results.statuses.get(0)));
- Nav.go((Activity) context, ThreadFragment.class, args);
- } else if (!results.accounts.isEmpty()) {
- args.putParcelable("profileAccount", Parcels.wrap(results.accounts.get(0)));
- Nav.go((Activity) context, ProfileFragment.class, args);
- } else {
- if (launchBrowser) launchWebBrowser(context, url);
- else Toast.makeText(context, R.string.sk_resource_not_found, Toast.LENGTH_SHORT).show();
+ go.accept(ThreadFragment.class, args);
+ return;
}
+ Optional account = results.accounts.stream()
+ .filter(a -> uri.equals(Uri.parse(a.url))).findAny();
+ if (account.isPresent()) {
+ args.putParcelable("profileAccount", Parcels.wrap(account.get()));
+ go.accept(ProfileFragment.class, args);
+ return;
+ }
+ if (launchBrowser) launchWebBrowser(context, url);
+ Toast.makeText(context, R.string.sk_resource_not_found, Toast.LENGTH_SHORT).show();
+ go.accept(null, null);
}
@Override
public void onError(ErrorResponse error) {
error.showToast(context);
if (launchBrowser) launchWebBrowser(context, url);
+ go.accept(null, null);
}
})
- .wrapProgress((Activity)context, R.string.loading, true,
+ .wrapProgress((Activity) context, R.string.loading, true,
d -> transformDialogForLookup(context, accountID, url, d))
.exec(accountID);
return;
}
}
- launchWebBrowser(context, url);
+ if (launchBrowser) launchWebBrowser(context, url);
+ go.accept(null, null);
}
public static void copyText(View v, String text) {
Context context = v.getContext();
context.getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText(null, text));
- if(Build.VERSION.SDK_INT props=Class.forName("android.os.SystemProperties");
- Method get=props.getMethod("get", String.class);
- return (String)get.invoke(null, key);
- }catch(Exception ignore){}
+ private static String getSystemProperty(String key) {
+ try {
+ Class> props = Class.forName("android.os.SystemProperties");
+ Method get = props.getMethod("get", String.class);
+ return (String) get.invoke(null, key);
+ } catch (Exception ignore) {
+ }
return null;
}
- public static boolean isMIUI(){
+ public static boolean isMIUI() {
return !TextUtils.isEmpty(getSystemProperty("ro.miui.ui.version.code"));
}
@@ -1335,17 +1367,25 @@ public class UiUtils {
return !TextUtils.isEmpty(getSystemProperty("ro.build.version.emui"));
}
+ public static int alphaBlendColors(int color1, int color2, float alpha) {
+ float alpha0 = 1f - alpha;
+ int r = Math.round(((color1 >> 16) & 0xFF) * alpha0 + ((color2 >> 16) & 0xFF) * alpha);
+ int g = Math.round(((color1 >> 8) & 0xFF) * alpha0 + ((color2 >> 8) & 0xFF) * alpha);
+ int b = Math.round((color1 & 0xFF) * alpha0 + (color2 & 0xFF) * alpha);
+ return 0xFF000000 | (r << 16) | (g << 8) | b;
+ }
+
public static boolean pickAccountForCompose(Activity activity, String accountID, String prefilledText) {
Bundle args = new Bundle();
if (prefilledText != null) args.putString("prefilledText", prefilledText);
return pickAccountForCompose(activity, accountID, args);
}
- public static boolean pickAccountForCompose(Activity activity, String accountID){
+ public static boolean pickAccountForCompose(Activity activity, String accountID) {
return pickAccountForCompose(activity, accountID, (String) null);
}
- public static boolean pickAccountForCompose(Activity activity, String accountID, Bundle args){
+ public static boolean pickAccountForCompose(Activity activity, String accountID, Bundle args) {
if (AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1) {
UiUtils.pickAccount(activity, accountID, 0, 0, session -> {
args.putString("account", session.getID());
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/CheckableRelativeLayout.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/CheckableRelativeLayout.java
new file mode 100644
index 000000000..b5f4d5b36
--- /dev/null
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/CheckableRelativeLayout.java
@@ -0,0 +1,62 @@
+package org.joinmastodon.android.ui.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.Checkable;
+import android.widget.RelativeLayout;
+
+public class CheckableRelativeLayout extends RelativeLayout implements Checkable{
+ private boolean checked, checkable = true;
+ private static final int[] CHECKED_STATE_SET = {
+ android.R.attr.state_checked
+ };
+
+ public CheckableRelativeLayout(Context context){
+ this(context, null);
+ }
+
+ public CheckableRelativeLayout(Context context, AttributeSet attrs){
+ this(context, attrs, 0);
+ }
+
+ public CheckableRelativeLayout(Context context, AttributeSet attrs, int defStyle){
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void setChecked(boolean checked){
+ this.checked=checked;
+ refreshDrawableState();
+ }
+
+ public void setCheckable(boolean checkable) {
+ this.checkable = checkable;
+ }
+
+ @Override
+ public boolean isChecked(){
+ return checked;
+ }
+
+ @Override
+ public void toggle(){
+ setChecked(!checked);
+ }
+
+ @Override
+ protected int[] onCreateDrawableState(int extraSpace) {
+ final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+ if (isChecked()) {
+ mergeDrawableStates(drawableState, CHECKED_STATE_SET);
+ }
+ return drawableState;
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info){
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setCheckable(checkable);
+ info.setChecked(checked);
+ }
+}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/utils/ProvidesAssistContent.java b/mastodon/src/main/java/org/joinmastodon/android/utils/ProvidesAssistContent.java
new file mode 100644
index 000000000..ab82b44e8
--- /dev/null
+++ b/mastodon/src/main/java/org/joinmastodon/android/utils/ProvidesAssistContent.java
@@ -0,0 +1,32 @@
+package org.joinmastodon.android.utils;
+
+import android.app.Fragment;
+import android.app.assist.AssistContent;
+import android.net.Uri;
+
+import org.joinmastodon.android.fragments.HasAccountID;
+
+public interface ProvidesAssistContent {
+ void onProvideAssistContent(AssistContent assistContent);
+
+ default boolean callFragmentToProvideAssistContent(Fragment fragment, AssistContent assistContent) {
+ if (fragment instanceof ProvidesAssistContent assistiveFragment) {
+ assistiveFragment.onProvideAssistContent(assistContent);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ interface ProvidesWebUri extends ProvidesAssistContent, HasAccountID {
+ Uri getWebUri(Uri.Builder base);
+
+ default Uri.Builder getUriBuilder() {
+ return getSession().getInstanceUri().buildUpon();
+ }
+
+ default void onProvideAssistContent(AssistContent assistContent) {
+ assistContent.setWebUri(getWebUri(getUriBuilder()));
+ }
+ }
+}
diff --git a/mastodon/src/main/res/drawable/ic_fluent_share_28_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_share_28_regular.xml
new file mode 100644
index 000000000..6fa0d5917
--- /dev/null
+++ b/mastodon/src/main/res/drawable/ic_fluent_share_28_regular.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/mastodon/src/main/res/layout/item_account_list.xml b/mastodon/src/main/res/layout/item_account_list.xml
index 0c929bf7d..8d2da9e2f 100644
--- a/mastodon/src/main/res/layout/item_account_list.xml
+++ b/mastodon/src/main/res/layout/item_account_list.xml
@@ -29,10 +29,10 @@
-
+
+
+
+
+
+
+
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_toEndOf="@id/avatar"
+ android:layout_toStartOf="@id/radiobtn"
+ android:layout_centerInParent="true"
+ android:orientation="vertical">
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/mastodon/src/main/res/layout/item_external_share_heading.xml b/mastodon/src/main/res/layout/item_external_share_heading.xml
new file mode 100644
index 000000000..3086dee74
--- /dev/null
+++ b/mastodon/src/main/res/layout/item_external_share_heading.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mastodon/src/main/res/layout/item_text_with_icon.xml b/mastodon/src/main/res/layout/item_text_with_icon.xml
new file mode 100644
index 000000000..01c27abab
--- /dev/null
+++ b/mastodon/src/main/res/layout/item_text_with_icon.xml
@@ -0,0 +1,15 @@
+
+
\ No newline at end of file
diff --git a/mastodon/src/main/res/values-ar-rDZ/strings.xml b/mastodon/src/main/res/values-ar-rDZ/strings.xml
index 3ef38fd35..61d977a26 100644
--- a/mastodon/src/main/res/values-ar-rDZ/strings.xml
+++ b/mastodon/src/main/res/values-ar-rDZ/strings.xml
@@ -465,21 +465,7 @@
على الرغم من أن تطبيق ماستدون لا يجمع أي بيانات، فإن الخادم الذي قمت بالتسجيل من خلاله قد تكون له سياسة مختلفة. خذ دقيقة للمراجعة والموافقة على سياسة خصوصية التطبيق ماستدون وسياسة الخصوصية للخادم الخاص بك.
أنا مُوافِق
هذه القائمة فارغة
- هل أنت متأكد أنك تريد حذف وإعادة صياغة هذا المنشور؟
- غير مدرج
- القوائم
- طلبات المتابعة
هذا الخادم لا يقبل تسجيلات جديدة.
- مدبّس
- حذف وإعادة الصياغة
- حذف وإعادة صياغة الرسالة
- تدبيس على الصفحة الشخصية
- تدبيس الرسالة على الصفحة الشخصية
- إظهار الخيط الفديرالي
- المساهمة في Megalodon
- قبول طلب المتابعة
- رفض طلب المتابعة
- قوائم بها %s
تم النسخ إلى الحافظة
إضافة إلى الفواصل المرجعية
إزالة من الفواصل المرجعية
diff --git a/mastodon/src/main/res/values-ar-rDZ/strings_sk.xml b/mastodon/src/main/res/values-ar-rDZ/strings_sk.xml
index a6b3daec9..3668745aa 100644
--- a/mastodon/src/main/res/values-ar-rDZ/strings_sk.xml
+++ b/mastodon/src/main/res/values-ar-rDZ/strings_sk.xml
@@ -1,2 +1,17 @@
-
\ No newline at end of file
+
+ هل أنت متأكد أنك تريد حذف وإعادة صياغة هذا المنشور؟
+ غير مدرج
+ القوائم
+ طلبات المتابعة
+ مدبّس
+ حذف وإعادة الصياغة
+ حذف وإعادة صياغة الرسالة
+ تدبيس على الصفحة الشخصية
+ تدبيس الرسالة على الصفحة الشخصية
+ إظهار الخيط الفديرالي
+ المساهمة في Megalodon
+ قبول طلب المتابعة
+ رفض طلب المتابعة
+ قوائم بها %s
+
\ No newline at end of file
diff --git a/mastodon/src/main/res/values-bn-rBD/strings.xml b/mastodon/src/main/res/values-bn-rBD/strings.xml
index cb0c6c171..b537a7b63 100644
--- a/mastodon/src/main/res/values-bn-rBD/strings.xml
+++ b/mastodon/src/main/res/values-bn-rBD/strings.xml
@@ -20,11 +20,20 @@
শেয়ার করুন
সেটিংস
বাতিল করুন
+
+ - জন ফলোয়ার
+ - জন ফলোয়ারস
+
- পোস্ট
- পোস্টগুলো
পোস্টগুলো
+ মিডিয়া
+ ফলো করুন
+ ফলো করছেন
+ প্রোফাইল সংশোধন করুন
+ %s -কে পিং করুন
%s -কে শেয়ার করুন
%s -কে মিউট করুন
%s -কে আনমিউট করুন
@@ -53,6 +62,22 @@
- %d দিন
- %d দিন
+
+ - %d সেকেন্ড বাকি
+ - %d সেকেন্ড বাকি
+
+
+ - %d মিনিট বাকি
+ - %d মিনিট বাকি
+
+
+ - %d ঘণ্টা বাকি
+ - %d ঘণ্টা বাকি
+
+
+ - %d দিন বাকি
+ - %d দিন বাকি
+
বন্ধ
অ্যাকাউন্টটি মিউট করুন
মিউট করুন
@@ -88,6 +113,18 @@
- %d জন ব্যক্তি বলছেন
- %d jon ব্যক্তিরা বলছেন
+ রিপোর্ট পাঠানো হচ্ছে…
+ রিপোর্ট করার জন্য আপনাকে ধন্যবাদ, আমরা এটি শীঘ্রই দেখব.
+ আমরা যতক্ষণে আপনার রিপোর্ট পুনর্বিবেচনা করছি, আপনি %s এর বিরুদ্ধে ব্যবস্থা নিতে পারেন.
+ ফিরে যান
+ সার্ভারের নাম বা লিঙ্ক
+ সার্ভারের নিয়মাবলী
+ অ্যাকাউন্ট তৈরি করুন
+ নাম
+ ইউজারনেম
+ ই-মেইল
+ পাসওয়ার্ড
+ পাসওয়ার্ড নিশ্চিত করুন
@@ -96,4 +133,8 @@
+ Mastodon - এ আপনাকে স্বাগত জানাই
+ Mastodon হল একটি বিকেন্দ্রীভূত সামাজিক নেটওয়ার্ক, যার মানে কোনো একক কোম্পানি এটিকে নিয়ন্ত্রণ করে না। এটি অনেকগুলি স্বাধীনভাবে চালিত সার্ভারের সমন্বয়ে গঠিত, যেখানে সব সার্ভারগুলি একসাথে সংযুক্ত৷
+ সার্ভার কি?
+
diff --git a/mastodon/src/main/res/values-de-rDE/strings.xml b/mastodon/src/main/res/values-de-rDE/strings.xml
index a49d8d3dc..1c1032192 100644
--- a/mastodon/src/main/res/values-de-rDE/strings.xml
+++ b/mastodon/src/main/res/values-de-rDE/strings.xml
@@ -438,8 +438,11 @@
Anzeigen
Ausblenden
%s beitreten
+ Wähle einen anderen Server
oder
Mehr erfahren
Willkommen auf Mastodon
+ Mastodon ist ein dezentrales, soziales Netzwerk. Das bedeutet, dass es nicht von einem einzigen Unternehmen kontrolliert wird. Das Netzwerk besteht aus unabhängig voneinander betriebenen Servern, die miteinander verbunden sind.
Was sind Server?
+
diff --git a/mastodon/src/main/res/values-es-rES/strings_sk.xml b/mastodon/src/main/res/values-es-rES/strings_sk.xml
index f50936046..c8dc06230 100644
--- a/mastodon/src/main/res/values-es-rES/strings_sk.xml
+++ b/mastodon/src/main/res/values-es-rES/strings_sk.xml
@@ -275,16 +275,15 @@
Confirmar antes de volver a publicar
reaccionó con %s
reaccionó
- No especificado
- Texto plano
+ Tipo del contenido
+ Sin especificar
+ Texto sin formato
HTML
Markdown
BBCode
MFM
- Tipo de contenido predeterminado
- Esto te permite tener un tipo de contenido preseleccionado cuando creas una nueva publicación, sobreescribiendo el valor en \"Preferencias de publicación\".
- Tipo de contenido
- Activar formato de publicación
- Permite configurar un tipo de contenido como Markdown cuando creas una publicación. Ten en cuenta que no todas las instancias lo permiten.
- Botón \"Ver nuevas publicaciones\"
+ Habilitar el formato de los mensajes
+ Contenido por defecto
+ Permite establecer un tipo de contenido como Markdown al crear una entrada. Ten en cuenta que no todas las instancias lo admiten.
+ Permite preseleccionar un tipo de contenido al crear nuevas entradas, anulando el valor establecido en \"Preferencias de publicación\".
\ No newline at end of file
diff --git a/mastodon/src/main/res/values-fr-rFR/strings_sk.xml b/mastodon/src/main/res/values-fr-rFR/strings_sk.xml
index 84a7d3d32..01e013d7a 100644
--- a/mastodon/src/main/res/values-fr-rFR/strings_sk.xml
+++ b/mastodon/src/main/res/values-fr-rFR/strings_sk.xml
@@ -276,4 +276,21 @@
Confirmer avant de booster
a réagi
a réagi avec %s
+ Type de contenu
+ Texte brut
+ HTML
+ Markdown
+ BBCode
+ MFM
+ Activer la mise en forme du message
+ Cela vous permet de présélectionner un type de contenu lors de la création de nouveaux messages, en remplaçant la valeur définie dans \"Préférences de publication\".
+ Non spécifié
+ Permet de définir un type de contenu comme Markdown lors de la création d\'un message. Gardez à l\'esprit que toutes les instances ne le prennent pas en charge.
+ Type de contenu par défaut
+ Ouvrir dans l\'application
+ Partager avec le compte
+ Partager ou ouvrir avec le compte
+ Ce sont les publications les plus récentes des personnes présentes dans la bulle de votre serveur Akkoma.
+ Bulle
+ Informations sur l\'instance temporairement indisponibles
\ No newline at end of file
diff --git a/mastodon/src/main/res/values-gl-rES/strings_sk.xml b/mastodon/src/main/res/values-gl-rES/strings_sk.xml
index ee92fcbe0..181dfc4b5 100644
--- a/mastodon/src/main/res/values-gl-rES/strings_sk.xml
+++ b/mastodon/src/main/res/values-gl-rES/strings_sk.xml
@@ -90,7 +90,7 @@
Buscando no Fediverso
Impulsar con visibilidade
Publicar acerca disto
- Desfacer o impulso
+ Desfacer impulso
Copiar ligazón á publicación
Buscando en %s
Abrir con outra conta
@@ -275,4 +275,21 @@
Confirma antes de impulsar
Redactado con %s
redactado
+ Non especificado
+ Texto plano
+ HTML
+ BBCode
+ MFM
+ Activar o formato de publicacións
+ Tipo de contido por defecto
+ Isto permítelle ter un tipo de contido preseleccionado á hora de crear novas publicacións, sobrescribindo o valor establecido en \"Publicar preferencias\".
+ Tipo de contido
+ Markdown
+ Permite configurar un tipo de contido como Markdown ao crear unha publicación. Teña en conta que non tódalas instancias soportan isto.
+ Estas son as publicacións máis recentes da xente na burbulla do seu servidor Akkoma.
+ Burbulla
+ Información da instancia temporalmente non dispoñible
+ Abrir na aplicación
+ Compartir coa conta
+ Compartir ou abrir coa conta
\ No newline at end of file
diff --git a/mastodon/src/main/res/values-hy-rAM/strings.xml b/mastodon/src/main/res/values-hy-rAM/strings.xml
index 8b194cf26..333fc34e8 100644
--- a/mastodon/src/main/res/values-hy-rAM/strings.xml
+++ b/mastodon/src/main/res/values-hy-rAM/strings.xml
@@ -101,6 +101,8 @@
Արգելափակել տիրույթը
Հաստատեք %s-ի արգելափակումը
Արգելափակել
+ Արգելաբացել
+ Արգելափակված
Քվեարկել
Սեղմեք տեսնելու համար
Ջնջել
@@ -118,7 +120,9 @@
Նշումներ
Զեկուցել %s-ի մասին
Ինձ դուր չի գալիս
+ Դուք սա չեք ուզում տեսնել
Սպամ է
+ Վնասակար հղումներ, կեղծում կամ կրկնվող պատասխաններ
Խախտում է սերվերի կանոնները
Գիտեք, որ այն խախտում է կանոնները
Այլ բան է
@@ -157,6 +161,10 @@
Ավելացնել պատկերի նկարագրություն
Խմբագրել նկարը
Պահպանել
+ օր․՝ շունը նեղ աչքերով կասկածելի նայում է տեսախցիկին
+ Հրապարակային
+ Միայն հետեւողներին
+ Միայն նշածս մարդկանց
Բոլորը
Մարդիկ
Վերջին որոնումներ
@@ -226,6 +234,7 @@
Չեղարկել
Նոր գրառումներ
Հետեւում է ձեզ
+ Ընթացիկ հաշիվ
- %,d հետեւորդ
@@ -241,13 +250,32 @@
%1$s %2$s-ի միջոցով
նոր
+ Տարածումներ
Հավանումներ
Խմբագրել պատմությունը
+ Վերջին խմբագրում՝ %s
հենց նոր
+
+ - %d վայրկյան առաջ
+ - %d վայրկյան առաջ
+
+
+ - %d րոպե առաջ
+ - %d րոպե առաջ
+
+ Տեքստը փոփոխվել է
+ Հարցումն ավելացել է
+ Հարցումը խմբագրվել է
Հարցումը հեռացվել է
Մեդիան ավելացվել է
Մեդիան հեռացվել է
Նշվել է որպես դյուրազգաց
+ %d բայթ
+ %.2f ԿԲ
+ %.2f ՄԲ
+ %.2f ԳԲ
+ %1$s՝ %2$s-ից
+ մնացել է %s
%s տարբերակը պատրաստ է ներբեռնման։
@@ -255,6 +283,7 @@
Ներբեռնել (%s)
Տեղադրել
+ Ձեր գաղտնիությունը
Համաձայն եմ
Ցանկը դատարկ է
Սպասարկիչը գրանցումներ չի ընդունում։
@@ -274,9 +303,25 @@
Չի ընդունում նոր անդամներ
Գաղտնաբառը չի համապատասխանում
Ընտրել իմ համար
+ Կարճ ասած՝ մենք ոչինչ չենք հավաքում։
+ Կենսագրություն
+ %1$s-ը %2$s-ից գրանցումներ չի ընդունում։ Փորձեք ուրիշը կամ <a>ընտրեք ուրիշ սերվեր</a>։
+ Օգտանունը զբաղված է։
+ Ցույց տալ
+ Թաքցնել
+ Պահպանել
+ Հոսք
+ Դիտել բոլորը
+ Հաշիվներ
+ Հաստատված հղում
+ Ցույց տալ
+ Թաքցնել
+ Միանալ %s-ին
+ Ընտրել ուրիշ սերվեր
+ կամ
Իմանալ ավելին
Բարի գալուստ Մաստոդոն
Մաստոդոնը ապակենտրոնացված սոցցանց է, այսինքն՝ այն չի պատկանում մի ընկերության։ Այն բաղկացած է բազմաթիվ անկախ և կապակցված սերվերներից։
diff --git a/mastodon/src/main/res/values-in-rID/strings_sk.xml b/mastodon/src/main/res/values-in-rID/strings_sk.xml
index fee306969..e7caf9de2 100644
--- a/mastodon/src/main/res/values-in-rID/strings_sk.xml
+++ b/mastodon/src/main/res/values-in-rID/strings_sk.xml
@@ -276,4 +276,21 @@
Konfirmasi sebelum membagikan ulang
bereaksi dengan %s
bereaksi
+ Teks biasa
+ HTML
+ Markdown
+ BBCode
+ MFM
+ Aktifkan pemformatan kiriman
+ Jenis konten bawaan
+ Ini memungkinkan Anda untuk menerapkan jenis konten yang sudah ditentukan saat membuat kiriman baru, menimpa nilai yang ditetapkan dalam “Preferensi kiriman”.
+ Jenis konten
+ Tidak ditentukan
+ Memperbolehkan menetapkan jenis konten seperti Markdown ketika membuat kiriman. Perlu diingat bahwa tidak semua server mendukung ini.
+ Buka dalam aplikasi
+ Bagikan dengan akun
+ Ini adalah kiriman yamg paling terkini oleh orang-orang dalam gelembung server Akkoma Anda.
+ Gelembung
+ Info server sementara tidak tersedia
+ Bagikan atau buka dengan akun
\ No newline at end of file
diff --git a/mastodon/src/main/res/values-nl-rNL/strings.xml b/mastodon/src/main/res/values-nl-rNL/strings.xml
index a98a2dfd6..3bd362e49 100644
--- a/mastodon/src/main/res/values-nl-rNL/strings.xml
+++ b/mastodon/src/main/res/values-nl-rNL/strings.xml
@@ -8,14 +8,14 @@
Fout
%s lijkt geen Mastodonserver te zijn.
OK
- Verificatie aan het voorbereiden…
+ Verificatie voorbereiden…
Verificatie afronden…
%s boostte
Als reactie op %s
Meldingen
volgt jou
- heeft je een volgverzoek gestuurd
- markeerde als favoriet
+ wil jou graag volgen
+ markeerde bericht als favoriet
boostte jouw bericht
poll is beëindigd
%ds
@@ -47,20 +47,20 @@
Volgen
Volgend
Profiel bewerken
- Vermelden
+ %s vermelden
%s delen
- Negeren
- Niet langer negeren
- Blokkeren
- Deblokkeren
+ %s negeren
+ %s niet langer negeren
+ %s blokkeren
+ %s deblokkeren
%s rapporteren
- Blokkeren
- Deblokkeren
+ %s blokkeren
+ %s deblokkeren
- %,d bericht
- %,d berichten
- Geregistreerd op
+ Geregistreerd
Klaar
Aan het laden…
Label
@@ -98,23 +98,23 @@
- %d dagen resterend
- - %,d persoon
- - %,d mensen
+ - %,d stem
+ - %,d stemmen
Gesloten
Account negeren
- Bevestig om %s te negeren
+ Het negeren van %s bevestigen
Negeren
Account niet langer negeren
- Bevestig om %s niet langer te negeren
+ Het niet langer negeren van %s bevestigen
Niet langer negeren
Account blokkeren
Domein blokkeren
- Bevestig om %s te blokkeren
+ Het blokkeren van %s bevestigen
Blokkeren
Account deblokkeren
Domein deblokkeren
- Bevestig om %s te deblokkeren
+ Het deblokkeren van %s bevestigen
Deblokkeren
Genegeerd
Geblokkeerd
@@ -175,7 +175,7 @@
Kies een server gebaseerd op je interesses, regio, of voor algemene doeleinden. Je kunt nog steeds met iedereen in contact komen, ongeacht de server.
Servernaam of URL
Serverregels
- Door verder te gaan, ga je akkoord met het volgen van de regels ingesteld door de %s-moderators.
+ Door verder te gaan, ga je akkoord met het volgen van de regels ingesteld door de %s-moderatoren.
Account registreren
bewerken
Naam
@@ -193,14 +193,14 @@
Games
Algemeen
Journalistiek
- LGBT
+ LHBTQIA+
Muziek
Regionaal
Tech
Controleer je Postvak In
Klik op de link die we je hebben gestuurd om %s te verifiëren. We wachten op je.
- Geen link gekregen?
+ Geen verificatielink ontvangen?
Opnieuw verzenden
E-mail-app openen
Bevestigingsmail verzonden
@@ -211,7 +211,7 @@
Afbeelding bewerken
Opslaan
Alt-tekst toevoegen
- Alt-tekst beschrijft uw foto\'s voor mensen met weinig of geen zicht. Probeer alleen genoeg details toe te voegen om de context te begrijpen.
+ Alt-tekst beschrijft jouw foto\'s voor blinde of slechtziende mensen. Probeer alleen genoeg details toe te voegen om de context te begrijpen.
bijv.: ‘een hond die met versmalde ogen verdacht rondkijkt naar de camera’.
Openbaar
Alleen volgers
@@ -240,17 +240,17 @@
Donker
Echt zwart gebruiken
Gedrag
- Geanimeerde avatars en emoji\'s afspelen
+ Geanimeerde profielfoto\'s en emoji\'s afspelen
In-appbrowser gebruiken
Meldingen
Melding tonen wanneer
- iedereen
+ iemand
een volger
iemand die ik volg
niemand
Mijn bericht als favoriet markeert
Mij volgt
- Boost mijn bericht
+ Mijn bericht boost
Mij vermeldt
De saaie zone
Accountinstellingen
@@ -272,7 +272,7 @@
Nieuw bericht
Reageren
Boosten
- Toevoegen aan favorieten
+ Als favoriet markeren
Delen
Media zonder beschrijving
Media toevoegen
@@ -286,7 +286,7 @@
%s ontvolgd
Je volgt %s nu
Je volgverzoek is aan %s verstuurd
- Openen in browser
+ In browser openen
Boosts van %s verbergen
Boosts van %s tonen
Waarom wil je je hier registreren?
@@ -309,7 +309,7 @@
Dit zijn nieuwsartikelen die populair zijn op jouw Mastodon-server.
Dit zijn de meest recente berichten van mensen die ook op jouw Mastodon-server zitten.
Sluiten
- Nieuwe berichten bekijken
+ Nieuwe berichten
Resterende berichten laden
Terugvolgen
In afwachting
@@ -383,7 +383,7 @@
Downloaden (%s)
Installeren
Jouw privacy
- Hoewel de Mastodon-app geen gegevens verzamelt, kan de server waar je je aanmeldt een ander beleid hebben.\n\nAls je het niet eens bent met het beleid voor %s, kun je teruggaan en een andere server kiezen.
+ Hoewel de Mastodon-app geen gegevens verzamelt, kan de server waar je je aanmeldt een ander beleid hebben.\n\nAls je niet akkoord gaat met het beleid voor %s, kun je teruggaan en een andere server kiezen.
Ik ga akkoord
Deze lijst is leeg
Deze server accepteert geen nieuwe registraties.
@@ -416,10 +416,10 @@
Je kunt tot vier profielvelden toevoegen voor alles wat je wilt. Locatie, links, voornaamwoorden – de mogelijkheden zijn eindeloos.
Populair op Mastodon
Volg iedereen
- Oneens
+ Ik ga niet akkoord
TL;DR: We verzamelen of verwerken niets.
- Oneens met %s
+ Niet akkoord met %s
Bio
Gevolgde gebruikers…
@@ -428,20 +428,21 @@
Deze gebruikersnaam wordt al gebruikt.
Alsnog tonen
Opnieuw verbergen
- Selecteer een of meer
+ Maak een of meerdere keuzes
Wijzigingen opslaan
Aanbevolen
Tijdlijn
Alles bekijken
Accounts
- Geverifieerde koppeling
+ Geverifieerde link
Tonen
Verbergen
- Deelnemen aan %s
+ Registreren op %s
+ Kies een andere server
of
Meer informatie
Welkom bij Mastodon
- Mastodon is een gedecentraliseerd sociaal netwerk, wat betekent dat geen enkel bedrijf het controleert. Het bestaat uit veel onafhankelijk opererende servers, allemaal verbonden met elkaar.
+ Mastodon is een gedecentraliseerd sociaal netwerk, wat betekent dat geen enkel bedrijf het controleert. Het bestaat uit veel onafhankelijk opererende servers, allemaal met elkaar verbonden.
Wat zijn servers?
-
+
diff --git a/mastodon/src/main/res/values-nl-rNL/strings_sk.xml b/mastodon/src/main/res/values-nl-rNL/strings_sk.xml
index e0f16cfa4..5a3c2beac 100644
--- a/mastodon/src/main/res/values-nl-rNL/strings_sk.xml
+++ b/mastodon/src/main/res/values-nl-rNL/strings_sk.xml
@@ -251,8 +251,7 @@
Verberg interactie knoppen
Volgen met ander account
Gevolgd met %s
- Reactie zichtbaarheid
+ Quoting %s
+ Zichtbaarheid reactie
Alle reacties
- Changelog
- Knop \"Toon nieuwe berichten\"
\ No newline at end of file
diff --git a/mastodon/src/main/res/values-pt-rPT/strings_sk.xml b/mastodon/src/main/res/values-pt-rPT/strings_sk.xml
index d515bbe51..887c46050 100644
--- a/mastodon/src/main/res/values-pt-rPT/strings_sk.xml
+++ b/mastodon/src/main/res/values-pt-rPT/strings_sk.xml
@@ -2,80 +2,47 @@
Megalodon
Fixado
- Não especifícado
- Texto simples
- HTML
- Markdown
- BBCode
- MFM
- Tipo de conteúdo predefinido
- Queres fixar esta publicação no teu perfil\?
- Desfixar publicação do perfil
- Carregar novas publicações automaticamente
- Ligar notificações da publicação para %s
- Desativar deslocação de texto nas barras de título
- Permitir escolha múltipla
- %1$s (%2$s)
- De certeza que queres apagar os teus idiomas usados recentemente\?
- Configurações de segurança
- Regras
- De certeza que queres apagar esta notificação\?
- Personalizar o texto do botão de publicar
- Partilhar com visibilidade
- De certeza que queres apagar este rascunho\?
- Hora agendada é demasiado cedo
- Publicação agendada
- Sobre a instância
- Remover %s como seguidor bloqueando e desbloqueando imediatamente\?
- Se ativares as notificações de publicação para algumas pessoas as suas publicações vão aparecer aqui.
- Idioma
- Abertura
- Pasta
- Editar linhas do tempo
- Queres guardar as alterações a este rascunho ou publicar agora\?
- Esconder automaticamente o botão Escrever
- Tipo de conteúdo
- Permitir formatação da publicação
- Permitir configurar um tipo de conteúdo como Markdown ao criar uma publicação. Lembra-te que nem todas as instâncias suportam isto.
- Isto permite ter um tipo de conteúdo pré-selecionado ao criar publicações novas, substituindo o valor configurado nas \"Preferências de publicação\".
- Apagar e reescrever
- Apagar e reescrever publicação
- De certeza que querem apagar e reescrever esta publicação\?
- Fixar no perfil
- Fixar publicação no perfil
- A fixar publicação…
- Desfixar do perfil
- De certeza que queres desfixar esta publicação\?
+ Filtrado: %s
+ Expandir
+ Esconder
+ Esconder publicações muito grandes
+ Reparar anexos\?
+ Traduzir
+ Mostrar original
+ Idioma: %s
+ Idiomas disponíveis
A desfixar publicação…
Descrição da imagem
Não listado
Mostrar respostas
- Citar %s
- Visibilidade de resposta
- Todas as respostas
- Responder aos meus seguidores
- Respostas a mim
Mostrar partilhas
- Mostrar contagem de interações
- Megalodon v%1$s (%2$d)
- Marcar conteúdo como sensível
- Desligar notificações para publicação para %s
+ Carregar publicações novas automaticamente
+ Desfixar do perfil
+ Apagar e reescrever
+ Apagar e reescrever publicação
+ De certeza que quer apagar e reescrever a publicação\?
+ Fixar no perfil
+ Fixar publicação no perfil
+ Queres fixar esta publicação ao teu perfil\?
+ A fixar publicação…
Federação
- Estas são as publicações mais recentes das pessoas na tua federação.
- Megalodon %s está pronto a descarregar.
- Megalodon %s descarregado e pronto a instalar.
- A verificar atualizações
- Sem atualizações disponíveis
- Listas
+ Megalodon %s pronto a descarregar.
Pedidos para seguir
Aceitar pedido para seguir
- Rejeitar pedido para seguir
- Listas com %s
- Mostrar sempre avisos de conteúdo
+ exemplo.social
+ Desligar deslocamento de texto nas barras de título
Contribuir para o Megalodon
- Mostrar linha do tempo federada
- Publicações
- Notificações da publicação
+ Guardar com outra conta
+ Guardado como %s
+ Já guardado
+ Adicionar aos favoritos com outra conta
+ Configurar perfil
+ Configurar filtros
+ Configurações de segurança
+ Regras
+ Sobre a aplicação
+ Doar
+ Desligar deslocação entre separadores
Paleta de cores
Sistema
Rosa
@@ -85,107 +52,52 @@
Castanho
Vermelho
Amarelo
- Traduzir
- Mostrar original
- Traduzido com %s
- Idioma: %s
- Idiomas disponíveis
- Apagar idiomas usados recentemente
- Bem-vindo/a!
- Saudações do Tubarão! Para começar introduz o domínio da tua instância nativa abaixo.
- exemplo.social
- Desativar deslizar entre separadores
- Configurar perfil
- Preferências de notificação
- Configurar filtros
- Sobre a aplicação
- Doar
- Apagar notificação
- Apagar notificação
- Apagar todas as notificações
- Apagar tudo
- De certeza que queres apagar todas as notificações\?
- Ativar apagar notificações
+ De certeza que quer apagar esta notificação\?
+ Ligar apagar notificações
Texto do botão de publicar
- Traduzir apenas publicações abertas
- %s suporta tradução!
- %s não parece suportar tradução.
+ Apagar tudo
+ De certeza que quer apagar todas as notificações\?
A procurar no Fediverso
- A procurar em %s
- Escrever sobre isto
- Hashtags que segues
- Copiar ligação para a publicação
+ Partilhar com visibilidade
+ Publicar sobre isto
Abrir com outra conta
- O recurso não foi encontrado
- Guardar com outra conta
- Guardado como %s
- Já guardado
- Adicionar aos favoritos com outra conta
- Adicionado aos favoritos como %s
- Já adicionado aos favoritos
- Responder com outra conta
- Ícone uniforme para todas as notificações
- Encaminhar para %s
Publicações não enviadas
Rascunho
Horário
Apagar rascunho
Apagar publicação agendada
- De certeza que queres apagar esta publicação agendada\?
- Rascunho ou agendar
- A publicação será guardada como rascunho.
- Agendado para
+ De certeza que quer apagar esta publicação agendada\?
+ Rascunho ou horário
+ Publicação guardada como rascunho.
Rascunho guardado
- Publicação agendada
- A publicação tem de ser agendada com pelo menos 10 minutos de avanço.
- Guardar rascunho\?
+ Horário agendado é demasiado cedo
+ Agendada para
Guardar alterações\?
Marcar como rascunho
- Agendar ou rascunho
+ Agendar publicação
Não agendar
- Não criar rascunho
- Reduzir movimento nas animações
- Anúncios
+ Sem rascunho
+ Não especificado
+ Texto simples
+ HTML
+ Marcador
+ MFM
+ Permitir formatação de publicação
+ Tipo de conteúdo predefinido
+ Permite pré-selecionar um tipo de conteúdo quando se criam novas publicações, substituindo o valor definido em \"Preferências de publicação\".
Marcar como lido
+ Sobre a instância
Mostrar apenas uma notificação
Criar
Criar lista
- Nome da lista
- Mostrar respostas a
- Membros da lista
- utilizadores seguidos
- ninguém
- Apagar lista
- De certeza que queres apagar a lista \"%s\"\?
- Editar lista
- As tuas listas
- Página principal
- Local
- Federação
- Escrever para começar a procurar
- Remover como seguidor
+ Remover %s como seguidor bloqueando e logo a seguir desbloqueando\?
Remover
Seguidor removido com sucesso
- Registo de alterações
- Sem descrição de imagem
- Pelo menos um anexo não contém uma descrição.
+ Registo de mudanças
+ Pelo menos um anexo não contém descrição.
Publicar assim mesmo
- Desativar o lembrete para adicionar descrição de imagem
+ Desligar lembrete para adicionar texto alternativo
Linhas do tempo
- Publicações
- Adicionar
- Linha do tempo
- Lista
- Hashtag
- Fixar linha do tempo
- Desfixar linha do tempo
- Fixar à página principal
- Desfixar da página principal
- Remover
- Ícone
- Coração
- Estrela
- Cidade
Gato
Cão
Coelho
@@ -193,6 +105,7 @@
Balão
Imagem
Robô
+ Idioma
Localização
Megafone
Microfone
@@ -201,6 +114,125 @@
Café
Riso
Notícias
+ Sem resultados
+ Guardar rascunho\?
+ Sem texto alternativo disponível
+ Indicador de textos alternativos
+ Indicador para textos alternativos ausentes
+ Ligar versões beta
+ apenas-mencionados
+ .
+ Recursos da instância
+ O servidor só suporta publicações locais
+ Glitch em modo local
+ Liga isto se a tua instância nativa corre no Glitch. Não é necessário para Hometown ou Akkoma.
+ inscrito
+ reportado
+ Inscrições
+ Resultados do inquérito
+ Seguir com outra conta
+ Tem a certeza que quer desfixar esta publicação\?
+ Desligar notificações de publicação para %s
+ Estas são as publicações mais recentes das pessoas na tua federação.
+ Listas
+ Rejeitar pedido para seguir
+ Listas com %s
+ Mostrar sempre avisos de conteúdo
+ Permitir escolhas múltiplas
+ Traduzido por %s
+ Apagar idiomas usados recentemente
+ De certeza que quer apagar os seus idiomas usados recentemente\?
+ Bem-vindo/a!
+ Saudações do tubarão! Para começar, introduz o nome do domínio da tua instância nativa abaixo.
+ Preferências de publicação
+ Apagar notificação
+ Apagar notificação
+ Apagar todas as notificações
+ Personalizar o texto do botão de publicar
+ Traduzir apenas publicações abertas
+ %s suporta tradução!
+ %s não parece suportar tradução.
+ A procurar em %s
+ Desfazer partilha
+ Hastags que segues
+ Copiar ligação para publicação
+ Encaminhar para %s
+ De certeza que pretende apagar este rascunho\?
+ Publicação agendada
+ A publicação tem de ser agendada pelo menos 10 minutos no futuro.
+ Guardar rascunho\?
+ Horário ou rascunho
+ Reduzir movimento em animações
+ Anúncios
+ Escrever para iniciar a procura
+ Remover como seguidor
+ Texto alternativo em falta
+ Se ligar as notificações de publicação para algumas pessoas, as suas publicações novas aparecerão aqui.
+ Fixar linha do tempo
+ Cidade
+ Régua
+ Pionés
+ Publicações editadas
+ À procura…
+ Guardar alterações ou publicar o rascunho agora\?
+ Botão \"Ver publicações novas\"
+ apenas-local
+ Apenas instância local
+ A tua instância nativa tem de suportar publicações locais para isto funcionar. As versões modificadas do Mastodon suportam mas o Mastodon não.
+ Versão do servidor: %s
+ Esconder botões interativos
+ Adicionar \"re\" a respostas com AC
+ Em resposta
+ Tipo de conteúdo
+ BBCode
+ Permite configurar um tipo de conteúdo como Markdown na criação de uma publicação. Lembra-te que nem todas as instâncias permitem isto.
+ Desfixar publicação do perfil
+ Citar %s
+ Visibilidade da resposta
+ Todas as respostas
+ Respostas para os meus seguidores
+ Respostas para mim
+ Mostrar número de interações
+ Megalodon %1$s (%2$d)
+ Marcar conteúdo como sensível
+ Ligar notificações de publicação para %s
+ Megalodon %s descarregado e pronto a instalar.
+ A verificar atualizações
+ Sem atualizações disponíveis
+ Mostrar linha do tempo unificada
+ Publicações
+ Notificações de publicação
+ Recurso não encontrado
+ Adicionado aos favoritos como %s
+ Já adicionado aos favoritos
+ Partilhar com outra conta
+ Responder com outra conta
+ Ícone uniforme para todas as notificações
+ Nome da Lista
+ Mostrar respostas a
+ Membros da lista
+ Utilizadores seguidos
+ ninguém
+ Apagar lista
+ De certeza que quer apagar a lista \"%s\"\?
+ Editar lista
+ As tuas listas
+ Página principal
+ Local
+ Federação
+ Publicações
+ Adicionar
+ Linha do tempo
+ Lista
+ Hashtag
+ Desfixar linha do tempo
+ Fixado à página principal
+ Desfixado da página principal
+ Linha \"Em resposta a\" por cima da foto de perfil
+ Remover
+ ícone
+ Coração
+ Estrela
Pi
Paleta de cores
Chapéu académico
@@ -213,7 +245,8 @@
Comboio
Claquete
Folhas
- Desporto
+ Desposto
+ Abertura
Música
Pessoas
Saúde
@@ -225,64 +258,31 @@
Mapa
Fórmula matemática
Mochila
+ Pasta
Fogo
Inseto
Pizza
Martelo
- Régua
Auscultadores
Humano
Globo
- Pionés
Editar linha do tempo
+ Editar linhas do tempo
ALT
editado
- Publicações editadas
Anexar ficheiro
- À procura…
- Sem resultados
- Guardar rascunho\?
- Sem descrição de imagem
- Indicador para descrições de imagem
- Indicador para descrições de imagem ausentes
- Ativar versões beta
- apenas local
- apenas mencionado
- .
- Apenas instância local
- Recursos da instância
- O servidor suporta publicações locais
- A tua instância nativa tem de suportar publicações locais para isto funcionar. A maioria das versões modificadas do Mastodon suportam mas o Mastodon não.
- Modo local do Glitch
- Ativar isto se a tua instância nativa corre no Glitch. Não é necessário para Hometown ou Akkoma.
- inscrito
- denunciado
- Seguir com outra conta
- A seguir com %s
- Botão \"Mostrar novas publicações\"
- Desfazer partilha
- Partilhar com outra conta
- Partilhado com %s
- Já partilhado
- Edita uma publicação partilhada
- reagiu
- Novas denuncias
- Filtrado: %s
- Registos
- reagiu com %s
- Versão do servidor: %s
- Resultados do inquérito
- Mostrar \"re:\" ao responder a AC
- Expandir
- Esconder
- Esconder publicações muito longas
- Corrigir anexos\?
- Alguns anexos ainda não acabaram de carregar.
- Esconder botões de interação
Resposta enviada a %s
- Em resposta
- Linha \"Em resposta a\" acima da foto de perfil
- Mostrar fio
- Linha partilhar/responder compacta
Confirmar antes de partilhar
+ %1$s (%2$s)
+ Partilhado como %s
+ Já partilhado
+ Editar uma publicação partilhada
+ Linha compacta partilhar/responder
+ reagiu com %s
+ reagiu
+ Novas denúncias
+ Alguns anexos ainda não carregaram.
+ Seguido com %s
+ Esconder automaticamente o botão Escrever
+ Mostrar fio
\ No newline at end of file
diff --git a/mastodon/src/main/res/values-pt/strings_sk.xml b/mastodon/src/main/res/values-pt/strings_sk.xml
index a6b3daec9..a93f10998 100644
--- a/mastodon/src/main/res/values-pt/strings_sk.xml
+++ b/mastodon/src/main/res/values-pt/strings_sk.xml
@@ -1,2 +1,44 @@
-
\ No newline at end of file
+
+ Megalodon
+ Fixado
+ Apagar e reescrever
+ Tem a certeza que pretende apagar e reescrever esta publicação\?
+ Tem a certeza que deseja desafixar esta publicação\?
+ Todas as respostas
+ Carregar novas publicações automaticamente
+ Desligar as notificações de publicação para %s
+ Estas são as publicações mais recentes das pessoas na tua federação.
+ Apagar e reescrever publicação
+ Fixar no perfil
+ Fixar publicação no perfil
+ Deseja fixar esta publicação ao seu perfil\?
+ A fixar a publicação…
+ Desafixar do perfil
+ Desafixar publicação do perfil
+ A desafixar publicação…
+ Descrição da imagem
+ Não listado
+ Mostrar respostas
+ Citação %s
+ Visibilidade da resposta
+ Apagar idiomas usados recentemente
+ Respostas aos meus comentários
+ Respostas a mim
+ Mostrar impulsionamentos
+ Mostrar contagem de interações
+ Megalodon v%1$s (%2$d)
+ Marcar conteúdo como sensível
+ Ligar as notificações de publicação para %s
+ Federação
+ Megalodon %s pronto a descarregar.
+ Megalodon %s descarregado e pronto a instalar.
+ A verificar atualizações
+ Sem atualizações disponíveis
+ Listas
+ Pedidos para seguir
+ Aceitar pedido para seguir
+ Rejeitar pedido para seguir
+ Listas com %s
+ Mostrar sempre avisos de conteúdo
+
\ No newline at end of file
diff --git a/mastodon/src/main/res/values-ru-rRU/strings.xml b/mastodon/src/main/res/values-ru-rRU/strings.xml
index 9a70943ab..c5bd6eb64 100644
--- a/mastodon/src/main/res/values-ru-rRU/strings.xml
+++ b/mastodon/src/main/res/values-ru-rRU/strings.xml
@@ -472,7 +472,7 @@
Спрятать повторно
Выберите один или более
Сохранить изменения
- Возможности
+ Избранное
Лента
Посмотреть все
Учётные записи
@@ -480,6 +480,7 @@
Показать
Скрыть
Присоединиться к %s
+ Выбрать другой сервер
или
Узнать больше
Добро пожаловать в Mastodon
diff --git a/mastodon/src/main/res/values-sv-rSE/strings.xml b/mastodon/src/main/res/values-sv-rSE/strings.xml
index 099b54deb..c69fe4787 100644
--- a/mastodon/src/main/res/values-sv-rSE/strings.xml
+++ b/mastodon/src/main/res/values-sv-rSE/strings.xml
@@ -412,4 +412,7 @@
%1$s tillåter inte registrering från %2$s. Prova en annan eller <a>välj en annan server</a>.
Det här användarnamnet är redan taget.
+ Spara ändringar
+ eller
+ Välkommen till Mastodon
diff --git a/mastodon/src/main/res/values-uk-rUA/strings_sk.xml b/mastodon/src/main/res/values-uk-rUA/strings_sk.xml
index ac7b05590..58eb54971 100644
--- a/mastodon/src/main/res/values-uk-rUA/strings_sk.xml
+++ b/mastodon/src/main/res/values-uk-rUA/strings_sk.xml
@@ -275,4 +275,21 @@
Підтверджувати поширення
реагує
реагує з %s
+ Не визначено
+ Звичайний текст
+ HTML
+ BBCode
+ MFM
+ Увімкнути форматування допису
+ Типовий тип вмісту
+ Тип вмісту
+ Це дозволяє вам попередньо вибрати тип вмісту під час написання нових дописів, замінивши значення, встановлене в «Налаштуваннях постингу».
+ Markdown
+ Дозволяє налаштувати тип вмісту, наприклад, Markdown, під час написання допису. Зауважте, що не всі сервери підтримують цю функцію.
+ Відкрити у застосунку
+ Поділитися через обліковий запис
+ Це найновіші дописи людей у бульбашці вашого сервера Akkoma.
+ Бульбашка
+ Сервер тимчасово недоступний
+ Поділитися або відкрити за допомогою облікового запису
\ No newline at end of file
diff --git a/mastodon/src/main/res/values-v31/colors.xml b/mastodon/src/main/res/values-v31/colors.xml
index 662f77798..b622faf32 100644
--- a/mastodon/src/main/res/values-v31/colors.xml
+++ b/mastodon/src/main/res/values-v31/colors.xml
@@ -4,33 +4,57 @@
@android:color/system_neutral1_50
- @android:color/system_neutral1_1000
- @android:color/system_neutral1_900
- @android:color/system_neutral1_800
- @android:color/system_neutral1_800
- @android:color/system_neutral1_700
- @android:color/system_neutral1_600
- @android:color/system_neutral1_500
- @android:color/system_neutral1_400
- @android:color/system_neutral1_300
- @android:color/system_neutral1_200
- @android:color/system_neutral1_100
- @android:color/system_neutral1_50
- @android:color/system_neutral1_50
- @android:color/system_neutral1_10
+ @android:color/system_neutral1_900
+ @android:color/system_neutral1_800
+ @android:color/system_neutral1_800
+ @android:color/system_neutral1_700
+ @android:color/system_neutral1_600
+ @android:color/system_neutral1_500
+ @android:color/system_neutral1_400
+ @android:color/system_neutral1_300
+ @android:color/system_neutral1_200
+ @android:color/system_neutral1_100
+ @android:color/system_neutral1_50
+ @android:color/system_neutral1_50
+ @android:color/system_neutral1_10
- @android:color/system_accent1_10
- @android:color/system_accent1_50
- @android:color/system_accent1_100
- @android:color/system_accent1_200
- @android:color/system_accent1_300
- @android:color/system_accent1_400
- @android:color/system_accent1_500
- @android:color/system_accent1_600
- @android:color/system_accent1_700
- @android:color/system_accent1_800
- @android:color/system_accent1_900
+ @android:color/system_accent1_10
+ @android:color/system_accent1_50
+ @android:color/system_accent1_100
+ @android:color/system_accent1_200
+ @android:color/system_accent1_300
+ @android:color/system_accent1_400
+ @android:color/system_accent1_500
+ @android:color/system_accent1_600
+ @android:color/system_accent1_700
+ @android:color/system_accent1_800
+ @android:color/system_accent1_900
+ @android:color/system_neutral2_900
+ @android:color/system_neutral2_800
+ @android:color/system_neutral2_800
+ @android:color/system_neutral2_700
+ @android:color/system_neutral2_600
+ @android:color/system_neutral2_500
+ @android:color/system_neutral2_400
+ @android:color/system_neutral2_300
+ @android:color/system_neutral2_200
+ @android:color/system_neutral2_100
+ @android:color/system_neutral2_50
+ @android:color/system_neutral2_50
+ @android:color/system_neutral2_10
+
+ @android:color/system_accent2_10
+ @android:color/system_accent2_50
+ @android:color/system_accent2_100
+ @android:color/system_accent2_200
+ @android:color/system_accent2_300
+ @android:color/system_accent2_400
+ @android:color/system_accent2_500
+ @android:color/system_accent2_600
+ @android:color/system_accent2_700
+ @android:color/system_accent2_800
+ @android:color/system_accent2_900
@android:color/system_accent1_600
diff --git a/mastodon/src/main/res/values-vi-rVN/strings.xml b/mastodon/src/main/res/values-vi-rVN/strings.xml
index bc09172d4..d883cbca2 100644
--- a/mastodon/src/main/res/values-vi-rVN/strings.xml
+++ b/mastodon/src/main/res/values-vi-rVN/strings.xml
@@ -11,7 +11,7 @@
Chuẩn bị xác thực…
Hoàn tất xác thực…
%s đăng lại
- %s viết tiếp
+ Trả lời %s
Thông báo
đã theo dõi bạn
đã yêu cầu theo dõi bạn
@@ -127,7 +127,7 @@
- %d người đang thảo luận
- - Đề cập %d lần
+ - Chia sẻ %d lượt
Báo cáo %s
Có vấn đề gì với tút này?
@@ -150,8 +150,8 @@
Đang gửi báo cáo…
Cảm ơn đã báo cáo, chúng tôi sẽ xem xét kỹ.
Trong lúc chờ chúng tôi xem xét, bạn có thể áp dụng hành động với @%s.
- Ngưng theo dõi %s
- Ngưng theo dõi
+ Bỏ theo dõi %s
+ Bỏ theo dõi
Bạn sẽ không thấy tút hoặc lượt đăng lại của họ trên bảng tin. Họ không biết rằng bạn ẩn họ.
Họ sẽ không thể theo dõi hoặc đọc tút của bạn, nhưng họ có thể hiểu bạn đã chặn họ.
Không muốn xem thứ này?
@@ -223,7 +223,7 @@
Tự động
Sáng
Tối
- Chế độ tối chân thật
+ Chế độ tương phản cao
Thao tác
Ảnh đại diện và emoji động
Dùng trình duyệt tích hợp
@@ -268,7 +268,7 @@
Hồ sơ của tôi
Xem media
Theo dõi %s
- Ngưng theo dõi %s
+ Bỏ theo dõi %s
Bạn đã theo dõi %s
Yêu cầu theo dõi %s
Mở trong trình duyệt
@@ -317,8 +317,8 @@
%1$s qua %2$s
vừa xong
- Lượt đăng lại
- Lượt thích
+ Đăng lại
+ Thích
Lịch sử chỉnh sửa
Sửa lần cuối %s
vừa xong
@@ -369,14 +369,14 @@
Đã sao chép vào bộ nhớ tạm
Lưu
Bỏ lưu
- Tút đã lưu
- Lượt thích
+ Những tút đã lưu
+ Những tút đã thích
Chào mừng quay trở lại!
Đăng nhập với máy chủ nơi bạn tạo tài khoản.
URL Máy chủ
Chúng tôi sẽ chọn một máy chủ dựa trên ngôn ngữ của bạn nếu bạn tiếp tục mà không lựa chọn.
Mọi ngôn ngữ
- Đăng ký nhanh
+ Duyệt tự động
Duyệt thủ công
Mọi hình thức duyệt
Châu Âu
@@ -385,7 +385,7 @@
Châu Phi
Châu Á
Châu Đại Dương
- Tạm ngưng đăng ký mới
+ Tạm dừng đăng ký mới
Sở thích đặc biệt
Mật khẩu không khớp
Chọn giúp tôi
diff --git a/mastodon/src/main/res/values-zh-rCN/strings.xml b/mastodon/src/main/res/values-zh-rCN/strings.xml
index 9488e2d7a..89ee38b0f 100644
--- a/mastodon/src/main/res/values-zh-rCN/strings.xml
+++ b/mastodon/src/main/res/values-zh-rCN/strings.xml
@@ -381,6 +381,7 @@
非洲
亚洲
大洋洲
+ 不接受新成员
两次输入密码不匹配
帮我挑选
添加
@@ -396,9 +397,11 @@
时间轴
帐号
+ 已验证链接
显示
隐藏
加入 %s
+ 选择其他服务器
或者
了解更多
欢迎来到Mastodon
diff --git a/mastodon/src/main/res/values/attrs.xml b/mastodon/src/main/res/values/attrs.xml
index b170a87e2..214ba3b15 100644
--- a/mastodon/src/main/res/values/attrs.xml
+++ b/mastodon/src/main/res/values/attrs.xml
@@ -108,4 +108,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mastodon/src/main/res/values/colors.xml b/mastodon/src/main/res/values/colors.xml
index a49c3d4ba..ff1ccc8fc 100644
--- a/mastodon/src/main/res/values/colors.xml
+++ b/mastodon/src/main/res/values/colors.xml
@@ -32,7 +32,6 @@
#ae218a
#6d1556
-
#FFFBFA
#FEF3F2
#FEE4E2
@@ -104,31 +103,69 @@
@color/gray_50
- @color/gray_900
- @color/gray_800t
- @color/gray_800
- @color/gray_700
- @color/gray_600
- @color/gray_500
- @color/gray_400
- @color/gray_300
- @color/gray_200
- @color/gray_100
- @color/gray_50t
- @color/gray_50
- @color/gray_25
+ @color/gray_900
+ @color/gray_800t
+ @color/gray_800
+ @color/gray_700
+ @color/gray_600
+ @color/gray_500
+ @color/gray_400
+ @color/gray_300
+ @color/gray_200
+ @color/gray_100
+ @color/gray_50t
+ @color/gray_50
+ @color/gray_25
- @color/purple_primary_25
- @color/purple_primary_50
- @color/purple_primary_100
- @color/purple_primary_200
- @color/purple_primary_300
- @color/purple_primary_400
- @color/purple_primary_500
- @color/purple_primary_600
- @color/purple_primary_700
- @color/purple_primary_800
- @color/purple_primary_900
+ @color/gray_900
+ @color/gray_800t
+ @color/gray_800
+ @color/gray_700
+ @color/gray_600
+ @color/gray_500
+ @color/gray_400
+ @color/gray_300
+ @color/gray_200
+ @color/gray_100
+ @color/gray_50t
+ @color/gray_50
+ @color/gray_25
+
+ @color/primary_25
+ @color/primary_50
+ @color/primary_100
+ @color/primary_200
+ @color/primary_300
+ @color/primary_400
+ @color/primary_500
+ @color/primary_600
+ @color/primary_700
+ @color/primary_800
+ @color/primary_900
+
+ @color/primary_25
+ @color/primary_50
+ @color/primary_100
+ @color/primary_200
+ @color/primary_300
+ @color/primary_400
+ @color/primary_500
+ @color/primary_600
+ @color/primary_700
+ @color/primary_800
+ @color/primary_900
+
+ @color/primary_25
+ @color/primary_50
+ @color/primary_100
+ @color/primary_200
+ @color/primary_300
+ @color/primary_400
+ @color/primary_500
+ @color/primary_600
+ @color/primary_700
+ @color/primary_800
+ @color/primary_900
#6750A4
diff --git a/mastodon/src/main/res/values/palettes.xml b/mastodon/src/main/res/values/palettes.xml
index 102a2fe47..256d87d43 100644
--- a/mastodon/src/main/res/values/palettes.xml
+++ b/mastodon/src/main/res/values/palettes.xml
@@ -28,34 +28,115 @@
- @color/gray_50t
- @color/gray_50
- @color/gray_25
+
+
+
+ - @color/primary_25
+ - @color/primary_50
+ - @color/primary_100
+ - @color/primary_200
+ - @color/primary_300
+ - @color/primary_400
+ - @color/primary_500
+ - @color/primary_600
+ - @color/primary_700
+ - @color/primary_800
+ - @color/primary_900
+
+ - @color/primary_25
+ - @color/primary_50
+ - @color/primary_100
+ - @color/primary_200
+ - @color/primary_300
+ - @color/primary_400
+ - @color/primary_500
+ - @color/primary_600
+ - @color/primary_700
+ - @color/primary_800
+ - @color/primary_900
+
+ - @color/gray_900
+ - @color/gray_800t
+ - @color/gray_800
+ - @color/gray_700
+ - @color/gray_600
+ - @color/gray_500
+ - @color/gray_400
+ - @color/gray_300
+ - @color/gray_200
+ - @color/gray_100
+ - @color/gray_50t
+ - @color/gray_50
+ - @color/gray_25
@@ -462,6 +464,11 @@
- center_vertical
+
+