diff --git a/mastodon/src/main/AndroidManifest.xml b/mastodon/src/main/AndroidManifest.xml
index 69e170179..d7c114c33 100644
--- a/mastodon/src/main/AndroidManifest.xml
+++ b/mastodon/src/main/AndroidManifest.xml
@@ -62,7 +62,8 @@
-
+
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ExternalShareActivity.java b/mastodon/src/main/java/org/joinmastodon/android/ExternalShareActivity.java
index 38893027f..405a3e171 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ExternalShareActivity.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ExternalShareActivity.java
@@ -3,7 +3,6 @@ package org.joinmastodon.android;
import android.app.Fragment;
import android.content.ClipData;
import android.content.Intent;
-import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
@@ -12,6 +11,7 @@ import android.widget.Toast;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.ComposeFragment;
+import org.joinmastodon.android.ui.AccountSwitcherSheet;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.jsoup.internal.StringUtil;
@@ -28,18 +28,34 @@ public class ExternalShareActivity extends FragmentStackActivity{
UiUtils.setUserPreferredTheme(this);
super.onCreate(savedInstanceState);
if(savedInstanceState==null){
+
+ String text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
+ boolean isMastodonURL = UiUtils.looksLikeMastodonUrl(text);
+
List sessions=AccountSessionManager.getInstance().getLoggedInAccounts();
if(sessions.isEmpty()){
Toast.makeText(this, R.string.err_not_logged_in, Toast.LENGTH_SHORT).show();
finish();
- }else if(sessions.size()==1){
+ }else if(sessions.size()==1 && !isMastodonURL){
openComposeFragment(sessions.get(0).getID());
}else{
- getWindow().setBackgroundDrawable(new ColorDrawable(0xff000000));
- UiUtils.pickAccount(this, null, R.string.choose_account, 0,
- session -> openComposeFragment(session.getID()),
- b -> b.setOnCancelListener(d -> finish())
- );
+ new AccountSwitcherSheet(this, null, true, isMastodonURL, (accountId, open) -> {
+ if (open) {
+ UiUtils.lookupURL(this, accountId, text, 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();
}
}
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java b/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java
index 6b57c982a..dfab25f85 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java
@@ -37,10 +37,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){
@@ -54,6 +62,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);
@@ -77,11 +86,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);
+ AccountSessionManager.getInstance().getAccount(accountID);
}catch(IllegalStateException x){
return;
}
@@ -127,6 +137,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)
@@ -156,18 +179,23 @@ 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();
}
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java b/mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java
index b0466cb52..372f39e02 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java
@@ -25,6 +25,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;
@@ -33,6 +34,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;
@@ -273,8 +275,23 @@ public class PushNotificationReceiver extends BroadcastReceiver{
}
CharSequence input = remoteInput.getCharSequence(ACTION_KEY_TEXT_REPLY);
+ // copied from ComposeFragment - TODO: generalize?
+ ArrayList mentions=new ArrayList<>();
+ Status status = notification.status;
+ String ownID=AccountSessionManager.getInstance().getAccount(accountID).self.id;
+ if(!status.account.id.equals(ownID))
+ mentions.add('@'+status.account.acct);
+ for(Mention mention:status.mentions){
+ if(mention.id.equals(ownID))
+ continue;
+ String m='@'+mention.acct;
+ if(!mentions.contains(m))
+ mentions.add(m);
+ }
+ String initialText=mentions.isEmpty() ? "" : TextUtils.join(" ", mentions)+" ";
+
CreateStatus.Request req=new CreateStatus.Request();
- req.status = input.toString();
+ req.status = initialText + input.toString();
req.language = preferences.postingDefaultLanguage;
req.visibility = preferences.postingDefaultVisibility;
req.inReplyToId = notification.status.id;
@@ -282,7 +299,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
req.spoilerText = "re: " + notification.status.spoilerText;
}
- new CreateStatus(req, UUID.randomUUID().toString()).setCallback(new Callback() {
+ new CreateStatus(req, UUID.randomUUID().toString()).setCallback(new Callback<>() {
@Override
public void onSuccess(Status status) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java
index d94e11d41..cd9404d29 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java
@@ -15,6 +15,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;
@@ -89,7 +90,7 @@ public class AccountSession{
return pushSubscriptionManager;
}
- public Instance getInstance() {
- return AccountSessionManager.getInstance().getInstanceInfo(domain);
+ public Optional getInstance() {
+ return Optional.ofNullable(AccountSessionManager.getInstance().getInstanceInfo(domain));
}
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java
index ca3e5cba8..3b68ac36e 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java
@@ -125,14 +125,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);
@@ -256,31 +258,35 @@ public class AccountSessionManager{
}
public void maybeUpdateLocalInfo(){
+ maybeUpdateLocalInfo(null);
+ }
+
+ public void maybeUpdateLocalInfo(AccountSession activeSession){
long now=System.currentTimeMillis();
HashSet domains=new HashSet<>();
for(AccountSession session:sessions.values()){
domains.add(session.domain.toLowerCase());
-// if(now-session.infoLastUpdated>24L*3600_000L){
- updateSessionPreferences(session);
- updateSessionLocalInfo(session);
-// }
-// if(now-session.filtersLastUpdated>3600_000L){
- updateSessionWordFilters(session);
-// }
+ if(now-session.infoLastUpdated>24L*3600_000L || session == activeSession){
+ updateSessionPreferences(session);
+ updateSessionLocalInfo(session);
+ }
+ if(now-session.filtersLastUpdated>3600_000L || session == activeSession){
+ updateSessionWordFilters(session);
+ }
updateSessionMarkers(session);
}
if(loadedInstances){
- maybeUpdateCustomEmojis(domains);
+ maybeUpdateCustomEmojis(domains, activeSession != null ? activeSession.domain : null);
}
}
- private void maybeUpdateCustomEmojis(Set domains){
+ private void maybeUpdateCustomEmojis(Set domains, String activeDomain){
long now=System.currentTimeMillis();
for(String domain:domains){
-// Long lastUpdated=instancesLastUpdated.get(domain);
-// if(lastUpdated==null || now-lastUpdated>24L*3600_000L){
- updateInstanceInfo(domain);
-// }
+ Long lastUpdated=instancesLastUpdated.get(domain);
+ if(lastUpdated==null || now-lastUpdated>24L*3600_000L || domain.equals(activeDomain)){
+ updateInstanceInfo(domain);
+ }
}
}
@@ -408,7 +414,9 @@ public class AccountSessionManager{
@Override
public void onError(ErrorResponse error){
-
+ InstanceInfoStorageWrapper wrapper=new InstanceInfoStorageWrapper();
+ wrapper.instance = instance;
+ MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, domain));
}
})
.execNoAuth(domain);
@@ -419,10 +427,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);
}
@@ -442,7 +453,7 @@ public class AccountSessionManager{
}
if(!loadedInstances){
loadedInstances=true;
- maybeUpdateCustomEmojis(domains);
+ maybeUpdateCustomEmojis(domains, null);
}
}
@@ -463,12 +474,7 @@ public class AccountSessionManager{
}
public Instance getInstanceInfo(String domain){
- Instance instance = instances.get(domain);
- if (instance == null) {
- throw new IllegalStateException("Cannot get instance for " + domain + ". Sessions: "
- + String.join(", ", instances.keySet()));
- }
- return instance;
+ return instances.get(domain);
}
public void updateAccountInfo(String id, Account account){
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
index 1a174099a..c7054310e 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
@@ -263,9 +263,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"));
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java
index 0037c770c..59134d529 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java
@@ -2,6 +2,7 @@ package org.joinmastodon.android.fragments;
import android.app.Fragment;
import android.app.NotificationManager;
+import android.content.Intent;
import android.graphics.Outline;
import android.os.Build;
import android.os.Bundle;
@@ -17,7 +18,13 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import org.joinmastodon.android.DomainManager;
+import androidx.annotation.IdRes;
+import androidx.annotation.Nullable;
+
+import com.squareup.otto.Subscribe;
+
import org.joinmastodon.android.E;
+import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
import org.joinmastodon.android.api.session.AccountSession;
@@ -36,11 +43,7 @@ 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.api.Callback;
@@ -75,8 +78,9 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
E.register(this);
accountID=getArguments().getString("account");
setTitle(R.string.sk_app_name);
- Instance instance = AccountSessionManager.getInstance().getAccount(accountID).getInstance();
- isPleroma = instance.isPleroma();
+ isPleroma = AccountSessionManager.getInstance().getAccount(accountID).getInstance()
+ .map(Instance::isPleroma)
+ .orElse(false);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
setRetainInstance(true);
@@ -225,6 +229,13 @@ 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){
@@ -270,7 +281,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()).show();
+ new AccountSwitcherSheet(getActivity(), this).show();
return true;
}
return false;
@@ -303,10 +314,10 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
public void updateNotificationBadge() {
AccountSession session = AccountSessionManager.getInstance().getAccount(accountID);
- Instance instance = session.getInstance();
- if (instance == null) return;
+ Optional instance = session.getInstance();
+ if (instance.isEmpty()) return; // avoiding incompatibility with akkoma
- new GetNotifications(null, 1, EnumSet.allOf(Notification.Type.class), instance != null && instance.isPleroma())
+ new GetNotifications(null, 1, EnumSet.allOf(Notification.Type.class), instance.get().isPleroma())
.setCallback(new Callback<>() {
@Override
public void onSuccess(List notifications) {
@@ -343,4 +354,8 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
public void onAllNotificationsSeen(AllNotificationsSeenEvent allNotificationsSeenEvent) {
setNotificationBadge(false);
}
+
+ public String getAccountID() {
+ return accountID;
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java
index ba30cf5a6..9408cb8a2 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java
@@ -22,6 +22,7 @@ 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;
@@ -156,15 +157,15 @@ public class NotificationsListFragment extends BaseStatusListFragment instance = session.getInstance();
String instanceName = UiUtils.getInstanceName(accountID);
if(GithubSelfUpdater.needSelfUpdating()){
@@ -223,7 +223,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
GlobalUserPreferences.showReplies=i.checked;
GlobalUserPreferences.save();
}));
- if (instance.isPleroma()) {
+ if (instance.map(Instance::isPleroma).orElse(false)) {
items.add(new ButtonItem(R.string.sk_settings_reply_visibility, R.drawable.ic_fluent_chat_24_regular, b->{
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
popupMenu.inflate(R.menu.reply_visibility);
@@ -299,7 +299,9 @@ public class SettingsFragment extends MastodonToolbarFragment{
GlobalUserPreferences.save();
needAppRestart=true;
}));
- boolean translationAvailable = instance.v2 != null && instance.v2.configuration.translation != null && instance.v2.configuration.translation.enabled;
+ boolean translationAvailable = instance
+ .map(i -> i.v2 != null && i.v2.configuration.translation != null && i.v2.configuration.translation.enabled)
+ .orElse(false);
items.add(new SmallTextItem(getString(translationAvailable ?
R.string.sk_settings_translation_availability_note_available :
R.string.sk_settings_translation_availability_note_unavailable, instanceName)));
@@ -324,16 +326,18 @@ public class SettingsFragment extends MastodonToolbarFragment{
items.add(new TextItem(R.string.sk_settings_auth, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/auth/edit"), R.drawable.ic_fluent_open_24_regular));
items.add(new HeaderItem(instanceName));
- items.add(new TextItem(R.string.sk_settings_rules, ()->{
- Bundle args=new Bundle();
- args.putParcelable("instance", Parcels.wrap(instance));
+ items.add(new TextItem(R.string.sk_settings_rules, instance.map(i -> () -> {
+ Bundle args = new Bundle();
+ args.putParcelable("instance", Parcels.wrap(i));
Nav.go(getActivity(), InstanceRulesFragment.class, args);
- }, R.drawable.ic_fluent_task_list_ltr_24_regular));
+ }).orElse(null), R.drawable.ic_fluent_task_list_ltr_24_regular));
items.add(new TextItem(R.string.sk_settings_about_instance , ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/about"), R.drawable.ic_fluent_info_24_regular));
items.add(new TextItem(R.string.settings_tos, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular));
items.add(new TextItem(R.string.settings_privacy_policy, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular));
items.add(new TextItem(R.string.log_out, this::confirmLogOut, R.drawable.ic_fluent_sign_out_24_regular));
- if (!TextUtils.isEmpty(instance.version)) items.add(new SmallTextItem(getString(R.string.sk_settings_server_version, instance.version)));
+ items.add(new SmallTextItem(instance
+ .map(i -> getString(R.string.sk_settings_server_version, i.version))
+ .orElse(getString(R.string.sk_instance_info_unavailable))));
items.add(new HeaderItem(R.string.sk_instance_features));
items.add(new SwitchItem(R.string.sk_settings_content_types, 0, GlobalUserPreferences.accountsWithContentTypesEnabled.contains(accountID), (i)->{
@@ -361,14 +365,16 @@ public class SettingsFragment extends MastodonToolbarFragment{
b.setText(getContentTypeString(contentType));
contentTypeMenu = popupMenu.getMenu();
contentTypeMenu.findItem(ContentType.getContentTypeRes(contentType)).setChecked(true);
- ContentType.adaptMenuToInstance(contentTypeMenu, instance);
+ instance.ifPresent(i -> ContentType.adaptMenuToInstance(contentTypeMenu, i));
}));
items.add(new SmallTextItem(getString(R.string.sk_settings_default_content_type_explanation)));
items.add(new SwitchItem(R.string.sk_settings_support_local_only, 0, GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID), i->{
glitchModeItem.enabled = i.checked;
if (i.checked) {
GlobalUserPreferences.accountsWithLocalOnlySupport.add(accountID);
- if (instance.pleroma == null) GlobalUserPreferences.accountsInGlitchMode.add(accountID);
+ if (!instance.map(Instance::isPleroma).orElse(false)) {
+ GlobalUserPreferences.accountsInGlitchMode.add(accountID);
+ }
} else {
GlobalUserPreferences.accountsWithLocalOnlySupport.remove(accountID);
GlobalUserPreferences.accountsInGlitchMode.remove(accountID);
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/AccountActivationFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/AccountActivationFragment.java
index 92eb026ad..b1a77e9c0 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/AccountActivationFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/AccountActivationFragment.java
@@ -89,13 +89,6 @@ public class AccountActivationFragment extends ToolbarFragment{
return !UiUtils.isDarkTheme();
}
- @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();
@@ -110,7 +103,7 @@ public class AccountActivationFragment extends ToolbarFragment{
@Override
public void onToolbarNavigationClick(){
- new AccountSwitcherSheet(getActivity()).show();
+ new AccountSwitcherSheet(getActivity(), null).show();
}
@Override
diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java b/mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java
index bff0046b3..210e649d1 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java
@@ -259,13 +259,14 @@ public class TimelineDefinition {
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().isPleroma();
+ return session.getInstance().map(Instance::isPleroma).orElse(false);
}
@Override
public boolean wantsDefault(AccountSession session) {
- Instance instance = session.getInstance();
- return instance.isPleroma() && instance.pleroma.metadata.features.contains("bubble_timeline");
+ return session.getInstance()
+ .map(i -> i.isPleroma() && i.pleroma.metadata.features.contains("bubble_timeline"))
+ .orElse(false);
}
};
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/AccountSwitcherSheet.java b/mastodon/src/main/java/org/joinmastodon/android/ui/AccountSwitcherSheet.java
index 42e373f61..6c5cac475 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/AccountSwitcherSheet.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/AccountSwitcherSheet.java
@@ -2,8 +2,8 @@ package org.joinmastodon.android.ui;
import android.annotation.SuppressLint;
import android.app.Activity;
+import android.app.ProgressDialog;
import android.content.Intent;
-import android.content.res.ColorStateList;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -14,7 +14,7 @@ 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 org.joinmastodon.android.GlobalUserPreferences;
@@ -23,13 +23,21 @@ 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.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;
@@ -49,14 +57,25 @@ import me.grishka.appkit.views.UsableRecyclerView;
public class AccountSwitcherSheet extends BottomSheet{
private final Activity activity;
+ private final HomeFragment fragment;
+ private final BiConsumer onClick;
+ private final boolean externalShare, openInApp;
private UsableRecyclerView list;
private List accounts;
private ListImageLoaderWrapper imgLoader;
- public AccountSwitcherSheet(@NonNull Activity activity){
+ public AccountSwitcherSheet(@NonNull Activity activity, @Nullable HomeFragment fragment){
+ this(activity, fragment, false, false, null);
+ }
+
+ public AccountSwitcherSheet(@NonNull Activity activity, @Nullable HomeFragment fragment, boolean externalShare, boolean openInApp, BiConsumer onClick){
super(activity);
this.activity=activity;
-
+ this.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);
@@ -67,41 +86,59 @@ public class AccountSwitcherSheet extends BottomSheet{
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
View handle=new View(activity);
handle.setBackgroundResource(R.drawable.bg_bottom_sheet_handle);
+ handle.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(36)));
+
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(new AccountsAdapter());
- AccountViewHolder holder=new AccountViewHolder();
- holder.more.setVisibility(View.GONE);
- holder.currentIcon.setVisibility(View.GONE);
- holder.name.setText(R.string.add_account);
- 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, ()->{
- Nav.go(activity, CustomWelcomeFragment.class, null);
- dismiss();
- }));
+
+ if (!externalShare) {
+ adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(makeSimpleListItem(R.string.add_account, R.drawable.ic_add_24px), () -> {
+ Nav.go(activity, CustomWelcomeFragment.class, 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)
@@ -120,9 +157,52 @@ public class AccountSwitcherSheet extends BottomSheet{
.exec(accountID);
}
+ private void logOutAll(){
+ final ProgressDialog progress=new ProgressDialog(activity);
+ progress.setMessage(activity.getString(R.string.loading));
+ progress.setCancelable(false);
+ progress.show();
+ ArrayList sessions=new ArrayList<>(AccountSessionManager.getInstance().getLoggedInAccounts());
+ for(AccountSession session:sessions){
+ new RevokeOauthToken(session.app.clientId, session.app.clientSecret, session.token.accessToken)
+ .setCallback(new Callback<>(){
+ @Override
+ public void onSuccess(Object result){
+ AccountSessionManager.getInstance().removeAccount(session.getID());
+ sessions.remove(session);
+ if(sessions.isEmpty()){
+ progress.dismiss();
+ Nav.goClearingStack(activity, SplashFragment.class, null);
+ dismiss();
+ }
+ }
+
+ @Override
+ public void onError(ErrorResponse error){
+ AccountSessionManager.getInstance().removeAccount(session.getID());
+ sessions.remove(session);
+ if(sessions.isEmpty()){
+ progress.dismiss();
+ Nav.goClearingStack(activity, SplashFragment.class, null);
+ dismiss();
+ }
+ }
+ })
+ .exec(session.getID());
+ }
+ }
+
private void onLoggedOut(String accountID){
AccountSessionManager.getInstance().removeAccount(accountID);
- dismiss();
+ String activeAccountID = fragment != null
+ ? fragment.getAccountID()
+ : AccountSessionManager.getInstance().getLastActiveAccountID();
+ if (accountID.equals(activeAccountID)) {
+ activity.finish();
+ activity.startActivity(new Intent(activity, MainActivity.class));
+ } else {
+ dismiss();
+ }
}
@Override
@@ -140,6 +220,13 @@ public class AccountSwitcherSheet extends BottomSheet{
}
}
+ private View makeSimpleListItem(@StringRes int title, @DrawableRes int icon){
+ TextView tv=(TextView) activity.getLayoutInflater().inflate(R.layout.item_text_with_icon, list, false);
+ tv.setText(title);
+ tv.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, 0, 0, 0);
+ return tv;
+ }
+
private class AccountsAdapter extends UsableRecyclerView.Adapter implements ImageLoaderRecyclerAdapter{
public AccountsAdapter(){
super(imgLoader);
@@ -173,45 +260,42 @@ public class AccountSwitcherSheet extends BottomSheet{
}
}
- private class AccountViewHolder extends BindableViewHolder implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
- private final TextView name;
+ private class AccountViewHolder extends BindableViewHolder implements ImageLoaderViewHolder, UsableRecyclerView.Clickable, UsableRecyclerView.LongClickable{
+ private final TextView name, username;
private final ImageView avatar;
- private final ImageButton more;
- private final View currentIcon;
- private final PopupMenu menu;
+ private final CheckableRelativeLayout view;
+ private final View radioButton, extraBtnWrap;
+ private final ImageButton extraBtn;
public AccountViewHolder(){
super(activity, R.layout.item_account_switcher, list);
name=findViewById(R.id.name);
+ 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){
- 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()));
}
- menu.getMenu().findItem(R.id.log_out).setTitle(activity.getString(R.string.log_out_account, "@"+item.self.username));
- UiUtils.enablePopupMenuIcons(activity, menu);
}
@Override
@@ -226,12 +310,32 @@ 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());
activity.finish();
activity.startActivity(new Intent(activity, MainActivity.class));
}
+
+ @Override
+ public boolean onLongClick(){
+ if (externalShare) return false;
+ confirmLogOut(item.getID());
+ return true;
+ }
}
private static class WrappedAccount{
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/OutlineProviders.java b/mastodon/src/main/java/org/joinmastodon/android/ui/OutlineProviders.java
index 921598225..81f05df36 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/OutlineProviders.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/OutlineProviders.java
@@ -8,7 +8,15 @@ import android.view.ViewOutlineProvider;
import me.grishka.appkit.utils.V;
public class OutlineProviders{
- private static SparseArray roundedRects=new SparseArray<>();
+ private static final SparseArray roundedRects=new SparseArray<>();
+ private static final SparseArray topRoundedRects=new SparseArray<>();
+ private static final SparseArray endRoundedRects=new SparseArray<>();
+
+ public static final int RADIUS_XSMALL=4;
+ public static final int RADIUS_SMALL=8;
+ public static final int RADIUS_MEDIUM=12;
+ public static final int RADIUS_LARGE=16;
+ public static final int RADIUS_XLARGE=28;
private OutlineProviders(){
//no instance
@@ -21,6 +29,12 @@ public class OutlineProviders{
outline.setAlpha(view.getAlpha());
}
};
+ public static final ViewOutlineProvider OVAL=new ViewOutlineProvider(){
+ @Override
+ public void getOutline(View view, Outline outline){
+ outline.setOval(0, 0, view.getWidth(), view.getHeight());
+ }
+ };
public static ViewOutlineProvider roundedRect(int dp){
ViewOutlineProvider provider=roundedRects.get(dp);
@@ -31,6 +45,24 @@ public class OutlineProviders{
return provider;
}
+ public static ViewOutlineProvider topRoundedRect(int dp){
+ ViewOutlineProvider provider=topRoundedRects.get(dp);
+ if(provider!=null)
+ return provider;
+ provider=new TopRoundRectOutlineProvider(V.dp(dp));
+ topRoundedRects.put(dp, provider);
+ return provider;
+ }
+
+ public static ViewOutlineProvider endRoundedRect(int dp){
+ ViewOutlineProvider provider=endRoundedRects.get(dp);
+ if(provider!=null)
+ return provider;
+ provider=new EndRoundRectOutlineProvider(V.dp(dp));
+ endRoundedRects.put(dp, provider);
+ return provider;
+ }
+
private static class RoundRectOutlineProvider extends ViewOutlineProvider{
private final int radius;
@@ -43,4 +75,34 @@ public class OutlineProviders{
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), radius);
}
}
+
+ private static class TopRoundRectOutlineProvider extends ViewOutlineProvider{
+ private final int radius;
+
+ private TopRoundRectOutlineProvider(int radius){
+ this.radius=radius;
+ }
+
+ @Override
+ public void getOutline(View view, Outline outline){
+ outline.setRoundRect(0, 0, view.getWidth(), view.getHeight()+radius, radius);
+ }
+ }
+
+ private static class EndRoundRectOutlineProvider extends ViewOutlineProvider{
+ private final int radius;
+
+ private EndRoundRectOutlineProvider(int radius){
+ this.radius=radius;
+ }
+
+ @Override
+ public void getOutline(View view, Outline outline){
+ if(view.getLayoutDirection()==View.LAYOUT_DIRECTION_RTL){
+ outline.setRoundRect(-radius, 0, view.getWidth(), view.getHeight(), radius);
+ }else{
+ outline.setRoundRect(0, 0, view.getWidth()+radius, view.getHeight(), radius);
+ }
+ }
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
index ffc3c7b38..f2b3f1287 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
@@ -7,6 +7,7 @@ import static org.joinmastodon.android.GlobalUserPreferences.trueBlackTheme;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
+import android.app.Fragment;
import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
@@ -110,6 +111,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -948,8 +950,8 @@ public class UiUtils {
public static String getInstanceName(String accountID) {
AccountSession session = AccountSessionManager.getInstance().getAccount(accountID);
- Instance instance = session.getInstance();
- return instance != null && !instance.title.isBlank() ? instance.title : session.domain;
+ Optional instance = session.getInstance();
+ return instance.isPresent() && !instance.get().title.isBlank() ? instance.get().title : session.domain;
}
public static void pickAccount(Context context, String exceptFor, @StringRes int titleRes, @DrawableRes int iconRes, Consumer sessionConsumer, Consumer transformDialog) {
@@ -1080,6 +1082,13 @@ public class UiUtils {
}
public static void openURL(Context context, String accountID, String url, boolean launchBrowser) {
+ lookupURL(context, accountID, url, launchBrowser, (clazz, args) -> {
+ if (clazz == null) return;
+ Nav.go((Activity) context, clazz, args);
+ });
+ }
+
+ public static void lookupURL(Context context, String accountID, String url, boolean launchBrowser, BiConsumer, Bundle> go) {
Uri uri = Uri.parse(url);
List path = uri.getPathSegments();
if (accountID != null && "https".equals(uri.getScheme())) {
@@ -1091,13 +1100,14 @@ public class UiUtils {
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) {
error.showToast(context);
if (launchBrowser) launchWebBrowser(context, url);
+ go.accept(null, null);
}
})
.wrapProgress((Activity) context, R.string.loading, true,
@@ -1113,27 +1123,26 @@ public class UiUtils {
args.putString("account", accountID);
if (!results.statuses.isEmpty()) {
args.putParcelable("status", Parcels.wrap(results.statuses.get(0)));
- Nav.go((Activity) context, ThreadFragment.class, args);
+ go.accept(ThreadFragment.class, args);
return;
}
Optional account = results.accounts.stream()
.filter(a -> uri.equals(Uri.parse(a.url))).findAny();
if (account.isPresent()) {
args.putParcelable("profileAccount", Parcels.wrap(account.get()));
- Nav.go((Activity) context, ProfileFragment.class, args);
- return;
- }
- if (launchBrowser) {
- launchWebBrowser(context, url);
+ 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,
@@ -1142,7 +1151,8 @@ public class UiUtils {
return;
}
}
- launchWebBrowser(context, url);
+ if (launchBrowser) launchWebBrowser(context, url);
+ go.accept(null, null);
}
public static void copyText(View v, String text) {
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/CheckableRelativeLayout.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/CheckableRelativeLayout.java
new file mode 100644
index 000000000..b5f4d5b36
--- /dev/null
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/CheckableRelativeLayout.java
@@ -0,0 +1,62 @@
+package org.joinmastodon.android.ui.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.Checkable;
+import android.widget.RelativeLayout;
+
+public class CheckableRelativeLayout extends RelativeLayout implements Checkable{
+ private boolean checked, checkable = true;
+ private static final int[] CHECKED_STATE_SET = {
+ android.R.attr.state_checked
+ };
+
+ public CheckableRelativeLayout(Context context){
+ this(context, null);
+ }
+
+ public CheckableRelativeLayout(Context context, AttributeSet attrs){
+ this(context, attrs, 0);
+ }
+
+ public CheckableRelativeLayout(Context context, AttributeSet attrs, int defStyle){
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void setChecked(boolean checked){
+ this.checked=checked;
+ refreshDrawableState();
+ }
+
+ public void setCheckable(boolean checkable) {
+ this.checkable = checkable;
+ }
+
+ @Override
+ public boolean isChecked(){
+ return checked;
+ }
+
+ @Override
+ public void toggle(){
+ setChecked(!checked);
+ }
+
+ @Override
+ protected int[] onCreateDrawableState(int extraSpace) {
+ final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+ if (isChecked()) {
+ mergeDrawableStates(drawableState, CHECKED_STATE_SET);
+ }
+ return drawableState;
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info){
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setCheckable(checkable);
+ info.setChecked(checked);
+ }
+}
diff --git a/mastodon/src/main/res/drawable/ic_fluent_share_28_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_share_28_regular.xml
new file mode 100644
index 000000000..6fa0d5917
--- /dev/null
+++ b/mastodon/src/main/res/drawable/ic_fluent_share_28_regular.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/mastodon/src/main/res/layout/item_account_switcher.xml b/mastodon/src/main/res/layout/item_account_switcher.xml
index 1cffbf7af..f4e5134b6 100644
--- a/mastodon/src/main/res/layout/item_account_switcher.xml
+++ b/mastodon/src/main/res/layout/item_account_switcher.xml
@@ -1,44 +1,81 @@
-
-
-
+ 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" />
-
+
+
+
+
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mastodon/src/main/res/layout/item_external_share_heading.xml b/mastodon/src/main/res/layout/item_external_share_heading.xml
new file mode 100644
index 000000000..3086dee74
--- /dev/null
+++ b/mastodon/src/main/res/layout/item_external_share_heading.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mastodon/src/main/res/layout/item_text_with_icon.xml b/mastodon/src/main/res/layout/item_text_with_icon.xml
new file mode 100644
index 000000000..01c27abab
--- /dev/null
+++ b/mastodon/src/main/res/layout/item_text_with_icon.xml
@@ -0,0 +1,15 @@
+
+
\ No newline at end of file
diff --git a/mastodon/src/main/res/values-v31/colors.xml b/mastodon/src/main/res/values-v31/colors.xml
index 8937b4048..b622faf32 100644
--- a/mastodon/src/main/res/values-v31/colors.xml
+++ b/mastodon/src/main/res/values-v31/colors.xml
@@ -4,32 +4,57 @@
@android:color/system_neutral1_50
- @android:color/system_neutral1_900
- @android:color/system_neutral1_800
- @android:color/system_neutral1_800
- @android:color/system_neutral1_700
- @android:color/system_neutral1_600
- @android:color/system_neutral1_500
- @android:color/system_neutral1_400
- @android:color/system_neutral1_300
- @android:color/system_neutral1_200
- @android:color/system_neutral1_100
- @android:color/system_neutral1_50
- @android:color/system_neutral1_50
- @android:color/system_neutral1_10
+ @android:color/system_neutral1_900
+ @android:color/system_neutral1_800
+ @android:color/system_neutral1_800
+ @android:color/system_neutral1_700
+ @android:color/system_neutral1_600
+ @android:color/system_neutral1_500
+ @android:color/system_neutral1_400
+ @android:color/system_neutral1_300
+ @android:color/system_neutral1_200
+ @android:color/system_neutral1_100
+ @android:color/system_neutral1_50
+ @android:color/system_neutral1_50
+ @android:color/system_neutral1_10
- @android:color/system_accent1_10
- @android:color/system_accent1_50
- @android:color/system_accent1_100
- @android:color/system_accent1_200
- @android:color/system_accent1_300
- @android:color/system_accent1_400
- @android:color/system_accent1_500
- @android:color/system_accent1_600
- @android:color/system_accent1_700
- @android:color/system_accent1_800
- @android:color/system_accent1_900
+ @android:color/system_accent1_10
+ @android:color/system_accent1_50
+ @android:color/system_accent1_100
+ @android:color/system_accent1_200
+ @android:color/system_accent1_300
+ @android:color/system_accent1_400
+ @android:color/system_accent1_500
+ @android:color/system_accent1_600
+ @android:color/system_accent1_700
+ @android:color/system_accent1_800
+ @android:color/system_accent1_900
+ @android:color/system_neutral2_900
+ @android:color/system_neutral2_800
+ @android:color/system_neutral2_800
+ @android:color/system_neutral2_700
+ @android:color/system_neutral2_600
+ @android:color/system_neutral2_500
+ @android:color/system_neutral2_400
+ @android:color/system_neutral2_300
+ @android:color/system_neutral2_200
+ @android:color/system_neutral2_100
+ @android:color/system_neutral2_50
+ @android:color/system_neutral2_50
+ @android:color/system_neutral2_10
+
+ @android:color/system_accent2_10
+ @android:color/system_accent2_50
+ @android:color/system_accent2_100
+ @android:color/system_accent2_200
+ @android:color/system_accent2_300
+ @android:color/system_accent2_400
+ @android:color/system_accent2_500
+ @android:color/system_accent2_600
+ @android:color/system_accent2_700
+ @android:color/system_accent2_800
+ @android:color/system_accent2_900
@android:color/system_accent1_600
diff --git a/mastodon/src/main/res/values/attrs.xml b/mastodon/src/main/res/values/attrs.xml
index 6c0e11247..5755462c2 100644
--- a/mastodon/src/main/res/values/attrs.xml
+++ b/mastodon/src/main/res/values/attrs.xml
@@ -106,4 +106,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mastodon/src/main/res/values/colors.xml b/mastodon/src/main/res/values/colors.xml
index 7e403a784..74cdd7449 100644
--- a/mastodon/src/main/res/values/colors.xml
+++ b/mastodon/src/main/res/values/colors.xml
@@ -103,31 +103,69 @@
@color/gray_50
- @color/gray_900
- @color/gray_800t
- @color/gray_800
- @color/gray_700
- @color/gray_600
- @color/gray_500
- @color/gray_400
- @color/gray_300
- @color/gray_200
- @color/gray_100
- @color/gray_50t
- @color/gray_50
- @color/gray_25
+ @color/gray_900
+ @color/gray_800t
+ @color/gray_800
+ @color/gray_700
+ @color/gray_600
+ @color/gray_500
+ @color/gray_400
+ @color/gray_300
+ @color/gray_200
+ @color/gray_100
+ @color/gray_50t
+ @color/gray_50
+ @color/gray_25
- @color/primary_25
- @color/primary_50
- @color/primary_100
- @color/primary_200
- @color/primary_300
- @color/primary_400
- @color/primary_500
- @color/primary_600
- @color/primary_700
- @color/primary_800
- @color/primary_900
+ @color/gray_900
+ @color/gray_800t
+ @color/gray_800
+ @color/gray_700
+ @color/gray_600
+ @color/gray_500
+ @color/gray_400
+ @color/gray_300
+ @color/gray_200
+ @color/gray_100
+ @color/gray_50t
+ @color/gray_50
+ @color/gray_25
+
+ @color/primary_25
+ @color/primary_50
+ @color/primary_100
+ @color/primary_200
+ @color/primary_300
+ @color/primary_400
+ @color/primary_500
+ @color/primary_600
+ @color/primary_700
+ @color/primary_800
+ @color/primary_900
+
+ @color/primary_25
+ @color/primary_50
+ @color/primary_100
+ @color/primary_200
+ @color/primary_300
+ @color/primary_400
+ @color/primary_500
+ @color/primary_600
+ @color/primary_700
+ @color/primary_800
+ @color/primary_900
+
+ @color/primary_25
+ @color/primary_50
+ @color/primary_100
+ @color/primary_200
+ @color/primary_300
+ @color/primary_400
+ @color/primary_500
+ @color/primary_600
+ @color/primary_700
+ @color/primary_800
+ @color/primary_900
#6750A4
diff --git a/mastodon/src/main/res/values/palettes.xml b/mastodon/src/main/res/values/palettes.xml
index 4c4c9caf7..3a285c813 100644
--- a/mastodon/src/main/res/values/palettes.xml
+++ b/mastodon/src/main/res/values/palettes.xml
@@ -26,34 +26,115 @@
- @color/gray_50t
- @color/gray_50
- @color/gray_25
+
+
+
+ - @color/primary_25
+ - @color/primary_50
+ - @color/primary_100
+ - @color/primary_200
+ - @color/primary_300
+ - @color/primary_400
+ - @color/primary_500
+ - @color/primary_600
+ - @color/primary_700
+ - @color/primary_800
+ - @color/primary_900
+
+ - @color/primary_25
+ - @color/primary_50
+ - @color/primary_100
+ - @color/primary_200
+ - @color/primary_300
+ - @color/primary_400
+ - @color/primary_500
+ - @color/primary_600
+ - @color/primary_700
+ - @color/primary_800
+ - @color/primary_900
+
+ - @color/gray_900
+ - @color/gray_800t
+ - @color/gray_800
+ - @color/gray_700
+ - @color/gray_600
+ - @color/gray_500
+ - @color/gray_400
+ - @color/gray_300
+ - @color/gray_200
+ - @color/gray_100
+ - @color/gray_50t
+ - @color/gray_50
+ - @color/gray_25
+
@@ -447,6 +458,11 @@
- center_vertical
+
+