Merge remote-tracking branch 'megalodon_main/main'
# Conflicts: # mastodon/build.gradle # mastodon/src/main/java/org/joinmastodon/android/ExternalShareActivity.java # mastodon/src/main/java/org/joinmastodon/android/MainActivity.java # mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java # mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/EditTimelinesFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/FollowRequestsListFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/FollowedHashtagsFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelinesFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/FollowerListFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/FollowingListFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/StatusFavoritesListFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/StatusReblogsListFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverAccountsFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverHashtagsFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverNewsFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/discover/FederatedTimelineFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/discover/LocalTimelineFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/discover/SearchFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/AccountActivationFragment.java # mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java # mastodon/src/main/java/org/joinmastodon/android/ui/AccountSwitcherSheet.java # mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java # mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HashtagStatusDisplayItem.java # mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java # mastodon/src/main/res/layout/item_account_switcher.xml # mastodon/src/main/res/values-ar-rDZ/strings_sk.xml # mastodon/src/main/res/values-es-rES/strings_sk.xml # mastodon/src/main/res/values-nl-rNL/strings_sk.xml # mastodon/src/main/res/values-pt-rPT/strings_sk.xml # mastodon/src/main/res/values-v31/colors.xml # mastodon/src/main/res/values/colors.xml # mastodon/src/main/res/values/styles.xml
This commit is contained in:
commit
14175a9140
|
@ -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<ThreadFragment.NeighborAncestryInfo> 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<Status> expectedAncestors = List.of(
|
||||
fakeStatus("oldest ancestor", null),
|
||||
fakeStatus("younger ancestor", "oldest ancestor")
|
||||
);
|
||||
List<Status> 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
|
||||
}
|
||||
}
|
|
@ -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<String> text = Optional.ofNullable(getIntent().getStringExtra(Intent.EXTRA_TEXT));
|
||||
boolean isMastodonURL = text.map(UiUtils::looksLikeMastodonUrl).orElse(false);
|
||||
|
||||
List<AccountSession> 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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Notification> result){
|
||||
|
|
|
@ -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<List<Notification>> {
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -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<List<Status>> {
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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<List<Status>>{
|
|||
addQueryParameter("min_id", minID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", ""+limit);
|
||||
if(GlobalUserPreferences.replyVisibility != null)
|
||||
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<List<Status>> {
|
|||
addQueryParameter("limit", ""+limit);
|
||||
if(sinceID!=null)
|
||||
addQueryParameter("since_id", sinceID);
|
||||
if(GlobalUserPreferences.replyVisibility != null)
|
||||
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<List<Status>>{
|
|||
addQueryParameter("max_id", maxID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", limit+"");
|
||||
if(GlobalUserPreferences.replyVisibility != null)
|
||||
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Instance> 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String> 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<String> domains){
|
||||
private void maybeUpdateCustomEmojis(Set<String> 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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Announcement>
|
|||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return isInstanceAkkoma() ? base.path("/announcements").build() : null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<T extends DisplayItemsParent> extends BaseRecyclerFragment<T> implements PhotoViewerHost, ScrollableToTop, HasFab, DomainDisplay{
|
||||
public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends RecyclerFragment<T> implements PhotoViewerHost, ScrollableToTop, HasFab, ProvidesAssistContent.ProvidesWebUri, DomainDisplay {
|
||||
protected ArrayList<StatusDisplayItem> displayItems=new ArrayList<>();
|
||||
protected DisplayItemsAdapter adapter;
|
||||
protected String accountID;
|
||||
|
@ -132,7 +135,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||
displayItems.clear();
|
||||
}
|
||||
|
||||
protected void prependItems(List<T> items, boolean notify){
|
||||
protected int prependItems(List<T> items, boolean notify){
|
||||
data.addAll(0, items);
|
||||
int offset=0;
|
||||
for(T s:items){
|
||||
|
@ -145,6 +148,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||
}
|
||||
if(notify)
|
||||
adapter.notifyItemRangeInserted(0, offset);
|
||||
return offset;
|
||||
}
|
||||
|
||||
protected String getMaxID(){
|
||||
|
@ -205,7 +209,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> 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<T extends DisplayItemsParent> 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<T extends DisplayItemsParent> exten
|
|||
for(int i=0;i<list.getChildCount();i++){
|
||||
View child=list.getChildAt(i);
|
||||
holder=list.getChildViewHolder(child);
|
||||
if(holder instanceof StatusDisplayItem.Holder){
|
||||
if(holder instanceof StatusDisplayItem.Holder<?> 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<T extends DisplayItemsParent> 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<T extends DisplayItemsParent> exten
|
|||
warning.getItem().status.filterRevealed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccountID(){
|
||||
return accountID;
|
||||
}
|
||||
|
@ -717,6 +754,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||
return attachmentViewsPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvideAssistContent(AssistContent assistContent) {
|
||||
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
|
||||
}
|
||||
|
||||
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
|
||||
|
||||
|
@ -778,6 +819,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<TimelineDefinition>
|
|||
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<TimelineDefinition>
|
|||
|
||||
@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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<FollowRequestsListFragment.AccountWrapper> implements ScrollableToTop{
|
||||
public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsListFragment.AccountWrapper> implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri {
|
||||
private String accountID;
|
||||
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||
private GetAccountRelationships relationshipsRequest;
|
||||
|
@ -149,8 +151,13 @@ public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsL
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isScrolledToTop() {
|
||||
return list.getChildAt(0).getTop() == 0;
|
||||
public String getAccountID() {
|
||||
return accountID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return base.path(isInstanceAkkoma() ? "/friend-requests" : "/follow_requests").build();
|
||||
}
|
||||
|
||||
private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
|
|
|
@ -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<Hashtag> implements ScrollableToTop {
|
||||
public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> 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<Hashtag> 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<Hashtag> implemen
|
|||
onDataLoaded(result, nextMaxID!=null);
|
||||
}
|
||||
})
|
||||
.exec(accountId);
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -76,8 +78,13 @@ public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> 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<HashtagViewHolder>{
|
||||
|
@ -114,7 +121,7 @@ public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implemen
|
|||
|
||||
@Override
|
||||
public void onClick() {
|
||||
UiUtils.openHashtagTimeline(getActivity(), accountId, item.name, item.following);
|
||||
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name, item.following);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Instance> getInstance() {
|
||||
return getSession().getInstance();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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> 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<Notification> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<SimpleViewHolder> {
|
||||
@NonNull
|
||||
@Override
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ListTimeline> implements ScrollableToTop {
|
||||
private String accountId;
|
||||
private String profileAccountId;
|
||||
private final HashMap<String, Boolean> userInListBefore = new HashMap<>();
|
||||
private final HashMap<String, Boolean> 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<String> accountIdList = Collections.singletonList(profileAccountId);
|
||||
MastodonAPIRequest<Object> 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<ListTimeline> 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<ListTimeline> allLists) {
|
||||
if (getActivity() == null) return;
|
||||
List<ListTimeline> 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<ListViewHolder> 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<ListViewHolder>{
|
||||
@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<ListTimeline> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<ListTimeline> implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri {
|
||||
private String accountID;
|
||||
private String profileAccountId;
|
||||
private final HashMap<String, Boolean> userInListBefore = new HashMap<>();
|
||||
private final HashMap<String, Boolean> 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<String> accountIdList = Collections.singletonList(profileAccountId);
|
||||
MastodonAPIRequest<Object> 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<ListTimeline> 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<ListTimeline> allLists) {
|
||||
if (getActivity() == null) return;
|
||||
List<ListTimeline> 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<ListViewHolder> 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<ListViewHolder>{
|
||||
@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<ListTimeline> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<tabViews.length;i++){
|
||||
|
@ -124,6 +121,18 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||
|
||||
tabLayout.setTabTextSize(V.dp(16));
|
||||
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
||||
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||
@Override
|
||||
public void onTabSelected(TabLayout.Tab tab) {}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(TabLayout.Tab tab) {}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(TabLayout.Tab tab) {
|
||||
scrollToTop();
|
||||
}
|
||||
});
|
||||
|
||||
pager.setOffscreenPageLimit(4);
|
||||
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
|
||||
|
@ -145,20 +154,17 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putBoolean("__is_tab", true);
|
||||
args.putBoolean("noAutoLoad", true);
|
||||
|
||||
allNotificationsFragment=new NotificationsListFragment();
|
||||
allNotificationsFragment.setArguments(args);
|
||||
|
||||
args=new Bundle(args);
|
||||
args.putBoolean("onlyMentions", true);
|
||||
args.putBoolean("noAutoLoad", true);
|
||||
mentionsFragment=new NotificationsListFragment();
|
||||
mentionsFragment.setArguments(args);
|
||||
|
||||
args=new Bundle(args);
|
||||
args.putBoolean("onlyPosts", true);
|
||||
args.putBoolean("noAutoLoad", true);
|
||||
postsFragment=new NotificationsListFragment();
|
||||
postsFragment.setArguments(args);
|
||||
|
||||
|
@ -190,6 +196,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||
new GetFollowRequests(null, 1).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(HeaderPaginationList<Account> 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<SimpleViewHolder>{
|
||||
@NonNull
|
||||
@Override
|
||||
|
@ -263,4 +276,4 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||
return position;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Notificati
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDomain() {
|
||||
return super.getDomain() + "/notifications";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
|
@ -156,13 +155,17 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||
loadRelationships(needRelationships);
|
||||
maxID=result.maxID;
|
||||
|
||||
if(offset == 0 && !result.items.isEmpty() && !result.isFromCache() && AccountSessionManager.getInstance().getAccount(accountID).markers != null && AccountSessionManager.getInstance().getAccount(accountID).markers.notifications != null){
|
||||
Markers markers = AccountSessionManager.getInstance().getAccount(accountID).markers;
|
||||
if(offset==0 && !result.items.isEmpty() && !result.isFromCache() && markers != null && markers.notifications != null){
|
||||
E.post(new AllNotificationsSeenEvent());
|
||||
new SaveMarkers(null, result.items.get(0).id).exec(accountID);
|
||||
if (AccountSessionManager.getInstance().getAccount(accountID).markers != null)
|
||||
AccountSessionManager.getInstance().getAccount(accountID).markers
|
||||
.notifications.lastReadId = result.items.get(0).id;
|
||||
AccountSessionManager.getInstance().getAccount(accountID).markers
|
||||
.notifications.lastReadId = result.items.get(0).id;
|
||||
AccountSessionManager.getInstance().writeAccountsFile();
|
||||
|
||||
if (isInstanceAkkoma()) {
|
||||
new PleromaMarkNotificationsRead(result.items.get(0).id).exec(accountID);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -182,11 +185,8 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||
@Override
|
||||
protected void onShown(){
|
||||
super.onShown();
|
||||
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading){
|
||||
refreshing=true;
|
||||
loadData();
|
||||
}
|
||||
|
||||
// if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
||||
// loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -272,4 +272,11 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||
displayItems.subList(index, lastIndex).clear();
|
||||
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return base.path(isInstanceAkkoma()
|
||||
? "/users/" + getSession().self.username + "/interactions"
|
||||
: "/notifications").build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ public abstract class PinnableStatusListFragment extends StatusListFragment impl
|
|||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
pinnedTimelines = new ArrayList<>(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES));
|
||||
pinnedTimelines = new ArrayList<>(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.getDefaultTimelines(accountID)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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<BaseViewHolder> 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<MAX_FIELDS)
|
||||
if(size<maxFields)
|
||||
size++;
|
||||
return size;
|
||||
}
|
||||
|
@ -1448,7 +1449,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||
@Override
|
||||
public void onClick(){
|
||||
metadataListData.add(new AccountField());
|
||||
if(metadataListData.size()==MAX_FIELDS){ // replace this row with new row
|
||||
if(metadataListData.size()==maxFields){ // replace this row with new row
|
||||
adapter.notifyItemChanged(metadataListData.size()-1);
|
||||
}else{
|
||||
adapter.notifyItemInserted(metadataListData.size()-1);
|
||||
|
|
|
@ -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;
|
||||
import android.widget.ImageButton;
|
||||
|
@ -182,4 +183,10 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
// TODO: adapt when frontends finally implement a scheduled posts list
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Status>
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig){
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
if (getParentFragment() instanceof HomeTabFragment home) home.updateToolbarLogo();
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
* <pre>
|
||||
* [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
|
||||
* </pre>
|
||||
* confused? good. /j
|
||||
*/
|
||||
private final List<Pair<String, Integer>> levels = new ArrayList<>();
|
||||
private final HashMap<String, NeighborAncestryInfo> 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<StatusDisplayItem> buildDisplayItems(Status s){
|
||||
List<StatusDisplayItem> 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<Integer> deleteTheseItems = new ArrayDeque<>();
|
||||
|
||||
// modifying hidden filtered items if status is displayed as a warning
|
||||
List<StatusDisplayItem> 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<String> 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<Status> getDescendantsOrdered(String id, List<Status> statuses){
|
||||
public static List<NeighborAncestryInfo> mapNeighborhoodAncestry(Status mainStatus, StatusContext context) {
|
||||
List<NeighborAncestryInfo> ancestry = new ArrayList<>();
|
||||
|
||||
List<Status> 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<String> 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<Status> getDescendantsOrdered(String id, List<Status> statuses){
|
||||
List<Status> 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<Status> getDirectDescendants(String id, List<Status> statuses){
|
||||
private static List<Status> getDirectDescendants(String id, List<Status> 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<Status> getStatus() {
|
||||
return Optional.ofNullable(status);
|
||||
}
|
||||
|
||||
public Optional<Status> getDescendantNeighbor() {
|
||||
return Optional.ofNullable(descendantNeighbor);
|
||||
}
|
||||
|
||||
public Optional<Status> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<BaseAccountListFragment.AccountItem> {
|
||||
public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccountListFragment.AccountItem> implements ProvidesAssistContent.ProvidesWebUri {
|
||||
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
||||
protected String accountID;
|
||||
protected ArrayList<APIRequest<?>> relationshipsRequests=new ArrayList<>();
|
||||
|
@ -175,6 +178,16 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
|||
super.onApplyWindowInsets(insets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccountID() {
|
||||
return accountID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvideAssistContent(AssistContent assistContent) {
|
||||
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
|
||||
}
|
||||
|
||||
protected class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
public AccountsAdapter(){
|
||||
super(imgLoader);
|
||||
|
@ -406,7 +419,7 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
|||
args.putString("account", accountID);
|
||||
args.putString("profileAccount", account.id);
|
||||
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
||||
Nav.go(getActivity(), ListTimelinesFragment.class, args);
|
||||
Nav.go(getActivity(), ListsFragment.class, args);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -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 FollowerListFragment extends AccountRelatedAccountListFragment{
|
|||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
targetAccount = account;
|
||||
setSubtitle(getResources().getQuantityString(R.plurals.x_followers, (int)(account.followersCount%1000), account.followersCount));
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,8 @@ public class FollowerListFragment extends AccountRelatedAccountListFragment{
|
|||
}
|
||||
|
||||
@Override
|
||||
public HeaderPaginationRequest<Account> 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Account> 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Account> onCreateRemoteRequest(String id, String maxID, int count) {
|
||||
return null;
|
||||
|
|
|
@ -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<Account> onCreateRemoteRequest(String id, String maxID, int count) {
|
||||
return null;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Status> 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;
|
||||
}
|
||||
}
|
|
@ -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<DiscoverAccountsFragment.AccountWrapper> implements ScrollableToTop, IsOnTop, DomainDisplay {
|
||||
public class DiscoverAccountsFragment extends RecyclerFragment<DiscoverAccountsFragment.AccountWrapper> implements ScrollableToTop, IsOnTop, ProvidesAssistContent.ProvidesWebUri {
|
||||
private String accountID;
|
||||
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||
private GetAccountRelationships relationshipsRequest;
|
||||
|
@ -60,11 +61,6 @@ public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccou
|
|||
super(20);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDomain() {
|
||||
return DomainDisplay.super.getDomain() + "/explore/suggestions";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
|
@ -157,6 +153,16 @@ public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccou
|
|||
return isRecyclerViewOnTop(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccountID() {
|
||||
return accountID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return isInstanceAkkoma() ? null : base.path("/explore/suggestions").build();
|
||||
}
|
||||
|
||||
private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
|
||||
public AccountsAdapter(){
|
||||
|
|
|
@ -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<tabViews.length;i++){
|
||||
FrameLayout tabView=new FrameLayout(getActivity());
|
||||
|
||||
/// int switchIndex = noFederated && 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<SimpleViewHolder>{
|
||||
@NonNull
|
||||
@Override
|
||||
|
|
|
@ -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<Hashtag> implements ScrollableToTop, IsOnTop, DomainDisplay {
|
||||
public class DiscoverHashtagsFragment extends RecyclerFragment<Hashtag> 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<Hashtag> 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<HashtagViewHolder>{
|
||||
|
@ -117,14 +120,19 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> 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
|
|
@ -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<Card> implements ScrollableToTop, IsOnTop, DomainDisplay {
|
||||
public class DiscoverNewsFragment extends RecyclerFragment<Card> implements ScrollableToTop, IsOnTop, ProvidesAssistContent.ProvidesWebUri {
|
||||
private String accountID;
|
||||
private List<ImageLoaderRequest> imageRequests=Collections.emptyList();
|
||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS);
|
||||
|
@ -46,11 +46,6 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> 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<Card> 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<LinkViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<SearchResult>{
|
||||
public class SearchFragment extends BaseStatusListFragment<SearchResult> implements IsOnTop {
|
||||
private String currentQuery;
|
||||
private List<StatusDisplayItem> prevDisplayItems;
|
||||
private EnumSet<SearchResult.Type> currentFilter=EnumSet.allOf(SearchResult.Type.class);
|
||||
|
@ -57,11 +59,6 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||
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<SearchResult>{
|
|||
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<SearchResult>{
|
|||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<ItemViewHolder>{
|
||||
|
||||
@NonNull
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<List<String>> 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<String> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<TimelineDefinition> 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<TimelineDefinition> 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<TimelineDefinition> 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<TimelineDefinition> 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<TimelineDefinition> DEFAULT_TIMELINES = List.of(
|
||||
HOME_TIMELINE,
|
||||
LOCAL_TIMELINE,
|
||||
BUBBLE_TIMELINE,
|
||||
FEDERATED_TIMELINE
|
||||
);
|
||||
|
||||
private static final List<TimelineDefinition> ALL_TIMELINES = List.of(
|
||||
HOME_TIMELINE,
|
||||
LOCAL_TIMELINE,
|
||||
FEDERATED_TIMELINE,
|
||||
POSTS_TIMELINE,
|
||||
BUBBLE_TIMELINE
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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<String, Boolean> onClick;
|
||||
private final boolean externalShare, openInApp;
|
||||
private UsableRecyclerView list;
|
||||
private List<WrappedAccount> accounts;
|
||||
private ListImageLoaderWrapper imgLoader;
|
||||
private final boolean logOutEnabled;
|
||||
private final Consumer<AccountSession> onClick;
|
||||
private AccountsAdapter accountsAdapter;
|
||||
|
||||
public AccountSwitcherSheet(@NonNull Activity activity, boolean logOutEnabled, boolean addAccountEnabled, boolean showOpenURL, Consumer<AccountSession> 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<String, Boolean> 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<AccountSession> 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<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
public AccountsAdapter(){
|
||||
super(imgLoader);
|
||||
|
@ -197,55 +264,42 @@ public class AccountSwitcherSheet extends BottomSheet{
|
|||
}
|
||||
}
|
||||
|
||||
private class AccountViewHolder extends BindableViewHolder<AccountSession> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
|
||||
private final TextView name;
|
||||
private final TextView display_name;
|
||||
private final TextView display_add_account;
|
||||
private class AccountViewHolder extends BindableViewHolder<AccountSession> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -8,7 +8,15 @@ import android.view.ViewOutlineProvider;
|
|||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class OutlineProviders{
|
||||
private static SparseArray<ViewOutlineProvider> roundedRects=new SparseArray<>();
|
||||
private static final SparseArray<ViewOutlineProvider> roundedRects=new SparseArray<>();
|
||||
private static final SparseArray<ViewOutlineProvider> topRoundedRects=new SparseArray<>();
|
||||
private static final SparseArray<ViewOutlineProvider> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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){
|
||||
|
|
|
@ -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<HashtagStatusDisplayItem>{
|
||||
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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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){
|
||||
|
|
|
@ -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<GridItemType, MediaAttachmentViewController> viewPool;
|
||||
private final List<Attachment> attachments;
|
||||
private final ArrayList<ImageLoaderRequest> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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("<span class=\"quote-inline\">");
|
||||
if (!hasQuoteInlineTag) {
|
||||
String quoteUrl = statusForContent.quote.url;
|
||||
String quoteInline = String.format("<span class=\"quote-inline\">%sRE: <a href=\"%s\">%s</a></span>",
|
||||
statusForContent.content.endsWith("</p>") ? "" : "<br/><br/>", 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));
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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<drawables.length;i++){
|
||||
if(drawables[i]!=null){
|
||||
Drawable tinted=drawables[i].mutate();
|
||||
public static void fixCompoundDrawableTintOnAndroid6(TextView textView) {
|
||||
Drawable[] drawables = textView.getCompoundDrawablesRelative();
|
||||
for (int i = 0; i < drawables.length; i++) {
|
||||
if (drawables[i] != null) {
|
||||
Drawable tinted = drawables[i].mutate();
|
||||
tinted.setTintList(textView.getTextColors());
|
||||
drawables[i]=tinted;
|
||||
drawables[i] = tinted;
|
||||
}
|
||||
}
|
||||
textView.setCompoundDrawablesRelative(drawables[0], drawables[1], drawables[2], drawables[3]);
|
||||
}
|
||||
|
||||
public static void runOnUiThread(Runnable runnable){
|
||||
public static void runOnUiThread(Runnable runnable) {
|
||||
mainHandler.post(runnable);
|
||||
}
|
||||
|
||||
|
@ -284,67 +289,71 @@ public class UiUtils {
|
|||
mainHandler.postDelayed(runnable, delay);
|
||||
}
|
||||
|
||||
public static void removeCallbacks(Runnable runnable){
|
||||
public static void removeCallbacks(Runnable runnable) {
|
||||
mainHandler.removeCallbacks(runnable);
|
||||
}
|
||||
|
||||
/** Linear interpolation between {@code startValue} and {@code endValue} by {@code fraction}. */
|
||||
/**
|
||||
* Linear interpolation between {@code startValue} and {@code endValue} by {@code fraction}.
|
||||
*/
|
||||
public static int lerp(int startValue, int endValue, float fraction) {
|
||||
return startValue + Math.round(fraction * (endValue - startValue));
|
||||
}
|
||||
|
||||
public static String getFileName(Uri uri){
|
||||
if(uri.getScheme().equals("content")){
|
||||
try(Cursor cursor=MastodonApp.context.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null)){
|
||||
public static String getFileName(Uri uri) {
|
||||
if (uri.getScheme().equals("content")) {
|
||||
try (Cursor cursor = MastodonApp.context.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null)) {
|
||||
cursor.moveToFirst();
|
||||
String name=cursor.getString(0);
|
||||
if(name!=null)
|
||||
String name = cursor.getString(0);
|
||||
if (name != null)
|
||||
return name;
|
||||
}catch(Throwable ignore){}
|
||||
} catch (Throwable ignore) {
|
||||
}
|
||||
}
|
||||
return uri.getLastPathSegment();
|
||||
}
|
||||
|
||||
public static String formatFileSize(Context context, long size, boolean atLeastKB){
|
||||
if(size<1024 && !atLeastKB){
|
||||
public static String formatFileSize(Context context, long size, boolean atLeastKB) {
|
||||
if (size < 1024 && !atLeastKB) {
|
||||
return context.getString(R.string.file_size_bytes, size);
|
||||
}else if(size<1024*1024){
|
||||
return context.getString(R.string.file_size_kb, size/1024.0);
|
||||
}else if(size<1024*1024*1024){
|
||||
return context.getString(R.string.file_size_mb, size/(1024.0*1024.0));
|
||||
}else{
|
||||
return context.getString(R.string.file_size_gb, size/(1024.0*1024.0*1024.0));
|
||||
} else if (size < 1024 * 1024) {
|
||||
return context.getString(R.string.file_size_kb, size / 1024.0);
|
||||
} else if (size < 1024 * 1024 * 1024) {
|
||||
return context.getString(R.string.file_size_mb, size / (1024.0 * 1024.0));
|
||||
} else {
|
||||
return context.getString(R.string.file_size_gb, size / (1024.0 * 1024.0 * 1024.0));
|
||||
}
|
||||
}
|
||||
|
||||
public static MediaType getFileMediaType(File file){
|
||||
String name=file.getName();
|
||||
return MediaType.parse(MimeTypeMap.getSingleton().getMimeTypeFromExtension(name.substring(name.lastIndexOf('.')+1)));
|
||||
public static MediaType getFileMediaType(File file) {
|
||||
String name = file.getName();
|
||||
return MediaType.parse(MimeTypeMap.getSingleton().getMimeTypeFromExtension(name.substring(name.lastIndexOf('.') + 1)));
|
||||
}
|
||||
|
||||
public static void loadCustomEmojiInTextView(TextView view){
|
||||
CharSequence _text=view.getText();
|
||||
if(!(_text instanceof Spanned text))
|
||||
public static void loadCustomEmojiInTextView(TextView view) {
|
||||
CharSequence _text = view.getText();
|
||||
if (!(_text instanceof Spanned))
|
||||
return;
|
||||
CustomEmojiSpan[] spans=text.getSpans(0, text.length(), CustomEmojiSpan.class);
|
||||
if(spans.length==0)
|
||||
Spanned text = (Spanned) _text;
|
||||
CustomEmojiSpan[] spans = text.getSpans(0, text.length(), CustomEmojiSpan.class);
|
||||
if (spans.length == 0)
|
||||
return;
|
||||
int emojiSize=V.dp(20);
|
||||
Map<Emoji, List<CustomEmojiSpan>> spansByEmoji=Arrays.stream(spans).collect(Collectors.groupingBy(s->s.emoji));
|
||||
for(Map.Entry<Emoji, List<CustomEmojiSpan>> emoji:spansByEmoji.entrySet()){
|
||||
ViewImageLoader.load(new ViewImageLoader.Target(){
|
||||
int emojiSize = V.dp(20);
|
||||
Map<Emoji, List<CustomEmojiSpan>> spansByEmoji = Arrays.stream(spans).collect(Collectors.groupingBy(s -> s.emoji));
|
||||
for (Map.Entry<Emoji, List<CustomEmojiSpan>> 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<Relationship> resultCallback){
|
||||
public static void confirmToggleBlockUser(Activity activity, String accountID, Account account, boolean currentlyBlocked, Consumer<Relationship> resultCallback) {
|
||||
showConfirmationAlert(activity, activity.getString(currentlyBlocked ? R.string.confirm_unblock_title : R.string.confirm_block_title),
|
||||
activity.getString(currentlyBlocked ? R.string.confirm_unblock : R.string.confirm_block, account.displayName),
|
||||
activity.getString(currentlyBlocked ? R.string.do_unblock : R.string.do_block),
|
||||
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<Relationship> resultCallback){
|
||||
public static void confirmSoftBlockUser(Activity activity, String accountID, Account account, Consumer<Relationship> 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 <T> void updateList(List<T> oldList, List<T> newList, RecyclerView list, RecyclerView.Adapter<?> adapter, BiPredicate<T, T> areItemsSame){
|
||||
public static <T> void updateList(List<T> oldList, List<T> newList, RecyclerView list, RecyclerView.Adapter<?> adapter, BiPredicate<T, T> 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<m.size();i++){
|
||||
MenuItem item=m.getItem(i);
|
||||
ColorStateList iconTint = ColorStateList.valueOf(UiUtils.getThemeColor(context, android.R.attr.textColorSecondary));
|
||||
for (int i = 0; i < m.size(); i++) {
|
||||
MenuItem item = m.getItem(i);
|
||||
SubMenu subMenu = item.getSubMenu();
|
||||
if (subMenu != null) enableMenuIcons(context, subMenu, exclude);
|
||||
if (item.getIcon() == null || Arrays.stream(exclude).anyMatch(id -> 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> 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<AccountSession> sessionConsumer, Consumer<AlertDialog.Builder> transformDialog) {
|
||||
List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts()
|
||||
.stream().filter(s->!s.getID().equals(exceptFor)).collect(Collectors.toList());
|
||||
List<AccountSession> 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<String> 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<Class<? extends Fragment>, Bundle> go) {
|
||||
Uri uri = Uri.parse(url);
|
||||
List<String> 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> 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<Build.VERSION_CODES.TIRAMISU || UiUtils.isMIUI()){ // Android 13+ SystemUI shows its own thing when you put things into the clipboard
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || UiUtils.isMIUI()) { // Android 13+ SystemUI shows its own thing when you put things into the clipboard
|
||||
Toast.makeText(context, R.string.text_copied, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
v.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
|
||||
}
|
||||
|
||||
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){}
|
||||
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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="28dp" android:height="28dp" android:viewportWidth="28" android:viewportHeight="28">
|
||||
<path android:pathData="M18.27 3.21l7.5 7.25c0.073 0.07 0.13 0.154 0.17 0.247C25.98 10.8 26 10.9 26 11c0 0.101-0.02 0.201-0.06 0.294-0.04 0.093-0.097 0.176-0.17 0.246l-7.5 7.25c-0.069 0.068-0.15 0.121-0.239 0.157-0.09 0.037-0.185 0.055-0.281 0.053-0.1 0-0.198-0.02-0.29-0.06-0.136-0.056-0.252-0.152-0.334-0.275C17.044 18.542 17 18.398 17 18.25v-3.74c-6.7 0.27-9.52 4.02-9.64 4.18-0.096 0.126-0.227 0.22-0.378 0.268-0.15 0.048-0.311 0.049-0.462 0.003-0.15-0.049-0.282-0.144-0.375-0.271C6.052 18.562 6 18.408 6 18.25c0-8.02 6.59-10.48 11-10.73V3.75c0-0.147 0.044-0.291 0.126-0.414s0.198-0.218 0.334-0.275c0.135-0.059 0.284-0.075 0.428-0.049 0.144 0.027 0.277 0.096 0.382 0.199zm0.23 10.54v2.71L24.17 11 18.5 5.52v2.73c-0.003 0.199-0.082 0.388-0.223 0.528-0.14 0.14-0.329 0.22-0.527 0.223-0.97 0-8.85 0.22-10.09 7.28 2.876-2.24 6.447-3.401 10.09-3.28 0.198 0.002 0.387 0.082 0.527 0.222s0.22 0.33 0.223 0.527zm4.223 5.473c0.14-0.14 0.329-0.22 0.527-0.223 0.198 0.003 0.387 0.083 0.527 0.223s0.22 0.33 0.223 0.527v0.5c0 1.26-0.5 2.468-1.391 3.36-0.891 0.89-2.1 1.39-3.359 1.39H7.75c-1.26 0-2.468-0.5-3.359-1.39C3.501 22.717 3 21.51 3 20.25V8.75c0-1.26 0.5-2.468 1.391-3.358C5.282 4.5 6.491 4 7.75 4h4.5c0.199 0 0.39 0.08 0.53 0.22C12.921 4.36 13 4.552 13 4.75c0 0.2-0.079 0.39-0.22 0.53-0.14 0.141-0.331 0.22-0.53 0.22h-4.5C6.889 5.503 6.064 5.846 5.455 6.455 4.845 7.065 4.503 7.89 4.5 8.751v11.5c0.003 0.86 0.346 1.686 0.955 2.295S6.889 23.498 7.75 23.5h11.5c0.861-0.002 1.686-0.345 2.295-0.954 0.61-0.61 0.952-1.434 0.955-2.296v-0.5c0.003-0.198 0.082-0.387 0.223-0.527z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
|
@ -29,10 +29,10 @@
|
|||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="24sp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_toStartOf="@id/button"
|
||||
android:layout_marginTop="2sp"
|
||||
android:layout_above="@+id/username"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
|
@ -40,16 +40,16 @@
|
|||
tools:text="User"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/username"
|
||||
android:id="@id/username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20sp"
|
||||
android:layout_below="@id/name"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_toStartOf="@id/button"
|
||||
android:layout_alignBottom="@id/avatar"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
android:paddingBottom="3sp"
|
||||
tools:text="\@user@server"/>
|
||||
|
||||
<View
|
||||
|
|
|
@ -1,79 +1,82 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<org.joinmastodon.android.ui.views.CheckableRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="52dp"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingRight="20dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerInParent="true"
|
||||
android:importantForAccessibility="no"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/radiobtn"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_toStartOf="@+id/extra_btn_wrap"
|
||||
android:layout_alignWithParentIfMissing="true"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:duplicateParentState="true" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@id/extra_btn_wrap"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="12dp"
|
||||
android:visibility="gone">
|
||||
<View
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_gravity="center_vertical|start"
|
||||
android:background="?colorPollVoted" />
|
||||
<ImageButton
|
||||
android:id="@+id/extra_btn"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/sk_open_in_app"
|
||||
android:tooltipText="@string/sk_open_in_app"
|
||||
android:src="@drawable/ic_fluent_open_24_regular"
|
||||
android:background="?android:selectableItemBackground" />
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
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">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/display_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="24dp"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:id="@+id/username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="24dp"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:singleLine="true"
|
||||
android:gravity="center_vertical"
|
||||
android:ellipsize="end"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/add_account"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:text="@string/add_account"
|
||||
android:singleLine="true"
|
||||
android:visibility="gone"
|
||||
android:ellipsize="end"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="0px"
|
||||
android:layout_height="1px"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/current"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:background="@drawable/ic_fluent_checkmark_24_filled"
|
||||
android:backgroundTint="?android:textColorSecondary"
|
||||
android:contentDescription="@string/current_account"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/more"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_fluent_more_vertical_24_regular"
|
||||
android:tint="?android:textColorSecondary"
|
||||
android:contentDescription="@string/more_options"
|
||||
android:background="?android:selectableItemBackgroundBorderless"/>
|
||||
|
||||
</LinearLayout>
|
||||
</org.joinmastodon.android.ui.views.CheckableRelativeLayout>
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:src="@drawable/ic_fluent_share_28_regular"
|
||||
android:scaleType="centerInside"
|
||||
android:adjustViewBounds="true"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:importantForAccessibility="no"
|
||||
tools:ignore="RtlSymmetry" />
|
||||
|
||||
<TextView
|
||||
style="@style/sheet_title"
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/sk_external_share_or_open_title"
|
||||
android:textColor="?colorM3OnSurface" />
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:gravity="center_vertical"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingRight="24dp"
|
||||
android:drawablePadding="24dp"
|
||||
android:drawableTint="?colorM3OnSurfaceVariant"
|
||||
tools:text="List Item"/>
|
|
@ -465,21 +465,7 @@
|
|||
<string name="privacy_policy_subtitle">على الرغم من أن تطبيق ماستدون لا يجمع أي بيانات، فإن الخادم الذي قمت بالتسجيل من خلاله قد تكون له سياسة مختلفة. خذ دقيقة للمراجعة والموافقة على سياسة خصوصية التطبيق ماستدون وسياسة الخصوصية للخادم الخاص بك.</string>
|
||||
<string name="i_agree">أنا مُوافِق</string>
|
||||
<string name="empty_list">هذه القائمة فارغة</string>
|
||||
<string name="confirm_delete_and_redraft">هل أنت متأكد أنك تريد حذف وإعادة صياغة هذا المنشور؟</string>
|
||||
<string name="visibility_unlisted">غير مدرج</string>
|
||||
<string name="list_timelines">القوائم</string>
|
||||
<string name="follow_requests">طلبات المتابعة</string>
|
||||
<string name="instance_signup_closed">هذا الخادم لا يقبل تسجيلات جديدة.</string>
|
||||
<string name="pinned_posts">مدبّس</string>
|
||||
<string name="delete_and_redraft">حذف وإعادة الصياغة</string>
|
||||
<string name="confirm_delete_and_redraft_title">حذف وإعادة صياغة الرسالة</string>
|
||||
<string name="pin_post">تدبيس على الصفحة الشخصية</string>
|
||||
<string name="confirm_pin_post_title">تدبيس الرسالة على الصفحة الشخصية</string>
|
||||
<string name="settings_show_federated_timeline">إظهار الخيط الفديرالي</string>
|
||||
<string name="settings_contribute_fork">المساهمة في Megalodon</string>
|
||||
<string name="accept_follow_request">قبول طلب المتابعة</string>
|
||||
<string name="reject_follow_request">رفض طلب المتابعة</string>
|
||||
<string name="lists_with_user">قوائم بها %s</string>
|
||||
<string name="text_copied">تم النسخ إلى الحافظة</string>
|
||||
<string name="add_bookmark">إضافة إلى الفواصل المرجعية</string>
|
||||
<string name="remove_bookmark">إزالة من الفواصل المرجعية</string>
|
||||
|
|
|
@ -1,2 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="sk_confirm_delete_and_redraft">هل أنت متأكد أنك تريد حذف وإعادة صياغة هذا المنشور؟</string>
|
||||
<string name="sk_visibility_unlisted">غير مدرج</string>
|
||||
<string name="sk_list_timelines">القوائم</string>
|
||||
<string name="sk_follow_requests">طلبات المتابعة</string>
|
||||
<string name="sk_pinned_posts">مدبّس</string>
|
||||
<string name="sk_delete_and_redraft">حذف وإعادة الصياغة</string>
|
||||
<string name="sk_confirm_delete_and_redraft_title">حذف وإعادة صياغة الرسالة</string>
|
||||
<string name="sk_pin_post">تدبيس على الصفحة الشخصية</string>
|
||||
<string name="sk_confirm_pin_post_title">تدبيس الرسالة على الصفحة الشخصية</string>
|
||||
<string name="sk_settings_show_federated_timeline">إظهار الخيط الفديرالي</string>
|
||||
<string name="sk_settings_contribute">المساهمة في Megalodon</string>
|
||||
<string name="sk_accept_follow_request">قبول طلب المتابعة</string>
|
||||
<string name="sk_reject_follow_request">رفض طلب المتابعة</string>
|
||||
<string name="sk_lists_with_user">قوائم بها %s</string>
|
||||
</resources>
|
|
@ -20,11 +20,20 @@
|
|||
<string name="share_toot_title">শেয়ার করুন</string>
|
||||
<string name="settings">সেটিংস</string>
|
||||
<string name="cancel">বাতিল করুন</string>
|
||||
<plurals name="followers">
|
||||
<item quantity="one">জন ফলোয়ার</item>
|
||||
<item quantity="other">জন ফলোয়ারস</item>
|
||||
</plurals>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">পোস্ট</item>
|
||||
<item quantity="other">পোস্টগুলো</item>
|
||||
</plurals>
|
||||
<string name="posts">পোস্টগুলো</string>
|
||||
<string name="media">মিডিয়া</string>
|
||||
<string name="button_follow">ফলো করুন</string>
|
||||
<string name="button_following">ফলো করছেন</string>
|
||||
<string name="edit_profile">প্রোফাইল সংশোধন করুন</string>
|
||||
<string name="mention_user">%s -কে পিং করুন</string>
|
||||
<string name="share_user">%s -কে শেয়ার করুন</string>
|
||||
<string name="mute_user">%s -কে মিউট করুন</string>
|
||||
<string name="unmute_user">%s -কে আনমিউট করুন</string>
|
||||
|
@ -53,6 +62,22 @@
|
|||
<item quantity="one">%d দিন</item>
|
||||
<item quantity="other">%d দিন</item>
|
||||
</plurals>
|
||||
<plurals name="x_seconds_left">
|
||||
<item quantity="one">%d সেকেন্ড বাকি</item>
|
||||
<item quantity="other">%d সেকেন্ড বাকি</item>
|
||||
</plurals>
|
||||
<plurals name="x_minutes_left">
|
||||
<item quantity="one">%d মিনিট বাকি</item>
|
||||
<item quantity="other">%d মিনিট বাকি</item>
|
||||
</plurals>
|
||||
<plurals name="x_hours_left">
|
||||
<item quantity="one">%d ঘণ্টা বাকি</item>
|
||||
<item quantity="other">%d ঘণ্টা বাকি</item>
|
||||
</plurals>
|
||||
<plurals name="x_days_left">
|
||||
<item quantity="one">%d দিন বাকি</item>
|
||||
<item quantity="other">%d দিন বাকি</item>
|
||||
</plurals>
|
||||
<string name="poll_closed">বন্ধ</string>
|
||||
<string name="confirm_mute_title">অ্যাকাউন্টটি মিউট করুন</string>
|
||||
<string name="do_mute">মিউট করুন</string>
|
||||
|
@ -88,6 +113,18 @@
|
|||
<item quantity="one">%d জন ব্যক্তি বলছেন</item>
|
||||
<item quantity="other">%d jon ব্যক্তিরা বলছেন</item>
|
||||
</plurals>
|
||||
<string name="sending_report">রিপোর্ট পাঠানো হচ্ছে…</string>
|
||||
<string name="report_sent_title">রিপোর্ট করার জন্য আপনাকে ধন্যবাদ, আমরা এটি শীঘ্রই দেখব.</string>
|
||||
<string name="report_sent_subtitle">আমরা যতক্ষণে আপনার রিপোর্ট পুনর্বিবেচনা করছি, আপনি %s এর বিরুদ্ধে ব্যবস্থা নিতে পারেন.</string>
|
||||
<string name="back">ফিরে যান</string>
|
||||
<string name="search_communities">সার্ভারের নাম বা লিঙ্ক</string>
|
||||
<string name="instance_rules_title">সার্ভারের নিয়মাবলী</string>
|
||||
<string name="signup_title">অ্যাকাউন্ট তৈরি করুন</string>
|
||||
<string name="display_name">নাম</string>
|
||||
<string name="username">ইউজারনেম</string>
|
||||
<string name="email">ই-মেইল</string>
|
||||
<string name="password">পাসওয়ার্ড</string>
|
||||
<string name="confirm_password">পাসওয়ার্ড নিশ্চিত করুন</string>
|
||||
<!-- %s is the email address -->
|
||||
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
|
@ -96,4 +133,8 @@
|
|||
<!-- %s is server domain -->
|
||||
<!-- Shown in a progress dialog when you tap "follow all" -->
|
||||
<!-- %1$s is server domain, %2$s is email domain. You can reorder these placeholders to fit your language better. -->
|
||||
<string name="welcome_to_mastodon">Mastodon - এ আপনাকে স্বাগত জানাই</string>
|
||||
<string name="welcome_paragraph1">Mastodon হল একটি বিকেন্দ্রীভূত সামাজিক নেটওয়ার্ক, যার মানে কোনো একক কোম্পানি এটিকে নিয়ন্ত্রণ করে না। এটি অনেকগুলি স্বাধীনভাবে চালিত সার্ভারের সমন্বয়ে গঠিত, যেখানে সব সার্ভারগুলি একসাথে সংযুক্ত৷</string>
|
||||
<string name="what_are_servers">সার্ভার কি?</string>
|
||||
<string name="welcome_paragraph2"><![CDATA[প্রতিটি Mastodon অ্যাকাউন্টকে একটি সার্ভারে হোস্ট করা হয় — প্রত্যেকটির নিজস্ব মান, নিয়ম এবং প্রশাসক (অ্যাডমিন) রয়েছে। আপনি যে কোনো সার্ভারই বেছে নিন না কেন তা বিবেচ্য নয়, আপনি যেকোনো সার্ভারের লোকেদের সাথে যোগাযোগ করতে এবং তাদের ফলো করতে পারেন।]]></string>
|
||||
</resources>
|
||||
|
|
|
@ -438,8 +438,11 @@
|
|||
<string name="show">Anzeigen</string>
|
||||
<string name="hide">Ausblenden</string>
|
||||
<string name="join_default_server">%s beitreten</string>
|
||||
<string name="pick_server">Wähle einen anderen Server</string>
|
||||
<string name="signup_or_login">oder</string>
|
||||
<string name="learn_more">Mehr erfahren</string>
|
||||
<string name="welcome_to_mastodon">Willkommen auf Mastodon</string>
|
||||
<string name="welcome_paragraph1">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.</string>
|
||||
<string name="what_are_servers">Was sind Server?</string>
|
||||
<string name="welcome_paragraph2"><![CDATA[Jedes Mastodon-Konto wird auf einem Server gehostet. Jeder Server hat dabei seine eigenen Werte, Regeln und Administrator*innen. Aber egal, für welchen Server Du Dich entscheidest: Du kannst mit Leuten von anderen Servern interagieren und ihnen folgen.]]></string>
|
||||
</resources>
|
||||
|
|
|
@ -275,16 +275,15 @@
|
|||
<string name="sk_settings_confirm_before_reblog">Confirmar antes de volver a publicar</string>
|
||||
<string name="sk_reacted_with">reaccionó con %s</string>
|
||||
<string name="sk_reacted">reaccionó</string>
|
||||
<string name="sk_content_type_unspecified">No especificado</string>
|
||||
<string name="sk_content_type_plain">Texto plano</string>
|
||||
<string name="sk_content_type">Tipo del contenido</string>
|
||||
<string name="sk_content_type_unspecified">Sin especificar</string>
|
||||
<string name="sk_content_type_plain">Texto sin formato</string>
|
||||
<string name="sk_content_type_html">HTML</string>
|
||||
<string name="sk_content_type_markdown">Markdown</string>
|
||||
<string name="sk_content_type_bbcode">BBCode</string>
|
||||
<string name="sk_content_type_mfm">MFM</string>
|
||||
<string name="sk_settings_default_content_type">Tipo de contenido predeterminado</string>
|
||||
<string name="sk_settings_default_content_type_explanation">Esto te permite tener un tipo de contenido preseleccionado cuando creas una nueva publicación, sobreescribiendo el valor en \"Preferencias de publicación\".</string>
|
||||
<string name="sk_content_type">Tipo de contenido</string>
|
||||
<string name="sk_settings_content_types">Activar formato de publicación</string>
|
||||
<string name="sk_settings_content_types_explanation">Permite configurar un tipo de contenido como Markdown cuando creas una publicación. Ten en cuenta que no todas las instancias lo permiten.</string>
|
||||
<string name="sk_settings_show_new_posts_button">Botón \"Ver nuevas publicaciones\"</string>
|
||||
<string name="sk_settings_content_types">Habilitar el formato de los mensajes</string>
|
||||
<string name="sk_settings_default_content_type">Contenido por defecto</string>
|
||||
<string name="sk_settings_content_types_explanation">Permite establecer un tipo de contenido como Markdown al crear una entrada. Ten en cuenta que no todas las instancias lo admiten.</string>
|
||||
<string name="sk_settings_default_content_type_explanation">Permite preseleccionar un tipo de contenido al crear nuevas entradas, anulando el valor establecido en \"Preferencias de publicación\".</string>
|
||||
</resources>
|
|
@ -276,4 +276,21 @@
|
|||
<string name="sk_settings_confirm_before_reblog">Confirmer avant de booster</string>
|
||||
<string name="sk_reacted">a réagi</string>
|
||||
<string name="sk_reacted_with">a réagi avec %s</string>
|
||||
<string name="sk_content_type">Type de contenu</string>
|
||||
<string name="sk_content_type_plain">Texte brut</string>
|
||||
<string name="sk_content_type_html">HTML</string>
|
||||
<string name="sk_content_type_markdown">Markdown</string>
|
||||
<string name="sk_content_type_bbcode">BBCode</string>
|
||||
<string name="sk_content_type_mfm">MFM</string>
|
||||
<string name="sk_settings_content_types">Activer la mise en forme du message</string>
|
||||
<string name="sk_settings_default_content_type_explanation">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\".</string>
|
||||
<string name="sk_content_type_unspecified">Non spécifié</string>
|
||||
<string name="sk_settings_content_types_explanation">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.</string>
|
||||
<string name="sk_settings_default_content_type">Type de contenu par défaut</string>
|
||||
<string name="sk_open_in_app">Ouvrir dans l\'application</string>
|
||||
<string name="sk_external_share_title">Partager avec le compte</string>
|
||||
<string name="sk_external_share_or_open_title">Partager ou ouvrir avec le compte</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Ce sont les publications les plus récentes des personnes présentes dans la bulle de votre serveur Akkoma.</string>
|
||||
<string name="sk_timeline_bubble">Bulle</string>
|
||||
<string name="sk_instance_info_unavailable">Informations sur l\'instance temporairement indisponibles</string>
|
||||
</resources>
|
|
@ -90,7 +90,7 @@
|
|||
<string name="sk_loading_fediverse_resource_title">Buscando no Fediverso</string>
|
||||
<string name="sk_reblog_with_visibility">Impulsar con visibilidade</string>
|
||||
<string name="sk_quote_post">Publicar acerca disto</string>
|
||||
<string name="sk_undo_reblog">Desfacer o impulso</string>
|
||||
<string name="sk_undo_reblog">Desfacer impulso</string>
|
||||
<string name="sk_copy_link_to_post">Copiar ligazón á publicación</string>
|
||||
<string name="sk_loading_resource_on_instance_title">Buscando en %s</string>
|
||||
<string name="sk_open_with_account">Abrir con outra conta</string>
|
||||
|
@ -275,4 +275,21 @@
|
|||
<string name="sk_settings_confirm_before_reblog">Confirma antes de impulsar</string>
|
||||
<string name="sk_reacted_with">Redactado con %s</string>
|
||||
<string name="sk_reacted">redactado</string>
|
||||
<string name="sk_content_type_unspecified">Non especificado</string>
|
||||
<string name="sk_content_type_plain">Texto plano</string>
|
||||
<string name="sk_content_type_html">HTML</string>
|
||||
<string name="sk_content_type_bbcode">BBCode</string>
|
||||
<string name="sk_content_type_mfm">MFM</string>
|
||||
<string name="sk_settings_content_types">Activar o formato de publicacións</string>
|
||||
<string name="sk_settings_default_content_type">Tipo de contido por defecto</string>
|
||||
<string name="sk_settings_default_content_type_explanation">Isto permítelle ter un tipo de contido preseleccionado á hora de crear novas publicacións, sobrescribindo o valor establecido en \"Publicar preferencias\".</string>
|
||||
<string name="sk_content_type">Tipo de contido</string>
|
||||
<string name="sk_content_type_markdown">Markdown</string>
|
||||
<string name="sk_settings_content_types_explanation">Permite configurar un tipo de contido como Markdown ao crear unha publicación. Teña en conta que non tódalas instancias soportan isto.</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Estas son as publicacións máis recentes da xente na burbulla do seu servidor Akkoma.</string>
|
||||
<string name="sk_timeline_bubble">Burbulla</string>
|
||||
<string name="sk_instance_info_unavailable">Información da instancia temporalmente non dispoñible</string>
|
||||
<string name="sk_open_in_app">Abrir na aplicación</string>
|
||||
<string name="sk_external_share_title">Compartir coa conta</string>
|
||||
<string name="sk_external_share_or_open_title">Compartir ou abrir coa conta</string>
|
||||
</resources>
|
|
@ -101,6 +101,8 @@
|
|||
<string name="confirm_block_domain_title">Արգելափակել տիրույթը</string>
|
||||
<string name="confirm_block">Հաստատեք %s-ի արգելափակումը</string>
|
||||
<string name="do_block">Արգելափակել</string>
|
||||
<string name="do_unblock">Արգելաբացել</string>
|
||||
<string name="button_blocked">Արգելափակված</string>
|
||||
<string name="action_vote">Քվեարկել</string>
|
||||
<string name="tap_to_reveal">Սեղմեք տեսնելու համար</string>
|
||||
<string name="delete">Ջնջել</string>
|
||||
|
@ -118,7 +120,9 @@
|
|||
<string name="mentions">Նշումներ</string>
|
||||
<string name="report_title">Զեկուցել %s-ի մասին</string>
|
||||
<string name="report_reason_personal">Ինձ դուր չի գալիս</string>
|
||||
<string name="report_reason_personal_subtitle">Դուք սա չեք ուզում տեսնել</string>
|
||||
<string name="report_reason_spam">Սպամ է</string>
|
||||
<string name="report_reason_spam_subtitle">Վնասակար հղումներ, կեղծում կամ կրկնվող պատասխաններ</string>
|
||||
<string name="report_reason_violation">Խախտում է սերվերի կանոնները</string>
|
||||
<string name="report_reason_violation_subtitle">Գիտեք, որ այն խախտում է կանոնները</string>
|
||||
<string name="report_reason_other">Այլ բան է</string>
|
||||
|
@ -157,6 +161,10 @@
|
|||
<string name="add_image_description">Ավելացնել պատկերի նկարագրություն</string>
|
||||
<string name="edit_image">Խմբագրել նկարը</string>
|
||||
<string name="save">Պահպանել</string>
|
||||
<string name="alt_text_hint">օր․՝ շունը նեղ աչքերով կասկածելի նայում է տեսախցիկին</string>
|
||||
<string name="visibility_public">Հրապարակային</string>
|
||||
<string name="visibility_followers_only">Միայն հետեւողներին</string>
|
||||
<string name="visibility_private">Միայն նշածս մարդկանց</string>
|
||||
<string name="search_all">Բոլորը</string>
|
||||
<string name="search_people">Մարդիկ</string>
|
||||
<string name="recent_searches">Վերջին որոնումներ</string>
|
||||
|
@ -226,6 +234,7 @@
|
|||
<string name="dismiss">Չեղարկել</string>
|
||||
<string name="see_new_posts">Նոր գրառումներ</string>
|
||||
<string name="follows_you">Հետեւում է ձեզ</string>
|
||||
<string name="current_account">Ընթացիկ հաշիվ</string>
|
||||
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
||||
<plurals name="x_followers">
|
||||
<item quantity="one">%,d հետեւորդ</item>
|
||||
|
@ -241,13 +250,32 @@
|
|||
</plurals>
|
||||
<string name="timestamp_via_app">%1$s %2$s-ի միջոցով</string>
|
||||
<string name="time_now">նոր</string>
|
||||
<string name="post_info_reblogs">Տարածումներ</string>
|
||||
<string name="post_info_favorites">Հավանումներ</string>
|
||||
<string name="edit_history">Խմբագրել պատմությունը</string>
|
||||
<string name="last_edit_at_x">Վերջին խմբագրում՝ %s</string>
|
||||
<string name="time_just_now">հենց նոր</string>
|
||||
<plurals name="x_seconds_ago">
|
||||
<item quantity="one">%d վայրկյան առաջ</item>
|
||||
<item quantity="other">%d վայրկյան առաջ</item>
|
||||
</plurals>
|
||||
<plurals name="x_minutes_ago">
|
||||
<item quantity="one">%d րոպե առաջ</item>
|
||||
<item quantity="other">%d րոպե առաջ</item>
|
||||
</plurals>
|
||||
<string name="edit_text_edited">Տեքստը փոփոխվել է</string>
|
||||
<string name="edit_poll_added">Հարցումն ավելացել է</string>
|
||||
<string name="edit_poll_edited">Հարցումը խմբագրվել է</string>
|
||||
<string name="edit_poll_removed">Հարցումը հեռացվել է</string>
|
||||
<string name="edit_media_added">Մեդիան ավելացվել է</string>
|
||||
<string name="edit_media_removed">Մեդիան հեռացվել է</string>
|
||||
<string name="edit_marked_sensitive">Նշվել է որպես դյուրազգաց</string>
|
||||
<string name="file_size_bytes">%d բայթ</string>
|
||||
<string name="file_size_kb">%.2f ԿԲ</string>
|
||||
<string name="file_size_mb">%.2f ՄԲ</string>
|
||||
<string name="file_size_gb">%.2f ԳԲ</string>
|
||||
<string name="file_upload_progress">%1$s՝ %2$s-ից</string>
|
||||
<string name="file_upload_time_remaining">մնացել է %s</string>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<string name="update_available">%s տարբերակը պատրաստ է ներբեռնման։</string>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
|
@ -255,6 +283,7 @@
|
|||
<!-- %s is file size -->
|
||||
<string name="download_update">Ներբեռնել (%s)</string>
|
||||
<string name="install_update">Տեղադրել</string>
|
||||
<string name="privacy_policy_title">Ձեր գաղտնիությունը</string>
|
||||
<string name="i_agree">Համաձայն եմ</string>
|
||||
<string name="empty_list">Ցանկը դատարկ է</string>
|
||||
<string name="instance_signup_closed">Սպասարկիչը գրանցումներ չի ընդունում։</string>
|
||||
|
@ -274,9 +303,25 @@
|
|||
<string name="not_accepting_new_members">Չի ընդունում նոր անդամներ</string>
|
||||
<string name="signup_passwords_dont_match">Գաղտնաբառը չի համապատասխանում</string>
|
||||
<string name="pick_server_for_me">Ընտրել իմ համար</string>
|
||||
<string name="privacy_policy_explanation">Կարճ ասած՝ մենք ոչինչ չենք հավաքում։</string>
|
||||
<!-- %s is server domain -->
|
||||
<string name="profile_bio">Կենսագրություն</string>
|
||||
<!-- Shown in a progress dialog when you tap "follow all" -->
|
||||
<!-- %1$s is server domain, %2$s is email domain. You can reorder these placeholders to fit your language better. -->
|
||||
<string name="signup_email_domain_blocked">%1$s-ը %2$s-ից գրանցումներ չի ընդունում։ Փորձեք ուրիշը կամ <a>ընտրեք ուրիշ սերվեր</a>։</string>
|
||||
<string name="signup_username_taken">Օգտանունը զբաղված է։</string>
|
||||
<string name="spoiler_show">Ցույց տալ</string>
|
||||
<string name="spoiler_hide">Թաքցնել</string>
|
||||
<string name="save_changes">Պահպանել</string>
|
||||
<string name="profile_timeline">Հոսք</string>
|
||||
<string name="view_all">Դիտել բոլորը</string>
|
||||
<string name="profile_endorsed_accounts">Հաշիվներ</string>
|
||||
<string name="verified_link">Հաստատված հղում</string>
|
||||
<string name="show">Ցույց տալ</string>
|
||||
<string name="hide">Թաքցնել</string>
|
||||
<string name="join_default_server">Միանալ %s-ին</string>
|
||||
<string name="pick_server">Ընտրել ուրիշ սերվեր</string>
|
||||
<string name="signup_or_login">կամ</string>
|
||||
<string name="learn_more">Իմանալ ավելին</string>
|
||||
<string name="welcome_to_mastodon">Բարի գալուստ Մաստոդոն</string>
|
||||
<string name="welcome_paragraph1">Մաստոդոնը ապակենտրոնացված սոցցանց է, այսինքն՝ այն չի պատկանում մի ընկերության։ Այն բաղկացած է բազմաթիվ անկախ և կապակցված սերվերներից։</string>
|
||||
|
|
|
@ -276,4 +276,21 @@
|
|||
<string name="sk_settings_confirm_before_reblog">Konfirmasi sebelum membagikan ulang</string>
|
||||
<string name="sk_reacted_with">bereaksi dengan %s</string>
|
||||
<string name="sk_reacted">bereaksi</string>
|
||||
<string name="sk_content_type_plain">Teks biasa</string>
|
||||
<string name="sk_content_type_html">HTML</string>
|
||||
<string name="sk_content_type_markdown">Markdown</string>
|
||||
<string name="sk_content_type_bbcode">BBCode</string>
|
||||
<string name="sk_content_type_mfm">MFM</string>
|
||||
<string name="sk_settings_content_types">Aktifkan pemformatan kiriman</string>
|
||||
<string name="sk_settings_default_content_type">Jenis konten bawaan</string>
|
||||
<string name="sk_settings_default_content_type_explanation">Ini memungkinkan Anda untuk menerapkan jenis konten yang sudah ditentukan saat membuat kiriman baru, menimpa nilai yang ditetapkan dalam “Preferensi kiriman”.</string>
|
||||
<string name="sk_content_type">Jenis konten</string>
|
||||
<string name="sk_content_type_unspecified">Tidak ditentukan</string>
|
||||
<string name="sk_settings_content_types_explanation">Memperbolehkan menetapkan jenis konten seperti Markdown ketika membuat kiriman. Perlu diingat bahwa tidak semua server mendukung ini.</string>
|
||||
<string name="sk_open_in_app">Buka dalam aplikasi</string>
|
||||
<string name="sk_external_share_title">Bagikan dengan akun</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Ini adalah kiriman yamg paling terkini oleh orang-orang dalam gelembung server Akkoma Anda.</string>
|
||||
<string name="sk_timeline_bubble">Gelembung</string>
|
||||
<string name="sk_instance_info_unavailable">Info server sementara tidak tersedia</string>
|
||||
<string name="sk_external_share_or_open_title">Bagikan atau buka dengan akun</string>
|
||||
</resources>
|
|
@ -8,14 +8,14 @@
|
|||
<string name="error">Fout</string>
|
||||
<string name="not_a_mastodon_instance">%s lijkt geen Mastodonserver te zijn.</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="preparing_auth">Verificatie aan het voorbereiden…</string>
|
||||
<string name="preparing_auth">Verificatie voorbereiden…</string>
|
||||
<string name="finishing_auth">Verificatie afronden…</string>
|
||||
<string name="user_boosted">%s boostte</string>
|
||||
<string name="in_reply_to">Als reactie op %s</string>
|
||||
<string name="notifications">Meldingen</string>
|
||||
<string name="user_followed_you">volgt jou</string>
|
||||
<string name="user_sent_follow_request">heeft je een volgverzoek gestuurd</string>
|
||||
<string name="user_favorited">markeerde als favoriet</string>
|
||||
<string name="user_sent_follow_request">wil jou graag volgen</string>
|
||||
<string name="user_favorited">markeerde bericht als favoriet</string>
|
||||
<string name="notification_boosted">boostte jouw bericht</string>
|
||||
<string name="poll_ended">poll is beëindigd</string>
|
||||
<string name="time_seconds">%ds</string>
|
||||
|
@ -47,20 +47,20 @@
|
|||
<string name="button_follow">Volgen</string>
|
||||
<string name="button_following">Volgend</string>
|
||||
<string name="edit_profile">Profiel bewerken</string>
|
||||
<string name="mention_user">Vermelden</string>
|
||||
<string name="mention_user">%s vermelden</string>
|
||||
<string name="share_user">%s delen</string>
|
||||
<string name="mute_user">Negeren</string>
|
||||
<string name="unmute_user">Niet langer negeren</string>
|
||||
<string name="block_user">Blokkeren</string>
|
||||
<string name="unblock_user">Deblokkeren</string>
|
||||
<string name="mute_user">%s negeren</string>
|
||||
<string name="unmute_user">%s niet langer negeren</string>
|
||||
<string name="block_user">%s blokkeren</string>
|
||||
<string name="unblock_user">%s deblokkeren</string>
|
||||
<string name="report_user">%s rapporteren</string>
|
||||
<string name="block_domain">Blokkeren</string>
|
||||
<string name="unblock_domain">Deblokkeren</string>
|
||||
<string name="block_domain">%s blokkeren</string>
|
||||
<string name="unblock_domain">%s deblokkeren</string>
|
||||
<plurals name="x_posts">
|
||||
<item quantity="one">%,d bericht</item>
|
||||
<item quantity="other">%,d berichten</item>
|
||||
</plurals>
|
||||
<string name="profile_joined">Geregistreerd op</string>
|
||||
<string name="profile_joined">Geregistreerd</string>
|
||||
<string name="done">Klaar</string>
|
||||
<string name="loading">Aan het laden…</string>
|
||||
<string name="field_label">Label</string>
|
||||
|
@ -98,23 +98,23 @@
|
|||
<item quantity="other">%d dagen resterend</item>
|
||||
</plurals>
|
||||
<plurals name="x_voters">
|
||||
<item quantity="one">%,d persoon</item>
|
||||
<item quantity="other">%,d mensen</item>
|
||||
<item quantity="one">%,d stem</item>
|
||||
<item quantity="other">%,d stemmen</item>
|
||||
</plurals>
|
||||
<string name="poll_closed">Gesloten</string>
|
||||
<string name="confirm_mute_title">Account negeren</string>
|
||||
<string name="confirm_mute">Bevestig om %s te negeren</string>
|
||||
<string name="confirm_mute">Het negeren van %s bevestigen</string>
|
||||
<string name="do_mute">Negeren</string>
|
||||
<string name="confirm_unmute_title">Account niet langer negeren</string>
|
||||
<string name="confirm_unmute">Bevestig om %s niet langer te negeren</string>
|
||||
<string name="confirm_unmute">Het niet langer negeren van %s bevestigen</string>
|
||||
<string name="do_unmute">Niet langer negeren</string>
|
||||
<string name="confirm_block_title">Account blokkeren</string>
|
||||
<string name="confirm_block_domain_title">Domein blokkeren</string>
|
||||
<string name="confirm_block">Bevestig om %s te blokkeren</string>
|
||||
<string name="confirm_block">Het blokkeren van %s bevestigen</string>
|
||||
<string name="do_block">Blokkeren</string>
|
||||
<string name="confirm_unblock_title">Account deblokkeren</string>
|
||||
<string name="confirm_unblock_domain_title">Domein deblokkeren</string>
|
||||
<string name="confirm_unblock">Bevestig om %s te deblokkeren</string>
|
||||
<string name="confirm_unblock">Het deblokkeren van %s bevestigen</string>
|
||||
<string name="do_unblock">Deblokkeren</string>
|
||||
<string name="button_muted">Genegeerd</string>
|
||||
<string name="button_blocked">Geblokkeerd</string>
|
||||
|
@ -175,7 +175,7 @@
|
|||
<string name="instance_catalog_subtitle">Kies een server gebaseerd op je interesses, regio, of voor algemene doeleinden. Je kunt nog steeds met iedereen in contact komen, ongeacht de server.</string>
|
||||
<string name="search_communities">Servernaam of URL</string>
|
||||
<string name="instance_rules_title">Serverregels</string>
|
||||
<string name="instance_rules_subtitle">Door verder te gaan, ga je akkoord met het volgen van de regels ingesteld door de %s-moderators.</string>
|
||||
<string name="instance_rules_subtitle">Door verder te gaan, ga je akkoord met het volgen van de regels ingesteld door de %s-moderatoren.</string>
|
||||
<string name="signup_title">Account registreren</string>
|
||||
<string name="edit_photo">bewerken</string>
|
||||
<string name="display_name">Naam</string>
|
||||
|
@ -193,14 +193,14 @@
|
|||
<string name="category_games">Games</string>
|
||||
<string name="category_general">Algemeen</string>
|
||||
<string name="category_journalism">Journalistiek</string>
|
||||
<string name="category_lgbt">LGBT</string>
|
||||
<string name="category_lgbt">LHBTQIA+</string>
|
||||
<string name="category_music">Muziek</string>
|
||||
<string name="category_regional">Regionaal</string>
|
||||
<string name="category_tech">Tech</string>
|
||||
<string name="confirm_email_title">Controleer je Postvak In</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="confirm_email_subtitle">Klik op de link die we je hebben gestuurd om %s te verifiëren. We wachten op je.</string>
|
||||
<string name="confirm_email_didnt_get">Geen link gekregen?</string>
|
||||
<string name="confirm_email_didnt_get">Geen verificatielink ontvangen?</string>
|
||||
<string name="resend">Opnieuw verzenden</string>
|
||||
<string name="open_email_app">E-mail-app openen</string>
|
||||
<string name="resent_email">Bevestigingsmail verzonden</string>
|
||||
|
@ -211,7 +211,7 @@
|
|||
<string name="edit_image">Afbeelding bewerken</string>
|
||||
<string name="save">Opslaan</string>
|
||||
<string name="add_alt_text">Alt-tekst toevoegen</string>
|
||||
<string name="alt_text_subtitle">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.</string>
|
||||
<string name="alt_text_subtitle">Alt-tekst beschrijft jouw foto\'s voor blinde of slechtziende mensen. Probeer alleen genoeg details toe te voegen om de context te begrijpen.</string>
|
||||
<string name="alt_text_hint">bijv.: ‘een hond die met versmalde ogen verdacht rondkijkt naar de camera’.</string>
|
||||
<string name="visibility_public">Openbaar</string>
|
||||
<string name="visibility_followers_only">Alleen volgers</string>
|
||||
|
@ -240,17 +240,17 @@
|
|||
<string name="theme_dark">Donker</string>
|
||||
<string name="theme_true_black">Echt zwart gebruiken</string>
|
||||
<string name="settings_behavior">Gedrag</string>
|
||||
<string name="settings_gif">Geanimeerde avatars en emoji\'s afspelen</string>
|
||||
<string name="settings_gif">Geanimeerde profielfoto\'s en emoji\'s afspelen</string>
|
||||
<string name="settings_custom_tabs">In-appbrowser gebruiken</string>
|
||||
<string name="settings_notifications">Meldingen</string>
|
||||
<string name="notify_me_when">Melding tonen wanneer</string>
|
||||
<string name="notify_anyone">iedereen</string>
|
||||
<string name="notify_anyone">iemand</string>
|
||||
<string name="notify_follower">een volger</string>
|
||||
<string name="notify_followed">iemand die ik volg</string>
|
||||
<string name="notify_none">niemand</string>
|
||||
<string name="notify_favorites">Mijn bericht als favoriet markeert</string>
|
||||
<string name="notify_follow">Mij volgt</string>
|
||||
<string name="notify_reblog">Boost mijn bericht</string>
|
||||
<string name="notify_reblog">Mijn bericht boost</string>
|
||||
<string name="notify_mention">Mij vermeldt</string>
|
||||
<string name="settings_boring">De saaie zone</string>
|
||||
<string name="settings_account">Accountinstellingen</string>
|
||||
|
@ -272,7 +272,7 @@
|
|||
<string name="new_post">Nieuw bericht</string>
|
||||
<string name="button_reply">Reageren</string>
|
||||
<string name="button_reblog">Boosten</string>
|
||||
<string name="button_favorite">Toevoegen aan favorieten</string>
|
||||
<string name="button_favorite">Als favoriet markeren</string>
|
||||
<string name="button_share">Delen</string>
|
||||
<string name="media_no_description">Media zonder beschrijving</string>
|
||||
<string name="add_media">Media toevoegen</string>
|
||||
|
@ -286,7 +286,7 @@
|
|||
<string name="unfollowed_user">%s ontvolgd</string>
|
||||
<string name="followed_user">Je volgt %s nu</string>
|
||||
<string name="following_user_requested">Je volgverzoek is aan %s verstuurd</string>
|
||||
<string name="open_in_browser">Openen in browser</string>
|
||||
<string name="open_in_browser">In browser openen</string>
|
||||
<string name="hide_boosts_from_user">Boosts van %s verbergen</string>
|
||||
<string name="show_boosts_from_user">Boosts van %s tonen</string>
|
||||
<string name="signup_reason">Waarom wil je je hier registreren?</string>
|
||||
|
@ -309,7 +309,7 @@
|
|||
<string name="trending_links_info_banner">Dit zijn nieuwsartikelen die populair zijn op jouw Mastodon-server.</string>
|
||||
<string name="local_timeline_info_banner">Dit zijn de meest recente berichten van mensen die ook op jouw Mastodon-server zitten.</string>
|
||||
<string name="dismiss">Sluiten</string>
|
||||
<string name="see_new_posts">Nieuwe berichten bekijken</string>
|
||||
<string name="see_new_posts">Nieuwe berichten</string>
|
||||
<string name="load_missing_posts">Resterende berichten laden</string>
|
||||
<string name="follow_back">Terugvolgen</string>
|
||||
<string name="button_follow_pending">In afwachting</string>
|
||||
|
@ -383,7 +383,7 @@
|
|||
<string name="download_update">Downloaden (%s)</string>
|
||||
<string name="install_update">Installeren</string>
|
||||
<string name="privacy_policy_title">Jouw privacy</string>
|
||||
<string name="privacy_policy_subtitle">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.</string>
|
||||
<string name="privacy_policy_subtitle">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.</string>
|
||||
<string name="i_agree">Ik ga akkoord</string>
|
||||
<string name="empty_list">Deze lijst is leeg</string>
|
||||
<string name="instance_signup_closed">Deze server accepteert geen nieuwe registraties.</string>
|
||||
|
@ -416,10 +416,10 @@
|
|||
<string name="profile_setup_explanation">Je kunt tot vier profielvelden toevoegen voor alles wat je wilt. Locatie, links, voornaamwoorden – de mogelijkheden zijn eindeloos.</string>
|
||||
<string name="popular_on_mastodon">Populair op Mastodon</string>
|
||||
<string name="follow_all">Volg iedereen</string>
|
||||
<string name="server_rules_disagree">Oneens</string>
|
||||
<string name="server_rules_disagree">Ik ga niet akkoord</string>
|
||||
<string name="privacy_policy_explanation">TL;DR: We verzamelen of verwerken niets.</string>
|
||||
<!-- %s is server domain -->
|
||||
<string name="server_policy_disagree">Oneens met %s</string>
|
||||
<string name="server_policy_disagree">Niet akkoord met %s</string>
|
||||
<string name="profile_bio">Bio</string>
|
||||
<!-- Shown in a progress dialog when you tap "follow all" -->
|
||||
<string name="sending_follows">Gevolgde gebruikers…</string>
|
||||
|
@ -428,20 +428,21 @@
|
|||
<string name="signup_username_taken">Deze gebruikersnaam wordt al gebruikt.</string>
|
||||
<string name="spoiler_show">Alsnog tonen</string>
|
||||
<string name="spoiler_hide">Opnieuw verbergen</string>
|
||||
<string name="poll_multiple_choice">Selecteer een of meer</string>
|
||||
<string name="poll_multiple_choice">Maak een of meerdere keuzes</string>
|
||||
<string name="save_changes">Wijzigingen opslaan</string>
|
||||
<string name="profile_featured">Aanbevolen</string>
|
||||
<string name="profile_timeline">Tijdlijn</string>
|
||||
<string name="view_all">Alles bekijken</string>
|
||||
<string name="profile_endorsed_accounts">Accounts</string>
|
||||
<string name="verified_link">Geverifieerde koppeling</string>
|
||||
<string name="verified_link">Geverifieerde link</string>
|
||||
<string name="show">Tonen</string>
|
||||
<string name="hide">Verbergen</string>
|
||||
<string name="join_default_server">Deelnemen aan %s</string>
|
||||
<string name="join_default_server">Registreren op %s</string>
|
||||
<string name="pick_server">Kies een andere server</string>
|
||||
<string name="signup_or_login">of</string>
|
||||
<string name="learn_more">Meer informatie</string>
|
||||
<string name="welcome_to_mastodon">Welkom bij Mastodon</string>
|
||||
<string name="welcome_paragraph1">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.</string>
|
||||
<string name="welcome_paragraph1">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.</string>
|
||||
<string name="what_are_servers">Wat zijn servers?</string>
|
||||
<string name="welcome_paragraph2"><![CDATA[Elk Mastodon account wordt gehost op een server - elk met zijn eigen waarden, regels & admins. Het maakt niet uit welke server je kiest, je kunt mensen op elke server volgen en ermee communiceren.]]></string>
|
||||
<string name="welcome_paragraph2"><![CDATA[Elk Mastodonaccount wordt op een server gehost - elk met diens eigen waarden, regels en beheerders. Het maakt niet uit welke server je kiest, je kunt mensen op elke server volgen en ermee communiceren.]]></string>
|
||||
</resources>
|
||||
|
|
|
@ -251,8 +251,7 @@
|
|||
<string name="sk_settings_hide_interaction">Verberg interactie knoppen</string>
|
||||
<string name="sk_follow_as">Volgen met ander account</string>
|
||||
<string name="sk_followed_as">Gevolgd met %s</string>
|
||||
<string name="sk_settings_reply_visibility">Reactie zichtbaarheid</string>
|
||||
<string name="sk_quoting_user">Quoting %s</string>
|
||||
<string name="sk_settings_reply_visibility">Zichtbaarheid reactie</string>
|
||||
<string name="sk_settings_reply_visibility_all">Alle reacties</string>
|
||||
<string name="sk_changelog">Changelog</string>
|
||||
<string name="sk_settings_show_new_posts_button">Knop \"Toon nieuwe berichten\"</string>
|
||||
</resources>
|
|
@ -2,80 +2,47 @@
|
|||
<resources>
|
||||
<string name="sk_app_name">Megalodon</string>
|
||||
<string name="sk_pinned_posts">Fixado</string>
|
||||
<string name="sk_content_type_unspecified">Não especifícado</string>
|
||||
<string name="sk_content_type_plain">Texto simples</string>
|
||||
<string name="sk_content_type_html">HTML</string>
|
||||
<string name="sk_content_type_markdown">Markdown</string>
|
||||
<string name="sk_content_type_bbcode">BBCode</string>
|
||||
<string name="sk_content_type_mfm">MFM</string>
|
||||
<string name="sk_settings_default_content_type">Tipo de conteúdo predefinido</string>
|
||||
<string name="sk_confirm_pin_post">Queres fixar esta publicação no teu perfil\?</string>
|
||||
<string name="sk_confirm_unpin_post_title">Desfixar publicação do perfil</string>
|
||||
<string name="sk_settings_load_new_posts">Carregar novas publicações automaticamente</string>
|
||||
<string name="sk_user_post_notifications_on">Ligar notificações da publicação para %s</string>
|
||||
<string name="sk_disable_marquee">Desativar deslocação de texto nas barras de título</string>
|
||||
<string name="sk_poll_allow_multiple">Permitir escolha múltipla</string>
|
||||
<string name="sk_language_name">%1$s (%2$s)</string>
|
||||
<string name="sk_confirm_clear_recent_languages">De certeza que queres apagar os teus idiomas usados recentemente\?</string>
|
||||
<string name="sk_settings_auth">Configurações de segurança</string>
|
||||
<string name="sk_settings_rules">Regras</string>
|
||||
<string name="sk_delete_notification_confirm">De certeza que queres apagar esta notificação\?</string>
|
||||
<string name="sk_settings_publish_button_text_title">Personalizar o texto do botão de publicar</string>
|
||||
<string name="sk_reblog_with_visibility">Partilhar com visibilidade</string>
|
||||
<string name="sk_confirm_delete_draft">De certeza que queres apagar este rascunho\?</string>
|
||||
<string name="sk_scheduled_too_soon_title">Hora agendada é demasiado cedo</string>
|
||||
<string name="sk_schedule_post">Publicação agendada</string>
|
||||
<string name="sk_settings_about_instance">Sobre a instância</string>
|
||||
<string name="sk_remove_follower_confirm">Remover %s como seguidor bloqueando e desbloqueando imediatamente\?</string>
|
||||
<string name="sk_notify_posts_info_banner">Se ativares as notificações de publicação para algumas pessoas as suas publicações vão aparecer aqui.</string>
|
||||
<string name="sk_icon_language">Idioma</string>
|
||||
<string name="sk_icon_aperture">Abertura</string>
|
||||
<string name="sk_icon_briefcase">Pasta</string>
|
||||
<string name="sk_edit_timelines">Editar linhas do tempo</string>
|
||||
<string name="sk_save_draft_message">Queres guardar as alterações a este rascunho ou publicar agora\?</string>
|
||||
<string name="sk_settings_hide_fab">Esconder automaticamente o botão Escrever</string>
|
||||
<string name="sk_content_type">Tipo de conteúdo</string>
|
||||
<string name="sk_settings_content_types">Permitir formatação da publicação</string>
|
||||
<string name="sk_settings_content_types_explanation">Permitir configurar um tipo de conteúdo como Markdown ao criar uma publicação. Lembra-te que nem todas as instâncias suportam isto.</string>
|
||||
<string name="sk_settings_default_content_type_explanation">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\".</string>
|
||||
<string name="sk_delete_and_redraft">Apagar e reescrever</string>
|
||||
<string name="sk_confirm_delete_and_redraft_title">Apagar e reescrever publicação</string>
|
||||
<string name="sk_confirm_delete_and_redraft">De certeza que querem apagar e reescrever esta publicação\?</string>
|
||||
<string name="sk_pin_post">Fixar no perfil</string>
|
||||
<string name="sk_confirm_pin_post_title">Fixar publicação no perfil</string>
|
||||
<string name="sk_pinning">A fixar publicação…</string>
|
||||
<string name="sk_unpin_post">Desfixar do perfil</string>
|
||||
<string name="sk_confirm_unpin_post">De certeza que queres desfixar esta publicação\?</string>
|
||||
<string name="sk_filtered">Filtrado: %s</string>
|
||||
<string name="sk_expand">Expandir</string>
|
||||
<string name="sk_collapse">Esconder</string>
|
||||
<string name="sk_settings_collapse_long_posts">Esconder publicações muito grandes</string>
|
||||
<string name="sk_unfinished_attachments">Reparar anexos\?</string>
|
||||
<string name="sk_translate_post">Traduzir</string>
|
||||
<string name="sk_translate_show_original">Mostrar original</string>
|
||||
<string name="sk_post_language">Idioma: %s</string>
|
||||
<string name="sk_available_languages">Idiomas disponíveis</string>
|
||||
<string name="sk_unpinning">A desfixar publicação…</string>
|
||||
<string name="sk_image_description">Descrição da imagem</string>
|
||||
<string name="sk_visibility_unlisted">Não listado</string>
|
||||
<string name="sk_settings_show_replies">Mostrar respostas</string>
|
||||
<string name="sk_quoting_user">Citar %s</string>
|
||||
<string name="sk_settings_reply_visibility">Visibilidade de resposta</string>
|
||||
<string name="sk_settings_reply_visibility_all">Todas as respostas</string>
|
||||
<string name="sk_settings_reply_visibility_following">Responder aos meus seguidores</string>
|
||||
<string name="sk_settings_reply_visibility_self">Respostas a mim</string>
|
||||
<string name="sk_settings_show_boosts">Mostrar partilhas</string>
|
||||
<string name="sk_settings_show_interaction_counts">Mostrar contagem de interações</string>
|
||||
<string name="sk_settings_app_version">Megalodon v%1$s (%2$d)</string>
|
||||
<string name="sk_mark_media_as_sensitive">Marcar conteúdo como sensível</string>
|
||||
<string name="sk_user_post_notifications_off">Desligar notificações para publicação para %s</string>
|
||||
<string name="sk_settings_load_new_posts">Carregar publicações novas automaticamente</string>
|
||||
<string name="sk_unpin_post">Desfixar do perfil</string>
|
||||
<string name="sk_delete_and_redraft">Apagar e reescrever</string>
|
||||
<string name="sk_confirm_delete_and_redraft_title">Apagar e reescrever publicação</string>
|
||||
<string name="sk_confirm_delete_and_redraft">De certeza que quer apagar e reescrever a publicação\?</string>
|
||||
<string name="sk_pin_post">Fixar no perfil</string>
|
||||
<string name="sk_confirm_pin_post_title">Fixar publicação no perfil</string>
|
||||
<string name="sk_confirm_pin_post">Queres fixar esta publicação ao teu perfil\?</string>
|
||||
<string name="sk_pinning">A fixar publicação…</string>
|
||||
<string name="sk_federated_timeline">Federação</string>
|
||||
<string name="sk_federated_timeline_info_banner">Estas são as publicações mais recentes das pessoas na tua federação.</string>
|
||||
<string name="sk_update_available">Megalodon %s está pronto a descarregar.</string>
|
||||
<string name="sk_update_ready">Megalodon %s descarregado e pronto a instalar.</string>
|
||||
<string name="sk_check_for_update">A verificar atualizações</string>
|
||||
<string name="sk_no_update_available">Sem atualizações disponíveis</string>
|
||||
<string name="sk_list_timelines">Listas</string>
|
||||
<string name="sk_update_available">Megalodon %s pronto a descarregar.</string>
|
||||
<string name="sk_follow_requests">Pedidos para seguir</string>
|
||||
<string name="sk_accept_follow_request">Aceitar pedido para seguir</string>
|
||||
<string name="sk_reject_follow_request">Rejeitar pedido para seguir</string>
|
||||
<string name="sk_lists_with_user">Listas com %s</string>
|
||||
<string name="sk_settings_always_reveal_content_warnings">Mostrar sempre avisos de conteúdo</string>
|
||||
<string name="sk_example_domain">exemplo.social</string>
|
||||
<string name="sk_disable_marquee">Desligar deslocamento de texto nas barras de título</string>
|
||||
<string name="sk_settings_contribute">Contribuir para o Megalodon</string>
|
||||
<string name="sk_settings_show_federated_timeline">Mostrar linha do tempo federada</string>
|
||||
<string name="sk_notification_type_status">Publicações</string>
|
||||
<string name="sk_notify_posts">Notificações da publicação</string>
|
||||
<string name="sk_bookmark_as">Guardar com outra conta</string>
|
||||
<string name="sk_bookmarked_as">Guardado como %s</string>
|
||||
<string name="sk_already_bookmarked">Já guardado</string>
|
||||
<string name="sk_favorite_as">Adicionar aos favoritos com outra conta</string>
|
||||
<string name="sk_settings_profile">Configurar perfil</string>
|
||||
<string name="sk_settings_filters">Configurar filtros</string>
|
||||
<string name="sk_settings_auth">Configurações de segurança</string>
|
||||
<string name="sk_settings_rules">Regras</string>
|
||||
<string name="sk_settings_about">Sobre a aplicação</string>
|
||||
<string name="sk_settings_donate">Doar</string>
|
||||
<string name="sk_tabs_disable_swipe">Desligar deslocação entre separadores</string>
|
||||
<string name="sk_settings_color_palette">Paleta de cores</string>
|
||||
<string name="sk_color_palette_material3">Sistema</string>
|
||||
<string name="sk_color_palette_pink">Rosa</string>
|
||||
|
@ -85,107 +52,52 @@
|
|||
<string name="sk_color_palette_brown">Castanho</string>
|
||||
<string name="sk_color_palette_red">Vermelho</string>
|
||||
<string name="sk_color_palette_yellow">Amarelo</string>
|
||||
<string name="sk_translate_post">Traduzir</string>
|
||||
<string name="sk_translate_show_original">Mostrar original</string>
|
||||
<string name="sk_translated_using">Traduzido com %s</string>
|
||||
<string name="sk_post_language">Idioma: %s</string>
|
||||
<string name="sk_available_languages">Idiomas disponíveis</string>
|
||||
<string name="sk_clear_recent_languages">Apagar idiomas usados recentemente</string>
|
||||
<string name="sk_welcome_title">Bem-vindo/a!</string>
|
||||
<string name="sk_welcome_text">Saudações do Tubarão! Para começar introduz o domínio da tua instância nativa abaixo.</string>
|
||||
<string name="sk_example_domain">exemplo.social</string>
|
||||
<string name="sk_tabs_disable_swipe">Desativar deslizar entre separadores</string>
|
||||
<string name="sk_settings_profile">Configurar perfil</string>
|
||||
<string name="sk_settings_posting">Preferências de notificação</string>
|
||||
<string name="sk_settings_filters">Configurar filtros</string>
|
||||
<string name="sk_settings_about">Sobre a aplicação</string>
|
||||
<string name="sk_settings_donate">Doar</string>
|
||||
<string name="sk_delete_notification">Apagar notificação</string>
|
||||
<string name="sk_delete_notification_confirm_action">Apagar notificação</string>
|
||||
<string name="sk_clear_all_notifications">Apagar todas as notificações</string>
|
||||
<string name="sk_clear_all_notifications_confirm_action">Apagar tudo</string>
|
||||
<string name="sk_clear_all_notifications_confirm">De certeza que queres apagar todas as notificações\?</string>
|
||||
<string name="sk_enable_delete_notifications">Ativar apagar notificações</string>
|
||||
<string name="sk_delete_notification_confirm">De certeza que quer apagar esta notificação\?</string>
|
||||
<string name="sk_enable_delete_notifications">Ligar apagar notificações</string>
|
||||
<string name="sk_settings_publish_button_text">Texto do botão de publicar</string>
|
||||
<string name="sk_settings_translate_only_opened">Traduzir apenas publicações abertas</string>
|
||||
<string name="sk_settings_translation_availability_note_available">%s suporta tradução!</string>
|
||||
<string name="sk_settings_translation_availability_note_unavailable">%s não parece suportar tradução.</string>
|
||||
<string name="sk_clear_all_notifications_confirm_action">Apagar tudo</string>
|
||||
<string name="sk_clear_all_notifications_confirm">De certeza que quer apagar todas as notificações\?</string>
|
||||
<string name="sk_loading_fediverse_resource_title">A procurar no Fediverso</string>
|
||||
<string name="sk_loading_resource_on_instance_title">A procurar em %s</string>
|
||||
<string name="sk_quote_post">Escrever sobre isto</string>
|
||||
<string name="sk_hashtags_you_follow">Hashtags que segues</string>
|
||||
<string name="sk_copy_link_to_post">Copiar ligação para a publicação</string>
|
||||
<string name="sk_reblog_with_visibility">Partilhar com visibilidade</string>
|
||||
<string name="sk_quote_post">Publicar sobre isto</string>
|
||||
<string name="sk_open_with_account">Abrir com outra conta</string>
|
||||
<string name="sk_resource_not_found">O recurso não foi encontrado</string>
|
||||
<string name="sk_bookmark_as">Guardar com outra conta</string>
|
||||
<string name="sk_bookmarked_as">Guardado como %s</string>
|
||||
<string name="sk_already_bookmarked">Já guardado</string>
|
||||
<string name="sk_favorite_as">Adicionar aos favoritos com outra conta</string>
|
||||
<string name="sk_favorited_as">Adicionado aos favoritos como %s</string>
|
||||
<string name="sk_already_favorited">Já adicionado aos favoritos</string>
|
||||
<string name="sk_reply_as">Responder com outra conta</string>
|
||||
<string name="sk_settings_uniform_icon_for_notifications">Ícone uniforme para todas as notificações</string>
|
||||
<string name="sk_forward_report_to">Encaminhar para %s</string>
|
||||
<string name="sk_unsent_posts">Publicações não enviadas</string>
|
||||
<string name="sk_draft">Rascunho</string>
|
||||
<string name="sk_schedule">Horário</string>
|
||||
<string name="sk_confirm_delete_draft_title">Apagar rascunho</string>
|
||||
<string name="sk_confirm_delete_scheduled_post_title">Apagar publicação agendada</string>
|
||||
<string name="sk_confirm_delete_scheduled_post">De certeza que queres apagar esta publicação agendada\?</string>
|
||||
<string name="sk_draft_or_schedule">Rascunho ou agendar</string>
|
||||
<string name="sk_compose_draft">A publicação será guardada como rascunho.</string>
|
||||
<string name="sk_compose_scheduled">Agendado para</string>
|
||||
<string name="sk_confirm_delete_scheduled_post">De certeza que quer apagar esta publicação agendada\?</string>
|
||||
<string name="sk_draft_or_schedule">Rascunho ou horário</string>
|
||||
<string name="sk_compose_draft">Publicação guardada como rascunho.</string>
|
||||
<string name="sk_draft_saved">Rascunho guardado</string>
|
||||
<string name="sk_post_scheduled">Publicação agendada</string>
|
||||
<string name="sk_scheduled_too_soon">A publicação tem de ser agendada com pelo menos 10 minutos de avanço.</string>
|
||||
<string name="sk_confirm_save_draft">Guardar rascunho\?</string>
|
||||
<string name="sk_scheduled_too_soon_title">Horário agendado é demasiado cedo</string>
|
||||
<string name="sk_compose_scheduled">Agendada para</string>
|
||||
<string name="sk_confirm_save_changes">Guardar alterações\?</string>
|
||||
<string name="sk_mark_as_draft">Marcar como rascunho</string>
|
||||
<string name="sk_schedule_or_draft">Agendar ou rascunho</string>
|
||||
<string name="sk_schedule_post">Agendar publicação</string>
|
||||
<string name="sk_compose_no_schedule">Não agendar</string>
|
||||
<string name="sk_compose_no_draft">Não criar rascunho</string>
|
||||
<string name="sk_settings_reduce_motion">Reduzir movimento nas animações</string>
|
||||
<string name="sk_announcements">Anúncios</string>
|
||||
<string name="sk_compose_no_draft">Sem rascunho</string>
|
||||
<string name="sk_content_type_unspecified">Não especificado</string>
|
||||
<string name="sk_content_type_plain">Texto simples</string>
|
||||
<string name="sk_content_type_html">HTML</string>
|
||||
<string name="sk_content_type_markdown">Marcador</string>
|
||||
<string name="sk_content_type_mfm">MFM</string>
|
||||
<string name="sk_settings_content_types">Permitir formatação de publicação</string>
|
||||
<string name="sk_settings_default_content_type">Tipo de conteúdo predefinido</string>
|
||||
<string name="sk_settings_default_content_type_explanation">Permite pré-selecionar um tipo de conteúdo quando se criam novas publicações, substituindo o valor definido em \"Preferências de publicação\".</string>
|
||||
<string name="sk_mark_as_read">Marcar como lido</string>
|
||||
<string name="sk_settings_about_instance">Sobre a instância</string>
|
||||
<string name="sk_settings_single_notification">Mostrar apenas uma notificação</string>
|
||||
<string name="sk_create">Criar</string>
|
||||
<string name="sk_create_list_title">Criar lista</string>
|
||||
<string name="sk_list_name_hint">Nome da lista</string>
|
||||
<string name="sk_list_replies_policy">Mostrar respostas a</string>
|
||||
<string name="sk_list_replies_policy_list">Membros da lista</string>
|
||||
<string name="sk_list_replies_policy_followed">utilizadores seguidos</string>
|
||||
<string name="sk_list_replies_policy_none">ninguém</string>
|
||||
<string name="sk_delete_list">Apagar lista</string>
|
||||
<string name="sk_delete_list_confirm">De certeza que queres apagar a lista \"%s\"\?</string>
|
||||
<string name="sk_edit_list_title">Editar lista</string>
|
||||
<string name="sk_your_lists">As tuas listas</string>
|
||||
<string name="sk_timeline_home">Página principal</string>
|
||||
<string name="sk_timeline_local">Local</string>
|
||||
<string name="sk_timeline_federated">Federação</string>
|
||||
<string name="sk_recent_searches_placeholder">Escrever para começar a procurar</string>
|
||||
<string name="sk_remove_follower">Remover como seguidor</string>
|
||||
<string name="sk_remove_follower_confirm">Remover %s como seguidor bloqueando e logo a seguir desbloqueando\?</string>
|
||||
<string name="sk_do_remove_follower">Remover</string>
|
||||
<string name="sk_remove_follower_success">Seguidor removido com sucesso</string>
|
||||
<string name="sk_changelog">Registo de alterações</string>
|
||||
<string name="sk_alt_text_missing_title">Sem descrição de imagem</string>
|
||||
<string name="sk_alt_text_missing">Pelo menos um anexo não contém uma descrição.</string>
|
||||
<string name="sk_changelog">Registo de mudanças</string>
|
||||
<string name="sk_alt_text_missing">Pelo menos um anexo não contém descrição.</string>
|
||||
<string name="sk_publish_anyway">Publicar assim mesmo</string>
|
||||
<string name="sk_settings_disable_alt_text_reminder">Desativar o lembrete para adicionar descrição de imagem</string>
|
||||
<string name="sk_settings_disable_alt_text_reminder">Desligar lembrete para adicionar texto alternativo</string>
|
||||
<string name="sk_timelines">Linhas do tempo</string>
|
||||
<string name="sk_timeline_posts">Publicações</string>
|
||||
<string name="sk_timelines_add">Adicionar</string>
|
||||
<string name="sk_timeline">Linha do tempo</string>
|
||||
<string name="sk_list">Lista</string>
|
||||
<string name="sk_hashtag">Hashtag</string>
|
||||
<string name="sk_pin_timeline">Fixar linha do tempo</string>
|
||||
<string name="sk_unpin_timeline">Desfixar linha do tempo</string>
|
||||
<string name="sk_pinned_timeline">Fixar à página principal</string>
|
||||
<string name="sk_unpinned_timeline">Desfixar da página principal</string>
|
||||
<string name="sk_remove">Remover</string>
|
||||
<string name="sk_timeline_icon">Ícone</string>
|
||||
<string name="sk_icon_heart">Coração</string>
|
||||
<string name="sk_icon_star">Estrela</string>
|
||||
<string name="sk_icon_city">Cidade</string>
|
||||
<string name="sk_icon_cat">Gato</string>
|
||||
<string name="sk_icon_dog">Cão</string>
|
||||
<string name="sk_icon_rabbit">Coelho</string>
|
||||
|
@ -193,6 +105,7 @@
|
|||
<string name="sk_icon_balloon">Balão</string>
|
||||
<string name="sk_icon_image">Imagem</string>
|
||||
<string name="sk_icon_bot">Robô</string>
|
||||
<string name="sk_icon_language">Idioma</string>
|
||||
<string name="sk_icon_location">Localização</string>
|
||||
<string name="sk_icon_megaphone">Megafone</string>
|
||||
<string name="sk_icon_microphone">Microfone</string>
|
||||
|
@ -201,6 +114,125 @@
|
|||
<string name="sk_icon_coffee">Café</string>
|
||||
<string name="sk_icon_laugh">Riso</string>
|
||||
<string name="sk_icon_news">Notícias</string>
|
||||
<string name="sk_no_results">Sem resultados</string>
|
||||
<string name="sk_save_draft">Guardar rascunho\?</string>
|
||||
<string name="sk_no_alt_text">Sem texto alternativo disponível</string>
|
||||
<string name="sk_settings_show_alt_indicator">Indicador de textos alternativos</string>
|
||||
<string name="sk_settings_show_no_alt_indicator">Indicador para textos alternativos ausentes</string>
|
||||
<string name="sk_updater_enable_pre_releases">Ligar versões beta</string>
|
||||
<string name="sk_inline_direct">apenas-mencionados</string>
|
||||
<string name="sk_separator">.</string>
|
||||
<string name="sk_instance_features">Recursos da instância</string>
|
||||
<string name="sk_settings_support_local_only">O servidor só suporta publicações locais</string>
|
||||
<string name="sk_settings_glitch_instance">Glitch em modo local</string>
|
||||
<string name="sk_settings_glitch_mode_explanation">Liga isto se a tua instância nativa corre no Glitch. Não é necessário para Hometown ou Akkoma.</string>
|
||||
<string name="sk_signed_up">inscrito</string>
|
||||
<string name="sk_reported">reportado</string>
|
||||
<string name="sk_sign_ups">Inscrições</string>
|
||||
<string name="sk_notify_poll_results">Resultados do inquérito</string>
|
||||
<string name="sk_follow_as">Seguir com outra conta</string>
|
||||
<string name="sk_confirm_unpin_post">Tem a certeza que quer desfixar esta publicação\?</string>
|
||||
<string name="sk_user_post_notifications_off">Desligar notificações de publicação para %s</string>
|
||||
<string name="sk_federated_timeline_info_banner">Estas são as publicações mais recentes das pessoas na tua federação.</string>
|
||||
<string name="sk_list_timelines">Listas</string>
|
||||
<string name="sk_reject_follow_request">Rejeitar pedido para seguir</string>
|
||||
<string name="sk_lists_with_user">Listas com %s</string>
|
||||
<string name="sk_settings_always_reveal_content_warnings">Mostrar sempre avisos de conteúdo</string>
|
||||
<string name="sk_poll_allow_multiple">Permitir escolhas múltiplas</string>
|
||||
<string name="sk_translated_using">Traduzido por %s</string>
|
||||
<string name="sk_clear_recent_languages">Apagar idiomas usados recentemente</string>
|
||||
<string name="sk_confirm_clear_recent_languages">De certeza que quer apagar os seus idiomas usados recentemente\?</string>
|
||||
<string name="sk_welcome_title">Bem-vindo/a!</string>
|
||||
<string name="sk_welcome_text">Saudações do tubarão! Para começar, introduz o nome do domínio da tua instância nativa abaixo.</string>
|
||||
<string name="sk_settings_posting">Preferências de publicação</string>
|
||||
<string name="sk_delete_notification">Apagar notificação</string>
|
||||
<string name="sk_delete_notification_confirm_action">Apagar notificação</string>
|
||||
<string name="sk_clear_all_notifications">Apagar todas as notificações</string>
|
||||
<string name="sk_settings_publish_button_text_title">Personalizar o texto do botão de publicar</string>
|
||||
<string name="sk_settings_translate_only_opened">Traduzir apenas publicações abertas</string>
|
||||
<string name="sk_settings_translation_availability_note_available">%s suporta tradução!</string>
|
||||
<string name="sk_settings_translation_availability_note_unavailable">%s não parece suportar tradução.</string>
|
||||
<string name="sk_loading_resource_on_instance_title">A procurar em %s</string>
|
||||
<string name="sk_undo_reblog">Desfazer partilha</string>
|
||||
<string name="sk_hashtags_you_follow">Hastags que segues</string>
|
||||
<string name="sk_copy_link_to_post">Copiar ligação para publicação</string>
|
||||
<string name="sk_forward_report_to">Encaminhar para %s</string>
|
||||
<string name="sk_confirm_delete_draft">De certeza que pretende apagar este rascunho\?</string>
|
||||
<string name="sk_post_scheduled">Publicação agendada</string>
|
||||
<string name="sk_scheduled_too_soon">A publicação tem de ser agendada pelo menos 10 minutos no futuro.</string>
|
||||
<string name="sk_confirm_save_draft">Guardar rascunho\?</string>
|
||||
<string name="sk_schedule_or_draft">Horário ou rascunho</string>
|
||||
<string name="sk_settings_reduce_motion">Reduzir movimento em animações</string>
|
||||
<string name="sk_announcements">Anúncios</string>
|
||||
<string name="sk_recent_searches_placeholder">Escrever para iniciar a procura</string>
|
||||
<string name="sk_remove_follower">Remover como seguidor</string>
|
||||
<string name="sk_alt_text_missing_title">Texto alternativo em falta</string>
|
||||
<string name="sk_notify_posts_info_banner">Se ligar as notificações de publicação para algumas pessoas, as suas publicações novas aparecerão aqui.</string>
|
||||
<string name="sk_pin_timeline">Fixar linha do tempo</string>
|
||||
<string name="sk_icon_city">Cidade</string>
|
||||
<string name="sk_icon_gauge">Régua</string>
|
||||
<string name="sk_icon_pin">Pionés</string>
|
||||
<string name="sk_notification_type_update">Publicações editadas</string>
|
||||
<string name="sk_searching">À procura…</string>
|
||||
<string name="sk_save_draft_message">Guardar alterações ou publicar o rascunho agora\?</string>
|
||||
<string name="sk_settings_see_new_posts_button">Botão \"Ver publicações novas\"</string>
|
||||
<string name="sk_inline_local_only">apenas-local</string>
|
||||
<string name="sk_local_only">Apenas instância local</string>
|
||||
<string name="sk_settings_local_only_explanation">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.</string>
|
||||
<string name="sk_settings_server_version">Versão do servidor: %s</string>
|
||||
<string name="sk_settings_hide_interaction">Esconder botões interativos</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Adicionar \"re\" a respostas com AC</string>
|
||||
<string name="sk_in_reply">Em resposta</string>
|
||||
<string name="sk_content_type">Tipo de conteúdo</string>
|
||||
<string name="sk_content_type_bbcode">BBCode</string>
|
||||
<string name="sk_settings_content_types_explanation">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.</string>
|
||||
<string name="sk_confirm_unpin_post_title">Desfixar publicação do perfil</string>
|
||||
<string name="sk_quoting_user">Citar %s</string>
|
||||
<string name="sk_settings_reply_visibility">Visibilidade da resposta</string>
|
||||
<string name="sk_settings_reply_visibility_all">Todas as respostas</string>
|
||||
<string name="sk_settings_reply_visibility_following">Respostas para os meus seguidores</string>
|
||||
<string name="sk_settings_reply_visibility_self">Respostas para mim</string>
|
||||
<string name="sk_settings_show_interaction_counts">Mostrar número de interações</string>
|
||||
<string name="sk_settings_app_version">Megalodon %1$s (%2$d)</string>
|
||||
<string name="sk_mark_media_as_sensitive">Marcar conteúdo como sensível</string>
|
||||
<string name="sk_user_post_notifications_on">Ligar notificações de publicação para %s</string>
|
||||
<string name="sk_update_ready">Megalodon %s descarregado e pronto a instalar.</string>
|
||||
<string name="sk_check_for_update">A verificar atualizações</string>
|
||||
<string name="sk_no_update_available">Sem atualizações disponíveis</string>
|
||||
<string name="sk_settings_show_federated_timeline">Mostrar linha do tempo unificada</string>
|
||||
<string name="sk_notification_type_status">Publicações</string>
|
||||
<string name="sk_notify_posts">Notificações de publicação</string>
|
||||
<string name="sk_resource_not_found">Recurso não encontrado</string>
|
||||
<string name="sk_favorited_as">Adicionado aos favoritos como %s</string>
|
||||
<string name="sk_already_favorited">Já adicionado aos favoritos</string>
|
||||
<string name="sk_reblog_as">Partilhar com outra conta</string>
|
||||
<string name="sk_reply_as">Responder com outra conta</string>
|
||||
<string name="sk_settings_uniform_icon_for_notifications">Ícone uniforme para todas as notificações</string>
|
||||
<string name="sk_list_name_hint">Nome da Lista</string>
|
||||
<string name="sk_list_replies_policy">Mostrar respostas a</string>
|
||||
<string name="sk_list_replies_policy_list">Membros da lista</string>
|
||||
<string name="sk_list_replies_policy_followed">Utilizadores seguidos</string>
|
||||
<string name="sk_list_replies_policy_none">ninguém</string>
|
||||
<string name="sk_delete_list">Apagar lista</string>
|
||||
<string name="sk_delete_list_confirm">De certeza que quer apagar a lista \"%s\"\?</string>
|
||||
<string name="sk_edit_list_title">Editar lista</string>
|
||||
<string name="sk_your_lists">As tuas listas</string>
|
||||
<string name="sk_timeline_home">Página principal</string>
|
||||
<string name="sk_timeline_local">Local</string>
|
||||
<string name="sk_timeline_federated">Federação</string>
|
||||
<string name="sk_timeline_posts">Publicações</string>
|
||||
<string name="sk_timelines_add">Adicionar</string>
|
||||
<string name="sk_timeline">Linha do tempo</string>
|
||||
<string name="sk_list">Lista</string>
|
||||
<string name="sk_hashtag">Hashtag</string>
|
||||
<string name="sk_unpin_timeline">Desfixar linha do tempo</string>
|
||||
<string name="sk_pinned_timeline">Fixado à página principal</string>
|
||||
<string name="sk_unpinned_timeline">Desfixado da página principal</string>
|
||||
<string name="sk_reply_line_above_avatar">Linha \"Em resposta a\" por cima da foto de perfil</string>
|
||||
<string name="sk_remove">Remover</string>
|
||||
<string name="sk_timeline_icon">ícone</string>
|
||||
<string name="sk_icon_heart">Coração</string>
|
||||
<string name="sk_icon_star">Estrela</string>
|
||||
<string name="sk_icon_pi">Pi</string>
|
||||
<string name="sk_icon_color_palette">Paleta de cores</string>
|
||||
<string name="sk_icon_academic_cap">Chapéu académico</string>
|
||||
|
@ -213,7 +245,8 @@
|
|||
<string name="sk_icon_train">Comboio</string>
|
||||
<string name="sk_icon_clapper_board">Claquete</string>
|
||||
<string name="sk_icon_leaves">Folhas</string>
|
||||
<string name="sk_icon_sport">Desporto</string>
|
||||
<string name="sk_icon_sport">Desposto</string>
|
||||
<string name="sk_icon_aperture">Abertura</string>
|
||||
<string name="sk_icon_music">Música</string>
|
||||
<string name="sk_icon_people">Pessoas</string>
|
||||
<string name="sk_icon_health">Saúde</string>
|
||||
|
@ -225,64 +258,31 @@
|
|||
<string name="sk_icon_map">Mapa</string>
|
||||
<string name="sk_icon_math_formula">Fórmula matemática</string>
|
||||
<string name="sk_icon_backpack">Mochila</string>
|
||||
<string name="sk_icon_briefcase">Pasta</string>
|
||||
<string name="sk_icon_fire">Fogo</string>
|
||||
<string name="sk_icon_bug">Inseto</string>
|
||||
<string name="sk_icon_pizza">Pizza</string>
|
||||
<string name="sk_icon_gavel">Martelo</string>
|
||||
<string name="sk_icon_gauge">Régua</string>
|
||||
<string name="sk_icon_headphones">Auscultadores</string>
|
||||
<string name="sk_icon_human">Humano</string>
|
||||
<string name="sk_icon_globe">Globo</string>
|
||||
<string name="sk_icon_pin">Pionés</string>
|
||||
<string name="sk_edit_timeline">Editar linha do tempo</string>
|
||||
<string name="sk_edit_timelines">Editar linhas do tempo</string>
|
||||
<string name="sk_alt_button">ALT</string>
|
||||
<string name="sk_post_edited">editado</string>
|
||||
<string name="sk_notification_type_update">Publicações editadas</string>
|
||||
<string name="sk_attach_file">Anexar ficheiro</string>
|
||||
<string name="sk_searching">À procura…</string>
|
||||
<string name="sk_no_results">Sem resultados</string>
|
||||
<string name="sk_save_draft">Guardar rascunho\?</string>
|
||||
<string name="sk_no_alt_text">Sem descrição de imagem</string>
|
||||
<string name="sk_settings_show_alt_indicator">Indicador para descrições de imagem</string>
|
||||
<string name="sk_settings_show_no_alt_indicator">Indicador para descrições de imagem ausentes</string>
|
||||
<string name="sk_updater_enable_pre_releases">Ativar versões beta</string>
|
||||
<string name="sk_inline_local_only">apenas local</string>
|
||||
<string name="sk_inline_direct">apenas mencionado</string>
|
||||
<string name="sk_separator">.</string>
|
||||
<string name="sk_local_only">Apenas instância local</string>
|
||||
<string name="sk_instance_features">Recursos da instância</string>
|
||||
<string name="sk_settings_support_local_only">O servidor suporta publicações locais</string>
|
||||
<string name="sk_settings_local_only_explanation">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.</string>
|
||||
<string name="sk_settings_glitch_instance">Modo local do Glitch</string>
|
||||
<string name="sk_settings_glitch_mode_explanation">Ativar isto se a tua instância nativa corre no Glitch. Não é necessário para Hometown ou Akkoma.</string>
|
||||
<string name="sk_signed_up">inscrito</string>
|
||||
<string name="sk_reported">denunciado</string>
|
||||
<string name="sk_follow_as">Seguir com outra conta</string>
|
||||
<string name="sk_followed_as">A seguir com %s</string>
|
||||
<string name="sk_settings_show_new_posts_button">Botão \"Mostrar novas publicações\"</string>
|
||||
<string name="sk_undo_reblog">Desfazer partilha</string>
|
||||
<string name="sk_reblog_as">Partilhar com outra conta</string>
|
||||
<string name="sk_reblogged_as">Partilhado com %s</string>
|
||||
<string name="sk_already_reblogged">Já partilhado</string>
|
||||
<string name="sk_notify_update">Edita uma publicação partilhada</string>
|
||||
<string name="sk_reacted">reagiu</string>
|
||||
<string name="sk_new_reports">Novas denuncias</string>
|
||||
<string name="sk_filtered">Filtrado: %s</string>
|
||||
<string name="sk_sign_ups">Registos</string>
|
||||
<string name="sk_reacted_with">reagiu com %s</string>
|
||||
<string name="sk_settings_server_version">Versão do servidor: %s</string>
|
||||
<string name="sk_notify_poll_results">Resultados do inquérito</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Mostrar \"re:\" ao responder a AC</string>
|
||||
<string name="sk_expand">Expandir</string>
|
||||
<string name="sk_collapse">Esconder</string>
|
||||
<string name="sk_settings_collapse_long_posts">Esconder publicações muito longas</string>
|
||||
<string name="sk_unfinished_attachments">Corrigir anexos\?</string>
|
||||
<string name="sk_unfinished_attachments_message">Alguns anexos ainda não acabaram de carregar.</string>
|
||||
<string name="sk_settings_hide_interaction">Esconder botões de interação</string>
|
||||
<string name="sk_notification_action_replied">Resposta enviada a %s</string>
|
||||
<string name="sk_in_reply">Em resposta</string>
|
||||
<string name="sk_reply_line_above_avatar">Linha \"Em resposta a\" acima da foto de perfil</string>
|
||||
<string name="sk_show_thread">Mostrar fio</string>
|
||||
<string name="sk_compact_reblog_reply_line">Linha partilhar/responder compacta</string>
|
||||
<string name="sk_settings_confirm_before_reblog">Confirmar antes de partilhar</string>
|
||||
<string name="sk_language_name">%1$s (%2$s)</string>
|
||||
<string name="sk_reblogged_as">Partilhado como %s</string>
|
||||
<string name="sk_already_reblogged">Já partilhado</string>
|
||||
<string name="sk_notify_update">Editar uma publicação partilhada</string>
|
||||
<string name="sk_compact_reblog_reply_line">Linha compacta partilhar/responder</string>
|
||||
<string name="sk_reacted_with">reagiu com %s</string>
|
||||
<string name="sk_reacted">reagiu</string>
|
||||
<string name="sk_new_reports">Novas denúncias</string>
|
||||
<string name="sk_unfinished_attachments_message">Alguns anexos ainda não carregaram.</string>
|
||||
<string name="sk_followed_as">Seguido com %s</string>
|
||||
<string name="sk_settings_hide_fab">Esconder automaticamente o botão Escrever</string>
|
||||
<string name="sk_show_thread">Mostrar fio</string>
|
||||
</resources>
|
|
@ -1,2 +1,44 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="sk_app_name">Megalodon</string>
|
||||
<string name="sk_pinned_posts">Fixado</string>
|
||||
<string name="sk_delete_and_redraft">Apagar e reescrever</string>
|
||||
<string name="sk_confirm_delete_and_redraft">Tem a certeza que pretende apagar e reescrever esta publicação\?</string>
|
||||
<string name="sk_confirm_unpin_post">Tem a certeza que deseja desafixar esta publicação\?</string>
|
||||
<string name="sk_settings_reply_visibility_all">Todas as respostas</string>
|
||||
<string name="sk_settings_load_new_posts">Carregar novas publicações automaticamente</string>
|
||||
<string name="sk_user_post_notifications_off">Desligar as notificações de publicação para %s</string>
|
||||
<string name="sk_federated_timeline_info_banner">Estas são as publicações mais recentes das pessoas na tua federação.</string>
|
||||
<string name="sk_confirm_delete_and_redraft_title">Apagar e reescrever publicação</string>
|
||||
<string name="sk_pin_post">Fixar no perfil</string>
|
||||
<string name="sk_confirm_pin_post_title">Fixar publicação no perfil</string>
|
||||
<string name="sk_confirm_pin_post">Deseja fixar esta publicação ao seu perfil\?</string>
|
||||
<string name="sk_pinning">A fixar a publicação…</string>
|
||||
<string name="sk_unpin_post">Desafixar do perfil</string>
|
||||
<string name="sk_confirm_unpin_post_title">Desafixar publicação do perfil</string>
|
||||
<string name="sk_unpinning">A desafixar publicação…</string>
|
||||
<string name="sk_image_description">Descrição da imagem</string>
|
||||
<string name="sk_visibility_unlisted">Não listado</string>
|
||||
<string name="sk_settings_show_replies">Mostrar respostas</string>
|
||||
<string name="sk_quoting_user">Citação %s</string>
|
||||
<string name="sk_settings_reply_visibility">Visibilidade da resposta</string>
|
||||
<string name="sk_clear_recent_languages">Apagar idiomas usados recentemente</string>
|
||||
<string name="sk_settings_reply_visibility_following">Respostas aos meus comentários</string>
|
||||
<string name="sk_settings_reply_visibility_self">Respostas a mim</string>
|
||||
<string name="sk_settings_show_boosts">Mostrar impulsionamentos</string>
|
||||
<string name="sk_settings_show_interaction_counts">Mostrar contagem de interações</string>
|
||||
<string name="sk_settings_app_version">Megalodon v%1$s (%2$d)</string>
|
||||
<string name="sk_mark_media_as_sensitive">Marcar conteúdo como sensível</string>
|
||||
<string name="sk_user_post_notifications_on">Ligar as notificações de publicação para %s</string>
|
||||
<string name="sk_federated_timeline">Federação</string>
|
||||
<string name="sk_update_available">Megalodon %s pronto a descarregar.</string>
|
||||
<string name="sk_update_ready">Megalodon %s descarregado e pronto a instalar.</string>
|
||||
<string name="sk_check_for_update">A verificar atualizações</string>
|
||||
<string name="sk_no_update_available">Sem atualizações disponíveis</string>
|
||||
<string name="sk_list_timelines">Listas</string>
|
||||
<string name="sk_follow_requests">Pedidos para seguir</string>
|
||||
<string name="sk_accept_follow_request">Aceitar pedido para seguir</string>
|
||||
<string name="sk_reject_follow_request">Rejeitar pedido para seguir</string>
|
||||
<string name="sk_lists_with_user">Listas com %s</string>
|
||||
<string name="sk_settings_always_reveal_content_warnings">Mostrar sempre avisos de conteúdo</string>
|
||||
</resources>
|
|
@ -472,7 +472,7 @@
|
|||
<string name="spoiler_hide">Спрятать повторно</string>
|
||||
<string name="poll_multiple_choice">Выберите один или более</string>
|
||||
<string name="save_changes">Сохранить изменения</string>
|
||||
<string name="profile_featured">Возможности</string>
|
||||
<string name="profile_featured">Избранное</string>
|
||||
<string name="profile_timeline">Лента</string>
|
||||
<string name="view_all">Посмотреть все</string>
|
||||
<string name="profile_endorsed_accounts">Учётные записи</string>
|
||||
|
@ -480,6 +480,7 @@
|
|||
<string name="show">Показать</string>
|
||||
<string name="hide">Скрыть</string>
|
||||
<string name="join_default_server">Присоединиться к %s</string>
|
||||
<string name="pick_server">Выбрать другой сервер</string>
|
||||
<string name="signup_or_login">или</string>
|
||||
<string name="learn_more">Узнать больше</string>
|
||||
<string name="welcome_to_mastodon">Добро пожаловать в Mastodon</string>
|
||||
|
|
|
@ -412,4 +412,7 @@
|
|||
<!-- %1$s is server domain, %2$s is email domain. You can reorder these placeholders to fit your language better. -->
|
||||
<string name="signup_email_domain_blocked">%1$s tillåter inte registrering från %2$s. Prova en annan eller <a>välj en annan server</a>.</string>
|
||||
<string name="signup_username_taken">Det här användarnamnet är redan taget.</string>
|
||||
<string name="save_changes">Spara ändringar</string>
|
||||
<string name="signup_or_login">eller</string>
|
||||
<string name="welcome_to_mastodon">Välkommen till Mastodon</string>
|
||||
</resources>
|
||||
|
|
|
@ -275,4 +275,21 @@
|
|||
<string name="sk_settings_confirm_before_reblog">Підтверджувати поширення</string>
|
||||
<string name="sk_reacted">реагує</string>
|
||||
<string name="sk_reacted_with">реагує з %s</string>
|
||||
<string name="sk_content_type_unspecified">Не визначено</string>
|
||||
<string name="sk_content_type_plain">Звичайний текст</string>
|
||||
<string name="sk_content_type_html">HTML</string>
|
||||
<string name="sk_content_type_bbcode">BBCode</string>
|
||||
<string name="sk_content_type_mfm">MFM</string>
|
||||
<string name="sk_settings_content_types">Увімкнути форматування допису</string>
|
||||
<string name="sk_settings_default_content_type">Типовий тип вмісту</string>
|
||||
<string name="sk_content_type">Тип вмісту</string>
|
||||
<string name="sk_settings_default_content_type_explanation">Це дозволяє вам попередньо вибрати тип вмісту під час написання нових дописів, замінивши значення, встановлене в «Налаштуваннях постингу».</string>
|
||||
<string name="sk_content_type_markdown">Markdown</string>
|
||||
<string name="sk_settings_content_types_explanation">Дозволяє налаштувати тип вмісту, наприклад, Markdown, під час написання допису. Зауважте, що не всі сервери підтримують цю функцію.</string>
|
||||
<string name="sk_open_in_app">Відкрити у застосунку</string>
|
||||
<string name="sk_external_share_title">Поділитися через обліковий запис</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Це найновіші дописи людей у бульбашці вашого сервера Akkoma.</string>
|
||||
<string name="sk_timeline_bubble">Бульбашка</string>
|
||||
<string name="sk_instance_info_unavailable">Сервер тимчасово недоступний</string>
|
||||
<string name="sk_external_share_or_open_title">Поділитися або відкрити за допомогою облікового запису</string>
|
||||
</resources>
|
|
@ -4,33 +4,57 @@
|
|||
|
||||
<color name="m3_navigation_bar_bg">@android:color/system_neutral1_50</color>
|
||||
|
||||
<color name="m3_gray_1000">@android:color/system_neutral1_1000</color>
|
||||
<color name="m3_gray_900">@android:color/system_neutral1_900</color>
|
||||
<color name="m3_gray_800t">@android:color/system_neutral1_800</color>
|
||||
<color name="m3_gray_800">@android:color/system_neutral1_800</color>
|
||||
<color name="m3_gray_700">@android:color/system_neutral1_700</color>
|
||||
<color name="m3_gray_600">@android:color/system_neutral1_600</color>
|
||||
<color name="m3_gray_500">@android:color/system_neutral1_500</color>
|
||||
<color name="m3_gray_400">@android:color/system_neutral1_400</color>
|
||||
<color name="m3_gray_300">@android:color/system_neutral1_300</color>
|
||||
<color name="m3_gray_200">@android:color/system_neutral1_200</color>
|
||||
<color name="m3_gray_100">@android:color/system_neutral1_100</color>
|
||||
<color name="m3_gray_50t">@android:color/system_neutral1_50</color>
|
||||
<color name="m3_gray_50">@android:color/system_neutral1_50</color>
|
||||
<color name="m3_gray_25">@android:color/system_neutral1_10</color>
|
||||
<color name="m3_neutral1_900">@android:color/system_neutral1_900</color>
|
||||
<color name="m3_neutral1_800t">@android:color/system_neutral1_800</color>
|
||||
<color name="m3_neutral1_800">@android:color/system_neutral1_800</color>
|
||||
<color name="m3_neutral1_700">@android:color/system_neutral1_700</color>
|
||||
<color name="m3_neutral1_600">@android:color/system_neutral1_600</color>
|
||||
<color name="m3_neutral1_500">@android:color/system_neutral1_500</color>
|
||||
<color name="m3_neutral1_400">@android:color/system_neutral1_400</color>
|
||||
<color name="m3_neutral1_300">@android:color/system_neutral1_300</color>
|
||||
<color name="m3_neutral1_200">@android:color/system_neutral1_200</color>
|
||||
<color name="m3_neutral1_100">@android:color/system_neutral1_100</color>
|
||||
<color name="m3_neutral1_50t">@android:color/system_neutral1_50</color>
|
||||
<color name="m3_neutral1_50">@android:color/system_neutral1_50</color>
|
||||
<color name="m3_neutral1_25">@android:color/system_neutral1_10</color>
|
||||
|
||||
<color name="m3_primary_25">@android:color/system_accent1_10</color>
|
||||
<color name="m3_primary_50">@android:color/system_accent1_50</color>
|
||||
<color name="m3_primary_100">@android:color/system_accent1_100</color>
|
||||
<color name="m3_primary_200">@android:color/system_accent1_200</color>
|
||||
<color name="m3_primary_300">@android:color/system_accent1_300</color>
|
||||
<color name="m3_primary_400">@android:color/system_accent1_400</color>
|
||||
<color name="m3_primary_500">@android:color/system_accent1_500</color>
|
||||
<color name="m3_primary_600">@android:color/system_accent1_600</color>
|
||||
<color name="m3_primary_700">@android:color/system_accent1_700</color>
|
||||
<color name="m3_primary_800">@android:color/system_accent1_800</color>
|
||||
<color name="m3_primary_900">@android:color/system_accent1_900</color>
|
||||
<color name="m3_accent1_25">@android:color/system_accent1_10</color>
|
||||
<color name="m3_accent1_50">@android:color/system_accent1_50</color>
|
||||
<color name="m3_accent1_100">@android:color/system_accent1_100</color>
|
||||
<color name="m3_accent1_200">@android:color/system_accent1_200</color>
|
||||
<color name="m3_accent1_300">@android:color/system_accent1_300</color>
|
||||
<color name="m3_accent1_400">@android:color/system_accent1_400</color>
|
||||
<color name="m3_accent1_500">@android:color/system_accent1_500</color>
|
||||
<color name="m3_accent1_600">@android:color/system_accent1_600</color>
|
||||
<color name="m3_accent1_700">@android:color/system_accent1_700</color>
|
||||
<color name="m3_accent1_800">@android:color/system_accent1_800</color>
|
||||
<color name="m3_accent1_900">@android:color/system_accent1_900</color>
|
||||
|
||||
<color name="m3_neutral2_900">@android:color/system_neutral2_900</color>
|
||||
<color name="m3_neutral2_800t">@android:color/system_neutral2_800</color>
|
||||
<color name="m3_neutral2_800">@android:color/system_neutral2_800</color>
|
||||
<color name="m3_neutral2_700">@android:color/system_neutral2_700</color>
|
||||
<color name="m3_neutral2_600">@android:color/system_neutral2_600</color>
|
||||
<color name="m3_neutral2_500">@android:color/system_neutral2_500</color>
|
||||
<color name="m3_neutral2_400">@android:color/system_neutral2_400</color>
|
||||
<color name="m3_neutral2_300">@android:color/system_neutral2_300</color>
|
||||
<color name="m3_neutral2_200">@android:color/system_neutral2_200</color>
|
||||
<color name="m3_neutral2_100">@android:color/system_neutral2_100</color>
|
||||
<color name="m3_neutral2_50t">@android:color/system_neutral2_50</color>
|
||||
<color name="m3_neutral2_50">@android:color/system_neutral2_50</color>
|
||||
<color name="m3_neutral2_25">@android:color/system_neutral2_10</color>
|
||||
|
||||
<color name="m3_accent2_25">@android:color/system_accent2_10</color>
|
||||
<color name="m3_accent2_50">@android:color/system_accent2_50</color>
|
||||
<color name="m3_accent2_100">@android:color/system_accent2_100</color>
|
||||
<color name="m3_accent2_200">@android:color/system_accent2_200</color>
|
||||
<color name="m3_accent2_300">@android:color/system_accent2_300</color>
|
||||
<color name="m3_accent2_400">@android:color/system_accent2_400</color>
|
||||
<color name="m3_accent2_500">@android:color/system_accent2_500</color>
|
||||
<color name="m3_accent2_600">@android:color/system_accent2_600</color>
|
||||
<color name="m3_accent2_700">@android:color/system_accent2_700</color>
|
||||
<color name="m3_accent2_800">@android:color/system_accent2_800</color>
|
||||
<color name="m3_accent2_900">@android:color/system_accent2_900</color>
|
||||
|
||||
<!-- light theme -->
|
||||
<color name="m3_sys_light_primary">@android:color/system_accent1_600</color>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<string name="preparing_auth">Chuẩn bị xác thực…</string>
|
||||
<string name="finishing_auth">Hoàn tất xác thực…</string>
|
||||
<string name="user_boosted">%s đăng lại</string>
|
||||
<string name="in_reply_to">%s viết tiếp</string>
|
||||
<string name="in_reply_to">Trả lời %s</string>
|
||||
<string name="notifications">Thông báo</string>
|
||||
<string name="user_followed_you">đã theo dõi bạn</string>
|
||||
<string name="user_sent_follow_request">đã yêu cầu theo dõi bạn</string>
|
||||
|
@ -127,7 +127,7 @@
|
|||
<item quantity="other">%d người đang thảo luận</item>
|
||||
</plurals>
|
||||
<plurals name="discussed_x_times">
|
||||
<item quantity="other">Đề cập %d lần</item>
|
||||
<item quantity="other">Chia sẻ %d lượt</item>
|
||||
</plurals>
|
||||
<string name="report_title">Báo cáo %s</string>
|
||||
<string name="report_choose_reason">Có vấn đề gì với tút này?</string>
|
||||
|
@ -150,8 +150,8 @@
|
|||
<string name="sending_report">Đang gửi báo cáo…</string>
|
||||
<string name="report_sent_title">Cảm ơn đã báo cáo, chúng tôi sẽ xem xét kỹ.</string>
|
||||
<string name="report_sent_subtitle">Trong lúc chờ chúng tôi xem xét, bạn có thể áp dụng hành động với @%s.</string>
|
||||
<string name="unfollow_user">Ngưng theo dõi %s</string>
|
||||
<string name="unfollow">Ngưng theo dõi</string>
|
||||
<string name="unfollow_user">Bỏ theo dõi %s</string>
|
||||
<string name="unfollow">Bỏ theo dõi</string>
|
||||
<string name="mute_user_explain">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ọ.</string>
|
||||
<string name="block_user_explain">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ọ.</string>
|
||||
<string name="report_personal_title">Không muốn xem thứ này?</string>
|
||||
|
@ -223,7 +223,7 @@
|
|||
<string name="theme_auto">Tự động</string>
|
||||
<string name="theme_light">Sáng</string>
|
||||
<string name="theme_dark">Tối</string>
|
||||
<string name="theme_true_black">Chế độ tối chân thật</string>
|
||||
<string name="theme_true_black">Chế độ tương phản cao</string>
|
||||
<string name="settings_behavior">Thao tác</string>
|
||||
<string name="settings_gif">Ảnh đại diện và emoji động</string>
|
||||
<string name="settings_custom_tabs">Dùng trình duyệt tích hợp</string>
|
||||
|
@ -268,7 +268,7 @@
|
|||
<string name="my_profile">Hồ sơ của tôi</string>
|
||||
<string name="media_viewer">Xem media</string>
|
||||
<string name="follow_user">Theo dõi %s</string>
|
||||
<string name="unfollowed_user">Ngưng theo dõi %s</string>
|
||||
<string name="unfollowed_user">Bỏ theo dõi %s</string>
|
||||
<string name="followed_user">Bạn đã theo dõi %s</string>
|
||||
<string name="following_user_requested">Yêu cầu theo dõi %s</string>
|
||||
<string name="open_in_browser">Mở trong trình duyệt</string>
|
||||
|
@ -317,8 +317,8 @@
|
|||
</plurals>
|
||||
<string name="timestamp_via_app">%1$s qua %2$s</string>
|
||||
<string name="time_now">vừa xong</string>
|
||||
<string name="post_info_reblogs">Lượt đăng lại</string>
|
||||
<string name="post_info_favorites">Lượt thích</string>
|
||||
<string name="post_info_reblogs">Đăng lại</string>
|
||||
<string name="post_info_favorites">Thích</string>
|
||||
<string name="edit_history">Lịch sử chỉnh sửa</string>
|
||||
<string name="last_edit_at_x">Sửa lần cuối %s</string>
|
||||
<string name="time_just_now">vừa xong</string>
|
||||
|
@ -369,14 +369,14 @@
|
|||
<string name="text_copied">Đã sao chép vào bộ nhớ tạm</string>
|
||||
<string name="add_bookmark">Lưu</string>
|
||||
<string name="remove_bookmark">Bỏ lưu</string>
|
||||
<string name="bookmarks">Tút đã lưu</string>
|
||||
<string name="your_favorites">Lượt thích</string>
|
||||
<string name="bookmarks">Những tút đã lưu</string>
|
||||
<string name="your_favorites">Những tút đã thích</string>
|
||||
<string name="login_title">Chào mừng quay trở lại!</string>
|
||||
<string name="login_subtitle">Đăng nhập với máy chủ nơi bạn tạo tài khoản.</string>
|
||||
<string name="server_url">URL Máy chủ</string>
|
||||
<string name="signup_random_server_explain">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.</string>
|
||||
<string name="server_filter_any_language">Mọi ngôn ngữ</string>
|
||||
<string name="server_filter_instant_signup">Đăng ký nhanh</string>
|
||||
<string name="server_filter_instant_signup">Duyệt tự động</string>
|
||||
<string name="server_filter_manual_review">Duyệt thủ công</string>
|
||||
<string name="server_filter_any_signup_speed">Mọi hình thức duyệt</string>
|
||||
<string name="server_filter_region_europe">Châu Âu</string>
|
||||
|
@ -385,7 +385,7 @@
|
|||
<string name="server_filter_region_africa">Châu Phi</string>
|
||||
<string name="server_filter_region_asia">Châu Á</string>
|
||||
<string name="server_filter_region_oceania">Châu Đại Dương</string>
|
||||
<string name="not_accepting_new_members">Tạm ngưng đăng ký mới</string>
|
||||
<string name="not_accepting_new_members">Tạm dừng đăng ký mới</string>
|
||||
<string name="category_special_interests">Sở thích đặc biệt</string>
|
||||
<string name="signup_passwords_dont_match">Mật khẩu không khớp</string>
|
||||
<string name="pick_server_for_me">Chọn giúp tôi</string>
|
||||
|
|
|
@ -381,6 +381,7 @@
|
|||
<string name="server_filter_region_africa">非洲</string>
|
||||
<string name="server_filter_region_asia">亚洲</string>
|
||||
<string name="server_filter_region_oceania">大洋洲</string>
|
||||
<string name="not_accepting_new_members">不接受新成员</string>
|
||||
<string name="signup_passwords_dont_match">两次输入密码不匹配</string>
|
||||
<string name="pick_server_for_me">帮我挑选</string>
|
||||
<string name="profile_add_row">添加</string>
|
||||
|
@ -396,9 +397,11 @@
|
|||
<!-- %1$s is server domain, %2$s is email domain. You can reorder these placeholders to fit your language better. -->
|
||||
<string name="profile_timeline">时间轴</string>
|
||||
<string name="profile_endorsed_accounts">帐号</string>
|
||||
<string name="verified_link">已验证链接</string>
|
||||
<string name="show">显示</string>
|
||||
<string name="hide">隐藏</string>
|
||||
<string name="join_default_server">加入 %s</string>
|
||||
<string name="pick_server">选择其他服务器</string>
|
||||
<string name="signup_or_login">或者</string>
|
||||
<string name="learn_more">了解更多</string>
|
||||
<string name="welcome_to_mastodon">欢迎来到Mastodon</string>
|
||||
|
|
|
@ -108,4 +108,42 @@
|
|||
<attr name="colorGray800" format="color" />
|
||||
<attr name="colorGray800t" format="color" />
|
||||
<attr name="colorGray900" format="color" />
|
||||
|
||||
<attr name="colorSecondary25" format="color" />
|
||||
<attr name="colorSecondary50" format="color" />
|
||||
<attr name="colorSecondary100" format="color" />
|
||||
<attr name="colorSecondary200" format="color" />
|
||||
<attr name="colorSecondary300" format="color" />
|
||||
<attr name="colorSecondary400" format="color" />
|
||||
<attr name="colorSecondary500" format="color" />
|
||||
<attr name="colorSecondary600" format="color" />
|
||||
<attr name="colorSecondary700" format="color" />
|
||||
<attr name="colorSecondary800" format="color" />
|
||||
<attr name="colorSecondary900" format="color" />
|
||||
|
||||
<attr name="colorTertiary25" format="color" />
|
||||
<attr name="colorTertiary50" format="color" />
|
||||
<attr name="colorTertiary100" format="color" />
|
||||
<attr name="colorTertiary200" format="color" />
|
||||
<attr name="colorTertiary300" format="color" />
|
||||
<attr name="colorTertiary400" format="color" />
|
||||
<attr name="colorTertiary500" format="color" />
|
||||
<attr name="colorTertiary600" format="color" />
|
||||
<attr name="colorTertiary700" format="color" />
|
||||
<attr name="colorTertiary800" format="color" />
|
||||
<attr name="colorTertiary900" format="color" />
|
||||
|
||||
<attr name="colorNeutral25" format="color" />
|
||||
<attr name="colorNeutral50" format="color" />
|
||||
<attr name="colorNeutral50t" format="color" />
|
||||
<attr name="colorNeutral100" format="color" />
|
||||
<attr name="colorNeutral200" format="color" />
|
||||
<attr name="colorNeutral300" format="color" />
|
||||
<attr name="colorNeutral400" format="color" />
|
||||
<attr name="colorNeutral500" format="color" />
|
||||
<attr name="colorNeutral600" format="color" />
|
||||
<attr name="colorNeutral700" format="color" />
|
||||
<attr name="colorNeutral800" format="color" />
|
||||
<attr name="colorNeutral800t" format="color" />
|
||||
<attr name="colorNeutral900" format="color" />
|
||||
</resources>
|
|
@ -32,7 +32,6 @@
|
|||
<color name="primary_800">#ae218a</color>
|
||||
<color name="primary_900">#6d1556</color>
|
||||
|
||||
|
||||
<color name="error_25">#FFFBFA</color>
|
||||
<color name="error_50">#FEF3F2</color>
|
||||
<color name="error_100">#FEE4E2</color>
|
||||
|
@ -104,31 +103,69 @@
|
|||
<!-- M3 dynamic colors -->
|
||||
<color name="m3_navigation_bar_bg">@color/gray_50</color>
|
||||
|
||||
<color name="m3_gray_900">@color/gray_900</color>
|
||||
<color name="m3_gray_800t">@color/gray_800t</color>
|
||||
<color name="m3_gray_800">@color/gray_800</color>
|
||||
<color name="m3_gray_700">@color/gray_700</color>
|
||||
<color name="m3_gray_600">@color/gray_600</color>
|
||||
<color name="m3_gray_500">@color/gray_500</color>
|
||||
<color name="m3_gray_400">@color/gray_400</color>
|
||||
<color name="m3_gray_300">@color/gray_300</color>
|
||||
<color name="m3_gray_200">@color/gray_200</color>
|
||||
<color name="m3_gray_100">@color/gray_100</color>
|
||||
<color name="m3_gray_50t">@color/gray_50t</color>
|
||||
<color name="m3_gray_50">@color/gray_50</color>
|
||||
<color name="m3_gray_25">@color/gray_25</color>
|
||||
<color name="m3_neutral1_900">@color/gray_900</color>
|
||||
<color name="m3_neutral1_800t">@color/gray_800t</color>
|
||||
<color name="m3_neutral1_800">@color/gray_800</color>
|
||||
<color name="m3_neutral1_700">@color/gray_700</color>
|
||||
<color name="m3_neutral1_600">@color/gray_600</color>
|
||||
<color name="m3_neutral1_500">@color/gray_500</color>
|
||||
<color name="m3_neutral1_400">@color/gray_400</color>
|
||||
<color name="m3_neutral1_300">@color/gray_300</color>
|
||||
<color name="m3_neutral1_200">@color/gray_200</color>
|
||||
<color name="m3_neutral1_100">@color/gray_100</color>
|
||||
<color name="m3_neutral1_50t">@color/gray_50t</color>
|
||||
<color name="m3_neutral1_50">@color/gray_50</color>
|
||||
<color name="m3_neutral1_25">@color/gray_25</color>
|
||||
|
||||
<color name="m3_primary_25">@color/purple_primary_25</color>
|
||||
<color name="m3_primary_50">@color/purple_primary_50</color>
|
||||
<color name="m3_primary_100">@color/purple_primary_100</color>
|
||||
<color name="m3_primary_200">@color/purple_primary_200</color>
|
||||
<color name="m3_primary_300">@color/purple_primary_300</color>
|
||||
<color name="m3_primary_400">@color/purple_primary_400</color>
|
||||
<color name="m3_primary_500">@color/purple_primary_500</color>
|
||||
<color name="m3_primary_600">@color/purple_primary_600</color>
|
||||
<color name="m3_primary_700">@color/purple_primary_700</color>
|
||||
<color name="m3_primary_800">@color/purple_primary_800</color>
|
||||
<color name="m3_primary_900">@color/purple_primary_900</color>
|
||||
<color name="m3_neutral2_900">@color/gray_900</color>
|
||||
<color name="m3_neutral2_800t">@color/gray_800t</color>
|
||||
<color name="m3_neutral2_800">@color/gray_800</color>
|
||||
<color name="m3_neutral2_700">@color/gray_700</color>
|
||||
<color name="m3_neutral2_600">@color/gray_600</color>
|
||||
<color name="m3_neutral2_500">@color/gray_500</color>
|
||||
<color name="m3_neutral2_400">@color/gray_400</color>
|
||||
<color name="m3_neutral2_300">@color/gray_300</color>
|
||||
<color name="m3_neutral2_200">@color/gray_200</color>
|
||||
<color name="m3_neutral2_100">@color/gray_100</color>
|
||||
<color name="m3_neutral2_50t">@color/gray_50t</color>
|
||||
<color name="m3_neutral2_50">@color/gray_50</color>
|
||||
<color name="m3_neutral2_25">@color/gray_25</color>
|
||||
|
||||
<color name="m3_accent1_25">@color/primary_25</color>
|
||||
<color name="m3_accent1_50">@color/primary_50</color>
|
||||
<color name="m3_accent1_100">@color/primary_100</color>
|
||||
<color name="m3_accent1_200">@color/primary_200</color>
|
||||
<color name="m3_accent1_300">@color/primary_300</color>
|
||||
<color name="m3_accent1_400">@color/primary_400</color>
|
||||
<color name="m3_accent1_500">@color/primary_500</color>
|
||||
<color name="m3_accent1_600">@color/primary_600</color>
|
||||
<color name="m3_accent1_700">@color/primary_700</color>
|
||||
<color name="m3_accent1_800">@color/primary_800</color>
|
||||
<color name="m3_accent1_900">@color/primary_900</color>
|
||||
|
||||
<color name="m3_accent2_25">@color/primary_25</color>
|
||||
<color name="m3_accent2_50">@color/primary_50</color>
|
||||
<color name="m3_accent2_100">@color/primary_100</color>
|
||||
<color name="m3_accent2_200">@color/primary_200</color>
|
||||
<color name="m3_accent2_300">@color/primary_300</color>
|
||||
<color name="m3_accent2_400">@color/primary_400</color>
|
||||
<color name="m3_accent2_500">@color/primary_500</color>
|
||||
<color name="m3_accent2_600">@color/primary_600</color>
|
||||
<color name="m3_accent2_700">@color/primary_700</color>
|
||||
<color name="m3_accent2_800">@color/primary_800</color>
|
||||
<color name="m3_accent2_900">@color/primary_900</color>
|
||||
|
||||
<color name="m3_accent3_25">@color/primary_25</color>
|
||||
<color name="m3_accent3_50">@color/primary_50</color>
|
||||
<color name="m3_accent3_100">@color/primary_100</color>
|
||||
<color name="m3_accent3_200">@color/primary_200</color>
|
||||
<color name="m3_accent3_300">@color/primary_300</color>
|
||||
<color name="m3_accent3_400">@color/primary_400</color>
|
||||
<color name="m3_accent3_500">@color/primary_500</color>
|
||||
<color name="m3_accent3_600">@color/primary_600</color>
|
||||
<color name="m3_accent3_700">@color/primary_700</color>
|
||||
<color name="m3_accent3_800">@color/primary_800</color>
|
||||
<color name="m3_accent3_900">@color/primary_900</color>
|
||||
|
||||
<!-- light theme -->
|
||||
<color name="m3_sys_light_primary">#6750A4</color>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue