Add support for predictive back navigation

This commit is contained in:
Grishka
2024-06-09 21:17:01 +03:00
parent 767e414c94
commit 513b29f57d
17 changed files with 144 additions and 133 deletions

View File

@@ -90,7 +90,7 @@ dependencies {
implementation 'me.grishka.litex:viewpager:1.0.0' implementation 'me.grishka.litex:viewpager:1.0.0'
implementation 'me.grishka.litex:viewpager2:1.0.0' implementation 'me.grishka.litex:viewpager2:1.0.0'
implementation 'me.grishka.litex:palette:1.0.0' implementation 'me.grishka.litex:palette:1.0.0'
implementation 'me.grishka.appkit:appkit:1.2.17' implementation 'me.grishka.appkit:appkit:1.3.0'
implementation 'com.google.code.gson:gson:2.8.9' implementation 'com.google.code.gson:gson:2.8.9'
implementation 'org.jsoup:jsoup:1.14.3' implementation 'org.jsoup:jsoup:1.14.3'
implementation 'com.squareup:otto:1.3.8' implementation 'com.squareup:otto:1.3.8'

View File

@@ -33,7 +33,8 @@
android:supportsRtl="true" android:supportsRtl="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:theme="@style/Theme.Mastodon.AutoLightDark" android:theme="@style/Theme.Mastodon.AutoLightDark"
android:largeHeap="true"> android:largeHeap="true"
android:enableOnBackInvokedCallback="true">
<meta-data <meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES" android:name="com.google.mlkit.vision.DEPENDENCIES"

View File

@@ -102,13 +102,12 @@ import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.CustomTransitionsFragment; import me.grishka.appkit.fragments.CustomTransitionsFragment;
import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.imageloader.ViewImageLoader; import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.CubicBezierInterpolator; import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class ComposeFragment extends MastodonToolbarFragment implements OnBackPressedListener, ComposeEditText.SelectionListener, CustomTransitionsFragment{ public class ComposeFragment extends MastodonToolbarFragment implements ComposeEditText.SelectionListener, CustomTransitionsFragment{
private static final int MEDIA_RESULT=717; private static final int MEDIA_RESULT=717;
public static final int IMAGE_DESCRIPTION_RESULT=363; public static final int IMAGE_DESCRIPTION_RESULT=363;
@@ -173,6 +172,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private BackgroundColorSpan overLimitBG; private BackgroundColorSpan overLimitBG;
private ForegroundColorSpan overLimitFG; private ForegroundColorSpan overLimitFG;
private Runnable emojiKeyboardHider;
private Runnable sendingBackButtonBlocker;
private Runnable discardConfirmationCallback=this::confirmDiscardDraftAndFinish;
private boolean prevHadDraft;
public ComposeFragment(){ public ComposeFragment(){
super(R.layout.toolbar_fragment_with_progressbar); super(R.layout.toolbar_fragment_with_progressbar);
} }
@@ -249,6 +253,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
getActivity().dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL)); getActivity().dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
} }
}); });
emojiKeyboardHider=emojiKeyboard::hide;
View view=inflater.inflate(R.layout.fragment_compose, container, false); View view=inflater.inflate(R.layout.fragment_compose, container, false);
mainLayout=view.findViewById(R.id.compose_main_ll); mainLayout=view.findViewById(R.id.compose_main_ll);
@@ -305,6 +310,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
public void onIconChanged(int icon){ public void onIconChanged(int icon){
emojiBtn.setSelected(icon!=PopupKeyboard.ICON_HIDDEN); emojiBtn.setSelected(icon!=PopupKeyboard.ICON_HIDDEN);
updateNavigationBarColor(icon!=PopupKeyboard.ICON_HIDDEN); updateNavigationBarColor(icon!=PopupKeyboard.ICON_HIDDEN);
if(icon!=PopupKeyboard.ICON_HIDDEN)
addBackCallback(emojiKeyboardHider);
else
removeBackCallback(emojiKeyboardHider);
if(autocompleteViewController.getMode()==ComposeAutocompleteViewController.Mode.EMOJIS){ if(autocompleteViewController.getMode()==ComposeAutocompleteViewController.Mode.EMOJIS){
contentView.layout(contentView.getLeft(), contentView.getTop(), contentView.getRight(), contentView.getBottom()); contentView.layout(contentView.getLeft(), contentView.getTop(), contentView.getRight(), contentView.getBottom());
if(icon==PopupKeyboard.ICON_HIDDEN) if(icon==PopupKeyboard.ICON_HIDDEN)
@@ -480,6 +489,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
updateCharCounter(); updateCharCounter();
updateDraftState();
} }
}); });
spoilerEdit.addTextChangedListener(new SimpleTextWatcher(e->updateCharCounter())); spoilerEdit.addTextChangedListener(new SimpleTextWatcher(e->updateCharCounter()));
@@ -621,6 +631,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
if(publishButton==null) if(publishButton==null)
return; return;
publishButton.setEnabled((trimmedCharCount>0 || !mediaViewController.isEmpty()) && charCount<=charLimit && mediaViewController.getNonDoneAttachmentCount()==0 && (pollViewController.isEmpty() || pollViewController.getNonEmptyOptionsCount()>1)); publishButton.setEnabled((trimmedCharCount>0 || !mediaViewController.isEmpty()) && charCount<=charLimit && mediaViewController.getNonDoneAttachmentCount()==0 && (pollViewController.isEmpty() || pollViewController.getNonEmptyOptionsCount()>1));
updateDraftState();
} }
private void onCustomEmojiClick(Emoji emoji){ private void onCustomEmojiClick(Emoji emoji){
@@ -696,6 +707,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
overlayParams.softInputMode=WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED; overlayParams.softInputMode=WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
overlayParams.token=mainEditText.getWindowToken(); overlayParams.token=mainEditText.getWindowToken();
wm.addView(sendingOverlay, overlayParams); wm.addView(sendingOverlay, overlayParams);
addBackCallback(sendingBackButtonBlocker);
publishButton.setEnabled(false); publishButton.setEnabled(false);
V.setVisibilityAnimated(sendProgress, View.VISIBLE); V.setVisibilityAnimated(sendProgress, View.VISIBLE);
@@ -737,6 +749,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
public void onSuccess(Status result){ public void onSuccess(Status result){
wm.removeView(sendingOverlay); wm.removeView(sendingOverlay);
sendingOverlay=null; sendingOverlay=null;
removeBackCallback(sendingBackButtonBlocker);
if(editingStatus==null){ if(editingStatus==null){
E.post(new StatusCreatedEvent(result, accountID)); E.post(new StatusCreatedEvent(result, accountID));
if(replyTo!=null){ if(replyTo!=null){
@@ -769,6 +782,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private void handlePublishError(ErrorResponse error){ private void handlePublishError(ErrorResponse error){
wm.removeView(sendingOverlay); wm.removeView(sendingOverlay);
sendingOverlay=null; sendingOverlay=null;
removeBackCallback(sendingBackButtonBlocker);
V.setVisibilityAnimated(sendProgress, View.GONE); V.setVisibilityAnimated(sendProgress, View.GONE);
publishButton.setEnabled(true); publishButton.setEnabled(true);
if(error instanceof MastodonErrorResponse me){ if(error instanceof MastodonErrorResponse me){
@@ -796,19 +810,16 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
return (mainEditText.length()>0 && !mainEditText.getText().toString().equals(initialText)) || !mediaViewController.isEmpty() || pollFieldsHaveContent; return (mainEditText.length()>0 && !mainEditText.getText().toString().equals(initialText)) || !mediaViewController.isEmpty() || pollFieldsHaveContent;
} }
@Override private void updateDraftState(){
public boolean onBackPressed(){ boolean hasDraft=hasDraft();
if(emojiKeyboard.isVisible()){ if(hasDraft!=prevHadDraft){
emojiKeyboard.hide(); prevHadDraft=hasDraft;
return true; if(hasDraft){
addBackCallback(discardConfirmationCallback);
}else{
removeBackCallback(discardConfirmationCallback);
}
} }
if(hasDraft()){
confirmDiscardDraftAndFinish();
return true;
}
if(sendingOverlay!=null)
return true;
return false;
} }
@Override @Override
@@ -842,7 +853,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private void confirmDiscardDraftAndFinish(){ private void confirmDiscardDraftAndFinish(){
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())
.setTitle(editingStatus==null ? R.string.discard_draft : R.string.discard_changes) .setTitle(editingStatus==null ? R.string.discard_draft : R.string.discard_changes)
.setPositiveButton(R.string.discard, (dialog, which)->Nav.finish(this)) .setPositiveButton(R.string.discard, (dialog, which)->{
removeBackCallback(discardConfirmationCallback);
Nav.finish(this);
})
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show(); .show();
} }

View File

@@ -32,12 +32,11 @@ import java.util.Collections;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.imageloader.ViewImageLoader; import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class ComposeImageDescriptionFragment extends MastodonToolbarFragment implements OnBackPressedListener{ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment{
private static final String TAG="ComposeImageDescription"; private static final String TAG="ComposeImageDescription";
private String accountID, attachmentID; private String accountID, attachmentID;
@@ -138,9 +137,9 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment imp
} }
@Override @Override
public boolean onBackPressed(){ public void onStop(){
super.onStop();
deliverResult(); deliverResult();
return false;
} }
@Override @Override

View File

@@ -42,13 +42,11 @@ import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.fragments.WindowInsetsAwareFragment;
import me.grishka.appkit.utils.CubicBezierInterpolator; import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.FragmentRootLinearLayout; import me.grishka.appkit.views.FragmentRootLinearLayout;
public class CreateListAddMembersFragment extends BaseAccountListFragment implements OnBackPressedListener, AddNewListMembersFragment.Listener{ public class CreateListAddMembersFragment extends BaseAccountListFragment implements AddNewListMembersFragment.Listener{
private FollowList followList; private FollowList followList;
private Button nextButton; private Button nextButton;
private View buttonBar; private View buttonBar;
@@ -59,6 +57,7 @@ public class CreateListAddMembersFragment extends BaseAccountListFragment implem
private WindowInsets lastInsets; private WindowInsets lastInsets;
private boolean dismissingSearchFragment; private boolean dismissingSearchFragment;
private HashSet<String> accountIDsInList=new HashSet<>(); private HashSet<String> accountIDsInList=new HashSet<>();
private Runnable searchFragmentDismisser=this::dismissSearchFragment;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
@@ -156,6 +155,7 @@ public class CreateListAddMembersFragment extends BaseAccountListFragment implem
searchFragmentContainer.animate().translationX(0).alpha(1).setDuration(300).withLayer().setInterpolator(CubicBezierInterpolator.DEFAULT).withEndAction(()->{ searchFragmentContainer.animate().translationX(0).alpha(1).setDuration(300).withLayer().setInterpolator(CubicBezierInterpolator.DEFAULT).withEndAction(()->{
rootView.setVisibility(View.GONE); rootView.setVisibility(View.GONE);
}).start(); }).start();
addBackCallback(searchFragmentDismisser);
return true; return true;
} }
@@ -183,6 +183,7 @@ public class CreateListAddMembersFragment extends BaseAccountListFragment implem
private void dismissSearchFragment(){ private void dismissSearchFragment(){
if(searchFragment==null || dismissingSearchFragment) if(searchFragment==null || dismissingSearchFragment)
return; return;
removeBackCallback(searchFragmentDismisser);
dismissingSearchFragment=true; dismissingSearchFragment=true;
rootView.setVisibility(View.VISIBLE); rootView.setVisibility(View.VISIBLE);
searchFragmentContainer.animate().translationX(V.dp(100)).alpha(0).setDuration(200).withLayer().setInterpolator(CubicBezierInterpolator.DEFAULT).withEndAction(()->{ searchFragmentContainer.animate().translationX(V.dp(100)).alpha(0).setDuration(200).withLayer().setInterpolator(CubicBezierInterpolator.DEFAULT).withEndAction(()->{
@@ -201,15 +202,6 @@ public class CreateListAddMembersFragment extends BaseAccountListFragment implem
Nav.finish(this); Nav.finish(this);
} }
@Override
public boolean onBackPressed(){
if(searchFragment!=null){
dismissSearchFragment();
return true;
}
return false;
}
@Override @Override
public boolean isAccountInList(AccountViewModel account){ public boolean isAccountInList(AccountViewModel account){
return accountIDsInList.contains(account.account.id); return accountIDsInList.contains(account.account.id);

View File

@@ -30,8 +30,8 @@ import org.joinmastodon.android.fragments.onboarding.OnboardingFollowSuggestions
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Notification; import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.PaginatedResponse; import org.joinmastodon.android.model.PaginatedResponse;
import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet;
import org.joinmastodon.android.ui.OutlineProviders; import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.TabBar; import org.joinmastodon.android.ui.views.TabBar;
import org.joinmastodon.android.utils.ObjectIdComparator; import org.joinmastodon.android.utils.ObjectIdComparator;
@@ -48,13 +48,12 @@ import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.AppKitFragment; import me.grishka.appkit.fragments.AppKitFragment;
import me.grishka.appkit.fragments.LoaderFragment; import me.grishka.appkit.fragments.LoaderFragment;
import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.imageloader.ViewImageLoader; import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.FragmentRootLinearLayout; import me.grishka.appkit.views.FragmentRootLinearLayout;
public class HomeFragment extends AppKitFragment implements OnBackPressedListener{ public class HomeFragment extends AppKitFragment{
private FragmentRootLinearLayout content; private FragmentRootLinearLayout content;
private HomeTimelineFragment homeTimelineFragment; private HomeTimelineFragment homeTimelineFragment;
private NotificationsListFragment notificationsFragment; private NotificationsListFragment notificationsFragment;
@@ -272,15 +271,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
return false; return false;
} }
@Override
public boolean onBackPressed(){
if(currentTab==R.id.tab_profile)
return profileFragment.onBackPressed();
if(currentTab==R.id.tab_search)
return searchFragment.onBackPressed();
return false;
}
@Override @Override
public void onSaveInstanceState(Bundle outState){ public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);

View File

@@ -44,12 +44,11 @@ import java.util.stream.Collectors;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.utils.CubicBezierInterpolator; import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.FragmentRootLinearLayout; import me.grishka.appkit.views.FragmentRootLinearLayout;
public class ListMembersFragment extends PaginatedAccountListFragment implements AddNewListMembersFragment.Listener, OnBackPressedListener{ public class ListMembersFragment extends PaginatedAccountListFragment implements AddNewListMembersFragment.Listener{
private ImageButton fab; private ImageButton fab;
private FollowList followList; private FollowList followList;
private boolean inSelectionMode; private boolean inSelectionMode;
@@ -63,6 +62,8 @@ public class ListMembersFragment extends PaginatedAccountListFragment implements
private WindowInsets lastInsets; private WindowInsets lastInsets;
private HashSet<String> accountIDsInList=new HashSet<>(); private HashSet<String> accountIDsInList=new HashSet<>();
private boolean dismissingSearchFragment; private boolean dismissingSearchFragment;
private Runnable searchFragmentDismisser=this::dismissSearchFragment;;
private Runnable actionModeDismisser=()->actionMode.finish();
public ListMembersFragment(){ public ListMembersFragment(){
setListLayoutId(R.layout.recycler_fragment_with_fab); setListLayoutId(R.layout.recycler_fragment_with_fab);
@@ -214,6 +215,7 @@ public class ListMembersFragment extends PaginatedAccountListFragment implements
searchFragmentContainer.animate().translationX(0).alpha(1).setDuration(300).withLayer().setInterpolator(CubicBezierInterpolator.DEFAULT).withEndAction(()->{ searchFragmentContainer.animate().translationX(0).alpha(1).setDuration(300).withLayer().setInterpolator(CubicBezierInterpolator.DEFAULT).withEndAction(()->{
rootView.setVisibility(View.GONE); rootView.setVisibility(View.GONE);
}).start(); }).start();
addBackCallback(searchFragmentDismisser);
} }
private void onItemClick(AccountViewHolder holder){ private void onItemClick(AccountViewHolder holder){
@@ -293,9 +295,11 @@ public class ListMembersFragment extends PaginatedAccountListFragment implements
selectedAccounts.clear(); selectedAccounts.clear();
updateItemsForSelectionModeTransition(); updateItemsForSelectionModeTransition();
V.setVisibilityAnimated(fab, View.VISIBLE); V.setVisibilityAnimated(fab, View.VISIBLE);
removeBackCallback(actionModeDismisser);
} }
}); });
updateActionModeTitle(); updateActionModeTitle();
addBackCallback(actionModeDismisser);
} }
private void updateActionModeTitle(){ private void updateActionModeTitle(){
@@ -371,15 +375,6 @@ public class ListMembersFragment extends PaginatedAccountListFragment implements
removeAccounts(Set.of(account.account.id), onDone); removeAccounts(Set.of(account.account.id), onDone);
} }
@Override
public boolean onBackPressed(){
if(searchFragment!=null){
dismissSearchFragment();
return true;
}
return false;
}
private void dismissSearchFragment(){ private void dismissSearchFragment(){
if(searchFragment==null || dismissingSearchFragment) if(searchFragment==null || dismissingSearchFragment)
return; return;
@@ -393,6 +388,7 @@ public class ListMembersFragment extends PaginatedAccountListFragment implements
searchFragment=null; searchFragment=null;
dismissingSearchFragment=false; dismissingSearchFragment=false;
}).start(); }).start();
removeBackCallback(searchFragmentDismisser);
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0); getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0);
} }

View File

@@ -100,14 +100,13 @@ import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.fragments.BaseRecyclerFragment; import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.fragments.LoaderFragment; import me.grishka.appkit.fragments.LoaderFragment;
import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.imageloader.ViewImageLoader; import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.CubicBezierInterpolator; import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.FragmentRootLinearLayout; import me.grishka.appkit.views.FragmentRootLinearLayout;
public class ProfileFragment extends LoaderFragment implements OnBackPressedListener, ScrollableToTop{ public class ProfileFragment extends LoaderFragment implements ScrollableToTop{
private static final int AVATAR_RESULT=722; private static final int AVATAR_RESULT=722;
private static final int COVER_RESULT=343; private static final int COVER_RESULT=343;
@@ -158,6 +157,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private Animator tabBarColorAnim; private Animator tabBarColorAnim;
private MenuItem editSaveMenuItem; private MenuItem editSaveMenuItem;
private boolean savingEdits; private boolean savingEdits;
private Runnable editModeBackCallback=this::onEditModeBackCallback;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
@@ -983,12 +983,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
refreshLayout.setEnabled(false); refreshLayout.setEnabled(false);
editDirty=false; editDirty=false;
V.setVisibilityAnimated(fab, View.GONE); V.setVisibilityAnimated(fab, View.GONE);
addBackCallback(editModeBackCallback);
} }
private void exitEditMode(){ private void exitEditMode(){
if(!isInEditMode) if(!isInEditMode)
throw new IllegalStateException(); throw new IllegalStateException();
isInEditMode=false; isInEditMode=false;
removeBackCallback(editModeBackCallback);
invalidateOptionsMenu(); invalidateOptionsMenu();
actionButton.setText(R.string.edit_profile); actionButton.setText(R.string.edit_profile);
@@ -1098,23 +1100,18 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
updateRelationship(); updateRelationship();
} }
@Override private void onEditModeBackCallback(){
public boolean onBackPressed(){ if(savingEdits)
if(isInEditMode){ return;
if(savingEdits) if(editDirty || aboutFragment.isEditDirty()){
return true; new M3AlertDialogBuilder(getActivity())
if(editDirty || aboutFragment.isEditDirty()){ .setTitle(R.string.discard_changes)
new M3AlertDialogBuilder(getActivity()) .setPositiveButton(R.string.discard, (dlg, btn)->exitEditMode())
.setTitle(R.string.discard_changes) .setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.discard, (dlg, btn)->exitEditMode()) .show();
.setNegativeButton(R.string.cancel, null) }else{
.show(); exitEditMode();
}else{
exitEditMode();
}
return true;
} }
return false;
} }
private List<Attachment> createFakeAttachments(String url, Drawable drawable){ private List<Attachment> createFakeAttachments(String url, Drawable drawable){

View File

@@ -53,6 +53,8 @@ import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
import com.google.zxing.BarcodeFormat; import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType; import com.google.zxing.EncodeHintType;
@@ -144,12 +146,16 @@ public class ProfileQrCodeFragment extends AppKitFragment{
if(!isTablet){ if(!isTablet){
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
} }
dlg.setOnKeyListener((dialog, keyCode, event)->{ if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.TIRAMISU){
if(keyCode==KeyEvent.KEYCODE_BACK && event.getAction()==KeyEvent.ACTION_DOWN){ dlg.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, this::dismiss);
dismiss(); }else{
} dlg.setOnKeyListener((dialog, keyCode, event)->{
return true; if(keyCode==KeyEvent.KEYCODE_BACK && event.getAction()==KeyEvent.ACTION_DOWN){
}); dismiss();
}
return true;
});
}
} }
@Override @Override

View File

@@ -36,10 +36,9 @@ import androidx.viewpager2.widget.ViewPager2;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.fragments.AppKitFragment; import me.grishka.appkit.fragments.AppKitFragment;
import me.grishka.appkit.fragments.BaseRecyclerFragment; import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, OnBackPressedListener{ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop{
private static final int QUERY_RESULT=937; private static final int QUERY_RESULT=937;
private static final int SCAN_RESULT=456; private static final int SCAN_RESULT=456;
@@ -62,6 +61,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
private String accountID; private String accountID;
private String currentQuery; private String currentQuery;
private Intent scannerIntent; private Intent scannerIntent;
private Runnable searchExitCallback=this::exitSearch;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
@@ -232,6 +232,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
searchBack.setEnabled(true); searchBack.setEnabled(true);
searchBack.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); searchBack.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
tabsDivider.setVisibility(View.GONE); tabsDivider.setVisibility(View.GONE);
addBackCallback(searchExitCallback);
} }
} }
@@ -248,6 +249,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
searchBack.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); searchBack.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
tabsDivider.setVisibility(View.VISIBLE); tabsDivider.setVisibility(View.VISIBLE);
currentQuery=null; currentQuery=null;
removeBackCallback(searchExitCallback);
} }
private Fragment getFragmentForPage(int page){ private Fragment getFragmentForPage(int page){
@@ -260,15 +262,6 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
}; };
} }
@Override
public boolean onBackPressed(){
if(searchActive){
exitSearch();
return true;
}
return false;
}
@Override @Override
public void onFragmentResult(int reqCode, boolean success, Bundle result){ public void onFragmentResult(int reqCode, boolean success, Bundle result){
if(reqCode==QUERY_RESULT && success){ if(reqCode==QUERY_RESULT && success){

View File

@@ -57,14 +57,13 @@ import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.fragments.CustomTransitionsFragment; import me.grishka.appkit.fragments.CustomTransitionsFragment;
import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter; import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.utils.MergeRecyclerAdapter; import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView; import me.grishka.appkit.views.UsableRecyclerView;
public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultViewModel> implements CustomTransitionsFragment, OnBackPressedListener{ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultViewModel> implements CustomTransitionsFragment{
private static final Pattern HASHTAG_REGEX=Pattern.compile("^(\\w*[a-zA-Z·]\\w*)$", Pattern.CASE_INSENSITIVE); private static final Pattern HASHTAG_REGEX=Pattern.compile("^(\\w*[a-zA-Z·]\\w*)$", Pattern.CASE_INSENSITIVE);
private static final Pattern USERNAME_REGEX=Pattern.compile("^@?([a-z0-9_-]+)(@[^\\s]+)?$", Pattern.CASE_INSENSITIVE); private static final Pattern USERNAME_REGEX=Pattern.compile("^@?([a-z0-9_-]+)(@[^\\s]+)?$", Pattern.CASE_INSENSITIVE);
@@ -371,6 +370,11 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
container.invalidateOutline(); container.invalidateOutline();
navigationIcon.invalidateSelf(); navigationIcon.invalidateSelf();
}); });
if(!enter){
String initialQuery=getArguments().getString("query");
searchViewHelper.setQuery(TextUtils.isEmpty(initialQuery) ? "" : initialQuery);
currentQuery=initialQuery;
}
return set; return set;
} }
@@ -437,14 +441,6 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
Nav.finish(this); Nav.finish(this);
} }
@Override
public boolean onBackPressed(){
String initialQuery=getArguments().getString("query");
searchViewHelper.setQuery(TextUtils.isEmpty(initialQuery) ? "" : initialQuery);
currentQuery=initialQuery;
return false;
}
private static class AnimatableOutlineProvider extends ViewOutlineProvider{ private static class AnimatableOutlineProvider extends ViewOutlineProvider{
private float boundsFraction, radius; private float boundsFraction, radius;
private final Rect boundsFrom, boundsTo; private final Rect boundsFrom, boundsTo;

View File

@@ -58,12 +58,11 @@ import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.utils.BindableViewHolder; import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView; import me.grishka.appkit.views.UsableRecyclerView;
public class InstanceCatalogSignupFragment extends InstanceCatalogFragment implements OnBackPressedListener{ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
private View topBar; private View topBar;
private List<String> languages=Collections.emptyList(); private List<String> languages=Collections.emptyList();
@@ -84,6 +83,8 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
private String inviteCode, inviteCodeHost; private String inviteCode, inviteCodeHost;
private AlertDialog currentInviteLinkAlert; private AlertDialog currentInviteLinkAlert;
private Runnable exitQueryModeCallback=()->setSearchQueryMode(false);
public InstanceCatalogSignupFragment(){ public InstanceCatalogSignupFragment(){
super(R.layout.fragment_onboarding_common, 10); super(R.layout.fragment_onboarding_common, 10);
} }
@@ -582,19 +583,13 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom())); super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
} }
@Override
public boolean onBackPressed(){
if(searchQueryMode){
setSearchQueryMode(false);
return true;
}
return false;
}
private void setSearchQueryMode(boolean enabled){ private void setSearchQueryMode(boolean enabled){
if(searchQueryMode==enabled)
return;
searchQueryMode=enabled; searchQueryMode=enabled;
RelativeLayout.LayoutParams lp=(RelativeLayout.LayoutParams) searchEdit.getLayoutParams(); RelativeLayout.LayoutParams lp=(RelativeLayout.LayoutParams) searchEdit.getLayoutParams();
if(searchQueryMode){ if(searchQueryMode){
addBackCallback(exitQueryModeCallback);
filtersScroll.setVisibility(View.GONE); filtersScroll.setVisibility(View.GONE);
lp.removeRule(RelativeLayout.END_OF); lp.removeRule(RelativeLayout.END_OF);
backBtn.setScaleX(0.83333333f); backBtn.setScaleX(0.83333333f);
@@ -602,6 +597,7 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
backBtn.setTranslationX(V.dp(8)); backBtn.setTranslationX(V.dp(8));
searchEdit.setCompoundDrawableTintList(ColorStateList.valueOf(0)); searchEdit.setCompoundDrawableTintList(ColorStateList.valueOf(0));
}else{ }else{
removeBackCallback(exitQueryModeCallback);
filtersScroll.setVisibility(View.VISIBLE); filtersScroll.setVisibility(View.VISIBLE);
focusThing.requestFocus(); focusThing.requestFocus();
searchEdit.setText(""); searchEdit.setText("");

View File

@@ -24,6 +24,7 @@ import org.joinmastodon.android.model.FilterKeyword;
import org.joinmastodon.android.model.viewmodel.CheckableListItem; import org.joinmastodon.android.model.viewmodel.CheckableListItem;
import org.joinmastodon.android.model.viewmodel.ListItem; import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout; import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout;
import org.parceler.Parcels; import org.parceler.Parcels;
@@ -44,11 +45,10 @@ import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.utils.MergeRecyclerAdapter; import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter; import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
public class EditFilterFragment extends BaseSettingsFragment<Void> implements OnBackPressedListener{ public class EditFilterFragment extends BaseSettingsFragment<Void>{
private static final int WORDS_RESULT=370; private static final int WORDS_RESULT=370;
private static final int CONTEXT_RESULT=651; private static final int CONTEXT_RESULT=651;
@@ -63,6 +63,13 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
private ArrayList<String> deletedWordIDs=new ArrayList<>(); private ArrayList<String> deletedWordIDs=new ArrayList<>();
private EnumSet<FilterContext> context=EnumSet.allOf(FilterContext.class); private EnumSet<FilterContext> context=EnumSet.allOf(FilterContext.class);
private boolean dirty; private boolean dirty;
private boolean wasDirty;
private Runnable confirmCallback=()->{
if(isDirty()){
UiUtils.showConfirmationAlert(getActivity(), R.string.discard_changes, 0, R.string.discard, ()->Nav.finish(this));
}
};
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
@@ -101,6 +108,7 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
titleEditLayout.updateHint(); titleEditLayout.updateHint();
if(filter!=null) if(filter!=null)
titleEdit.setText(filter.title); titleEdit.setText(filter.title);
titleEdit.addTextChangedListener(new SimpleTextWatcher(e->updateBackCallback()));
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter(); MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
adapter.addAdapter(new SingleViewRecyclerAdapter(titleEditLayout)); adapter.addAdapter(new SingleViewRecyclerAdapter(titleEditLayout));
@@ -158,6 +166,7 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
} }
a.dismiss(); a.dismiss();
} }
updateBackCallback();
}) })
.show(); .show();
alert.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); alert.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
@@ -309,6 +318,7 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
} }
deletedWordIDs.addAll(result.getStringArrayList("deleted")); deletedWordIDs.addAll(result.getStringArrayList("deleted"));
} }
updateBackCallback();
} }
} }
@@ -317,11 +327,19 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
} }
@Override @Override
public boolean onBackPressed(){ protected void toggleCheckableItem(ListItem<?> item){
if(isDirty()){ super.toggleCheckableItem(item);
UiUtils.showConfirmationAlert(getActivity(), R.string.discard_changes, 0, R.string.discard, ()->Nav.finish(this)); updateBackCallback();
return true; }
private void updateBackCallback(){
boolean dirty=isDirty();
if(dirty!=wasDirty){
wasDirty=dirty;
if(dirty)
addBackCallback(confirmCallback);
else
removeBackCallback(confirmCallback);
} }
return false;
} }
} }

