Merge remote-tracking branch 'megalodon_main/main'
# Conflicts: # mastodon/build.gradle # mastodon/src/main/java/org/joinmastodon/android/ExternalShareActivity.java # mastodon/src/main/java/org/joinmastodon/android/MainActivity.java # mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java # mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/EditTimelinesFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/FollowRequestsListFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/FollowedHashtagsFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelinesFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/FollowerListFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/FollowingListFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/StatusFavoritesListFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/StatusReblogsListFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverAccountsFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverHashtagsFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverNewsFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/discover/FederatedTimelineFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/discover/LocalTimelineFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/discover/SearchFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/AccountActivationFragment.java # mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java # mastodon/src/main/java/org/joinmastodon/android/ui/AccountSwitcherSheet.java # mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java # mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HashtagStatusDisplayItem.java # mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java # mastodon/src/main/res/layout/item_account_switcher.xml # mastodon/src/main/res/values-ar-rDZ/strings_sk.xml # mastodon/src/main/res/values-es-rES/strings_sk.xml # mastodon/src/main/res/values-nl-rNL/strings_sk.xml # mastodon/src/main/res/values-pt-rPT/strings_sk.xml # mastodon/src/main/res/values-v31/colors.xml # mastodon/src/main/res/values/colors.xml # mastodon/src/main/res/values/styles.xml
This commit is contained in:
@@ -2,7 +2,8 @@ package org.joinmastodon.android.ui;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
@@ -13,24 +14,31 @@ import android.view.WindowInsets;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.HomeFragment;
|
||||
import org.joinmastodon.android.fragments.SplashFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.CheckableRelativeLayout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
@@ -49,18 +57,26 @@ import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class AccountSwitcherSheet extends BottomSheet{
|
||||
private final Activity activity;
|
||||
private final HomeFragment fragment;
|
||||
private final BiConsumer<String, Boolean> onClick;
|
||||
private final boolean externalShare, openInApp;
|
||||
private UsableRecyclerView list;
|
||||
private List<WrappedAccount> accounts;
|
||||
private ListImageLoaderWrapper imgLoader;
|
||||
private final boolean logOutEnabled;
|
||||
private final Consumer<AccountSession> onClick;
|
||||
private AccountsAdapter accountsAdapter;
|
||||
|
||||
public AccountSwitcherSheet(@NonNull Activity activity, boolean logOutEnabled, boolean addAccountEnabled, boolean showOpenURL, Consumer<AccountSession> onClick){
|
||||
public AccountSwitcherSheet(@NonNull Activity activity, @Nullable HomeFragment fragment){
|
||||
this(activity, fragment, false, false, null);
|
||||
}
|
||||
|
||||
public AccountSwitcherSheet(@NonNull Activity activity, @Nullable HomeFragment fragment, boolean externalShare, boolean openInApp, BiConsumer<String, Boolean> onClick){
|
||||
super(activity);
|
||||
this.activity=activity;
|
||||
this.logOutEnabled=logOutEnabled;
|
||||
this.onClick=onClick;
|
||||
|
||||
this.fragment=fragment;
|
||||
this.externalShare = externalShare;
|
||||
this.openInApp = openInApp;
|
||||
this.onClick = onClick;
|
||||
|
||||
accounts=AccountSessionManager.getInstance().getLoggedInAccounts().stream().map(WrappedAccount::new).collect(Collectors.toList());
|
||||
|
||||
list=new UsableRecyclerView(activity);
|
||||
@@ -71,61 +87,59 @@ public class AccountSwitcherSheet extends BottomSheet{
|
||||
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
|
||||
View handle=new View(activity);
|
||||
handle.setBackgroundResource(R.drawable.bg_bottom_sheet_handle);
|
||||
adapter.addAdapter(new SingleViewRecyclerAdapter(handle));
|
||||
adapter.addAdapter(new AccountsAdapter());
|
||||
handle.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(36)));
|
||||
|
||||
if(addAccountEnabled){
|
||||
AccountViewHolder holder = new AccountViewHolder();
|
||||
holder.more.setVisibility(View.GONE);
|
||||
holder.currentIcon.setVisibility(View.GONE);
|
||||
holder.display_name.setVisibility(View.GONE);
|
||||
holder.display_add_account.setVisibility(View.VISIBLE);
|
||||
holder.avatar.setScaleType(ImageView.ScaleType.CENTER);
|
||||
holder.avatar.setImageResource(R.drawable.ic_fluent_add_circle_24_filled);
|
||||
holder.avatar.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(activity, android.R.attr.textColorPrimary)));
|
||||
adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(holder.itemView, () -> {
|
||||
adapter.addAdapter(new SingleViewRecyclerAdapter(handle));
|
||||
|
||||
if (externalShare) {
|
||||
FrameLayout shareHeading = new FrameLayout(activity);
|
||||
activity.getLayoutInflater().inflate(R.layout.item_external_share_heading, shareHeading);
|
||||
((TextView) shareHeading.findViewById(R.id.title)).setText(openInApp
|
||||
? R.string.sk_external_share_or_open_title
|
||||
: R.string.sk_external_share_title);
|
||||
adapter.addAdapter(new SingleViewRecyclerAdapter(shareHeading));
|
||||
|
||||
setOnDismissListener((d) -> activity.finish());
|
||||
}
|
||||
|
||||
adapter.addAdapter(accountsAdapter = new AccountsAdapter());
|
||||
|
||||
if (!externalShare) {
|
||||
adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(makeSimpleListItem(R.string.add_account, R.drawable.ic_fluent_add_24_regular), () -> {
|
||||
Nav.go(activity, CustomWelcomeFragment.class, null);
|
||||
dismiss();
|
||||
}));
|
||||
}
|
||||
|
||||
if(showOpenURL) {
|
||||
AccountViewHolder holder = new AccountViewHolder();
|
||||
holder.more.setVisibility(View.GONE);
|
||||
holder.currentIcon.setVisibility(View.GONE);
|
||||
holder.display_name.setVisibility(View.VISIBLE);
|
||||
holder.display_add_account.setVisibility(View.VISIBLE);
|
||||
holder.display_add_account.setText(R.string.mo_share_open_url);
|
||||
holder.avatar.setScaleType(ImageView.ScaleType.CENTER);
|
||||
holder.avatar.setImageResource(R.drawable.ic_fluent_open_24_regular);
|
||||
holder.avatar.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(activity, android.R.attr.textColorPrimary)));
|
||||
adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(holder.itemView, () -> {
|
||||
onClick.accept(null);
|
||||
dismiss();
|
||||
}));
|
||||
// disabled in megalodon
|
||||
// adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(makeSimpleListItem(R.string.log_out_all_accounts, R.drawable.ic_fluent_person_arrow_right_24_filled), this::confirmLogOutAll));
|
||||
}
|
||||
|
||||
list.setAdapter(adapter);
|
||||
DividerItemDecoration divider=new DividerItemDecoration(activity, R.attr.colorPollVoted, .5f, 72, 16, DividerItemDecoration.NOT_FIRST);
|
||||
divider.setDrawBelowLastItem(true);
|
||||
list.addItemDecoration(divider);
|
||||
|
||||
FrameLayout content=new FrameLayout(activity);
|
||||
content.setBackgroundResource(R.drawable.bg_bottom_sheet);
|
||||
content.addView(list);
|
||||
setContentView(content);
|
||||
setNavigationBarBackground(new ColorDrawable(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground)), !UiUtils.isDarkTheme());
|
||||
setNavigationBarBackground(new ColorDrawable(UiUtils.alphaBlendColors(UiUtils.getThemeColor(activity, R.attr.colorM3Surface),
|
||||
UiUtils.getThemeColor(activity, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme());
|
||||
}
|
||||
|
||||
private void confirmLogOut(String accountID){
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
new M3AlertDialogBuilder(activity)
|
||||
.setTitle(R.string.log_out)
|
||||
.setMessage(R.string.confirm_log_out)
|
||||
.setMessage(activity.getString(R.string.confirm_log_out, session.getFullUsername()))
|
||||
.setPositiveButton(R.string.log_out, (dialog, which) -> logOut(accountID))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void confirmLogOutAll(){
|
||||
new M3AlertDialogBuilder(activity)
|
||||
.setMessage(R.string.confirm_log_out_all_accounts)
|
||||
.setPositiveButton(R.string.log_out, (dialog, which) -> logOutAll())
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void logOut(String accountID){
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
new RevokeOauthToken(session.app.clientId, session.app.clientSecret, session.token.accessToken)
|
||||
@@ -144,9 +158,55 @@ public class AccountSwitcherSheet extends BottomSheet{
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
private void logOutAll(){
|
||||
final ProgressDialog progress=new ProgressDialog(activity);
|
||||
progress.setMessage(activity.getString(R.string.loading));
|
||||
progress.setCancelable(false);
|
||||
progress.show();
|
||||
ArrayList<AccountSession> sessions=new ArrayList<>(AccountSessionManager.getInstance().getLoggedInAccounts());
|
||||
for(AccountSession session:sessions){
|
||||
new RevokeOauthToken(session.app.clientId, session.app.clientSecret, session.token.accessToken)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Object result){
|
||||
AccountSessionManager.getInstance().removeAccount(session.getID());
|
||||
sessions.remove(session);
|
||||
if(sessions.isEmpty()){
|
||||
progress.dismiss();
|
||||
Nav.goClearingStack(activity, SplashFragment.class, null);
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
AccountSessionManager.getInstance().removeAccount(session.getID());
|
||||
sessions.remove(session);
|
||||
if(sessions.isEmpty()){
|
||||
progress.dismiss();
|
||||
Nav.goClearingStack(activity, SplashFragment.class, null);
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
})
|
||||
.exec(session.getID());
|
||||
}
|
||||
}
|
||||
|
||||
private void onLoggedOut(String accountID){
|
||||
AccountSessionManager.getInstance().removeAccount(accountID);
|
||||
dismiss();
|
||||
String activeAccountID = fragment != null
|
||||
? fragment.getAccountID()
|
||||
: AccountSessionManager.getInstance().getLastActiveAccountID();
|
||||
if (accountID.equals(activeAccountID)) {
|
||||
activity.finish();
|
||||
activity.startActivity(new Intent(activity, MainActivity.class));
|
||||
} else {
|
||||
accounts.stream().filter(w -> accountID.equals(w.session.getID())).findAny().ifPresent(w -> {
|
||||
accountsAdapter.notifyItemRemoved(accounts.indexOf(w));
|
||||
accounts.remove(w);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -164,6 +224,13 @@ public class AccountSwitcherSheet extends BottomSheet{
|
||||
}
|
||||
}
|
||||
|
||||
private View makeSimpleListItem(@StringRes int title, @DrawableRes int icon){
|
||||
TextView tv=(TextView) activity.getLayoutInflater().inflate(R.layout.item_text_with_icon, list, false);
|
||||
tv.setText(title);
|
||||
tv.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, 0, 0, 0);
|
||||
return tv;
|
||||
}
|
||||
|
||||
private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
public AccountsAdapter(){
|
||||
super(imgLoader);
|
||||
@@ -197,55 +264,42 @@ public class AccountSwitcherSheet extends BottomSheet{
|
||||
}
|
||||
}
|
||||
|
||||
private class AccountViewHolder extends BindableViewHolder<AccountSession> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
|
||||
private final TextView name;
|
||||
private final TextView display_name;
|
||||
private final TextView display_add_account;
|
||||
private class AccountViewHolder extends BindableViewHolder<AccountSession> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable, UsableRecyclerView.LongClickable{
|
||||
private final TextView name, username;
|
||||
private final ImageView avatar;
|
||||
private final ImageButton more;
|
||||
private final View currentIcon;
|
||||
private final PopupMenu menu;
|
||||
private final CheckableRelativeLayout view;
|
||||
private final View radioButton, extraBtnWrap;
|
||||
private final ImageButton extraBtn;
|
||||
|
||||
public AccountViewHolder(){
|
||||
super(activity, R.layout.item_account_switcher, list);
|
||||
name=findViewById(R.id.name);
|
||||
display_name=findViewById(R.id.display_name);
|
||||
display_add_account=findViewById(R.id.add_account);
|
||||
username=findViewById(R.id.username);
|
||||
radioButton=findViewById(R.id.radiobtn);
|
||||
radioButton.setBackground(new RadioButton(activity).getButtonDrawable());
|
||||
avatar=findViewById(R.id.avatar);
|
||||
more=findViewById(R.id.more);
|
||||
currentIcon=findViewById(R.id.current);
|
||||
|
||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(OutlineProviders.RADIUS_MEDIUM));
|
||||
avatar.setClipToOutline(true);
|
||||
|
||||
menu=new PopupMenu(activity, more);
|
||||
menu.inflate(R.menu.account_switcher);
|
||||
menu.setOnMenuItemClickListener(item1 -> {
|
||||
confirmLogOut(item.getID());
|
||||
return true;
|
||||
});
|
||||
more.setOnClickListener(v->menu.show());
|
||||
view=(CheckableRelativeLayout) itemView;
|
||||
extraBtnWrap = findViewById(R.id.extra_btn_wrap);
|
||||
extraBtn = findViewById(R.id.extra_btn);
|
||||
extraBtn.setOnClickListener(this::onExtraBtnClick);
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@Override
|
||||
public void onBind(AccountSession item){
|
||||
display_name.setText(item.self.displayName);
|
||||
name.setText("@"+item.self.username+"@"+item.domain);
|
||||
if(AccountSessionManager.getInstance().getLastActiveAccountID().equals(item.getID())){
|
||||
more.setVisibility(View.GONE);
|
||||
currentIcon.setVisibility(View.VISIBLE);
|
||||
}else{
|
||||
more.setVisibility(View.VISIBLE);
|
||||
currentIcon.setVisibility(View.GONE);
|
||||
name.setText(item.self.displayName);
|
||||
username.setText(item.getFullUsername());
|
||||
radioButton.setVisibility(externalShare ? View.GONE : View.VISIBLE);
|
||||
extraBtnWrap.setVisibility(externalShare && openInApp ? View.VISIBLE : View.GONE);
|
||||
if (externalShare) view.setCheckable(false);
|
||||
else {
|
||||
String accountId = fragment != null
|
||||
? fragment.getAccountID()
|
||||
: AccountSessionManager.getInstance().getLastActiveAccountID();
|
||||
view.setChecked(accountId.equals(item.getID()));
|
||||
}
|
||||
|
||||
if(!logOutEnabled){
|
||||
more.setVisibility(View.GONE);
|
||||
currentIcon.setVisibility(View.GONE);
|
||||
}
|
||||
menu.getMenu().findItem(R.id.log_out).setTitle(activity.getString(R.string.log_out_account, "@"+item.self.username));
|
||||
UiUtils.enablePopupMenuIcons(activity, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -260,11 +314,31 @@ public class AccountSwitcherSheet extends BottomSheet{
|
||||
setImage(index, null);
|
||||
}
|
||||
|
||||
private void onExtraBtnClick(View view) {
|
||||
setOnDismissListener(null);
|
||||
dismiss();
|
||||
onClick.accept(item.getID(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
setOnDismissListener(null);
|
||||
if (onClick != null) {
|
||||
dismiss();
|
||||
onClick.accept(item.getID(), false);
|
||||
return;
|
||||
}
|
||||
|
||||
AccountSessionManager.getInstance().setLastActiveAccountID(item.getID());
|
||||
dismiss();
|
||||
onClick.accept(AccountSessionManager.getInstance().getAccount(item.getID()));
|
||||
activity.finish();
|
||||
activity.startActivity(new Intent(activity, MainActivity.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(){
|
||||
if (externalShare) return false;
|
||||
confirmLogOut(item.getID());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,13 +18,15 @@ package org.joinmastodon.android.ui;
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.TimeInterpolator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.view.View;
|
||||
import android.view.ViewPropertyAnimator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator;
|
||||
|
||||
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
||||
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -358,7 +360,14 @@ public class BetterItemAnimator extends SimpleItemAnimator{
|
||||
mChangeAnimations.add(changeInfo.oldHolder);
|
||||
oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
|
||||
oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
|
||||
oldViewAnim.alpha(0).setListener(new AnimatorListenerAdapter() {
|
||||
float alpha = 0;
|
||||
if (holder instanceof MediaGridStatusDisplayItem.Holder mediaItemHolder) {
|
||||
if (mediaItemHolder.isSizeUpdating()) {
|
||||
alpha = 1; // Image will flicker out and then in if alpha is 0
|
||||
mediaItemHolder.sizeUpdated();
|
||||
}
|
||||
}
|
||||
oldViewAnim.alpha(alpha).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animator) {
|
||||
dispatchChangeStarting(changeInfo.oldHolder, true);
|
||||
|
||||
@@ -8,7 +8,15 @@ import android.view.ViewOutlineProvider;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class OutlineProviders{
|
||||
private static SparseArray<ViewOutlineProvider> roundedRects=new SparseArray<>();
|
||||
private static final SparseArray<ViewOutlineProvider> roundedRects=new SparseArray<>();
|
||||
private static final SparseArray<ViewOutlineProvider> topRoundedRects=new SparseArray<>();
|
||||
private static final SparseArray<ViewOutlineProvider> endRoundedRects=new SparseArray<>();
|
||||
|
||||
public static final int RADIUS_XSMALL=4;
|
||||
public static final int RADIUS_SMALL=8;
|
||||
public static final int RADIUS_MEDIUM=12;
|
||||
public static final int RADIUS_LARGE=16;
|
||||
public static final int RADIUS_XLARGE=28;
|
||||
|
||||
private OutlineProviders(){
|
||||
//no instance
|
||||
@@ -21,6 +29,12 @@ public class OutlineProviders{
|
||||
outline.setAlpha(view.getAlpha());
|
||||
}
|
||||
};
|
||||
public static final ViewOutlineProvider OVAL=new ViewOutlineProvider(){
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline){
|
||||
outline.setOval(0, 0, view.getWidth(), view.getHeight());
|
||||
}
|
||||
};
|
||||
|
||||
public static ViewOutlineProvider roundedRect(int dp){
|
||||
ViewOutlineProvider provider=roundedRects.get(dp);
|
||||
@@ -31,6 +45,24 @@ public class OutlineProviders{
|
||||
return provider;
|
||||
}
|
||||
|
||||
public static ViewOutlineProvider topRoundedRect(int dp){
|
||||
ViewOutlineProvider provider=topRoundedRects.get(dp);
|
||||
if(provider!=null)
|
||||
return provider;
|
||||
provider=new TopRoundRectOutlineProvider(V.dp(dp));
|
||||
topRoundedRects.put(dp, provider);
|
||||
return provider;
|
||||
}
|
||||
|
||||
public static ViewOutlineProvider endRoundedRect(int dp){
|
||||
ViewOutlineProvider provider=endRoundedRects.get(dp);
|
||||
if(provider!=null)
|
||||
return provider;
|
||||
provider=new EndRoundRectOutlineProvider(V.dp(dp));
|
||||
endRoundedRects.put(dp, provider);
|
||||
return provider;
|
||||
}
|
||||
|
||||
private static class RoundRectOutlineProvider extends ViewOutlineProvider{
|
||||
private final int radius;
|
||||
|
||||
@@ -43,4 +75,34 @@ public class OutlineProviders{
|
||||
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), radius);
|
||||
}
|
||||
}
|
||||
|
||||
private static class TopRoundRectOutlineProvider extends ViewOutlineProvider{
|
||||
private final int radius;
|
||||
|
||||
private TopRoundRectOutlineProvider(int radius){
|
||||
this.radius=radius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline){
|
||||
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight()+radius, radius);
|
||||
}
|
||||
}
|
||||
|
||||
private static class EndRoundRectOutlineProvider extends ViewOutlineProvider{
|
||||
private final int radius;
|
||||
|
||||
private EndRoundRectOutlineProvider(int radius){
|
||||
this.radius=radius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline){
|
||||
if(view.getLayoutDirection()==View.LAYOUT_DIRECTION_RTL){
|
||||
outline.setRoundRect(-radius, 0, view.getWidth(), view.getHeight(), radius);
|
||||
}else{
|
||||
outline.setRoundRect(0, 0, view.getWidth()+radius, view.getHeight(), radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +139,7 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", item.parentFragment.getAccountID());
|
||||
args.putString("id", item.status.id);
|
||||
args.putString("url", item.status.url);
|
||||
Nav.go(item.parentFragment.getActivity(), StatusEditHistoryFragment.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,12 +148,27 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
bindButton(reply, item.status.repliesCount);
|
||||
bindButton(boost, item.status.reblogsCount);
|
||||
bindButton(favorite, item.status.favouritesCount);
|
||||
reply.setSelected(item.status.repliesCount > 0);
|
||||
// in thread view, direct descendant posts display one direct reply to themselves,
|
||||
// hence in that case displaying whether there is another reply
|
||||
int compareTo = item.isMainStatus || !item.hasDescendantNeighbor ? 0 : 1;
|
||||
reply.setSelected(item.status.repliesCount > compareTo);
|
||||
boost.setSelected(item.status.reblogged);
|
||||
favorite.setSelected(item.status.favourited);
|
||||
bookmark.setSelected(item.status.bookmarked);
|
||||
boost.setEnabled(item.status.canBeBoosted(item.accountID));
|
||||
boost.setEnabled(item.status.visibility==StatusPrivacy.PUBLIC || item.status.visibility==StatusPrivacy.UNLISTED || item.status.visibility==StatusPrivacy.LOCAL
|
||||
|| (item.status.visibility==StatusPrivacy.PRIVATE && item.status.account.id.equals(AccountSessionManager.getInstance().getAccount(item.accountID).self.id)));
|
||||
|
||||
int nextPos = getAbsoluteAdapterPosition() + 1;
|
||||
boolean nextIsWarning = item.parentFragment.getDisplayItems().size() > nextPos &&
|
||||
item.parentFragment.getDisplayItems().get(nextPos) instanceof WarningFilteredStatusDisplayItem;
|
||||
boolean condenseBottom = !item.isMainStatus && item.hasDescendantNeighbor &&
|
||||
!nextIsWarning;
|
||||
|
||||
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) itemView.getLayoutParams();
|
||||
params.setMargins(params.leftMargin, params.topMargin, params.rightMargin,
|
||||
condenseBottom ? V.dp(-8) : 0);
|
||||
|
||||
itemView.requestLayout();
|
||||
}
|
||||
|
||||
private void bindButton(TextView btn, long count){
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.joinmastodon.android.ui.displayitems;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
@@ -25,6 +27,13 @@ public class HashtagStatusDisplayItem extends StatusDisplayItem{
|
||||
public static class Holder extends StatusDisplayItem.Holder<HashtagStatusDisplayItem>{
|
||||
private final TextView title, subtitle;
|
||||
private final HashtagChartView chart;
|
||||
public static final RelativeLayout.LayoutParams
|
||||
withHistoryParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT),
|
||||
withoutHistoryParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
static {
|
||||
withoutHistoryParams.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
|
||||
}
|
||||
|
||||
public Holder(Context context, ViewGroup parent){
|
||||
super(context, R.layout.item_trending_hashtag, parent);
|
||||
@@ -37,14 +46,20 @@ public class HashtagStatusDisplayItem extends StatusDisplayItem{
|
||||
public void onBind(HashtagStatusDisplayItem _item){
|
||||
Hashtag item=_item.tag;
|
||||
title.setText('#'+item.name);
|
||||
int numPeople = 0;
|
||||
if(item.history != null){
|
||||
numPeople=item.history.get(0).accounts;
|
||||
if(item.history.size()>1)
|
||||
numPeople+=item.history.get(1).accounts;
|
||||
chart.setData(item.history);
|
||||
if (item.history == null || item.history.isEmpty()) {
|
||||
subtitle.setText(null);
|
||||
chart.setVisibility(View.GONE);
|
||||
title.setLayoutParams(withoutHistoryParams);
|
||||
return;
|
||||
}
|
||||
chart.setVisibility(View.VISIBLE);
|
||||
title.setLayoutParams(withHistoryParams);
|
||||
int numPeople=item.history.get(0).accounts;
|
||||
if(item.history.size()>1)
|
||||
numPeople+=item.history.get(1).accounts;
|
||||
subtitle.setText(_item.parentFragment.getResources().getQuantityString(R.plurals.x_people_talking, numPeople, numPeople));
|
||||
chart.setData(item.history);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package org.joinmastodon.android.ui.displayitems;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
@@ -27,8 +25,6 @@ import android.widget.PopupMenu;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
@@ -39,7 +35,7 @@ import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||
import org.joinmastodon.android.fragments.ListTimelinesFragment;
|
||||
import org.joinmastodon.android.fragments.ListsFragment;
|
||||
import org.joinmastodon.android.fragments.NotificationsListFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||
@@ -47,12 +43,10 @@ import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Announcement;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.model.ContentType;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.model.ScheduledStatus;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
@@ -62,7 +56,6 @@ import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -290,7 +283,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
args.putString("account", item.parentFragment.getAccountID());
|
||||
args.putString("profileAccount", account.id);
|
||||
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
||||
Nav.go(item.parentFragment.getActivity(), ListTimelinesFragment.class, args);
|
||||
Nav.go(item.parentFragment.getActivity(), ListsFragment.class, args);
|
||||
}
|
||||
|
||||
if(!item.status.filterRevealed){
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.Activity;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
@@ -17,7 +18,6 @@ import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
@@ -40,7 +40,7 @@ import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
private static final String TAG="MediaGridDisplayItem";
|
||||
|
||||
private final PhotoLayoutHelper.TiledLayoutResult tiledLayout;
|
||||
private PhotoLayoutHelper.TiledLayoutResult tiledLayout;
|
||||
private final TypedObjectPool<GridItemType, MediaAttachmentViewController> viewPool;
|
||||
private final List<Attachment> attachments;
|
||||
private final ArrayList<ImageLoaderRequest> requests=new ArrayList<>();
|
||||
@@ -98,6 +98,8 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
private int altTextIndex=-1;
|
||||
private Animator altTextAnimator;
|
||||
|
||||
private boolean sizeUpdating = false;
|
||||
|
||||
public Holder(Activity activity, ViewGroup parent){
|
||||
super(new FrameLayoutThatOnlyMeasuresFirstChild(activity));
|
||||
wrapper=(FrameLayout)itemView;
|
||||
@@ -126,6 +128,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
layout.removeAllViews();
|
||||
controllers.clear();
|
||||
|
||||
int i=0;
|
||||
for(Attachment att:item.attachments){
|
||||
MediaAttachmentViewController c=item.viewPool.obtain(switch(att.type){
|
||||
@@ -158,6 +161,19 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
|
||||
@Override
|
||||
public void setImage(int index, Drawable drawable){
|
||||
Rect bounds=drawable.getBounds();
|
||||
drawable.setBounds(bounds.left, bounds.top, bounds.left+drawable.getIntrinsicWidth(), bounds.top+drawable.getIntrinsicHeight());
|
||||
if(item.attachments.get(index).meta==null){
|
||||
Attachment.Metadata metadata = new Attachment.Metadata();
|
||||
metadata.width=drawable.getIntrinsicWidth();
|
||||
metadata.height=drawable.getIntrinsicHeight();
|
||||
item.attachments.get(index).meta=metadata;
|
||||
|
||||
item.tiledLayout=PhotoLayoutHelper.processThumbs(item.attachments);
|
||||
sizeUpdating = true;
|
||||
item.parentFragment.onImageUpdated(this, index);
|
||||
}
|
||||
|
||||
controllers.get(index).setImage(drawable);
|
||||
}
|
||||
|
||||
@@ -314,5 +330,13 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
layout.setClipChildren(clip);
|
||||
wrapper.setClipChildren(clip);
|
||||
}
|
||||
|
||||
public boolean isSizeUpdating() {
|
||||
return sizeUpdating;
|
||||
}
|
||||
|
||||
public void sizeUpdated() {
|
||||
sizeUpdating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.joinmastodon.android.ui.displayitems;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
@@ -26,6 +27,7 @@ import org.joinmastodon.android.model.ScheduledStatus;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
@@ -45,6 +47,23 @@ public abstract class StatusDisplayItem{
|
||||
public final BaseStatusListFragment parentFragment;
|
||||
public boolean inset;
|
||||
public int index;
|
||||
public boolean
|
||||
hasDescendantNeighbor = false,
|
||||
hasAncestoringNeighbor = false,
|
||||
isMainStatus = true,
|
||||
isDirectDescendant = false;
|
||||
|
||||
public void setAncestryInfo(
|
||||
boolean hasDescendantNeighbor,
|
||||
boolean hasAncestoringNeighbor,
|
||||
boolean isMainStatus,
|
||||
boolean isDirectDescendant
|
||||
) {
|
||||
this.hasDescendantNeighbor = hasDescendantNeighbor;
|
||||
this.hasAncestoringNeighbor = hasAncestoringNeighbor;
|
||||
this.isMainStatus = isMainStatus;
|
||||
this.isDirectDescendant = isDirectDescendant;
|
||||
}
|
||||
|
||||
public StatusDisplayItem(String parentID, BaseStatusListFragment parentFragment){
|
||||
this.parentID=parentID;
|
||||
@@ -163,6 +182,15 @@ public abstract class StatusDisplayItem{
|
||||
items.add(replyLine);
|
||||
}
|
||||
|
||||
if (statusForContent.quote != null) {
|
||||
boolean hasQuoteInlineTag = statusForContent.content.contains("<span class=\"quote-inline\">");
|
||||
if (!hasQuoteInlineTag) {
|
||||
String quoteUrl = statusForContent.quote.url;
|
||||
String quoteInline = String.format("<span class=\"quote-inline\">%sRE: <a href=\"%s\">%s</a></span>",
|
||||
statusForContent.content.endsWith("</p>") ? "" : "<br/><br/>", quoteUrl, quoteUrl);
|
||||
statusForContent.content += quoteInline;
|
||||
}
|
||||
}
|
||||
if(!TextUtils.isEmpty(statusForContent.content))
|
||||
items.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent, disableTranslate));
|
||||
else if (!GlobalUserPreferences.replyLineAboveHeader && replyLine != null)
|
||||
@@ -174,6 +202,12 @@ public abstract class StatusDisplayItem{
|
||||
.filter(att->att.type.isImage() && !att.type.equals(Attachment.Type.UNKNOWN))
|
||||
.collect(Collectors.toList());
|
||||
if(!imageAttachments.isEmpty()){
|
||||
int color = UiUtils.getThemeColor(fragment.getContext(), R.attr.colorAccentLightest);
|
||||
for (Attachment att : imageAttachments) {
|
||||
if (att.blurhashPlaceholder == null) {
|
||||
att.blurhashPlaceholder = new ColorDrawable(color);
|
||||
}
|
||||
}
|
||||
PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(imageAttachments);
|
||||
items.add(new MediaGridStatusDisplayItem(parentID, fragment, layout, imageAttachments, statusForContent));
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
||||
spoilerEmojiHelper.setText(parsedSpoilerText);
|
||||
}
|
||||
session = AccountSessionManager.getInstance().getAccount(parentFragment.getAccountID());
|
||||
UiUtils.loadMaxWidth(parentFragment.getContext());
|
||||
}
|
||||
|
||||
public void setTranslationShown(boolean translationShown) {
|
||||
@@ -225,13 +226,33 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
||||
readMore.setText(item.status.textExpanded ? R.string.sk_collapse : R.string.sk_expand);
|
||||
spaceBelowText.setVisibility(translateVisible ? View.VISIBLE : View.GONE);
|
||||
|
||||
// remove additional padding when (transparently padded) translate button is visible
|
||||
int pos = getAbsoluteAdapterPosition();
|
||||
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(),
|
||||
(translateVisible &&
|
||||
item.parentFragment.getDisplayItems().size() >= pos + 1 &&
|
||||
item.parentFragment.getDisplayItems().get(pos + 1) instanceof FooterStatusDisplayItem)
|
||||
? 0 : V.dp(12)
|
||||
);
|
||||
|
||||
if (!GlobalUserPreferences.collapseLongPosts) {
|
||||
textScrollView.setLayoutParams(wrapParams);
|
||||
readMore.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// incredibly ugly workaround for https://github.com/sk22/megalodon/issues/520
|
||||
// i am so, so sorry. FIXME
|
||||
// attempts to use OnPreDrawListener, OnGlobalLayoutListener and .post have failed -
|
||||
// the view didn't want to reliably update after calling .setVisibility etc :(
|
||||
int width = parent.getWidth() != 0 ? parent.getWidth()
|
||||
: item.parentFragment.getView().getWidth() != 0
|
||||
? item.parentFragment.getView().getWidth()
|
||||
: item.parentFragment.getParentFragment() != null && item.parentFragment.getParentFragment().getView().getWidth() != 0
|
||||
? item.parentFragment.getParentFragment().getView().getWidth() // YIKES
|
||||
: UiUtils.MAX_WIDTH;
|
||||
|
||||
text.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY),
|
||||
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
|
||||
|
||||
if (GlobalUserPreferences.collapseLongPosts && !item.status.textExpandable) {
|
||||
|
||||
@@ -38,6 +38,7 @@ public class DiscoverInfoBannerHelper{
|
||||
case LOCAL_TIMELINE -> R.string.local_timeline_info_banner;
|
||||
case FEDERATED_TIMELINE -> R.string.sk_federated_timeline_info_banner;
|
||||
case POST_NOTIFICATIONS -> R.string.sk_notify_posts_info_banner;
|
||||
case BUBBLE_TIMELINE -> R.string.sk_bubble_timeline_info_banner;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -63,6 +64,7 @@ public class DiscoverInfoBannerHelper{
|
||||
LOCAL_TIMELINE,
|
||||
FEDERATED_TIMELINE,
|
||||
POST_NOTIFICATIONS,
|
||||
// ACCOUNTS
|
||||
// ACCOUNTS,
|
||||
BUBBLE_TIMELINE
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import static org.joinmastodon.android.GlobalUserPreferences.trueBlackTheme;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Fragment;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ClipData;
|
||||
@@ -102,6 +103,7 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
@@ -114,6 +116,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
@@ -141,12 +144,13 @@ import me.grishka.appkit.utils.V;
|
||||
import okhttp3.MediaType;
|
||||
|
||||
public class UiUtils {
|
||||
private static final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
private static Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
private static final DateTimeFormatter DATE_FORMATTER_SHORT_WITH_YEAR = DateTimeFormatter.ofPattern("d MMM uuuu"), DATE_FORMATTER_SHORT = DateTimeFormatter.ofPattern("d MMM");
|
||||
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT);
|
||||
public static int MAX_WIDTH;
|
||||
|
||||
private UiUtils(){}
|
||||
private UiUtils() {
|
||||
}
|
||||
|
||||
public static void loadMaxWidth(Context ctx) {
|
||||
if (MAX_WIDTH == 0) MAX_WIDTH = (int) ctx.getResources().getDimension(R.dimen.layout_max_width);
|
||||
@@ -159,33 +163,33 @@ public class UiUtils {
|
||||
.setShowTitle(true)
|
||||
.build()
|
||||
.launchUrl(context, Uri.parse(url));
|
||||
}else{
|
||||
} else {
|
||||
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
|
||||
}
|
||||
}catch(ActivityNotFoundException x){
|
||||
} catch (ActivityNotFoundException x) {
|
||||
Toast.makeText(context, R.string.no_app_to_handle_action, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
public static String formatRelativeTimestamp(Context context, Instant instant){
|
||||
long t=instant.toEpochMilli();
|
||||
long now=System.currentTimeMillis();
|
||||
long diff=now-t;
|
||||
if(diff<1000L){
|
||||
public static String formatRelativeTimestamp(Context context, Instant instant) {
|
||||
long t = instant.toEpochMilli();
|
||||
long now = System.currentTimeMillis();
|
||||
long diff = now - t;
|
||||
if (diff < 1000L) {
|
||||
return context.getString(R.string.time_now);
|
||||
}else if(diff<60_000L){
|
||||
return context.getString(R.string.time_seconds, diff/1000L);
|
||||
}else if(diff<3600_000L){
|
||||
return context.getString(R.string.time_minutes, diff/60_000L);
|
||||
}else if(diff<3600_000L*24L){
|
||||
return context.getString(R.string.time_hours, diff/3600_000L);
|
||||
}else{
|
||||
int days=(int)(diff/(3600_000L*24L));
|
||||
if(days>30){
|
||||
ZonedDateTime dt=instant.atZone(ZoneId.systemDefault());
|
||||
if(dt.getYear()==ZonedDateTime.now().getYear()){
|
||||
} else if (diff < 60_000L) {
|
||||
return context.getString(R.string.time_seconds, diff / 1000L);
|
||||
} else if (diff < 3600_000L) {
|
||||
return context.getString(R.string.time_minutes, diff / 60_000L);
|
||||
} else if (diff < 3600_000L * 24L) {
|
||||
return context.getString(R.string.time_hours, diff / 3600_000L);
|
||||
} else {
|
||||
int days = (int) (diff / (3600_000L * 24L));
|
||||
if (days > 30) {
|
||||
ZonedDateTime dt = instant.atZone(ZoneId.systemDefault());
|
||||
if (dt.getYear() == ZonedDateTime.now().getYear()) {
|
||||
return DATE_FORMATTER_SHORT.format(dt);
|
||||
}else{
|
||||
} else {
|
||||
return DATE_FORMATTER_SHORT_WITH_YEAR.format(dt);
|
||||
}
|
||||
}
|
||||
@@ -207,76 +211,77 @@ public class UiUtils {
|
||||
long diff=now-t;
|
||||
if(diff<1000L){
|
||||
return context.getString(R.string.time_just_now);
|
||||
}else if(diff<60_000L){
|
||||
int secs=(int)(diff/1000L);
|
||||
} else if (diff < 60_000L) {
|
||||
int secs = (int) (diff / 1000L);
|
||||
return context.getResources().getQuantityString(R.plurals.x_seconds_ago, secs, secs);
|
||||
}else if(diff<3600_000L){
|
||||
int mins=(int)(diff/60_000L);
|
||||
} else if (diff < 3600_000L) {
|
||||
int mins = (int) (diff / 60_000L);
|
||||
return context.getResources().getQuantityString(R.plurals.x_minutes_ago, mins, mins);
|
||||
}else{
|
||||
} else {
|
||||
return DATE_TIME_FORMATTER.format(instant.atZone(ZoneId.systemDefault()));
|
||||
}
|
||||
}
|
||||
|
||||
public static String formatTimeLeft(Context context, Instant instant){
|
||||
long t=instant.toEpochMilli();
|
||||
long now=System.currentTimeMillis();
|
||||
long diff=t-now;
|
||||
if(diff<60_000L){
|
||||
int secs=(int)(diff/1000L);
|
||||
public static String formatTimeLeft(Context context, Instant instant) {
|
||||
long t = instant.toEpochMilli();
|
||||
long now = System.currentTimeMillis();
|
||||
long diff = t - now;
|
||||
if (diff < 60_000L) {
|
||||
int secs = (int) (diff / 1000L);
|
||||
return context.getResources().getQuantityString(R.plurals.x_seconds_left, secs, secs);
|
||||
}else if(diff<3600_000L){
|
||||
int mins=(int)(diff/60_000L);
|
||||
} else if (diff < 3600_000L) {
|
||||
int mins = (int) (diff / 60_000L);
|
||||
return context.getResources().getQuantityString(R.plurals.x_minutes_left, mins, mins);
|
||||
}else if(diff<3600_000L*24L){
|
||||
int hours=(int)(diff/3600_000L);
|
||||
} else if (diff < 3600_000L * 24L) {
|
||||
int hours = (int) (diff / 3600_000L);
|
||||
return context.getResources().getQuantityString(R.plurals.x_hours_left, hours, hours);
|
||||
}else{
|
||||
int days=(int)(diff/(3600_000L*24L));
|
||||
} else {
|
||||
int days = (int) (diff / (3600_000L * 24L));
|
||||
return context.getResources().getQuantityString(R.plurals.x_days_left, days, days);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
public static String abbreviateNumber(int n){
|
||||
if(n<1000){
|
||||
public static String abbreviateNumber(int n) {
|
||||
if (n < 1000) {
|
||||
return String.format("%,d", n);
|
||||
}else if(n<1_000_000){
|
||||
float a=n/1000f;
|
||||
return a>99f ? String.format("%,dK", (int)Math.floor(a)) : String.format("%,.1fK", a);
|
||||
}else{
|
||||
float a=n/1_000_000f;
|
||||
return a>99f ? String.format("%,dM", (int)Math.floor(a)) : String.format("%,.1fM", n/1_000_000f);
|
||||
} else if (n < 1_000_000) {
|
||||
float a = n / 1000f;
|
||||
return a > 99f ? String.format("%,dK", (int) Math.floor(a)) : String.format("%,.1fK", a);
|
||||
} else {
|
||||
float a = n / 1_000_000f;
|
||||
return a > 99f ? String.format("%,dM", (int) Math.floor(a)) : String.format("%,.1fM", n / 1_000_000f);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
public static String abbreviateNumber(long n){
|
||||
if(n<1_000_000_000L)
|
||||
return abbreviateNumber((int)n);
|
||||
public static String abbreviateNumber(long n) {
|
||||
if (n < 1_000_000_000L)
|
||||
return abbreviateNumber((int) n);
|
||||
|
||||
double a=n/1_000_000_000.0;
|
||||
return a>99f ? String.format("%,dB", (int)Math.floor(a)) : String.format("%,.1fB", n/1_000_000_000.0);
|
||||
double a = n / 1_000_000_000.0;
|
||||
return a > 99f ? String.format("%,dB", (int) Math.floor(a)) : String.format("%,.1fB", n / 1_000_000_000.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Android 6.0 has a bug where start and end compound drawables don't get tinted.
|
||||
* This works around it by setting the tint colors directly to the drawables.
|
||||
*
|
||||
* @param textView
|
||||
*/
|
||||
public static void fixCompoundDrawableTintOnAndroid6(TextView textView){
|
||||
Drawable[] drawables=textView.getCompoundDrawablesRelative();
|
||||
for(int i=0;i<drawables.length;i++){
|
||||
if(drawables[i]!=null){
|
||||
Drawable tinted=drawables[i].mutate();
|
||||
public static void fixCompoundDrawableTintOnAndroid6(TextView textView) {
|
||||
Drawable[] drawables = textView.getCompoundDrawablesRelative();
|
||||
for (int i = 0; i < drawables.length; i++) {
|
||||
if (drawables[i] != null) {
|
||||
Drawable tinted = drawables[i].mutate();
|
||||
tinted.setTintList(textView.getTextColors());
|
||||
drawables[i]=tinted;
|
||||
drawables[i] = tinted;
|
||||
}
|
||||
}
|
||||
textView.setCompoundDrawablesRelative(drawables[0], drawables[1], drawables[2], drawables[3]);
|
||||
}
|
||||
|
||||
public static void runOnUiThread(Runnable runnable){
|
||||
public static void runOnUiThread(Runnable runnable) {
|
||||
mainHandler.post(runnable);
|
||||
}
|
||||
|
||||
@@ -284,67 +289,71 @@ public class UiUtils {
|
||||
mainHandler.postDelayed(runnable, delay);
|
||||
}
|
||||
|
||||
public static void removeCallbacks(Runnable runnable){
|
||||
public static void removeCallbacks(Runnable runnable) {
|
||||
mainHandler.removeCallbacks(runnable);
|
||||
}
|
||||
|
||||
/** Linear interpolation between {@code startValue} and {@code endValue} by {@code fraction}. */
|
||||
/**
|
||||
* Linear interpolation between {@code startValue} and {@code endValue} by {@code fraction}.
|
||||
*/
|
||||
public static int lerp(int startValue, int endValue, float fraction) {
|
||||
return startValue + Math.round(fraction * (endValue - startValue));
|
||||
}
|
||||
|
||||
public static String getFileName(Uri uri){
|
||||
if(uri.getScheme().equals("content")){
|
||||
try(Cursor cursor=MastodonApp.context.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null)){
|
||||
public static String getFileName(Uri uri) {
|
||||
if (uri.getScheme().equals("content")) {
|
||||
try (Cursor cursor = MastodonApp.context.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null)) {
|
||||
cursor.moveToFirst();
|
||||
String name=cursor.getString(0);
|
||||
if(name!=null)
|
||||
String name = cursor.getString(0);
|
||||
if (name != null)
|
||||
return name;
|
||||
}catch(Throwable ignore){}
|
||||
} catch (Throwable ignore) {
|
||||
}
|
||||
}
|
||||
return uri.getLastPathSegment();
|
||||
}
|
||||
|
||||
public static String formatFileSize(Context context, long size, boolean atLeastKB){
|
||||
if(size<1024 && !atLeastKB){
|
||||
public static String formatFileSize(Context context, long size, boolean atLeastKB) {
|
||||
if (size < 1024 && !atLeastKB) {
|
||||
return context.getString(R.string.file_size_bytes, size);
|
||||
}else if(size<1024*1024){
|
||||
return context.getString(R.string.file_size_kb, size/1024.0);
|
||||
}else if(size<1024*1024*1024){
|
||||
return context.getString(R.string.file_size_mb, size/(1024.0*1024.0));
|
||||
}else{
|
||||
return context.getString(R.string.file_size_gb, size/(1024.0*1024.0*1024.0));
|
||||
} else if (size < 1024 * 1024) {
|
||||
return context.getString(R.string.file_size_kb, size / 1024.0);
|
||||
} else if (size < 1024 * 1024 * 1024) {
|
||||
return context.getString(R.string.file_size_mb, size / (1024.0 * 1024.0));
|
||||
} else {
|
||||
return context.getString(R.string.file_size_gb, size / (1024.0 * 1024.0 * 1024.0));
|
||||
}
|
||||
}
|
||||
|
||||
public static MediaType getFileMediaType(File file){
|
||||
String name=file.getName();
|
||||
return MediaType.parse(MimeTypeMap.getSingleton().getMimeTypeFromExtension(name.substring(name.lastIndexOf('.')+1)));
|
||||
public static MediaType getFileMediaType(File file) {
|
||||
String name = file.getName();
|
||||
return MediaType.parse(MimeTypeMap.getSingleton().getMimeTypeFromExtension(name.substring(name.lastIndexOf('.') + 1)));
|
||||
}
|
||||
|
||||
public static void loadCustomEmojiInTextView(TextView view){
|
||||
CharSequence _text=view.getText();
|
||||
if(!(_text instanceof Spanned text))
|
||||
public static void loadCustomEmojiInTextView(TextView view) {
|
||||
CharSequence _text = view.getText();
|
||||
if (!(_text instanceof Spanned))
|
||||
return;
|
||||
CustomEmojiSpan[] spans=text.getSpans(0, text.length(), CustomEmojiSpan.class);
|
||||
if(spans.length==0)
|
||||
Spanned text = (Spanned) _text;
|
||||
CustomEmojiSpan[] spans = text.getSpans(0, text.length(), CustomEmojiSpan.class);
|
||||
if (spans.length == 0)
|
||||
return;
|
||||
int emojiSize=V.dp(20);
|
||||
Map<Emoji, List<CustomEmojiSpan>> spansByEmoji=Arrays.stream(spans).collect(Collectors.groupingBy(s->s.emoji));
|
||||
for(Map.Entry<Emoji, List<CustomEmojiSpan>> emoji:spansByEmoji.entrySet()){
|
||||
ViewImageLoader.load(new ViewImageLoader.Target(){
|
||||
int emojiSize = V.dp(20);
|
||||
Map<Emoji, List<CustomEmojiSpan>> spansByEmoji = Arrays.stream(spans).collect(Collectors.groupingBy(s -> s.emoji));
|
||||
for (Map.Entry<Emoji, List<CustomEmojiSpan>> emoji : spansByEmoji.entrySet()) {
|
||||
ViewImageLoader.load(new ViewImageLoader.Target() {
|
||||
@Override
|
||||
public void setImageDrawable(Drawable d){
|
||||
if(d==null)
|
||||
public void setImageDrawable(Drawable d) {
|
||||
if (d == null)
|
||||
return;
|
||||
for(CustomEmojiSpan span:emoji.getValue()){
|
||||
for (CustomEmojiSpan span : emoji.getValue()) {
|
||||
span.setDrawable(d);
|
||||
}
|
||||
view.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(){
|
||||
public View getView() {
|
||||
return view;
|
||||
}
|
||||
}, null, new UrlImageLoaderRequest(emoji.getKey().url, emojiSize, emojiSize), null, false, true);
|
||||
@@ -371,22 +380,22 @@ public class UiUtils {
|
||||
Bundle args = new Bundle();
|
||||
args.putString("account", selfID);
|
||||
args.putString("profileAccountID", id);
|
||||
Nav.go((Activity)context, ProfileFragment.class, args);
|
||||
Nav.go((Activity) context, ProfileFragment.class, args);
|
||||
}
|
||||
|
||||
public static void openHashtagTimeline(Context context, String accountID, String hashtag, @Nullable Boolean following){
|
||||
Bundle args=new Bundle();
|
||||
public static void openHashtagTimeline(Context context, String accountID, String hashtag, @Nullable Boolean following) {
|
||||
Bundle args = new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putString("hashtag", hashtag);
|
||||
if (following != null) args.putBoolean("following", following);
|
||||
Nav.go((Activity)context, HashtagTimelineFragment.class, args);
|
||||
Nav.go((Activity) context, HashtagTimelineFragment.class, args);
|
||||
}
|
||||
|
||||
public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, Runnable onConfirmed){
|
||||
public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, Runnable onConfirmed) {
|
||||
showConfirmationAlert(context, title, message, confirmButton, 0, onConfirmed);
|
||||
}
|
||||
|
||||
public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, @DrawableRes int icon, Runnable onConfirmed){
|
||||
public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, @DrawableRes int icon, Runnable onConfirmed) {
|
||||
showConfirmationAlert(context, context.getString(title), context.getString(message), context.getString(confirmButton), icon, onConfirmed);
|
||||
}
|
||||
|
||||
@@ -404,25 +413,25 @@ public class UiUtils {
|
||||
.show();
|
||||
}
|
||||
|
||||
public static void confirmToggleBlockUser(Activity activity, String accountID, Account account, boolean currentlyBlocked, Consumer<Relationship> resultCallback){
|
||||
public static void confirmToggleBlockUser(Activity activity, String accountID, Account account, boolean currentlyBlocked, Consumer<Relationship> resultCallback) {
|
||||
showConfirmationAlert(activity, activity.getString(currentlyBlocked ? R.string.confirm_unblock_title : R.string.confirm_block_title),
|
||||
activity.getString(currentlyBlocked ? R.string.confirm_unblock : R.string.confirm_block, account.displayName),
|
||||
activity.getString(currentlyBlocked ? R.string.do_unblock : R.string.do_block),
|
||||
R.drawable.ic_fluent_person_prohibited_28_regular,
|
||||
()->{
|
||||
() -> {
|
||||
new SetAccountBlocked(account.id, !currentlyBlocked)
|
||||
.setCallback(new Callback<>(){
|
||||
.setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
public void onSuccess(Relationship result) {
|
||||
if (activity == null) return;
|
||||
resultCallback.accept(result);
|
||||
if(!currentlyBlocked){
|
||||
if (!currentlyBlocked) {
|
||||
E.post(new RemoveAccountPostsEvent(accountID, account.id, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(activity);
|
||||
}
|
||||
})
|
||||
@@ -431,54 +440,54 @@ public class UiUtils {
|
||||
});
|
||||
}
|
||||
|
||||
public static void confirmSoftBlockUser(Activity activity, String accountID, Account account, Consumer<Relationship> resultCallback){
|
||||
public static void confirmSoftBlockUser(Activity activity, String accountID, Account account, Consumer<Relationship> resultCallback) {
|
||||
showConfirmationAlert(activity,
|
||||
activity.getString(R.string.sk_remove_follower),
|
||||
activity.getString(R.string.sk_remove_follower_confirm, account.displayName),
|
||||
activity.getString(R.string.sk_do_remove_follower),
|
||||
R.drawable.ic_fluent_person_delete_24_regular,
|
||||
() -> new SetAccountBlocked(account.id, true).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Relationship relationship) {
|
||||
new SetAccountBlocked(account.id, false).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Relationship relationship) {
|
||||
if (activity == null) return;
|
||||
Toast.makeText(activity, R.string.sk_remove_follower_success, Toast.LENGTH_SHORT).show();
|
||||
resultCallback.accept(relationship);
|
||||
}
|
||||
@Override
|
||||
public void onSuccess(Relationship relationship) {
|
||||
new SetAccountBlocked(account.id, false).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Relationship relationship) {
|
||||
if (activity == null) return;
|
||||
Toast.makeText(activity, R.string.sk_remove_follower_success, Toast.LENGTH_SHORT).show();
|
||||
resultCallback.accept(relationship);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(activity);
|
||||
resultCallback.accept(relationship);
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(activity);
|
||||
resultCallback.accept(relationship);
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(activity);
|
||||
}
|
||||
}).exec(accountID)
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(activity);
|
||||
}
|
||||
}).exec(accountID)
|
||||
);
|
||||
}
|
||||
|
||||
public static void confirmToggleBlockDomain(Activity activity, String accountID, String domain, boolean currentlyBlocked, Runnable resultCallback){
|
||||
public static void confirmToggleBlockDomain(Activity activity, String accountID, String domain, boolean currentlyBlocked, Runnable resultCallback) {
|
||||
showConfirmationAlert(activity, activity.getString(currentlyBlocked ? R.string.confirm_unblock_domain_title : R.string.confirm_block_domain_title),
|
||||
activity.getString(currentlyBlocked ? R.string.confirm_unblock : R.string.confirm_block, domain),
|
||||
activity.getString(currentlyBlocked ? R.string.do_unblock : R.string.do_block),
|
||||
R.drawable.ic_fluent_shield_28_regular,
|
||||
()->{
|
||||
() -> {
|
||||
new SetDomainBlocked(domain, !currentlyBlocked)
|
||||
.setCallback(new Callback<>(){
|
||||
.setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Object result){
|
||||
public void onSuccess(Object result) {
|
||||
resultCallback.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(activity);
|
||||
}
|
||||
})
|
||||
@@ -585,9 +594,9 @@ public class UiUtils {
|
||||
R.string.delete,
|
||||
R.drawable.ic_fluent_delete_28_regular,
|
||||
() -> new DeleteStatus.Scheduled(status.id)
|
||||
.setCallback(new Callback<>(){
|
||||
.setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Object o){
|
||||
public void onSuccess(Object o) {
|
||||
resultCallback.run();
|
||||
E.post(new ScheduledStatusDeletedEvent(status.id, accountID));
|
||||
}
|
||||
@@ -675,38 +684,39 @@ public class UiUtils {
|
||||
setRelationshipToActionButton(relationship, button, false);
|
||||
}
|
||||
|
||||
public static void setRelationshipToActionButton(Relationship relationship, Button button, boolean keepText){
|
||||
public static void setRelationshipToActionButton(Relationship relationship, Button button, boolean keepText) {
|
||||
CharSequence textBefore = keepText ? button.getText() : null;
|
||||
boolean secondaryStyle;
|
||||
if(relationship.blocking){
|
||||
if (relationship.blocking) {
|
||||
button.setText(R.string.button_blocked);
|
||||
secondaryStyle=true;
|
||||
}else if(relationship.blockedBy){
|
||||
button.setText(R.string.button_follow);
|
||||
secondaryStyle=false;
|
||||
}else if(relationship.requested){
|
||||
secondaryStyle = true;
|
||||
// } else if (relationship.blockedBy) {
|
||||
// button.setText(R.string.button_follow);
|
||||
// secondaryStyle = false;
|
||||
} else if (relationship.requested) {
|
||||
button.setText(R.string.button_follow_pending);
|
||||
secondaryStyle=true;
|
||||
}else if(!relationship.following){
|
||||
secondaryStyle = true;
|
||||
} else if (!relationship.following) {
|
||||
button.setText(relationship.followedBy ? R.string.follow_back : R.string.button_follow);
|
||||
secondaryStyle=false;
|
||||
}else{
|
||||
secondaryStyle = false;
|
||||
} else {
|
||||
button.setText(R.string.button_following);
|
||||
secondaryStyle=true;
|
||||
secondaryStyle = true;
|
||||
}
|
||||
|
||||
if (keepText) button.setText(textBefore);
|
||||
|
||||
button.setEnabled(!relationship.blockedBy);
|
||||
int attr=secondaryStyle ? R.attr.secondaryButtonStyle : android.R.attr.buttonStyle;
|
||||
TypedArray ta=button.getContext().obtainStyledAttributes(new int[]{attr});
|
||||
int styleRes=ta.getResourceId(0, 0);
|
||||
// https://github.com/sk22/megalodon/issues/526
|
||||
// button.setEnabled(!relationship.blockedBy);
|
||||
int attr = secondaryStyle ? R.attr.secondaryButtonStyle : android.R.attr.buttonStyle;
|
||||
TypedArray ta = button.getContext().obtainStyledAttributes(new int[]{attr});
|
||||
int styleRes = ta.getResourceId(0, 0);
|
||||
ta.recycle();
|
||||
ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background});
|
||||
ta = button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background});
|
||||
button.setBackground(ta.getDrawable(0));
|
||||
ta.recycle();
|
||||
ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.textColor});
|
||||
if(relationship.blocking)
|
||||
ta = button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.textColor});
|
||||
if (relationship.blocking)
|
||||
button.setTextColor(button.getResources().getColorStateList(R.color.error_600));
|
||||
else
|
||||
button.setTextColor(ta.getColorStateList(0));
|
||||
@@ -721,7 +731,7 @@ public class UiUtils {
|
||||
public void onSuccess(Relationship result) {
|
||||
resultCallback.accept(result);
|
||||
progressCallback.accept(false);
|
||||
Toast.makeText(activity, activity.getString(result.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username), Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(activity, activity.getString(result.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@' + account.username), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -839,7 +849,8 @@ public class UiUtils {
|
||||
@Override
|
||||
public void onSuccess(Relationship rel) {
|
||||
E.post(new FollowRequestHandledEvent(accountID, false, account, rel));
|
||||
if (notificationID != null) E.post(new NotificationDeletedEvent(notificationID));
|
||||
if (notificationID != null)
|
||||
E.post(new NotificationDeletedEvent(notificationID));
|
||||
resultCallback.accept(rel);
|
||||
}
|
||||
|
||||
@@ -852,34 +863,34 @@ public class UiUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void updateList(List<T> oldList, List<T> newList, RecyclerView list, RecyclerView.Adapter<?> adapter, BiPredicate<T, T> areItemsSame){
|
||||
public static <T> void updateList(List<T> oldList, List<T> newList, RecyclerView list, RecyclerView.Adapter<?> adapter, BiPredicate<T, T> areItemsSame) {
|
||||
// Save topmost item position and offset because for some reason RecyclerView would scroll the list to weird places when you insert items at the top
|
||||
int topItem, topItemOffset;
|
||||
if(list.getChildCount()==0){
|
||||
topItem=topItemOffset=0;
|
||||
}else{
|
||||
View child=list.getChildAt(0);
|
||||
topItem=list.getChildAdapterPosition(child);
|
||||
topItemOffset=child.getTop();
|
||||
if (list.getChildCount() == 0) {
|
||||
topItem = topItemOffset = 0;
|
||||
} else {
|
||||
View child = list.getChildAt(0);
|
||||
topItem = list.getChildAdapterPosition(child);
|
||||
topItemOffset = child.getTop();
|
||||
}
|
||||
DiffUtil.calculateDiff(new DiffUtil.Callback(){
|
||||
DiffUtil.calculateDiff(new DiffUtil.Callback() {
|
||||
@Override
|
||||
public int getOldListSize(){
|
||||
public int getOldListSize() {
|
||||
return oldList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNewListSize(){
|
||||
public int getNewListSize() {
|
||||
return newList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition){
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
return areItemsSame.test(oldList.get(oldItemPosition), newList.get(newItemPosition));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition){
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
return true;
|
||||
}
|
||||
}).dispatchUpdatesTo(adapter);
|
||||
@@ -887,34 +898,35 @@ public class UiUtils {
|
||||
list.scrollBy(0, topItemOffset);
|
||||
}
|
||||
|
||||
public static Bitmap getBitmapFromDrawable(Drawable d){
|
||||
if(d instanceof BitmapDrawable)
|
||||
public static Bitmap getBitmapFromDrawable(Drawable d) {
|
||||
if (d instanceof BitmapDrawable)
|
||||
return ((BitmapDrawable) d).getBitmap();
|
||||
Bitmap bitmap=Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
|
||||
Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
|
||||
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
|
||||
d.draw(new Canvas(bitmap));
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public static void insetPopupMenuIcon(Context context, MenuItem item) {
|
||||
ColorStateList iconTint=ColorStateList.valueOf(UiUtils.getThemeColor(context, android.R.attr.textColorSecondary));
|
||||
ColorStateList iconTint = ColorStateList.valueOf(UiUtils.getThemeColor(context, android.R.attr.textColorSecondary));
|
||||
insetPopupMenuIcon(item, iconTint);
|
||||
}
|
||||
|
||||
public static void insetPopupMenuIcon(MenuItem item, ColorStateList iconTint) {
|
||||
Drawable icon=item.getIcon().mutate();
|
||||
if(Build.VERSION.SDK_INT>=26) item.setIconTintList(iconTint);
|
||||
Drawable icon = item.getIcon().mutate();
|
||||
if (Build.VERSION.SDK_INT >= 26) item.setIconTintList(iconTint);
|
||||
else icon.setTintList(iconTint);
|
||||
icon=new InsetDrawable(icon, V.dp(8), 0, V.dp(8), 0);
|
||||
icon = new InsetDrawable(icon, V.dp(8), 0, V.dp(8), 0);
|
||||
item.setIcon(icon);
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder(item.getTitle());
|
||||
SpannableStringBuilder ssb = new SpannableStringBuilder(item.getTitle());
|
||||
item.setTitle(ssb);
|
||||
}
|
||||
|
||||
public static void resetPopupItemTint(MenuItem item) {
|
||||
if(Build.VERSION.SDK_INT>=26) {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
item.setIconTintList(null);
|
||||
} else {
|
||||
Drawable icon=item.getIcon().mutate();
|
||||
Drawable icon = item.getIcon().mutate();
|
||||
icon.setTintList(null);
|
||||
item.setIcon(icon);
|
||||
}
|
||||
@@ -923,7 +935,7 @@ public class UiUtils {
|
||||
/// Add icons to the menu.
|
||||
/// Passing in items will be colored to be visible on the background.
|
||||
public static void enableOptionsMenuIcons(Context context, Menu menu, @IdRes int... asAction) {
|
||||
if(menu.getClass().getSimpleName().equals("MenuBuilder")){
|
||||
if (menu.getClass().getSimpleName().equals("MenuBuilder")) {
|
||||
try {
|
||||
Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE);
|
||||
m.setAccessible(true);
|
||||
@@ -935,31 +947,33 @@ public class UiUtils {
|
||||
}
|
||||
|
||||
public static void enableMenuIcons(Context context, Menu m, @IdRes int... exclude) {
|
||||
ColorStateList iconTint=ColorStateList.valueOf(UiUtils.getThemeColor(context, android.R.attr.textColorSecondary));
|
||||
for(int i=0;i<m.size();i++){
|
||||
MenuItem item=m.getItem(i);
|
||||
ColorStateList iconTint = ColorStateList.valueOf(UiUtils.getThemeColor(context, android.R.attr.textColorSecondary));
|
||||
for (int i = 0; i < m.size(); i++) {
|
||||
MenuItem item = m.getItem(i);
|
||||
SubMenu subMenu = item.getSubMenu();
|
||||
if (subMenu != null) enableMenuIcons(context, subMenu, exclude);
|
||||
if (item.getIcon() == null || Arrays.stream(exclude).anyMatch(id -> id == item.getItemId())) continue;
|
||||
if (item.getIcon() == null || Arrays.stream(exclude).anyMatch(id -> id == item.getItemId()))
|
||||
continue;
|
||||
insetPopupMenuIcon(item, iconTint);
|
||||
}
|
||||
}
|
||||
|
||||
public static void enablePopupMenuIcons(Context context, PopupMenu menu){
|
||||
Menu m=menu.getMenu();
|
||||
if(Build.VERSION.SDK_INT>=29){
|
||||
public static void enablePopupMenuIcons(Context context, PopupMenu menu) {
|
||||
Menu m = menu.getMenu();
|
||||
if (Build.VERSION.SDK_INT >= 29) {
|
||||
menu.setForceShowIcon(true);
|
||||
}else{
|
||||
try{
|
||||
Method setOptionalIconsVisible=m.getClass().getDeclaredMethod("setOptionalIconsVisible", boolean.class);
|
||||
} else {
|
||||
try {
|
||||
Method setOptionalIconsVisible = m.getClass().getDeclaredMethod("setOptionalIconsVisible", boolean.class);
|
||||
setOptionalIconsVisible.setAccessible(true);
|
||||
setOptionalIconsVisible.invoke(m, true);
|
||||
}catch(Exception ignore){}
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
enableMenuIcons(context, m);
|
||||
}
|
||||
|
||||
public static void setUserPreferredTheme(Context context){
|
||||
public static void setUserPreferredTheme(Context context) {
|
||||
context.setTheme(switch (theme) {
|
||||
case LIGHT -> R.style.Theme_Mastodon_Light;
|
||||
case DARK -> trueBlackTheme ? R.style.Theme_Mastodon_Dark_TrueBlack : R.style.Theme_Mastodon_Dark;
|
||||
@@ -969,10 +983,11 @@ public class UiUtils {
|
||||
ColorPalette palette = ColorPalette.palettes.get(GlobalUserPreferences.color);
|
||||
if (palette != null) palette.apply(context);
|
||||
}
|
||||
public static boolean isDarkTheme(){
|
||||
if(theme==GlobalUserPreferences.ThemePreference.AUTO)
|
||||
return (MastodonApp.context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)==Configuration.UI_MODE_NIGHT_YES;
|
||||
return theme==GlobalUserPreferences.ThemePreference.DARK;
|
||||
|
||||
public static boolean isDarkTheme() {
|
||||
if (theme == GlobalUserPreferences.ThemePreference.AUTO)
|
||||
return (MastodonApp.context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
|
||||
return theme == GlobalUserPreferences.ThemePreference.DARK;
|
||||
}
|
||||
|
||||
// https://mastodon.foo.bar/@User
|
||||
@@ -1001,7 +1016,8 @@ public class UiUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (uri.getQuery() != null || uri.getFragment() != null || uri.getPath() == null) return false;
|
||||
if (uri.getQuery() != null || uri.getFragment() != null || uri.getPath() == null)
|
||||
return false;
|
||||
|
||||
String it = uri.getPath();
|
||||
return it.matches("^/@[^/]+$") ||
|
||||
@@ -1021,13 +1037,13 @@ public class UiUtils {
|
||||
|
||||
public static String getInstanceName(String accountID) {
|
||||
AccountSession session = AccountSessionManager.getInstance().getAccount(accountID);
|
||||
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
|
||||
return instance != null && !instance.title.isBlank() ? instance.title : session.domain;
|
||||
Optional<Instance> instance = session.getInstance();
|
||||
return instance.isPresent() && !instance.get().title.isBlank() ? instance.get().title : session.domain;
|
||||
}
|
||||
|
||||
public static void pickAccount(Context context, String exceptFor, @StringRes int titleRes, @DrawableRes int iconRes, Consumer<AccountSession> sessionConsumer, Consumer<AlertDialog.Builder> transformDialog) {
|
||||
List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts()
|
||||
.stream().filter(s->!s.getID().equals(exceptFor)).collect(Collectors.toList());
|
||||
List<AccountSession> sessions = AccountSessionManager.getInstance().getLoggedInAccounts()
|
||||
.stream().filter(s -> !s.getID().equals(exceptFor)).collect(Collectors.toList());
|
||||
|
||||
AlertDialog.Builder builder = new M3AlertDialogBuilder(context)
|
||||
.setItems(
|
||||
@@ -1140,7 +1156,7 @@ public class UiUtils {
|
||||
error.showToast(context);
|
||||
}
|
||||
})
|
||||
.wrapProgress((Activity)context, R.string.loading, true,
|
||||
.wrapProgress((Activity) context, R.string.loading, true,
|
||||
d -> transformDialogForLookup(context, targetAccountID, null, d))
|
||||
.exec(targetAccountID);
|
||||
}
|
||||
@@ -1250,28 +1266,36 @@ public class UiUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void openURL(Context context, String accountID, String url, boolean launchBrowser){
|
||||
Uri uri=Uri.parse(url);
|
||||
List<String> path=uri.getPathSegments();
|
||||
if(accountID!=null && "https".equals(uri.getScheme())){
|
||||
if(path.size()==2 && path.get(0).matches("^@[a-zA-Z0-9_]+$") && path.get(1).matches("^[0-9]+$") && AccountSessionManager.getInstance().getAccount(accountID).domain.equalsIgnoreCase(uri.getAuthority())){
|
||||
public static void openURL(Context context, String accountID, String url, boolean launchBrowser) {
|
||||
lookupURL(context, accountID, url, launchBrowser, (clazz, args) -> {
|
||||
if (clazz == null) return;
|
||||
Nav.go((Activity) context, clazz, args);
|
||||
});
|
||||
}
|
||||
|
||||
public static void lookupURL(Context context, String accountID, String url, boolean launchBrowser, BiConsumer<Class<? extends Fragment>, Bundle> go) {
|
||||
Uri uri = Uri.parse(url);
|
||||
List<String> path = uri.getPathSegments();
|
||||
if (accountID != null && "https".equals(uri.getScheme())) {
|
||||
if (path.size() == 2 && path.get(0).matches("^@[a-zA-Z0-9_]+$") && path.get(1).matches("^[0-9]+$") && AccountSessionManager.getInstance().getAccount(accountID).domain.equalsIgnoreCase(uri.getAuthority())) {
|
||||
new GetStatusByID(path.get(1))
|
||||
.setCallback(new Callback<>(){
|
||||
.setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
Bundle args=new Bundle();
|
||||
public void onSuccess(Status result) {
|
||||
Bundle args = new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("status", Parcels.wrap(result));
|
||||
Nav.go((Activity) context, ThreadFragment.class, args);
|
||||
go.accept(ThreadFragment.class, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(context);
|
||||
if (launchBrowser) launchWebBrowser(context, url);
|
||||
go.accept(null, null);
|
||||
}
|
||||
})
|
||||
.wrapProgress((Activity)context, R.string.loading, true,
|
||||
.wrapProgress((Activity) context, R.string.loading, true,
|
||||
d -> transformDialogForLookup(context, accountID, url, d))
|
||||
.exec(accountID);
|
||||
return;
|
||||
@@ -1280,54 +1304,62 @@ public class UiUtils {
|
||||
.setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(SearchResults results) {
|
||||
Bundle args=new Bundle();
|
||||
Bundle args = new Bundle();
|
||||
args.putString("account", accountID);
|
||||
if (!results.statuses.isEmpty()) {
|
||||
args.putParcelable("status", Parcels.wrap(results.statuses.get(0)));
|
||||
Nav.go((Activity) context, ThreadFragment.class, args);
|
||||
} else if (!results.accounts.isEmpty()) {
|
||||
args.putParcelable("profileAccount", Parcels.wrap(results.accounts.get(0)));
|
||||
Nav.go((Activity) context, ProfileFragment.class, args);
|
||||
} else {
|
||||
if (launchBrowser) launchWebBrowser(context, url);
|
||||
else Toast.makeText(context, R.string.sk_resource_not_found, Toast.LENGTH_SHORT).show();
|
||||
go.accept(ThreadFragment.class, args);
|
||||
return;
|
||||
}
|
||||
Optional<Account> account = results.accounts.stream()
|
||||
.filter(a -> uri.equals(Uri.parse(a.url))).findAny();
|
||||
if (account.isPresent()) {
|
||||
args.putParcelable("profileAccount", Parcels.wrap(account.get()));
|
||||
go.accept(ProfileFragment.class, args);
|
||||
return;
|
||||
}
|
||||
if (launchBrowser) launchWebBrowser(context, url);
|
||||
Toast.makeText(context, R.string.sk_resource_not_found, Toast.LENGTH_SHORT).show();
|
||||
go.accept(null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(context);
|
||||
if (launchBrowser) launchWebBrowser(context, url);
|
||||
go.accept(null, null);
|
||||
}
|
||||
})
|
||||
.wrapProgress((Activity)context, R.string.loading, true,
|
||||
.wrapProgress((Activity) context, R.string.loading, true,
|
||||
d -> transformDialogForLookup(context, accountID, url, d))
|
||||
.exec(accountID);
|
||||
return;
|
||||
}
|
||||
}
|
||||
launchWebBrowser(context, url);
|
||||
if (launchBrowser) launchWebBrowser(context, url);
|
||||
go.accept(null, null);
|
||||
}
|
||||
|
||||
public static void copyText(View v, String text) {
|
||||
Context context = v.getContext();
|
||||
context.getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText(null, text));
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.TIRAMISU || UiUtils.isMIUI()){ // Android 13+ SystemUI shows its own thing when you put things into the clipboard
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || UiUtils.isMIUI()) { // Android 13+ SystemUI shows its own thing when you put things into the clipboard
|
||||
Toast.makeText(context, R.string.text_copied, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
v.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
|
||||
}
|
||||
|
||||
private static String getSystemProperty(String key){
|
||||
try{
|
||||
Class<?> props=Class.forName("android.os.SystemProperties");
|
||||
Method get=props.getMethod("get", String.class);
|
||||
return (String)get.invoke(null, key);
|
||||
}catch(Exception ignore){}
|
||||
private static String getSystemProperty(String key) {
|
||||
try {
|
||||
Class<?> props = Class.forName("android.os.SystemProperties");
|
||||
Method get = props.getMethod("get", String.class);
|
||||
return (String) get.invoke(null, key);
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isMIUI(){
|
||||
public static boolean isMIUI() {
|
||||
return !TextUtils.isEmpty(getSystemProperty("ro.miui.ui.version.code"));
|
||||
}
|
||||
|
||||
@@ -1335,17 +1367,25 @@ public class UiUtils {
|
||||
return !TextUtils.isEmpty(getSystemProperty("ro.build.version.emui"));
|
||||
}
|
||||
|
||||
public static int alphaBlendColors(int color1, int color2, float alpha) {
|
||||
float alpha0 = 1f - alpha;
|
||||
int r = Math.round(((color1 >> 16) & 0xFF) * alpha0 + ((color2 >> 16) & 0xFF) * alpha);
|
||||
int g = Math.round(((color1 >> 8) & 0xFF) * alpha0 + ((color2 >> 8) & 0xFF) * alpha);
|
||||
int b = Math.round((color1 & 0xFF) * alpha0 + (color2 & 0xFF) * alpha);
|
||||
return 0xFF000000 | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
public static boolean pickAccountForCompose(Activity activity, String accountID, String prefilledText) {
|
||||
Bundle args = new Bundle();
|
||||
if (prefilledText != null) args.putString("prefilledText", prefilledText);
|
||||
return pickAccountForCompose(activity, accountID, args);
|
||||
}
|
||||
|
||||
public static boolean pickAccountForCompose(Activity activity, String accountID){
|
||||
public static boolean pickAccountForCompose(Activity activity, String accountID) {
|
||||
return pickAccountForCompose(activity, accountID, (String) null);
|
||||
}
|
||||
|
||||
public static boolean pickAccountForCompose(Activity activity, String accountID, Bundle args){
|
||||
public static boolean pickAccountForCompose(Activity activity, String accountID, Bundle args) {
|
||||
if (AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1) {
|
||||
UiUtils.pickAccount(activity, accountID, 0, 0, session -> {
|
||||
args.putString("account", session.getID());
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.widget.Checkable;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
public class CheckableRelativeLayout extends RelativeLayout implements Checkable{
|
||||
private boolean checked, checkable = true;
|
||||
private static final int[] CHECKED_STATE_SET = {
|
||||
android.R.attr.state_checked
|
||||
};
|
||||
|
||||
public CheckableRelativeLayout(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CheckableRelativeLayout(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public CheckableRelativeLayout(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChecked(boolean checked){
|
||||
this.checked=checked;
|
||||
refreshDrawableState();
|
||||
}
|
||||
|
||||
public void setCheckable(boolean checkable) {
|
||||
this.checkable = checkable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked(){
|
||||
return checked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toggle(){
|
||||
setChecked(!checked);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int[] onCreateDrawableState(int extraSpace) {
|
||||
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
|
||||
if (isChecked()) {
|
||||
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
|
||||
}
|
||||
return drawableState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info){
|
||||
super.onInitializeAccessibilityNodeInfo(info);
|
||||
info.setCheckable(checkable);
|
||||
info.setChecked(checked);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user