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..a862c61bd 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,25 @@ 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) -> {
+ AccountSessionManager.getInstance().setLastActiveAccountID(accountId);
+ if (open) {
+ UiUtils.openURL(this, AccountSessionManager.getInstance().getLastActiveAccountID(), text, false);
+ } else {
+ openComposeFragment(accountId);
+ }
+ }).show();
}
}
}
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 a260c2111..ae436b4a3 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;
@@ -16,7 +17,13 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
+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;
@@ -37,11 +44,6 @@ import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
-import androidx.annotation.IdRes;
-import androidx.annotation.Nullable;
-
-import com.squareup.otto.Subscribe;
-
import me.grishka.appkit.FragmentStackActivity;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
@@ -224,6 +226,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){
@@ -265,7 +274,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;
@@ -338,4 +347,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/ProfileFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java
index 6e1d81a99..780052ec3 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java
@@ -92,6 +92,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.viewpager2.widget.ViewPager2;
+
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
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/ui/AccountSwitcherSheet.java b/mastodon/src/main/java/org/joinmastodon/android/ui/AccountSwitcherSheet.java
index 42e373f61..04f44dc9b 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,6 +157,41 @@ 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();
@@ -140,6 +212,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 +252,37 @@ 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);
- }
- menu.getMenu().findItem(R.id.log_out).setTitle(activity.getString(R.string.log_out_account, "@"+item.self.username));
- UiUtils.enablePopupMenuIcons(activity, menu);
+ name.setText(item.self.displayName);
+ username.setText(item.getFullUsername());
+ view.setChecked(AccountSessionManager.getInstance().getLastActiveAccountID().equals(item.getID()));
+ radioButton.setVisibility(externalShare ? View.GONE : View.VISIBLE);
+ extraBtnWrap.setVisibility(externalShare && openInApp ? View.VISIBLE : View.GONE);
+ if (externalShare) view.setCheckable(false);
}
@Override
@@ -226,12 +297,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/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..b5d4a5a15
--- /dev/null
+++ b/mastodon/src/main/res/layout/item_external_share_heading.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
\ 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/strings.xml b/mastodon/src/main/res/values/strings.xml
index 725c0b350..997d3014c 100644
--- a/mastodon/src/main/res/values/strings.xml
+++ b/mastodon/src/main/res/values/strings.xml
@@ -452,4 +452,6 @@
Mastodon is a decentralized social network, meaning no single company controls it. It’s made up of many independently-run servers, all connected together.
What are servers?
+ Log out of all accounts
+ Log out of all accounts?
diff --git a/mastodon/src/main/res/values/strings_sk.xml b/mastodon/src/main/res/values/strings_sk.xml
index 228a276c9..d8900891b 100644
--- a/mastodon/src/main/res/values/strings_sk.xml
+++ b/mastodon/src/main/res/values/strings_sk.xml
@@ -289,4 +289,7 @@
Default content type
This lets you have a content type be pre-selected when creating new posts, overriding the value set in “Posting preferences”.
Instance info temporarily unavailable
+ Open in app
+ Share with account
+ Share or open with account
\ No newline at end of file
diff --git a/mastodon/src/main/res/values/styles.xml b/mastodon/src/main/res/values/styles.xml
index 2657ebc75..d44c60df1 100644
--- a/mastodon/src/main/res/values/styles.xml
+++ b/mastodon/src/main/res/values/styles.xml
@@ -1,5 +1,15 @@
+
+
+