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 + +