View File

@@ -11,9 +11,7 @@ import java.util.Arrays;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import me.grishka.appkit.fragments.OnBackPressedListener; public class FilterContextFragment extends BaseSettingsFragment<FilterContext>{
public class FilterContextFragment extends BaseSettingsFragment<FilterContext> implements OnBackPressedListener{
private EnumSet<FilterContext> context; private EnumSet<FilterContext> context;
@Override @Override
@@ -33,7 +31,8 @@ public class FilterContextFragment extends BaseSettingsFragment<FilterContext> i
protected void doLoadData(int offset, int count){} protected void doLoadData(int offset, int count){}
@Override @Override
public boolean onBackPressed(){ public void onStop(){
super.onStop();
context=EnumSet.noneOf(FilterContext.class); context=EnumSet.noneOf(FilterContext.class);
for(ListItem<FilterContext> item:data){ for(ListItem<FilterContext> item:data){
if(((CheckableListItem<FilterContext>) item).checked) if(((CheckableListItem<FilterContext>) item).checked)
@@ -42,6 +41,5 @@ public class FilterContextFragment extends BaseSettingsFragment<FilterContext> i
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putSerializable("context", context); args.putSerializable("context", context);
setResult(true, args); setResult(true, args);
return false;
} }
} }

View File

@@ -1,7 +1,6 @@
package org.joinmastodon.android.fragments.settings; package org.joinmastodon.android.fragments.settings;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
import android.text.InputType; import android.text.InputType;
@@ -11,11 +10,9 @@ import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets; import android.view.WindowInsets;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageButton;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.model.FilterKeyword; import org.joinmastodon.android.model.FilterKeyword;
@@ -33,15 +30,15 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> implements OnBackPressedListener{ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword>{
private Button fab; private Button fab;
private ActionMode actionMode; private ActionMode actionMode;
private ArrayList<ListItem<FilterKeyword>> selectedItems=new ArrayList<>(); private ArrayList<ListItem<FilterKeyword>> selectedItems=new ArrayList<>();
private ArrayList<String> deletedItemIDs=new ArrayList<>(); private ArrayList<String> deletedItemIDs=new ArrayList<>();
private MenuItem deleteItem; private MenuItem deleteItem;
private Runnable actionModeDismisser=()->actionMode.finish();
public FilterWordsFragment(){ public FilterWordsFragment(){
setListLayoutId(R.layout.recycler_fragment_with_text_fab); setListLayoutId(R.layout.recycler_fragment_with_text_fab);
@@ -80,12 +77,12 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
} }
@Override @Override
public boolean onBackPressed(){ public void onStop(){
super.onStop();
Bundle result=new Bundle(); Bundle result=new Bundle();
result.putParcelableArrayList("words", (ArrayList<? extends Parcelable>) data.stream().map(i->i.parentObject).map(Parcels::wrap).collect(Collectors.toCollection(ArrayList::new))); result.putParcelableArrayList("words", (ArrayList<? extends Parcelable>) data.stream().map(i->i.parentObject).map(Parcels::wrap).collect(Collectors.toCollection(ArrayList::new)));
result.putStringArrayList("deleted", deletedItemIDs); result.putStringArrayList("deleted", deletedItemIDs);
setResult(true, result); setResult(true, result);
return false;
} }
@Override @Override
@@ -259,6 +256,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
} }
itemsAdapter.notifyItemRangeChanged(0, data.size()); itemsAdapter.notifyItemRangeChanged(0, data.size());
updateActionModeTitle(); updateActionModeTitle();
addBackCallback(actionModeDismisser);
} }
private void leaveSelectionMode(boolean fromActionMode){ private void leaveSelectionMode(boolean fromActionMode){
@@ -280,6 +278,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
data.set(i, newItem); data.set(i, newItem);
} }
itemsAdapter.notifyItemRangeChanged(0, data.size()); itemsAdapter.notifyItemRangeChanged(0, data.size());
removeBackCallback(actionModeDismisser);
} }
private void updateActionModeTitle(){ private void updateActionModeTitle(){

View File

@@ -54,6 +54,7 @@ import android.widget.SeekBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import android.widget.Toolbar; import android.widget.Toolbar;
import android.window.OnBackInvokedDispatcher;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController; import org.joinmastodon.android.api.MastodonAPIController;
@@ -169,7 +170,7 @@ public class PhotoViewer implements ZoomPanView.Listener{
windowView=new FrameLayout(activity){ windowView=new FrameLayout(activity){
@Override @Override
public boolean dispatchKeyEvent(KeyEvent event){ public boolean dispatchKeyEvent(KeyEvent event){
if(event.getKeyCode()==KeyEvent.KEYCODE_BACK){ if(Build.VERSION.SDK_INT<Build.VERSION_CODES.TIRAMISU && event.getKeyCode()==KeyEvent.KEYCODE_BACK){
if(event.getAction()==KeyEvent.ACTION_DOWN){ if(event.getAction()==KeyEvent.ACTION_DOWN){
onStartSwipeToDismissTransition(0f); onStartSwipeToDismissTransition(0f);
} }
@@ -257,6 +258,10 @@ public class PhotoViewer implements ZoomPanView.Listener{
wlp.layoutInDisplayCutoutMode=Build.VERSION.SDK_INT>=30 ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS : WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; wlp.layoutInDisplayCutoutMode=Build.VERSION.SDK_INT>=30 ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS : WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
windowView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); windowView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
wm.addView(windowView, wlp); wm.addView(windowView, wlp);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.TIRAMISU){
// TODO make use of the progress callback for nicer animation
windowView.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, ()->onStartSwipeToDismissTransition(0));
}
windowView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){ windowView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@Override @Override

View File

@@ -10,6 +10,7 @@ import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.PixelFormat; import android.graphics.PixelFormat;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Build;
import android.view.Gravity; import android.view.Gravity;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
@@ -19,6 +20,7 @@ import android.view.ViewTreeObserver;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.Toolbar; import android.widget.Toolbar;
import android.window.OnBackInvokedDispatcher;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.ui.OutlineProviders; import org.joinmastodon.android.ui.OutlineProviders;
@@ -83,6 +85,15 @@ public class ToolbarDropdownMenuController{
.withLayer() .withLayer()
.start(); .start();
controllerStack.add(initialSubmenu); controllerStack.add(initialSubmenu);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.TIRAMISU){
windowView.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, ()->{
if(controllerStack.size()>1)
popSubmenuController();
else
dismiss();
});
}
} }
public void dismiss(){ public void dismiss(){
@@ -243,7 +254,7 @@ public class ToolbarDropdownMenuController{
@Override @Override
public boolean dispatchKeyEvent(KeyEvent event){ public boolean dispatchKeyEvent(KeyEvent event){
if(event.getKeyCode()==KeyEvent.KEYCODE_BACK){ if(Build.VERSION.SDK_INT<Build.VERSION_CODES.TIRAMISU && event.getKeyCode()==KeyEvent.KEYCODE_BACK){
if(event.getAction()==KeyEvent.ACTION_DOWN){ if(event.getAction()==KeyEvent.ACTION_DOWN){
if(controllerStack.size()>1) if(controllerStack.size()>1)
popSubmenuController(); popSubmenuController();